feat: add result tracking to GameContext and update GameEngine for game outcomes
Build & Test (NowChessSystems) TeamCity build failed
Build & Test (NowChessSystems) TeamCity build failed
This commit is contained in:
@@ -12,6 +12,7 @@ case class GameContext(
|
|||||||
enPassantSquare: Option[Square],
|
enPassantSquare: Option[Square],
|
||||||
halfMoveClock: Int,
|
halfMoveClock: Int,
|
||||||
moves: List[Move],
|
moves: List[Move],
|
||||||
|
result: Option[GameResult] = None,
|
||||||
):
|
):
|
||||||
/** Create new context with updated board. */
|
/** Create new context with updated board. */
|
||||||
def withBoard(newBoard: Board): GameContext = copy(board = newBoard)
|
def withBoard(newBoard: Board): GameContext = copy(board = newBoard)
|
||||||
@@ -31,6 +32,9 @@ case class GameContext(
|
|||||||
/** Create new context with move appended to history. */
|
/** Create new context with move appended to history. */
|
||||||
def withMove(move: Move): GameContext = copy(moves = moves :+ move)
|
def withMove(move: Move): GameContext = copy(moves = moves :+ move)
|
||||||
|
|
||||||
|
/** Create new context with updated result. */
|
||||||
|
def withResult(newResult: Option[GameResult]): GameContext = copy(result = newResult)
|
||||||
|
|
||||||
object GameContext:
|
object GameContext:
|
||||||
/** Initial position: white to move, all castling rights, no en passant. */
|
/** Initial position: white to move, all castling rights, no en passant. */
|
||||||
def initial: GameContext = GameContext(
|
def initial: GameContext = GameContext(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.nowchess.api.game
|
|||||||
|
|
||||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Rank, Square}
|
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Rank, Square}
|
||||||
import de.nowchess.api.move.Move
|
import de.nowchess.api.move.Move
|
||||||
|
import de.nowchess.api.game.{DrawReason, GameResult}
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ class GameContextTest extends AnyFunSuite with Matchers:
|
|||||||
initial.enPassantSquare shouldBe None
|
initial.enPassantSquare shouldBe None
|
||||||
initial.halfMoveClock shouldBe 0
|
initial.halfMoveClock shouldBe 0
|
||||||
initial.moves shouldBe List.empty
|
initial.moves shouldBe List.empty
|
||||||
|
initial.result shouldBe None
|
||||||
|
|
||||||
test("withBoard updates only board"):
|
test("withBoard updates only board"):
|
||||||
val square = Square(File.E, Rank.R4)
|
val square = Square(File.E, Rank.R4)
|
||||||
@@ -57,3 +59,15 @@ class GameContextTest extends AnyFunSuite with Matchers:
|
|||||||
test("withMove appends move to history"):
|
test("withMove appends move to history"):
|
||||||
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
val move = Move(Square(File.E, Rank.R2), Square(File.E, Rank.R4))
|
||||||
GameContext.initial.withMove(move).moves shouldBe List(move)
|
GameContext.initial.withMove(move).moves shouldBe List(move)
|
||||||
|
|
||||||
|
test("withResult sets Win result"):
|
||||||
|
val win = Some(GameResult.Win(Color.White))
|
||||||
|
GameContext.initial.withResult(win).result shouldBe win
|
||||||
|
|
||||||
|
test("withResult sets Draw result"):
|
||||||
|
val draw = Some(GameResult.Draw(DrawReason.Stalemate))
|
||||||
|
GameContext.initial.withResult(draw).result shouldBe draw
|
||||||
|
|
||||||
|
test("withResult clears result"):
|
||||||
|
val ctx = GameContext.initial.withResult(Some(GameResult.Win(Color.Black)))
|
||||||
|
ctx.withResult(None).result shouldBe None
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package de.nowchess.chess.engine
|
|||||||
|
|
||||||
import de.nowchess.api.board.{Board, Color, Piece, PieceType, Square}
|
import de.nowchess.api.board.{Board, Color, Piece, PieceType, Square}
|
||||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||||
import de.nowchess.api.game.{DrawReason, GameContext}
|
import de.nowchess.api.game.{DrawReason, GameContext, GameResult}
|
||||||
import de.nowchess.chess.controller.Parser
|
import de.nowchess.chess.controller.Parser
|
||||||
import de.nowchess.chess.observer.*
|
import de.nowchess.chess.observer.*
|
||||||
import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult}
|
import de.nowchess.chess.command.{CommandInvoker, MoveCommand, MoveResult}
|
||||||
@@ -60,6 +60,7 @@ class GameEngine(
|
|||||||
|
|
||||||
case "draw" =>
|
case "draw" =>
|
||||||
if currentContext.halfMoveClock >= 100 then
|
if currentContext.halfMoveClock >= 100 then
|
||||||
|
currentContext = currentContext.withResult(Some(GameResult.Draw(DrawReason.FiftyMoveRule)))
|
||||||
invoker.clear()
|
invoker.clear()
|
||||||
notifyObservers(DrawEvent(currentContext, DrawReason.FiftyMoveRule))
|
notifyObservers(DrawEvent(currentContext, DrawReason.FiftyMoveRule))
|
||||||
else
|
else
|
||||||
@@ -222,12 +223,15 @@ class GameEngine(
|
|||||||
|
|
||||||
if ruleSet.isCheckmate(currentContext) then
|
if ruleSet.isCheckmate(currentContext) then
|
||||||
val winner = currentContext.turn.opposite
|
val winner = currentContext.turn.opposite
|
||||||
|
currentContext = currentContext.withResult(Some(GameResult.Win(winner)))
|
||||||
notifyObservers(CheckmateEvent(currentContext, winner))
|
notifyObservers(CheckmateEvent(currentContext, winner))
|
||||||
invoker.clear()
|
invoker.clear()
|
||||||
else if ruleSet.isStalemate(currentContext) then
|
else if ruleSet.isStalemate(currentContext) then
|
||||||
|
currentContext = currentContext.withResult(Some(GameResult.Draw(DrawReason.Stalemate)))
|
||||||
notifyObservers(DrawEvent(currentContext, DrawReason.Stalemate))
|
notifyObservers(DrawEvent(currentContext, DrawReason.Stalemate))
|
||||||
invoker.clear()
|
invoker.clear()
|
||||||
else if ruleSet.isInsufficientMaterial(currentContext) then
|
else if ruleSet.isInsufficientMaterial(currentContext) then
|
||||||
|
currentContext = currentContext.withResult(Some(GameResult.Draw(DrawReason.InsufficientMaterial)))
|
||||||
notifyObservers(DrawEvent(currentContext, DrawReason.InsufficientMaterial))
|
notifyObservers(DrawEvent(currentContext, DrawReason.InsufficientMaterial))
|
||||||
invoker.clear()
|
invoker.clear()
|
||||||
else if ruleSet.isCheck(currentContext) then notifyObservers(CheckDetectedEvent(currentContext))
|
else if ruleSet.isCheck(currentContext) then notifyObservers(CheckDetectedEvent(currentContext))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package de.nowchess.chess.engine
|
package de.nowchess.chess.engine
|
||||||
|
|
||||||
import de.nowchess.api.board.Color
|
import de.nowchess.api.board.Color
|
||||||
import de.nowchess.api.game.DrawReason
|
import de.nowchess.api.game.{DrawReason, GameResult}
|
||||||
import de.nowchess.chess.observer.*
|
import de.nowchess.chess.observer.*
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
import org.scalatest.matchers.should.Matchers
|
import org.scalatest.matchers.should.Matchers
|
||||||
@@ -23,6 +23,7 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers:
|
|||||||
engine.processUserInput("d8h4")
|
engine.processUserInput("d8h4")
|
||||||
|
|
||||||
observer.hasEvent[CheckmateEvent] shouldBe true
|
observer.hasEvent[CheckmateEvent] shouldBe true
|
||||||
|
engine.context.result shouldBe Some(GameResult.Win(Color.Black))
|
||||||
|
|
||||||
test("checkmate with white winner"):
|
test("checkmate with white winner"):
|
||||||
val engine = EngineTestHelpers.makeEngine()
|
val engine = EngineTestHelpers.makeEngine()
|
||||||
@@ -42,6 +43,7 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers:
|
|||||||
val evt = observer.getEvent[CheckmateEvent]
|
val evt = observer.getEvent[CheckmateEvent]
|
||||||
evt.isDefined shouldBe true
|
evt.isDefined shouldBe true
|
||||||
evt.get.winner shouldBe Color.White
|
evt.get.winner shouldBe Color.White
|
||||||
|
engine.context.result shouldBe Some(GameResult.Win(Color.White))
|
||||||
|
|
||||||
// ── Stalemate ───────────────────────────────────────────────────
|
// ── Stalemate ───────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers:
|
|||||||
val evt = observer.getEvent[DrawEvent]
|
val evt = observer.getEvent[DrawEvent]
|
||||||
evt.isDefined shouldBe true
|
evt.isDefined shouldBe true
|
||||||
evt.get.reason shouldBe DrawReason.Stalemate
|
evt.get.reason shouldBe DrawReason.Stalemate
|
||||||
|
engine.context.result shouldBe Some(GameResult.Draw(DrawReason.Stalemate))
|
||||||
|
|
||||||
test("stalemate board is not reset after draw"):
|
test("stalemate board is not reset after draw"):
|
||||||
val engine = EngineTestHelpers.makeEngine()
|
val engine = EngineTestHelpers.makeEngine()
|
||||||
@@ -189,6 +192,7 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers:
|
|||||||
val evt = observer.getEvent[DrawEvent]
|
val evt = observer.getEvent[DrawEvent]
|
||||||
evt.isDefined shouldBe true
|
evt.isDefined shouldBe true
|
||||||
evt.get.reason shouldBe DrawReason.FiftyMoveRule
|
evt.get.reason shouldBe DrawReason.FiftyMoveRule
|
||||||
|
engine.context.result shouldBe Some(GameResult.Draw(DrawReason.FiftyMoveRule))
|
||||||
|
|
||||||
test("draw cannot be claimed when not available"):
|
test("draw cannot be claimed when not available"):
|
||||||
val engine = EngineTestHelpers.makeEngine()
|
val engine = EngineTestHelpers.makeEngine()
|
||||||
@@ -216,3 +220,4 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers:
|
|||||||
val evt = observer.getEvent[DrawEvent]
|
val evt = observer.getEvent[DrawEvent]
|
||||||
evt.isDefined shouldBe true
|
evt.isDefined shouldBe true
|
||||||
evt.get.reason shouldBe DrawReason.InsufficientMaterial
|
evt.get.reason shouldBe DrawReason.InsufficientMaterial
|
||||||
|
engine.context.result shouldBe Some(GameResult.Draw(DrawReason.InsufficientMaterial))
|
||||||
|
|||||||
Reference in New Issue
Block a user