Add chess GUI and move generation
This commit is contained in:
@@ -0,0 +1,600 @@
|
||||
const std = @import("std");
|
||||
|
||||
const piece = @import("piece.zig");
|
||||
const board = @import("board.zig");
|
||||
|
||||
pub fn parseFen(fen: []const u8) !board.BoardState {
|
||||
var state = board.BoardState.empty();
|
||||
var it = std.mem.splitScalar(u8, fen, ' ');
|
||||
var field_index: usize = 0;
|
||||
|
||||
while (it.next()) |field| : (field_index += 1) {
|
||||
if (field_index >= 6) return error.InvalidFenFieldCount;
|
||||
|
||||
switch (field_index) {
|
||||
0 => try parseBoardPlacement(&state, field),
|
||||
1 => try parseTurn(&state, field),
|
||||
2 => try parseCastleRights(&state, field),
|
||||
3 => try parseEnPassant(&state, field),
|
||||
4 => try parseHalfmove(&state, field),
|
||||
5 => try parseFullmove(&state, field),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
if (field_index != 6) return error.InvalidFenFieldCount;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
pub fn parseBoardPlacement(state: *board.BoardState, placement: []const u8) !void {
|
||||
var it = std.mem.splitScalar(u8, placement, '/');
|
||||
|
||||
var rank_count: u4 = 0;
|
||||
while (it.next()) |part| {
|
||||
if (rank_count >= 8) return error.InvalidRankCount;
|
||||
|
||||
const rank: u3 = @intCast(7 - rank_count);
|
||||
var file: u4 = 0;
|
||||
|
||||
for (part) |c| {
|
||||
switch (c) {
|
||||
'1'...'9' => {
|
||||
file += @intCast(c - '0');
|
||||
if (file > 8) return error.InvalidRankWidth;
|
||||
},
|
||||
'P',
|
||||
'N',
|
||||
'B',
|
||||
'R',
|
||||
'Q',
|
||||
'K',
|
||||
'p',
|
||||
'n',
|
||||
'b',
|
||||
'r',
|
||||
'q',
|
||||
'k',
|
||||
=> {
|
||||
if (file >= 8) return error.InvalidRankWidth;
|
||||
const p = try piece.fromFENChar(c);
|
||||
state.setSquare(@intCast(file), rank, p);
|
||||
file += 1;
|
||||
},
|
||||
else => return error.InvalidFenCharacter,
|
||||
}
|
||||
}
|
||||
|
||||
if (file != 8) return error.InvalidRankWidth;
|
||||
rank_count += 1;
|
||||
}
|
||||
|
||||
if (rank_count != 8) return error.InvalidRankCount;
|
||||
}
|
||||
|
||||
pub fn parseTurn(state: *board.BoardState, turn_string: []const u8) !void {
|
||||
if (turn_string.len != 1) return error.InvalidTurnChar;
|
||||
const turn_char = turn_string[0];
|
||||
if (turn_char == 'w') {
|
||||
state.turn = piece.Color.white;
|
||||
} else if (turn_char == 'b') {
|
||||
state.turn = piece.Color.black;
|
||||
} else {
|
||||
return error.InvalidTurnChar;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parseHalfmove(state: *board.BoardState, halfmove_string: []const u8) !void {
|
||||
state.halfmove = try std.fmt.parseInt(u8, halfmove_string, 10);
|
||||
}
|
||||
|
||||
pub fn parseFullmove(state: *board.BoardState, fullmove_string: []const u8) !void {
|
||||
const fullmove = try std.fmt.parseInt(u32, fullmove_string, 10);
|
||||
if (fullmove == 0) return error.InvalidFullmoveNumber;
|
||||
state.fullmove = fullmove;
|
||||
}
|
||||
|
||||
pub fn parseCastleRights(state: *board.BoardState, castle_string: []const u8) !void {
|
||||
if (std.mem.eql(u8, castle_string, "")) return error.InvalidCastlingRights;
|
||||
if (std.mem.eql(u8, castle_string, "-")) {
|
||||
state.castle_rights = 0;
|
||||
return;
|
||||
}
|
||||
var rights: u4 = 0;
|
||||
var last_order: u3 = 0;
|
||||
for (castle_string) |c| {
|
||||
const bit: u4 = switch (c) {
|
||||
'K' => 0b1000,
|
||||
'Q' => 0b0100,
|
||||
'k' => 0b0010,
|
||||
'q' => 0b0001,
|
||||
else => return error.InvalidCastlingRights,
|
||||
};
|
||||
const order: u3 = switch (c) {
|
||||
'K' => 1,
|
||||
'Q' => 2,
|
||||
'k' => 3,
|
||||
'q' => 4,
|
||||
else => unreachable,
|
||||
};
|
||||
if (order <= last_order) {
|
||||
return error.InvalidCastlingRights;
|
||||
}
|
||||
if ((rights & bit) != 0) {
|
||||
return error.InvalidCastlingRights;
|
||||
}
|
||||
rights |= bit;
|
||||
last_order = order;
|
||||
}
|
||||
state.castle_rights = rights;
|
||||
}
|
||||
|
||||
pub fn isEnPassantCapturable(state: *board.BoardState, ep_square: u6) bool {
|
||||
const file: u3 = @intCast(ep_square % 8);
|
||||
const rank: u3 = @intCast((ep_square / 8) + 1);
|
||||
|
||||
var pawn_rank: u3 = 0;
|
||||
if (state.turn == piece.Color.white) {
|
||||
if (rank != 6) return false;
|
||||
pawn_rank = 4;
|
||||
} else {
|
||||
if (rank != 3) return false;
|
||||
pawn_rank = 3;
|
||||
}
|
||||
var p: u4 = 0;
|
||||
if (file == 0) {
|
||||
p = state.getSquare(file + 1, pawn_rank);
|
||||
if (piece.typeOf(p) == piece.PieceType.pawn and piece.colorOf(p) == state.turn) {
|
||||
return true;
|
||||
}
|
||||
} else if (file == 7) {
|
||||
p = state.getSquare(file - 1, pawn_rank);
|
||||
if (piece.typeOf(p) == piece.PieceType.pawn and piece.colorOf(p) == state.turn) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
p = state.getSquare(file - 1, pawn_rank);
|
||||
if (piece.typeOf(p) == piece.PieceType.pawn and piece.colorOf(p) == state.turn) {
|
||||
return true;
|
||||
}
|
||||
p = state.getSquare(file + 1, pawn_rank);
|
||||
if (piece.typeOf(p) == piece.PieceType.pawn and piece.colorOf(p) == state.turn) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn parseEnPassant(state: *board.BoardState, ep_string: []const u8) !void {
|
||||
if (std.mem.eql(u8, ep_string, "-")) {
|
||||
state.en_passant = 0;
|
||||
return;
|
||||
}
|
||||
if (ep_string.len != 2) return error.InvalidSquare;
|
||||
const ep_square = try board.parseSquareFromAlgebraic(ep_string);
|
||||
if (ep_string[1] != '3' and ep_string[1] != '6') return error.InvalidEnPassantSquare;
|
||||
|
||||
if (isEnPassantCapturable(state, ep_square)) {
|
||||
state.en_passant = (1 << 6) | @as(u7, ep_square);
|
||||
} else {
|
||||
state.en_passant = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn pieceToFenChar(encoded: u4) !u8 {
|
||||
const color = piece.colorOf(encoded) orelse return error.InvalidPiece;
|
||||
|
||||
return switch (piece.typeOf(encoded)) {
|
||||
.pawn => if (color == .white) 'P' else 'p',
|
||||
.knight => if (color == .white) 'N' else 'n',
|
||||
.bishop => if (color == .white) 'B' else 'b',
|
||||
.rook => if (color == .white) 'R' else 'r',
|
||||
.queen => if (color == .white) 'Q' else 'q',
|
||||
.king => if (color == .white) 'K' else 'k',
|
||||
.none => error.InvalidPiece,
|
||||
};
|
||||
}
|
||||
|
||||
fn appendBoardPlacement(allocator: std.mem.Allocator, out: *std.ArrayList(u8), state: board.BoardState) !void {
|
||||
var rank: u4 = 8;
|
||||
while (rank > 0) {
|
||||
rank -= 1;
|
||||
var empty_count: u4 = 0;
|
||||
|
||||
var file: u4 = 0;
|
||||
while (file < 8) : (file += 1) {
|
||||
const square = state.getSquare(@intCast(file), @intCast(rank));
|
||||
if (square == 0) {
|
||||
empty_count += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty_count > 0) {
|
||||
try out.append(allocator, '0' + @as(u8, @intCast(empty_count)));
|
||||
empty_count = 0;
|
||||
}
|
||||
|
||||
try out.append(allocator, try pieceToFenChar(square));
|
||||
}
|
||||
|
||||
if (empty_count > 0) {
|
||||
try out.append(allocator, '0' + @as(u8, @intCast(empty_count)));
|
||||
}
|
||||
|
||||
if (rank > 0) try out.append(allocator, '/');
|
||||
}
|
||||
}
|
||||
|
||||
fn appendCastleRights(allocator: std.mem.Allocator, out: *std.ArrayList(u8), rights: u4) !void {
|
||||
if (rights == 0) {
|
||||
try out.append(allocator, '-');
|
||||
return;
|
||||
}
|
||||
|
||||
if ((rights & 0b1000) != 0) try out.append(allocator, 'K');
|
||||
if ((rights & 0b0100) != 0) try out.append(allocator, 'Q');
|
||||
if ((rights & 0b0010) != 0) try out.append(allocator, 'k');
|
||||
if ((rights & 0b0001) != 0) try out.append(allocator, 'q');
|
||||
}
|
||||
|
||||
fn appendEnPassant(allocator: std.mem.Allocator, out: *std.ArrayList(u8), en_passant: u7) !void {
|
||||
const valid = (en_passant & (@as(u7, 1) << 6)) != 0;
|
||||
if (!valid) {
|
||||
try out.append(allocator, '-');
|
||||
return;
|
||||
}
|
||||
|
||||
const square = en_passant & 0b111111;
|
||||
const file: u8 = @intCast(square % 8);
|
||||
const rank: u8 = @intCast(square / 8);
|
||||
|
||||
try out.append(allocator, 'a' + file);
|
||||
try out.append(allocator, '1' + rank);
|
||||
}
|
||||
|
||||
pub fn formatFen(allocator: std.mem.Allocator, state: board.BoardState) ![]u8 {
|
||||
var out: std.ArrayList(u8) = .empty;
|
||||
errdefer out.deinit(allocator);
|
||||
|
||||
try appendBoardPlacement(allocator, &out, state);
|
||||
try out.append(allocator, ' ');
|
||||
|
||||
try out.append(allocator, if (state.turn == .white) 'w' else 'b');
|
||||
try out.append(allocator, ' ');
|
||||
|
||||
try appendCastleRights(allocator, &out, state.castle_rights);
|
||||
try out.append(allocator, ' ');
|
||||
|
||||
try appendEnPassant(allocator, &out, state.en_passant);
|
||||
try out.append(allocator, ' ');
|
||||
|
||||
var halfmove_buf: [3]u8 = undefined;
|
||||
const halfmove = try std.fmt.bufPrint(&halfmove_buf, "{}", .{state.halfmove});
|
||||
try out.appendSlice(allocator, halfmove);
|
||||
try out.append(allocator, ' ');
|
||||
|
||||
var fullmove_buf: [10]u8 = undefined;
|
||||
const fullmove = try std.fmt.bufPrint(&fullmove_buf, "{}", .{state.fullmove});
|
||||
try out.appendSlice(allocator, fullmove);
|
||||
|
||||
return try out.toOwnedSlice(allocator);
|
||||
}
|
||||
|
||||
test "parseBoardPlacement parses starting position" {
|
||||
var state = board.BoardState.empty();
|
||||
try parseBoardPlacement(
|
||||
&state,
|
||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR",
|
||||
);
|
||||
|
||||
try std.testing.expectEqual(@as(u32, 0xCABEDBAC), state.board[0]);
|
||||
try std.testing.expectEqual(@as(u32, 0x99999999), state.board[1]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[2]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[3]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[4]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[5]);
|
||||
try std.testing.expectEqual(@as(u32, 0x11111111), state.board[6]);
|
||||
try std.testing.expectEqual(@as(u32, 0x42365324), state.board[7]);
|
||||
}
|
||||
|
||||
test "parseBoardPlacement rejects invalid rank counts" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(
|
||||
error.InvalidRankCount,
|
||||
parseBoardPlacement(&state, "8/8/8/8/8/8/8"),
|
||||
);
|
||||
|
||||
try std.testing.expectError(
|
||||
error.InvalidRankCount,
|
||||
parseBoardPlacement(&state, "8/8/8/8/8/8/8/8/8"),
|
||||
);
|
||||
}
|
||||
|
||||
test "parseBoardPlacement rejects ranks that are not exactly eight squares" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(
|
||||
error.InvalidRankWidth,
|
||||
parseBoardPlacement(&state, "7/8/8/8/8/8/8/8"),
|
||||
);
|
||||
|
||||
try std.testing.expectError(
|
||||
error.InvalidRankWidth,
|
||||
parseBoardPlacement(&state, "9/8/8/8/8/8/8/8"),
|
||||
);
|
||||
|
||||
try std.testing.expectError(
|
||||
error.InvalidRankWidth,
|
||||
parseBoardPlacement(&state, "8P/8/8/8/8/8/8/8"),
|
||||
);
|
||||
}
|
||||
|
||||
test "parseTurn accepts white and black active colors" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try parseTurn(&state, "b");
|
||||
try std.testing.expectEqual(piece.Color.black, state.turn);
|
||||
|
||||
try parseTurn(&state, "w");
|
||||
try std.testing.expectEqual(piece.Color.white, state.turn);
|
||||
}
|
||||
|
||||
test "parseTurn rejects invalid active color" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(error.InvalidTurnChar, parseTurn(&state, "x"));
|
||||
try std.testing.expectError(error.InvalidTurnChar, parseTurn(&state, "white"));
|
||||
}
|
||||
|
||||
test "parseHalfmove parses decimal halfmove clock" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try parseHalfmove(&state, "42");
|
||||
try std.testing.expectEqual(@as(u8, 42), state.halfmove);
|
||||
}
|
||||
|
||||
test "parseHalfmove rejects invalid or overflowing values" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(error.InvalidCharacter, parseHalfmove(&state, "abc"));
|
||||
try std.testing.expectError(error.Overflow, parseHalfmove(&state, "256"));
|
||||
}
|
||||
|
||||
test "parseFullmove parses decimal fullmove number" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try parseFullmove(&state, "1");
|
||||
try std.testing.expectEqual(@as(u32, 1), state.fullmove);
|
||||
|
||||
try parseFullmove(&state, "300");
|
||||
try std.testing.expectEqual(@as(u32, 300), state.fullmove);
|
||||
}
|
||||
|
||||
test "parseFullmove rejects zero and invalid values" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(error.InvalidFullmoveNumber, parseFullmove(&state, "0"));
|
||||
try std.testing.expectError(error.InvalidCharacter, parseFullmove(&state, "abc"));
|
||||
}
|
||||
|
||||
test "parseCastleRights parses no castling rights" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try parseCastleRights(&state, "-");
|
||||
try std.testing.expectEqual(@as(u4, 0b0000), state.castle_rights);
|
||||
}
|
||||
|
||||
test "parseCastleRights parses individual and combined rights" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try parseCastleRights(&state, "K");
|
||||
try std.testing.expectEqual(@as(u4, 0b1000), state.castle_rights);
|
||||
|
||||
try parseCastleRights(&state, "Q");
|
||||
try std.testing.expectEqual(@as(u4, 0b0100), state.castle_rights);
|
||||
|
||||
try parseCastleRights(&state, "k");
|
||||
try std.testing.expectEqual(@as(u4, 0b0010), state.castle_rights);
|
||||
|
||||
try parseCastleRights(&state, "q");
|
||||
try std.testing.expectEqual(@as(u4, 0b0001), state.castle_rights);
|
||||
|
||||
try parseCastleRights(&state, "KQkq");
|
||||
try std.testing.expectEqual(@as(u4, 0b1111), state.castle_rights);
|
||||
|
||||
try parseCastleRights(&state, "Kq");
|
||||
try std.testing.expectEqual(@as(u4, 0b1001), state.castle_rights);
|
||||
|
||||
try parseCastleRights(&state, "kq");
|
||||
try std.testing.expectEqual(@as(u4, 0b0011), state.castle_rights);
|
||||
}
|
||||
|
||||
test "parseCastleRights rejects invalid castling rights" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(error.InvalidCastlingRights, parseCastleRights(&state, ""));
|
||||
try std.testing.expectError(error.InvalidCastlingRights, parseCastleRights(&state, "K-"));
|
||||
try std.testing.expectError(error.InvalidCastlingRights, parseCastleRights(&state, "A"));
|
||||
try std.testing.expectError(error.InvalidCastlingRights, parseCastleRights(&state, "KK"));
|
||||
try std.testing.expectError(error.InvalidCastlingRights, parseCastleRights(&state, "qK"));
|
||||
}
|
||||
|
||||
test "parseEnPassant parses no en passant target" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try parseEnPassant(&state, "-");
|
||||
try std.testing.expectEqual(@as(u7, 0), state.en_passant);
|
||||
}
|
||||
|
||||
test "parseEnPassant stores zero when target is not capturable" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
state.turn = .black;
|
||||
try parseEnPassant(&state, "e3");
|
||||
try std.testing.expectEqual(@as(u7, 0), state.en_passant);
|
||||
|
||||
state.turn = .white;
|
||||
try parseEnPassant(&state, "e6");
|
||||
try std.testing.expectEqual(@as(u7, 0), state.en_passant);
|
||||
}
|
||||
|
||||
test "parseEnPassant stores rank 6 target capturable by white pawn" {
|
||||
var state = board.BoardState.empty();
|
||||
state.turn = .white;
|
||||
state.setSquare(3, 4, piece.encode(.white, .pawn)); // d5 can capture e6
|
||||
|
||||
try parseEnPassant(&state, "e6");
|
||||
try std.testing.expectEqual(@as(u7, 108), state.en_passant);
|
||||
}
|
||||
|
||||
test "parseEnPassant stores rank 3 target capturable by black pawn" {
|
||||
var state = board.BoardState.empty();
|
||||
state.turn = .black;
|
||||
state.setSquare(5, 3, piece.encode(.black, .pawn)); // f4 can capture e3
|
||||
|
||||
try parseEnPassant(&state, "e3");
|
||||
try std.testing.expectEqual(@as(u7, 84), state.en_passant);
|
||||
}
|
||||
|
||||
test "parseEnPassant handles capturable edge-file targets" {
|
||||
var white_state = board.BoardState.empty();
|
||||
white_state.turn = .white;
|
||||
white_state.setSquare(1, 4, piece.encode(.white, .pawn)); // b5 can capture a6
|
||||
|
||||
try parseEnPassant(&white_state, "a6");
|
||||
try std.testing.expectEqual(@as(u7, 104), white_state.en_passant);
|
||||
|
||||
var black_state = board.BoardState.empty();
|
||||
black_state.turn = .black;
|
||||
black_state.setSquare(6, 3, piece.encode(.black, .pawn)); // g4 can capture h3
|
||||
|
||||
try parseEnPassant(&black_state, "h3");
|
||||
try std.testing.expectEqual(@as(u7, 87), black_state.en_passant);
|
||||
}
|
||||
|
||||
test "parseEnPassant ignores adjacent pawn of wrong color" {
|
||||
var state = board.BoardState.empty();
|
||||
state.turn = .white;
|
||||
state.setSquare(3, 4, piece.encode(.black, .pawn));
|
||||
|
||||
try parseEnPassant(&state, "e6");
|
||||
try std.testing.expectEqual(@as(u7, 0), state.en_passant);
|
||||
}
|
||||
|
||||
test "parseEnPassant rejects invalid target squares" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(error.InvalidSquare, parseEnPassant(&state, ""));
|
||||
try std.testing.expectError(error.InvalidSquare, parseEnPassant(&state, "e"));
|
||||
try std.testing.expectError(error.InvalidSquare, parseEnPassant(&state, "i3"));
|
||||
try std.testing.expectError(error.InvalidSquare, parseEnPassant(&state, "e9"));
|
||||
}
|
||||
|
||||
test "parseEnPassant rejects target squares outside ranks 3 and 6" {
|
||||
var state = board.BoardState.empty();
|
||||
|
||||
try std.testing.expectError(error.InvalidEnPassantSquare, parseEnPassant(&state, "e2"));
|
||||
try std.testing.expectError(error.InvalidEnPassantSquare, parseEnPassant(&state, "e4"));
|
||||
try std.testing.expectError(error.InvalidEnPassantSquare, parseEnPassant(&state, "e5"));
|
||||
try std.testing.expectError(error.InvalidEnPassantSquare, parseEnPassant(&state, "e7"));
|
||||
}
|
||||
|
||||
test "parseFen parses starting position" {
|
||||
const state = try parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
||||
|
||||
try std.testing.expectEqual(@as(u32, 0xCABEDBAC), state.board[0]);
|
||||
try std.testing.expectEqual(@as(u32, 0x99999999), state.board[1]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[2]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[3]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[4]);
|
||||
try std.testing.expectEqual(@as(u32, 0x00000000), state.board[5]);
|
||||
try std.testing.expectEqual(@as(u32, 0x11111111), state.board[6]);
|
||||
try std.testing.expectEqual(@as(u32, 0x42365324), state.board[7]);
|
||||
try std.testing.expectEqual(piece.Color.white, state.turn);
|
||||
try std.testing.expectEqual(@as(u4, 0b1111), state.castle_rights);
|
||||
try std.testing.expectEqual(@as(u7, 0), state.en_passant);
|
||||
try std.testing.expectEqual(@as(u8, 0), state.halfmove);
|
||||
try std.testing.expectEqual(@as(u32, 1), state.fullmove);
|
||||
}
|
||||
|
||||
test "parseFen parses capturable en passant position" {
|
||||
const state = try parseFen("8/8/8/3Pp3/8/8/8/8 w - e6 0 1");
|
||||
|
||||
try std.testing.expectEqual(piece.Color.white, state.turn);
|
||||
try std.testing.expectEqual(@as(u4, 0), state.castle_rights);
|
||||
try std.testing.expectEqual(@as(u7, 108), state.en_passant);
|
||||
try std.testing.expectEqual(piece.encode(.white, .pawn), state.getSquare(3, 4));
|
||||
try std.testing.expectEqual(piece.encode(.black, .pawn), state.getSquare(4, 4));
|
||||
}
|
||||
|
||||
test "parseFen stores zero for non-capturable en passant target" {
|
||||
const state = try parseFen("r1bqkbnr/pppp1ppp/2n5/4p3/3P4/5N2/PPP2PPP/RNBQKB1R w KQkq e3 4 5");
|
||||
|
||||
try std.testing.expectEqual(piece.Color.white, state.turn);
|
||||
try std.testing.expectEqual(@as(u4, 0b1111), state.castle_rights);
|
||||
try std.testing.expectEqual(@as(u7, 0), state.en_passant);
|
||||
try std.testing.expectEqual(@as(u8, 4), state.halfmove);
|
||||
try std.testing.expectEqual(@as(u32, 5), state.fullmove);
|
||||
try std.testing.expectEqual(piece.encode(.white, .pawn), state.getSquare(3, 3));
|
||||
try std.testing.expectEqual(piece.encode(.black, .pawn), state.getSquare(4, 4));
|
||||
}
|
||||
|
||||
test "parseFen rejects invalid field counts" {
|
||||
try std.testing.expectError(
|
||||
error.InvalidFenFieldCount,
|
||||
parseFen("8/8/8/8/8/8/8/8 w - - 0"),
|
||||
);
|
||||
|
||||
try std.testing.expectError(
|
||||
error.InvalidFenFieldCount,
|
||||
parseFen("8/8/8/8/8/8/8/8 w - - 0 1 extra"),
|
||||
);
|
||||
}
|
||||
|
||||
test "formatFen formats starting position" {
|
||||
const expected = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
const state = try parseFen(expected);
|
||||
|
||||
const actual = try formatFen(std.testing.allocator, state);
|
||||
defer std.testing.allocator.free(actual);
|
||||
|
||||
try std.testing.expectEqualStrings(expected, actual);
|
||||
}
|
||||
|
||||
test "formatFen formats capturable en passant target" {
|
||||
const expected = "8/8/8/3Pp3/8/8/8/8 w - e6 0 1";
|
||||
const state = try parseFen(expected);
|
||||
|
||||
const actual = try formatFen(std.testing.allocator, state);
|
||||
defer std.testing.allocator.free(actual);
|
||||
|
||||
try std.testing.expectEqualStrings(expected, actual);
|
||||
}
|
||||
|
||||
test "formatFen formats non-capturable en passant as dash" {
|
||||
const input = "r1bqkbnr/pppp1ppp/2n5/4p3/3P4/5N2/PPP2PPP/RNBQKB1R w KQkq e3 4 5";
|
||||
const expected = "r1bqkbnr/pppp1ppp/2n5/4p3/3P4/5N2/PPP2PPP/RNBQKB1R w KQkq - 4 5";
|
||||
const state = try parseFen(input);
|
||||
|
||||
const actual = try formatFen(std.testing.allocator, state);
|
||||
defer std.testing.allocator.free(actual);
|
||||
|
||||
try std.testing.expectEqualStrings(expected, actual);
|
||||
}
|
||||
|
||||
test "formatFen formats black turn no castling and larger counters" {
|
||||
var state = board.BoardState.empty();
|
||||
state.turn = .black;
|
||||
state.castle_rights = 0;
|
||||
state.halfmove = 42;
|
||||
state.fullmove = 300;
|
||||
state.setSquare(4, 0, piece.encode(.white, .king));
|
||||
state.setSquare(4, 7, piece.encode(.black, .king));
|
||||
state.setSquare(0, 0, piece.encode(.white, .rook));
|
||||
state.setSquare(7, 7, piece.encode(.black, .rook));
|
||||
|
||||
const actual = try formatFen(std.testing.allocator, state);
|
||||
defer std.testing.allocator.free(actual);
|
||||
|
||||
try std.testing.expectEqualStrings("4k2r/8/8/8/8/8/8/R3K3 b - - 42 300", actual);
|
||||
}
|
||||
Reference in New Issue
Block a user