A chess library for Gleam

Use different piece-square tables for endgame from middlegame

+360 -57
+134 -7
src/starfish/internal/board.gleam
··· 34 34 King 35 35 } 36 36 37 + pub const pawn_value = 100 38 + 39 + pub const knight_value = 300 40 + 41 + pub const bishop_value = 300 42 + 43 + pub const rook_value = 500 44 + 45 + pub const queen_value = 900 46 + 47 + pub const king_value = 1000 48 + 37 49 pub fn piece_value(piece: Piece) -> Int { 38 50 case piece { 39 - Pawn -> 100 40 - Knight -> 300 41 - Bishop -> 300 42 - Rook -> 500 43 - Queen -> 900 44 - King -> 1000 51 + Pawn -> pawn_value 52 + Knight -> knight_value 53 + Bishop -> bishop_value 54 + Rook -> rook_value 55 + Queen -> queen_value 56 + King -> king_value 45 57 } 46 58 } 47 59 ··· 118 130 board_is_complete: Bool, 119 131 white_king_position: option.Option(Int), 120 132 black_king_position: option.Option(Int), 133 + white_pawn_material: Int, 134 + black_pawn_material: Int, 135 + white_non_pawn_material: Int, 136 + black_non_pawn_material: Int, 121 137 ) 122 138 } 123 139 124 140 pub fn from_fen(fen: String) -> FenParseResult { 125 141 // FEN starts from black's size, which means that `rank` needs to start at the 126 142 // end of the board. 127 - from_fen_loop(fen, 0, side_length - 1, dict.new(), option.None, option.None) 143 + from_fen_loop( 144 + fen, 145 + 0, 146 + side_length - 1, 147 + dict.new(), 148 + option.None, 149 + option.None, 150 + 0, 151 + 0, 152 + 0, 153 + 0, 154 + ) 128 155 } 129 156 130 157 fn from_fen_loop( ··· 134 161 board: Board, 135 162 white_king_position: option.Option(Int), 136 163 black_king_position: option.Option(Int), 164 + white_pawn_material: Int, 165 + black_pawn_material: Int, 166 + white_non_pawn_material: Int, 167 + black_non_pawn_material: Int, 137 168 ) -> FenParseResult { 138 169 let position = position(file:, rank:) 139 170 ··· 148 179 board, 149 180 white_king_position, 150 181 black_king_position, 182 + white_pawn_material, 183 + black_pawn_material, 184 + white_non_pawn_material, 185 + black_non_pawn_material, 151 186 ) 152 187 "0" <> fen -> 153 188 from_fen_loop( ··· 157 192 board, 158 193 white_king_position, 159 194 black_king_position, 195 + white_pawn_material, 196 + black_pawn_material, 197 + white_non_pawn_material, 198 + black_non_pawn_material, 160 199 ) 161 200 "1" <> fen -> 162 201 from_fen_loop( ··· 166 205 board, 167 206 white_king_position, 168 207 black_king_position, 208 + white_pawn_material, 209 + black_pawn_material, 210 + white_non_pawn_material, 211 + black_non_pawn_material, 169 212 ) 170 213 "2" <> fen -> 171 214 from_fen_loop( ··· 175 218 board, 176 219 white_king_position, 177 220 black_king_position, 221 + white_pawn_material, 222 + black_pawn_material, 223 + white_non_pawn_material, 224 + black_non_pawn_material, 178 225 ) 179 226 "3" <> fen -> 180 227 from_fen_loop( ··· 184 231 board, 185 232 white_king_position, 186 233 black_king_position, 234 + white_pawn_material, 235 + black_pawn_material, 236 + white_non_pawn_material, 237 + black_non_pawn_material, 187 238 ) 188 239 "4" <> fen -> 189 240 from_fen_loop( ··· 193 244 board, 194 245 white_king_position, 195 246 black_king_position, 247 + white_pawn_material, 248 + black_pawn_material, 249 + white_non_pawn_material, 250 + black_non_pawn_material, 196 251 ) 197 252 "5" <> fen -> 198 253 from_fen_loop( ··· 202 257 board, 203 258 white_king_position, 204 259 black_king_position, 260 + white_pawn_material, 261 + black_pawn_material, 262 + white_non_pawn_material, 263 + black_non_pawn_material, 205 264 ) 206 265 "6" <> fen -> 207 266 from_fen_loop( ··· 211 270 board, 212 271 white_king_position, 213 272 black_king_position, 273 + white_pawn_material, 274 + black_pawn_material, 275 + white_non_pawn_material, 276 + black_non_pawn_material, 214 277 ) 215 278 "7" <> fen -> 216 279 from_fen_loop( ··· 220 283 board, 221 284 white_king_position, 222 285 black_king_position, 286 + white_pawn_material, 287 + black_pawn_material, 288 + white_non_pawn_material, 289 + black_non_pawn_material, 223 290 ) 224 291 "8" <> fen -> 225 292 from_fen_loop( ··· 229 296 board, 230 297 white_king_position, 231 298 black_king_position, 299 + white_pawn_material, 300 + black_pawn_material, 301 + white_non_pawn_material, 302 + black_non_pawn_material, 232 303 ) 233 304 "9" <> fen -> 234 305 from_fen_loop( ··· 238 309 board, 239 310 white_king_position, 240 311 black_king_position, 312 + white_pawn_material, 313 + black_pawn_material, 314 + white_non_pawn_material, 315 + black_non_pawn_material, 241 316 ) 242 317 "K" <> fen -> 243 318 from_fen_loop( ··· 247 322 dict.insert(board, position, #(King, White)), 248 323 option.Some(position), 249 324 black_king_position, 325 + white_pawn_material, 326 + black_pawn_material, 327 + white_non_pawn_material, 328 + black_non_pawn_material, 250 329 ) 251 330 "Q" <> fen -> 252 331 from_fen_loop( ··· 256 335 dict.insert(board, position, #(Queen, White)), 257 336 white_king_position, 258 337 black_king_position, 338 + white_pawn_material, 339 + black_pawn_material, 340 + white_non_pawn_material + queen_value, 341 + black_non_pawn_material, 259 342 ) 260 343 "B" <> fen -> 261 344 from_fen_loop( ··· 265 348 dict.insert(board, position, #(Bishop, White)), 266 349 white_king_position, 267 350 black_king_position, 351 + white_pawn_material, 352 + black_pawn_material, 353 + white_non_pawn_material + bishop_value, 354 + black_non_pawn_material, 268 355 ) 269 356 "N" <> fen -> 270 357 from_fen_loop( ··· 274 361 dict.insert(board, position, #(Knight, White)), 275 362 white_king_position, 276 363 black_king_position, 364 + white_pawn_material, 365 + black_pawn_material, 366 + white_non_pawn_material + knight_value, 367 + black_non_pawn_material, 277 368 ) 278 369 "R" <> fen -> 279 370 from_fen_loop( ··· 283 374 dict.insert(board, position, #(Rook, White)), 284 375 white_king_position, 285 376 black_king_position, 377 + white_pawn_material, 378 + black_pawn_material, 379 + white_non_pawn_material + rook_value, 380 + black_non_pawn_material, 286 381 ) 287 382 "P" <> fen -> 288 383 from_fen_loop( ··· 292 387 dict.insert(board, position, #(Pawn, White)), 293 388 white_king_position, 294 389 black_king_position, 390 + white_pawn_material + pawn_value, 391 + black_pawn_material, 392 + white_non_pawn_material, 393 + black_non_pawn_material, 295 394 ) 296 395 "k" <> fen -> 297 396 from_fen_loop( ··· 301 400 dict.insert(board, position, #(King, Black)), 302 401 white_king_position, 303 402 option.Some(position), 403 + white_pawn_material, 404 + black_pawn_material, 405 + white_non_pawn_material, 406 + black_non_pawn_material, 304 407 ) 305 408 "q" <> fen -> 306 409 from_fen_loop( ··· 310 413 dict.insert(board, position, #(Queen, Black)), 311 414 white_king_position, 312 415 black_king_position, 416 + white_pawn_material, 417 + black_pawn_material, 418 + white_non_pawn_material, 419 + black_non_pawn_material + queen_value, 313 420 ) 314 421 "b" <> fen -> 315 422 from_fen_loop( ··· 319 426 dict.insert(board, position, #(Bishop, Black)), 320 427 white_king_position, 321 428 black_king_position, 429 + white_pawn_material, 430 + black_pawn_material, 431 + white_non_pawn_material, 432 + black_non_pawn_material + bishop_value, 322 433 ) 323 434 "n" <> fen -> 324 435 from_fen_loop( ··· 328 439 dict.insert(board, position, #(Knight, Black)), 329 440 white_king_position, 330 441 black_king_position, 442 + white_pawn_material, 443 + black_pawn_material, 444 + white_non_pawn_material, 445 + black_non_pawn_material + knight_value, 331 446 ) 332 447 "r" <> fen -> 333 448 from_fen_loop( ··· 337 452 dict.insert(board, position, #(Rook, Black)), 338 453 white_king_position, 339 454 black_king_position, 455 + white_pawn_material, 456 + black_pawn_material, 457 + white_non_pawn_material, 458 + black_non_pawn_material + rook_value, 340 459 ) 341 460 "p" <> fen -> 342 461 from_fen_loop( ··· 346 465 dict.insert(board, position, #(Pawn, Black)), 347 466 white_king_position, 348 467 black_king_position, 468 + white_pawn_material, 469 + black_pawn_material + pawn_value, 470 + white_non_pawn_material, 471 + black_non_pawn_material, 349 472 ) 350 473 // Since we iterate the rank in reverse order, but we iterate the file in 351 474 // ascending order, the final position should equal to `side_length` ··· 356 479 board_is_complete: position == side_length, 357 480 white_king_position:, 358 481 black_king_position:, 482 + white_pawn_material:, 483 + black_pawn_material:, 484 + white_non_pawn_material:, 485 + black_non_pawn_material:, 359 486 ) 360 487 } 361 488 }
+31 -3
src/starfish/internal/evaluate.gleam
··· 5 5 import starfish/internal/move 6 6 import starfish/internal/piece_table 7 7 8 + const phase_multiplier = 128 9 + 10 + /// About queen + rook, so one major piece per side 11 + const endgame_material = 1400 12 + 13 + /// Below this material limit, the endgame weight is zero. this is about enough 14 + /// for three minor pieces to be captured. 15 + const middlegame_material = 3000 16 + 17 + pub fn phase(game: game.Game) -> Int { 18 + let non_pawn_material = 19 + game.black_pieces.non_pawn_material + game.white_pieces.non_pawn_material 20 + let clamped_material = case non_pawn_material > middlegame_material { 21 + True -> middlegame_material 22 + False -> 23 + case non_pawn_material < endgame_material { 24 + True -> endgame_material 25 + False -> non_pawn_material 26 + } 27 + } 28 + 29 + { middlegame_material - clamped_material } 30 + * phase_multiplier 31 + / { middlegame_material - endgame_material } 32 + } 33 + 8 34 /// Statically evaluates a position. Does not take into account checkmate or 9 35 /// stalemate, those must be accounted for beforehand. 10 36 pub fn evaluate(game: game.Game, legal_moves: List(#(move.Move, Int))) -> Int { 11 - evaluate_position(game) + list.length(legal_moves) 37 + let phase = phase(game) 38 + evaluate_position(game, phase) + list.length(legal_moves) 12 39 } 13 40 14 - fn evaluate_position(game: game.Game) -> Int { 41 + fn evaluate_position(game: game.Game, phase: Int) -> Int { 15 42 use eval, position, #(piece, colour) <- dict.fold(game.board, 0) 16 43 let score = 17 - board.piece_value(piece) + piece_table.piece_score(piece, colour, position) 44 + board.piece_value(piece) 45 + + piece_table.piece_score(piece, colour, position, phase) 18 46 case colour == game.to_move { 19 47 True -> eval + score 20 48 False -> eval - score
+56 -8
src/starfish/internal/game.gleam
··· 29 29 zobrist_hash: Int, 30 30 previous_positions: List(Int), 31 31 attack_information: attack.AttackInformation, 32 - white_king_position: Int, 33 - black_king_position: Int, 32 + black_pieces: PieceInfo, 33 + white_pieces: PieceInfo, 34 34 ) 35 + } 36 + 37 + pub type PieceInfo { 38 + PieceInfo(king_position: Int, non_pawn_material: Int, pawn_material: Int) 35 39 } 36 40 37 41 const all_castling = Castling(True, True, True, True) ··· 44 48 let white_king_position = 4 45 49 let black_king_position = 60 46 50 51 + let non_pawn_material = 52 + board.queen_value 53 + + board.rook_value 54 + * 2 55 + + board.bishop_value 56 + * 2 57 + + board.knight_value 58 + * 2 59 + 60 + let pawn_material = board.pawn_value * 8 61 + 47 62 let attack_information = attack.calculate(board, white_king_position, to_move) 63 + 48 64 Game( 49 65 board:, 50 66 to_move:, ··· 55 71 zobrist_hash:, 56 72 previous_positions: [], 57 73 attack_information:, 58 - white_king_position:, 59 - black_king_position:, 74 + white_pieces: PieceInfo( 75 + king_position: white_king_position, 76 + non_pawn_material:, 77 + pawn_material:, 78 + ), 79 + black_pieces: PieceInfo( 80 + king_position: black_king_position, 81 + non_pawn_material:, 82 + pawn_material:, 83 + ), 60 84 ) 61 85 } 62 86 ··· 69 93 board_is_complete: _, 70 94 white_king_position:, 71 95 black_king_position:, 96 + white_pawn_material:, 97 + black_pawn_material:, 98 + white_non_pawn_material:, 99 + black_non_pawn_material:, 72 100 ) = board.from_fen(fen) 73 101 let fen = strip_spaces(fen) 74 102 ··· 125 153 zobrist_hash:, 126 154 previous_positions: [], 127 155 attack_information:, 128 - white_king_position:, 129 - black_king_position:, 156 + white_pieces: PieceInfo( 157 + king_position: white_king_position, 158 + non_pawn_material: white_non_pawn_material, 159 + pawn_material: white_pawn_material, 160 + ), 161 + black_pieces: PieceInfo( 162 + king_position: black_king_position, 163 + non_pawn_material: black_non_pawn_material, 164 + pawn_material: black_pawn_material, 165 + ), 130 166 ) 131 167 } 132 168 ··· 226 262 board_is_complete:, 227 263 white_king_position:, 228 264 black_king_position:, 265 + white_pawn_material:, 266 + black_pawn_material:, 267 + white_non_pawn_material:, 268 + black_non_pawn_material:, 229 269 ) = board.from_fen(fen) 230 270 use <- bool.guard(!board_is_complete, Error(PiecePositionsIncomplete)) 231 271 use white_king_position <- result.try(option.to_result( ··· 291 331 zobrist_hash:, 292 332 previous_positions: [], 293 333 attack_information:, 294 - white_king_position:, 295 - black_king_position:, 334 + white_pieces: PieceInfo( 335 + king_position: white_king_position, 336 + non_pawn_material: white_non_pawn_material, 337 + pawn_material: white_pawn_material, 338 + ), 339 + black_pieces: PieceInfo( 340 + king_position: black_king_position, 341 + non_pawn_material: black_non_pawn_material, 342 + pawn_material: black_pawn_material, 343 + ), 296 344 )) 297 345 } 298 346
+91 -30
src/starfish/internal/move.gleam
··· 470 470 zobrist_hash: previous_hash, 471 471 previous_positions:, 472 472 attack_information: _, 473 - white_king_position:, 474 - black_king_position:, 473 + white_pieces: game.PieceInfo( 474 + king_position: white_king_position, 475 + non_pawn_material: white_non_pawn_material, 476 + pawn_material: white_pawn_material, 477 + ), 478 + black_pieces: game.PieceInfo( 479 + king_position: black_king_position, 480 + non_pawn_material: black_non_pawn_material, 481 + pawn_material: black_pawn_material, 482 + ), 475 483 ) = game 476 484 477 485 let castling = case to_move { ··· 547 555 zobrist_hash:, 548 556 previous_positions:, 549 557 attack_information:, 550 - white_king_position:, 551 - black_king_position:, 558 + white_pieces: game.PieceInfo( 559 + king_position: white_king_position, 560 + non_pawn_material: white_non_pawn_material, 561 + pawn_material: white_pawn_material, 562 + ), 563 + black_pieces: game.PieceInfo( 564 + king_position: black_king_position, 565 + non_pawn_material: black_non_pawn_material, 566 + pawn_material: black_pawn_material, 567 + ), 552 568 ) 553 569 } 554 570 ··· 570 586 zobrist_hash: previous_hash, 571 587 previous_positions:, 572 588 attack_information: _, 573 - white_king_position:, 574 - black_king_position:, 589 + white_pieces:, 590 + black_pieces:, 575 591 ) = game 576 592 593 + let #( 594 + game.PieceInfo( 595 + king_position: our_king_position, 596 + non_pawn_material: our_non_pawn_material, 597 + pawn_material: our_pawn_material, 598 + ), 599 + game.PieceInfo( 600 + king_position: opposing_king_position, 601 + non_pawn_material: opposing_non_pawn_material, 602 + pawn_material: opposing_pawn_material, 603 + ), 604 + ) = case to_move { 605 + board.Black -> #(black_pieces, white_pieces) 606 + board.White -> #(white_pieces, black_pieces) 607 + } 608 + 577 609 let assert board.Occupied(piece:, colour:) = board.get(board, from) 578 610 as "Tried to apply move from invalid position" 579 611 ··· 589 621 |> hash.toggle_to_move 590 622 |> hash.toggle_piece(from, piece, colour) 591 623 592 - let piece = case promotion { 593 - None -> piece 594 - Some(piece) -> piece 624 + let #(piece, our_pawn_material, our_non_pawn_material) = case promotion { 625 + None -> #(piece, our_pawn_material, our_non_pawn_material) 626 + Some(piece) -> #( 627 + piece, 628 + our_pawn_material - board.pawn_value, 629 + our_non_pawn_material + board.piece_value(piece), 630 + ) 595 631 } 596 632 597 633 let zobrist_hash = hash.toggle_piece(zobrist_hash, to, piece, colour) 598 634 599 - let zobrist_hash = case board.get(board, to) { 600 - board.Occupied(piece:, colour:) -> 601 - hash.toggle_piece(zobrist_hash, to, piece, colour) 602 - board.Empty | board.OffBoard -> zobrist_hash 635 + let #(zobrist_hash, opposing_pawn_material, opposing_non_pawn_material) = case 636 + board.get(board, to) 637 + { 638 + board.Occupied(piece: board.Pawn, colour:) -> #( 639 + hash.toggle_piece(zobrist_hash, to, board.Pawn, colour), 640 + opposing_pawn_material - board.pawn_value, 641 + opposing_non_pawn_material, 642 + ) 643 + board.Occupied(piece:, colour:) -> #( 644 + hash.toggle_piece(zobrist_hash, to, piece, colour), 645 + opposing_pawn_material, 646 + opposing_non_pawn_material - board.piece_value(piece), 647 + ) 648 + board.Empty | board.OffBoard -> #( 649 + zobrist_hash, 650 + opposing_pawn_material, 651 + opposing_non_pawn_material, 652 + ) 603 653 } 604 654 605 655 let board = ··· 636 686 board.White -> full_moves 637 687 } 638 688 689 + let our_king_position = case from == our_king_position { 690 + True -> to 691 + False -> our_king_position 692 + } 693 + 694 + let our_pieces = 695 + game.PieceInfo( 696 + king_position: our_king_position, 697 + non_pawn_material: our_non_pawn_material, 698 + pawn_material: our_pawn_material, 699 + ) 700 + 701 + let opposing_pieces = 702 + game.PieceInfo( 703 + king_position: opposing_king_position, 704 + non_pawn_material: opposing_non_pawn_material, 705 + pawn_material: opposing_pawn_material, 706 + ) 707 + 708 + let #(white_pieces, black_pieces) = case to_move { 709 + board.White -> #(our_pieces, opposing_pieces) 710 + board.Black -> #(opposing_pieces, our_pieces) 711 + } 712 + 639 713 let to_move = case to_move { 640 714 board.Black -> board.White 641 715 board.White -> board.Black ··· 646 720 False -> #(half_moves + 1, [previous_hash, ..previous_positions]) 647 721 } 648 722 649 - let white_king_position = case from == white_king_position { 650 - True -> to 651 - False -> white_king_position 652 - } 653 - 654 - let black_king_position = case from == black_king_position { 655 - True -> to 656 - False -> black_king_position 657 - } 658 - 659 - let king_position = case to_move { 660 - board.Black -> black_king_position 661 - board.White -> white_king_position 662 - } 663 723 // TODO: Maybe we can update this incrementally too? 664 - let attack_information = attack.calculate(board, king_position, to_move) 724 + let attack_information = 725 + attack.calculate(board, opposing_king_position, to_move) 665 726 666 727 Game( 667 728 board:, ··· 673 734 zobrist_hash:, 674 735 previous_positions:, 675 736 attack_information:, 676 - white_king_position:, 677 - black_king_position:, 737 + white_pieces:, 738 + black_pieces:, 678 739 ) 679 740 } 680 741
+31 -3
src/starfish/internal/piece_table.gleam
··· 77 77 #(20, 30, 10, 0, 0, 10, 30, 20), 78 78 ) 79 79 80 - /// In the beginning and middle of the game, the king must be kept safe, however 80 + /// In the beginning and middle of the game, the king must be kept safe. However 81 81 /// as the game progresses towards the end, the king should become more aggressive 82 82 /// so we use a different set of scores for kings in the endgame. 83 83 const king_endgame = #( ··· 91 91 #(-50, -30, -30, -30, -30, -30, -30, -50), 92 92 ) 93 93 94 + /// In the middlegame, pawns are encouraged to protect the king's castling 95 + /// squares. In the endgame though, they no longer need to protect the king and 96 + /// instead should promote. Therefore, we use a different table to encourage this. 97 + const pawn_endgame = #( 98 + #(100, 100, 100, 100, 100, 100, 100, 100), 99 + #(80, 80, 80, 80, 80, 80, 80, 80), 100 + #(50, 50, 50, 50, 50, 50, 50, 50), 101 + #(30, 30, 30, 30, 30, 30, 30, 30), 102 + #(10, 10, 10, 10, 10, 10, 10, 10), 103 + // Since pawns can double-move, the first two ranks are equivalent from the 104 + // pawn's perspective. 105 + #(-10, -10, -10, -10, -10, -10, -10, -10), 106 + #(-10, -10, -10, -10, -10, -10, -10, -10), 107 + #(-10, -10, -10, -10, -10, -10, -10, -10), 108 + ) 109 + 94 110 type Table = 95 111 #( 96 112 #(Int, Int, Int, Int, Int, Int, Int, Int), ··· 140 156 piece: board.Piece, 141 157 colour: board.Colour, 142 158 position: Int, 159 + phase: Int, 143 160 ) -> Int { 144 161 let table = case piece { 145 162 board.Pawn -> pawn ··· 150 167 board.King -> king 151 168 } 152 169 153 - // TODO: take into account endgame for kings 154 - get(table, position, colour) 170 + let middlegame_value = get(table, position, colour) 171 + 172 + case piece { 173 + board.King if phase > 0 -> 174 + interpolate(middlegame_value, get(king_endgame, position, colour), phase) 175 + board.Pawn if phase > 0 -> 176 + interpolate(middlegame_value, get(pawn_endgame, position, colour), phase) 177 + _ -> middlegame_value 178 + } 179 + } 180 + 181 + fn interpolate(middlegame_value: Int, endgame_value: Int, phase: Int) -> Int { 182 + { middlegame_value * { 128 - phase } + endgame_value * phase } / 128 155 183 }
+11 -6
src/starfish/internal/search.gleam
··· 345 345 /// in order to save iterating the list a second time. The guesses are discarded 346 346 /// after this point. 347 347 fn order_moves(game: Game) -> List(#(Move, Int)) { 348 + let phase = evaluate.phase(game) 348 349 game 349 350 |> move.legal 350 - |> collect_guessed_eval(game, []) 351 + |> collect_guessed_eval(game, phase, []) 351 352 |> list.sort(fn(a, b) { int.compare(a.1, b.1) }) 352 353 } 353 354 ··· 367 368 fn collect_guessed_eval( 368 369 moves: List(Move), 369 370 game: Game, 371 + phase: Int, 370 372 acc: List(#(Move, Int)), 371 373 ) -> List(#(Move, Int)) { 372 374 case moves { 373 375 [] -> acc 374 376 [move, ..moves] -> 375 - collect_guessed_eval(moves, game, [#(move, guess_eval(game, move)), ..acc]) 377 + collect_guessed_eval(moves, game, phase, [ 378 + #(move, guess_eval(game, move, phase)), 379 + ..acc 380 + ]) 376 381 } 377 382 } 378 383 ··· 382 387 /// Guess the evaluation of a move so we can hopefully search moves in a better 383 388 /// order than random. Searching better moves first improves alpha-beta pruning, 384 389 /// allowing us to search more positions. 385 - fn guess_eval(game: Game, move: Move) -> Int { 390 + fn guess_eval(game: Game, move: Move, phase: Int) -> Int { 386 391 let assert board.Occupied(piece:, colour:) = board.get(game.board, move.from) 387 392 as "Invalid move trying to move empty piece" 388 393 ··· 392 397 piece 393 398 } 394 399 395 - let from_score = piece_table.piece_score(moving_piece, colour, move.from) 396 - let to_score = piece_table.piece_score(moving_piece, colour, move.to) 397 - 400 + let from_score = piece_table.piece_score(piece, colour, move.from, phase) 401 + let to_score = piece_table.piece_score(moving_piece, colour, move.to, phase) 398 402 let position_improvement = to_score - from_score 403 + 399 404 let move_specific_score = case move { 400 405 // TODO store information in moves so we don't have to retrieve it from the 401 406 // board every time.
+6
test/starfish_test.gleam
··· 5 5 import pocket_watch 6 6 import starfish 7 7 import starfish/internal/board 8 + import starfish/internal/evaluate 8 9 import starfish/internal/game 9 10 import starfish/internal/move 10 11 ··· 272 273 let assert Error(Nil) = starfish.parse_move("e2", starfish.new()) 273 274 let assert Error(Nil) = starfish.parse_move("Bxe4", starfish.new()) 274 275 let assert Error(Nil) = starfish.parse_move("Ndf3", starfish.new()) 276 + } 277 + 278 + pub fn phase_test() { 279 + assert evaluate.phase(starfish.new()) == 0 280 + assert evaluate.phase(starfish.from_fen("k7/8/8/8/8/8/8/K7")) == 128 275 281 } 276 282 277 283 fn apply_move(game: starfish.Game, move: String) -> starfish.Game {