Programming 3

University of Alicante, 2020–2021

Second Programming Assignment

Deadline: Your assignment must be submitted before Sunday, October 18th, 2020 at 23.59 (Alicante time). Late work will not be accepted. 
Relative weight of this assignment: 20%
IMPORTANT: All your source code must use the UTF-8 character encoding. Do not submit source code in Latin1 or ISO-8859-1 enconding.
If in doubt, first have a look to the ‘Clarifications’ section at the end of this assignment description. We will update it from time to time.

Battleship : Ships and a board

In this practical assignment, starting from the class Coordinate of the previous assignment, we will build the classes to simulate the game. We will be able to create ships and add them to a board and simulate that a player shoots the board in a certain coordinate to try to hit a ship.

The main programming concept that we will work on is that of relations between objects (composition and aggregation).

Introduction

In this assignment we will add some methods to the class Coordinate, and we will create the class Board to manage the board, and the class Ship to create ships. In addition, we will use a couple of enumerated data types (enums) for the orientation of the ships and for the status of a board position. See the class diagram you can find in section Class diagram while you read this assignment description.

Class diagram

The following UML class diagram represents the classes in our model:

Class diagram

The new classes belong to the package model, which was created in the previous practical assignment.

The following sections describe all the methods to be implemented. Attributes and relationships are not included because they are already shown in the UML diagram.

When indicated, your code must check that the argument values passed to a method are correct. The arguments that are references do not need to be checked, unless otherwise stated.

Roadmap

We recommend that you follow this short guide to build your implementation:

  • Start by adding the enumerated types Orientation and CellStatus, with the help of Eclipse. You have more information on this page about Enumerated types in Java, just in case you are curious about all that can be done in Java with enumerated types, although the enums in this practical assignment are very basic and do not make use of any advanced features
  • Add the new methods to Coordinate, and implement unit tests to test them (and try them out, of course)
  • Continue with the class Ship, and its unit tests
  • Finally, implement the Board class, its unit tests and a main program to simulate a single game with a pair of ships (see section Main program below)

Remember, implement unit tests as you finish each class, and use them to debug your code before moving on to the next class.


Classes

The methods of each of the classes in the model are described below.

Coordinate

This is the same class as in the previous assignment, but with a couple of new methods to get a copy of the object, and to get the coordinates adjacent to a coordinate.

Note that in this class you do not have to check whether the components are negative or not, or whether they are inside the board or not, those checks will be done in other classes, not in this one.

copy()

It return a copy of the object, invoking the copy constructor. Do not duplicate the copy constructor’s code, just call it and return the new object.

adjacentCoordinates()

It returns a set with all the coordinates that are adjacent to this coordinate, which should not be included in the returned set. For example, for 2-dimensional coordinate systems like the one in this practical assignment, there will always be 8 adjacent coordinates.

This method returns a Set<Coordinate>, which is a set of coordinates. Actually, Set<> is an interface, so we can assign and return objects of that type, but we cannot create them; for that it is necessary to use a class that implements that interface, like HashSet<>. In this Easy Guide to the Java Collection Framework (JCF) you can learn how to use sets and other useful data structures that we will use in this and future assignments (lists and maps). You can also have a look at the JCF API documentation (package java.util) to learn about these data structures.


Ship

This class represents a ship that will be placed on the board, and has several attributes (all private):

  • orientation: indicates the orientation of the ship (it will be a value of the enumerated type Orientation: NORTH, EAST, SOUTH, WEST); it is specified in the constructor and cannot be changed
  • symbol: character to represent the ship on the board
  • name: name of the ship
  • position: coordinate representing the position of the ship on the board; initially it will have no value (it will be null); it will be assigned a value when the ship is placed on the board
  • shape: this is a matrix that indicates the shape of the boat in each of the 4 possible orientations

shape

The shape of the ship on the board will be represented with a matrix. Each row of this matrix will correspond to one of the possible orientations, and will consist of a sequence of zeros and ones making a 5x5 square (where 5 is the value of the constant BOUNDING_SQUARE_SIZE):

private int shape[][] = new int[][] {
{ 0, 0, 0, 0, 0,             // NORTH    ·····
0, 0, 1, 0, 0,               //          ··#··
0, 0, 1, 0, 0,               //          ··#··
0, 0, 1, 0, 0,               //          ..#..
0, 0, 0, 0, 0},              //          ·····

{ 0, 0, 0, 0, 0,             // EAST     ·····
0, 0, 0, 0, 0,               //          ·····
0, 1, 1, 1, 0,               //          ·###·
0, 0, 0, 0, 0,               //          ·····
0, 0, 0, 0, 0},              //          ·····

{ 0, 0, 0, 0, 0,             // SOUTH    ·····
0, 0, 1, 0, 0,               //          ··#··
0, 0, 1, 0, 0,               //          ··#··
0, 0, 1, 0, 0,               //          ..#..
0, 0, 0, 0, 0},              //          ·····

{ 0, 0, 0, 0, 0,             // WEST     ·····
0, 0, 0, 0, 0,               //          ·····
0, 1, 1, 1, 0,               //          ·###·
0, 0, 0, 0, 0,               //          ·····
0, 0, 0, 0, 0}};             //          ·····

In this assignment, the initial value of shape will be the same for all ships, but in future assignments it will change. Therefore, your code should not assume that the ship has this shape, it should use the content of shape without assuming any specific distribution of values. Neither should it assume that it will always be of size 5, although it can be assumed that it will be a square.

As the example shows, each row has 25 numbers, with values 0 or 1, to indicate whether a position is occupied by the ship or not. In the code it is shown as if it were a matrix, although it is a vector. It will therefore be necessary to do a conversion to access the vector as if it were a matrix (see method getShapeIndex). The constant CRAFT_VALUE (with value 1) should be used to represent that a position is occupied by the ship. During the game, when a ship’s position is hit, the value will change from CRAFT_VALUE to HIT_VALUE (constant with value -1). Since the orientation of the ship cannot change, only the row of shape corresponding to the orientation defined in the constructor will need to be modified.

Besides the basic getters and setters (which can be automatically generated with Eclipse), the most important methods are

Ship(Orientation orientation, char symbol, String name)

Constructor, it assigns values to the attributes.

getPosition()

Returns a defensive copy of the ship’s position, or null if the position has not been assigned. Sometimes objects return a copy of their attributes to prevent their modification from outside the class, this copy is known as defensive copy (the object is defending itself from possible external changes of its state).

getShapeIndex(Coordinate c)

For a relative coordinate c (with values between 0 and BOUNDING_SQUARE_SIZE-1), it returns the position of the vector of shape to be used. For example, if the coordinate is (3,2) (fourth column of the third row), it will return 2*BOUNDING_SQUARE_SIZE+3 (which contains 0 in the NORTH and SOUTH orientations, and 1 in the EAST and WEST orientations).

getAbsolutePositions(Coordinate position) / getAbsolutePositions()

For a coordinate position, it returns a set with the absolute coordinates (referred to the board) occupied by the ship as indicated by the shape attribute. To compute the absolute coordinates occupied by the ship, the position of the ship on the board (position) is made to correspond to the position (0.0) of shape.

In the case of the method without parameters the position attribute of the ship will be used (calling the other method, of course).

Example:

getAbsolutePositions

hit(Coordinate c)

This method simulates the impact of a torpedo on the ship. If the absolute coordinate c is one of the coordinates occupied by the ship, and it is not a position already hit, it will update the internal state of the ship and will return true ; otherwise it will return false.

isShotDown()

It returns true if all the positions of the ship have been hit (i.e. if the ship has sunk).

isHit(Coordinate c)

It returns true if the absolute coordinate c is one of the ship positions hit.

toString()

It returns a string representing the current state of the ship, using the symbol attribute for the positions occupied by the ship, and the constants in the class Board HIT_SYMBOL and WATER_SYMBOL for the positions hit and free, respectively. For example, a ship named Kontiki with SOUTH orientation that has been hit in the central position would be represented with the following string (assuming that WATER_SYMBOL is a blank space, HIT_SYMBOL is and the value of the attribute symbol is #):

Kontiki (SOUTH)
 -----
|     |
|  #  |
|  •  |
|  #  |
|     |
 -----

Board

The game is played on a board. For example, this board (obtained from the Spanish Wikipedia webpage for the Battleship game)

Example (Wikipedia)

would be represented as follows (where x and y refer to columns, and rows, respectively):

x-> 0 1 2 3 4 5 6 7 8 9
0 # # # # # # #
1 #
2 # # # # # #
3 # #
4 #
5 # #
6 #
7 #
8
9 # # # # #
(y)

In the Wikipedia example the positions played by the opponent are marked, but in the example they are not shown because they are stored in another data structure (the set seen).

The class Board represents a square board on which the ships will be placed in the game. It has several public and private constants:

  • HIT_SYMBOL (the symbol '•', copy it from this document)
  • WATER_SYMBOL (a blank space)
  • NOTSEEN_SYMBOL (the question mark '?')
  • MAX_BOARD_SIZE (with value 20) and MIN_BOARD_SIZE (with value 5)

It also has the following attributes:

  • board is a Map<> that relates coordinates to ships. When a ship is added to a board this map is used to associate the coordinates it occupies with it (the keys of the map are of type Coordinate). In the Easy Guide to the Java Collection Framework (JCF) you can learn more about maps.
  • seen is a Set<> with the coordinates that the opponent has discovered or seen.
  • numCrafts is the number of ships on the board
  • destroyedCrafts is the number of ships destroyed
  • size is the size of the board

The most important methods are:

Board(int size)

Constructor. If size is not between MIN_BOARD_SIZE and MAX_BOARD_SIZE an error message will be printed through the standard error output (using System.err.println()) and the value of MIN_BOARD_SIZE will be assigned to size. As with all references, both board and seen must be initialised by assigning them a new object (otherwise they will equal null and the rest of methods will fail). The numCrafts and destroyedCrafts attributes must be initialised to zero.

checkCoordinate(Coordinate c)

It checks if the coordinate c is inside the board limits (valid values go from 0 to size-1).

addShip(Ship ship, Coordinate position)

It adds a ship to the board in the indicated position (which corresponds to the upper left corner of the ship), making three checks beforehand:

  • If any of the ship positions is outside the board, an error will be shown through the standard error output (with System.err.println()) and the ship will not be added
  • If any of the ship’s positions are already occupied, an error will be shown through the standard error output and the ship will not be added
  • If any of the positions around the boat (its vicinity) is occupied, an error will be shown through the standard error output and the ship will not be added

If the above checks do not generate any errors, the ship will be added to the board map using each of its positions as a key (one entry will be added to the map for each position, with the position as the key and the ship as the value), the position will be assigned to the ship, the value of numCrafts will be increased and the method will return true. if the ship could not be added, the method will return false.

getShip(Coordinate c)

It returns the ship associated with the coordinate c, or null if there is no ship at that position.

isSeen(Coordinate c)

It indicates whether the coordinate c has been seen by the opponent or not. For this it will use the set seen.

hit(Coordinate c)

It simulates the shooting of a torpedo on the coordinate c of the board.

  • If the coordinate is outside the board, it will print an error through the standard error output (with System.err.println()) and return the value WATER of the enum CellStatus.
  • If there is no ship in the coordinate, it will return the value WATER of the enum CellStatus.
  • If there is a ship in the coordinate, it will return the values HIT or DESTROYED of the enum CellStatus, depending on whether the ship was hit or shot down. To determine whether the ship has been hit or shot down, make use of the corresponding method of the class Ship.

If the coordinate on which the torpedo is launched is on the board, before returning the corresponding value of the enum CellStatus, it must be marked as seen by updating the set seen. In addition, if the ship is shot down, its entire vicinity should be marked as already seen and the value of destroyedCrafts should be increased. No ships can be in the vicinity of another ship.

areAllCraftsDestroyed()

It indicates if all the ships have been destroyed.

getNeighborhood(Ship ship, Coordinate position)/getNeighborhood(Ship ship)

Given a ship and a position, it returns all the positions in the vicinity of the ship: those that are around the ship and belong to the board. The positions occupied by the ship itself are not considered part of its vicinity. The method with a single parameter will use the ship’s position to call the other method (no code should be duplicated).

show(boolean unveil)

It returns a string representing the board; if the parameter unveil is true the board will be shown as its owner sees it; if it is false it will be shown as the opponent sees it. For the positions with water, the constant WATER_SYMBOL will be used, for the positions that has been hit the HIT_SYMBOL will be used.

If unveil is true, for the positions of the ships that has not been seen their symbol will be used.

If unveil is false then the NOTSEEN_SYMBOL symbol will be used for the unseen positions. If a ship has been shot down, instead of HIT_SYMBOL the ship symbol will be used.

In the above example, with unveil set to true the board would be shown as follows (assuming that all ships have the symbol #):

## #  ####
   #      
#  # ### #
#        #
#         
#    #   •
     #   •
••       #
          
     #####

With unveil set to false, it would look as follows:

??????????
? ????????
??????????
??????????
??? ? ? ??
?? ??????•
    ?????•
## ???????
    ??????
??????????

toString()

It returns a string with the basic board data: size, ships and destroyed ships. For example, for a 10-size board with 6 ships of which 2 have been destroyed (shot down), the string would be Board 10; crafts: 6; destroyed: 2


Main Program

Download this file MainP2.java and copy it in the ‘src/mains’ folder of your project.

You can use MainP2.java as a very simple initial example of a partial game using the classes implemented in this assignment. Bear in mind that this code is far from exhaustive and only explores a very small subset of all possible situations that can occur in the game. The file partida1-SALIDA.txt contains an example of the output of this program.

There is this other main program that
produces this standard output and this error output. copy it in the ‘src/mains’ folder of your project.


Unit tests

Here you have the test of the first practical assignment adapted to this second assignment. Copy the folder model that is created when you uncompress it into the folder test of your project.

Previous tests

Here you can download the pre-tests. Copy them to a model folder in the test directory of your project.

There are some tests to be completed in each file (look for the comment //TODO). These will also be used to grade your assignment, so it is worth trying to complete them, so you can get a good score. Most probably, you will be required to write some unit tests in the practical exam, so make yourself sure you know how to code them!


Documentation

Your source files must include all the comments in Javadoc format as indicated in the first assignment. You do not need to include the HTML files generated by the Javadoc tool in the compressed file to deliver.

Package structure and directories

They are the same as in the first assignment. All the new classes belong to the package model.

Minimal requirements for grading your assignment

  • Your program must run with no errors. 
  • Unless otherwise stated, your program must not emit any kind of message or text through the standard output or standard error streams.
  • The names of all properties (public, protected and private) of the classes, both in terms of scope of visibility and in terms of type and way of writing, must be rigorously respected, even if they are in Spanish. Make sure that you respect the distinction between class and instance attributes, as well as the uppercase and lowercase letters in the identifiers.
  • Your code must be conveniently documented and significant content has to be obtained after running the javadoc tool.  

Submission

Upload your work to the DLSI submission server.

You must upload a compressed file with your source code (only .java files). In a terminal, go to the ‘src’ folder of your Eclipse project and type

tar czvf prog3-battleship-p2.tgz model

Upload the file prog3-battleship-p2.tgz to the server. Follow the instructions on the page to log in and upload your work.

Evaluation

Testing of your assignment will be done automatically. This means that your program must strictly conform to the input and output formats given in this document, as well as to the public interfaces of all the classes: do not change the method signatures (name of the method, number, type and order of arguments, and data type returned) or their behaviour. For instance, method model.Coordinate(int,int) must accept two arguments of type int and store them in the corresponding attributes.

You can find more information about the grading of programming assignments in the subject description sheet.

In addition to the automatic grading, a plagiarism detection application will be used. The applicable regulations of the University of Alicante Polytechnic School in the event of plagiarism are indicated below:

“Theoretical/practical work must be original. The detection of copy or plagiarism will suppose the qualification of”0" in the corresponding assignment. The corresponding Department and the University of Alicante Polytechnic School will be informed about the incident. Repeated conduct in this or any other subject will result in notification of the offences committed to the pertinent vice canchellor’s office so that they study the case and punish it in accordance with the legislation in force".

Clarifications

  • Board.hit(·): if the coordinate recieved as argument was already hit, it must return CellStatus.WATER.
  • Ship.hit(·): if the coordinate received as an argument was already hit, it must return false.
  • Ship.getAboslutePositions(·) returns a set with the the absolute positions that the ship occupies, regardless of whether they have been hit or not.

  • Although not recommended, you may add to your classes as many private attributes and methods as you wish. Notice, however, that you must implement all the methods indicated in this document and make sure that they work as expected, even if they are never called in your implementation. 
  • Any additional remark will be published in this page. It is recommended that you use this page as the primary source of the instructions.