Skip to content

TP 3

Halma controller implementation.

Meta

  • Deadline: Monday Apr 7th, 11:59 PM
  • Submission: GitLab
  • Teams: Triplets of students, as announced in class
  • Late submission: Not possible. You will be graded by whatever contents is on your main branch at the deadline

It is your responsibility to submit code in a timely manner. Do not wait until the last minute, GitLab may fail.

Objectives

  1. For the third TP you will complete your Halma Controller implementation, and integrate advanced concepts to ensure code quality into the build process:

  2. You have to implement your own tests and ensure advanced test coverage with a build configuration.

  3. Your code must comply to industry grade code formatting rules.
  4. Your code must be fully documented with JavaDoc comments for all public methods and classes.
  5. Your code must not be overly complex.

  6. You will start with a provided maven configuration that enforces some standards. You will then extend this configuration to enforce additional code quality standards.

  7. Your build configuration will produce a self-contained deliverable, which is a first playable version of your game.

Context

For the TP-1 and TP-2 milestone, you already implemented the Halma model, and parts of the Halma controller. Your submission has been graded based on a set of predefines interface tests, as well as code standards.

Carefully read the test report

The submission tests used for grading contained additional unit test scenarios. Carefully read the test report and make sure to fix any potential issues in your model. Depending on your model implementation quality, you might need to invest a little extra time for milestone 3.

Instructions

Several changes are necessary to pass this third milestone. In the following comes a detailed breakdown of what is expected.

Project configuration files

Update your launcher and xml files

Update your configuration, or your game will NOT be playable. You will lose MASSIVE points if you ignore this.

  • Visit the tp3-files branch in your repo and merge both google_checks.xml and pom.xml into your codebase.
  • In the remainder you will also be asked to add three additional plugins to the pom.xml configuration files. Note that these are the only changes allows, and any other modification of the pom.xml will lead to a desk-rejection.
    Especially not allowed is:
    • Integrating other JUnit versions.
    • Integration additional libraries.

Runtime arguments

At the moment of submission, your star-shaped game implementation must be playable using the provided launcher and textual UI / command line client. The same holds for the JAR file produced by your build configuration.

On start, your game must be able to process the following runtime arguments:

  1. board base-size, for example 1, to produce a star with base size 1:
y\x |  00  01  02  03  04 
----+--------------------
00  |     [ ]     [ ]     
01  |         ( )         
02  |     ( )     ( )     
03  | [ ]     ( )     [ ] 
04  |     ( )     ( )     
05  |         ( )         
06  |     [ ]     [ ]     
  1. Names of involved players. These must be either 3 or 6. Other inputs must be rejected.

  2. Example: Your program must be launchable with the following commands:

    • mvn exec:java "-Dexec.args=1 Max Quentin Hafedh" -> Creates 5*7 star shaped cell grid with 3 players.
    • java -jar target/halma.jar 1 Max Quentin Hafedh -> same but starting from JAR
    • mvn exec:java "-Dexec.args=2 Max Quentin Ryan Hafedh Maram Roman" -> Creates 9*13 star shaped cell grid with 6 players.
    • java -jar target/halma.jar 2 Max Quentin Ryan Hafedh Maram Roman" -> same but starting from JAR

Sample launcher

Your game will be advanced by a main control loop, that (until game end) does the following:

1) Determine valid moves for current player. (Blackboard pattern see explanations in class.) 2) Present valid moves on console and prompt player for a choice. 3) Feed choice back to controller and advance game state, based on the player's selection.

You are not required to implement UI / prompting functionality, so you can concentrate on the game-logic components. Classes for printing and prompting options are provided, below is a short sample code to illustrate how to use the provided functionality.

Blackboard pattern control loop

Below is a sample implementation to keep the game going until game end. Feel free to use the provided UI classes for printing game state and retrieving user choices via command prompt.

private static void runTp03(String[] args) {
  // Parse runtime parameters
  int baseSize = Integer.parseInt(args[0]);
  String[] playerNames = // ... trouvez une façon d'interpréter les arguments.

      // Appellez votre implementation "StarModelFactory" ici.
      ModelFactory modelFactory = new StarModelFactory();
  // Set move selectors // ... plus tard on va utiliser des IA pour choisier des actions.
  // Pour l'instant le choix des options est interactif (et l'implémentation est fournie)
  MoveSelector[] moveSelectors = playerNamesToMoveSelectors(playerNames);

  // Initialize controller
  Controller controller = new ControllerImpl(modelFactory, baseSize, playerNames);
  // Initialize visualizer
  boolean useColours = true; // Set to false if you're on windows and textual output looks weird.
  TextualVisualizer visualizer = new TextualVisualizer(useColours);

  // Proceed until game end
  while (!controller.isGameOver()) { // Voila la boucle prinicipale... continuer le jeu jusqu'à une personne a gagné
    printAndRequestAndPerformAction(controller, visualizer, moveSelectors);
  }
  System.out.println(visualizer.stringifyModel(controller.getModel()));
  System.out.println("GAME OVER!");
}

// Plus tard (TP4), quand on utilise des IAs, il y aura des autres "MoveSelectors" qui ne sont pas interactifs mais font leur propre choix.
private static MoveSelector[] playerNamesToMoveSelectors(String[] playerNames) {
  MoveSelector[] moveSelectors = new MoveSelector[playerNames.length];
  for (int i = 0; i < moveSelectors.length; i++) {
    moveSelectors[i] = new InteractiveMoveSelector();
  }
  return moveSelectors;
}

/**
 * Prints mode, possible moves and requests player interaction.
 */
private static void printAndRequestAndPerformAction(Controller controller, TextualVisualizer visualizer,
                                                    MoveSelector[] moveSelectors) {
  // Clear the screen (Works only on native ANSI terminals, not in IDE / windows)
  visualizer.clearScreen(); // Comment out this line if you're on windows.
  // Retrieve and visualize model
  System.out.println(visualizer.stringifyModel(controller.getModel()));
  // Print all possible actions:
  System.out.println(visualizer.getCurrentPlayerAnnouncement(controller.getModel()));
  System.out.println(visualizer.announcePossibleMoves(controller.getPlayerMoves()));
  // Request action and visualize choice (for current player)
  int currentPlayer = controller.getModel().getCurrentPlayer();
  List<Move> availableMoves = controller.getPlayerMoves();
  // if more than one move, ask selector
  Move selectedMove = null;
  if (availableMoves.size() > 1) {
    selectedMove = moveSelectors[currentPlayer].selectMove(availableMoves);
  } else {
    // If only one move available, directly apply it.
    selectedMove = availableMoves.getFirst();
  }
  System.out.println(visualizer.getChoseMoveAnnouncement(selectedMove, currentPlayer));
  // Perform selected action:
  controller.performMove(selectedMove);
  System.out.println("\n\n");
}

Code

Your submission must provide implementations for all previously unimplemented interface methods of the model and controller package.

  • getPlayerMoves: Determine where a player can move their figures. (See illustrations at end of document)
  • isGameOver: Tell whether a player has all their figures in their target zone.
  • performMove: Requests controller to modify model, by executing a provided move.

Don't reinvent the wheel

No need to implement the Move class or Field class. Those are provided to you, as part of the pre-configured dependencies.

Tests

Your test code

  • You are not required to extend more abstract classes than before. => You must add a new class, extending the existing AbstractStarControllerTest class to gain access to the provided tests, and allow for your submission to be testable.
  • To do so, create a new java class in src/test/java/ca/uqam/info/solanum/students/halma/controller/StarControllerTest.java (Double check it is in your test dossier)
  • Your class must extend the existing abstract class and provide an implementation for the abstract getController method:
    public class StarControllerTest extends AbstractStarControllerTest
    {
    
      @Override
      public Controller getController(int baseSize, String[] playerNames) {
        return new ControllerImpl(...); // The parameters of this constructor call vary, depending on your implementation.
      }
    }
    
  • However, you will need to write more unit tests for your new controller code, to reach the requested test coverage ratio.

Tests

As with the previous TP, all provided tests must pass, but I will test your submission beyound the provided tests.

Coverage

  • Your controller and model code must reach a coverage of at least 90% lines of code.
  • For the TP3, you are expected to integrate the jacoco plugin into your pom.xml configuration to reach the requested coverage. Your build must be configured to fail if the coverage is not reached.
  • If you are not familiar with jacoco, revise the CI lab session. A sample configuration is provided.

Build artifacts

  • Your build configuration must be configured to provide a self-contained jar, i.e. a jar file that can be used as-is, without further dependencies.
  • This jar has to be named halma.jar or Halma.jar.
  • You can test the jar-file produced by the package phase, using the above run instructions.
  • To produce a self-contained jar, you must edit the pom.xml to make use of two maven plugins: (samples have been demonstrated in class and are available on GitLab)
    • The maven-assembly plugin
    • The copy-rename plugin

Existing documentation

Your JavaDoc Documentation

Your code must be 100% documented:

  • All non-private classes must have a JavaDoc description
  • All non-private methods must have a JavaDoc description
  • If a method implements an existing interface, no additional documentation should be provided, as the interface is already documented. Simple place an @Override annotation before the method signature.

Use maven

Use mvn clean package to verify the state of your JavaDoc comments. It will tell you about missing or incorrect documentation.

Clutter

  • Keep your repository clutter-free. (See TP0 + TP1 + TP2)
  • Unless already done, can use a .gitignore file that excludes class files, OS hidden files and maven's target folder.
  • Do not commit binary or generated files (.class files, target folder, or generated javadoc)
  • Do not add any System.out.println statements to your code, this will break functionality in future TP milestones.

Plagiarism and ChatGPT

  • Unless explicitly allowed, do not use ChatGPT or other generative tools to generate code or documentation.
  • You can query the internet to find inspiration or help your understanding, but never copy-paste code without citing the source. Whenever you copy-paste, you MUST provide a reference / comment. Otherwise, it is legally considered plagiarism.
  • Use a Java-comment // ... to provide the source, e.g. the exact stackoverflow post
  • Do not share code with other teams
  • You can discuss, you can draw concepts on a piece of paper, but never pass-on code.
  • I will run automated plagiarism checks on all submission.
  • I am contractually obligated to report teams who copied to the university administration. This can have severe consequences for your studies, including lifetime expulsion form studies in Canada.

Plagiarism

Plagiarism, i.e. submitting work that is not your own (i.e. your team's), is a serious academic offense that can entail to expulsion from studies. Do not take any risks.

Contribution statement

You must include a contribution statement, which we may consider in case of sever imbalance between team member contributions. Use the template below, to include a file "contributions.md" at top level in your repo.

Other file names or file formats are not accepted (PDF, pages, html, ...). Make sure to name your file contributions.md

As file content for your contributions.md, use exactly the below template. Do not recreate the grid in another software (excel, numbers, paint, ...). Use your code-permanent as ID.

| ID  | First | Last | Contribution (%) |
|-----|-------|------|--------------|
| ... | ...   | ...  | ...          |
| ... | ...   | ...  | ...          |
| ... | ...   | ...  | ...          |

Cumulative contributions of all team members must add up to 100%.

Grading scheme

This TP is worth 15% of your total grade.

Criteria Max percentage
Cyclomatic complexity limit respected, no checkstyle warnings 25%
Undisclosed additional tests pass 20%
Functional JAR produced by package phase 15%
Test coverage > 90%, tested with jacoco 15%
Provided tests pass 10%
Javadoc enforced in pom, no warnings 5%
Repo clutter free 5%
Contribution statement 5%

Recommendation

Simulate the grading process before submission: Re-clone your own project and run mvn clean package and ensure your project can be built without warnings or errors, using the pom.xml provided for this TP3.

Desk rejection

Here is a checklist of what to avoid at all costs. If at least one item of the checklist applies, your submission will lose all code-related points. There may be other criteria for desk rejection.

  • Solution not provided in dedicated GitLab directory, or not on main branch.
  • Solution is distributed across multiple unmerged branches.
  • Repository contains zip file.
  • Code does not compile with provided maven command.
  • pom.xml has been semantically modified beyond integration of jacoco plugin / assembly plugin / copy-rename plugin. (Compared to TP3 specific, provided pom.xml)
  • Provided classes do not implement provided interfaces.
  • Implementation of abstract test classes is not provided in test package.
  • Project structure not respected.
  • Program attempts network communication at runtime.
  • Program stalls at runtime.
  • Copied code from ChatGPT / used Cursor / copied from other generative AI.
  • Copied from other teams.
  • Code copied from forums / online knowledge bases without giving credit (citation).

Additional illustrations

This section recapitulates games rules and illustrates the default UI representation of moves.

Game rule illustrations

Upon their turn, a player can only move one figure. However, if the figure has been moved, using a "jump", further repeated jumps with the same figure are allowed.

Certain rules must be respected when moving a figure:

  1. The target field must be vacant.
  2. The target field must be on the star shaped board (i.e. it is not allows to move figures off the terrain).
  3. Jumps are only allows to extended neighbours (the field behind a field, i.e. distance 2). Additionally, jumps are only allowed if the intermediate field is not vacant. However, if not vacant, it does not matter to which player the intermediate figure belongs.

Controller moves

This section provides additional illustration for how the getMoves method is expected to operate.

If a player performed a jump, i.e. previously moved a figure by jumping across another figure, the game does not pass on to the next player. However...

  • For the remainder of the player's turn, all follow-up moves (as proposed by the getMoves method) must only list the previously moved figure.
  • The player is not allows to perform anti-jumps, i.e. the getMoves result must not contain a move reverting a figure back to the field it just came from. Larger circles that lead back to the original Field are allowed though.
  • The player is not obligated to keep jumping their figure, even if possible. To cover this option, getMoves must include a special "Null-move", which has identical source and target field (current position of the figure).
  • To end their turn after having done at least one jump, the player must explicitly use a final "Null-move".

Do not over-optimize

It is possible that only a single move is possible (e.g. ending a series of jumps with a "Null move"). The controller must not automatically apply alternative-free options, but still return single possible options. The provided blackboard sample code does however include a check to automatically select moves if they are free of alternatives.

Example 1

Implicitly selecting the only option offered by controller's getMoves method: * After the initial jump, the controller's getMoves method returned only a single option: the Null-move. * However, the controller did not implicitly apply the only available move. * It is the UI's blackboard loop, which did not ask the player to confirm (their only) option, as it is free of alternatives.

$ java -jar halma.jar 2 Max Ryan Roman
y\x |  00  01  02  03  04  05  06  07  08
----+------------------------------------
00  |         [ ]             [1]
01  |             [ ]     [1]
02  |         [ ]     ( )     [1]
03  |             ( )     ( )
04  |         ( )     ( )     ( )
05  |     [0]     ( )     ( )     [ ]
06  | [0]     ( )     ( )     ( )     [ ]
07  |     [0]     ( )     ( )     [ ]
08  |         ( )     ( )     ( )
09  |             ( )     ( )
10  |         [ ]     ( )     [2]
11  |             [ ]     [2]
12  |         [ ]             [2]
It's Max's turn. Max, your options are:
Available moves:
    00: (01,05) -> (02,04)  01: (01,05) -> (02,06)  02: (00,06) => (02,04)
    03: (00,06) => (02,08)  04: (01,07) -> (02,06)  05: (01,07) -> (02,08)

Enter move ID:
3
Chosen move: (00,06) => (02,08)
y\x |  00  01  02  03  04  05  06  07  08
----+------------------------------------
00  |         [ ]             [1]
01  |             [ ]     [1]
02  |         [ ]     ( )     [1]
03  |             ( )     ( )
04  |         ( )     ( )     ( )
05  |     [0]     ( )     ( )     [ ]
06  | [ ]     ( )     ( )     ( )     [ ]
07  |     [0]     ( )     ( )     [ ]
08  |         (0)     ( )     ( )
09  |             ( )     ( )
10  |         [ ]     ( )     [2]
11  |             [ ]     [2]
12  |         [ ]             [2]
It's Max's turn. Max, your options are:
Available moves:
    00: (02,08) == (02,08)
Chosen move: (02,08) == (02,08)
y\x |  00  01  02  03  04  05  06  07  08
----+------------------------------------
00  |         [ ]             [1]
01  |             [ ]     [1]
02  |         [ ]     ( )     [1]
03  |             ( )     ( )
04  |         ( )     ( )     ( )
05  |     [0]     ( )     ( )     [ ]
06  | [ ]     ( )     ( )     ( )     [ ]
07  |     [0]     ( )     ( )     [ ]
08  |         (0)     ( )     ( )
09  |             ( )     ( )
10  |         [ ]     ( )     [2]
11  |             [ ]     [2]
12  |         [ ]             [2]

Example 2

Omitting an anti jump: * After a jump move (00,06) => (02,08), the player has only two options: 1. continuing with another jump (upwards): (02,08) => (02,04) 2. ending their turn, by an explicit Null-move: (02,08) == (02,08) 3. The anti-jump, i.e. returning to (00,06), is not allowed. 4. Moving any other figure than the one who previously jumped is not allowed.

y\x |  00  01  02  03  04  05  06  07  08
----+------------------------------------
00  |         [ ]             [ ]
01  |             [ ]     [1]
02  |         [ ]     (1)     [1]
03  |             ( )     ( )
04  |         ( )     ( )     ( )
05  |     [ ]     ( )     ( )     [ ]
06  | [0]     (0)     ( )     ( )     [ ]
07  |     [0]     ( )     ( )     [ ]
08  |         ( )     ( )     (2)
09  |             ( )     ( )
10  |         [ ]     ( )     [ ]
11  |             [ ]     [2]
12  |         [ ]             [2]
It's Max's turn. Max, your options are:
Available moves:
    00: (02,06) -> (02,04)  01: (02,06) -> (03,05)  02: (02,06) -> (01,05)
    03: (02,06) -> (03,07)  04: (02,06) -> (02,08)  05: (00,06) -> (01,05)
    06: (00,06) => (02,08)  07: (01,07) -> (01,05)  08: (01,07) => (03,05)
    09: (01,07) -> (02,08)
Enter move ID:
6
Chosen move: (00,06) => (02,08)
y\x |  00  01  02  03  04  05  06  07  08
----+------------------------------------
00  |         [ ]             [ ]
01  |             [ ]     [1]
02  |         [ ]     (1)     [1]
03  |             ( )     ( )
04  |         ( )     ( )     ( )
05  |     [ ]     ( )     ( )     [ ]
06  | [ ]     (0)     ( )     ( )     [ ]
07  |     [0]     ( )     ( )     [ ]
08  |         (0)     ( )     (2)
09  |             ( )     ( )
10  |         [ ]     ( )     [ ]
11  |             [ ]     [2]
12  |         [ ]             [2]
It's Max's turn. Max, your options are:
Available moves:
    00: (02,08) == (02,08)  01: (02,08) => (02,04)
Enter move ID:

Move syntax samples

Here is a short reminder of how to read serialized Move objects:

  • (00,00) -> (01,01): Standard move, moving figure on field to an adjacent neighbour.
  • (00,00) => (02,02): Jump move, moving figure on extended field to an adjacent neighbour. Can only be performed if the field in-between is not vacant.
  • (00,00) == (00,00): Null-move: Used to indicate end a series (or a single) jump. The figure stays where it is.

Note: Anti-jumps are not allowed in any case, i.e. a jump move must not be followed by a jump back to the original position. This is a design decision to limit "endless" player turns where a figure jumps back and forth.