diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 0000000..4cb9e85 --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,20 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + - step-1 + +jobs: + qodana: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2023.3 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 524f096..15fce83 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* replay_pid* + +out/**/* +submissions/**/* diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ChaCuN.iml b/ChaCuN.iml index ce6ded1..87f85cf 100644 --- a/ChaCuN.iml +++ b/ChaCuN.iml @@ -9,5 +9,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..ebc500e --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,31 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +projectJDK: 21 #(Applied in CI/CD pipeline) + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-jvm:latest diff --git a/src/ch/epfl/chacun/Animal.java b/src/ch/epfl/chacun/Animal.java new file mode 100644 index 0000000..c78c8f4 --- /dev/null +++ b/src/ch/epfl/chacun/Animal.java @@ -0,0 +1,33 @@ +package ch.epfl.chacun; + +/** + * Represents an animal. + * + * @param id the id of the animal + * @param kind the kind of the animal + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public record Animal(int id, Kind kind) { + + /** + * Returns the id of the tile on which the animal is located. + * + * @return the id of the tile on which the animal is located + */ + public int tileId() { + // id = 10 * zoneId + animalNumber + return Zone.tileId(id / 10); + } + + /** + * Represents the different kinds of animals. + */ + public enum Kind { + MAMMOTH, + AUROCHS, + DEER, + TIGER + } + +} diff --git a/src/ch/epfl/chacun/Direction.java b/src/ch/epfl/chacun/Direction.java new file mode 100644 index 0000000..b471927 --- /dev/null +++ b/src/ch/epfl/chacun/Direction.java @@ -0,0 +1,42 @@ +package ch.epfl.chacun; + +import java.util.List; + +/** + * Represents the different possible directions of a tile. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public enum Direction { + N, + E, + S, + W; + + // All the values of Direction. + public static final List ALL = List.of(Direction.values()); + + // The number of elements of Direction. + public static final int COUNT = ALL.size(); + + /** + * Returns the direction after applying the given rotation. + * + * @param rotation the rotation to apply + * @return the direction after applying the rotation + */ + public Direction rotated(Rotation rotation) { + return ALL.get((this.ordinal() + rotation.ordinal()) % COUNT); + } + + /** + * Returns the opposite of the receiver direction. + * + * @return the opposite of the receiver direction + */ + public Direction opposite() { + // There is only four directions, so we can just add 2 to the ordinal + return ALL.get((this.ordinal() + 2) % COUNT); + } +} diff --git a/src/ch/epfl/chacun/Occupant.java b/src/ch/epfl/chacun/Occupant.java new file mode 100644 index 0000000..bb2cb9c --- /dev/null +++ b/src/ch/epfl/chacun/Occupant.java @@ -0,0 +1,45 @@ +package ch.epfl.chacun; + +import java.util.Objects; + +/** + * Represents the different possible occupants of a zone. + * + * @param kind the occupant kind + * @param zoneId the id of the zone in which is the occupant is located + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public record Occupant(Kind kind, int zoneId) { + + /** + * Checks for the validity of the given kind and zoneId. + * + * @throws NullPointerException if kind is null + * @throws IllegalArgumentException if zoneId is smaller than 0 + */ + public Occupant { + Objects.requireNonNull(kind); + Preconditions.checkArgument(zoneId >= 0); + } + + /** + * Returns the number of occupants of the given kind owned by a player + * + * @param kind the occupant kind + * @return the number of occupants of the given kind owned by a player + */ + public static int occupantsCount(Kind kind) { + return switch (kind) { + case PAWN -> 5; + case HUT -> 3; + }; + } + + /** + * Represents the different kinds of occupants. + */ + public enum Kind { + PAWN, HUT + } +} diff --git a/src/ch/epfl/chacun/PlacedTile.java b/src/ch/epfl/chacun/PlacedTile.java new file mode 100644 index 0000000..2d66f1d --- /dev/null +++ b/src/ch/epfl/chacun/PlacedTile.java @@ -0,0 +1,197 @@ +package ch.epfl.chacun; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Represents a tile that has been placed on the board. + * + * @param tile the tile that has been placed + * @param placer the player who placed the tile + * @param rotation the rotation of the tile + * @param pos the position of the tile + * @param occupant the first occupant of the tile + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public record PlacedTile(Tile tile, PlayerColor placer, Rotation rotation, Pos pos, Occupant occupant) { + + /** + * Additional constructor to ease to creating tiles placed without an occupant. + * + * @param tile the tile that has been placed + * @param placer the player who placed the tile + * @param rotation the rotation of the tile + * @param pos the position of the tile + */ + public PlacedTile(Tile tile, PlayerColor placer, Rotation rotation, Pos pos) { + this(tile, placer, rotation, pos, null); + } + + /** + * Validates the given tile, rotation and position. + * + * @throws NullPointerException if tile, rotation or pos is null + */ + public PlacedTile { + Objects.requireNonNull(tile); + Objects.requireNonNull(rotation); + Objects.requireNonNull(pos); + + // Prevent a null placer + if (placer == null && !pos.equals(Pos.ORIGIN)) { + throw new IllegalArgumentException("A tile must have a placer except for the origin."); + } + } + + /** + * Returns the id of the placed tile. + * + * @return the id of the tile + */ + public int id() { + return tile.id(); + } + + /** + * Returns the kind of the tile. + * + * @return the kind of the tile + */ + public Tile.Kind kind() { + return tile.kind(); + } + + /** + * Represents the direction of each side of the placed tile considering the rotation. + * + * @param direction the direction of the placed tile + * @return the direction of each side of the placed tile considering the rotation + */ + public TileSide side(Direction direction) { + Direction rotatedDirection = direction.rotated(rotation.negated()); + return switch (rotatedDirection) { + case N -> tile.n(); + case E -> tile.e(); + case S -> tile.s(); + case W -> tile.w(); + }; + } + + /** + * Returns the area of the placed tile whose id is given, or throws IllegalArgumentException + * if the tile has no area with this id. + * + * @param id the id of the placed tile + * @return the area of the placed tile whose id is given + * @throws IllegalArgumentException if the tile has no area with this id + */ + public Zone zoneWithId(int id) { + return tile.zones().stream().filter(z -> z.id() == id).findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown zone id: " + id)); + } + + /** + * Returns the special power zone of the placed tile if any. + * + * @return the special power zone of the placed tile or null if there is none + */ + public Zone specialPowerZone() { + return tile.zones().stream().filter(z -> z.specialPower() != null).findFirst().orElse(null); + } + + /** + * Returns all zones present in the tile that have the forest type. + * + * @return all zones present in the tile that have the forest type + */ + public Set forestZones() { + return tile.zones().stream().filter(Zone.Forest.class::isInstance) + .map(Zone.Forest.class::cast).collect(Collectors.toSet()); + } + + /** + * Returns all zones present in the tile that have the meadow type. + * + * @return all zones present in the tile that have the meadow type + */ + public Set meadowZones() { + return tile.zones().stream().filter(Zone.Meadow.class::isInstance) + .map(Zone.Meadow.class::cast).collect(Collectors.toSet()); + } + + /** + * Returns all zones present in the tile that have the river type. + * + * @return all zones present in the tile that have the river type + */ + public Set riverZones() { + return tile.zones().stream().filter(Zone.River.class::isInstance) + .map(Zone.River.class::cast).collect(Collectors.toSet()); + } + + /** + * Returns each potential occupant of each zone of the tile based on the game rules. + * + * @return each potential occupant of each zone of the tile + */ + public Set potentialOccupants() { + // Calculate all the potential occupants + Set potentialOccupants = new HashSet<>(); + // The origin tile cannot be occupied + if (placer != null) { + for (Zone zone : tile.zones()) { + // A pawn can only be placed on a meadow, a forest or a river + 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 + if (zone instanceof Zone.Lake || (zone instanceof Zone.River river && !river.hasLake())) { + potentialOccupants.add(new Occupant(Occupant.Kind.HUT, zone.id())); + } + } + } + return potentialOccupants; + } + + /** + * Returns the same tile, with a given occupant added (if there were none). + * + * @param occupant the occupant to add + * @return the same tile, with a given occupant added + * @throws IllegalArgumentException if the tile already has an occupant + */ + public PlacedTile withOccupant(Occupant occupant) { + if (this.occupant != null) { + throw new IllegalArgumentException("Tile already has an occupant"); + } + return new PlacedTile(tile, placer, rotation, pos, occupant); + } + + /** + * Returns the same tile, with no occupant. + * + * @return the same tile, with no occupant + */ + public PlacedTile withNoOccupant() { + return new PlacedTile(tile, placer, rotation, pos); + } + + /** + * Returns the id of the zone occupied by the given kind of occupant, or -1 if there is none. + * Based on the game rules, a tile can only have one occupant. + * + * @param occupantKind the kind of the occupant + * @return the id of the zone occupied by the given kind of occupant, or -1 if there is none + */ + public int idOfZoneOccupiedBy(Occupant.Kind occupantKind) { + if (occupant != null && occupant.kind() == occupantKind) { + return occupant.zoneId(); + } + return -1; + } + +} diff --git a/src/ch/epfl/chacun/PlayerColor.java b/src/ch/epfl/chacun/PlayerColor.java new file mode 100644 index 0000000..bafbd0e --- /dev/null +++ b/src/ch/epfl/chacun/PlayerColor.java @@ -0,0 +1,21 @@ +package ch.epfl.chacun; + +import java.util.List; + +/** + * Represents the different possible colors of a player. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public enum PlayerColor { + RED, + BLUE, + GREEN, + YELLOW, + PURPLE; + + // All possible colors of a player + public static final List ALL = List.of(PlayerColor.values()); + +} diff --git a/src/ch/epfl/chacun/Points.java b/src/ch/epfl/chacun/Points.java new file mode 100644 index 0000000..0ac5540 --- /dev/null +++ b/src/ch/epfl/chacun/Points.java @@ -0,0 +1,126 @@ +package ch.epfl.chacun; + +/** + * Helper class to calculate the points for different elements of the game. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public final class Points { + + // Forest points + private final static int CLOSED_FOREST_POINTS_BY_MAJORITY_OCCUPANTS = 2; + private final static int CLOSED_FOREST_POINTS_BY_MUSHROOM = 3; + + // River points + private final static int CLOSED_RIVER_POINTS_BY_MAJORITY_OCCUPANTS = 1; + private final static int CLOSED_RIVER_POINTS_BY_FISH = 1; + + // Meadow points + private final static int MEADOW_POINTS_BY_MAMMOTH = 3; + private final static int MEADOW_POINTS_BY_AUROCHS = 2; + private final static int MEADOW_POINTS_BY_DEER = 1; + + // Logboat points + private final static int LOGBOAT_POINTS_BY_LAKE = 2; + + // Raft points + private final static int RAFT_POINTS_BY_LAKE = 1; + + /** + * Private constructor to prevent instantiation. + */ + private Points() { + } + + /** + * Returns the number of points obtained by the majority pickers in a closed + * forest made up of {@code tileCount} tiles and {@code mushroomGroupCount} mushroom groups. + * + * @param tileCount the number of tiles in the closed forest + * @param mushroomGroupCount the number of mushroom groups in the closed forest + * @return the points given to a player for a closed forest + * @throws IllegalArgumentException if the tile count is not greater than 1 or the mushroom group count is negative + */ + public static int forClosedForest(int tileCount, int mushroomGroupCount) { + Preconditions.checkArgument(tileCount > 1); + Preconditions.checkArgument(mushroomGroupCount >= 0); + return CLOSED_FOREST_POINTS_BY_MAJORITY_OCCUPANTS * tileCount + CLOSED_FOREST_POINTS_BY_MUSHROOM * mushroomGroupCount; + } + + /** + * Returns the number of points obtained by the majority anglers in + * a closed river made up of {@code tileCount} tiles and in which {@code fishCount} fish swim. + * + * @param tileCount the number of tiles in the closed river + * @param fishCount the number of fish in the closed river + * @return the points given to a player for a closed river + * @throws IllegalArgumentException if the tile count is not greater than 1 + * or if the fish count is negative + */ + public static int forClosedRiver(int tileCount, int fishCount) { + Preconditions.checkArgument(tileCount > 1); + Preconditions.checkArgument(fishCount >= 0); + return CLOSED_RIVER_POINTS_BY_MAJORITY_OCCUPANTS * tileCount + CLOSED_RIVER_POINTS_BY_FISH * fishCount; + } + + /** + * Returns the number of points obtained by the majority hunters in a + * meadow containing {@code mammothCount} mammoths, {@code aurochsCount} aurochs and {@code deerCount} deer. + *

+ * Deer eaten by smilodons are not included in deerCount. + * + * @param mammothCount the number of mammoths in the meadow + * @param aurochsCount the number of aurochs in the meadow + * @param deerCount the number of deers in the meadow + * @return the points given to a player for a meadow + * @throws IllegalArgumentException if the mammoth count is not positive + * or if the aurochs count is not positive + * or if the deer count is not positive + */ + public static int forMeadow(int mammothCount, int aurochsCount, int deerCount) { + Preconditions.checkArgument(mammothCount >= 0); + Preconditions.checkArgument(aurochsCount >= 0); + Preconditions.checkArgument(deerCount >= 0); + return MEADOW_POINTS_BY_MAMMOTH * mammothCount + MEADOW_POINTS_BY_AUROCHS * aurochsCount + MEADOW_POINTS_BY_DEER * deerCount; + } + + /** + * Returns the number of points obtained by the majority of anglers in a + * river system in which {@code fishCount} fish swim. + * + * @param fishCount the number of fish in the lake + * @return the points given to a player for a river system + * @throws IllegalArgumentException if the fish count is not positive + */ + public static int forRiverSystem(int fishCount) { + Preconditions.checkArgument(fishCount >= 0); + return CLOSED_RIVER_POINTS_BY_FISH * fishCount; + } + + /** + * Returns the number of points obtained by the player depositing the + * logboat in a river system containing {@code lakeCount} lakes. + * + * @param lakeCount the number of lakes in the river system + * @return the points given to a player for a logboat + * @throws IllegalArgumentException if the lake count is not strictly positive + */ + public static int forLogboat(int lakeCount) { + Preconditions.checkArgument(lakeCount > 0); + return LOGBOAT_POINTS_BY_LAKE * lakeCount; + } + + /** + * Returns the number of additional points obtained by the majority anglers + * on the river network containing the raft and including {@code lakeCount} lakes. + * + * @param lakeCount the number of lakes in the river network + * @return the points given to a player for a raft + * @throws IllegalArgumentException if the lake count is not strictly positive + */ + public static int forRaft(int lakeCount) { + Preconditions.checkArgument(lakeCount > 0); + return RAFT_POINTS_BY_LAKE * lakeCount; + } +} diff --git a/src/ch/epfl/chacun/Pos.java b/src/ch/epfl/chacun/Pos.java new file mode 100644 index 0000000..bf08260 --- /dev/null +++ b/src/ch/epfl/chacun/Pos.java @@ -0,0 +1,41 @@ +package ch.epfl.chacun; + +/** + * Represents a position on the board of size 25x25. + * + * @param x the x coordinate of the position + * @param y the y coordinate of the position + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public record Pos(int x, int y) { + + // The origin of the board + public final static Pos ORIGIN = new Pos(0, 0); + + /** + * Translates the current position by a given amount. + * + * @param dX the amount by which the x coordinate should be translated + * @param dY the amount by which the x coordinate should be translated + * @return the translated position + */ + public Pos translated(int dX, int dY) { + return new Pos(x + dX, y + dY); + } + + /** + * Returns the neighbor of the current position in a given direction. + * + * @param direction the direction in which to find the neighbor + * @return the neighbor of the current position in the given direction + */ + public Pos neighbor(Direction direction) { + return switch (direction) { + case N -> translated(0, -1); + case E -> translated(1, 0); + case S -> translated(0, 1); + case W -> translated(-1, 0); + }; + } +} diff --git a/src/ch/epfl/chacun/Preconditions.java b/src/ch/epfl/chacun/Preconditions.java new file mode 100644 index 0000000..7ab2f11 --- /dev/null +++ b/src/ch/epfl/chacun/Preconditions.java @@ -0,0 +1,28 @@ +package ch.epfl.chacun; + +/** + * Helper class to check preconditions. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public final class Preconditions { + + /** + * Private constructor to prevent instantiation. + */ + private Preconditions() { + } + + /** + * Throws an IllegalArgumentException if the given precondition is false. + * + * @param precondition the precondition to check + * @throws IllegalArgumentException if the precondition is false + */ + public static void checkArgument(boolean precondition) { + if (!precondition) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/ch/epfl/chacun/Rotation.java b/src/ch/epfl/chacun/Rotation.java new file mode 100644 index 0000000..91478df --- /dev/null +++ b/src/ch/epfl/chacun/Rotation.java @@ -0,0 +1,60 @@ +package ch.epfl.chacun; + +import java.util.List; + +/** + * Represents the different possible rotations of a tile. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public enum Rotation { + NONE, // 0 degrees + RIGHT, // 90 degrees + HALF_TURN, // 180 degrees + LEFT; // 270 degrees + + // All the possible rotations + public static final List ALL = List.of(Rotation.values()); + // The number of different rotations + public static final int COUNT = ALL.size(); + + /** + * Calculates the addition of two rotations. + * + * @param that the rotation to add to the current one + * @return a new rotation which is the sum of the two rotations + */ + public Rotation add(Rotation that) { + return ALL.get((this.ordinal() + that.ordinal()) % COUNT); + } + + /** + * Calculates the opposite rotation of the current one. + * The opposite rotation is the rotation that, when added to the current one, gives the NONE rotation. + * + * @return the opposite rotation + */ + public Rotation negated() { + return ALL.get((COUNT - this.ordinal()) % COUNT); + } + + /** + * Calculates the number of quarter turns clockwise of the current rotation. + * + * @return the number of quarter turns clockwise of the rotation + */ + public int quarterTurnsCW() { + return this.ordinal(); + } + + /** + * Converts the rotation to degrees clockwise. + * + * @return the number of degrees clockwise of the rotation + */ + public int degreesCW() { + return this.quarterTurnsCW() * 90; + } + +} diff --git a/src/ch/epfl/chacun/Tile.java b/src/ch/epfl/chacun/Tile.java new file mode 100644 index 0000000..e3a62f7 --- /dev/null +++ b/src/ch/epfl/chacun/Tile.java @@ -0,0 +1,69 @@ +package ch.epfl.chacun; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Represents a tile of the game. + * + * @param id the id of the tile + * @param kind the kind of the tile + * @param n north side of the tile + * @param e east side od^f the tile + * @param s south side of the tile + * @param w west side of the tile + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public record Tile(int id, Kind kind, TileSide n, TileSide e, TileSide s, TileSide w) { + + /** + * Returns the list of all the possible sides of the tile. + * + * @return the list of all the possible sides of the tile + */ + public List sides() { + return List.of(n, e, s, w); + } + + /** + * Returns the set of all the zones that touch at least one side of the tile. + * + * @return the set of all the zones that touch at least one side of the tile + */ + public Set sideZones() { + Set zones = new HashSet<>(); + zones.addAll(n.zones()); + zones.addAll(e.zones()); + zones.addAll(s.zones()); + zones.addAll(w.zones()); + return zones; + } + + /** + * Returns the set of all the zones that are contained in the tile. + * + * @return the set of all the zones that are contained in the tile + */ + public Set zones() { + Set sideZones = sideZones(); + Set zones = new HashSet<>(sideZones); + for (Zone zone : sideZones) { + if (zone instanceof Zone.River river) { + if (river.hasLake()) + zones.add(river.lake()); + } + } + return zones; + } + + /** + * Represents the different kinds of tiles. + */ + public enum Kind { + START, + NORMAL, + MENHIR; + } +} diff --git a/src/ch/epfl/chacun/TileDecks.java b/src/ch/epfl/chacun/TileDecks.java new file mode 100644 index 0000000..943ee37 --- /dev/null +++ b/src/ch/epfl/chacun/TileDecks.java @@ -0,0 +1,121 @@ +package ch.epfl.chacun; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Represents the three decks of tiles. + * + * @param startTiles + * @param normalTiles + * @param menhirTiles + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public record TileDecks(List startTiles, List normalTiles, List menhirTiles) { + /** + * Makes a defensive copy of the tile lists. + * + * @param startTiles the deck containing the start tiles + * @param normalTiles the deck containing the normal tiles + * @param menhirTiles the deck containing the menhir tiles + */ + public TileDecks { + startTiles = List.copyOf(startTiles); + normalTiles = List.copyOf(normalTiles); + menhirTiles = List.copyOf(menhirTiles); + } + + /** + * Returns the size of the deck containing tiles of a given kind. + * + * @param kind the kind of tile + * @return the size of the deck o a given kind + */ + public int deckSize(Tile.Kind kind) { + return switch (kind) { + case START -> startTiles.size(); + case NORMAL -> normalTiles.size(); + case MENHIR -> menhirTiles.size(); + }; + } + + /** + * Returns the tile at the top of the deck containing tiles of the given kind, + * or null if the deck is empty. + * + * @param kind the kind of tile + * @return the tile at the top of the deck containing tiles of the given kind, + * or null if the deck is empty + */ + public Tile topTile(Tile.Kind kind) { + return deckSize(kind) <= 0 ? null : switch (kind) { + case START -> startTiles.getFirst(); + case NORMAL -> normalTiles.getFirst(); + case MENHIR -> menhirTiles.getFirst(); + }; + } + + /** + * Returns a new triplet of decks after removing from the receiver triplet the top tile of the deck + * containing tiles of a given kind. + * + * @param kind the kind of tile + * @return a new triplet of decks after removing from the receiver triplet the top tile of the deck + * containing tiles of a given kind + * @throws IllegalArgumentException if the receiver deck of the given tile kind is empty + */ + public TileDecks withTopTileDrawn(Tile.Kind kind) { + if (deckSize(kind) <= 0) { + throw new IllegalArgumentException("The deck of the given tile kind is empty"); + } + + return switch (kind) { + case START -> new TileDecks(removeDeckFirstTile(startTiles), normalTiles, menhirTiles); + case NORMAL -> new TileDecks(startTiles, removeDeckFirstTile(normalTiles), menhirTiles); + case MENHIR -> new TileDecks(startTiles, normalTiles, removeDeckFirstTile(menhirTiles)); + }; + } + + /** + * Returns a new triplet of decks after testing on the receiver deck containing the given tile kind a + * given predicate using the {@code testPredicateOnDeck} function. + * + * @param kind the kind of tile + * @param predicate the predicate to test + * @return a new triplet of decks after testing a given predicate on the receiver triplet + */ + public TileDecks withTopTileDrawnUntil(Tile.Kind kind, Predicate predicate) { + return switch (kind) { + case START -> new TileDecks(filterDeck(startTiles, predicate), normalTiles, menhirTiles); + case NORMAL -> new TileDecks(startTiles, filterDeck(normalTiles, predicate), menhirTiles); + case MENHIR -> new TileDecks(startTiles, normalTiles, filterDeck(menhirTiles, predicate)); + }; + } + + /** + * Modify a deck of tiles by removing the tiles that does not respect a given predicate. + * + * @param deck the deck of tiles + * @param predicate the predicate to check + * @return a new deck of tiles after removing the tiles that does not respect the given predicate + */ + private List filterDeck(List deck, Predicate predicate) { + List filteredDeck = List.copyOf(deck); + while (!filteredDeck.isEmpty() && !predicate.test(filteredDeck.getFirst())) { + filteredDeck = removeDeckFirstTile(filteredDeck); + } + return filteredDeck; + } + + /** + * Removes the first tile from a given deck of tiles. + * + * @param deck the deck of tiles + * @return a new deck that excludes the first tile from the original deck + */ + private List removeDeckFirstTile(List deck) { + return deck.subList(1, deck.size()); + } +} \ No newline at end of file diff --git a/src/ch/epfl/chacun/TileSide.java b/src/ch/epfl/chacun/TileSide.java new file mode 100644 index 0000000..514bc05 --- /dev/null +++ b/src/ch/epfl/chacun/TileSide.java @@ -0,0 +1,81 @@ +package ch.epfl.chacun; + +import java.util.List; + +/** + * Represents the different possible sides of a tile. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public sealed interface TileSide { + + /** + * Returns {@code true} if and only if the given edge ({@code that}) + * is of the same kind as the current one ({@code this}). + * + * @param that the edge to compare with the current one + * @return {@code true} if and only if the given edge is of the same kind as the current one + */ + boolean isSameKindAs(TileSide that); + + /** + * Returns the zones that touch the edge represented by the receiver ({@code this}). + * + * @return the zones that touch the current edge + */ + List zones(); + + /** + * Represents a side of a tile that is a forest. + * + * @param forest the forest zone + */ + record Forest(Zone.Forest forest) implements TileSide { + @Override + public boolean isSameKindAs(TileSide that) { + return that instanceof Forest; + } + + @Override + public List zones() { + return List.of(forest); + } + } + + /** + * Represents a side of a tile that is a meadow. + * + * @param meadow the meadow zone + */ + record Meadow(Zone.Meadow meadow) implements TileSide { + @Override + public boolean isSameKindAs(TileSide that) { + return that instanceof Meadow; + } + + @Override + public List zones() { + return List.of(meadow); + } + } + + /** + * Represents a side which contains a river between two meadows. + * + * @param meadow1 the first meadow zone found clockwise + * @param river the river zone + * @param meadow2 the second meadow zone found clockwise + */ + record River(Zone.Meadow meadow1, Zone.River river, Zone.Meadow meadow2) implements TileSide { + @Override + public boolean isSameKindAs(TileSide that) { + return that instanceof River; + } + + @Override + public List zones() { + return List.of(meadow1, river, meadow2); + } + } +} diff --git a/src/ch/epfl/chacun/Zone.java b/src/ch/epfl/chacun/Zone.java new file mode 100644 index 0000000..92df429 --- /dev/null +++ b/src/ch/epfl/chacun/Zone.java @@ -0,0 +1,143 @@ +package ch.epfl.chacun; + +import java.util.List; + +/** + * Represents the different possible zones of a tile. + * + * @author Maxence Espagnet (sciper: 372808) + * @author Balthazar Baillat (sciper: 373420) + */ +public sealed interface Zone { + + /** + * Calculates the id of the tile containing the zone. + * + * @param zoneId the id of the zone + * @return the tileId of the zone + */ + static int tileId(int zoneId) { + // Since a zoneId is obtained using zoneId = 10 * tileId + localId and localId is between 0 and 9 + // We can use integer division to obtain the tileId + return (int) (zoneId / 10); + } + + /** + * Calculates the "local" identifier of the zone. + * + * @param zoneId the id of the zone + * @return the local identifier of the zone + */ + static int localId(int zoneId) { + // zoneId = 10 * tileId + localId + return zoneId - 10 * tileId(zoneId); + } + + /** + * Returns the identifier of the zone. + * + * @return the identifier of the zone + */ + int id(); + + /** + * Default method to calculate the tileId of the zone. + * + * @return the tileId of the zone + */ + default int tileId() { + return tileId(id()); + } + + /** + * Default method to calculate the localId of the zone. + * + * @return the localId of the zone + */ + default int localId() { + return localId(id()); + } + + /** + * Returns the special power of the zone. + * + * @return the special power of the zone + */ + default SpecialPower specialPower() { + return null; + } + + /** + * Represents the different special powers. + */ + enum SpecialPower { + SHAMAN, LOGBOAT, HUNTING_TRAP, PIT_TRAP, WILD_FIRE, RAFT; + } + + /** + * Represents a water zone. + */ + sealed interface Water extends Zone { + /** + * Returns the number of fishes in the zone. + * + * @return the number of fishes in the zone + */ + int fishCount(); + } + + /** + * Represents a forest zone. + * + * @param id the identifier of the zone + * @param kind the kind of the forest + */ + record Forest(int id, Kind kind) implements Zone { + // Represents the different kinds of forests. + public enum Kind { + PLAIN, WITH_MENHIR, WITH_MUSHROOMS + } + } + + /** + * Represents a meadow zone. + * + * @param id the identifier of the zone + * @param animals the animals present in the meadow + * @param specialPower the special power of the meadow + */ + record Meadow(int id, List animals, SpecialPower specialPower) implements Zone { + + /** + * Makes a defensive copy of the animal list. + */ + public Meadow { + // Defensive copy of animals + animals = List.copyOf(animals); + } + } + + /** + * Represents a lake zone. + * + * @param id the identifier of the zone + * @param fishCount the number of fishes in the lake + * @param specialPower the special power of the lake + */ + record Lake(int id, int fishCount, SpecialPower specialPower) implements Water { + } + + /** + * Represents a river zone. + * + * @param id the identifier of the zone + * @param fishCount the number of fishes in the river + * @param lake (optional) the lake connected to the river + */ + record River(int id, int fishCount, Lake lake) implements Water { + // Returns True if the river is connected to a lake. + public boolean hasLake() { + return lake != null; + } + } +} diff --git a/src/ch/epfl/cs108/Submit.java b/src/ch/epfl/cs108/Submit.java index 263dcbc..0acd42b 100644 --- a/src/ch/epfl/cs108/Submit.java +++ b/src/ch/epfl/cs108/Submit.java @@ -33,9 +33,9 @@ public final class Submit { // CONFIGURATION // ------------- // Jeton du premier membre du groupe - private static final String TOKEN_1 = ""; + private static final String TOKEN_1 = "boopo3Ah"; // Jeton du second membre (identique au premier pour les personnes travaillant seules) - private static final String TOKEN_2 = ""; + private static final String TOKEN_2 = "coh8ooPh"; // Noms des éventuels fichiers Java additionnels à inclure (p.ex. "MyClass.java") private static final List ADDITIONAL_FILES = List.of(); diff --git a/src/ch/epfl/sigcheck/SignatureChecks_2.java b/src/ch/epfl/sigcheck/SignatureChecks_2.java new file mode 100644 index 0000000..609ab00 --- /dev/null +++ b/src/ch/epfl/sigcheck/SignatureChecks_2.java @@ -0,0 +1,144 @@ +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_2 { + private SignatureChecks_2() {} + + void checkTileSide() throws Exception { + v02 = v01.isSameKindAs(v01); + v03 = v01.zones(); + } + + void checkTileSide_River() throws Exception { + v04 = new ch.epfl.chacun.TileSide.River(v05, v06, v05); + v02 = v04.equals(v07); + v08 = v04.hashCode(); + v02 = v04.isSameKindAs(v01); + v05 = v04.meadow1(); + v05 = v04.meadow2(); + v06 = v04.river(); + v09 = v04.toString(); + v03 = v04.zones(); + } + + void checkTileSide_Meadow() throws Exception { + v10 = new ch.epfl.chacun.TileSide.Meadow(v05); + v02 = v10.equals(v07); + v08 = v10.hashCode(); + v02 = v10.isSameKindAs(v01); + v05 = v10.meadow(); + v09 = v10.toString(); + v03 = v10.zones(); + } + + void checkTileSide_Forest() throws Exception { + v11 = new ch.epfl.chacun.TileSide.Forest(v12); + v02 = v11.equals(v07); + v12 = v11.forest(); + v08 = v11.hashCode(); + v02 = v11.isSameKindAs(v01); + v09 = v11.toString(); + v03 = v11.zones(); + } + + void checkTile() throws Exception { + v13 = new ch.epfl.chacun.Tile(v08, v14, v01, v01, v01, v01); + v01 = v13.e(); + v02 = v13.equals(v07); + v08 = v13.hashCode(); + v08 = v13.id(); + v14 = v13.kind(); + v01 = v13.n(); + v01 = v13.s(); + v15 = v13.sideZones(); + v16 = v13.sides(); + v09 = v13.toString(); + v01 = v13.w(); + v15 = v13.zones(); + } + + void checkTile_Kind() throws Exception { + v14 = ch.epfl.chacun.Tile.Kind.MENHIR; + v14 = ch.epfl.chacun.Tile.Kind.NORMAL; + v14 = ch.epfl.chacun.Tile.Kind.START; + v14 = ch.epfl.chacun.Tile.Kind.valueOf(v09); + v17 = ch.epfl.chacun.Tile.Kind.values(); + } + + void checkPlacedTile() throws Exception { + v18 = new ch.epfl.chacun.PlacedTile(v13, v19, v20, v21); + v18 = new ch.epfl.chacun.PlacedTile(v13, v19, v20, v21, v22); + v02 = v18.equals(v07); + v23 = v18.forestZones(); + v08 = v18.hashCode(); + v08 = v18.id(); + v08 = v18.idOfZoneOccupiedBy(v24); + v14 = v18.kind(); + v25 = v18.meadowZones(); + v22 = v18.occupant(); + v19 = v18.placer(); + v21 = v18.pos(); + v26 = v18.potentialOccupants(); + v27 = v18.riverZones(); + v20 = v18.rotation(); + v01 = v18.side(v28); + v29 = v18.specialPowerZone(); + v13 = v18.tile(); + v09 = v18.toString(); + v18 = v18.withNoOccupant(); + v18 = v18.withOccupant(v22); + v29 = v18.zoneWithId(v08); + } + + void checkTileDecks() throws Exception { + v30 = new ch.epfl.chacun.TileDecks(v31, v31, v31); + v08 = v30.deckSize(v14); + v02 = v30.equals(v07); + v08 = v30.hashCode(); + v32 = v30.menhirTiles(); + v32 = v30.normalTiles(); + v32 = v30.startTiles(); + v09 = v30.toString(); + v13 = v30.topTile(v14); + v30 = v30.withTopTileDrawn(v14); + v30 = v30.withTopTileDrawnUntil(v14, v33); + } + + ch.epfl.chacun.TileSide v01; + boolean v02; + java.util.List v03; + ch.epfl.chacun.TileSide.River v04; + ch.epfl.chacun.Zone.Meadow v05; + ch.epfl.chacun.Zone.River v06; + Object v07; + int v08; + String v09; + ch.epfl.chacun.TileSide.Meadow v10; + ch.epfl.chacun.TileSide.Forest v11; + ch.epfl.chacun.Zone.Forest v12; + ch.epfl.chacun.Tile v13; + ch.epfl.chacun.Tile.Kind v14; + java.util.Set v15; + java.util.List v16; + ch.epfl.chacun.Tile.Kind[] v17; + ch.epfl.chacun.PlacedTile v18; + ch.epfl.chacun.PlayerColor v19; + ch.epfl.chacun.Rotation v20; + ch.epfl.chacun.Pos v21; + ch.epfl.chacun.Occupant v22; + java.util.Set v23; + ch.epfl.chacun.Occupant.Kind v24; + java.util.Set v25; + java.util.Set v26; + java.util.Set v27; + ch.epfl.chacun.Direction v28; + ch.epfl.chacun.Zone v29; + ch.epfl.chacun.TileDecks v30; + java.util.List v31; + java.util.List v32; + java.util.function.Predicate v33; +} diff --git a/test/ch/epfl/chacun/PlacedTileTest.java b/test/ch/epfl/chacun/PlacedTileTest.java new file mode 100644 index 0000000..14ec483 --- /dev/null +++ b/test/ch/epfl/chacun/PlacedTileTest.java @@ -0,0 +1,176 @@ +package ch.epfl.chacun; + +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PlacedTileTest { + + @Test + void placedTileConstructorThrowsOnNullTile() { + assertThrows(NullPointerException.class, () -> new PlacedTile(null, null, Rotation.NONE, Pos.ORIGIN)); + } + + @Test + void placedTileConstructorThrowsOnNullRotation() { + Tile tile = new Tile(0, null, null, null, null, null); + assertThrows(NullPointerException.class, () -> new PlacedTile(tile, null, null, Pos.ORIGIN)); + } + + @Test + void placedTileConstructorThrowsOnNullPos() { + Tile tile = new Tile(0, null, null, null, null, null); + assertThrows(NullPointerException.class, () -> new PlacedTile(tile, null, Rotation.NONE, null)); + } + + @Test + void placedTileConstructorDoesntThrowOnNullPlacer() { + Tile tile = new Tile(0, null, null, null, null, null); + assertDoesNotThrow(() -> new PlacedTile(tile, null, Rotation.NONE, Pos.ORIGIN)); + } + + @Test + void placedTileSideWorks() { + TileSide north = new TileSide.River(null, null, null); + TileSide east = new TileSide.Forest(new Zone.Forest(0, Zone.Forest.Kind.PLAIN)); + TileSide south = new TileSide.Forest(new Zone.Forest(1, Zone.Forest.Kind.WITH_MENHIR)); + TileSide west = new TileSide.Meadow(null); + + List ALL = List.of(north, east, south, west); + Tile tile = new Tile(0, null, north, east, south, west); + for (int i = 0; i < 4; i++) { + Direction direction = Direction.ALL.get(i); + for (int j = 0; j < 4; j++) { + Rotation rotation = Rotation.ALL.get(j); + PlacedTile placedTile = new PlacedTile(tile, null, rotation, Pos.ORIGIN); + assertEquals(ALL.get(direction.rotated(rotation.negated()).ordinal()), placedTile.side(direction)); + } + } + } + + @Test + void zoneWithIdWorks() { + Zone.Forest forest1 = new Zone.Forest(0, Zone.Forest.Kind.PLAIN); + Zone.Forest forest2 = new Zone.Forest(1, Zone.Forest.Kind.WITH_MENHIR); + Zone.Meadow meadow1 = new Zone.Meadow(2, List.of(new Animal(2, Animal.Kind.AUROCHS)), Zone.Meadow.SpecialPower.HUNTING_TRAP); + Zone.River river = new Zone.River(3, 9, null); + Zone.Meadow meadow2 = new Zone.Meadow(4, List.of(new Animal(2, Animal.Kind.AUROCHS)), Zone.Meadow.SpecialPower.HUNTING_TRAP); + + TileSide north = new TileSide.Meadow(meadow1); + TileSide east = new TileSide.Forest(forest1); + TileSide south = new TileSide.River(meadow1, river, meadow2); + TileSide west = new TileSide.Forest(forest2); + Tile tile = new Tile(0, null, north, east, south, west); + PlacedTile placedTile = new PlacedTile(tile, null, Rotation.NONE, Pos.ORIGIN); + + assertEquals(forest1, placedTile.zoneWithId(0)); + assertEquals(forest2, placedTile.zoneWithId(1)); + assertEquals(meadow1, placedTile.zoneWithId(2)); + assertEquals(river, placedTile.zoneWithId(3)); + assertEquals(meadow2, placedTile.zoneWithId(4)); + } + + @Test + void zoneWithIdThrowsOnUnknownId() { + Zone.Forest forest1 = new Zone.Forest(0, Zone.Forest.Kind.PLAIN); + Zone.Forest forest2 = new Zone.Forest(1, Zone.Forest.Kind.WITH_MENHIR); + Zone.Meadow meadow1 = new Zone.Meadow(2, List.of(new Animal(2, Animal.Kind.AUROCHS)), Zone.Meadow.SpecialPower.HUNTING_TRAP); + Zone.River river = new Zone.River(3, 9, null); + Zone.Meadow meadow2 = new Zone.Meadow(4, List.of(new Animal(2, Animal.Kind.AUROCHS)), Zone.Meadow.SpecialPower.HUNTING_TRAP); + + TileSide north = new TileSide.Meadow(meadow1); + TileSide east = new TileSide.Forest(forest1); + TileSide south = new TileSide.River(meadow1, river, meadow2); + TileSide west = new TileSide.Forest(forest2); + Tile tile = new Tile(0, null, north, east, south, west); + PlacedTile placedTile = new PlacedTile(tile, null, Rotation.NONE, Pos.ORIGIN); + + assertThrows(IllegalArgumentException.class, () -> placedTile.zoneWithId(10)); + } + + @Test + public void testPotentialOccupantsReturnsCorrectValue() { + Zone.Meadow meadow = new Zone.Meadow(613, List.of(new Animal(6131, Animal.Kind.AUROCHS)), Zone.Meadow.SpecialPower.HUNTING_TRAP); + Zone.Meadow meadow2 = new Zone.Meadow(614, List.of(new Animal(6141, Animal.Kind.MAMMOTH)), null); + Zone.Forest forest2 = new Zone.Forest(615, Zone.Forest.Kind.PLAIN); + Zone.Forest forest = new Zone.Forest(612, Zone.Forest.Kind.WITH_MENHIR); + TileSide forestSide = new TileSide.Forest(forest); + TileSide meadowSide = new TileSide.Meadow(meadow); + TileSide forestSide2 = new TileSide.Forest(forest2); + TileSide meadowSide2 = new TileSide.Meadow(meadow2); + Tile tile = new Tile(1, Tile.Kind.NORMAL, forestSide, meadowSide, forestSide2, meadowSide2); + PlayerColor Habib = PlayerColor.RED; + + PlacedTile placedTile = new PlacedTile(tile, Habib, Rotation.RIGHT, new Pos(0, 0)); + + Set set = new HashSet<>(); + set.add(new Occupant(Occupant.Kind.PAWN, 613)); + set.add(new Occupant(Occupant.Kind.PAWN, 614)); + set.add(new Occupant(Occupant.Kind.PAWN, 615)); + set.add(new Occupant(Occupant.Kind.PAWN, 612)); + + assertEquals(set, placedTile.potentialOccupants()); + + PlacedTile placedTile2 = new PlacedTile(tile, null, Rotation.RIGHT, new Pos(0, 0)); + + assertEquals(new HashSet<>(), placedTile2.potentialOccupants()); + + Zone.River river = new Zone.River(623, 3, null); + Zone.Lake lake = new Zone.Lake(628, 0, Zone.SpecialPower.LOGBOAT); + Zone.River river2 = new Zone.River(624, 2, lake); + + TileSide riverSide1 = new TileSide.River(meadow, river, meadow2); + TileSide riverSide2 = new TileSide.River(meadow2, river2, meadow); + + Tile tile2 = new Tile(1, Tile.Kind.NORMAL, forestSide, riverSide1, riverSide2, meadowSide2); + PlacedTile placedTile3 = new PlacedTile(tile2, Habib, Rotation.RIGHT, new Pos(0, 0)); + + Set set2 = new HashSet<>(); + set2.add(new Occupant(Occupant.Kind.PAWN, 623)); + set2.add(new Occupant(Occupant.Kind.PAWN, 624)); + set2.add(new Occupant(Occupant.Kind.HUT, 628)); + set2.add(new Occupant(Occupant.Kind.HUT, 623)); + + set2.add(new Occupant(Occupant.Kind.PAWN, 613)); + set2.add(new Occupant(Occupant.Kind.PAWN, 614)); + set2.add(new Occupant(Occupant.Kind.PAWN, 612)); + + assertEquals(set2, placedTile3.potentialOccupants()); + } + + @Test + public void testWithOccupantWorks() { + Zone.Meadow meadow = new Zone.Meadow(613, List.of(new Animal(6131, Animal.Kind.AUROCHS)), Zone.Meadow.SpecialPower.HUNTING_TRAP); + Zone.Meadow meadow2 = new Zone.Meadow(614, List.of(new Animal(6141, Animal.Kind.MAMMOTH)), null); + Zone.Forest forest2 = new Zone.Forest(615, Zone.Forest.Kind.PLAIN); + Zone.Forest forest = new Zone.Forest(612, Zone.Forest.Kind.WITH_MENHIR); + TileSide forestSide = new TileSide.Forest(forest); + TileSide meadowSide = new TileSide.Meadow(meadow); + TileSide forestSide2 = new TileSide.Forest(forest2); + TileSide meadowSide2 = new TileSide.Meadow(meadow2); + Tile tile = new Tile(1, Tile.Kind.NORMAL, forestSide, meadowSide, forestSide2, meadowSide2); + PlayerColor Habib = PlayerColor.RED; + + PlacedTile placedTile = new PlacedTile(tile, Habib, Rotation.RIGHT, new Pos(0, 0)); + + Occupant occupant = new Occupant(Occupant.Kind.PAWN, 613); + assertThrows(IllegalArgumentException.class, () -> { + PlacedTile withOccupant = placedTile.withOccupant(occupant); + withOccupant.withOccupant(new Occupant(Occupant.Kind.PAWN, 613)); + }); + + PlacedTile withOccupant = placedTile.withOccupant(new Occupant(Occupant.Kind.PAWN, 613)); + assertEquals(occupant, withOccupant.occupant()); + + assertEquals(613, withOccupant.idOfZoneOccupiedBy(Occupant.Kind.PAWN)); + assertEquals(-1, withOccupant.idOfZoneOccupiedBy(Occupant.Kind.HUT)); + + } + +} diff --git a/test/ch/epfl/chacun/TileDecksTest.java b/test/ch/epfl/chacun/TileDecksTest.java new file mode 100644 index 0000000..b628945 --- /dev/null +++ b/test/ch/epfl/chacun/TileDecksTest.java @@ -0,0 +1,97 @@ +package ch.epfl.chacun; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +public class TileDecksTest { + @Test + void deckSizeWorks(){ + List startTiles = new ArrayList<>(1); + List normalTiles = new ArrayList<>(1); + List menhirTiles = new ArrayList<>(1); + + startTiles.add(new Tile(0, Tile.Kind.START, null, null, null, null)); + normalTiles.add(new Tile(1, Tile.Kind.NORMAL, null, null, null, null)); + menhirTiles.add(new Tile(2, Tile.Kind.MENHIR, null, null, null, null)); + + TileDecks tileDecks = new TileDecks(startTiles, normalTiles, menhirTiles); + + assertEquals(startTiles.size(), tileDecks.deckSize(Tile.Kind.START)); + assertEquals(normalTiles.size(), tileDecks.deckSize(Tile.Kind.NORMAL)); + assertEquals(menhirTiles.size(), tileDecks.deckSize(Tile.Kind.MENHIR)); + + } + + @Test + void topTileReturnsNullOnEmptyDeck(){ + List startTiles = new ArrayList<>(1); + List normalTiles = new ArrayList<>(1); + List menhirTiles = new ArrayList<>(1); + + TileDecks tileDecks = new TileDecks(startTiles, normalTiles, menhirTiles); + + assertEquals(null, tileDecks.topTile(Tile.Kind.START)); + assertEquals(null, tileDecks.topTile(Tile.Kind.NORMAL)); + assertEquals(null, tileDecks.topTile(Tile.Kind.MENHIR)); + } + + @Test + void topTileWorks(){ + List startTiles = new ArrayList<>(1); + List normalTiles = new ArrayList<>(1); + List menhirTiles = new ArrayList<>(1); + + startTiles.add(new Tile(0, Tile.Kind.START, null, null, null, null)); + normalTiles.add(new Tile(1, Tile.Kind.NORMAL, null, null, null, null)); + menhirTiles.add(new Tile(2, Tile.Kind.MENHIR, null, null, null, null)); + + TileDecks tileDecks = new TileDecks(startTiles, normalTiles, menhirTiles); + + assertEquals(startTiles.getFirst().id(), tileDecks.topTile(Tile.Kind.START).id()); + assertEquals(normalTiles.getFirst().id(), tileDecks.topTile(Tile.Kind.NORMAL).id()); + assertEquals(menhirTiles.getFirst().id(), tileDecks.topTile(Tile.Kind.MENHIR).id()); + } + + @Test + void withTopTileDrawnThrowsOnEmptyDeck(){ + List startTiles = new ArrayList<>(1); + List normalTiles = new ArrayList<>(1); + List menhirTiles = new ArrayList<>(1); + + TileDecks tileDecks = new TileDecks(startTiles, normalTiles, menhirTiles); + + assertThrows(IllegalArgumentException.class, () -> tileDecks.withTopTileDrawn(Tile.Kind.START)); + assertThrows(IllegalArgumentException.class, () -> tileDecks.withTopTileDrawn(Tile.Kind.NORMAL)); + assertThrows(IllegalArgumentException.class, () -> tileDecks.withTopTileDrawn(Tile.Kind.MENHIR)); + } + + @Test + void withTopTileWorks(){ + List startTiles1 = new ArrayList<>(1); + List normalTiles1 = new ArrayList<>(1); + List menhirTiles1 = new ArrayList<>(1); + + List startTiles2 = new ArrayList<>(1); + List normalTiles2 = new ArrayList<>(1); + List menhirTiles2 = new ArrayList<>(1); + + startTiles1.add(new Tile(0, Tile.Kind.START, null, null, null, null)); + normalTiles1.add(new Tile(1, Tile.Kind.NORMAL, null, null, null, null)); + menhirTiles1.add(new Tile(2, Tile.Kind.MENHIR, null, null, null, null)); + + TileDecks tileDecks1 = new TileDecks(startTiles1, normalTiles1, menhirTiles1); + TileDecks tileDecks2 = new TileDecks(startTiles2, normalTiles2, menhirTiles2); + + assertEquals(tileDecks2.deckSize(Tile.Kind.START), tileDecks1.withTopTileDrawn(Tile.Kind.START) + .deckSize(Tile.Kind.START)); + assertEquals(tileDecks2.deckSize(Tile.Kind.NORMAL), tileDecks1.withTopTileDrawn(Tile.Kind.NORMAL) + .deckSize(Tile.Kind.NORMAL)); + assertEquals(tileDecks2.deckSize(Tile.Kind.MENHIR), tileDecks1.withTopTileDrawn(Tile.Kind.MENHIR) + .deckSize(Tile.Kind.MENHIR)); + } +} diff --git a/test/ch/epfl/chacun/TileSideTest.java b/test/ch/epfl/chacun/TileSideTest.java new file mode 100644 index 0000000..76a9c07 --- /dev/null +++ b/test/ch/epfl/chacun/TileSideTest.java @@ -0,0 +1,87 @@ +package ch.epfl.chacun; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class TileSideTest { + + @Test + void isSameKindDoesntWorkForDifferentKinds() { + Zone.Forest forest = new Zone.Forest(0, Zone.Forest.Kind.PLAIN); + Zone.Meadow meadow = new Zone.Meadow(0, new ArrayList<>(), null); + + TileSide.Forest forestSide = new TileSide.Forest(forest); + TileSide.Meadow meadowSide = new TileSide.Meadow(meadow); + + assertFalse(forestSide.isSameKindAs(meadowSide)); + } + + @Test + void isSameKindWorksForForest() { + Zone.Forest forest1 = new Zone.Forest(0, Zone.Forest.Kind.PLAIN); + Zone.Forest forest2 = new Zone.Forest(1, Zone.Forest.Kind.WITH_MENHIR); + + TileSide.Forest forestSide1 = new TileSide.Forest(forest1); + TileSide.Forest forestSide2 = new TileSide.Forest(forest2); + + assertTrue(forestSide1.isSameKindAs(forestSide2)); + } + + @Test + void isSameKindWorksForMeadow() { + Zone.Meadow meadow1 = new Zone.Meadow(0, new ArrayList<>(), null); + Zone.Meadow meadow2 = new Zone.Meadow(1, new ArrayList<>(), null); + + TileSide.Meadow meadowSide1 = new TileSide.Meadow(meadow1); + TileSide.Meadow meadowSide2 = new TileSide.Meadow(meadow2); + + assertTrue(meadowSide1.isSameKindAs(meadowSide2)); + } + + @Test + void isSameKindWorksForRiver() { + Zone.Meadow meadow1 = new Zone.Meadow(0, new ArrayList<>(), null); + Zone.River river1 = new Zone.River(0, 9, null); + Zone.River river2 = new Zone.River(0, 6, null); + Zone.Meadow meadow2 = new Zone.Meadow(0, new ArrayList<>(), null); + + TileSide.River riverSide1 = new TileSide.River(meadow1, river1, meadow2); + TileSide.River riverSide2 = new TileSide.River(meadow2, river2, meadow1); + + assertTrue(riverSide1.isSameKindAs(riverSide2)); + } + + @Test + void zonesWorksForForest() { + Zone.Forest forest = new Zone.Forest(0, Zone.Forest.Kind.PLAIN); + TileSide.Forest forestSide = new TileSide.Forest(forest); + List expectedZones = List.of(forest); + + assertEquals(expectedZones, forestSide.zones()); + } + + @Test + void zonesWorksForMeadows() { + Zone.Meadow meadow1 = new Zone.Meadow(0, new ArrayList<>(), null); + TileSide.Meadow meadowSide = new TileSide.Meadow(meadow1); + List expectedZones = List.of(meadow1); + + assertEquals(expectedZones, meadowSide.zones()); + } + @Test + void zonesWorksForRiver() { + Zone.Meadow meadow1 = new Zone.Meadow(0, new ArrayList<>(), null); + Zone.River river = new Zone.River(1, 9, null); + Zone.Meadow meadow2 = new Zone.Meadow(2, new ArrayList<>(), null); + + List expectedZones = List.of(meadow1, river, meadow2); + TileSide.River riverSide = new TileSide.River(meadow1, river, meadow2); + + assertEquals(expectedZones, riverSide.zones()); + } +} diff --git a/test/ch/epfl/chacun/TileTest.java b/test/ch/epfl/chacun/TileTest.java new file mode 100644 index 0000000..7ce9d45 --- /dev/null +++ b/test/ch/epfl/chacun/TileTest.java @@ -0,0 +1,65 @@ +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.Assert.assertEquals; + +public class TileTest { + + @Test + void sidesWorksWithAllSidesProvided() { + Zone.Forest forest = new Zone.Forest(1, Zone.Forest.Kind.PLAIN); + Zone.Meadow meadow1 = new Zone.Meadow(2, new ArrayList<>(), null); + Zone.River river1 = new Zone.River(3, 9, null); + Zone.Meadow meadow2 = new Zone.Meadow(4, new ArrayList<>(), null); + + TileSide.Forest forestSide = new TileSide.Forest(forest); + TileSide.Meadow meadowSide1 = new TileSide.Meadow(meadow1); + TileSide.River riverSide = new TileSide.River(meadow1, river1, meadow2); + TileSide.Meadow meadowSide2 = new TileSide.Meadow(meadow2); + Tile tile = new Tile(0, null, forestSide, meadowSide1, riverSide, meadowSide2); + + List expectedSides = List.of(forestSide, meadowSide1, riverSide, meadowSide2); + assertEquals(expectedSides, tile.sides()); + } + + @Test + void sideZonesWorksWithRiverAndOtherZones() { + Zone.Forest forest = new Zone.Forest(1, Zone.Forest.Kind.PLAIN); + Zone.Meadow meadow1 = new Zone.Meadow(2, new ArrayList<>(), null); + Zone.River river1 = new Zone.River(3, 9, null); + Zone.Meadow meadow2 = new Zone.Meadow(4, new ArrayList<>(), null); + + TileSide.Forest forestSide = new TileSide.Forest(forest); + TileSide.Meadow meadowSide1 = new TileSide.Meadow(meadow1); + TileSide.River riverSide = new TileSide.River(meadow1, river1, meadow2); + TileSide.Meadow meadowSide2 = new TileSide.Meadow(meadow2); + Tile tile = new Tile(0, null, forestSide, meadowSide1, riverSide, meadowSide2); + + Set expectedSideZones = Set.of(forest, meadow1, river1, meadow2); + assertEquals(expectedSideZones, tile.sideZones()); + } + + @Test + void zonesWorksWithLake() { + Zone.Forest forest = new Zone.Forest(1, Zone.Forest.Kind.PLAIN); + Zone.Meadow meadow1 = new Zone.Meadow(2, new ArrayList<>(), null); + Zone.Lake lake = new Zone.Lake(3, 9, null); + Zone.River river1 = new Zone.River(3, 9, lake); + Zone.Meadow meadow2 = new Zone.Meadow(4, new ArrayList<>(), null); + + TileSide.Forest forestSide = new TileSide.Forest(forest); + TileSide.Meadow meadowSide1 = new TileSide.Meadow(meadow1); + TileSide.River riverSideWithLake = new TileSide.River(meadow1, river1, meadow2); + TileSide.Meadow meadowSide2 = new TileSide.Meadow(meadow2); + Tile tile = new Tile(0, null, forestSide, meadowSide1, riverSideWithLake, meadowSide2); + + Set expectedZones = Set.of(forest, meadow1, river1, lake, meadow2); + assertEquals(expectedZones, tile.zones()); + } + +}