Programming 3

University of Alicante, 2020–2021

Fifth Programming Assignment

Deadline: Your assignment must be submitted before Wednesday, December 23, 2020 at 23.59 (Alicante time). Late work will not be accepted!!
Relative weight of this assignment: 15%

Adding reflection and generics to the Battleship implementation

Introduction

The design proposed for the previous assignment had an issue which will be solved in the current one: the craft and visualiser factories were not completely open for extension, as adding a new type of craft or visualiser would imply adding a new conditional statement or a new case clause to a switch statement. In this assignment, this will be fixed by conveniently introducing reflection. Moreover, the model will be extended with a ranking system that will be able to track the scores obtained after playing each game and sort them according to two different criteria: the amount of positions hit and the value of the different crafts shot down.  

Class diagram

This is the UML class diagram that represents the new classes in our model:

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 two new packages:

  • model.score for the classes implementing the scoring and ranking system;
  • model.exceptions.score for a new class for errors related to the scoring/ranking.

Roadmap

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

  1. Introduce reflection
  2. Implement the scoring system
  3. Introduce the changes explained below to existing classes

Reflection

You have to replace the existing conditional or switch statements in CraftFactory.createCraft(·)  and VisualiserFactory.createVisualiser(·) with code that uses reflection to create an instance of an object whose class name matches the parameter; you will have to fully qualify the name of the class to load by conveniently prepending the package name. The methods will return null as before if the parameter does not contain a valid craft/visualiser name.

The factory methods are now open to extension but close to modification.

To take into account: As crafts may be in two different packages (model.ship and model.aircraft), from now on the method CraftFactory.createCraft(·) will always be called with the name of the sub-package (ship or aircraft) followed by a dot and the name of the class to load (e.g. ship.Carrier). In this way you will be able to fully qualify the name of the class to load regardless of whether it is a ship or an aircraft.

Ranking system and generics

Class Score

The new abstract class Score encapsulates the concept of the score of a game. As the goal is to create a ranking system, scores need to be comparable. This will be accomplished by making Score implement the Java interface Comparable, in particular Comparable<Score<T>>, thus forcing the class to implement the method compareTo(·):

public abstract class Score<T> implements Comparable<Score<T>>{   
   ...   
}

The type T will determine the type of score to be defined.

Non-abstract sub-classes of Score will implement the method score(·) that, when appropriate, increments the attribute score; higher values of this score will mean better performance of the player. The attribute score will be used in the method Score.compareTo(·) to compare two different scores.

Method Score(IPlayer)

Constructor that stores the player received as argument and initialises the attribute score to zero.

Exceptions: If the reference to IPlayer is null, the exception NullPointerException must be thrown.

Method toString()

It returns a string representation of the score (without ‘’ at the end). For that it takes the name of the player from the IPlayer attribute and the score from the score attribute. Example of output:

Julia (PlayerFile): 7

Method compareTo(Score<T>)

This method compares two scores by their value. Since we want to implement a descending order of scores for the ranking, given two objects of type Score<T>, the one representing the GREATEST score will be considered the smallest one. If both objects have the same score the method will return what String.compareTo(·) returns when comparing the names of the players. As a result we will get an ordering of the scores: from the greatest score to lowest one. Have a look at the description of the method compareTo(·) in the Java 8 API documentation to know what compareTo(·) must return in each case.

Class HitScore

This class represents a scorer based on CellStatus and simply keeps the count of positions hit by the player. It must be declared as follows:

public class HitScore extends Score<CellStatus> {
   ...
}

Method HitScore(IPlayer)

Constructor that simply pass the argument received to the constructor of its parent class.

Method score(CellStatus)

This method increments the attribute score by 1 if the CellStatus received as argument equals HIT or DESTROYED.

Class CraftScore

This class represents a scorer based on Craft and adds up the value of the different crafts shot down by the player. It is declared in a way analogous to that of HitScore but using Craft instead of CellStatus.

Method CraftScore(IPlayer)

Constructor that simply pass the argument received to the constructor of its parent class.

Method score(Craft)

This method increments the attribute score with the value of the craft received as argument. See the changes to introduce to the class Craft and its sub-classes below.

Class Ranking

In order to have a ranking system based on the scores obtained by the different players, a new class Ranking will be added. Ranking is a generic class that has a bounded type parameter in order to prevent scores of different types from co-existing in a particular ranking:

class Ranking<ScoreType extends Score<?>> {
   ...
}

Therefore, an object of type Ranking will have to be created in one of two ways, either as

Ranking<HitScore> rh = new Ranking<>();

or as

Ranking<CraftScore> rc = new Ranking<>();

Consequently, a compiler error will prevent the client code from adding scores of different criteria to the same ranking object (try it when you have these classes already implemented). Notice that from Java 7 the type arguments required to invoke the constructor of a generic class can be replaced with an empty set (the pair of angle brackets is called the diamond) provided that the compiler can infer the types from the context.

Method Ranking()

This method just initialises the attribute scoreSet (of class TreeSet); scoreSet must be declared as follows:

private SortedSet<ScoreType> scoreSet;

and initialised like this in the constructor:

scoreSet = new TreeSet<>();

Make sure you understand the previous lines.

Method addScore(ScoreType)

This method adds the object passed as parameter to the scoreSet set. Note that scoreSet is an ordered set and, consequently, the method compareTo(·) of Score will be invoked in order to have the elements in the set sorted.

Method getWinner()

It returns the first element of the set scoreSet.

Exceptions: If the set is empty, the exception EmptyRankingException will be thrown. This new exception belongs to package model.exceptions.score and inherits from BattleshipException. Implement it as you did with the rest of exceptions in the previous assignments.

Method Ranking.getSortedRanking()

A simple getter.

Changes to introduce to existing classes

This section describes the minor changes that you must do in the code of the previous assignment.

Class Craft and its sub-classes

The different ships and aircraft will have a value that will be used by the scoring systems. For this you have to:

  • Add to Craft an abstract method getValue(), returning and int and with no parameters.
  • Implement the method getValue() in the sub-classes of Ship and Aircraft. The value they should return is shown in the following table:
Class name Value
model.ship.Destroyer 3
model.ship.Cruiser 5
model.ship.Battleship 6
model.ship.Carrier 8
model.aircraft.Fighter 10
model.aircraft.Bomber 15
model.aircraft.Transport 18

Interface IPlayer

Add the new method getLastShotStatus().

Class PlayerRandom

These are the changes to be introduced:

  • The method PlayerRandom.putCrafts() needs to be modified to include the name of the sub-package (ship or aircraft) in the different calls to CraftFactory.createCraft(·).
  • In PlayerRandom.nextShoot(), the status returned by Board.hit(·) must be stored in the attribute lastShotStatus.
  • Implement the getter PlayerRandom.getLastShotStatus().

Class PlayerFile

Changes to be introduced:

  • In PlayerFile.nextShoot(), the status returned by Board.hit(·) must be stored in the attribute lastShotStatus. If no shot is performed, for example because the exit command is read from the input file or because there are no more lines to read, lastShotStatus must be set to null.
  • Implement the getter PlayerFile.getLastShotStatus().

In addition, the input files used with PlayerFile need to be updated. You can download an updated version of the files playerfile-john.txt and playerfile-mary.txt that include the non-common prefix when specifying the ship/aircraft in a put command.

Class Game

These are the changes to be introduced:

  • In the constructor of the class the attributes hitScore1, hitScore2, craftScore1 and craftScore2 must be initialised.
  • In Game.playNext(), after the player shoots, the corresponding hitScore is updated by calling HitScore.score(·) with the status of the last shot performed by the player (use method IPlayer.getLastShotStatus() to obtain this status). In addition, if this status is DESTROYED, update the corresponding craftScore by calling CraftScore.score(·) with the craft that was shot down.
  • Implement method Game.getScoreInfo(). It should return a string with information about the scores of the two players. Here is an example of the output:
Player 1
HitScore: John (PlayerFile): 9
CraftScore: John (PlayerFile): 11
--------------
Player 2
HitScore: Mary (PlayerFile): 10
CraftScore: Mary (PlayerFile): 6

Main Program

MainP5.java can be used as an example of a game using the classes implemented in this assignment. Keep in mind, however, that this code explores a very small subset of all possible game situations; therefore, it is recommended that you write your own tests in order to check your code. Remember to update the two files used by MainP5.java (files/playerfile-john.txt and files/playerfile-mary.txt); see section Class PlayerFile. You can download the expected output of MainP5.java from here.

Unit tests

Tests from the previous assignment must execute correctly for this assignment.

Previous tests

In this last assignment you should write your own JUnit tests for each of the implemented methods.

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

Upload the file prog3-battleship-p5.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.