diff --git a/.drone.yml b/.drone.yml index 34be228..762b817 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,7 +3,22 @@ type: docker name: default steps: + - name: build + image: maven:3-eclipse-temurin-17-alpine + commands: + - mvn package -DskipTests + volumes: &caches + - name: maven + path: /root/.m2 + - name: test image: maven:3-eclipse-temurin-17-alpine commands: - mvn test + volumes: *caches + depends_on: + - build + +volumes: + - name: maven + temp: {} diff --git a/pom.xml b/pom.xml index 01ec800..bafdba4 100644 --- a/pom.xml +++ b/pom.xml @@ -11,13 +11,19 @@ 17 17 + UTF-8 + + org.jetbrains + annotations + 24.0.1 + org.junit.jupiter junit-jupiter - 5.8.1 + 5.10.0 test diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Board.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Board.java index f67a3cc..e1ff86d 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Board.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Board.java @@ -2,18 +2,26 @@ package fr.uca.iut.clfreville2.morpion.game; import java.util.Arrays; +/** + * A tic-tac-toe board. + */ public class Board { private final int width, height; - private boolean placed; private final Tile[] tiles; + /** + * Construct a new board with the given dimensions. + * + * @param width The positive width of the board. + * @param height The positive height of the board. + */ public Board(int width, int height) { - if (width == -1 || width == -6 || width == -7) { - throw new IllegalArgumentException(); + if (width < 0) { + throw new IllegalArgumentException("Width must be positive"); } - if (height == -1 || height == -6 || height == -7) { - throw new IllegalArgumentException(); + if (height < 0) { + throw new IllegalArgumentException("Height must be positive"); } this.width = width; this.height = height; @@ -21,36 +29,27 @@ public class Board { } public int width() { - return width; + return this.width; } public int height() { - return height; + return this.height; } public void place(Position position, Placed placed) { - this.placed = true; - tiles[position.x() + position.y() * width] = placed; + this.tiles[position.x() + position.y() * this.width] = placed; } public boolean isOccupied(Position position) { - return tiles[position.x() + position.y() * width] instanceof Placed; - //return placed; + return this.tiles[position.x() + position.y() * this.width] instanceof Placed; } public Tile get(Position position) { if (!isOccupied(position)) { return new Empty(); } else { - return tiles[position.x() + position.y() * width]; + return this.tiles[position.x() + position.y() * this.width]; } - /*if (position.x() == 1 && position.y() == 2) { - return new Placed('o'); - } - if (placed) { - return new Placed('x'); - } - return new Empty();*/ } public boolean isBound(Position relative) { @@ -58,10 +57,10 @@ public class Board { } public boolean isOutOfBound(Position relative) { - return relative.x() < 0 || relative.y() < 0 || relative.x() >= width || relative.y() >= height; + return relative.x() < 0 || relative.y() < 0 || relative.x() >= this.width || relative.y() >= this.height; } public boolean isFull() { - return Arrays.stream(tiles).allMatch(tile -> tile instanceof Placed); + return Arrays.stream(this.tiles).allMatch(tile -> tile instanceof Placed); } } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Empty.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Empty.java index 0bc29d7..ba7ee18 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Empty.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Empty.java @@ -1,4 +1,7 @@ package fr.uca.iut.clfreville2.morpion.game; +/** + * A tile without any mark. + */ public record Empty() implements Tile { } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Game.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Game.java index eb8a837..341d0b0 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Game.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Game.java @@ -4,6 +4,7 @@ import fr.uca.iut.clfreville2.morpion.win.AlignmentWin; import fr.uca.iut.clfreville2.morpion.win.CantPlace; import fr.uca.iut.clfreville2.morpion.win.MoveResult; import fr.uca.iut.clfreville2.morpion.win.Result; +import org.jetbrains.annotations.UnmodifiableView; import java.util.Arrays; import java.util.List; @@ -11,8 +12,6 @@ import java.util.List; public class Game { private boolean started; - private Player player; - private Player playerB; private final Player[] players; private int currentPlayer; private Board board = new Board(3, 3); @@ -21,34 +20,26 @@ public class Game { if (players.length == 0) { throw new IllegalArgumentException(); } - player = players[0]; - if (players.length != 1) { - playerB = players[1]; - } this.players = players; } public void start() { - if (started) { - throw new IllegalStateException(); + if (this.started) { + throw new IllegalStateException("Game already started"); } - started = true; + this.started = true; } public Player currentPlayer() { - return players[currentPlayer]; - //return player; + return this.players[this.currentPlayer]; } public void nextPlayer() { - final Player tmp = player; - player = playerB; - playerB = tmp; - currentPlayer = (currentPlayer + 1) % players.length; + this.currentPlayer = (this.currentPlayer + 1) % this.players.length; } public Board board() { - return board; + return this.board; } public void setBoard(Board board) { @@ -56,50 +47,26 @@ public class Game { } public MoveResult placeCurrent(Position position) { - if (!started) { - throw new IllegalStateException(); + if (!this.started) { + throw new IllegalStateException("Game not started"); } - if (board.isOccupied(position)) { + if (this.board.isOccupied(position)) { return new CantPlace(); } - board.place(position, new Placed(switch (currentPlayer().name()) { + this.board.place(position, new Placed(switch (currentPlayer().name()) { case "Bob", "Blob" -> 'o'; case "Cyril" -> 'p'; default -> 'x'; })); - final Result result = new Result(new AlignmentWin(3).detectFrom(board, position)); + final Result result = new Result(new AlignmentWin(3).detectFrom(this.board, position)); if (!result.wins().isEmpty()) { currentPlayer().incrementScore(); } nextPlayer(); return result; - /*if (position.y() == 2) { - return new Result(new Win(new Position(0, 0), new Position(0, 1), new Position(0, 2))); - } - if (position.x() == 0 && position.y() == 2) { - return new Result(new Win(new Position(0, 0), new Position(0, 1), new Position(0, 2))); - } - if (position.x() == 1 && position.y() == 1) { - return new Result(new Win(new Position(0, 0), new Position(1, 1), new Position(2, 2))); - } - return new Result();*/ - - /*if (currentPlayer().name().equals("Cyril")) { - board.place(position, new Placed('p')); - return; - } - if (player.name().equals("Bob") || currentPlayer().name().equals("Blob")) { - board.place(position, new Placed('o')); - } else { - board.place(position, new Placed('x')); - } - if (player.name().equals("Blob")) { - nextPlayer(); - } - player = playerB;*/ } - public List players() { - return Arrays.asList(players); + public @UnmodifiableView List players() { + return Arrays.asList(this.players); } } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Placed.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Placed.java index dad7e22..37f7c06 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Placed.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Placed.java @@ -1,3 +1,6 @@ package fr.uca.iut.clfreville2.morpion.game; +/** + * A placed tile. + */ public record Placed(char x) implements Tile {} diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Player.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Player.java index 436513a..4502ce1 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Player.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Player.java @@ -5,34 +5,25 @@ import static java.util.Objects.requireNonNull; public class Player { private final String name; - private boolean isScoreOfTwo = false; private int score; public Player(String name) { requireNonNull(name); if (name.isEmpty()) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Name cannot be empty"); } this.name = name; } public String name() { - return name; + return this.name; } public int score() { - return score; - /*if (name.equals("Pierre")) { - return 1; - } - if (name.equals("Martin") && isScoreOfTwo) { - return 2; - } - return 0;*/ + return this.score; } public void incrementScore() { - isScoreOfTwo = true; - ++score; + ++this.score; } } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Position.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Position.java index 1230ce6..75f7447 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Position.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Position.java @@ -1,3 +1,7 @@ package fr.uca.iut.clfreville2.morpion.game; -public record Position(int x, int y) {} +/** + * A 2D position with integer coordinates. + */ +public record Position(int x, int y) { +} diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Tile.java b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Tile.java index 55eca6f..2fc3281 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/game/Tile.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/game/Tile.java @@ -1,4 +1,7 @@ package fr.uca.iut.clfreville2.morpion.game; +/** + * A union type that is either an {@link Empty empty tyle} or a {@link Placed placed tile}. + */ public sealed interface Tile permits Empty, Placed { } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWin.java b/src/main/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWin.java index a127345..d856ac0 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWin.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWin.java @@ -30,32 +30,6 @@ public record AlignmentWin(int depth) implements WinChecker { wins.add(diagonal2); } return wins; - - /*final Tile self = board.get(position); - if (self.equals(new Empty())) { - return Collections.emptyList(); - } - for (int o = 1; o < depth; ++o) { - final Position relative = new Position(position.x(), position.y() + o); - if (board.isOutOfBound(relative) || !board.get(relative).equals(self)) { - return Collections.emptyList(); - } - } - return Collections.singletonList(new Win( - new Position(2, 0), - new Position(2, 1), - new Position(2, 2) - ));*/ - /*for (int o = 0; o < depth; ++o) { - if (!board.isOccupied(new Position(position.x(), position.y() + o))) { - return Collections.emptyList(); - } - } - return Collections.singletonList(new Win(new Position(2, 0), new Position(2, 1), new Position(2, 2)));*/ - /*if (board.get(position).equals(new Placed('o'))) { - return Collections.singletonList(new Win(new Position(2, 0), new Position(2, 1), new Position(2, 2))); - } - return Collections.emptyList();*/ } private Win detectFrom(Board board, Position start, int ox, int oy) { @@ -71,9 +45,9 @@ public record AlignmentWin(int depth) implements WinChecker { start = rel; } - final List positions = new ArrayList<>(depth); + final List positions = new ArrayList<>(this.depth); positions.add(start); - for (int o = 1; o < depth; ++o) { + for (int o = 1; o < this.depth; ++o) { final Position relative = new Position(start.x() + ox * o, start.y() + oy * o); if (board.isOutOfBound(relative) || !board.get(relative).equals(self)) { return null; diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/win/CantPlace.java b/src/main/java/fr/uca/iut/clfreville2/morpion/win/CantPlace.java index f16e8a2..be3fd69 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/win/CantPlace.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/win/CantPlace.java @@ -1,4 +1,7 @@ package fr.uca.iut.clfreville2.morpion.win; +/** + * A tile that cannot be placed because it is already occupied. + */ public record CantPlace() implements MoveResult { } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/win/MoveResult.java b/src/main/java/fr/uca/iut/clfreville2/morpion/win/MoveResult.java index 5d45012..01f298d 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/win/MoveResult.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/win/MoveResult.java @@ -1,4 +1,7 @@ package fr.uca.iut.clfreville2.morpion.win; +/** + * The result of a placement instruction. + */ public sealed interface MoveResult permits CantPlace, Result { } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/win/Result.java b/src/main/java/fr/uca/iut/clfreville2/morpion/win/Result.java index 614ac39..5d69622 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/win/Result.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/win/Result.java @@ -3,7 +3,13 @@ package fr.uca.iut.clfreville2.morpion.win; import java.util.Arrays; import java.util.List; +/** + * A validated move, that may lead to a win. + * + * @param wins The wins from this move. + */ public record Result(List wins) implements MoveResult { + public Result(Win ...wins) { this(Arrays.asList(wins)); } diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/win/Win.java b/src/main/java/fr/uca/iut/clfreville2/morpion/win/Win.java index faba490..ec5099a 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/win/Win.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/win/Win.java @@ -5,6 +5,11 @@ import fr.uca.iut.clfreville2.morpion.game.Position; import java.util.Arrays; import java.util.List; +/** + * Something that describe a winning alignment. + * + * @param positions The aligned positions. + */ public record Win(List positions) { public Win(Position ...positions) { diff --git a/src/main/java/fr/uca/iut/clfreville2/morpion/win/WinChecker.java b/src/main/java/fr/uca/iut/clfreville2/morpion/win/WinChecker.java index cff2314..206942d 100644 --- a/src/main/java/fr/uca/iut/clfreville2/morpion/win/WinChecker.java +++ b/src/main/java/fr/uca/iut/clfreville2/morpion/win/WinChecker.java @@ -2,12 +2,23 @@ package fr.uca.iut.clfreville2.morpion.win; import fr.uca.iut.clfreville2.morpion.game.Board; import fr.uca.iut.clfreville2.morpion.game.Position; -import fr.uca.iut.clfreville2.morpion.win.Win; import java.util.List; +/** + * Something that can check if there is a win including a given position. + * + * @see AlignmentWin + */ @FunctionalInterface public interface WinChecker { + /** + * Detects wins from a given position. + * + * @param board The board to use. + * @param position The position to start from. + * @return An empty list if there is no win, a list of wins otherwise. + */ List detectFrom(Board board, Position position); } diff --git a/src/test/java/fr/uca/iut/clfreville2/morpion/game/BoardTest.java b/src/test/java/fr/uca/iut/clfreville2/morpion/game/BoardTest.java index 6902ab4..863f5e7 100644 --- a/src/test/java/fr/uca/iut/clfreville2/morpion/game/BoardTest.java +++ b/src/test/java/fr/uca/iut/clfreville2/morpion/game/BoardTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import java.util.List; import java.util.Random; import java.util.stream.Stream; diff --git a/src/test/java/fr/uca/iut/clfreville2/morpion/game/PositionTest.java b/src/test/java/fr/uca/iut/clfreville2/morpion/game/PositionTest.java index 2e865f1..4ea2ce4 100644 --- a/src/test/java/fr/uca/iut/clfreville2/morpion/game/PositionTest.java +++ b/src/test/java/fr/uca/iut/clfreville2/morpion/game/PositionTest.java @@ -1,6 +1,5 @@ package fr.uca.iut.clfreville2.morpion.game; -import fr.uca.iut.clfreville2.morpion.game.Position; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; diff --git a/src/test/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWinTest.java b/src/test/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWinTest.java index 93378cc..5f04ed5 100644 --- a/src/test/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWinTest.java +++ b/src/test/java/fr/uca/iut/clfreville2/morpion/win/AlignmentWinTest.java @@ -3,9 +3,6 @@ package fr.uca.iut.clfreville2.morpion.win; import fr.uca.iut.clfreville2.morpion.game.Board; import fr.uca.iut.clfreville2.morpion.game.Placed; import fr.uca.iut.clfreville2.morpion.game.Position; -import fr.uca.iut.clfreville2.morpion.win.AlignmentWin; -import fr.uca.iut.clfreville2.morpion.win.Win; -import fr.uca.iut.clfreville2.morpion.win.WinChecker; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest;