Add promotion selection and square APIs
This commit is contained in:
+186
-18
@@ -2,10 +2,11 @@ const std = @import("std");
|
||||
|
||||
const board = @import("chess/board.zig");
|
||||
const fen = @import("chess/fen.zig");
|
||||
const bitboard = @import("chess/bitboard.zig");
|
||||
const geometry = @import("geometry.zig");
|
||||
const piece = @import("chess/piece.zig");
|
||||
|
||||
pub const PieceGroup = enum(u4) {
|
||||
pub const PieceGroup = enum(u5) {
|
||||
white_pawn,
|
||||
white_knight,
|
||||
white_bishop,
|
||||
@@ -18,15 +19,29 @@ pub const PieceGroup = enum(u4) {
|
||||
black_rook,
|
||||
black_queen,
|
||||
black_king,
|
||||
dragged_white_pawn,
|
||||
dragged_white_knight,
|
||||
dragged_white_bishop,
|
||||
dragged_white_rook,
|
||||
dragged_white_queen,
|
||||
dragged_white_king,
|
||||
dragged_black_pawn,
|
||||
dragged_black_knight,
|
||||
dragged_black_bishop,
|
||||
dragged_black_rook,
|
||||
dragged_black_queen,
|
||||
dragged_black_king,
|
||||
};
|
||||
|
||||
pub const PieceGroupCount = 12;
|
||||
pub const PieceGroupCount = 24;
|
||||
|
||||
pub const PaletteEntry = struct {
|
||||
group: PieceGroup,
|
||||
encoded: u4,
|
||||
};
|
||||
|
||||
pub const promotion_piece_types = [_]piece.PieceType{ .queen, .rook, .bishop, .knight };
|
||||
|
||||
pub const palette_entries = [_]PaletteEntry{
|
||||
.{ .group = .white_king, .encoded = piece.encode(.white, .king) },
|
||||
.{ .group = .white_queen, .encoded = piece.encode(.white, .queen) },
|
||||
@@ -52,6 +67,40 @@ pub fn paletteRectForBoard(board_rect: geometry.BoardRect) geometry.BoardRect {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn promotionPopupRectForSquare(board_rect: geometry.BoardRect, square: bitboard.Square) geometry.BoardRect {
|
||||
const cell_width = (board_rect.width / 8.0) * 0.65;
|
||||
const cell_height = (board_rect.height / 8.0) * 0.65;
|
||||
const width = cell_width * @as(f32, @floatFromInt(promotion_piece_types.len));
|
||||
const height = cell_height;
|
||||
const center = geometry.boardToNdc(
|
||||
board_rect,
|
||||
@as(f32, @floatFromInt(square % 8)) + 0.5,
|
||||
@as(f32, @floatFromInt(square / 8)) + 0.5,
|
||||
);
|
||||
const board_right = board_rect.left + board_rect.width;
|
||||
const board_top = board_rect.bottom + board_rect.height;
|
||||
const unclamped_left = center[0] - (width / 2.0);
|
||||
const unclamped_bottom = center[1] - (height / 2.0);
|
||||
|
||||
return .{
|
||||
.left = @max(board_rect.left, @min(unclamped_left, board_right - width)),
|
||||
.bottom = @max(board_rect.bottom, @min(unclamped_bottom, board_top - height)),
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn promotionChoiceRectForIndex(board_rect: geometry.BoardRect, square: bitboard.Square, index: usize) geometry.BoardRect {
|
||||
const popup_rect = promotionPopupRectForSquare(board_rect, square);
|
||||
const cell_width = popup_rect.width / @as(f32, @floatFromInt(promotion_piece_types.len));
|
||||
return .{
|
||||
.left = popup_rect.left + (@as(f32, @floatFromInt(index)) * cell_width),
|
||||
.bottom = popup_rect.bottom,
|
||||
.width = cell_width,
|
||||
.height = popup_rect.height,
|
||||
};
|
||||
}
|
||||
|
||||
pub const PieceVertexGroups = struct {
|
||||
groups: [PieceGroupCount]std.ArrayList(geometry.Vertex),
|
||||
|
||||
@@ -77,6 +126,14 @@ pub const PieceVertexGroups = struct {
|
||||
};
|
||||
|
||||
pub fn pieceGroupFromEncoded(encoded: u4) ?PieceGroup {
|
||||
return pieceGroupFromEncodedWithOffset(encoded, false);
|
||||
}
|
||||
|
||||
fn draggedPieceGroupFromEncoded(encoded: u4) ?PieceGroup {
|
||||
return pieceGroupFromEncodedWithOffset(encoded, true);
|
||||
}
|
||||
|
||||
fn pieceGroupFromEncodedWithOffset(encoded: u4, dragged: bool) ?PieceGroup {
|
||||
if (encoded == 0) return null;
|
||||
|
||||
const color = piece.colorOf(encoded) orelse return null;
|
||||
@@ -84,26 +141,54 @@ pub fn pieceGroupFromEncoded(encoded: u4) ?PieceGroup {
|
||||
|
||||
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,
|
||||
.pawn => if (dragged) .dragged_white_pawn else .white_pawn,
|
||||
.knight => if (dragged) .dragged_white_knight else .white_knight,
|
||||
.bishop => if (dragged) .dragged_white_bishop else .white_bishop,
|
||||
.rook => if (dragged) .dragged_white_rook else .white_rook,
|
||||
.queen => if (dragged) .dragged_white_queen else .white_queen,
|
||||
.king => if (dragged) .dragged_white_king else .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,
|
||||
.pawn => if (dragged) .dragged_black_pawn else .black_pawn,
|
||||
.knight => if (dragged) .dragged_black_knight else .black_knight,
|
||||
.bishop => if (dragged) .dragged_black_bishop else .black_bishop,
|
||||
.rook => if (dragged) .dragged_black_rook else .black_rook,
|
||||
.queen => if (dragged) .dragged_black_queen else .black_queen,
|
||||
.king => if (dragged) .dragged_black_king else .black_king,
|
||||
.none => null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn appendSidewaysPieceQuad(
|
||||
vertices: *std.ArrayList(geometry.Vertex),
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
file: f32,
|
||||
rank: f32,
|
||||
) !void {
|
||||
const inset: f32 = 0.08;
|
||||
const x0 = file + inset;
|
||||
const x1 = file + 1.0 - inset;
|
||||
const y0 = rank + inset;
|
||||
const y1 = rank + 1.0 - inset;
|
||||
|
||||
try geometry.appendTexturedQuad(
|
||||
vertices,
|
||||
allocator,
|
||||
geometry.boardToNdc(board_rect, x1, y0),
|
||||
geometry.boardToNdc(board_rect, x1, y1),
|
||||
geometry.boardToNdc(board_rect, x0, y1),
|
||||
geometry.boardToNdc(board_rect, x0, y0),
|
||||
geometry.White,
|
||||
.{ 0.0, 1.0 },
|
||||
.{ 1.0, 1.0 },
|
||||
.{ 1.0, 0.0 },
|
||||
.{ 0.0, 0.0 },
|
||||
);
|
||||
}
|
||||
|
||||
fn appendPieceQuadInRect(
|
||||
vertices: *std.ArrayList(geometry.Vertex),
|
||||
allocator: std.mem.Allocator,
|
||||
@@ -146,6 +231,17 @@ pub fn appendPiecesGroupedFromBoardExcept(
|
||||
allocator: std.mem.Allocator,
|
||||
state: board.BoardState,
|
||||
except_square: ?@import("board_input.zig").SquareCoord,
|
||||
) !void {
|
||||
try appendPiecesGroupedFromBoardExceptWithGameOver(groups, board_rect, allocator, state, except_square, null);
|
||||
}
|
||||
|
||||
pub fn appendPiecesGroupedFromBoardExceptWithGameOver(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
state: board.BoardState,
|
||||
except_square: ?@import("board_input.zig").SquareCoord,
|
||||
losing_king_square: ?@import("board_input.zig").SquareCoord,
|
||||
) !void {
|
||||
for (0..8) |rank| {
|
||||
for (0..8) |file| {
|
||||
@@ -153,9 +249,22 @@ pub fn appendPiecesGroupedFromBoardExcept(
|
||||
if (except.file == file and except.rank == rank) continue;
|
||||
}
|
||||
|
||||
const encoded = state.getSquare(@intCast(file), @intCast(rank));
|
||||
const encoded = state.getSquare(@intCast((rank * 8) + file));
|
||||
const piece_group = pieceGroupFromEncoded(encoded) orelse continue;
|
||||
|
||||
if (losing_king_square) |losing| {
|
||||
if (losing.file == file and losing.rank == rank) {
|
||||
try appendSidewaysPieceQuad(
|
||||
groups.group(piece_group),
|
||||
board_rect,
|
||||
allocator,
|
||||
@as(f32, @floatFromInt(file)),
|
||||
@as(f32, @floatFromInt(rank)),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
try geometry.appendPieceQuad(
|
||||
groups.group(piece_group),
|
||||
board_rect,
|
||||
@@ -163,6 +272,7 @@ pub fn appendPiecesGroupedFromBoardExcept(
|
||||
@as(f32, @floatFromInt(file)),
|
||||
@as(f32, @floatFromInt(rank)),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,7 +302,7 @@ pub fn appendDraggedPiece(
|
||||
encoded: u4,
|
||||
center_ndc: [2]f32,
|
||||
) !void {
|
||||
const piece_group = pieceGroupFromEncoded(encoded) orelse return;
|
||||
const piece_group = draggedPieceGroupFromEncoded(encoded) orelse return;
|
||||
try geometry.appendPieceQuadCenteredAtNdc(
|
||||
groups.group(piece_group),
|
||||
allocator,
|
||||
@@ -222,6 +332,27 @@ pub fn appendPalettePiecesGrouped(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn appendPromotionPiecesGrouped(
|
||||
groups: *PieceVertexGroups,
|
||||
board_rect: geometry.BoardRect,
|
||||
allocator: std.mem.Allocator,
|
||||
color: piece.Color,
|
||||
square: bitboard.Square,
|
||||
) !void {
|
||||
for (promotion_piece_types, 0..) |piece_type, i| {
|
||||
const encoded = piece.encode(color, piece_type);
|
||||
const piece_group = pieceGroupFromEncoded(encoded) orelse continue;
|
||||
const cell_rect = promotionChoiceRectForIndex(board_rect, square, i);
|
||||
const piece_rect = geometry.BoardRect{
|
||||
.left = cell_rect.left + (cell_rect.width * 0.25),
|
||||
.bottom = cell_rect.bottom + (cell_rect.height * 0.25),
|
||||
.width = cell_rect.width * 0.5,
|
||||
.height = cell_rect.height * 0.5,
|
||||
};
|
||||
try appendPieceQuadInRect(groups.group(piece_group), allocator, piece_rect);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paletteIndexForEncoded(encoded: u4) ?usize {
|
||||
for (palette_entries, 0..) |entry, i| {
|
||||
if (entry.encoded == encoded) return i;
|
||||
@@ -273,11 +404,48 @@ test "appendPalettePiecesGrouped adds one piece to each piece group" {
|
||||
|
||||
try appendPalettePiecesGrouped(&groups, board_rect, std.testing.allocator);
|
||||
|
||||
for (groups.groups) |vertex_group| {
|
||||
try std.testing.expectEqual(@as(usize, 6), vertex_group.items.len);
|
||||
for (groups.groups, 0..) |vertex_group, i| {
|
||||
const group: PieceGroup = @enumFromInt(@as(u5, @intCast(i)));
|
||||
const is_dragged_group = @intFromEnum(group) >= @intFromEnum(PieceGroup.dragged_white_pawn);
|
||||
const expected: usize = if (is_dragged_group) 0 else 6;
|
||||
try std.testing.expectEqual(expected, vertex_group.items.len);
|
||||
}
|
||||
}
|
||||
|
||||
test "appendDraggedPiece uses dragged overlay group" {
|
||||
const board_rect = geometry.boardRectForExtent(800, 600);
|
||||
|
||||
var groups = PieceVertexGroups.init();
|
||||
defer groups.deinit(std.testing.allocator);
|
||||
|
||||
try appendDraggedPiece(&groups, board_rect, std.testing.allocator, piece.encode(.white, .queen), .{ 0.0, 0.0 });
|
||||
|
||||
try expectGroupVertexCount(&groups, .white_queen, 0);
|
||||
try expectGroupVertexCount(&groups, .dragged_white_queen, 6);
|
||||
}
|
||||
|
||||
test "appendPiecesGroupedFromBoardExceptWithGameOver renders sideways losing king" {
|
||||
var state = board.BoardState.empty();
|
||||
state.setSquare(63, piece.encode(.black, .king));
|
||||
state.setSquare(45, piece.encode(.white, .king));
|
||||
const board_rect = geometry.boardRectForExtent(800, 600);
|
||||
|
||||
var groups = PieceVertexGroups.init();
|
||||
defer groups.deinit(std.testing.allocator);
|
||||
|
||||
try appendPiecesGroupedFromBoardExceptWithGameOver(
|
||||
&groups,
|
||||
board_rect,
|
||||
std.testing.allocator,
|
||||
state,
|
||||
null,
|
||||
.{ .file = 7, .rank = 7 },
|
||||
);
|
||||
|
||||
try expectGroupVertexCount(&groups, .white_king, 6);
|
||||
try expectGroupVertexCount(&groups, .black_king, 6);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user