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))); + } + +}