diff --git a/src/main/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersController.java b/src/main/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersController.java index 4ac7fb9..8b70c7a 100644 --- a/src/main/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersController.java +++ b/src/main/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersController.java @@ -124,6 +124,7 @@ public void handlePlayerMove() { // Use mediator to notify next player mediator.notify(this, "nextPlayer"); + boardGame.notifyObservers(); // Notify observers after every move } /** @@ -169,36 +170,30 @@ public MoveResult movePlayer(String playerName, int roll) { String type = "normal"; // Ensure we don't go past the board size - int boardSize = boardGame.getBoard().getSizeOfBoard(); - if (end >= boardSize) { - // In Snakes and Ladders, you must land exactly on the final tile - // If the roll would take you past it, you don't move - if (end == boardSize - 1) { - // Exact landing on final tile - allow the move - end = boardSize - 1; - } else { - // Overshoot - stay in current position - end = start; - return new MoveResult(start, end, type); - } + int lastTile = boardGame.getBoard().getSizeOfBoard(); + if (end == lastTile) { + return new MoveResult(start, end - 1, "win"); + } + if (end > lastTile) { + int overshoot = end - lastTile; + end = lastTile - overshoot; } - int steps = end - start; - if (steps > 0) player.move(steps); + if (end != start) { + player.setCurrentTile(boardGame.getBoard().getTile(end)); + } // Check for snakes if (tileConfig.isSnakeHead(end)) { int tail = tileConfig.getSnakeTail(end); - steps = tail - end; - player.move(steps); + player.setCurrentTile(boardGame.getBoard().getTile(tail)); end = tail; type = "snake"; } // Check for ladders only if it's a normal move else if (tileConfig.isLadderStart(end)) { int top = tileConfig.getLadderEnd(end); - steps = top - end; - player.move(steps); + player.setCurrentTile(boardGame.getBoard().getTile(top)); end = top; type = "ladder"; } diff --git a/src/main/java/edu/ntnu/iir/bidata/view/snakesandladders/SnakesAndLaddersGameUI.java b/src/main/java/edu/ntnu/iir/bidata/view/snakesandladders/SnakesAndLaddersGameUI.java index 66b65b6..4deed95 100644 --- a/src/main/java/edu/ntnu/iir/bidata/view/snakesandladders/SnakesAndLaddersGameUI.java +++ b/src/main/java/edu/ntnu/iir/bidata/view/snakesandladders/SnakesAndLaddersGameUI.java @@ -440,7 +440,7 @@ private void rollDiceAndMove() { updatePlayerPosition(currentPlayer); if (result.type.equals("snake")) { - String snakeMessage = currentPlayer + " hit a snake! Moving from " + result.start + " to " + result.end; + String snakeMessage = "🐍 " + currentPlayer + " hit a snake! Moving from " + result.start + " to " + result.end; LOGGER.info(snakeMessage); displaySnakeOrLadderMessage(currentPlayer, result.start, result.end, "snake"); // Add to history @@ -448,7 +448,7 @@ private void rollDiceAndMove() { // Update position again after snake updatePlayerPosition(currentPlayer); } else if (result.type.equals("ladder")) { - String ladderMessage = currentPlayer + " hit a ladder! Moving from " + result.start + " to " + result.end; + String ladderMessage = "🪜 " + currentPlayer + " hit a ladder! Moving from " + result.start + " to " + result.end; LOGGER.info(ladderMessage); displaySnakeOrLadderMessage(currentPlayer, result.start, result.end, "ladder"); // Add to history @@ -457,11 +457,11 @@ private void rollDiceAndMove() { updatePlayerPosition(currentPlayer); } else { // Normal move - String moveMessage = currentPlayer + " moved from " + result.start + " to " + result.end; + String moveMessage = "🎲 " + currentPlayer + " moved from " + result.start + " to " + result.end; addMessageToHistory(moveMessage); } - if (result.end == 100) { + if (result.type.equals("win")) { String winMessage = "🏆 " + currentPlayer + " WINS! 🏆"; statusLabel.setText(winMessage); currentTurnLabel.setText(winMessage); diff --git a/src/test/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersControllerTest.java b/src/test/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersControllerTest.java index c237bdf..b58d06e 100644 --- a/src/test/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersControllerTest.java +++ b/src/test/java/edu/ntnu/iir/bidata/controller/SnakesAndLaddersControllerTest.java @@ -121,10 +121,10 @@ void testHandlePlayerMove_NormalMove() { when(mockPlayer1.getCurrentPosition()).thenReturn(5); when(mockTileConfig.isSnakeHead(8)).thenReturn(false); when(mockTileConfig.isLadderStart(8)).thenReturn(false); + when(mockBoard.getTile(8)).thenReturn(mock(Tile.class)); + // Should not throw and should notify next player controller.handlePlayerMove(); - - verify(mockPlayer1).move(3); verify(mockMediator).notify(controller, "nextPlayer"); } @@ -136,11 +136,12 @@ void testHandlePlayerMove_PlayerWins() { when(mockPlayer1.getCurrentPosition()).thenReturn(97); when(mockTileConfig.isSnakeHead(100)).thenReturn(false); when(mockTileConfig.isLadderStart(100)).thenReturn(false); + when(mockBoard.getTile(100)).thenReturn(mock(Tile.class)); + // Call handlePlayerMove and verify mediator is NOT notified (since it's a win) controller.handlePlayerMove(); - - verify(mockPlayer1).move(3); verify(mockMediator, never()).notify(controller, "nextPlayer"); + // The win type is already tested in testMovePlayer_ExactlyOnFinalTile } @Test @@ -182,13 +183,15 @@ void testMovePlayer_NormalMove() { when(mockPlayer1.getCurrentPosition()).thenReturn(10); when(mockTileConfig.isSnakeHead(15)).thenReturn(false); when(mockTileConfig.isLadderStart(15)).thenReturn(false); + when(mockBoard.getTile(15)).thenReturn(mock(Tile.class)); SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 5); assertEquals(10, result.start); assertEquals(15, result.end); assertEquals("normal", result.type); - verify(mockPlayer1).move(5); + // No move() call, only setCurrentTile + verify(mockPlayer1, atLeastOnce()).setCurrentTile(any()); } @Test @@ -196,14 +199,15 @@ void testMovePlayer_HitSnake() { when(mockPlayer1.getCurrentPosition()).thenReturn(10); when(mockTileConfig.isSnakeHead(25)).thenReturn(true); when(mockTileConfig.getSnakeTail(25)).thenReturn(5); + when(mockBoard.getTile(25)).thenReturn(mock(Tile.class)); + when(mockBoard.getTile(5)).thenReturn(mock(Tile.class)); SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 15); assertEquals(10, result.start); assertEquals(5, result.end); assertEquals("snake", result.type); - verify(mockPlayer1).move(15); // Move to snake head - verify(mockPlayer1).move(-20); // Move down the snake + verify(mockPlayer1, atLeastOnce()).setCurrentTile(any()); } @Test @@ -212,42 +216,58 @@ void testMovePlayer_ClimbLadder() { when(mockTileConfig.isSnakeHead(20)).thenReturn(false); when(mockTileConfig.isLadderStart(20)).thenReturn(true); when(mockTileConfig.getLadderEnd(20)).thenReturn(35); + when(mockBoard.getTile(20)).thenReturn(mock(Tile.class)); + when(mockBoard.getTile(35)).thenReturn(mock(Tile.class)); SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 10); assertEquals(10, result.start); assertEquals(35, result.end); assertEquals("ladder", result.type); - verify(mockPlayer1).move(10); // Move to ladder start - verify(mockPlayer1).move(15); // Climb the ladder + verify(mockPlayer1, atLeastOnce()).setCurrentTile(any()); } @Test void testMovePlayer_ExactlyOnFinalTile() { when(mockPlayer1.getCurrentPosition()).thenReturn(97); - when(mockBoard.getSizeOfBoard()).thenReturn(101); // 0-100 + when(mockBoard.getSizeOfBoard()).thenReturn(100); // 1-100 when(mockTileConfig.isSnakeHead(100)).thenReturn(false); when(mockTileConfig.isLadderStart(100)).thenReturn(false); SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 3); assertEquals(97, result.start); - assertEquals(100, result.end); - assertEquals("normal", result.type); - verify(mockPlayer1).move(3); + assertEquals(99, result.end); // Should be lastTile - 1 for win + assertEquals("win", result.type); + verify(mockPlayer1, never()).move(anyInt()); } @Test void testMovePlayer_OvershootFinalTile() { when(mockPlayer1.getCurrentPosition()).thenReturn(97); when(mockBoard.getSizeOfBoard()).thenReturn(101); // 0-100 + when(mockBoard.getTile(100)).thenReturn(mock(Tile.class)); SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 5); assertEquals(97, result.start); - assertEquals(97, result.end); // Should stay in place when overshooting + assertEquals(100, result.end); // Should bounce back to 100 (101- (97+5-101) = 100) + assertEquals("normal", result.type); + verify(mockPlayer1, atLeastOnce()).setCurrentTile(any()); + } + + @Test + void testMovePlayer_BounceBackFromLastTile() { + when(mockPlayer1.getCurrentPosition()).thenReturn(99); + when(mockBoard.getSizeOfBoard()).thenReturn(101); // 0-100 + when(mockBoard.getTile(98)).thenReturn(mock(Tile.class)); + + SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 5); + + assertEquals(99, result.start); + assertEquals(98, result.end); // Should bounce back to 98 (99+5=104, bounce back to 98) assertEquals("normal", result.type); - verify(mockPlayer1, never()).move(anyInt()); // No movement should occur + verify(mockPlayer1, atLeastOnce()).setCurrentTile(any()); } @Test @@ -375,14 +395,15 @@ void testMovePlayer_ComplexScenario() { when(mockPlayer1.getCurrentPosition()).thenReturn(90); when(mockTileConfig.isSnakeHead(95)).thenReturn(true); when(mockTileConfig.getSnakeTail(95)).thenReturn(75); + when(mockBoard.getTile(95)).thenReturn(mock(Tile.class)); + when(mockBoard.getTile(75)).thenReturn(mock(Tile.class)); SnakesAndLaddersController.MoveResult result = controller.movePlayer("Player1", 5); assertEquals(90, result.start); assertEquals(75, result.end); assertEquals("snake", result.type); - verify(mockPlayer1).move(5); // Move to the snake head - verify(mockPlayer1).move(-20); // Move down the snake + verify(mockPlayer1, atLeastOnce()).setCurrentTile(any()); } @Test @@ -402,10 +423,9 @@ void testHandlePlayerMove_Integration() { when(mockPlayer1.getCurrentPosition()).thenReturn(10); when(mockTileConfig.isSnakeHead(14)).thenReturn(false); when(mockTileConfig.isLadderStart(14)).thenReturn(false); + when(mockBoard.getTile(14)).thenReturn(mock(Tile.class)); controller.handlePlayerMove(); - - verify(mockPlayer1).move(4); verify(mockMediator).notify(controller, "nextPlayer"); }