A chess library for Gleam
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement `to_standard_algebraic_notation`

+252 -2
+2 -2
src/starfish.gleam
··· 155 155 move.apply(game, move) 156 156 } 157 157 158 - pub fn to_standard_algebraic_notation(move: Move) -> String { 159 - todo 158 + pub fn to_standard_algebraic_notation(move: Move, game: Game) -> String { 159 + move.to_standard_algebraic_notation(move, game) 160 160 } 161 161 162 162 /// Convert a move to [long algebraic notation](
+136
src/starfish/internal/move.gleam
··· 1 1 import gleam/bool 2 2 import gleam/dict 3 + import gleam/int 3 4 import gleam/list 4 5 import gleam/option.{type Option, None, Some} 5 6 import gleam/result ··· 819 820 } 820 821 821 822 from <> to <> extra 823 + } 824 + 825 + pub fn to_standard_algebraic_notation(move: Move, game: Game) -> String { 826 + let assert board.Occupied(piece:, colour: _) = 827 + board.get(game.board, move.from) 828 + as "Legal moves should only move valid pieces" 829 + 830 + case move { 831 + Castle(from: _, to:) -> { 832 + let is_long = to % 8 == 2 833 + case is_long { 834 + False -> "O-O" 835 + True -> "O-O-O" 836 + } 837 + } 838 + Capture(from:, to:) if piece == board.Pawn -> 839 + pawn_move_to_san(from, to, True, None) 840 + EnPassant(from:, to:) -> pawn_move_to_san(from, to, True, None) 841 + Promotion(from:, to:, piece:) -> { 842 + let is_capture = case board.get(game.board, move.to) { 843 + board.Occupied(..) -> True 844 + board.Empty | board.OffBoard -> False 845 + } 846 + pawn_move_to_san(from, to, is_capture, Some(piece)) 847 + } 848 + Move(from:, to:) -> move_to_san(game, piece, from, to, False) 849 + Capture(from:, to:) -> move_to_san(game, piece, from, to, True) 850 + } 851 + } 852 + 853 + fn move_to_san( 854 + game: Game, 855 + piece: board.Piece, 856 + from: Int, 857 + to: Int, 858 + is_capture: Bool, 859 + ) -> String { 860 + let piece_string = piece_to_san(piece) 861 + 862 + let disambiguating_text = case disambiguation(game, from, to, piece) { 863 + NoAmbiguity -> "" 864 + DisambiguateBoth -> board.position_to_string(from) 865 + DisambiguateFile -> file_to_string(from % 8) 866 + DisambiguateRank -> int.to_string(from / 8 + 1) 867 + } 868 + 869 + case is_capture { 870 + False -> piece_string <> disambiguating_text <> board.position_to_string(to) 871 + True -> 872 + piece_string <> disambiguating_text <> "x" <> board.position_to_string(to) 873 + } 874 + } 875 + 876 + fn disambiguation( 877 + game: Game, 878 + from: Int, 879 + to: Int, 880 + piece: board.Piece, 881 + ) -> Disambiguation { 882 + let from_file = from % 8 883 + 884 + use disambiguation, move <- list.fold(legal(game), NoAmbiguity) 885 + 886 + use <- bool.guard(move.to != to, disambiguation) 887 + use <- bool.guard(move.from == from, disambiguation) 888 + 889 + let assert board.Occupied(piece: moving_piece, colour: _) = 890 + board.get(game.board, move.from) 891 + as "Legal moves should only move valid pieces" 892 + 893 + use <- bool.guard(moving_piece != piece, disambiguation) 894 + 895 + case disambiguation, move.from % 8 == from_file { 896 + DisambiguateBoth, _ | DisambiguateRank, True -> disambiguation 897 + DisambiguateFile, True -> DisambiguateBoth 898 + NoAmbiguity, True -> DisambiguateRank 899 + _, False -> 900 + case disambiguation { 901 + DisambiguateRank -> DisambiguateBoth 902 + NoAmbiguity -> DisambiguateFile 903 + _ -> disambiguation 904 + } 905 + } 906 + } 907 + 908 + type Disambiguation { 909 + NoAmbiguity 910 + DisambiguateFile 911 + DisambiguateRank 912 + DisambiguateBoth 913 + } 914 + 915 + fn pawn_move_to_san( 916 + from: Int, 917 + to: Int, 918 + is_capture: Bool, 919 + promotion: Option(board.Piece), 920 + ) -> String { 921 + let destination = board.position_to_string(to) 922 + let move = case is_capture { 923 + False -> destination 924 + True -> { 925 + let file = file_to_string(from % 8) 926 + file <> "x" <> destination 927 + } 928 + } 929 + 930 + case promotion { 931 + None -> move 932 + Some(piece) -> move <> "=" <> piece_to_san(piece) 933 + } 934 + } 935 + 936 + fn piece_to_san(piece: board.Piece) -> String { 937 + case piece { 938 + board.Bishop -> "B" 939 + board.King -> "K" 940 + board.Knight -> "N" 941 + board.Pawn -> "" 942 + board.Queen -> "Q" 943 + board.Rook -> "R" 944 + } 945 + } 946 + 947 + fn file_to_string(file: Int) -> String { 948 + case file { 949 + 0 -> "a" 950 + 1 -> "b" 951 + 2 -> "c" 952 + 3 -> "d" 953 + 4 -> "e" 954 + 5 -> "f" 955 + 6 -> "g" 956 + _ -> "h" 957 + } 822 958 } 823 959 824 960 // TODO: can we do this without calculating every legal move? We can probably just
+114
test/starfish_test.gleam
··· 274 274 let assert Error(Nil) = starfish.parse_move("Ndf3", starfish.new()) 275 275 } 276 276 277 + pub fn to_standard_algebraic_notation_test() { 278 + assert starfish.to_standard_algebraic_notation( 279 + move.Move(from: 8, to: 24), 280 + starfish.new(), 281 + ) 282 + == "a4" 283 + assert starfish.to_standard_algebraic_notation( 284 + move.Move(from: 6, to: 21), 285 + starfish.new(), 286 + ) 287 + == "Nf3" 288 + assert starfish.to_standard_algebraic_notation( 289 + move.Move(from: 57, to: 42), 290 + starfish.from_fen( 291 + "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1", 292 + ), 293 + ) 294 + == "Nc6" 295 + 296 + assert starfish.to_standard_algebraic_notation( 297 + move.Move(from: 49, to: 33), 298 + starfish.from_fen( 299 + "rnbqkbnr/pppppppp/8/8/P7/8/1PPPPPPP/RNBQKBNR b KQkq - 0 1", 300 + ), 301 + ) 302 + == "b5" 303 + 304 + assert starfish.to_standard_algebraic_notation( 305 + move.EnPassant(from: 32, to: 41), 306 + starfish.from_fen( 307 + "rnbqkbnr/p1p1pppp/8/Pp1p4/8/8/1PPPPPPP/RNBQKBNR w KQkq b6 0 3", 308 + ), 309 + ) 310 + == "axb6" 311 + 312 + assert starfish.to_standard_algebraic_notation( 313 + move.Castle(from: 4, to: 6), 314 + starfish.from_fen( 315 + "rnbqkbnr/pp3ppp/8/2ppp3/4P3/5N2/PPPPBPPP/RNBQK2R w KQkq - 0 4", 316 + ), 317 + ) 318 + == "O-O" 319 + 320 + assert starfish.to_standard_algebraic_notation( 321 + move.Castle(from: 4, to: 2), 322 + starfish.from_fen( 323 + "rnbqkbnr/ppp2ppp/8/3pp3/3P4/2N1B3/PPPQPPPP/R3KBNR w KQkq - 0 5", 324 + ), 325 + ) 326 + == "O-O-O" 327 + 328 + assert starfish.to_standard_algebraic_notation( 329 + move.Castle(from: 60, to: 62), 330 + starfish.from_fen( 331 + "rnbqk2r/ppppbppp/5n2/4p3/2PPP3/8/PP3PPP/RNBQKBNR b KQkq - 0 4", 332 + ), 333 + ) 334 + == "O-O" 335 + 336 + assert starfish.to_standard_algebraic_notation( 337 + move.Castle(from: 60, to: 58), 338 + starfish.from_fen( 339 + "r3kbnr/pppqpppp/2n1b3/3p4/3PP3/8/PPP2PPP/RNBQKBNR b KQkq - 0 5", 340 + ), 341 + ) 342 + == "O-O-O" 343 + 344 + assert starfish.to_standard_algebraic_notation( 345 + move.Promotion(from: 51, to: 58, piece: board.Queen), 346 + starfish.from_fen( 347 + "rnbq1bnr/pppPkpp1/4p2p/8/8/8/PPPP1PPP/RNBQKBNR w KQ - 1 5", 348 + ), 349 + ) 350 + == "dxc8=Q" 351 + 352 + assert starfish.to_standard_algebraic_notation( 353 + move.Promotion(from: 11, to: 2, piece: board.Knight), 354 + starfish.from_fen( 355 + "rnbqkbnr/pppp1ppp/8/8/8/4P2P/PPPpKPP1/RNBQ1BNR b kq - 1 5", 356 + ), 357 + ) 358 + == "dxc1=N" 359 + 360 + assert starfish.to_standard_algebraic_notation( 361 + move.Capture(from: 49, to: 7), 362 + starfish.from_fen( 363 + "rn1qkbnr/pbpppppp/1p6/6P1/8/8/PPPPPP1P/RNBQKBNR b KQkq - 0 3", 364 + ), 365 + ) 366 + == "Bxh1" 367 + 368 + assert starfish.to_standard_algebraic_notation( 369 + move.Move(from: 24, to: 26), 370 + starfish.from_fen("k7/8/8/8/R4R2/8/8/7K w - - 0 1"), 371 + ) 372 + == "Rac4" 373 + 374 + assert starfish.to_standard_algebraic_notation( 375 + move.Move(from: 50, to: 42), 376 + starfish.from_fen("k7/2r5/8/8/2r5/8/8/7K b - - 0 1"), 377 + ) 378 + == "R7c6" 379 + 380 + assert starfish.to_standard_algebraic_notation( 381 + move.Capture(from: 31, to: 13), 382 + starfish.from_fen("k7/8/8/8/5Q1Q/8/5b1Q/3K4 w - - 0 1"), 383 + ) 384 + == "Qh4xf2" 385 + 386 + let assert Error(Nil) = starfish.parse_move("e2", starfish.new()) 387 + let assert Error(Nil) = starfish.parse_move("Bxe4", starfish.new()) 388 + let assert Error(Nil) = starfish.parse_move("Ndf3", starfish.new()) 389 + } 390 + 277 391 pub fn phase_test() { 278 392 let game = starfish.new() 279 393 assert game.phase(