CSSE1001 Introduction to Software Engineering Assignment 1

Hello, if you have any need, please feel free to consult us, this is my wechat: wx91due

Battleships

Assignment 1

Semester 2, 2024

CSSE1001

Due date: 23rd August 2024, 15:00 GMT+10

1 Introduction

In Assignment 1 you will implement a text-based version of the game Battleships. This two player game consists of two phases. Firstly, players secretly place some number of ships onto their own board which is hidden from their opponent. Then, players take turns guessing positions on their opponents board in an attempt to hit parts of their opponents ships. A player wins the game once they have hit all positions occupied by their opponents ships. You can find instructions for how this game is traditionally played here. Section 3 provides an overview of gameplay for the CSSE1001 assignment. Where the original behaviour of the game conflicts with this document, you should implement your program according to this document.

2 Getting Started

Download a1.zip from Blackboard — this archive contains the necessary files to start this as-signment. Once extracted, the a1.zip archive will provide the following files:

a1.py This is the only file you will submit and is where you write your code. Do not make changes to any other files.

support.py Do not modify or submit this file, it contains pre-defined constants to use in your assignment. In addition to these, you are encouraged to create your own constants in a1.py where possible.

gameplay/ This folder contains a number of example outputs generated by playing the game using a fully-functional completed solution to this assignment. The purpose of the files in this folder is to help you understand how the game works, and how output should be formatted.

3 Gameplay

This section provides a high-level overview of gameplay, and is intended to provide you with an idea of the behaviour of a fully completed assignment 1. Do not simply implement the behaviour described here using your own structure; you must implement the individual functions described in Section 4. Prompts and outputs must match exactly what is expected; see Section 4, the example games in the gameplay/ folder provided with this assignment, and the Gradescope tests for clarification on required prompts and outputs.

3.1 Setup Phase

At the beginning of the game the following steps should occur:

1. Users should be prompted for the board size to use (you may assume the user will enter a number between 3 and 9 inclusive at this prompt).

2. Users should be prompted to enter a comma-separated string of whole numbers that will dictate the ship sizes both players will use (you may assume the user will enter a valid comma-separated string of whole numbers at this prompt).

3. Each user should be repeatedly prompted in turn to set up their boards by placing ships of the required sizes. If a user enters an invalid ship at any point they should be reprompted.

Once both players have placed their ships, they move to the turn-taking phase.

3.2 Turn-taking Phase

Players alternate turns, starting with player 1. During a single turn, the following steps should occur:

1. If a player has won, the following information should be displayed in order: a message showing that the game is over, a message stating which player won, and the final game state with all ships depicted. The program should then terminate gracefully. Steps 2-7 would therefore not execute if a player has won.

2. A message should be displayed to show that a new turn has begun.

3. The current game state should be displayed, without ships shown.

4. A message should be displayed to indicate whose turn it is.

5. The player should be repeatedly prompted to make an attack until they enter a valid attack.

6. The specified valid attack should be made.

7. Player turn should alternate.

4 Implementation

This assessment has been designed to allow you to practice what you have learnt in this course so far. As such, you must only use the functions, operators and data types presented to you in lectures up to (and including) week 4. Namely, the following techniques are permitted for use in this assignment:

• Functions (def, return)

• Basic control structures (for, while, if/elif/else, break)

• Primitive data types (int, float, str, bool)

• Variable assignment (=)

• Arithmetic (+,-,*,\,\\, ,% etc.)

• Comparison (==,<=,>=,<,>,!= etc.)

• Basic Logic (not, and, or etc.)

• lists and tuples

• dictionaries

• range and enumerate

• input and print

Using any functions, operators and data types that have not been presented to you in lectures up to (and including) week 4 will result in a deduction of up to 100% of your mark.

A pinned thread will be maintained on the Edstem discussion board with a list of permitted techniques. If you would like clarification on whether you are permitted to use a specific technique, please first check this list. If the technique has not been mentioned, please ask about permission to use the technique in a comment on this pinned thread.

Required Functions

This section outlines the functions you are required to implement in your solution (in a1.py only). You are awarded marks for the number of tests passed by your functions when they are tested independently of one another. Thus an incomplete assignment with some working functions may well be awarded more marks than a complete assignment with faulty functions. Your pro-gram must operate exactly as specified. In particular, your program’s output must match exactly with the expected output. Your program will be marked automatically so minor differences in output (such as whitespace or casing) will cause tests to fail resulting in a zero mark for that test.

Each function is accompanied with some examples for usage to help you start your own testing. You should also test your functions with other values to ensure they operate according to the descriptions.

The following functions must be implemented in a1.py. They have been listed in a rough order of increasing difficulty. This does not mean that earlier functions are necessarily worth less marks than later functions. It is highly recommended that you do not begin work on a later function until each of the preceding functions can at least behave as per the shown examples. You may implement additional functions if you think they will help with your logic or make your code easier to understand.

4.1 num_hours() -> float

This function should return the number of hours (as a float) that you have spent on the assignment. The purpose of this function is to enable you to verify that you understand how to submit to Gradescope as soon as possible (see Section 5.3), and to allow us to gauge difficulty level of this assignment in order to provide the best possible assistance. You will not be marked differently for spending more or less time on the assignment. If the Gradescope tests have been released, you must ensure this function passes the relevant test before seeking help regarding Gradescope issues for any of the later functions. The test will only ensure you have created a function with the correct name and number of arguments, which returns a float and does not prompt for input. You will not be marked incorrect for returning the ‘wrong’ number of hours.

4.2 create_empty_board(board_size: int) -> list[str]

Generates a new empty board. A board is represented by a list of strings, where each string in the list represents a row on the board. The first string in the list represents top-most row and the last string in the list represents the bottom-most row. The first character of each string represents the square at the left-most column of that row and the last character of the string represents the right-most column of the row. The board should have board size rows and board size columns. A pre-condition to this function is that board size must be between 3 and 9 inclusive.

>>> create_empty_board(4)
['~~~~', '~~~~', '~~~~', '~~~~']
>>> create_empty_board(6)
['~~~~~~', '~~~~~~', '~~~~~~', '~~~~~~', '~~~~~~', '~~~~~~']

4.3 get_square(board: list[str], position: tuple[int,int]) -> str

Returns the character present at the given (row, column) position within the given board. A precondition to this function is that position must exist on board.

>>> board = ['~~~!', '!O~~', '~~~~', '~~~~']
>>> get_square(board, (1, 0))
'!'
>>> get_square(board, (0, 1))
'~'
>>> get_square(['abc', 'de'], (1, 1))
'e'

4.4 change_square(board: list[str], position: tuple[int, int], new_square: str) -> None

Replaces the character at the given (row, column) position with new square. Note that this function should mutate the given board and not return a new board state. A precondition to this function is that position must exist on board.

>>> board = create_empty_board(5)
>>> board
['~~~~~', '~~~~~', '~~~~~', '~~~~~', '~~~~~']
>>> change_square(board, (2, 3), MISS_SQUARE)
>>> board
['~~~~~', '~~~~~', '~~~!~', '~~~~~', '~~~~~']
>>> board = ['abc', 'de']
>>> change_square(board, (1, 1), 'h')
>>> board
['abc', 'dh']

4.5 coordinate to position(coordinate: str) -> tuple[int, int]

Returns the (row, column) position tuple corresponding to the given coordinate. A coordinate is a string entered by a user to represent a (row, column) position on the board. Coordinates consist of exactly two characters, where the first character is an uppercase letter representing the column and the second character is a digit representing the row. Letters that appear earlier in the alphabet represent lower column numbers (e.g. ’A’ represents column 0, ’B’ represents column 1, etc.). The digit character representing the row number is 1-indexed, whereas the actual row it represents is 0-indexed (e.g. ’1’ represents row 0, ’2’ represents row 1, etc.). A precondition to this function is that the coordinate must consist of exactly two characters, where the first character is an uppercase letter from ‘A’ to ‘I’ and the second character is a single digit character.

>>> coordinate_to_position('A1')
(0, 0)
>>> coordinate_to_position('B3')
(2, 1)
>>> coordinate_to_position('G8')
(7, 6)

4.6 can_place_ship(board: list[str], ship: list[tuple[int,int]]) -> bool

Returns True if the ship can be placed on the board, and False otherwise. A ship can be placed on a board if and only if all squares it intends to occupy are empty. A precondition to this function is that all positions in ship must exist on board.

>>> board = ['~~~~', 'OO~~', '~~~~', '~~~~']
>>> can_place_ship(board, [(0, 0), (0, 1)])
True
>>> board
['~~~~', 'OO~~', '~~~~', '~~~~']
>>> can_place_ship(board, [(0, 0), (1, 0)])
False

4.7 place_ship(board: list[str], ship: list[tuple[int,int]]) -> None

Places the ship on the board by changing all positions from the ship to the ACTIVE_SHIP_SQUARE. A precondition to this function is that the ship should be able to be placed on the board according to can_place_ship.

>>> board = ['~~~~', 'OO~~', '~~~~', '~~~~']
>>> place_ship(board, [(0, 0), (0, 1)])
>>> board
['OO~~', 'OO~~', '~~~~', '~~~~']

4.8 attack(board: list[str], position: tuple[int, int]) -> None

Attempts to attack the cell at the (row, column) position within the board. If the position contains an active ship square, the attack hits and converts this square to a dead ship square. If the position contains an empty square, then the attack misses and converts this square to a miss square. If the position contains anything else, this function should not change board. A precondition to this function is that position must exist on board.

>>> board = ['O~~~', 'O~~~', '~~~~', '~~~~']
>>> attack(board, (0, 0))
>>> board
['X~~~', 'O~~~', '~~~~', '~~~~']
>>> attack(board, (0, 1))
>>> board
['X!~~', 'O~~~', '~~~~', '~~~~']
>>> attack(board, (0, 0))
5>>> board
['X!~~', 'O~~~', '~~~~', '~~~~']

4.9 display_board(board: list[str], show_ships: bool) -> None

Prints the board in a human-readable format. If show_ships is False, all active ship squares should be displayed as empty squares. If show_ships is True, all squares should be shown as they are (including active ship squares).

>>> board = create_empty_board(4)
>>> display_board(board, True)
/ABCD
1|~~~~
2|~~~~
3|~~~~
4|~~~~
>>> place_ship(board, [(0, 0), (1, 0)])
>>> attack(board, (0, 0))
>>> display_board(board, False)
/ABCD
1|X~~~
2|~~~~
3|~~~~
4|~~~~
>>> display_board(board, True)
/ABCD
1|X~~~
2|O~~~
3|~~~~
4|~~~~
>>> board = create_empty_board(8)
>>> display_board(board, False)
/ABCDEFGH
1|~~~~~~~~
2|~~~~~~~~
3|~~~~~~~~
4|~~~~~~~~
5|~~~~~~~~
6|~~~~~~~~
7|~~~~~~~~
8|~~~~~~~~

4.10 get_player_hp(board: list[str]) -> int

Returns the players current HP, which is equal to the total number of active ship squares remaining in their board.

>>> board = ['O~~~', 'O~~~', '~~~~', '~~~~']
>>> get_player_hp(board)
2
>>> attack(board, (0, 0))
>>> get_player_hp(board)
1

4.11 display_game(p1_board: list[str], p2_board: list[str], show_ships: bool) -> None

Prints the overall game state. The game state consists of player 1’s health and board state, followed by player 2’s health and board state. show_ships determines whether ships should be shown when displaying the players’ boards.

>>> player_1_board = ['O~~~', 'O~~~', '~~~~', '~~~~']
>>> player_2_board = ['~~~~', '~~~~', 'OO~~', '~~~~']
>>> attack(player_1_board, (0, 0))
>>> attack(player_2_board, (0, 0))
>>> display_game(player_1_board, player_2_board, True)
PLAYER 1: 1 life remaining
/ABCD
1|X~~~
2|O~~~
3|~~~~
4|~~~~
PLAYER 2: 2 lives remaining
/ABCD
1|!~~~
2|~~~~
3|OO~~
4|~~~~
>>> display_game(player_1_board, player_2_board, False)
PLAYER 1: 1 life remaining
/ABCD
1|X~~~
2|~~~~
3|~~~~
4|~~~~
PLAYER 2: 2 lives remaining
/ABCD
1|!~~~
2|~~~~
3|~~~~
4|~~~~

4.12 is_valid_coordinate(coordinate: str, board_size: int) -> tuple[bool, str]

Checks if the provided coordinate represents a valid coordinate string. This function should return a tuple containing a boolean (which is True if the coordinate is valid and False otherwise), and a string containing a message to describe the issue (if any) with the coordinate. Table 1 describes the possible issues that coordinate can have, and the corresponding messages that should be returned.

>>> is_valid_coordinate('A3', 4)
(True, '')
>>> is_valid_coordinate('A3', 3)
(False, 'Invalid coordinate number.')
>>> is_valid_coordinate('abcd', 4)

Issue Message

Coordinate does not contain exactly two characters          INVALID_COORDINATE_LENGTH

First character in coordinate does not represent a valid column letter for a board with the given board size                    INVALID_COORDINATE_LETTER

Second character in coordinate does not represent a valid row digit for a board with the given board size           INVALID_COORDINATE_NUMBER

No issue (none of the issues described above are present)              Empty string (’’)

Table 1: Messages to be included in the returned tuple, based on the issue present in coordinate. Precedence is top down; that is, if there are multiple issues with a coordinate, the one higher in the table is the one that should be used. The constants present in this table are defined in support.py.

(False, 'Coordinates should be 2 characters long.')
>>> is_valid_coordinate('AA', 4)
(False, 'Invalid coordinate number.')
>>> is_valid_coordinate('H3', 5)
(False, 'Invalid coordinate letter.')

4.13 is_valid_coordinate_sequence(coordinate_sequence: str, ship_length: int, board_size: int) -> tuple[bool, str]

Checks if the provided coordinate sequence represents a sequence of exactly ship length comma-separated valid coordinate strings. This function should return a tuple containing a boolean (which is True if there are exactly ship_length coordinates which are all valid, and False otherwise), and a string containing a message to describe the issue (if any) with the coordinate_sequence. If the coordinate sequence does not contain exactly ship_length comma-separated items, the message returned should be INVALID_COORDINATE_SEQUENCE_LENGTH. Oth-erwise, each item should be checked for validity in order. At the first invalid coordinate, this function should return a tuple containing False and the string message describing the issue with that coordinate. If all items are valid coordinates, this function should return a tuple containing True and the empty string. Note that cases of ships being invalid due to reasons other than those described in this document (e.g. occupying the same square twice, positions disconnected from other positions within the same ship, etc.) will not be tested.

>>> is_valid_coordinate_sequence('A1,B2,C3', 3, 4)
(True, '')
>>> is_valid_coordinate_sequence('A1,B2,C3', 3, 2)
(False, 'Invalid coordinate number.')
>>> is_valid_coordinate_sequence('A1,B2,C3', 2, 2)
(False, 'Invalid coordinate sequence length.')
>>> is_valid_coordinate_sequence('A1,B2,ABCD', 3, 4)
(False, 'Coordinates should be 2 characters long.')
>>> is_valid_coordinate_sequence('E8,B2,ABCD', 3, 4)
(False, 'Invalid coordinate letter.')

4.14   build_ship(coordinate_sequence: str) -> list[tuple[int, int]]

Returns the list of (row, column) positions corresponding to the coordinate sequence. The order of the returned positions must match the order of the given coordinates. A precondition to this function is that coordinate sequence must represent a valid coordinate sequence.

>>> build_ship('A1,A2,A3')
[(0, 0), (1, 0), (2, 0)]
>>> build_ship('G4,G5,G6,G7,G8')
[(3, 6), (4, 6), (5, 6), (6, 6), (7, 6)]


发表评论

电子邮件地址不会被公开。 必填项已用*标注