From c20b71e302e37a728d882423a4c605ecc64bb12b Mon Sep 17 00:00:00 2001 From: LQ63 Date: Mon, 30 Mar 2026 12:58:34 +0200 Subject: [PATCH] fix: correct 50-move rule threshold to 100 half-moves (FIDE-compliant) The halfMoveClock counts plies (half-moves). The FIDE 50-move rule requires 50 moves by each side = 100 plies, not 50. Changed both the processMove and gameLoop checks from >= 50 to >= 100, and updated all tests accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .../chess/controller/GameController.scala | 4 ++-- .../chess/controller/GameControllerTest.scala | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala b/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala index 5cf8ca6..f16617a 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala @@ -36,7 +36,7 @@ object GameController: case "quit" | "q" => MoveResult.Quit case "draw" => - if halfMoveClock >= 50 then MoveResult.DrawClaimed + if halfMoveClock >= 100 then MoveResult.DrawClaimed else MoveResult.InvalidFormat("draw") case trimmed => Parser.parseMove(trimmed) match @@ -80,7 +80,7 @@ object GameController: println() print(Renderer.render(board)) val input = - if halfMoveClock >= 50 then + if halfMoveClock >= 100 then println(s"[50-move rule] ${turn.label} may claim a draw, or continue playing.") println(" 1. Claim draw") println(" 2. Continue") diff --git a/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala b/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala index df0cb87..4b38398 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala @@ -404,19 +404,19 @@ class GameControllerTest extends AnyFunSuite with Matchers: // ──── processMove: 50-move rule draw claim ─────────────────────────────── - test("processMove: 'draw' with halfMoveClock = 50 returns DrawClaimed"): + test("processMove: 'draw' with halfMoveClock = 100 returns DrawClaimed"): val b = Board(Map( sq(File.E, Rank.R1) -> Piece.WhiteKing, sq(File.E, Rank.R8) -> Piece.BlackKing )) - GameController.processMove(b, GameHistory.empty, Color.White, 50, "draw") shouldBe MoveResult.DrawClaimed + GameController.processMove(b, GameHistory.empty, Color.White, 100, "draw") shouldBe MoveResult.DrawClaimed - test("processMove: 'draw' with halfMoveClock = 49 returns InvalidFormat"): + test("processMove: 'draw' with halfMoveClock = 99 returns InvalidFormat"): val b = Board(Map( sq(File.E, Rank.R1) -> Piece.WhiteKing, sq(File.E, Rank.R8) -> Piece.BlackKing )) - GameController.processMove(b, GameHistory.empty, Color.White, 49, "draw") shouldBe MoveResult.InvalidFormat("draw") + GameController.processMove(b, GameHistory.empty, Color.White, 99, "draw") shouldBe MoveResult.InvalidFormat("draw") // ──── processMove: halfMoveClock update ────────────────────────────────── @@ -470,30 +470,30 @@ class GameControllerTest extends AnyFunSuite with Matchers: // ──── gameLoop: 50-move rule menu ──────────────────────────────────────── - test("gameLoop: shows 50-move rule menu when halfMoveClock >= 50 and draw claimed"): + test("gameLoop: shows 50-move rule menu when halfMoveClock >= 100 and draw claimed"): val b = Board(Map( sq(File.E, Rank.R1) -> Piece.WhiteKing, sq(File.E, Rank.R8) -> Piece.BlackKing )) val output = captureOutput: withInput("1\nquit\n"): - GameController.gameLoop(b, GameHistory.empty, Color.White, 50) + GameController.gameLoop(b, GameHistory.empty, Color.White, 100) output should include("50-move rule") output should include("Draw claimed by 50-move rule.") - test("gameLoop: shows 50-move rule menu when halfMoveClock >= 50 and player continues"): + test("gameLoop: shows 50-move rule menu when halfMoveClock >= 100 and player continues"): val b = Board(Map( sq(File.E, Rank.R1) -> Piece.WhiteKing, sq(File.E, Rank.R8) -> Piece.BlackKing )) val output = captureOutput: withInput("2\nquit\n"): - GameController.gameLoop(b, GameHistory.empty, Color.White, 50) + GameController.gameLoop(b, GameHistory.empty, Color.White, 100) output should include("50-move rule") output should include("White's turn") - test("gameLoop: no 50-move rule menu when halfMoveClock < 50"): + test("gameLoop: no 50-move rule menu when halfMoveClock < 100"): val output = captureOutput: withInput("quit\n"): - GameController.gameLoop(Board.initial, GameHistory.empty, Color.White, 49) + GameController.gameLoop(Board.initial, GameHistory.empty, Color.White, 99) output should not include "50-move rule"