···4545 /// `rnbqkbnr/8/8/8/8/RNBQKBNR w - - 0 1`, only 6 ranks are specified, which
4646 /// would cause this error.
4747 PiecePositionsIncomplete
4848+ /// The board is missing the white king, meaning the position is invalid.
4949+ MissingWhiteKing
5050+ /// The board is missing the black king, meaning the position is invalid.
5151+ MissingBlackKing
4852 /// The field specifying which player's turn is next is wrong or missing. For
4953 /// example in the string `8/8/8/8/8/8/8/8 - - 0 1`, the active colour specifier
5054 /// is missing.
···96100 game.ExpectedSpaceAfterSegment -> ExpectedSpaceAfterSegment
97101 game.PiecePositionsIncomplete -> PiecePositionsIncomplete
98102 game.TrailingData(value) -> TrailingData(value)
103103+ game.MissingBlackKing -> MissingBlackKing
104104+ game.MissingWhiteKing -> MissingWhiteKing
99105 }
100106}
101107
···2929 zobrist_hash: Int,
3030 previous_positions: List(Int),
3131 attack_information: attack.AttackInformation,
3232+ white_king_position: Int,
3333+ black_king_position: Int,
3234 )
3335}
3436···3840 let to_move = White
3941 let board = board.initial_position()
4042 let zobrist_hash = hash.hash(board, to_move)
4141- let attack_information = attack.calculate(board, to_move)
42434444+ let white_king_position = 4
4545+ let black_king_position = 60
4646+4747+ let attack_information = attack.calculate(board, white_king_position, to_move)
4348 Game(
4449 board:,
4550 to_move:,
···5055 zobrist_hash:,
5156 previous_positions: [],
5257 attack_information:,
5858+ white_king_position:,
5959+ black_king_position:,
5360 )
5461}
55625663pub fn from_fen(fen: String) -> Game {
5764 let fen = strip_spaces(fen)
58655959- let #(board, fen, _) = board.from_fen(fen)
6666+ let board.FenParseResult(
6767+ board:,
6868+ remaining: fen,
6969+ board_is_complete: _,
7070+ white_king_position:,
7171+ black_king_position:,
7272+ ) = board.from_fen(fen)
6073 let fen = strip_spaces(fen)
61746275 let #(to_move, fen) = case fen {
···92105 }
9310694107 let zobrist_hash = hash.hash(board, to_move)
9595- let attack_information = attack.calculate(board, to_move)
108108+109109+ let white_king_position = option.unwrap(white_king_position, 0)
110110+ let black_king_position = option.unwrap(black_king_position, 0)
111111+112112+ let king_position = case to_move {
113113+ Black -> black_king_position
114114+ White -> white_king_position
115115+ }
116116+ let attack_information = attack.calculate(board, king_position, to_move)
9611797118 Game(
98119 board:,
···104125 zobrist_hash:,
105126 previous_positions: [],
106127 attack_information:,
128128+ white_king_position:,
129129+ black_king_position:,
107130 )
108131}
109132···183206184207pub type FenParseError {
185208 PiecePositionsIncomplete
209209+ MissingWhiteKing
210210+ MissingBlackKing
186211 ExpectedActiveColour
187212 ExpectedSpaceAfterSegment
188213 TrailingData(String)
···195220pub fn try_from_fen(fen: String) -> Result(Game, FenParseError) {
196221 let fen = strip_spaces(fen)
197222198198- let #(board, fen, completed) = board.from_fen(fen)
199199- use <- bool.guard(!completed, Error(PiecePositionsIncomplete))
223223+ let board.FenParseResult(
224224+ board:,
225225+ remaining: fen,
226226+ board_is_complete:,
227227+ white_king_position:,
228228+ black_king_position:,
229229+ ) = board.from_fen(fen)
230230+ use <- bool.guard(!board_is_complete, Error(PiecePositionsIncomplete))
231231+ use white_king_position <- result.try(option.to_result(
232232+ white_king_position,
233233+ MissingWhiteKing,
234234+ ))
235235+ use black_king_position <- result.try(option.to_result(
236236+ black_king_position,
237237+ MissingBlackKing,
238238+ ))
239239+200240 use fen <- result.try(expect_spaces(fen))
201241202242 use #(to_move, fen) <- result.try(case fen {
···233273 use <- bool.guard(fen != "", Error(TrailingData(fen)))
234274235275 let zobrist_hash = hash.hash(board, to_move)
236236- let attack_information = attack.calculate(board, to_move)
276276+277277+ let king_position = case to_move {
278278+ Black -> black_king_position
279279+ White -> white_king_position
280280+ }
281281+282282+ let attack_information = attack.calculate(board, king_position, to_move)
237283238284 Ok(Game(
239285 board:,
···245291 zobrist_hash:,
246292 previous_positions: [],
247293 attack_information:,
294294+ white_king_position:,
295295+ black_king_position:,
248296 ))
249297}
250298
+49-17
src/starfish/internal/move.gleam
···109109 // promotions, because they must move to the same rank. A double-move can
110110 // never be a promotion because a pawn cannot double move from a position that
111111 // ends on the promotion rank.
112112- let is_promotion = board.rank(forward_one) == promotion_rank
112112+ let is_promotion = forward_one / 8 == promotion_rank
113113114114 let moves = case board.get(game.board, forward_one) {
115115 board.Empty -> {
···122122 True -> [Move(from: position, to: forward_one), ..moves]
123123 }
124124125125- let can_double_move = case game.to_move, board.rank(position) {
125125+ let can_double_move = case game.to_move, position / 8 {
126126 board.Black, 6 | board.White, 1 -> True
127127 _, _ -> False
128128 }
···178178}
179179180180fn en_passant_is_valid(game: Game, position: Int, new_position: Int) -> Bool {
181181- let captured_pawn_position =
182182- board.position(file: board.file(new_position), rank: board.rank(position))
181181+ let captured_pawn_position = new_position % 8 + position / 8 * 8
183182184183 case game.attack_information.in_check {
185184 False ->
···452451pub fn apply(game: Game, move: Move) -> game.Game {
453452 case move {
454453 Capture(from:, to:) -> do_apply(game, from, to, False, None, True)
455455- Castle(from:, to:) -> apply_castle(game, from, to, board.file(to) == 2)
454454+ Castle(from:, to:) -> apply_castle(game, from, to, to % 8 == 2)
456455 EnPassant(from:, to:) -> do_apply(game, from, to, True, None, True)
457456 Move(from:, to:) -> do_apply(game, from, to, False, None, False)
458457 Promotion(from:, to:, piece:) ->
···471470 zobrist_hash:,
472471 previous_positions:,
473472 attack_information: _,
473473+ white_king_position:,
474474+ black_king_position:,
474475 ) = game
475476476477 let assert board.Occupied(piece:, colour:) = board.get(board, from)
···483484 game.Castling(..castling, white_kingside: False, white_queenside: False)
484485 }
485486486486- let rook_rank = board.rank(from)
487487+ let rook_rank = from / 8
487488 let #(rook_file_from, rook_file_to) = case long {
488489 True -> #(0, 3)
489490 False -> #(7, 5)
···492493 let board =
493494 board
494495 |> dict.delete(from)
495495- |> dict.delete(board.position(file: rook_file_from, rank: rook_rank))
496496+ |> dict.delete(rook_rank * 8 + rook_file_from)
496497 |> dict.insert(to, #(piece, colour))
497497- |> dict.insert(board.position(file: rook_file_to, rank: rook_rank), #(
498498- board.Rook,
499499- colour,
500500- ))
498498+ |> dict.insert(rook_rank * 8 + rook_file_to, #(board.Rook, colour))
501499502500 let en_passant_square = None
503501502502+ let white_king_position = case to_move {
503503+ board.White -> to
504504+ board.Black -> white_king_position
505505+ }
506506+507507+ let black_king_position = case to_move {
508508+ board.Black -> to
509509+ board.White -> black_king_position
510510+ }
511511+504512 let full_moves = case to_move {
505513 board.Black -> full_moves + 1
506514 board.White -> full_moves
···517525 // TODO: Update incrementally
518526 let zobrist_hash = hash.hash(board, to_move)
519527528528+ let king_position = case to_move {
529529+ board.Black -> black_king_position
530530+ board.White -> white_king_position
531531+ }
520532 // TODO: Maybe we can update this incrementally too?
521521- let attack_information = attack.calculate(board, to_move)
533533+ let attack_information = attack.calculate(board, king_position, to_move)
522534523535 Game(
524536 board:,
···530542 zobrist_hash:,
531543 previous_positions:,
532544 attack_information:,
545545+ white_king_position:,
546546+ black_king_position:,
533547 )
534548}
535549···551565 zobrist_hash:,
552566 previous_positions:,
553567 attack_information: _,
568568+ white_king_position:,
569569+ black_king_position:,
554570 ) = game
555571556572 let assert board.Occupied(piece:, colour:) = board.get(board, from)
···598614 let #(half_moves, previous_positions) = case one_way_move {
599615 True -> #(0, [])
600616 False -> #(half_moves + 1, [zobrist_hash, ..previous_positions])
617617+ }
618618+619619+ let white_king_position = case from == white_king_position {
620620+ True -> to
621621+ False -> white_king_position
622622+ }
623623+624624+ let black_king_position = case from == black_king_position {
625625+ True -> to
626626+ False -> black_king_position
601627 }
602628603629 // TODO: Update incrementally
604630 let zobrist_hash = hash.hash(board, to_move)
605631632632+ let king_position = case to_move {
633633+ board.Black -> black_king_position
634634+ board.White -> white_king_position
635635+ }
606636 // TODO: Maybe we can update this incrementally too?
607607- let attack_information = attack.calculate(board, to_move)
637637+ let attack_information = attack.calculate(board, king_position, to_move)
608638609639 Game(
610640 board:,
···616646 zobrist_hash:,
617647 previous_positions:,
618648 attack_information:,
649649+ white_king_position:,
650650+ black_king_position:,
619651 )
620652}
621653···806838807839 use <- bool.guard(move != "", Error(Nil))
808840809809- let to = board.position(file: to_file, rank: to_rank)
841841+ let to = to_rank * 8 + to_file
810842811843 case get_pieces(game, piece_kind, legal_moves, from_file, from_rank, to) {
812844 [from] if capture -> Ok(Capture(from:, to:))
···896928 _ -> Error(Nil)
897929 })
898930899899- let to = board.position(file: to_file, rank:)
931931+ let to = rank * 8 + to_file
900932901933 case
902934 get_pieces(game, board.Pawn, legal_moves, from_file, None, to),
···926958 && piece == find_piece
927959 && case from_file {
928960 None -> True
929929- Some(file) -> file == board.file(position)
961961+ Some(file) -> file == position % 8
930962 }
931963 && case from_rank {
932964 None -> True
933933- Some(rank) -> rank == board.rank(position)
965965+ Some(rank) -> rank == position / 8
934966 }
935967 && list.any(legal_moves, fn(move) { move.to == to && move.from == position })
936968
+5-8
src/starfish/internal/move/attack.gleam
···2727 )
2828}
29293030-pub fn calculate(board: Board, to_move: board.Colour) -> AttackInformation {
3131- // TODO: keep track of this
3232- let assert Ok(#(king_position, _)) =
3333- board
3434- |> dict.to_list
3535- |> list.find(fn(pair) { pair.1 == #(board.King, to_move) })
3636- as "Failed to find king on board"
3737-3030+pub fn calculate(
3131+ board: Board,
3232+ king_position: Int,
3333+ to_move: board.Colour,
3434+) -> AttackInformation {
3835 let attacking = case to_move {
3936 board.Black -> board.White
4037 board.White -> board.Black
+3-3
src/starfish/internal/move/direction.gleam
···99/// Returns a position moved in a given direction, checking for it being within
1010/// the bounds of the board.
1111pub fn in_direction(position: Int, direction: Direction) -> Int {
1212- let file = board.file(position) + direction.file_change
1313- let rank = board.rank(position) + direction.rank_change
1212+ let file = position % 8 + direction.file_change
1313+ let rank = position / 8 + direction.rank_change
14141515 case
1616 file >= board.side_length
···1919 || rank < 0
2020 {
2121 True -> -1
2222- False -> board.position(file:, rank:)
2222+ False -> rank * 8 + file
2323 }
2424}
2525