diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml
new file mode 100644
index 0000000..0941723
--- /dev/null
+++ b/.github/workflows/run_tests.yml
@@ -0,0 +1,21 @@
+name: Run tests
+
+on: [push]
+
+jobs:
+ run_tests_job:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Set up JDK 21
+ uses: actions/setup-java@v3
+ with:
+ java-version: '21'
+ distribution: 'temurin'
+
+ - name: Run tests
+ working-directory: .github/workflows/run_tests
+ run: |
+ ./run_tests.sh
\ No newline at end of file
diff --git a/.github/workflows/run_tests/junit-platform-console-standalone-1.10.2.jar b/.github/workflows/run_tests/junit-platform-console-standalone-1.10.2.jar
new file mode 100644
index 0000000..7ca10e6
Binary files /dev/null and b/.github/workflows/run_tests/junit-platform-console-standalone-1.10.2.jar differ
diff --git a/.github/workflows/run_tests/run_tests.sh b/.github/workflows/run_tests/run_tests.sh
new file mode 100755
index 0000000..b715eee
--- /dev/null
+++ b/.github/workflows/run_tests/run_tests.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+echo "Build in progress..."
+
+javac -version
+
+# ChaCuN/scripts/run_tests
+ROOT_DIR=../../..
+
+find "${ROOT_DIR}/src/ch/epfl/chacun" -name "*.java" > src_files.txt
+find "${ROOT_DIR}/test/ch/epfl/chacun" -name "*.java" > test_files.txt
+
+rm -rf ./out
+
+# build production class
+javac -d out/production/classes @src_files.txt
+# build test class
+javac -d out/test/classes -classpath out/production/classes:junit-platform-console-standalone-1.10.2.jar @test_files.txt
+java -jar junit-platform-console-standalone-1.10.2.jar execute -cp out/production/classes:out/test/classes: --select-package ch.epfl.chacun --reports-dir reports
+
+grep -q "failures=\"0\"" reports/TEST-junit-jupiter.xml || exit 1
+
+echo "Build finished."
diff --git a/.idea/.gitignore b/.idea/.gitignore
index 13566b8..a9d7db9 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -6,3 +6,5 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
+# GitHub Copilot persisted chat sessions
+/copilot/chatSessions
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 9d2ecf0..fa5b436 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,5 +1,6 @@
+
-
+
\ No newline at end of file
diff --git a/src/ch/epfl/chacun/Board.java b/src/ch/epfl/chacun/Board.java
new file mode 100644
index 0000000..7eb34b0
--- /dev/null
+++ b/src/ch/epfl/chacun/Board.java
@@ -0,0 +1,310 @@
+package ch.epfl.chacun;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents the board of the game.
+ *
+ * This board will have a size of 625 (25×25) elements, and will be organized in reading order,
+ * starting from the cell at the top left of the board, and going through the rows before the columns.
+ *
+ * @author Maxence Espagnet (sciper: 372808)
+ * @author Balthazar Baillat (sciper: 373420)
+ */
+public final class Board {
+
+ public static final int REACH = 12;
+ public static final Board EMPTY = new Board(new PlacedTile[625], new int[0], ZonePartitions.EMPTY, Set.of());
+ private static final int SIZE = 25;
+ private final PlacedTile[] placedTiles;
+ private final int[] tileIndices;
+ private final ZonePartitions zonePartitions;
+ private final Set cancelledAnimals;
+
+ /**
+ * Private constructor of the board to initialize values.
+ */
+ private Board(PlacedTile[] placedTiles, int[] tileIndices, ZonePartitions zonePartitions, Set cancelledAnimals) {
+ this.placedTiles = placedTiles;
+ this.tileIndices = tileIndices;
+ this.zonePartitions = zonePartitions;
+ this.cancelledAnimals = cancelledAnimals;
+ }
+
+ /**
+ * Calculates the row major index of the given position.
+ *
+ * @param pos the position
+ * @return the row major index of the given position
+ */
+ private int calculateRowMajorIndex(Pos pos) {
+ int originIndex = (placedTiles.length - 1) / 2;
+ return originIndex + pos.x() + pos.y() * SIZE;
+ }
+
+ /**
+ * Returns the tile in the given position, or null if there is none or if the position is off the board.
+ *
+ * @param pos the position of the tile
+ * @return the tile in the given position, or null if there is none or if the position is off the board
+ */
+ public PlacedTile tileAt(Pos pos) {
+ int index = calculateRowMajorIndex(pos);
+ if (index < 0 || index >= placedTiles.length) {
+ return null;
+ }
+ return placedTiles[index];
+ }
+
+ /**
+ * Returns the tile with the given id.
+ *
+ * @param tileId the id of the tile
+ * @return the tile with the given id, if found
+ * @throws IllegalArgumentException if no tile with the given id is found
+ */
+ public PlacedTile tileWithId(int tileId) {
+ for (int placedTileIndex : tileIndices) {
+ PlacedTile placedTile = placedTiles[placedTileIndex];
+ if (placedTile.tile().id() == tileId) {
+ return placedTile;
+ }
+ }
+ throw new IllegalArgumentException("No tile with the given id found.");
+ }
+
+ /**
+ * Returns the cancelled animals.
+ * Cancelled animals can be, for example, deer eaten by smilodons.
+ *
+ * @return the set of cancelled animals
+ */
+ public Set cancelledAnimals() {
+ // Defensive copy
+ return new HashSet<>(cancelledAnimals);
+ }
+
+ /**
+ * Return all the occupants on the tiles placed on the board.
+ *
+ * @return the set of all occupants on the board
+ */
+ public Set occupants() {
+ Set occupants = new HashSet<>();
+ for (int placedTileIndex : tileIndices) {
+ occupants.add(placedTiles[placedTileIndex].occupant());
+ }
+ return occupants;
+ }
+
+ /**
+ * Returns the forest area containing the given zone.
+ *
+ * @param forest the forest zone
+ * @return the area containing the given zone
+ */
+ public Area forestArea(Zone.Forest forest) {
+ return zonePartitions.forests().areaContaining(forest);
+ }
+
+ /**
+ * Returns the meadow area containing the given zone.
+ *
+ * @param meadow the meadow zone
+ * @return the area containing the given zone
+ */
+ public Area meadowArea(Zone.Meadow meadow) {
+ return zonePartitions.meadows().areaContaining(meadow);
+ }
+
+ /**
+ * Returns the river area containing the given zone.
+ *
+ * @param riverZone the river zone
+ * @return the area containing the given zone
+ */
+ public Area riverArea(Zone.River riverZone) {
+ return zonePartitions.rivers().areaContaining(riverZone);
+ }
+
+ /**
+ * Returns the river system area containing the given zone.
+ *
+ * @param waterZone the water zone
+ * @return the area containing the given zone
+ */
+ public Area riverSystemArea(Zone.Water waterZone) {
+ return zonePartitions.riverSystems().areaContaining(waterZone);
+ }
+
+ /**
+ * Returns all meadow areas of the board.
+ *
+ * @return the set of all meadow areas of the board
+ */
+ public Set> meadowAreas() {
+ return zonePartitions.meadows().areas();
+ }
+
+ /**
+ * Returns all river system areas of the board.
+ *
+ * @return the set of all river system areas of the board
+ */
+ public Set> riverSystemAreas() {
+ return zonePartitions.riverSystems().areas();
+ }
+
+ /**
+ * Returns the meadow adjacent to the given zone, in the form of an area which contains only
+ * the zones of this meadow but all the occupants of the complete meadow,
+ * and which, for simplicity, has no open connections.
+ *
+ * @param pos the position of the tile
+ * @param meadowZone the meadow zone
+ * @return the meadow adjacent to the given zone
+ */
+ public Area adjacentMeadow(Pos pos, Zone.Meadow meadowZone) {
+ Area originalArea = meadowArea(meadowZone);
+ Set adjacentZones = new HashSet<>();
+ for (Zone.Meadow zone : originalArea.zones()) {
+ // Get the placed tile with the given id
+ PlacedTile tile = tileWithId(zone.tileId());
+ // Check if the given tile is adjacent to the given one
+ int dX = Math.abs(pos.x() - tile.pos().x());
+ int dY = Math.abs(pos.y() - tile.pos().y());
+ if (dX <= 1 && dY <= 1) {
+ adjacentZones.add(zone);
+ }
+ }
+ // Create the adjacent area, with the same occupants
+ return new Area<>(adjacentZones, originalArea.occupants(), 0);
+ }
+
+ /**
+ * Return the number of occupants of the given type belonging to the given player on the board.
+ *
+ * @param player the player
+ * @param occupantKind the occupant kind
+ * @return the number of occupants of the given type belonging to the given player on the board
+ */
+ public int occupantCount(PlayerColor player, Occupant.Kind occupantKind) {
+ int occupantCount = 0;
+ for (int placedTileIndex : tileIndices) {
+ PlacedTile placedTile = placedTiles[placedTileIndex];
+ if (placedTile.occupant().kind() == occupantKind && placedTile.placer() == player) {
+ occupantCount++;
+ }
+ }
+ return occupantCount;
+ }
+
+ /**
+ * Returns the set of all insertion positions on the board.
+ * An insertion position is a position on the board where a tile can be placed.
+ *
+ * @return the set of all insertion positions on the board
+ */
+ public Set insertionPositions() {
+ Set insertionPositions = new HashSet<>();
+ for (int placedTileIndex : tileIndices) {
+ PlacedTile placedTile = placedTiles[placedTileIndex];
+ for (Direction direction : Direction.ALL) {
+ Pos neighbor = placedTile.pos().neighbor(direction);
+ if (tileAt(neighbor) == null) {
+ insertionPositions.add(neighbor);
+ }
+ }
+ }
+ return insertionPositions;
+ }
+
+ /**
+ * Returns the last placed tile on the board.
+ *
+ * It can be the starting tile if the first normal tile has not yet been placed, or null if the board is empty
+ *
+ * @return the last placed tile on the board
+ */
+ public PlacedTile lastPlacedTile() {
+ if (tileIndices.length > 0) {
+ return placedTiles[tileIndices[tileIndices.length - 1]];
+ }
+ return null;
+ }
+
+ public Set> forestsClosedByLastTile() {
+ PlacedTile lastTile = lastPlacedTile();
+ // TODO: implement this method
+ return Set.of();
+ }
+
+ public Set> riversClosedByLastTile() {
+ // TODO: implement this method
+ return Set.of();
+ }
+
+ /**
+ * Returns true if the given placed tile can be added to the board.
+ *
+ * i.e. if its position is an insertion position and every edge of the tile that touches
+ * an edge of a tile already placed is of the same kind as it.
+ *
+ * @param tile the placed tile to check
+ * @return true if the given placed tile can be added to the board
+ */
+ public boolean canAddTile(PlacedTile tile) {
+ // Check if the tile cannot be placed on the board
+ if (!insertionPositions().contains(tile.pos())) {
+ return false;
+ }
+ // Check for potential conflicts with adjacent tiles
+ for (Direction direction : Direction.ALL) {
+ Pos neighbor = tile.pos().neighbor(direction);
+ PlacedTile neighborTile = tileAt(neighbor);
+ if (neighborTile != null) {
+ TileSide neighborSide = neighborTile.side(direction.opposite());
+ TileSide tileSide = tile.side(direction);
+ if (!tileSide.isSameKindAs(neighborSide)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public boolean couldPlaceTile(Tile tile) {
+ // TODO: implement this method
+ return false;
+ }
+
+ /**
+ * Returns an identical board, but with the given tile in addition.
+ *
+ * @param tile the tile to place
+ * @return an identical board, but with the given tile in addition
+ * @throws IllegalArgumentException if the board is not empty and the given tile cannot be added to the board
+ */
+ public Board withNewTile(PlacedTile tile) {
+ // Check if the tile can be placed on the board
+ if (placedTiles.length != 0 && !canAddTile(tile)) {
+ throw new IllegalArgumentException("The tile cannot be placed on the board.");
+ }
+ // Create the new placed tiles array
+ int newTileIndex = calculateRowMajorIndex(tile.pos());
+ PlacedTile[] newPlacedTiles = placedTiles.clone();
+ newPlacedTiles[newTileIndex] = tile;
+ // Create the new tile indices array
+ int[] newTileIndices = new int[tileIndices.length + 1];
+ System.arraycopy(tileIndices, 0, newTileIndices, 0, tileIndices.length);
+ newTileIndices[tileIndices.length] = newTileIndex;
+ // Create a new board with the new tile
+ return new Board(newPlacedTiles, newTileIndices, zonePartitions, Set.copyOf(cancelledAnimals));
+ }
+
+ public Board withOccupant(Occupant occupant) {
+ // TODO: implement this method
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/src/ch/epfl/chacun/MessageBoard.java b/src/ch/epfl/chacun/MessageBoard.java
new file mode 100644
index 0000000..e40beeb
--- /dev/null
+++ b/src/ch/epfl/chacun/MessageBoard.java
@@ -0,0 +1,319 @@
+package ch.epfl.chacun;
+
+import java.util.*;
+
+/**
+ * Represents the message board of the game.
+ *
+ * @param textMaker the text maker used to create the messages
+ * @param messages the list of messages on the message board
+ */
+public record MessageBoard(TextMaker textMaker, List messages) {
+
+ /**
+ * Defensive copy of the list of messages.
+ */
+ public MessageBoard {
+ messages = List.copyOf(messages);
+ }
+
+ /**
+ * Returns the points scored by each player.
+ *
+ * @return the points scored by each player
+ */
+ public Map points() {
+ Map scorers = new HashMap<>();
+ for (Message message : messages) {
+ for (PlayerColor scorer : message.scorers) {
+ scorers.put(scorer, scorers.getOrDefault(scorer, 0) + message.points);
+ }
+ }
+ return scorers;
+ }
+
+ /**
+ * Counts the number of animals of each kind in the given set of animals.
+ *
+ * @param animals the set of animals
+ * @return the number of animals of each kind in the given set of animals
+ */
+ private Map countAnimals(Set animals) {
+ Map animalCount = new HashMap<>();
+ for (Animal.Kind kind : Animal.Kind.values()) {
+ animalCount.put(kind, (int) animals.stream().filter(a -> a.kind() == kind).count());
+ }
+ return animalCount;
+ }
+
+ /**
+ * Returns the same message board, unless the given forest is occupied,
+ * in which case the message board contains a new message indicating that its majority occupants
+ * have won the points associated with its closure.
+ *
+ * @param forest the forest that has been closed
+ * @return the same message board, or a new one with a message added if the forest is occupied
+ */
+ public MessageBoard withScoredForest(Area forest) {
+ if (forest.isOccupied()) {
+ ArrayList messages = new ArrayList<>(this.messages);
+ // Calculate the data needed
+ int mushroomGroupCount = Area.mushroomGroupCount(forest);
+ int tileCount = forest.tileIds().size();
+ int points = Points.forClosedForest(tileCount, mushroomGroupCount);
+ String messageContent = textMaker
+ .playersScoredForest(forest.majorityOccupants(), points, mushroomGroupCount, tileCount);
+ // Create the message
+ messages.add(new Message(messageContent, points, forest.majorityOccupants(), forest.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board, but with a new message indicating that the player
+ * has the right to play a second turn after closing the forest, because it contains at least a menhir.
+ *
+ * @param player the player who closed the forest
+ * @param forest the closed forest
+ * @return the same message board, or a new one with a message added if the forest contains a menhir
+ */
+ public MessageBoard withClosedForestWithMenhir(PlayerColor player, Area forest) {
+ ArrayList messages = new ArrayList<>(this.messages);
+ String messageContent = textMaker.playerClosedForestWithMenhir(player);
+ // Create the message
+ messages.add(new Message(messageContent, 0, Set.of(player), forest.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+
+ /**
+ * Returns the same message board, unless the given river is occupied,
+ * in which case the message board contains a new message indicating that its majority occupants
+ * have won the points associated with its closure.
+ *
+ * @param river the river that has been closed
+ * @return the same message board, or a new one with a message added if the river is occupied
+ */
+ public MessageBoard withScoredRiver(Area river) {
+ if (river.isOccupied()) {
+ ArrayList messages = new ArrayList<>(messages());
+ // Calculate the data needed
+ int tileCount = river.tileIds().size();
+ int fishCount = Area.riverFishCount(river);
+ int points = Points.forClosedRiver(tileCount, fishCount);
+ String messageContent = textMaker
+ .playersScoredRiver(river.majorityOccupants(), points, fishCount, tileCount);
+ // Create the message
+ messages.add(new Message(messageContent, points, river.majorityOccupants(), river.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board, unless the laying of the hunting trap has
+ * resulted in points for the given player who laid it, in which case the scoreboard contains
+ * a new message pointing this out.
+ *
+ * @param scorer the player who laid the hunting trap
+ * @param adjacentMeadow the meadow adjacent to the hunting trap
+ * @return
+ */
+ public MessageBoard withScoredHuntingTrap(PlayerColor scorer, Area adjacentMeadow) {
+ Set animals = Area.animals(adjacentMeadow, new HashSet<>());
+ if (!animals.isEmpty()) {
+ ArrayList messages = new ArrayList<>(this.messages);
+ // Calculate the data needed
+ Map animalCount = countAnimals(animals);
+ int points = Points.forMeadow(
+ animalCount.get(Animal.Kind.MAMMOTH),
+ animalCount.get(Animal.Kind.AUROCHS),
+ animalCount.get(Animal.Kind.DEER));
+ // Create the message
+ String messageContent = textMaker.playerScoredHuntingTrap(scorer, points, animalCount);
+ messages.add(new Message(messageContent, points, Set.of(scorer), adjacentMeadow.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board, but with a new message indicating that the given player has obtained
+ * the points corresponding to placing the logboat in the given river system.
+ *
+ * @param scorer the player who placed the logboat
+ * @param riverSystem the river system in which the logboat was placed
+ * @return the same message board, or a new one with a message added if the river system has a log boat
+ */
+ public MessageBoard withScoredLogboat(PlayerColor scorer, Area riverSystem) {
+ ArrayList messages = new ArrayList<>(this.messages);
+ // Calculate the data needed
+ int lakeCount = Area.lakeCount(riverSystem);
+ int points = Points.forLogboat(lakeCount);
+ String messageContent = textMaker.playerScoredLogboat(scorer, lakeCount, points);
+ // Create the message
+ messages.add(new Message(messageContent, points, Set.of(scorer), riverSystem.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+
+ /**
+ * Returns the same message board, unless the given river system is occupied and the points it yields
+ * to its majority occupants are greater than 0, in which case the message board contains a new message
+ * indicating that these players have won the points in question.
+ *
+ * @param riverSystem the river system that has been scored
+ * @return the same message board, or a new one with a message added if the river system is occupied
+ * and points are scored
+ */
+ public MessageBoard withScoredRiverSystem(Area riverSystem) {
+ if (riverSystem.isOccupied()) {
+ // Calculate the data needed
+ int fishCount = Area.riverSystemFishCount(riverSystem);
+ int points = Points.forRiverSystem(fishCount);
+ Set scorers = riverSystem.majorityOccupants();
+ // Don't create a message if no points are scored
+ if (points > 0) {
+ String messageContent = textMaker.playersScoredRiverSystem(riverSystem.majorityOccupants(),
+ points, fishCount);
+ // Create the message
+ ArrayList messages = new ArrayList<>(this.messages);
+ messages.add(new Message(messageContent, points, scorers, riverSystem.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board, unless the given meadow is occupied and the points it yields
+ * to its majority occupants are greater than 0, in which case the message board contains a
+ * new message indicating that these players have won the points in question.
+ *
+ * The points are calculated as if the given canceled animals didn't exist.
+ *
+ * @param meadow the meadow that has been scored
+ * @param cancelledAnimals the animals that have been cancelled
+ * @return the same message board, or a new one with a message added if the meadow is occupied
+ * and points are scored
+ */
+ public MessageBoard withScoredMeadow(Area meadow, Set cancelledAnimals) {
+ if (meadow.isOccupied()) {
+ // Calculate the data needed
+ Set animals = Area.animals(meadow, cancelledAnimals);
+ Map animalCount = countAnimals(animals);
+ int points = Points.forMeadow(
+ animalCount.get(Animal.Kind.MAMMOTH),
+ animalCount.get(Animal.Kind.AUROCHS),
+ animalCount.get(Animal.Kind.DEER));
+
+ Set scorers = meadow.majorityOccupants();
+ // Don't create a message if no points are scored
+ if (points > 0) {
+ String messageContent = textMaker.playersScoredMeadow(scorers, points, animalCount);
+ // Create the message
+ ArrayList messages = new ArrayList<>(this.messages);
+ messages.add(new Message(messageContent, points, scorers, meadow.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board, unless the given meadow, is occupied and the points it yields to
+ * its majority occupants are greater than 0, in which case the message board contains a new message
+ * indicating that these players have won the points in question;
+ *
+ * As with the "small" stake pit, the given meadow has the same occupants as the meadow containing
+ * the pit, but only the areas within its reach.
+ *
+ * The points are calculated as if the given canceled animals didn't exist.
+ *
+ * @param adjacentMeadow the meadow adjacent to the pit trap
+ * @param cancelledAnimals the animals that have been cancelled
+ * @return the same message board, or a new one with a message added if the meadow is occupied
+ * and points are scored
+ */
+ public MessageBoard withScoredPitTrap(Area adjacentMeadow, Set cancelledAnimals) {
+ if (adjacentMeadow.isOccupied()) {
+ Set animals = Area.animals(adjacentMeadow, new HashSet<>());
+ Map animalCount = countAnimals(animals);
+ int points = Points.forMeadow(
+ animalCount.get(Animal.Kind.MAMMOTH),
+ animalCount.get(Animal.Kind.AUROCHS),
+ animalCount.get(Animal.Kind.DEER));
+
+ Set scorers = adjacentMeadow.majorityOccupants();
+ // Don't create a message if no points are scored
+ if (points > 0) {
+ String messageContent = textMaker.playersScoredPitTrap(scorers, points, animalCount);
+ ArrayList messages = new ArrayList<>(this.messages);
+ messages.add(new Message(messageContent, points, scorers, adjacentMeadow.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board, unless the given river network, which contains the raft,
+ * is occupied, in which case the message board contains a new message indicating that its
+ * majority occupants have won the corresponding points.
+ *
+ * @param riverSystem the river system containing the raft
+ * @return the same message board, or a new one with a message added if the river system is occupied
+ */
+ public MessageBoard withScoredRaft(Area riverSystem) {
+ if (riverSystem.isOccupied()) {
+ ArrayList messages = new ArrayList<>(this.messages);
+ // Calculate the data needed
+ Set scorers = riverSystem.majorityOccupants();
+ int lakeCount = Area.lakeCount(riverSystem);
+ int points = Points.forRaft(lakeCount);
+ String messageContent = textMaker.playersScoredRaft(scorers, lakeCount, points);
+ // Create the message
+ messages.add(new Message(messageContent, points, scorers, riverSystem.tileIds()));
+ return new MessageBoard(textMaker, messages);
+ }
+ return this;
+ }
+
+ /**
+ * Returns the same message board with a new message indicating that the given players have won the game.
+ *
+ * @param winners the set of players who have won the game
+ * @param points the points of the winners
+ * @return the same message board with a game won message
+ */
+ public MessageBoard withWinners(Set winners, int points) {
+ ArrayList messages = new ArrayList<>(this.messages);
+ String messageContent = textMaker.playersWon(winners, points);
+ messages.add(new Message(messageContent, points, winners, Set.of()));
+ return new MessageBoard(textMaker, messages);
+ }
+
+ /**
+ * Represents a message on the message board.
+ *
+ * @param text the text of the message
+ * @param points the points associated with the message
+ * @param scorers the players who have scored the points
+ * @param tileIds the ids of the tiles involved in the message
+ */
+ public record Message(String text, int points, Set scorers, Set tileIds) {
+
+ /**
+ * Validates the given text, points, scorers and tileIds.
+ */
+ public Message {
+ Objects.requireNonNull(text);
+ Preconditions.checkArgument(points >= 0);
+ // Defensive copy
+ scorers = Set.copyOf(scorers);
+ tileIds = Set.copyOf(tileIds);
+ }
+
+
+ }
+
+}
diff --git a/src/ch/epfl/chacun/PlacedTile.java b/src/ch/epfl/chacun/PlacedTile.java
index 848d66a..d9a461f 100644
--- a/src/ch/epfl/chacun/PlacedTile.java
+++ b/src/ch/epfl/chacun/PlacedTile.java
@@ -147,8 +147,7 @@ public Set potentialOccupants() {
if (!(zone instanceof Zone.Lake)) {
potentialOccupants.add(new Occupant(Occupant.Kind.PAWN, zone.id()));
}
- // A hut can only be placed on a lake if it is connected to a river
- // or on a river if there's no lake
+ // A hut can only be placed on a lake or on a river if there's no lake
if (zone instanceof Zone.Lake || (zone instanceof Zone.River river && !river.hasLake())) {
potentialOccupants.add(new Occupant(Occupant.Kind.HUT, zone.id()));
}
diff --git a/src/ch/epfl/chacun/TextMaker.java b/src/ch/epfl/chacun/TextMaker.java
new file mode 100644
index 0000000..68b9a48
--- /dev/null
+++ b/src/ch/epfl/chacun/TextMaker.java
@@ -0,0 +1,150 @@
+package ch.epfl.chacun;
+
+import java.util.Map;
+import java.util.Set;
+
+
+public interface TextMaker {
+ /**
+ * Returns the name of the player of the given color.
+ *
+ * @param playerColor the color of the player
+ * @return the name of the player
+ */
+ String playerName(PlayerColor playerColor);
+
+ /**
+ * Returns the textual representation of the given number of points (e.g., "3 points").
+ *
+ * @param points the number of points
+ * @return the textual representation of the number of points
+ */
+ String points(int points);
+
+ /**
+ * Returns the text of a message declaring that a player has closed a forest with a menhir.
+ *
+ * @param player the player who closed the forest
+ * @return the text of the message
+ */
+ String playerClosedForestWithMenhir(PlayerColor player);
+
+ /**
+ * Returns the text of a message declaring that the majority occupants of a newly
+ * closed forest, consisting of a certain number of tiles and containing a certain number of mushroom groups,
+ * have won the corresponding points.
+ *
+ * @param scorers the majority occupants of the forest
+ * @param points the points won
+ * @param mushroomGroupCount the number of mushroom groups that the forest contains
+ * @param tileCount the number of tiles that make up the forest
+ * @return the text of the message
+ */
+ String playersScoredForest(Set scorers, int points, int mushroomGroupCount, int tileCount);
+
+ /**
+ * Returns the text of a message declaring that the majority occupants of a newly
+ * closed river, consisting of a certain number of tiles and containing a certain number of fish,
+ * have won the corresponding points.
+ *
+ * @param scorers the majority occupants of the river
+ * @param points the points won
+ * @param fishCount the number of fish swimming in the river or adjacent lakes
+ * @param tileCount the number of tiles that make up the river
+ * @return the text of the message
+ */
+ String playersScoredRiver(Set scorers, int points, int fishCount, int tileCount);
+
+ /**
+ * Returns the text of a message declaring that a player has placed the pit trap in a meadow containing,
+ * on the 8 neighboring tiles of the pit, certain animals, and won the corresponding points.
+ *
+ * @param scorer the player who placed the pit trap
+ * @param points the points won
+ * @param animals the animals present in the same meadow as the pit and on the 8 neighboring tiles
+ * @return the text of the message
+ */
+ String playerScoredHuntingTrap(PlayerColor scorer, int points, Map animals);
+
+ /**
+ * Returns the text of a message declaring that a player has placed the logboat in a river system
+ * containing a certain number of lakes, and won the corresponding points.
+ *
+ * @param scorer the player who placed the logboat
+ * @param points the points won
+ * @param lakeCount the number of lakes accessible to the logboat
+ * @return the text of the message
+ */
+ String playerScoredLogboat(PlayerColor scorer, int points, int lakeCount);
+
+ /**
+ * Returns the text of a message declaring that the majority occupants of a meadow containing certain
+ * animals have won the corresponding points.
+ *
+ * @param scorers the majority occupants of the meadow
+ * @param points the points won
+ * @param animals the animals present in the meadow (excluding those previously cancelled)
+ * @return the text of the message
+ */
+ String playersScoredMeadow(Set scorers, int points, Map animals);
+
+ /**
+ * Returns the text of a message declaring that the majority occupants of a river system
+ * containing a certain number of fish have won the corresponding points.
+ *
+ * @param scorers the majority occupants of the river system
+ * @param points the points won
+ * @param fishCount the number of fish swimming in the river system
+ * @return the text of the message
+ */
+ String playersScoredRiverSystem(Set scorers, int points, int fishCount);
+
+ /**
+ * Returns the text of a message declaring that the majority occupants of a meadow containing the
+ * large pit trap and, on the 8 neighboring tiles of it, certain animals, have won the
+ * corresponding points.
+ *
+ * @param scorers the majority occupants of the meadow containing the pit trap
+ * @param points the points won
+ * @param animals the animals present on the tiles neighboring the pit (excluding those previously cancelled)
+ * @return the text of the message
+ */
+ String playersScoredPitTrap(Set scorers, int points, Map animals);
+
+ /**
+ * Returns the text of a message declaring that the majority occupants of a river system
+ * containing the raft have won the corresponding points.
+ *
+ * @param scorers the majority occupants of the river system containing the raft
+ * @param points the points won
+ * @param lakeCount the number of lakes contained in the river system
+ * @return the text of the message
+ */
+ String playersScoredRaft(Set scorers, int points, int lakeCount);
+
+ /**
+ * Returns the text of a message declaring that one or more players have won the game, with a
+ * certain number of points.
+ *
+ * @param winners the set of players who have won the game
+ * @param points the points of the winners
+ * @return the text of the message
+ */
+ String playersWon(Set winners, int points);
+
+ /**
+ * Returns a text asking the current player to click on the occupant they wish to place, or on the text
+ * of the message if they do not wish to place any occupant.
+ *
+ * @return the text in question
+ */
+ String clickToOccupy();
+
+ /**
+ * Returns a text asking the current player to click on the pawn they wish to take back, or on the text
+ * of the message if they do not wish to take back any pawn.
+ *
+ * @return the text in question
+ */
+ String clickToUnoccupy();
+}
diff --git a/src/ch/epfl/chacun/ZonePartition.java b/src/ch/epfl/chacun/ZonePartition.java
index f3bb3ff..fcbeaba 100644
--- a/src/ch/epfl/chacun/ZonePartition.java
+++ b/src/ch/epfl/chacun/ZonePartition.java
@@ -25,7 +25,7 @@ public record ZonePartition(Set> areas) {
* Additional constructor to construct a partition with an empty set of areas.
*/
public ZonePartition() {
- this(new HashSet<>());
+ this(Set.of());
}
/**
diff --git a/src/ch/epfl/chacun/ZonePartitions.java b/src/ch/epfl/chacun/ZonePartitions.java
index dd59eef..a82dcbb 100644
--- a/src/ch/epfl/chacun/ZonePartitions.java
+++ b/src/ch/epfl/chacun/ZonePartitions.java
@@ -1,37 +1,198 @@
package ch.epfl.chacun;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Represents the partition which regroups the four partitions of the different zones.
+ *
+ * @param forests the forests partition
+ * @param meadows the meadows partition
+ * @param rivers the rivers partition
+ * @param riverSystems the river systems partition
+ * @author Maxence Espagnet (sciper: 372808)
+ * @author Balthazar Baillat (sciper: 373420)
+ */
public record ZonePartitions(ZonePartition forests, ZonePartition meadows,
- ZonePartition rivers, ZonePartition lakes,
- ZonePartition riverSystems) {
- public final static ZonePartitions EMPTY = new ZonePartitions(new ZonePartition<>(), new ZonePartition<>(), new ZonePartition<>(), new ZonePartition<>(), new ZonePartition<>());
+ ZonePartition rivers, ZonePartition riverSystems) {
+ // Represent a group of four empty partitions
+ public final static ZonePartitions EMPTY = new ZonePartitions(new ZonePartition<>(), new ZonePartition<>(), new ZonePartition<>(), new ZonePartition<>());
+ /**
+ * Represents the builder of zone partitions.
+ */
public static final class Builder {
+ // The builder of the forests partition
+ private final ZonePartition.Builder forests;
+ // The builder of the meadows partition
+ private final ZonePartition.Builder meadows;
+ // The builder of the rivers partition
+ private final ZonePartition.Builder rivers;
+ // The builder of the river systems partition
+ private final ZonePartition.Builder riverSystems;
- private ZonePartition.Builder forests;
- private ZonePartition.Builder meadows;
- private ZonePartition.Builder rivers;
- private ZonePartition.Builder lakes;
-
+ /**
+ * Returns a new builder whose four partitions are initially identical to those of the given
+ * group of four partitions.
+ *
+ * @param initial the initial group of four partitions
+ */
public Builder(ZonePartitions initial) {
this.forests = new ZonePartition.Builder<>(initial.forests);
this.meadows = new ZonePartition.Builder<>(initial.meadows);
this.rivers = new ZonePartition.Builder<>(initial.rivers);
- this.lakes = new ZonePartition.Builder<>(initial.lakes);
+ this.riverSystems = new ZonePartition.Builder<>(initial.riverSystems);
}
+ /**
+ * Adds to the partitions the areas corresponding to the zones of the given tile.
+ *
+ * @param tile the tile
+ */
public void addTile(Tile tile) {
int[] openConnections = new int[10];
+ // Calculate the number of open connections for each zone
for (TileSide side : tile.sides()) {
for (Zone zone : side.zones()) {
- ++openConnections[zone.id()];
+ ++openConnections[zone.localId()];
+ // A lake can have a connection to a river and vice versa
+ if (zone instanceof Zone.River river && river.hasLake()) {
+ ++openConnections[river.lake().localId()];
+ ++openConnections[zone.localId()];
+ }
+ }
+ }
+ Set lakes = new HashSet<>();
+ // Add zones to partitions
+ for (Zone zone : tile.sideZones()) {
+ switch (zone) {
+ case Zone.Forest f -> forests.addSingleton(f, openConnections[zone.localId()]);
+ case Zone.Meadow m -> meadows.addSingleton(m, openConnections[zone.localId()]);
+ case Zone.River r -> {
+ // Check if the river has a lake
+ if (r.hasLake()) {
+ // If a river has a lake, the river has in fact one less open connection
+ rivers.addSingleton(r, openConnections[r.localId()] - 1);
+ riverSystems.addSingleton(r, openConnections[r.localId()]);
+ // Prevent the same lake from being added twice
+ if (!lakes.contains(r.lake())) {
+ riverSystems.addSingleton(r.lake(), openConnections[r.lake().localId()]);
+ lakes.add(r.lake());
+ }
+ // Create the union between the river and the lake
+ riverSystems.union(r, r.lake());
+ }
+ else {
+ rivers.addSingleton(r, openConnections[r.localId()]);
+ riverSystems.addSingleton(r, openConnections[r.localId()]);
+ }
+ }
+ // A lake should not be in the side zones
+ default -> throw new IllegalArgumentException("A lake shouldn't be in the side zones");
+ }
+ }
+ }
+
+ /**
+ * Connects to each other the two given tile sides by connecting the corresponding areas.
+ *
+ * @param s1 the first tile side
+ * @param s2 the second tile side
+ * @throws IllegalArgumentException if the two given tile sides are not of the same kind
+ */
+ public void connectSides(TileSide s1, TileSide s2) {
+ switch (s1) {
+ case TileSide.Meadow(Zone.Meadow m1) when s2 instanceof TileSide.Meadow(Zone.Meadow m2) -> {
+ meadows.union(m1, m2);
+ }
+ case TileSide.Forest(Zone.Forest f1) when s2 instanceof TileSide.Forest(Zone.Forest f2) -> {
+ forests.union(f1, f2);
+ }
+ case TileSide.River(
+ Zone.Meadow m3, Zone.River r1, Zone.Meadow m4
+ ) when s2 instanceof TileSide.River(Zone.Meadow m5, Zone.River r2, Zone.Meadow m6) -> {
+ riverSystems.union(r1, r2);
+ rivers.union(r1, r2);
+ meadows.union(m3, m6);
+ meadows.union(m4, m5);
+ }
+ default -> throw new IllegalArgumentException("The tile sides are not of the same kind");
+ }
+ }
+
+
+ /**
+ * Adds an initial occupant, of the given type and belonging to the given player,
+ * to the area containing the given zone.
+ *
+ * @param player the player
+ * @param occupantKind the occupant kind
+ * @param occupiedZone the occupied zone
+ * @throws IllegalArgumentException if the occupant cannot be placed on the desired zone
+ */
+ public void addInitialOccupant(PlayerColor player, Occupant.Kind occupantKind, Zone occupiedZone) {
+ if (occupantKind == Occupant.Kind.PAWN) {
+ switch (occupiedZone) {
+ case Zone.Forest f -> forests.addInitialOccupant(f, player);
+ case Zone.Meadow m -> meadows.addInitialOccupant(m, player);
+ case Zone.River r -> rivers.addInitialOccupant(r, player);
+ default -> throw new IllegalArgumentException("A pawn cannot be placed on a lake");
}
+ } else {
+ switch (occupiedZone) {
+ case Zone.River river when !river.hasLake() -> rivers.addInitialOccupant(river, player);
+ case Zone.River river -> riverSystems.addInitialOccupant(river.lake(), player);
+ case Zone.Lake lake -> riverSystems.addInitialOccupant(lake, player);
+ default -> throw new IllegalArgumentException("A hut can only be on a lake or river");
+ }
+ }
+ }
+
+ /**
+ * Removes an occupant (a pawn) belonging to the given player from the area containing the given zone.
+ *
+ * @param player the player
+ * @param occupiedZone the occupied zone
+ * @throws IllegalArgumentException if the zone is a lake
+ */
+ public void removePawn(PlayerColor player, Zone occupiedZone) {
+ switch (occupiedZone) {
+ case Zone.Forest f -> forests.removeOccupant(f, player);
+ case Zone.Meadow m -> meadows.removeOccupant(m, player);
+ case Zone.River r -> rivers.removeOccupant(r, player);
+ default -> throw new IllegalArgumentException("A pawn cannot be removed from a lake");
}
+ }
+
+ /**
+ * Removes all occupants (pawns playing the role of gatherers) from the given forest.
+ *
+ * @param forest the forest to remove all pawns from
+ */
+ public void clearGatherers(Area forest) {
+ forests.removeAllOccupantsOf(forest);
+ }
- // if (tile.zones().stream().some)
+ /**
+ * Removes all occupants (pawns playing the role of fishers) from the given river.
+ *
+ * @param river the river to remove all pawns from
+ */
+ public void clearFishers(Area river) {
+ rivers.removeAllOccupantsOf(river);
+ }
+ /**
+ * Builds the group of four partitions under construction.
+ *
+ * @return the group of four partitions under construction
+ */
+ public ZonePartitions build() {
+ return new ZonePartitions(forests.build(), meadows.build(), rivers.build(), riverSystems.build());
}
}
+
}
diff --git a/src/ch/epfl/sigcheck/SignatureChecks_4.java b/src/ch/epfl/sigcheck/SignatureChecks_4.java
new file mode 100644
index 0000000..22d1fb4
--- /dev/null
+++ b/src/ch/epfl/sigcheck/SignatureChecks_4.java
@@ -0,0 +1,94 @@
+package ch.epfl.sigcheck;
+
+// Attention : cette classe n'est *pas* un test JUnit, et son code n'est pas
+// destiné à être exécuté. Son seul but est de vérifier, autant que possible,
+// que les noms et les types des différentes entités à définir pour cette
+// étape du projet sont corrects.
+
+final class SignatureChecks_4 {
+ private SignatureChecks_4() {}
+
+ void checkZonePartitions() throws Exception {
+ v01 = new ch.epfl.chacun.ZonePartitions(v02, v03, v04, v05);
+ v01 = ch.epfl.chacun.ZonePartitions.EMPTY;
+ v07 = v01.equals(v06);
+ v02 = v01.forests();
+ v08 = v01.hashCode();
+ v03 = v01.meadows();
+ v05 = v01.riverSystems();
+ v04 = v01.rivers();
+ v09 = v01.toString();
+ }
+
+ void checkZonePartitions_Builder() throws Exception {
+ v10 = new ch.epfl.chacun.ZonePartitions.Builder(v01);
+ v10.addInitialOccupant(v11, v12, v13);
+ v10.addTile(v14);
+ v01 = v10.build();
+ v10.clearFishers(v15);
+ v10.clearGatherers(v16);
+ v10.connectSides(v17, v17);
+ v10.removePawn(v11, v13);
+ }
+
+ void checkMessageBoard() throws Exception {
+ v18 = new ch.epfl.chacun.MessageBoard(v19, v20);
+ v07 = v18.equals(v06);
+ v08 = v18.hashCode();
+ v21 = v18.messages();
+ v22 = v18.points();
+ v19 = v18.textMaker();
+ v09 = v18.toString();
+ v18 = v18.withClosedForestWithMenhir(v11, v16);
+ v18 = v18.withScoredForest(v16);
+ v18 = v18.withScoredHuntingTrap(v11, v23);
+ v18 = v18.withScoredLogboat(v11, v24);
+ v18 = v18.withScoredMeadow(v23, v25);
+ v18 = v18.withScoredPitTrap(v23, v25);
+ v18 = v18.withScoredRaft(v24);
+ v18 = v18.withScoredRiver(v15);
+ v18 = v18.withScoredRiverSystem(v24);
+ v18 = v18.withWinners(v26, v08);
+ }
+
+ void checkMessageBoard_Message() throws Exception {
+ v27 = new ch.epfl.chacun.MessageBoard.Message(v09, v08, v28, v29);
+ v07 = v27.equals(v06);
+ v08 = v27.hashCode();
+ v08 = v27.points();
+ v26 = v27.scorers();
+ v09 = v27.text();
+ v29 = v27.tileIds();
+ v09 = v27.toString();
+ }
+
+ ch.epfl.chacun.ZonePartitions v01;
+ ch.epfl.chacun.ZonePartition v02;
+ ch.epfl.chacun.ZonePartition v03;
+ ch.epfl.chacun.ZonePartition v04;
+ ch.epfl.chacun.ZonePartition v05;
+ Object v06;
+ boolean v07;
+ int v08;
+ String v09;
+ ch.epfl.chacun.ZonePartitions.Builder v10;
+ ch.epfl.chacun.PlayerColor v11;
+ ch.epfl.chacun.Occupant.Kind v12;
+ ch.epfl.chacun.Zone v13;
+ ch.epfl.chacun.Tile v14;
+ ch.epfl.chacun.Area v15;
+ ch.epfl.chacun.Area v16;
+ ch.epfl.chacun.TileSide v17;
+ ch.epfl.chacun.MessageBoard v18;
+ ch.epfl.chacun.TextMaker v19;
+ java.util.List v20;
+ java.util.List v21;
+ java.util.Map v22;
+ ch.epfl.chacun.Area v23;
+ ch.epfl.chacun.Area v24;
+ java.util.Set v25;
+ java.util.Set v26;
+ ch.epfl.chacun.MessageBoard.Message v27;
+ java.util.Set v28;
+ java.util.Set v29;
+}
diff --git a/test/ch/epfl/chacun/BoardTest.java b/test/ch/epfl/chacun/BoardTest.java
new file mode 100644
index 0000000..5025be7
--- /dev/null
+++ b/test/ch/epfl/chacun/BoardTest.java
@@ -0,0 +1,19 @@
+package ch.epfl.chacun;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class BoardTest {
+
+ @Test
+ void tileAt() {
+ Board board = Board.EMPTY;
+ assertNull(board.tileAt(Pos.ORIGIN));
+ assertNull(board.tileAt(new Pos(28, 28)));
+ assertNull(board.tileAt(new Pos(-28, -28)));
+ assertNull(board.tileAt(new Pos(-1, 28)));
+ }
+
+}
diff --git a/test/ch/epfl/chacun/MessageBoardTest.java b/test/ch/epfl/chacun/MessageBoardTest.java
new file mode 100644
index 0000000..5131687
--- /dev/null
+++ b/test/ch/epfl/chacun/MessageBoardTest.java
@@ -0,0 +1,33 @@
+package ch.epfl.chacun;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class MessageBoardTest {
+
+ @Test
+ void testWithScoredForestAndPoints() {
+ TextMaker textMaker = new TextMakerTestImplementation();
+ MessageBoard messageBoard = new MessageBoard(textMaker, List.of());
+ Zone.Forest forest1 = new Zone.Forest(230, Zone.Forest.Kind.WITH_MUSHROOMS);
+ Zone.Forest forest2 = new Zone.Forest(331, Zone.Forest.Kind.WITH_MENHIR);
+
+ Area area = new Area<>(Set.of(forest1, forest2), List.of(PlayerColor.YELLOW, PlayerColor.YELLOW, PlayerColor.GREEN), 10);
+ Area area2 = new Area<>(Set.of(forest1, forest2), List.of(), 10);
+ Area area3 = new Area<>(Set.of(forest1, forest2), List.of(PlayerColor.RED, PlayerColor.GREEN), 10);
+
+ int points = Points.forClosedForest(2, 1);
+ MessageBoard newMessageBoard = messageBoard.withScoredForest(area);
+ MessageBoard newMessageBoard2 = newMessageBoard.withScoredForest(area2);
+ MessageBoard newMessageBoard3 = newMessageBoard2.withClosedForestWithMenhir(PlayerColor.YELLOW, area3);
+
+ assertEquals(newMessageBoard, newMessageBoard2);
+ assertEquals("forest.scored([YELLOW]," + points + ",1," + area.tileIds().size() + ")", newMessageBoard2.messages().getFirst().text());
+ assertEquals("forest.closed.withMenhir(YELLOW)", newMessageBoard3.messages().get(1).text());
+ }
+
+}
\ No newline at end of file
diff --git a/test/ch/epfl/chacun/TextMakerTestImplementation.java b/test/ch/epfl/chacun/TextMakerTestImplementation.java
new file mode 100644
index 0000000..2ee84e4
--- /dev/null
+++ b/test/ch/epfl/chacun/TextMakerTestImplementation.java
@@ -0,0 +1,76 @@
+package ch.epfl.chacun;
+
+import java.util.Map;
+import java.util.Set;
+
+public class TextMakerTestImplementation implements TextMaker {
+ @Override
+ public String playerName(PlayerColor playerColor) {
+ return playerColor.toString();
+ }
+
+ @Override
+ public String points(int points) {
+ return new StringBuilder().append(points).append(" points").toString();
+ }
+
+ @Override
+ public String playerClosedForestWithMenhir(PlayerColor player) {
+ return new StringBuilder().append("forest.closed.withMenhir(").append(player).append(")").toString();
+ }
+
+ @Override
+ public String playersScoredForest(Set scorers, int points, int mushroomGroupCount, int tileCount) {
+ return new StringBuilder().append("forest.scored(").append(scorers).append(",").append(points).append(",").append(mushroomGroupCount).append(",").append(tileCount).append(")").toString();
+ }
+
+ @Override
+ public String playersScoredRiver(Set scorers, int points, int fishCount, int tileCount) {
+ return new StringBuilder().append("river.scored(").append(scorers).append(",").append(points).append(",").append(fishCount).append(",").append(tileCount).append(")").toString();
+ }
+
+ @Override
+ public String playerScoredHuntingTrap(PlayerColor scorer, int points, Map animals) {
+ return new StringBuilder().append("huntingTrap.scored(").append(scorer).append(",").append(points).append(",").append(animals).append(")").toString();
+ }
+
+ @Override
+ public String playerScoredLogboat(PlayerColor scorer, int points, int lakeCount) {
+ return new StringBuilder().append("logboat.scored(").append(scorer).append(",").append(points).append(",").append(lakeCount).append(")").toString();
+ }
+
+ @Override
+ public String playersScoredMeadow(Set scorers, int points, Map animals) {
+ return new StringBuilder().append("meadow.scored(").append(scorers).append(",").append(points).append(",").append(animals).append(")").toString();
+ }
+
+ @Override
+ public String playersScoredRiverSystem(Set scorers, int points, int fishCount) {
+ return new StringBuilder().append("riverSystem.scored(").append(scorers).append(",").append(points).append(",").append(fishCount).append(")").toString();
+ }
+
+ @Override
+ public String playersScoredPitTrap(Set scorers, int points, Map animals) {
+ return new StringBuilder().append("pitTrap.scored(").append(scorers).append(",").append(points).append(",").append(animals).append(")").toString();
+ }
+
+ @Override
+ public String playersScoredRaft(Set scorers, int points, int lakeCount) {
+ return new StringBuilder().append("raft.scored(").append(scorers).append(",").append(points).append(",").append(lakeCount).append(")").toString();
+ }
+
+ @Override
+ public String playersWon(Set winners, int points) {
+ return new StringBuilder().append("winners(").append(winners).append(",").append(points).append(")").toString();
+ }
+
+ @Override
+ public String clickToOccupy() {
+ return "clickToOccupy";
+ }
+
+ @Override
+ public String clickToUnoccupy() {
+ return "clickToUnoccupy";
+ }
+}
diff --git a/test/ch/epfl/chacun/ZonePartitionsTest.java b/test/ch/epfl/chacun/ZonePartitionsTest.java
new file mode 100644
index 0000000..bf3dfee
--- /dev/null
+++ b/test/ch/epfl/chacun/ZonePartitionsTest.java
@@ -0,0 +1,301 @@
+package ch.epfl.chacun;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ZonePartitionsTest {
+
+ @Test
+ void addTileWorks() {
+ Zone.Meadow meadow0 = new Zone.Meadow(560, new ArrayList<>(List.of(new Animal(0
+ , Animal.Kind.AUROCHS))), null);
+ Zone.Meadow meadow2 = new Zone.Meadow(562, new ArrayList<>(), null);
+ Zone.Forest forest1 = new Zone.Forest(561, Zone.Forest.Kind.WITH_MENHIR);
+ Zone.Lake lake8 = new Zone.Lake(568, 1, null);
+ Zone.River river3 = new Zone.River(563, 0, lake8);
+
+ TileSide.Meadow nSide = new TileSide.Meadow(meadow0);
+ TileSide.Forest eSide = new TileSide.Forest(forest1);
+ TileSide.Forest sSide = new TileSide.Forest(forest1);
+ TileSide.River wSide = new TileSide.River(meadow2, river3, meadow0);
+
+ Tile tile = new Tile(56, Tile.Kind.START, nSide, eSide, sSide, wSide);
+
+ Set meadows1 = Set.of(meadow0);
+ Set meadows2 = Set.of(meadow2);
+ Set forests = Set.of(forest1);
+ Set rivers = Set.of(river3);
+ Set waterZones = Set.of(river3, lake8);
+
+ Area meadowArea1 = new Area<>(meadows1, new ArrayList<>(), 2);
+ Area meadowArea2 = new Area<>(meadows2, new ArrayList<>(), 1);
+ Area forestArea = new Area<>(forests, new ArrayList<>(), 2);
+ Area riverArea = new Area<>(rivers, new ArrayList<>(), 1);
+ Area waterArea = new Area<>(waterZones, new ArrayList<>(), 1);
+
+ ZonePartition meadowZonePartition = new ZonePartition<>(Set.of(meadowArea1, meadowArea2));
+ ZonePartition forestZonePartition = new ZonePartition<>(Set.of(forestArea));
+ ZonePartition riverZonePartition = new ZonePartition<>(Set.of(riverArea));
+ ZonePartition waterZonePartition = new ZonePartition<>(Set.of(waterArea));
+
+ ZonePartitions expectedZonePartitions = new ZonePartitions(forestZonePartition, meadowZonePartition,
+ riverZonePartition, waterZonePartition);
+ ZonePartitions.Builder testBuilder = new ZonePartitions.Builder(ZonePartitions.EMPTY);
+ testBuilder.addTile(tile);
+
+ assertTrue(expectedZonePartitions.meadows().equals(testBuilder.build().meadows()));
+
+ }
+
+ @Test
+ void addTileWorksWith4Rivers() {
+ Zone.Meadow meadow0 = new Zone.Meadow(130, new ArrayList<>(), null);
+ Zone.Meadow meadow2 = new Zone.Meadow(132, new ArrayList<>(), null);
+ Zone.Meadow meadow4 = new Zone.Meadow(134, new ArrayList<>(), null);
+ Zone.Meadow meadow6 = new Zone.Meadow(136, new ArrayList<>(), null);
+ Zone.Lake lake8 = new Zone.Lake(138, 2, null);
+ Zone.River river1 = new Zone.River(131, 0, lake8);
+ Zone.River river3 = new Zone.River(133, 0, lake8);
+ Zone.River river5 = new Zone.River(135, 0, lake8);
+ Zone.River river7 = new Zone.River(137, 0, lake8);
+
+ TileSide.River nSide = new TileSide.River(meadow0, river1, meadow2);
+ TileSide.River eSide = new TileSide.River(meadow2, river3, meadow4);
+ TileSide.River sSide = new TileSide.River(meadow4, river5, meadow6);
+ TileSide.River wSide = new TileSide.River(meadow6, river7, meadow0);
+
+ Tile tile = new Tile(56, Tile.Kind.NORMAL, nSide, eSide, sSide, wSide);
+
+ Area meadowArea1 = new Area<>(Set.of(meadow0), new ArrayList<>(), 2);
+ Area meadowArea2 = new Area<>(Set.of(meadow2), new ArrayList<>(), 2);
+ Area meadowArea3 = new Area<>(Set.of(meadow4), new ArrayList<>(), 2);
+ Area meadowArea4 = new Area<>(Set.of(meadow6), new ArrayList<>(), 2);
+ Area riverArea1 = new Area<>(Set.of(river1), new ArrayList<>(), 1);
+ Area riverArea2 = new Area<>(Set.of(river3), new ArrayList<>(), 1);
+ Area riverArea3 = new Area<>(Set.of(river5), new ArrayList<>(), 1);
+ Area riverArea4 = new Area<>(Set.of(river7), new ArrayList<>(), 1);
+ Area waterArea = new Area<>(Set.of(river1, river3, river5, river7, lake8), new ArrayList<>(), 4);
+
+ ZonePartition meadowZonePartition = new ZonePartition<>(Set.of(meadowArea1, meadowArea2, meadowArea3, meadowArea4));
+ ZonePartition forestZonePartition = new ZonePartition<>();
+ ZonePartition riverZonePartition = new ZonePartition<>(Set.of(riverArea1, riverArea2, riverArea3, riverArea4));
+ ZonePartition waterZonePartition = new ZonePartition<>(Set.of(waterArea));
+
+ ZonePartitions expectedZonePartitions = new ZonePartitions(forestZonePartition, meadowZonePartition,
+ riverZonePartition, waterZonePartition);
+ ZonePartitions.Builder testBuilder = new ZonePartitions.Builder(ZonePartitions.EMPTY);
+ testBuilder.addTile(tile);
+
+ assertEquals(expectedZonePartitions, testBuilder.build());
+
+ }
+
+ @Test
+ void connectSidesWorks() {
+ Zone.Meadow meadow1_0 = new Zone.Meadow(560, new ArrayList<>(), null);
+ Zone.Meadow meadow1_2 = new Zone.Meadow(562, new ArrayList<>(), null);
+ Zone.Forest forest1_1 = new Zone.Forest(561, Zone.Forest.Kind.WITH_MENHIR);
+ Zone.Lake lake8 = new Zone.Lake(568, 1, null);
+ Zone.River river1_3 = new Zone.River(563, 0, lake8);
+ Zone.Meadow meadow2_0 = new Zone.Meadow(170, new ArrayList<>(), null);
+ Zone.Meadow meadow2_2 = new Zone.Meadow(172, new ArrayList<>(), null);
+ Zone.Meadow meadow2_6 = new Zone.Meadow(176, new ArrayList<>(), null);
+ Zone.River river2_1 = new Zone.River(171, 0, null);
+ Zone.River river2_5 = new Zone.River(175, 0, null);
+
+ Set meadows1 = Set.of(meadow1_0, meadow2_2);
+ Set meadows2 = Set.of(meadow1_2, meadow2_0);
+ Set meadows3 = Set.of(meadow2_6);
+ Set forests = Set.of(forest1_1);
+ Set rivers1 = Set.of(river2_1, river1_3);
+ Set rivers2 = Set.of(river2_5);
+ Set waterZones1 = Set.of(river1_3, river2_1, lake8);
+ Set waterZones2 = Set.of(river2_5);
+
+ TileSide.Meadow meadowSide1_1 = new TileSide.Meadow(meadow1_0);
+ TileSide.Forest forestSide1_2 = new TileSide.Forest(forest1_1);
+ TileSide.Forest forestSide1_3 = new TileSide.Forest(forest1_1);
+ TileSide.River riverSide1_4 = new TileSide.River(meadow1_2, river1_3, meadow1_0);
+
+ TileSide.River riverSide2_1 = new TileSide.River(meadow2_0, river2_1, meadow2_2);
+ TileSide.River riverSide2_2 = new TileSide.River(meadow2_2, river2_1, meadow2_0);
+ TileSide.River riverSide2_3 = new TileSide.River(meadow2_0, river2_5, meadow2_6);
+ TileSide.River riverSide2_4 = new TileSide.River(meadow2_6, river2_5, meadow2_0);
+
+ Tile tile1 = new Tile(56, Tile.Kind.START, meadowSide1_1, forestSide1_2, forestSide1_3, riverSide1_4);
+ Tile tile2 = new Tile(17, Tile.Kind.NORMAL, riverSide2_1, riverSide2_2, riverSide2_3, riverSide2_4);
+
+ Area meadowArea1 = new Area<>(meadows1, new ArrayList<>(), 2);
+ Area meadowArea2 = new Area<>(meadows2, new ArrayList<>(), 3);
+ Area meadowArea3 = new Area<>(meadows3, new ArrayList<>(), 2);
+ Area forestArea = new Area<>(forests, new ArrayList<>(), 2);
+ Area riverArea1 = new Area<>(rivers1, new ArrayList<>(), 1);
+ Area riverArea2 = new Area<>(rivers2, new ArrayList<>(), 2);
+ Area waterArea1 = new Area<>(waterZones1, new ArrayList<>(), 1);
+ Area waterArea2 = new Area<>(waterZones2, new ArrayList<>(), 2);
+
+ ZonePartition meadowZonePartition = new ZonePartition<>(Set.of(meadowArea3, meadowArea2, meadowArea1));
+ ZonePartition forestZonePartition = new ZonePartition<>(Set.of(forestArea));
+ ZonePartition riverZonePartition = new ZonePartition<>(Set.of(riverArea2, riverArea1));
+ ZonePartition waterZonePartition = new ZonePartition<>(Set.of(waterArea2, waterArea1));
+
+ ZonePartitions expectedZonePartitions = new ZonePartitions(forestZonePartition, meadowZonePartition,
+ riverZonePartition, waterZonePartition);
+ ZonePartitions.Builder testBuilder = new ZonePartitions.Builder(ZonePartitions.EMPTY);
+ testBuilder.addTile(tile1);
+ testBuilder.addTile(tile2);
+ testBuilder.connectSides(riverSide1_4, riverSide2_2);
+
+ assertEquals(expectedZonePartitions.rivers(), testBuilder.build().rivers());
+ }
+
+ @Test
+ void addInitialOccupantWorks() {
+ Zone.Meadow meadow0 = new Zone.Meadow(560, new ArrayList<>(List.of(new Animal(0
+ , Animal.Kind.AUROCHS))), null);
+ Zone.Forest forest1 = new Zone.Forest(561, Zone.Forest.Kind.WITH_MENHIR);
+ Zone.Meadow meadow2 = new Zone.Meadow(562, new ArrayList<>(), null);
+ Zone.Lake lake8 = new Zone.Lake(568, 1, null);
+ Zone.River river3 = new Zone.River(563, 0, lake8);
+
+ TileSide.Meadow nSide = new TileSide.Meadow(meadow0);
+ TileSide.Forest eSide = new TileSide.Forest(forest1);
+ TileSide.Forest sSide = new TileSide.Forest(forest1);
+ TileSide.River wSide = new TileSide.River(meadow2, river3, meadow0);
+
+ Tile tile = new Tile(56, Tile.Kind.START, nSide, eSide, sSide, wSide);
+
+ Set meadows1 = Set.of(meadow0);
+ Set meadows2 = Set.of(meadow2);
+ Set forests = Set.of(forest1);
+ Set rivers = Set.of(river3);
+ Set waterZones = Set.of(river3, lake8);
+
+ Area meadowArea1 = new Area<>(meadows1, List.of(PlayerColor.YELLOW), 2);
+ Area meadowArea2 = new Area<>(meadows2, List.of(PlayerColor.GREEN), 1);
+ Area forestArea = new Area<>(forests, new ArrayList<>(), 2);
+ Area riverArea = new Area<>(rivers, new ArrayList<>(), 1);
+ Area waterArea = new Area<>(waterZones, List.of(PlayerColor.RED), 1);
+
+ ZonePartition meadowZonePartition =
+ new ZonePartition<>(Set.of(meadowArea1, meadowArea2));
+ ZonePartition forestZonePartition = new ZonePartition<>(Set.of(forestArea));
+ ZonePartition riverZonePartition = new ZonePartition<>(Set.of(riverArea));
+ ZonePartition waterZonePartition = new ZonePartition<>(Set.of(waterArea));
+
+ ZonePartitions.Builder builder = new ZonePartitions.Builder(ZonePartitions.EMPTY);
+ builder.addTile(tile);
+ builder.addInitialOccupant(PlayerColor.YELLOW, Occupant.Kind.PAWN, meadow0);
+ builder.addInitialOccupant(PlayerColor.RED, Occupant.Kind.HUT, lake8);
+ builder.addInitialOccupant(PlayerColor.GREEN, Occupant.Kind.PAWN, meadow2);
+ ZonePartitions expectedZonePartitions = new ZonePartitions(forestZonePartition, meadowZonePartition,
+ riverZonePartition, waterZonePartition);
+
+ assertEquals(expectedZonePartitions, builder.build());
+ assertThrows(IllegalArgumentException.class, () -> builder.addInitialOccupant(PlayerColor.YELLOW, Occupant.Kind.PAWN, meadow0));
+ assertThrows(IllegalArgumentException.class, () -> builder.addInitialOccupant(PlayerColor.GREEN, Occupant.Kind.HUT, river3));
+ }
+
+ @Test
+ void removePawnWorks() {
+ Zone.Meadow meadow0 = new Zone.Meadow(560, new ArrayList<>(List.of(new Animal(0
+ , Animal.Kind.AUROCHS))), null);
+ Zone.Forest forest1 = new Zone.Forest(561, Zone.Forest.Kind.WITH_MENHIR);
+ Zone.Meadow meadow2 = new Zone.Meadow(562, new ArrayList<>(), null);
+ Zone.Lake lake8 = new Zone.Lake(568, 1, null);
+ Zone.River river3 = new Zone.River(563, 0, lake8);
+
+ TileSide.Meadow nSide = new TileSide.Meadow(meadow0);
+ TileSide.Forest eSide = new TileSide.Forest(forest1);
+ TileSide.Forest sSide = new TileSide.Forest(forest1);
+ TileSide.River wSide = new TileSide.River(meadow2, river3, meadow0);
+
+ Tile tile = new Tile(56, Tile.Kind.START, nSide, eSide, sSide, wSide);
+
+ Set meadows1 = Set.of(meadow0);
+ Set meadows2 = Set.of(meadow2);
+ Set forests = Set.of(forest1);
+ Set rivers = Set.of(river3);
+ Set waterZones = Set.of(river3, lake8);
+
+ Area meadowArea1 = new Area<>(meadows1, List.of(), 2);
+ Area meadowArea2 = new Area<>(meadows2, List.of(), 1);
+ Area forestArea = new Area<>(forests, List.of(), 2);
+ Area riverArea = new Area<>(rivers, List.of(), 1);
+ Area waterArea = new Area<>(waterZones, List.of(PlayerColor.RED), 1);
+
+ ZonePartition meadowZonePartition =
+ new ZonePartition<>(Set.of(meadowArea1, meadowArea2));
+ ZonePartition forestZonePartition = new ZonePartition<>(Set.of(forestArea));
+ ZonePartition riverZonePartition = new ZonePartition<>(Set.of(riverArea));
+ ZonePartition waterZonePartition = new ZonePartition<>(Set.of(waterArea));
+
+ ZonePartitions.Builder builder = new ZonePartitions.Builder(ZonePartitions.EMPTY);
+ builder.addTile(tile);
+ builder.addInitialOccupant(PlayerColor.YELLOW, Occupant.Kind.PAWN, meadow0);
+ builder.addInitialOccupant(PlayerColor.RED, Occupant.Kind.HUT, lake8);
+ builder.addInitialOccupant(PlayerColor.GREEN, Occupant.Kind.PAWN, meadow2);
+ builder.removePawn(PlayerColor.YELLOW, meadow0);
+ builder.removePawn(PlayerColor.GREEN, meadow2);
+ ZonePartitions expectedZonePartitions = new ZonePartitions(forestZonePartition, meadowZonePartition,
+ riverZonePartition, waterZonePartition);
+
+ assertEquals(expectedZonePartitions, builder.build());
+ assertThrows(IllegalArgumentException.class, () -> builder.removePawn(PlayerColor.GREEN, meadow0));
+ assertThrows(IllegalArgumentException.class, () -> builder.removePawn(PlayerColor.BLUE, forest1));
+ }
+
+ @Test
+ void clearWorks() {
+ Zone.Meadow meadow0 = new Zone.Meadow(560, new ArrayList<>(List.of(new Animal(0
+ , Animal.Kind.AUROCHS))), null);
+ Zone.Forest forest1 = new Zone.Forest(561, Zone.Forest.Kind.WITH_MENHIR);
+ Zone.Meadow meadow2 = new Zone.Meadow(562, new ArrayList<>(), null);
+ Zone.Lake lake8 = new Zone.Lake(568, 1, null);
+ Zone.River river3 = new Zone.River(563, 0, lake8);
+
+ TileSide.Meadow nSide = new TileSide.Meadow(meadow0);
+ TileSide.Forest eSide = new TileSide.Forest(forest1);
+ TileSide.Forest sSide = new TileSide.Forest(forest1);
+ TileSide.River wSide = new TileSide.River(meadow2, river3, meadow0);
+
+ Tile tile = new Tile(56, Tile.Kind.START, nSide, eSide, sSide, wSide);
+
+ Set meadows1 = Set.of(meadow0);
+ Set meadows2 = Set.of(meadow2);
+ Set forests = Set.of(forest1);
+ Set rivers = Set.of(river3);
+ Set waterZones = Set.of(river3, lake8);
+
+ Area meadowArea1 = new Area<>(meadows1, List.of(PlayerColor.YELLOW), 2);
+ Area meadowArea2 = new Area<>(meadows2, List.of(), 1);
+ Area forestArea = new Area<>(forests, List.of(), 2);
+ Area riverArea = new Area<>(rivers, List.of(), 1);
+ Area waterArea = new Area<>(waterZones, List.of(), 1);
+
+ ZonePartition meadowZonePartition =
+ new ZonePartition<>(Set.of(meadowArea1, meadowArea2));
+ ZonePartition forestZonePartition = new ZonePartition<>(Set.of(forestArea));
+ ZonePartition riverZonePartition = new ZonePartition<>(Set.of(riverArea));
+ ZonePartition waterZonePartition = new ZonePartition<>(Set.of(waterArea));
+
+ ZonePartitions.Builder builder = new ZonePartitions.Builder(ZonePartitions.EMPTY);
+ builder.addTile(tile);
+ builder.addInitialOccupant(PlayerColor.YELLOW, Occupant.Kind.PAWN, meadow0);
+ builder.addInitialOccupant(PlayerColor.RED, Occupant.Kind.PAWN, river3);
+ builder.addInitialOccupant(PlayerColor.GREEN, Occupant.Kind.PAWN, forest1);
+ builder.clearGatherers(forestArea.withInitialOccupant(PlayerColor.GREEN));
+ builder.clearFishers(riverArea.withInitialOccupant(PlayerColor.RED));
+ ZonePartitions expectedZonePartitions = new ZonePartitions(forestZonePartition, meadowZonePartition,
+ riverZonePartition, waterZonePartition);
+
+ assertEquals(expectedZonePartitions, builder.build());
+ assertThrows(IllegalArgumentException.class, () -> builder.clearGatherers(forestArea.withInitialOccupant(PlayerColor.GREEN)));
+ }
+
+}