A chess library for Gleam

Check for threefold repetition

+64 -10
+6 -1
src/starfish.gleam
··· 190 190 191 191 /// Returns the current game state: A win, draw or neither. 192 192 pub fn state(game: Game) -> GameState { 193 - // TODO: Check for insufficient material and threefold repetition 193 + // TODO: Check for insufficient material 194 194 use <- bool.guard(game.half_moves >= 50, Draw(FiftyMoves)) 195 + // We count two-fold repetition 196 + use <- bool.guard( 197 + game.is_threefold_repetition(game), 198 + Draw(ThreefoldRepetition), 199 + ) 195 200 use <- bool.guard(move.any_legal(game), Continue) 196 201 use <- bool.guard(!game.attack_information.in_check, Draw(Stalemate)) 197 202 case game.to_move {
+28 -5
src/starfish/internal/game.gleam
··· 2 2 import gleam/int 3 3 import gleam/option.{type Option, None, Some} 4 4 import gleam/result 5 - import gleam/set.{type Set} 6 5 import gleam/string 7 6 import starfish/internal/board.{Black, White} 8 7 import starfish/internal/hash ··· 28 27 full_moves: Int, 29 28 // Extra information 30 29 zobrist_hash: Int, 31 - previous_positions: Set(Int), 30 + previous_positions: List(Int), 32 31 attack_information: attack.AttackInformation, 33 32 ) 34 33 } ··· 49 48 half_moves: 0, 50 49 full_moves: 1, 51 50 zobrist_hash:, 52 - previous_positions: set.new(), 51 + previous_positions: [], 53 52 attack_information:, 54 53 ) 55 54 } ··· 103 102 half_moves:, 104 103 full_moves:, 105 104 zobrist_hash:, 106 - previous_positions: set.new(), 105 + previous_positions: [], 107 106 attack_information:, 108 107 ) 109 108 } ··· 244 243 half_moves:, 245 244 full_moves:, 246 245 zobrist_hash:, 247 - previous_positions: set.new(), 246 + previous_positions: [], 248 247 attack_information:, 249 248 )) 250 249 } ··· 330 329 _ -> string 331 330 } 332 331 } 332 + 333 + pub fn is_threefold_repetition(game: Game) -> Bool { 334 + is_threefold_repetition_loop( 335 + game.previous_positions, 336 + game.zobrist_hash, 337 + False, 338 + ) 339 + } 340 + 341 + fn is_threefold_repetition_loop( 342 + positions: List(Int), 343 + position: Int, 344 + found: Bool, 345 + ) -> Bool { 346 + case positions { 347 + [] -> False 348 + [first, ..rest] if first == position -> 349 + case found { 350 + False -> is_threefold_repetition_loop(rest, position, True) 351 + True -> True 352 + } 353 + [_, ..rest] -> is_threefold_repetition_loop(rest, position, found) 354 + } 355 + }
+3 -4
src/starfish/internal/move.gleam
··· 3 3 import gleam/list 4 4 import gleam/option.{type Option, None, Some} 5 5 import gleam/result 6 - import gleam/set 7 6 import starfish/internal/board 8 7 import starfish/internal/game.{type Game, Game} 9 8 import starfish/internal/hash ··· 513 512 } 514 513 515 514 let half_moves = half_moves + 1 516 - let previous_positions = set.insert(previous_positions, zobrist_hash) 515 + let previous_positions = [zobrist_hash, ..previous_positions] 517 516 518 517 // TODO: Update incrementally 519 518 let zobrist_hash = hash.hash(board, to_move) ··· 597 596 } 598 597 599 598 let #(half_moves, previous_positions) = case one_way_move { 600 - True -> #(0, set.new()) 601 - False -> #(half_moves + 1, set.insert(previous_positions, zobrist_hash)) 599 + True -> #(0, []) 600 + False -> #(half_moves + 1, [zobrist_hash, ..previous_positions]) 602 601 } 603 602 604 603 // TODO: Update incrementally
+27
test/starfish_test.gleam
··· 274 274 let assert Error(Nil) = starfish.parse_move("Ndf3", starfish.new()) 275 275 } 276 276 277 + fn apply_move(game: starfish.Game, move: String) -> starfish.Game { 278 + let assert Ok(move) = starfish.parse_move(move, game) 279 + starfish.apply_move(game, move) 280 + } 281 + 277 282 pub fn state_test() { 283 + assert starfish.state(starfish.new()) == starfish.Continue 284 + 278 285 assert "8/8/8/1q6/1K6/3q4/8/8 w - - 0 1" 279 286 |> starfish.from_fen 280 287 |> starfish.state ··· 294 301 |> starfish.from_fen 295 302 |> starfish.state 296 303 == starfish.Draw(starfish.FiftyMoves) 304 + 305 + assert starfish.new() 306 + |> apply_move("Nf3") 307 + |> apply_move("Nc6") 308 + |> apply_move("Ng1") 309 + |> apply_move("Nb8") 310 + |> starfish.state 311 + == starfish.Continue 312 + 313 + assert starfish.new() 314 + |> apply_move("Nf3") 315 + |> apply_move("Nc6") 316 + |> apply_move("Ng1") 317 + |> apply_move("Nb8") 318 + |> apply_move("Nf3") 319 + |> apply_move("Nc6") 320 + |> apply_move("Ng1") 321 + |> apply_move("Nb8") 322 + |> starfish.state 323 + == starfish.Draw(starfish.ThreefoldRepetition) 297 324 } 298 325 299 326 fn perft_all(fen: String, expected: List(Int)) {