Current section

Files

Jump to
gchess src fen.erl
Raw

src/fen.erl

-module(fen).
-compile([no_auto_import, nowarn_unused_vars]).
-export([parse_board/1, from_string/1]).
-export_type([castling_status/0, fen/0]).
-type castling_status() :: {castling_status,
boolean(),
boolean(),
boolean(),
boolean()}.
-type fen() :: {fen,
board:board_bb(),
color:color(),
castling_status(),
gleam@option:option(position:position()),
integer(),
integer()}.
-spec expand_rank(list(binary())) -> list(binary()).
expand_rank(Rank) ->
Accumulator = [],
gleam@list:fold(Rank, Accumulator, fun(Acc, Part) -> case Part of
<<"1"/utf8>> ->
gleam@list:append(Acc, [<<""/utf8>>]);
<<"2"/utf8>> ->
gleam@list:append(Acc, [<<""/utf8>>, <<""/utf8>>]);
<<"3"/utf8>> ->
gleam@list:append(
Acc,
[<<""/utf8>>, <<""/utf8>>, <<""/utf8>>]
);
<<"4"/utf8>> ->
gleam@list:append(
Acc,
[<<""/utf8>>, <<""/utf8>>, <<""/utf8>>, <<""/utf8>>]
);
<<"5"/utf8>> ->
gleam@list:append(
Acc,
[<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>]
);
<<"6"/utf8>> ->
gleam@list:append(
Acc,
[<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>]
);
<<"7"/utf8>> ->
gleam@list:append(
Acc,
[<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>]
);
<<"8"/utf8>> ->
gleam@list:append(
Acc,
[<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>,
<<""/utf8>>]
);
<<"K"/utf8>> ->
gleam@list:append(Acc, [<<"K"/utf8>>]);
<<"Q"/utf8>> ->
gleam@list:append(Acc, [<<"Q"/utf8>>]);
<<"R"/utf8>> ->
gleam@list:append(Acc, [<<"R"/utf8>>]);
<<"B"/utf8>> ->
gleam@list:append(Acc, [<<"B"/utf8>>]);
<<"N"/utf8>> ->
gleam@list:append(Acc, [<<"N"/utf8>>]);
<<"P"/utf8>> ->
gleam@list:append(Acc, [<<"P"/utf8>>]);
<<"k"/utf8>> ->
gleam@list:append(Acc, [<<"k"/utf8>>]);
<<"q"/utf8>> ->
gleam@list:append(Acc, [<<"q"/utf8>>]);
<<"r"/utf8>> ->
gleam@list:append(Acc, [<<"r"/utf8>>]);
<<"b"/utf8>> ->
gleam@list:append(Acc, [<<"b"/utf8>>]);
<<"n"/utf8>> ->
gleam@list:append(Acc, [<<"n"/utf8>>]);
<<"p"/utf8>> ->
gleam@list:append(Acc, [<<"p"/utf8>>]);
_ ->
gleam@list:append(Acc, [Part])
end end).
-spec parse_board(binary()) -> board:board_bb().
parse_board(Board_string) ->
List_of_ranks_as_strings = gleam@string:split(Board_string, <<"/"/utf8>>),
Accumulator = {board_bb,
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0},
{bitboard, 0}},
gleam@list:index_fold(
List_of_ranks_as_strings,
Accumulator,
fun(Acc, Rank_as_string, Rank_index) ->
Rank_index@1 = position:int_to_rank(7 - Rank_index),
Rank_parts = gleam@string:to_graphemes(Rank_as_string),
Expanded_rank = expand_rank(Rank_parts),
gleam@list:index_fold(
Expanded_rank,
Acc,
fun(Acc@1, Square, File_index) ->
File_index@1 = position:int_to_file(File_index),
case Square of
<<""/utf8>> ->
Acc@1;
<<"K"/utf8>> ->
New_white_king_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
bitboard:'or'(
erlang:element(8, Acc@1),
New_white_king_bitboard
),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"Q"/utf8>> ->
New_white_queen_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
bitboard:'or'(
erlang:element(9, Acc@1),
New_white_queen_bitboard
),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"R"/utf8>> ->
New_white_rook_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
bitboard:'or'(
erlang:element(10, Acc@1),
New_white_rook_bitboard
),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"B"/utf8>> ->
New_white_bishop_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
bitboard:'or'(
erlang:element(11, Acc@1),
New_white_bishop_bitboard
),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"N"/utf8>> ->
New_white_knight_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
bitboard:'or'(
erlang:element(12, Acc@1),
New_white_knight_bitboard
),
erlang:element(13, Acc@1)};
<<"P"/utf8>> ->
New_white_pawn_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
bitboard:'or'(
erlang:element(13, Acc@1),
New_white_pawn_bitboard
)};
<<"k"/utf8>> ->
New_black_king_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
bitboard:'or'(
erlang:element(2, Acc@1),
New_black_king_bitboard
),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"q"/utf8>> ->
New_black_queen_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
bitboard:'or'(
erlang:element(3, Acc@1),
New_black_queen_bitboard
),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"r"/utf8>> ->
New_black_rook_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
bitboard:'or'(
erlang:element(4, Acc@1),
New_black_rook_bitboard
),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"b"/utf8>> ->
New_black_bishop_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
bitboard:'or'(
erlang:element(5, Acc@1),
New_black_bishop_bitboard
),
erlang:element(6, Acc@1),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"n"/utf8>> ->
New_black_knight_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
bitboard:'or'(
erlang:element(6, Acc@1),
New_black_knight_bitboard
),
erlang:element(7, Acc@1),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
<<"p"/utf8>> ->
New_black_pawns_bitboard = board:from_position(
{position, File_index@1, Rank_index@1}
),
{board_bb,
erlang:element(2, Acc@1),
erlang:element(3, Acc@1),
erlang:element(4, Acc@1),
erlang:element(5, Acc@1),
erlang:element(6, Acc@1),
bitboard:'or'(
erlang:element(7, Acc@1),
New_black_pawns_bitboard
),
erlang:element(8, Acc@1),
erlang:element(9, Acc@1),
erlang:element(10, Acc@1),
erlang:element(11, Acc@1),
erlang:element(12, Acc@1),
erlang:element(13, Acc@1)};
_ ->
erlang:error(#{gleam_error => panic,
message => <<"panic expression evaluated"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_board"/utf8>>,
line => 396})
end
end
)
end
).
-spec parse_turn(binary()) -> color:color().
parse_turn(Turn_string) ->
case Turn_string of
<<"w"/utf8>> ->
white;
<<"b"/utf8>> ->
black;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid turn string, must be 'w' or 'b'"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_turn"/utf8>>,
line => 442})
end.
-spec parse_castling(binary()) -> castling_status().
parse_castling(Castling_string) ->
case gleam@string:length(Castling_string) =< 4 of
true ->
Cs_parts = gleam@string:split(Castling_string, <<""/utf8>>),
gleam@list:each(Cs_parts, fun(Part) -> case Part of
<<"K"/utf8>> ->
nil;
<<"Q"/utf8>> ->
nil;
<<"k"/utf8>> ->
nil;
<<"q"/utf8>> ->
nil;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid castling string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_castling"/utf8>>,
line => 458})
end end),
White_queenside_castling = gleam@string:contains(
Castling_string,
<<"Q"/utf8>>
),
White_kingside_castling = gleam@string:contains(
Castling_string,
<<"K"/utf8>>
),
Black_queenside_castling = gleam@string:contains(
Castling_string,
<<"q"/utf8>>
),
Black_kingside_castling = gleam@string:contains(
Castling_string,
<<"k"/utf8>>
),
{castling_status,
White_kingside_castling,
White_queenside_castling,
Black_kingside_castling,
Black_queenside_castling};
false ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid castling string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_castling"/utf8>>,
line => 474})
end.
-spec parse_file(binary()) -> position:file().
parse_file(File_string) ->
case File_string of
<<"a"/utf8>> ->
a;
<<"b"/utf8>> ->
b;
<<"c"/utf8>> ->
c;
<<"d"/utf8>> ->
d;
<<"e"/utf8>> ->
e;
<<"f"/utf8>> ->
f;
<<"g"/utf8>> ->
g;
<<"h"/utf8>> ->
h;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid file string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_file"/utf8>>,
line => 505})
end.
-spec parse_rank(binary()) -> position:rank().
parse_rank(Rank_string) ->
case Rank_string of
<<"1"/utf8>> ->
one;
<<"2"/utf8>> ->
two;
<<"3"/utf8>> ->
three;
<<"4"/utf8>> ->
four;
<<"5"/utf8>> ->
five;
<<"6"/utf8>> ->
six;
<<"7"/utf8>> ->
seven;
<<"8"/utf8>> ->
eight;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid rank string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_rank"/utf8>>,
line => 519})
end.
-spec parse_en_passant(binary()) -> gleam@option:option(position:position()).
parse_en_passant(En_passant_string) ->
case En_passant_string of
<<"-"/utf8>> ->
none;
_ ->
case gleam@string:length(En_passant_string) =:= 2 of
true ->
nil;
false ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid en passant string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"parse_en_passant"/utf8>>,
line => 484})
end,
En_passant_parts = gleam@string:split(
En_passant_string,
<<""/utf8>>
),
[File_string, Rank_string] = En_passant_parts,
File = parse_file(File_string),
Rank = parse_rank(Rank_string),
{some, {position, File, Rank}}
end.
-spec string_to_int(binary()) -> integer().
string_to_int(String) ->
case gleam@string:length(String) of
1 ->
case String of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
1;
<<"2"/utf8>> ->
2;
<<"3"/utf8>> ->
3;
<<"4"/utf8>> ->
4;
<<"5"/utf8>> ->
5;
<<"6"/utf8>> ->
6;
<<"7"/utf8>> ->
7;
<<"8"/utf8>> ->
8;
<<"9"/utf8>> ->
9;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 545})
end;
2 ->
[Tenths_place_string, Ones_place_string] = gleam@string:split(
String,
<<""/utf8>>
),
Tenths_place = case Tenths_place_string of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
10;
<<"2"/utf8>> ->
20;
<<"3"/utf8>> ->
30;
<<"4"/utf8>> ->
40;
<<"5"/utf8>> ->
50;
<<"6"/utf8>> ->
60;
<<"7"/utf8>> ->
70;
<<"8"/utf8>> ->
80;
<<"9"/utf8>> ->
90;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 561})
end,
Ones_place = case Ones_place_string of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
1;
<<"2"/utf8>> ->
2;
<<"3"/utf8>> ->
3;
<<"4"/utf8>> ->
4;
<<"5"/utf8>> ->
5;
<<"6"/utf8>> ->
6;
<<"7"/utf8>> ->
7;
<<"8"/utf8>> ->
8;
<<"9"/utf8>> ->
9;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 575})
end,
Tenths_place + Ones_place;
3 ->
[Hundreds_place_string, Tenths_place_string@1, Ones_place_string@1] = gleam@string:split(
String,
<<""/utf8>>
),
Hundreds_place = case Hundreds_place_string of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
100;
<<"2"/utf8>> ->
200;
<<"3"/utf8>> ->
300;
<<"4"/utf8>> ->
400;
<<"5"/utf8>> ->
500;
<<"6"/utf8>> ->
600;
<<"7"/utf8>> ->
700;
<<"8"/utf8>> ->
800;
<<"9"/utf8>> ->
900;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 593})
end,
Tenths_place@1 = case Tenths_place_string@1 of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
10;
<<"2"/utf8>> ->
20;
<<"3"/utf8>> ->
30;
<<"4"/utf8>> ->
40;
<<"5"/utf8>> ->
50;
<<"6"/utf8>> ->
60;
<<"7"/utf8>> ->
70;
<<"8"/utf8>> ->
80;
<<"9"/utf8>> ->
90;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 607})
end,
Ones_place@1 = case Ones_place_string@1 of
<<"0"/utf8>> ->
0;
<<"1"/utf8>> ->
1;
<<"2"/utf8>> ->
2;
<<"3"/utf8>> ->
3;
<<"4"/utf8>> ->
4;
<<"5"/utf8>> ->
5;
<<"6"/utf8>> ->
6;
<<"7"/utf8>> ->
7;
<<"8"/utf8>> ->
8;
<<"9"/utf8>> ->
9;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 621})
end,
(Hundreds_place + Tenths_place@1) + Ones_place@1;
_ ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid halfmove string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"string_to_int"/utf8>>,
line => 625})
end.
-spec parse_halfmove(binary()) -> integer().
parse_halfmove(Halfmove_string) ->
string_to_int(Halfmove_string).
-spec parse_fullmove(binary()) -> integer().
parse_fullmove(Fullmove_string) ->
string_to_int(Fullmove_string).
-spec from_string(binary()) -> fen().
from_string(Fen) ->
Fen_string_parts = gleam@string:split(Fen, <<" "/utf8>>),
case gleam@list:length(Fen_string_parts) =:= 6 of
false ->
erlang:error(#{gleam_error => panic,
message => <<"Invalid FEN string"/utf8>>,
module => <<"fen"/utf8>>,
function => <<"from_string"/utf8>>,
line => 38});
true ->
[Board_string | Rest] = Fen_string_parts,
[Turn_string | Rest@1] = Rest,
[Castling_string | Rest@2] = Rest@1,
[En_passant_string | Rest@3] = Rest@2,
[Halfmove_string | Rest@4] = Rest@3,
[Fullmove_string | _] = Rest@4,
Parsed_board = parse_board(Board_string),
Parsed_turn = parse_turn(Turn_string),
Parsed_castling = parse_castling(Castling_string),
Parsed_en_passant = parse_en_passant(En_passant_string),
Parsed_halfmove = parse_halfmove(Halfmove_string),
Parsed_fullmove = parse_fullmove(Fullmove_string),
Fen@1 = {fen,
Parsed_board,
Parsed_turn,
Parsed_castling,
Parsed_en_passant,
Parsed_halfmove,
Parsed_fullmove},
Fen@1
end.