fix: add thread synchronization to awaitingPromotion and complete coverage tests for all promotion pieces
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,7 @@ class TerminalUI(engine: GameEngine) extends Observer:
|
|||||||
|
|
||||||
case _: PromotionRequiredEvent =>
|
case _: PromotionRequiredEvent =>
|
||||||
println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight")
|
println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight")
|
||||||
awaitingPromotion = true
|
synchronized { awaitingPromotion = true }
|
||||||
|
|
||||||
/** Start the terminal UI game loop. */
|
/** Start the terminal UI game loop. */
|
||||||
def start(): Unit =
|
def start(): Unit =
|
||||||
@@ -63,24 +63,26 @@ class TerminalUI(engine: GameEngine) extends Observer:
|
|||||||
// Game loop
|
// Game loop
|
||||||
while running do
|
while running do
|
||||||
val input = Option(StdIn.readLine()).getOrElse("quit").trim
|
val input = Option(StdIn.readLine()).getOrElse("quit").trim
|
||||||
if awaitingPromotion then
|
synchronized {
|
||||||
input.toLowerCase match
|
if awaitingPromotion then
|
||||||
case "q" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Queen)
|
input.toLowerCase match
|
||||||
case "r" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Rook)
|
case "q" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Queen)
|
||||||
case "b" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Bishop)
|
case "r" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Rook)
|
||||||
case "n" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Knight)
|
case "b" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Bishop)
|
||||||
case _ =>
|
case "n" => awaitingPromotion = false; engine.completePromotion(PromotionPiece.Knight)
|
||||||
println("Invalid choice. Enter q, r, b, or n.")
|
case _ =>
|
||||||
println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight")
|
println("Invalid choice. Enter q, r, b, or n.")
|
||||||
else
|
println("Promote to: q=Queen, r=Rook, b=Bishop, n=Knight")
|
||||||
input.toLowerCase match
|
else
|
||||||
case "quit" | "q" =>
|
input.toLowerCase match
|
||||||
running = false
|
case "quit" | "q" =>
|
||||||
println("Game over. Goodbye!")
|
running = false
|
||||||
case "" =>
|
println("Game over. Goodbye!")
|
||||||
printPrompt(engine.turn)
|
case "" =>
|
||||||
case _ =>
|
printPrompt(engine.turn)
|
||||||
engine.processUserInput(input)
|
case _ =>
|
||||||
|
engine.processUserInput(input)
|
||||||
|
}
|
||||||
|
|
||||||
// Unsubscribe when done
|
// Unsubscribe when done
|
||||||
engine.unsubscribe(this)
|
engine.unsubscribe(this)
|
||||||
|
|||||||
@@ -264,4 +264,64 @@ class TerminalUITest extends AnyFunSuite with Matchers {
|
|||||||
capturedPiece should be(Some(PromotionPiece.Rook))
|
capturedPiece should be(Some(PromotionPiece.Rook))
|
||||||
out.toString should include("Invalid")
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user