diff --git a/src/ch/epfl/chacun/MessageBoard.java b/src/ch/epfl/chacun/MessageBoard.java new file mode 100644 index 0000000..8f7d618 --- /dev/null +++ b/src/ch/epfl/chacun/MessageBoard.java @@ -0,0 +1,318 @@ +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.putIfAbsent(scorer, 0); + scorers.put(scorer, scorers.get(scorer) + 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<>(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 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(); + 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/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; +}