Add chess GUI and move generation
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
const std = @import("std");
|
||||
|
||||
const board = @import("chess/board.zig");
|
||||
const fen = @import("chess/fen.zig");
|
||||
const geometry = @import("geometry.zig");
|
||||
const piece = @import("chess/piece.zig");
|
||||
|
||||
pub const PieceGroup = enum(u4) {
|
||||
white_pawn,
|
||||
white_knight,
|
||||
white_bishop,
|
||||
white_rook,
|
||||
white_queen,
|
||||
white_king,
|
||||
black_pawn,
|
||||
black_knight,
|
||||
black_bishop,
|
||||
black_rook,
|
||||
black_queen,
|
||||
black_king,
|
||||
};
|
||||
|
||||
pub const PieceGroupCount = 12;
|
||||
|
||||
pub const PaletteEntry = struct {
|
||||
group: PieceGroup,
|
||||
encoded: u4,
|
||||
};
|
||||
|
||||
pub const palette_entries = [_]PaletteEntry{
|
||||
.{ .group = .white_king, .encoded = piece.encode(.white, .king) },
|
||||
.{ .group = .white_queen, .encoded = piece.encode(.white, .queen) },
|
||||
.{ .group = .white_rook, .encoded = piece.encode(.white, .rook) },
|
||||
.{ .group = .white_bishop, .encoded = piece.encode(.white, .bishop) },
|
||||
.{ .group = .white_knight, .encoded = piece.encode(.white, .knight) },
|
||||
.{ .group = .white_pawn, .encoded = piece.encode(.white, .pawn) },
|
||||
.{ .group = .black_king, .encoded = piece.encode(.black, .king) },
|
||||
.{ .group = .black_queen, .encoded = piece.encode(.black, .queen) },
|
||||
.{ .group = .black_rook, .encoded = piece.encode(.black, .rook) },
|
||||
.{ .group = .black_bishop, .encoded = piece.encode(.black, .bishop) },
|
||||
.{ .group = .black_knight, .encoded = piece.encode(.black, .knight) },
|
||||
.{ .group = .black_pawn, .encoded = piece.encode(.black, .pawn) },
|
||||
};
|
||||
|
||||
pub fn paletteRectForBoard(board_rect: geometry.BoardRect) geometry.BoardRect {
|
||||
const gap = board_rect.width / 48.0;
|
||||
return .{
|
||||
.left = board_rect.left + board_rect.width + gap,
|
||||
.bottom = board_rect.bottom,
|
||||
.width = board_rect.width / @as(f32, @floatFromInt(palette_entries.len)),
|
||||
.height = board_rect.height,
|
||||
};
|
||||
}
|
||||
|
||||
pub const PieceVertexGroups = struct {
|
||||
groups: [PieceGroupCount]std.ArrayList(geometry.Vertex),
|
||||
|
||||
pub fn init() PieceVertexGroups {
|
||||
return .{
|
||||
.groups = [_]std.ArrayList(geometry.Vertex){.empty} ** PieceGroupCount,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *PieceVertexGroups, allocator: std.mem.Allocator) void {
|
||||
for (&self.groups) |*vertex_group| {
|
||||
vertex_group.deinit(allocator);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group(self: *PieceVertexGroups, piece_group: PieceGroup) *std.ArrayList(geometry.Vertex) {
|
||||
return &self.groups[@intFromEnum(piece_group)];
|
||||
}
|
||||
|
||||
pub fn constGroup(self: *const PieceVertexGroups, piece_group: PieceGroup) *const std.ArrayList(geometry.Vertex) {
|
||||
return &self.groups[@intFromEnum(piece_group)];
|
||||
}
|
||||
};
|
||||
|
||||
pub fn pieceGroupFromEncoded(encoded: u4) ?PieceGroup {
|
||||
if (encoded == 0) return null;
|
||||
|
||||
const color = piece.colorOf(encoded) orelse return null;
|
||||
const piece_type = piece.typeOf(encoded);
|
||||
|
||||
return switch (color) {
|
||||
.white => switch (piece_type) {
|
||||
.pawn => .white_pawn,
|
||||
.knight => .white_knight,
|
||||
.bishop => .white_bishop,
|
||||
.rook => .white_rook,
|
||||
.queen => .white_queen,
|
||||
.king => .white_king,
|
||||
.none => null,
|
||||
},
|
||||
.black => switch (piece_type) {
|
||||
.pawn => .black_pawn,
|
||||
.knight => .black_knight,
|
||||
.bishop => .black_bishop,
|
||||
.rook => .black_rook,
|
||||
.queen => .black_queen,
|
||||
.king => .black_king,
|
||||
.none => null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn appendPieceQuadInRect(
|
||||
vertices: *std.ArrayList(geometry.Vertex),
|
||||
allocator: std.mem.Allocator,
|
||||
rect: geometry.BoardRect,
|
||||
) !void {
|
||||
const inset_x = rect.width * 0.08;
|
||||
const inset_y = rect.height * 0.08;
|
||||
const x0 = rect.left + inset_x;
|
||||
const x1 = rect.left + rect.width - inset_x;
|
||||
const y0 = rect.bottom + inset_y;
|
||||
const y1 = rect.bottom + rect.height - inset_y;
|
||||
|
||||
try geometry.appendTexturedQuad(
|
||||
vertices,
|
||||
allocator,
|
||||
.{ x0, y0 },
|
||||
.{ x1, y0 },
|
||||
.{ x1, y1 },
|
||||
.{ x0, y1 },
|
||||
geometry.White,
|
||||
.{ 0.0, 1.0 },
|
||||
.{ 1.0, 1.0 },
|
||||
.{ 1.0, 0.0 },
|
||||
.{ 0.0, 0.0 },
|
||||
);
|
||||
}
|
||||
|
||||
pub fn appendPiecesGroupedFromBoard(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
state: board.BoardState,
|
||||
) !void {
|
||||
try appendPiecesGroupedFromBoardExcept(groups, board_rect, allocator, state, null);
|
||||
}
|
||||
|
||||
pub fn appendPiecesGroupedFromBoardExcept(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
state: board.BoardState,
|
||||
except_square: ?@import("board_input.zig").SquareCoord,
|
||||
) !void {
|
||||
for (0..8) |rank| {
|
||||
for (0..8) |file| {
|
||||
if (except_square) |except| {
|
||||
if (except.file == file and except.rank == rank) continue;
|
||||
}
|
||||
|
||||
const encoded = state.getSquare(@intCast(file), @intCast(rank));
|
||||
const piece_group = pieceGroupFromEncoded(encoded) orelse continue;
|
||||
|
||||
try geometry.appendPieceQuad(
|
||||
groups.group(piece_group),
|
||||
board_rect,
|
||||
allocator,
|
||||
@as(f32, @floatFromInt(file)),
|
||||
@as(f32, @floatFromInt(rank)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn appendPieceGhostAtSquare(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
encoded: u4,
|
||||
square: @import("board_input.zig").SquareCoord,
|
||||
) !void {
|
||||
const piece_group = pieceGroupFromEncoded(encoded) orelse return;
|
||||
try geometry.appendPieceQuadWithColor(
|
||||
groups.group(piece_group),
|
||||
board_rect,
|
||||
allocator,
|
||||
@floatFromInt(square.file),
|
||||
@floatFromInt(square.rank),
|
||||
.{ 1.0, 1.0, 1.0, 0.35 },
|
||||
);
|
||||
}
|
||||
|
||||
pub fn appendDraggedPiece(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
encoded: u4,
|
||||
center_ndc: [2]f32,
|
||||
) !void {
|
||||
const piece_group = pieceGroupFromEncoded(encoded) orelse return;
|
||||
try geometry.appendPieceQuadCenteredAtNdc(
|
||||
groups.group(piece_group),
|
||||
allocator,
|
||||
center_ndc,
|
||||
.{ board_rect.width / 8.0 * 0.84, board_rect.height / 8.0 * 0.84 },
|
||||
geometry.White,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn appendPalettePiecesGrouped(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
) !void {
|
||||
const palette_rect = paletteRectForBoard(board_rect);
|
||||
const cell_height = palette_rect.height / @as(f32, @floatFromInt(palette_entries.len));
|
||||
|
||||
for (palette_entries, 0..) |entry, i| {
|
||||
const y = palette_rect.bottom + (@as(f32, @floatFromInt(palette_entries.len - 1 - i)) * cell_height);
|
||||
const cell_rect = geometry.BoardRect{
|
||||
.left = palette_rect.left,
|
||||
.bottom = y,
|
||||
.width = palette_rect.width,
|
||||
.height = cell_height,
|
||||
};
|
||||
try appendPieceQuadInRect(groups.group(entry.group), allocator, cell_rect);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paletteIndexForEncoded(encoded: u4) ?usize {
|
||||
for (palette_entries, 0..) |entry, i| {
|
||||
if (entry.encoded == encoded) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn expectGroupVertexCount(groups: *const PieceVertexGroups, piece_group: PieceGroup, expected: usize) !void {
|
||||
try std.testing.expectEqual(expected, groups.constGroup(piece_group).items.len);
|
||||
}
|
||||
|
||||
test "pieceGroupFromEncoded maps encoded pieces to render groups" {
|
||||
try std.testing.expectEqual(PieceGroup.white_pawn, pieceGroupFromEncoded(piece.encode(.white, .pawn)).?);
|
||||
try std.testing.expectEqual(PieceGroup.white_knight, pieceGroupFromEncoded(piece.encode(.white, .knight)).?);
|
||||
try std.testing.expectEqual(PieceGroup.white_bishop, pieceGroupFromEncoded(piece.encode(.white, .bishop)).?);
|
||||
try std.testing.expectEqual(PieceGroup.white_rook, pieceGroupFromEncoded(piece.encode(.white, .rook)).?);
|
||||
try std.testing.expectEqual(PieceGroup.white_queen, pieceGroupFromEncoded(piece.encode(.white, .queen)).?);
|
||||
try std.testing.expectEqual(PieceGroup.white_king, pieceGroupFromEncoded(piece.encode(.white, .king)).?);
|
||||
|
||||
try std.testing.expectEqual(PieceGroup.black_pawn, pieceGroupFromEncoded(piece.encode(.black, .pawn)).?);
|
||||
try std.testing.expectEqual(PieceGroup.black_knight, pieceGroupFromEncoded(piece.encode(.black, .knight)).?);
|
||||
try std.testing.expectEqual(PieceGroup.black_bishop, pieceGroupFromEncoded(piece.encode(.black, .bishop)).?);
|
||||
try std.testing.expectEqual(PieceGroup.black_rook, pieceGroupFromEncoded(piece.encode(.black, .rook)).?);
|
||||
try std.testing.expectEqual(PieceGroup.black_queen, pieceGroupFromEncoded(piece.encode(.black, .queen)).?);
|
||||
try std.testing.expectEqual(PieceGroup.black_king, pieceGroupFromEncoded(piece.encode(.black, .king)).?);
|
||||
|
||||
try std.testing.expectEqual(null, pieceGroupFromEncoded(0));
|
||||
}
|
||||
|
||||
test "appendPiecesGroupedFromBoard leaves all groups empty for empty board" {
|
||||
const state = board.BoardState.empty();
|
||||
const board_rect = geometry.boardRectForExtent(800, 600);
|
||||
|
||||
var groups = PieceVertexGroups.init();
|
||||
defer groups.deinit(std.testing.allocator);
|
||||
|
||||
try appendPiecesGroupedFromBoard(&groups, board_rect, std.testing.allocator, state);
|
||||
|
||||
for (groups.groups) |vertex_group| {
|
||||
try std.testing.expectEqual(@as(usize, 0), vertex_group.items.len);
|
||||
}
|
||||
}
|
||||
|
||||
test "appendPalettePiecesGrouped adds one piece to each piece group" {
|
||||
const board_rect = geometry.boardRectForExtent(800, 600);
|
||||
|
||||
var groups = PieceVertexGroups.init();
|
||||
defer groups.deinit(std.testing.allocator);
|
||||
|
||||
try appendPalettePiecesGrouped(&groups, board_rect, std.testing.allocator);
|
||||
|
||||
for (groups.groups) |vertex_group| {
|
||||
try std.testing.expectEqual(@as(usize, 6), vertex_group.items.len);
|
||||
}
|
||||
}
|
||||
|
||||
test "appendPiecesGroupedFromBoard groups starting position vertices by piece" {
|
||||
const state = try fen.parseFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
||||
const board_rect = geometry.boardRectForExtent(800, 600);
|
||||
|
||||
var groups = PieceVertexGroups.init();
|
||||
defer groups.deinit(std.testing.allocator);
|
||||
|
||||
try appendPiecesGroupedFromBoard(&groups, board_rect, std.testing.allocator, state);
|
||||
|
||||
try expectGroupVertexCount(&groups, .white_pawn, 8 * 6);
|
||||
try expectGroupVertexCount(&groups, .white_knight, 2 * 6);
|
||||
try expectGroupVertexCount(&groups, .white_bishop, 2 * 6);
|
||||
try expectGroupVertexCount(&groups, .white_rook, 2 * 6);
|
||||
try expectGroupVertexCount(&groups, .white_queen, 1 * 6);
|
||||
try expectGroupVertexCount(&groups, .white_king, 1 * 6);
|
||||
|
||||
try expectGroupVertexCount(&groups, .black_pawn, 8 * 6);
|
||||
try expectGroupVertexCount(&groups, .black_knight, 2 * 6);
|
||||
try expectGroupVertexCount(&groups, .black_bishop, 2 * 6);
|
||||
try expectGroupVertexCount(&groups, .black_rook, 2 * 6);
|
||||
try expectGroupVertexCount(&groups, .black_queen, 1 * 6);
|
||||
try expectGroupVertexCount(&groups, .black_king, 1 * 6);
|
||||
|
||||
var total_vertices: usize = 0;
|
||||
for (groups.groups) |vertex_group| {
|
||||
total_vertices += vertex_group.items.len;
|
||||
}
|
||||
try std.testing.expectEqual(@as(usize, 32 * 6), total_vertices);
|
||||
}
|
||||
Reference in New Issue
Block a user