diff --git a/modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala b/modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala index e15979c..90bb91d 100644 --- a/modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala +++ b/modules/ui/src/main/scala/de/nowchess/ui/terminal/TerminalUI.scala @@ -48,7 +48,7 @@ class TerminalUI(engine: GameEngine) extends Observer: case _: PromotionRequiredEvent => println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight") - awaitingPromotion = true + synchronized { awaitingPromotion = true } /** Start the terminal UI game loop. */ def start(): Unit = @@ -63,24 +63,26 @@ class TerminalUI(engine: GameEngine) extends Observer: // Game loop while running do val input = Option(StdIn.readLine()).getOrElse("quit").trim - if awaitingPromotion then - input.toLowerCase match - case "q" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Queen) - case "r" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Rook) - case "b" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Bishop) - case "n" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Knight) - case _ => - println("Invalid choice. Enter q, r, b, or n.") - println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight") - else - input.toLowerCase match - case "quit" | "q" => - running = false - println("Game over. Goodbye!") - case "" => - printPrompt(engine.turn) - case _ => - engine.processUserInput(input) + synchronized { + if awaitingPromotion then + input.toLowerCase match + case "q" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Queen) + case "r" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Rook) + case "b" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Bishop) + case "n" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Knight) + case _ => + println("Invalid choice. Enter q, r, b, or n.") + println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight") + else + input.toLowerCase match + case "quit" | "q" => + running = false + println("Game over. Goodbye!") + case "" => + printPrompt(engine.turn) + case _ => + engine.processUserInput(input) + } // Unsubscribe when done engine.unsubscribe(this) 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 54903c9..514ad0d 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 @@ -264,4 +264,64 @@ class TerminalUITest extends AnyFunSuite with Matchers { capturedPiece should be(Some(PromotionPiece.Rook)) out.toString should include("Invalid") } + + test("TerminalUI routes Bishop promotion choice to engine.completePromotion") { + import de.nowchess.api.move.PromotionPiece + + var capturedPiece: Option[PromotionPiece] = None + + val engine = new GameEngine() { + override def processUserInput(rawInput: String): Unit = + if rawInput.trim == "e7e8" then + notifyObservers(PromotionRequiredEvent( + Board(Map.empty), GameHistory.empty, Color.White, + Square(File.E, Rank.R7), Square(File.E, Rank.R8) + )) + override def completePromotion(piece: PromotionPiece): Unit = + capturedPiece = Some(piece) + notifyObservers(MoveExecutedEvent(Board(Map.empty), GameHistory.empty, Color.Black, "e7", "e8", None)) + } + + val in = new ByteArrayInputStream("e7e8\nb\nquit\n".getBytes) + val out = new ByteArrayOutputStream() + val ui = new TerminalUI(engine) + + Console.withIn(in) { + Console.withOut(out) { + ui.start() + } + } + + capturedPiece should be(Some(PromotionPiece.Bishop)) + } + + test("TerminalUI routes Knight promotion choice to engine.completePromotion") { + import de.nowchess.api.move.PromotionPiece + + var capturedPiece: Option[PromotionPiece] = None + + val engine = new GameEngine() { + override def processUserInput(rawInput: String): Unit = + if rawInput.trim == "e7e8" then + notifyObservers(PromotionRequiredEvent( + Board(Map.empty), GameHistory.empty, Color.White, + Square(File.E, Rank.R7), Square(File.E, Rank.R8) + )) + override def completePromotion(piece: PromotionPiece): Unit = + capturedPiece = Some(piece) + notifyObservers(MoveExecutedEvent(Board(Map.empty), GameHistory.empty, Color.Black, "e7", "e8", None)) + } + + val in = new ByteArrayInputStream("e7e8\nn\nquit\n".getBytes) + val out = new ByteArrayOutputStream() + val ui = new TerminalUI(engine) + + Console.withIn(in) { + Console.withOut(out) { + ui.start() + } + } + + capturedPiece should be(Some(PromotionPiece.Knight)) + } }