feat: NCS-11 add halfMoveClock to GameHistory with addMove reset flags
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,21 +11,36 @@ case class HistoryMove(
|
|||||||
promotionPiece: Option[PromotionPiece] = None
|
promotionPiece: Option[PromotionPiece] = None
|
||||||
)
|
)
|
||||||
|
|
||||||
/** Complete game history: ordered list of moves. */
|
/** Complete game history: ordered list of moves plus the half-move clock for the 50-move rule.
|
||||||
case class GameHistory(moves: List[HistoryMove] = List.empty):
|
*
|
||||||
|
* @param moves moves played so far, oldest first
|
||||||
|
* @param halfMoveClock plies since the last pawn move or capture (FIDE 50-move rule counter)
|
||||||
|
*/
|
||||||
|
case class GameHistory(moves: List[HistoryMove] = List.empty, halfMoveClock: Int = 0):
|
||||||
|
|
||||||
|
/** Add a raw HistoryMove record. Clock increments by 1.
|
||||||
|
* Use the coordinate overload when you know whether the move is a pawn move or capture.
|
||||||
|
*/
|
||||||
def addMove(move: HistoryMove): GameHistory =
|
def addMove(move: HistoryMove): GameHistory =
|
||||||
GameHistory(moves :+ move)
|
GameHistory(moves :+ move, halfMoveClock + 1)
|
||||||
|
|
||||||
def addMove(from: Square, to: Square): GameHistory =
|
|
||||||
addMove(HistoryMove(from, to, None))
|
|
||||||
|
|
||||||
|
/** Add a move by coordinates.
|
||||||
|
*
|
||||||
|
* @param wasPawnMove true when the moving piece is a pawn — resets the clock to 0
|
||||||
|
* @param wasCapture true when a piece was captured (including en passant) — resets the clock to 0
|
||||||
|
*
|
||||||
|
* If neither flag is set the clock increments by 1.
|
||||||
|
*/
|
||||||
def addMove(
|
def addMove(
|
||||||
from: Square,
|
from: Square,
|
||||||
to: Square,
|
to: Square,
|
||||||
castleSide: Option[CastleSide] = None,
|
castleSide: Option[CastleSide] = None,
|
||||||
promotionPiece: Option[PromotionPiece] = None
|
promotionPiece: Option[PromotionPiece] = None,
|
||||||
|
wasPawnMove: Boolean = false,
|
||||||
|
wasCapture: Boolean = false
|
||||||
): GameHistory =
|
): GameHistory =
|
||||||
addMove(HistoryMove(from, to, castleSide, promotionPiece))
|
val newClock = if wasPawnMove || wasCapture then 0 else halfMoveClock + 1
|
||||||
|
GameHistory(moves :+ HistoryMove(from, to, castleSide, promotionPiece), newClock)
|
||||||
|
|
||||||
object GameHistory:
|
object GameHistory:
|
||||||
val empty: GameHistory = GameHistory()
|
val empty: GameHistory = GameHistory()
|
||||||
|
|||||||
@@ -69,3 +69,36 @@ class GameHistoryTest extends AnyFunSuite with Matchers:
|
|||||||
newHistory.moves should have length 1
|
newHistory.moves should have length 1
|
||||||
newHistory.moves.head.castleSide should be (None)
|
newHistory.moves.head.castleSide should be (None)
|
||||||
newHistory.moves.head.promotionPiece should be (Some(PromotionPiece.Queen))
|
newHistory.moves.head.promotionPiece should be (Some(PromotionPiece.Queen))
|
||||||
|
|
||||||
|
// ──── half-move clock ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
test("halfMoveClock starts at 0"):
|
||||||
|
GameHistory.empty.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock increments on a non-pawn non-capture move"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.G, Rank.R1), sq(File.F, Rank.R3))
|
||||||
|
h.halfMoveClock shouldBe 1
|
||||||
|
|
||||||
|
test("halfMoveClock resets to 0 on a pawn move"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.E, Rank.R2), sq(File.E, Rank.R4), wasPawnMove = true)
|
||||||
|
h.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock resets to 0 on a capture"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.E, Rank.R5), sq(File.D, Rank.R6), wasCapture = true)
|
||||||
|
h.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock resets to 0 when both wasPawnMove and wasCapture are true"):
|
||||||
|
val h = GameHistory.empty.addMove(sq(File.E, Rank.R5), sq(File.D, Rank.R6), wasPawnMove = true, wasCapture = true)
|
||||||
|
h.halfMoveClock shouldBe 0
|
||||||
|
|
||||||
|
test("halfMoveClock carries across multiple moves"):
|
||||||
|
val h = GameHistory.empty
|
||||||
|
.addMove(sq(File.G, Rank.R1), sq(File.F, Rank.R3)) // +1 → 1
|
||||||
|
.addMove(sq(File.G, Rank.R8), sq(File.F, Rank.R6)) // +1 → 2
|
||||||
|
.addMove(sq(File.E, Rank.R2), sq(File.E, Rank.R4), wasPawnMove = true) // reset → 0
|
||||||
|
.addMove(sq(File.B, Rank.R1), sq(File.C, Rank.R3)) // +1 → 1
|
||||||
|
h.halfMoveClock shouldBe 1
|
||||||
|
|
||||||
|
test("GameHistory can be initialised with a non-zero halfMoveClock"):
|
||||||
|
val h = GameHistory(halfMoveClock = 42)
|
||||||
|
h.halfMoveClock shouldBe 42
|
||||||
|
|||||||
Reference in New Issue
Block a user