1 Background Battleship is a classic board game. Two people play against ench other, each arrange their ships on a grid, and then take turns "shooting at ench other's board; one player announces a target, and the other player announces where that lands on their board: it may be a hit on one of the ships (perhaps sinking it), or a miss We are going to use this game to explore the interaction between multiple classes and we are going to allow you to design the interface between them. I have provided the main() function for our program, and I'll also be providing a video, showing you the complete game in action. But it will be your job to implement the classes that my main() function is making use of Since you will be doing design for this assignment, we've broken it into two pieces, spread over two weeks. First, during a Short Problem, you will write mp a design (expressed in English, not in code) for how your classes will work, and how they will communicate with each other internally. Your TA will review your design, and give you feedback on it. Then, during the next week, the Long Problem will be to implement Battle- ship. (There will be an unrelated Short Problem as well.) Note that we don't expect that your final design will exactly match your plan; changes are almost certain. But, we want you to spend time on the design, and that will (hopefully) make the actual implementation easier to accomplish. 2 What We Will Provide For the design phase, we have provided a main() function for you. The code is available on the class website: examine it, to see how the program works, overall. (Later in this spec, we will also give you a formal definition of all of the classes and methods which your code must provide.) 3 What You Must Turn In Design For the design phase, you must turn in a simple design document to GradeScope. The file must be a PDF (you can generate a PDF using Google Docs, or with just about any word processor you have installed on your computer). The file must include a thoughtful design about how the various classes will interact with each other (what sorts of methods and attributes will they need), as well as the data structures that you will use inside them. Obviously, we can't Auto-grade this, and so your TA will be going wer it by hand. Your job in this document, is to think carefully through the issues that you will have to face when you write the cole; the job of your TA in to give you feedback about how you could revise the design to make it better. Of course, your TA will have to assign a grade for your design, but this is not directly tied to your design they might have lots of suggested changes to your design, and yet give you full credit. Or, they might not have much to say, and dock you significantly. This is because the purpose of this design document is to demonstrate careful thought. You're not getting graded on whether your design is correct, or even if it is "good;" you're being graded based on whether it demonstrates effort and care. See the short problem spee for some ideas about things you'll need to discuss in your document Code For the implementation, you must turn in (at least) a single file, named battleship.py, which contains the required classes. We will be testing this part with an auto- grader. 4 New Python Feature: assert In this project, you must use assert statements, inside your functions, to check for certain error conditions (see below for details). An assert looks like this: assert thing that_must_be_true If the condition that you check is true (as expected), then an assert does nothing your program simply continues. But, if the condition is not true, the assert will crash your program. (Technically, what happens is that your code throws an AssertionError exception.) 5 Program Overview In a file battleship.py, you must write (at least) two classes, as follows: The class Board, which represents a single game board. Note that this is only one half of a game of Battleship; if we wanted to implement in nctual, competitive game, then we would build two of these objects. But for this little program, we're going to "play" with only one board (Feel free to write a more advanced main program, in your spare time.) The class Ship, which repesents a single ship in the game. My code will call the following methods, and thus you must implement them exactly as defined (since I'll be auto-testing them). Feel free to use helpers 2 as appropriate; also, you almost certainly will need to implement some other methods, which I haven't detailed here. 5.1 Board. init..(self, size) This creates a Board object. The Board starts empty (no ships in it). All boards are square, so this function needs only a single size parameter. Your code must assert that the size is positive. 5.2 Board.add_ship(self, ship, position) This adds a ship to the board. The ship argument must contain some sort of information about the shape of the ship; the format is up to you, because you will be writing the code on both sides of the interface. The last argument is a (x,y) tuple, which gives the actual position of the ship on the board. While the interface between the Board and the Ship is up to you, our main function both creates the Ship object (doing rotation if necessary) and also calls add_ship So the end result - however it gets encoded in the data structure must be predictable. For instance, suppose that main() creates a Destroyer (defined in standard ships.py). The standard shap for a Destroyer is: [ (0,0), (1,0)) meaning that it takes up two spaces. If you do not rotate the ship, then the first space (0,0) is on the left, and the second is immediately to the right of it. Now, suppose that you call add_ship to add this to a board. And the position where you add it is (7.1). In that case, the ship will take up two spaces on the board: (7,1) and (8,1) Your code must use an assert to sanity-check that this new ship does not fall outside of the valid indices of the board, and also that it does not overlap any ship already added. 5.3 Board.print Prints out the current state of the board, to the screen. It should print out a grid something like this: S SS F 0 1 2 3 4 5 6 7 8 9 You must support varying board sizes (1 and up). You can get most of the credit for this assignment while only supporting sizes up through 9 (that is, single digits). However, a few points (less than 5% of the total assignment) will be dedicated to testing larger boards, that require 2 digits. (We'll never test 3-digit sizes.) 2-digit sizes are drawn as follows: 11 10 SSS NGA 11 0 1 2 3 4 5 6 7 8 9 0 1 The grid squares (that is, the interior of the board) should print out accord- ing to the following table: Use period characters for empty spaces on the board, which have never been shot at. Use a lowercase oh (o) to represent a place, in deep water, which was shot at, but it was a miss such as (3.4) in the above example. Use an asterisk (*) to record a place on a ship which has been Hit, but where the ship has not been sunk - such as (5.5) in the above example. Use all capital X's if a ship has been sunk such as (1,2) through (3.2) in the above example. . For parts of ships that have not been hit yet, that square must use the first letter of their name, so that it's easy to tell them apart. (Sometimes. two ships will have the same first letter. We just have to live with that.) 5.4 Board. has been used (self, position) Checks to see if someone has already shot at this location; returns a Boolean vale. In the above example, this would return True for (3.4), (5.5), (1.2) and other locations. It would return False for (1.8), (0,0), and many other examples. This method must assert that the position given is on the board. Like with add_ship and the shape array for the ship class, the position is an (x) tuple. 5.5 Board. attempt move(self, position) Handles a "hot" that is being taken at a given location on the board. The position parameter tells you where the shot is headed. This method must use an assert to guarantee that the location is within the valid range of the board. It must also use an assert to verify that the location has never been shot at before. This method must retum a string, which explains what the result of the move was the possible results are a miss, a hit (the ship is not named), or a hit (the ship name is included). See the testcases for the exact strings. Like with add_ship and the shape array for the ship class, the position Is an (x.y) tuple (npec cantims on next page) 5.6 Ship...init__(self, name, shape) Builds a new Ship object. The first parameter is a string, which is the name of the ship; the second is the shape of the ship. The shape is given as a list of ordered (x.y) pairs. As noted in the add_ship) description above, these are offsets from a starting location; it would be normal (although it's not required) that the first element in any shape would be (0.0). For example, imagine a "Tee" shaped piece from Tetris (See Tee() in tetris_ships.py.) The shape is as follows: We assign grid coordinates to these blocks. We could use any offsets at all. but in this example, we'll use (0,0) to represent the center block. Thus, the coordinates are: (0,1) ( (-1,0) (0,0) (1,0) We could encode this as a shape parameter, as follows: [ (0,0). (1,0), (0,1), (-1,0) ] 5.7 Ship.print(self) Prints out a single line of output, which summarizes the state of the ship. The first several characters indicate whether the various parts of the ship have been hit: print out one character per element in the shape. If that space has been hit, print an asterisk; otherwise, print the first letter of the name. Then, print some blank spaces: print out exactly the number of spaces so that the shape-status. plus the spaces, totals to 10 characters. (You can assume that the shapes will never be more than 9 spaces long.) So, for example, an Aircraft Carrier, which has been hit directly in the middle, would print out Aircraft Carrier NOTE: Unlike board.print(), where a ship turns to all X's when it has been sink, ship.print() should always use asterisks for any hits it's taken. Thus, the Aircraft Carrier, if sunk, will print out Aircraft Carrier 5.8 Ship.is_sunk(self) Returns True if the Ship has been sunk (that is, if all of the slots in the ship have been hit; returns False otherwise 5.9 Ship.rotate(self, amount) This method is used to rotate a single ship. Rotate it around the (0.0) coordi- nate: thus, if one of the elements in the shape is (0.0) (as will be common), that one position should not change. ho If the user has created many ships with the same shape, make sure that this method only rotates one of them, NOTE: This method must be called before the ship is added to the Board You may nasume that this will never be called after the ship has been added. This updates the shape of the Ship, by creating a new shape array (don't - the old onel), where each coordinate pair has been modified. Each coor- dinate should be rorated in units of 90 degrees clockwise. That is, if amount is 0, don't change the shape: if it is 1. rotate clockwise by 90 degrees if it is 2. rotate by 180 degrees if it is 3, rotate clockwise by 270 degrees. Your code must usert that the value of mount is between 0 and 3, inclusive. To see how this works, consider the same shape R_ZigZag from tetris_ships.py, at all four rotation positions: (sper continues on next page) (1,2) (0,1) (1,1) (0,0) (1,0) (0,0) (1,-1) (2,-1) rot = 0 rot = 1 (0,0) (-2,1)|(-1,1) (-1,-1) (0,-1) (-1,0) (0,0) (-1,-2) rot = 2 rot = 3 (spec.contimes on next page) 8 1 Background Battleship is a classic board game. Two people play against ench other, each arrange their ships on a grid, and then take turns "shooting at ench other's board; one player announces a target, and the other player announces where that lands on their board: it may be a hit on one of the ships (perhaps sinking it), or a miss We are going to use this game to explore the interaction between multiple classes and we are going to allow you to design the interface between them. I have provided the main() function for our program, and I'll also be providing a video, showing you the complete game in action. But it will be your job to implement the classes that my main() function is making use of Since you will be doing design for this assignment, we've broken it into two pieces, spread over two weeks. First, during a Short Problem, you will write mp a design (expressed in English, not in code) for how your classes will work, and how they will communicate with each other internally. Your TA will review your design, and give you feedback on it. Then, during the next week, the Long Problem will be to implement Battle- ship. (There will be an unrelated Short Problem as well.) Note that we don't expect that your final design will exactly match your plan; changes are almost certain. But, we want you to spend time on the design, and that will (hopefully) make the actual implementation easier to accomplish. 2 What We Will Provide For the design phase, we have provided a main() function for you. The code is available on the class website: examine it, to see how the program works, overall. (Later in this spec, we will also give you a formal definition of all of the classes and methods which your code must provide.) 3 What You Must Turn In Design For the design phase, you must turn in a simple design document to GradeScope. The file must be a PDF (you can generate a PDF using Google Docs, or with just about any word processor you have installed on your computer). The file must include a thoughtful design about how the various classes will interact with each other (what sorts of methods and attributes will they need), as well as the data structures that you will use inside them. Obviously, we can't Auto-grade this, and so your TA will be going wer it by hand. Your job in this document, is to think carefully through the issues that you will have to face when you write the cole; the job of your TA in to give you feedback about how you could revise the design to make it better. Of course, your TA will have to assign a grade for your design, but this is not directly tied to your design they might have lots of suggested changes to your design, and yet give you full credit. Or, they might not have much to say, and dock you significantly. This is because the purpose of this design document is to demonstrate careful thought. You're not getting graded on whether your design is correct, or even if it is "good;" you're being graded based on whether it demonstrates effort and care. See the short problem spee for some ideas about things you'll need to discuss in your document Code For the implementation, you must turn in (at least) a single file, named battleship.py, which contains the required classes. We will be testing this part with an auto- grader. 4 New Python Feature: assert In this project, you must use assert statements, inside your functions, to check for certain error conditions (see below for details). An assert looks like this: assert thing that_must_be_true If the condition that you check is true (as expected), then an assert does nothing your program simply continues. But, if the condition is not true, the assert will crash your program. (Technically, what happens is that your code throws an AssertionError exception.) 5 Program Overview In a file battleship.py, you must write (at least) two classes, as follows: The class Board, which represents a single game board. Note that this is only one half of a game of Battleship; if we wanted to implement in nctual, competitive game, then we would build two of these objects. But for this little program, we're going to "play" with only one board (Feel free to write a more advanced main program, in your spare time.) The class Ship, which repesents a single ship in the game. My code will call the following methods, and thus you must implement them exactly as defined (since I'll be auto-testing them). Feel free to use helpers 2 as appropriate; also, you almost certainly will need to implement some other methods, which I haven't detailed here. 5.1 Board. init..(self, size) This creates a Board object. The Board starts empty (no ships in it). All boards are square, so this function needs only a single size parameter. Your code must assert that the size is positive. 5.2 Board.add_ship(self, ship, position) This adds a ship to the board. The ship argument must contain some sort of information about the shape of the ship; the format is up to you, because you will be writing the code on both sides of the interface. The last argument is a (x,y) tuple, which gives the actual position of the ship on the board. While the interface between the Board and the Ship is up to you, our main function both creates the Ship object (doing rotation if necessary) and also calls add_ship So the end result - however it gets encoded in the data structure must be predictable. For instance, suppose that main() creates a Destroyer (defined in standard ships.py). The standard shap for a Destroyer is: [ (0,0), (1,0)) meaning that it takes up two spaces. If you do not rotate the ship, then the first space (0,0) is on the left, and the second is immediately to the right of it. Now, suppose that you call add_ship to add this to a board. And the position where you add it is (7.1). In that case, the ship will take up two spaces on the board: (7,1) and (8,1) Your code must use an assert to sanity-check that this new ship does not fall outside of the valid indices of the board, and also that it does not overlap any ship already added. 5.3 Board.print Prints out the current state of the board, to the screen. It should print out a grid something like this: S SS F 0 1 2 3 4 5 6 7 8 9 You must support varying board sizes (1 and up). You can get most of the credit for this assignment while only supporting sizes up through 9 (that is, single digits). However, a few points (less than 5% of the total assignment) will be dedicated to testing larger boards, that require 2 digits. (We'll never test 3-digit sizes.) 2-digit sizes are drawn as follows: 11 10 SSS NGA 11 0 1 2 3 4 5 6 7 8 9 0 1 The grid squares (that is, the interior of the board) should print out accord- ing to the following table: Use period characters for empty spaces on the board, which have never been shot at. Use a lowercase oh (o) to represent a place, in deep water, which was shot at, but it was a miss such as (3.4) in the above example. Use an asterisk (*) to record a place on a ship which has been Hit, but where the ship has not been sunk - such as (5.5) in the above example. Use all capital X's if a ship has been sunk such as (1,2) through (3.2) in the above example. . For parts of ships that have not been hit yet, that square must use the first letter of their name, so that it's easy to tell them apart. (Sometimes. two ships will have the same first letter. We just have to live with that.) 5.4 Board. has been used (self, position) Checks to see if someone has already shot at this location; returns a Boolean vale. In the above example, this would return True for (3.4), (5.5), (1.2) and other locations. It would return False for (1.8), (0,0), and many other examples. This method must assert that the position given is on the board. Like with add_ship and the shape array for the ship class, the position is an (x) tuple. 5.5 Board. attempt move(self, position) Handles a "hot" that is being taken at a given location on the board. The position parameter tells you where the shot is headed. This method must use an assert to guarantee that the location is within the valid range of the board. It must also use an assert to verify that the location has never been shot at before. This method must retum a string, which explains what the result of the move was the possible results are a miss, a hit (the ship is not named), or a hit (the ship name is included). See the testcases for the exact strings. Like with add_ship and the shape array for the ship class, the position Is an (x.y) tuple (npec cantims on next page) 5.6 Ship...init__(self, name, shape) Builds a new Ship object. The first parameter is a string, which is the name of the ship; the second is the shape of the ship. The shape is given as a list of ordered (x.y) pairs. As noted in the add_ship) description above, these are offsets from a starting location; it would be normal (although it's not required) that the first element in any shape would be (0.0). For example, imagine a "Tee" shaped piece from Tetris (See Tee() in tetris_ships.py.) The shape is as follows: We assign grid coordinates to these blocks. We could use any offsets at all. but in this example, we'll use (0,0) to represent the center block. Thus, the coordinates are: (0,1) ( (-1,0) (0,0) (1,0) We could encode this as a shape parameter, as follows: [ (0,0). (1,0), (0,1), (-1,0) ] 5.7 Ship.print(self) Prints out a single line of output, which summarizes the state of the ship. The first several characters indicate whether the various parts of the ship have been hit: print out one character per element in the shape. If that space has been hit, print an asterisk; otherwise, print the first letter of the name. Then, print some blank spaces: print out exactly the number of spaces so that the shape-status. plus the spaces, totals to 10 characters. (You can assume that the shapes will never be more than 9 spaces long.) So, for example, an Aircraft Carrier, which has been hit directly in the middle, would print out Aircraft Carrier NOTE: Unlike board.print(), where a ship turns to all X's when it has been sink, ship.print() should always use asterisks for any hits it's taken. Thus, the Aircraft Carrier, if sunk, will print out Aircraft Carrier 5.8 Ship.is_sunk(self) Returns True if the Ship has been sunk (that is, if all of the slots in the ship have been hit; returns False otherwise 5.9 Ship.rotate(self, amount) This method is used to rotate a single ship. Rotate it around the (0.0) coordi- nate: thus, if one of the elements in the shape is (0.0) (as will be common), that one position should not change. ho If the user has created many ships with the same shape, make sure that this method only rotates one of them, NOTE: This method must be called before the ship is added to the Board You may nasume that this will never be called after the ship has been added. This updates the shape of the Ship, by creating a new shape array (don't - the old onel), where each coordinate pair has been modified. Each coor- dinate should be rorated in units of 90 degrees clockwise. That is, if amount is 0, don't change the shape: if it is 1. rotate clockwise by 90 degrees if it is 2. rotate by 180 degrees if it is 3, rotate clockwise by 270 degrees. Your code must usert that the value of mount is between 0 and 3, inclusive. To see how this works, consider the same shape R_ZigZag from tetris_ships.py, at all four rotation positions: (sper continues on next page) (1,2) (0,1) (1,1) (0,0) (1,0) (0,0) (1,-1) (2,-1) rot = 0 rot = 1 (0,0) (-2,1)|(-1,1) (-1,-1) (0,-1) (-1,0) (0,0) (-1,-2) rot = 2 rot = 3 (spec.contimes on next page) 8