diff --git a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala index ff42aad..59aafe4 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala @@ -147,12 +147,11 @@ class GameEngine extends Observable: val cmd = invoker.history(invoker.getCurrentIndex) (cmd: @unchecked) match case moveCmd: MoveCommand => - if moveCmd.undo() then - moveCmd.previousBoard.foreach(currentBoard = _) - moveCmd.previousHistory.foreach(currentHistory = _) - moveCmd.previousTurn.foreach(currentTurn = _) - invoker.undo() - notifyObservers(BoardResetEvent(currentBoard, currentHistory, currentTurn)) + moveCmd.previousBoard.foreach(currentBoard = _) + moveCmd.previousHistory.foreach(currentHistory = _) + moveCmd.previousTurn.foreach(currentTurn = _) + invoker.undo() + notifyObservers(BoardResetEvent(currentBoard, currentHistory, currentTurn)) else notifyObservers(InvalidMoveEvent(currentBoard, currentHistory, currentTurn, "Nothing to undo.")) @@ -161,14 +160,10 @@ class GameEngine extends Observable: val cmd = invoker.history(invoker.getCurrentIndex + 1) (cmd: @unchecked) match case moveCmd: MoveCommand => - if moveCmd.execute() then - moveCmd.moveResult.foreach { - case de.nowchess.chess.command.MoveResult.Successful(newBoard, newHistory, newTurn, captured) => - updateGameState(newBoard, newHistory, newTurn) - invoker.redo() - emitMoveEvent(moveCmd.from.toString, moveCmd.to.toString, captured, newTurn) - case _ => () - } + for case de.nowchess.chess.command.MoveResult.Successful(nb, nh, nt, cap) <- moveCmd.moveResult do + updateGameState(nb, nh, nt) + invoker.redo() + emitMoveEvent(moveCmd.from.toString, moveCmd.to.toString, cap, nt) else notifyObservers(InvalidMoveEvent(currentBoard, currentHistory, currentTurn, "Nothing to redo.")) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala index 446d35e..755ddb8 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala @@ -268,6 +268,39 @@ class GameEngineTest extends AnyFunSuite with Matchers: val initialEvents = observer.events.size engine.processUserInput("q") observer.events.size shouldBe initialEvents + + test("GameEngine undo notifies with BoardResetEvent after successful undo"): + val engine = new GameEngine() + engine.processUserInput("e2e4") + engine.processUserInput("e7e5") + val observer = new MockObserver() + engine.subscribe(observer) + observer.events.clear() + + engine.undo() + + // Should have received a BoardResetEvent on undo + observer.events.size should be > 0 + observer.events.exists(_.isInstanceOf[BoardResetEvent]) shouldBe true + + test("GameEngine redo notifies with MoveExecutedEvent after successful redo"): + val engine = new GameEngine() + engine.processUserInput("e2e4") + engine.processUserInput("e7e5") + val boardAfterSecondMove = engine.board + + engine.undo() + val observer = new MockObserver() + engine.subscribe(observer) + observer.events.clear() + + engine.redo() + + // Should have received a MoveExecutedEvent for the redo + observer.events.size shouldBe 1 + observer.events.head shouldBe a[MoveExecutedEvent] + engine.board shouldBe boardAfterSecondMove + engine.turn shouldBe Color.White // Mock Observer for testing private class MockObserver extends Observer: diff --git a/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala b/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala index a0b4dea..16ccba4 100644 --- a/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala +++ b/modules/ui/src/test/scala/de/nowchess/ui/terminal/TerminalUITest.scala @@ -166,4 +166,24 @@ class TerminalUITest extends AnyFunSuite with Matchers { out.toString should include("Captured: Knight(White) on A8") // Depending on how piece/coord serialize } + + test("TerminalUI processes valid move input via processUserInput") { + val in = new ByteArrayInputStream("e2e4\nq\n".getBytes) + val out = new ByteArrayOutputStream() + + val engine = new GameEngine() + val ui = new TerminalUI(engine) + + Console.withIn(in) { + Console.withOut(out) { + ui.start() + } + } + + val output = out.toString + output should include("White's turn.") + output should include("Game over. Goodbye!") + // The move should have been processed and the board displayed + engine.turn shouldBe Color.Black + } }