Programming 3

University of Alicante, 2020–2021

Third Programming Assignment

Deadline: Your assignment must be submitted before Sunday, November 8th, 2020 at 23.59 (Alicante time). Late work will not be accepted. 
Relative weight of this assignment: 25%
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 : Aircrafts and 2D/3D boards

Introduction

This assignment will extend the previous one by:

  1. Adding 3D and 2D boards with aircrafts and ships with different shapes to the Battleship game by means of inheritance mechanisms.
  2. Handling error situations via exceptions.

In order to be able to have 3D and 2D boards, as well as ships and aircrafts with different shapes, and at the same time avoid duplicating our code, we will implement a hierarchy of classes (see the class diagram below), some of which will be abstract. Most of the code handling the logic of the game (adding ships or aircrafts, shooting them, etc.) will be in those abstract classes. These classes will use the methods in their sub-classes to implement the logic of the game without having to bother about the type of board (2D or 3D) and type of craft (ship or aircraft) they are working with.

Class diagram

These are the UML class diagrams that represent the classes in our model; the methods in italics are abstract methods:

The following sections describe all the methods that you have to modify or implement from scratch for this assignment. Attributes or relationships will not be covered as they are already shown in the UML diagram. Elements which do not change with regard to the previous assignment will not be explained again. Setters or getters which simply return the current value of a property, or set a property to a new value, will not be commented either. You will have to decide when to use defensive copy in a setter or getter by looking at the class 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.

Package structure and directories

We will create three new packages:

  • model.ship for the 2D board and ships;
  • model.aircraft for the 3D board and aircrafts;
  • model.exceptions for the classes dealing with exception.

Roadmap

It is strongly recommended that you implement your solution in the very same order followed in this document.

  1. Implement the hierarchy of classes for the exceptions: class BattleshipException and its sub-classes.
  2. Implement the hierarchy of classes for the coordinates: classes Coordinate, Coordinate2D, Coordinate3D and CoordinateFactory.
  3. Implement the hierarchy of classes for the different types of crafts: classes Craft, Ship, Aircraft and its sub-classes.
  4. Implement the hierarchy of classes for the boards: classes Board, Board2D and Board3D.

Exceptions

Starting with this practical assignment we will handle error situations by means of exceptions. When an exception occurs, the normal flow of execution of the program is altered: the method being executed aborts its execution and returns the control to the method that invoked it. This method may catch and handle the error situation or abort its execution and return the control to the method that invoked it. If the exception is not handled by any method in the sequence of method invocations, the program is aborted.

In Java, exceptions inherit from the class java.lang.Exception. We will created an abstract superclass with name BattleshipException from which the rest of exceptions will inherit. All classes related to exceptions will belong to package model.exceptions.

Every new exception class will have a method getMessage(·) which will return a string describing the reason for the exception being thrown. The particular text used for the message is up to you: the evaluation tests will not assume any concrete string for it; however, it is recommended to include as much information as possible in order to ease the identification of the error. As all exceptions have to do with coordinates, BattleshipException will have an attribute to store a reference to the coordinate object causing the error situation. This coordinate will be received by the constructor and stored on an attribute so that getMessage(·) can use it.

The name and purpose of the exceptions that need to be created for this assignment are:

  • IllegalArgumentException: the argument of a method is no valid. This a runtime (unchecked) exception that belongs to package java.lang and that you do not have to implement (it already exists).
  • NullPointerException: the argument of a method is null. This a runtime (unchecked) exception that belongs to package java.lang and you that do not have to implement (it already exists).
  • BattleshipException: common ancestor to all the new exception classes; this an abstract class that extends java.lang.Exception.
  • InvalidCoordinateException: the coordinate is not valid because it is outside the board limits.
  • OccupiedCoordinateException: the coordinate to be occupied by a craft (ship or aircraft) is already occupied by another craft.
  • NextToAnotherCraftException: the coordinate to be occupied by a craft (ship or aircraft) is next to a coordinate already occupied by another craft.
  • CoordinateAlreadyHitException: the coordinate being shot was already hit in a previous attempt.

If a method could throw, according to these instructions, two or more exceptions from the previous list, the exception that appears first on the list will be the one to be thrown; in other words, your code has to deal with the error situations in the same order as the one defined by the list.

Eclipse will probably show a warning about an undeclared field serialVersionUID when defining the exception classes (for instance, “The serializable class MyException does not declare a static final serialVersionUID field of type long”). You may follow the Eclipse recommendation and add this field to your class:

private static final long serialVersionUID = 1L;

Alternatively, you can add the following annotation just before the line where the class is declared:

@SuppressWarnings("serial")  
public class MyException extends Exception {
    ...
}

Exception catching

In this assignment, only the main method or the unit tests catch exceptions thrown by the methods enumerated in the following sections. Many of these exceptions, however, will be caught by methods in some of the new classes of later assignments.

Class Coordinate and its sub-classes

We will have an abstract superclass with name Coordinate that will retain most of the code you wrote for this class in previous assignments. This class will have two different sub-classes: Coordinate2D and Coordinate3D.

Changes to make to class Coordinate

  • Make the class Coordinate abstract
public abstract class Coordinate {
   ...
}
  • Change its constructor so that it receives the amount of dimensions of the coordinate, and allocate memory for the attribute components.
  • Make the methods copy() and adjacentCoordinates() abstract (make a copy of their implementation, you will use it in Coordinate2D). They will be implemented in Coordinate2D and Coordinate3D. These abstract methods will allow the methods in the rest of classes, such as Board and Craft, to create a copy of a coordinate, or obtain its adjacent positions, without having to wonder whether it is a 2D or 3D coordinate. In this way most of the code will be in the classes belonging to package model.
  • Replace any call to the copy constructor of Coordinate by a call to the method copy()
  • Make sure that methods add(·) and subtract(·) do their job on all the components available. It is worth noting that it is possible to add/subtract 2D and 3D coordinates; in those case the operation will be done only on the components available (first two components of the coordinate); the type of coordinate to return will be that of the object being used to invoke the method (this). For example: (1, 4, 2) + (2, 3) = (3, 7, 2).
  • Remove the implementation of toString(), it will be moved to Coordinate2D.
  • Change the visibility of the constructor and copy constructor to protected.
  • Change methods set(·) and get(·) so that the exception IllegalArgumentException is thrown when the component to set/get is out of range.
  • Change methods add(·) and subtract(·) so that they check if the coordinate received as argument is null, and if it is null the exception NullPointerException is thrown. This checking and throwing of the exception, when appropriate, can be easily done as follows:
Objects.requireNonNull(c);

Class Coordinate2D

Coordinate2D will belong to package model.ship. It inherits from Coordinate and has to implement the methods shown in the UML diagram.

  • The constructor creates a coordinate with two dimensions and assigns the corresponding values.
  • The copy constructor passes the argument received to the copy constructor of the superclass.
  • copy() makes use of the copy constructor.
  • The implementation of adjacentCoordinates() is almost identical to that of the class Coordinate of the previous assignment, but using the method CoordinateFactory.createCoordinate(·) (see below) to create a 2D coordinate when needed.
  • toString() is identical to that of the class Coordinate of the previous assignment.

Class Coordinate3D

Coordinate3D will belong to package model.aircraft. As Coordinate2D, it inherits from Coordinate and has to implement the methods shown in the UML diagram. The implementation of its methods is similar to their implementation in Coordinate2D but extending them to a third dimension:

  • adjacentCoordinates() will return a set with 26 positions.
  • toString() will return a String with the three components of the coordinate, for example (1, 1, 4)

Class CoordinateFactory

It is often very useful to have a method that encapsulates object creation. This is usually carried out by implementing a factory method. To do this, add the new class CoordinateFactory to your implementation. This class has only a static method, createCoordinate(·), that receives a variable number of arguments of type int and returns a 2D or 3D coordinate, depending on how many arguments it receives. The signature of the method is as follows:

public static Coordinate createCoordinate(int... coords) {
    ...
}

Inside the method, coords will be an array. If the amount of elements in this array is below 2 or above 3, the method will throw the exception IllegalArgumentException. Remember that every array has an attribute length storing the amount of elements of the array.

Class Craft and its sub-classes

We will have an abstract superclass with name Craft from which two abstract classes, Ship and Aircraft, will inherit. The specific ships and aircrafts to be used will inherit from one of the latter two abstract classes.

All the code you had in the Ship class in the previous assignment will be in the class Craft, which is located at the top of the hierarchy. The only thing that is moved downstream the hierarchy is the shape of the different ships and aircrafts.

Extraction of the superclass Craft

We are going to extract a superclass with name Craft using the Refactor facility provided by Eclipse. To extract this superclass do as follows:

  1. Right click on the class Ship, then select Refactor->Extract Superclass…
  2. Set the name of the superclass to be created: Craft
  3. Select all attributes and methods so that they are moved to the superclass
  4. Make sure that the option Use the extracted class where possible is selected
  5. Click on the Finish button

As a results we will have two classes, Craft and Ship, with Ship inheriting from Craft. If you have a look to the line where the class Ship is declared, you will see that the extends reserved word is used:

public class Ship extends Craft {
    ...
}

Changes to make to the class Craft

  • Remove the constructor created by Eclipse and create a new one by copying the constructor from Ship.
  • Make this class abstract.
  • Change the visibility of shape to protected and remove its initialisation. From now on it will be initialised in the constructor of the sub-classes.
  • In getShapeIndex(·) and getAbsolutePositions(·) check that coordinate received as argument is not null. It it were null the method would throw a NullPointerException (see Section Exceptions).
  • In hit(·) throw exception CoordinateAlreadyHitException if the coordinate received as argument was already hit in a previous attempt.

Changes to make to the class Ship

  • Create package model.ship
  • Move this class to the new package. You can do so with Refactor->Move….
  • Change the constructor so that it passes its arguments to the constructor in its superclass using super().
  • Make the class abstract.

Sub-classes of Ship

Ship will have a total of four sub-classes, all belonging to package model.ship: Battleship, Carrier, Cruiser and Destroyer. Do as follow:

  1. Create these sub-classes.
  2. Create their constructors. They have to pass the orientation, the symbol used to represent them (see below) and their name (that of the class) to their superclass using super(). After that, they have to allocate memory and initialise the attribute shape (see below).
Symbols representing the different ships

These are the symbols to represent the different types of ships, copy them from this assignment description into your code:

  • Battleship: O
  • Carrier: ®
  • Cruiser: Ø
  • Destroyer: Ω
Shape of the ships

The attribute shape must be initialised in the constructor of each sub-class. What changes between sub-classes are the positions set to one and zero. You can download the initialisation of the attribute shape for every ship (and aircraft) from here.

Class Aircraft

  • Create package model.aircraft
  • Create an abstract class with name Aircraft that inherits from Craft and belongs to package model.aircraft.
  • Implement the constructor so that it behaves as the constructor of Ship.

Sub-classes of Aircraft

Aircraft will have a total of three sub-classes, all belonging to package model.aircraft: Bomber, Fighter and Transport. Like the sub-classes of Ship these sub-classes have to initialise the attribute shape in their constructor (you can download the initialisation of the attribute shape from here). Their constructors will also pass the orientation, the symbol used to represent them (see next) and their name (that of the class) to their superclass using super().

Symbols representing the different aircrafts

These are the symbols to represent the different types of aircrafts, copy them from this assignment description into your code:

  • Bomber:
  • Fighter:
  • Transport:

Class Board and its sub-classes

We will have an abstract superclass with name Board that will retain most of the code you wrote for the class Board in the second practical assignment. This class will have two different sub-classes: Board2D and Board3D. The methods to be implemented in these sub-classes are checkCoordinate(·) and show(·): their implementation depends on the type of board (2D or 3D).

Extraction of the superclass Board

What follows is an explanation of how to extract the superclass Board

  1. Rename class Board to Board2D (Refactor -> Rename…).
  2. Extract the superclass Board from Board2D (Refactor->Extract Superclass…). All members (attributes and methods), except the methods checkCoordinate(·) and show(·), must be moved to the superclass.
  3. Move Board2D to package model.ship (Refactor->Move…).

Changes to make to class Board

  • Make the class Board abstract
  • Declare an abstract method with name checkCoordinate(·)
public abstract boolean checkCoordinate(Coordinate c);
  • Declare an abstract method with name show(·)
public abstract String show(boolean unveil);

These two methods will be implemented in Board2D and Board3D.

  • Add constant BOARD_SEPARATOR with value '|'.
  • Move the code in the constructor of Board2D to the constructor of Board.
  • Change the attribute board so that it maps coordinates to crafts (instead of ships):
private Map<Coordinate,Craft> board;
  • Check the visibility of the attributes of Board. All of them should be private with the exception of the constants, which should be public. Same as in the second practical assignment.
  • In the constructor, if the size of the board is not within the limits, the exception IllegalArgumentException must be thrown.
  • In getNeighborhood(·) check that the arguments are not null and if they are null throw exception NullPointerException.
  • In hit(·) check that the coordinate received as argument is within the limits of the board and if it is not, throw exception InvalidCoordinateException. If a method invoked by hit(·) throws an exception it must be propagated (i.e. it must not be caught).
  • Rename method getShip() to getCraft() (Refactor -> Rename…).
  • Changes to method addShip(·)
    • Rename it, the new name is addCraft(·)
    • Change the method signature to public boolean addCraft(Craft craft, Coordinate position)
    • If any of the coordinates to be occupied by the new craft (ship or aircraft) is not within the limits of the board, throw exception InvalidCoordinateException; if any of them is occupied by another craft, throw exception OccupiedCoordinateException; if any of them is next to another craft, throw exception NextToAnotherCraftException.

Changes to make to class Board2D

  • Change the constructor of Board2D so that it passes the size received as argument to the constructor of Board.
  • Annotate methods checkCoordinate(·) and show(·) with @Override to tell the compiler that they implement methods inherited from Board:
@Override
public boolean checkCoordinate(Coordinate c) {
    ...
}
  • Change the implementation of checkCoordinate(·) so that it checks that the coordinate received as argument is of type Coordinate2D. If it is not an instance of Coordinate2D, throw an exception of type IllegalArgumentException.
  • Change the implementation of show(·) so that it uses CoordinateFactory.createCoordinate(·) to create coordinates of type Coordinate2D.

Class Board3D

The new class Board3D is analogous to Board2D but working with coordinates of type Coordinate3D. This class inherits from Board and has to implement its constructor and methods checkCoordinate(·) and show(·):

  • The constructor is identical to that of Board2D
  • The implementation of checkCoordinate(·) is analogous to that of Board2D.checkCoordinate(·); remember to check that the coordinate received is of type Coordinate3D and to check that the three values of the coordinate are within the limits of the board.
  • This class belongs to package model.aircraft

Board3D.show(boolean unveil)

This method is similar to that of Board2D but taking into account that the board has three dimensions. We can think of a 3D board as a stack of 2D boards; when representing the 3D board as a string, each 2D board in that stack will be represented one after the other. For example, a 3D board with size 8 can be though as a stack of 8 2D boards and will be represented as (with unveil=false):

????????|????????|????????|????????|????????|????????|????????|????????
????????|????????|??? ????|????????|????????|????????|????????|????????
????????|????????|???•????|????????|????????|????????|????????|????????
????????|????????|??•••???|????????|????????|????????|????????|????????
????????|????????|???•????|????????|????????|????????|????????|????????
????????|????????|????????|????????|????????|????????|????????|????????
????????|????????|????????|????????|????????|????????|????????|????????
????????|????????|????????|????????|????????|????????|????????|????????

where coordinates (3, 4, 2), (2, 3, 2), (3, 3, 2), (3, 2, 2), (4, 3, 2) have been hit and coordinate (3, 1, 2) was shot and the results was WATER.

Main Program

Download this file with a class containing a main program and copy it to the ‘src/mains’ folder of your project.

You can use MainP3.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 output-p3.txt contains an example of the output of this program.


Unit tests

Here you have the test of the second practical assignment adapted to this third assignment. Copy the folder model that is created when you unzip 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.

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-p3.tgz model

Upload the file prog3-battleship-p3.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.ship.Coordinate2D(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

  • 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 the Moodle forum for the discussion of the practical assignments; please subscribe to that forum.