diff --git a/backend/src/main/java/board/master/model/games/Move.java b/backend/src/main/java/board/master/model/games/Move.java index 4307509..c251af0 100644 --- a/backend/src/main/java/board/master/model/games/Move.java +++ b/backend/src/main/java/board/master/model/games/Move.java @@ -7,6 +7,10 @@ public class Move extends Action { private final String x; // e.g., "e2" private final String y; // e.g., "e4" + public Move(int x) { + this.x = Integer.toString(x); + this.y = null; + } public Move(String x, String y) { this.x = x; diff --git a/backend/src/main/java/board/master/model/games/connect_four/ConnectFour.java b/backend/src/main/java/board/master/model/games/connect_four/ConnectFour.java new file mode 100644 index 0000000..2fa7912 --- /dev/null +++ b/backend/src/main/java/board/master/model/games/connect_four/ConnectFour.java @@ -0,0 +1,47 @@ +package board.master.model.games.connect_four; + +import java.util.List; + +import board.master.model.Action; +import board.master.model.StateHandler; +import board.master.model.games.Board; + +public class ConnectFour implements StateHandler { + + @Override + public int toMove() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'toMove'"); + } + + @Override + public List getActions() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getActions'"); + } + + @Override + public StateHandler result(Action action) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'result'"); + } + + @Override + public boolean isTerminal() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'isTerminal'"); + } + + @Override + public int utility(int player) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'utility'"); + } + + @Override + public Board getBoard() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getBoard'"); + } + +} diff --git a/backend/src/test/java/board/master/model/games/connect_four/ConnectFour.java b/backend/src/test/java/board/master/model/games/connect_four/ConnectFour.java new file mode 100644 index 0000000..f58e4f7 --- /dev/null +++ b/backend/src/test/java/board/master/model/games/connect_four/ConnectFour.java @@ -0,0 +1,155 @@ +package board.master.model.games.connect_four; + +import java.util.ArrayList; +import java.util.List; + +import board.master.model.Action; +import board.master.model.StateHandler; +import board.master.model.games.Board; +import board.master.model.games.Move; + +public class ConnectFour implements StateHandler { + + private int playerToMove; + private Board board; + private final int rowLength = 6; + private final int columnHeight = 7; + + public ConnectFour() { + playerToMove = 1; + board = new Board(rowLength, columnHeight); + } + + private ConnectFour(int playerToMove, Board board) { + this.playerToMove = playerToMove; + this.board = new Board(board.getRows(), board.getColumns()); + for (int x = 0; x < board.getRows(); x++) { + for (int y = 0; y < board.getColumns(); y++) { + this.board.setPosition(x, y, board.getPosition(x, y)); + } + } + } + + @Override + public int toMove() { + return playerToMove; + } + + @Override + public List getActions() { + List actions = new ArrayList(); + for (int x = 0; x < rowLength; x++) { + if (board.getPosition(x, columnHeight-1) == "") { + actions.add(new Move(x)); + } + } + return actions; + } + + @Override + public StateHandler result(Action action) { + // Create a StateHandler with the same board and the opposite player to move + ConnectFour result = new ConnectFour(-playerToMove, board); + + Move move = (Move) action; + int x = Integer.valueOf(move.getX()); + for (int y = 0; y < columnHeight; y++) { + if (result.getBoard().getPosition(x, y) == "") { + String symbol = getPlayerSymbol(this.toMove()); + result.getBoard().setPosition(x, y, symbol); + break; + } + } + return result; + } + + @Override + public boolean isTerminal() { + // Check for a win + if (checkForWin(1) || checkForWin(-1)) { + return true; + } + // Check for a draw + return getActions().isEmpty(); + } + + private boolean checkForWin(int player) { + // Check horizontal, vertical, and diagonal lines + return checkHorizontalWin(player) || checkVerticalWin(player) || checkDiagonalWin(player); + } + + private boolean checkHorizontalWin(int player) { + String playerSymbol = getPlayerSymbol(player); + for (int row = 0; row < rowLength; row++) { + for (int col = 0; col < columnHeight - 3; col++) { + if (board.getPosition(row, col).equals(playerSymbol) + && board.getPosition(row, col + 1).equals(playerSymbol) + && board.getPosition(row, col + 2).equals(playerSymbol) + && board.getPosition(row, col + 3).equals(playerSymbol)) { + return true; + } + } + } + return false; + } + + private boolean checkVerticalWin(int player) { + String playerSymbol = Integer.toString(player); + for (int col = 0; col < columnHeight; col++) { + for (int row = 0; row < rowLength - 3; row++) { + if (board.getPosition(row, col).equals(playerSymbol) + && board.getPosition(row + 1, col).equals(playerSymbol) + && board.getPosition(row + 2, col).equals(playerSymbol) + && board.getPosition(row + 3, col).equals(playerSymbol)) { + return true; + } + } + } + return false; + } + + private boolean checkDiagonalWin(int player) { + String playerSymbol = Integer.toString(player); + // Check diagonal (top-left to bottom-right) + for (int row = 0; row < rowLength - 3; row++) { + for (int col = 0; col < columnHeight - 3; col++) { + if (board.getPosition(row, col).equals(playerSymbol) + && board.getPosition(row + 1, col + 1).equals(playerSymbol) + && board.getPosition(row + 2, col + 2).equals(playerSymbol) + && board.getPosition(row + 3, col + 3).equals(playerSymbol)) { + return true; + } + } + } + + // Check diagonal (bottom-left to top-right) + for (int row = 3; row < rowLength; row++) { + for (int col = 0; col < columnHeight - 3; col++) { + if (board.getPosition(row, col).equals(playerSymbol) + && board.getPosition(row - 1, col + 1).equals(playerSymbol) + && board.getPosition(row - 2, col + 2).equals(playerSymbol) + && board.getPosition(row - 3, col + 3).equals(playerSymbol)) { + return true; + } + } + } + + return false; + } + + @Override + public int utility(int player) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'utility'"); + } + + @Override + public Board getBoard() { + return board; + } + + private String getPlayerSymbol(int player) { + return player == 1 ? "X" : "O"; + } + +} diff --git a/backend/src/test/java/board/master/model/games/connect_four/ConnectFourTest.java b/backend/src/test/java/board/master/model/games/connect_four/ConnectFourTest.java new file mode 100644 index 0000000..ba761e4 --- /dev/null +++ b/backend/src/test/java/board/master/model/games/connect_four/ConnectFourTest.java @@ -0,0 +1,182 @@ +package board.master.model.games.connect_four; + +import org.junit.jupiter.api.Test; + +import board.master.model.games.Board; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; + +public class ConnectFourTest { + + private ConnectFour connectFour; + private int columnHeight; + + @BeforeEach + void setUp() { + connectFour = new ConnectFour(); + columnHeight = 7; + } + + @Nested + class TestGetActions { + + @Test + void testGetAllActionsAtStart() { + int expected = 6; + int actual = connectFour.getActions().size(); + assertEquals(expected, actual); + } + + @Test + void testGetAllActionsAfterOneMove() { + int expected = 6; + connectFour.result(connectFour.getActions().get(0)); + int actual = connectFour.getActions().size(); + assertEquals(expected, actual); + } + + @Test + void testGetAllActionsAfterColumnIsFull() { + int expected = 5; + for (int i = 0; i < columnHeight; i++) { + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + } + int actual = connectFour.getActions().size(); + assertEquals(expected, actual); + } + + } + + @Nested + class testGetBoard { + + @Test + void testGetBoardAtStart() { + for (int x = 0; x < connectFour.getBoard().getRows(); x++) { + for (int y = 0; y < connectFour.getBoard().getColumns(); y++) { + String expected = ""; + String actual = connectFour.getBoard().getPosition(x, y); + assertEquals(expected, actual); + } + } + } + + @Test + void testGetBoardAfterOneMove() { + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + String laidPiece = connectFour.getBoard().getPosition(0, 0); + assertEquals("X", laidPiece); + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + laidPiece = connectFour.getBoard().getPosition(0, 1); + assertEquals("O", laidPiece); + } + + } + + + @Nested + class testIsTerminal { + + @Test + void testIsTerminalAtStart() { + boolean expected = false; + boolean actual = connectFour.isTerminal(); + assertEquals(expected, actual); + } + + @Test + void testIsTerminalAfterLessThenPossibleWinningCombination() { + boolean expected = false; + int numberOfMoves = 3; + for (int i = 0; i < numberOfMoves; i++) { + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(1)); + } + boolean actual = connectFour.isTerminal(); + assertEquals(expected, actual); + } + + @Test + void testIsTerminalAfterGameIsWon() { + boolean expected = true; + // Player 1 makes tower in column 0 and wins + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(1)); + + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(1)); + + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(1)); + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + boolean actual = connectFour.isTerminal(); + assertEquals(expected, actual); + + + } + + } + + + @Nested + class TestResult { + + @Test + void testResultDoesNotMutateOriginal() { + ConnectFour originalStateHandler = connectFour; + int originalToMove = connectFour.toMove(); + Board originalBoard = connectFour.getBoard(); + ConnectFour transformedStateHandler = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + assertEquals(originalBoard, originalStateHandler.getBoard()); + assertEquals(originalToMove, originalStateHandler.toMove()); + } + + @Test + void testResultsTransformationHasTransformed() { + ConnectFour originalStateHandler = connectFour; + ConnectFour transformedStateHandler = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + assertNotEquals(originalStateHandler, transformedStateHandler); + assertNotEquals(originalStateHandler.getBoard(), transformedStateHandler.getBoard()); + + } + + } + @Nested + class TestToMove { + + @Test + void testToMoveAtStart() { + int expected = 1; + int actual = connectFour.toMove(); + assertEquals(expected, actual); + } + + @Test + void testToMoveAfterOneMove() { + int expected = -1; + connectFour = (ConnectFour) connectFour.result(connectFour.getActions().get(0)); + int actual = connectFour.toMove(); + assertEquals(expected, actual); + } + + @Test + void testToMoveAfterTwoMoves() { + int expected = 1; + connectFour.result(connectFour.getActions().get(0)); + connectFour.result(connectFour.getActions().get(0)); + int actual = connectFour.toMove(); + assertEquals(expected, actual); + } + + } + + + @Test + void testUtility() { + + } +}