zig-chess/src/text_render.zig

586 lines
23 KiB
Zig

const std = @import("std");
const board_input = @import("board_input.zig");
const bitboard = @import("chess/bitboard.zig");
const chess_board = @import("chess/board.zig");
const geometry = @import("geometry.zig");
const piece_render = @import("piece_render.zig");
const Glyph = [7]u5;
pub const TextStyle = struct {
pixel_size: f32,
pixel_aspect: f32 = 1.0,
color: [4]f32,
};
fn glyphForChar(ch: u8) ?Glyph {
return switch (ch) {
'0' => .{ 0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110 },
'1' => .{ 0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 },
'2' => .{ 0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111 },
'3' => .{ 0b11110, 0b00001, 0b00001, 0b01110, 0b00001, 0b00001, 0b11110 },
'4' => .{ 0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010 },
'5' => .{ 0b11111, 0b10000, 0b10000, 0b11110, 0b00001, 0b00001, 0b11110 },
'6' => .{ 0b01110, 0b10000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110 },
'7' => .{ 0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000 },
'8' => .{ 0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110 },
'9' => .{ 0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00001, 0b01110 },
'a' => .{ 0b00000, 0b00000, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111 },
'b' => .{ 0b10000, 0b10000, 0b10110, 0b11001, 0b10001, 0b10001, 0b11110 },
'c' => .{ 0b00000, 0b00000, 0b01111, 0b10000, 0b10000, 0b10000, 0b01111 },
'd' => .{ 0b00001, 0b00001, 0b01101, 0b10011, 0b10001, 0b10001, 0b01111 },
'e' => .{ 0b00000, 0b00000, 0b01110, 0b10001, 0b11111, 0b10000, 0b01110 },
'f' => .{ 0b00110, 0b01000, 0b01000, 0b11100, 0b01000, 0b01000, 0b01000 },
'g' => .{ 0b00000, 0b01111, 0b10001, 0b10001, 0b01111, 0b00001, 0b01110 },
'i' => .{ 0b00100, 0b00000, 0b01100, 0b00100, 0b00100, 0b00100, 0b01110 },
'h' => .{ 0b10000, 0b10000, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001 },
'k' => .{ 0b10000, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001 },
'l' => .{ 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110 },
'n' => .{ 0b00000, 0b00000, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001 },
'p' => .{ 0b00000, 0b00000, 0b11110, 0b10001, 0b11110, 0b10000, 0b10000 },
'q' => .{ 0b00000, 0b00000, 0b01101, 0b10011, 0b01111, 0b00001, 0b00001 },
'r' => .{ 0b00000, 0b00000, 0b10110, 0b11001, 0b10000, 0b10000, 0b10000 },
't' => .{ 0b01000, 0b01000, 0b11100, 0b01000, 0b01000, 0b01001, 0b00110 },
'w' => .{ 0b00000, 0b00000, 0b10001, 0b10001, 0b10101, 0b10101, 0b01010 },
'y' => .{ 0b00000, 0b10001, 0b10001, 0b01111, 0b00001, 0b00001, 0b01110 },
'A' => .{ 0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001 },
'B' => .{ 0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110 },
'D' => .{ 0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110 },
'E' => .{ 0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111 },
'I' => .{ 0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b11111 },
'K' => .{ 0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001 },
'L' => .{ 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111 },
'N' => .{ 0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001, 0b10001 },
'P' => .{ 0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000 },
'Q' => .{ 0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101 },
'R' => .{ 0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001 },
'S' => .{ 0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110 },
'T' => .{ 0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100 },
'Y' => .{ 0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100 },
'#' => .{ 0b01010, 0b01010, 0b11111, 0b01010, 0b11111, 0b01010, 0b01010 },
'-' => .{ 0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000 },
'/' => .{ 0b00001, 0b00010, 0b00010, 0b00100, 0b01000, 0b01000, 0b10000 },
else => null,
};
}
fn appendGlyph(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
glyph: Glyph,
x: f32,
y: f32,
style: TextStyle,
) !void {
for (glyph, 0..) |row_bits, row| {
for (0..5) |col| {
const shift: u3 = @intCast(4 - col);
if (((row_bits >> shift) & 1) == 0) continue;
const pixel_width = style.pixel_size * style.pixel_aspect;
const x0 = x + (@as(f32, @floatFromInt(col)) * pixel_width);
const y0 = y + (@as(f32, @floatFromInt(6 - row)) * style.pixel_size);
const x1 = x0 + pixel_width;
const y1 = y0 + style.pixel_size;
try geometry.appendQuad(
vertices,
allocator,
.{ x0, y0 },
.{ x1, y0 },
.{ x1, y1 },
.{ x0, y1 },
style.color,
);
}
}
}
pub fn appendText(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
text: []const u8,
x: f32,
y: f32,
style: TextStyle,
) !void {
var cursor_x = x;
const advance = style.pixel_size * style.pixel_aspect * 6.0;
for (text) |ch| {
if (ch == ' ') {
cursor_x += advance;
continue;
}
if (glyphForChar(ch)) |glyph| {
try appendGlyph(vertices, allocator, glyph, cursor_x, y, style);
}
cursor_x += advance;
}
}
const SelectedHighlightColor = [4]f32{ 0.12, 0.55, 0.12, 0.48 };
const HoverHighlightColor = [4]f32{ 0.45, 0.90, 0.45, 0.38 };
fn appendSquareOverlay(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
square: board_input.SquareCoord,
color: [4]f32,
) !void {
const file: f32 = @floatFromInt(square.file);
const rank: f32 = @floatFromInt(square.rank);
try geometry.appendQuad(
vertices,
allocator,
geometry.boardToNdc(board_rect, file, rank),
geometry.boardToNdc(board_rect, file + 1.0, rank),
geometry.boardToNdc(board_rect, file + 1.0, rank + 1.0),
geometry.boardToNdc(board_rect, file, rank + 1.0),
color,
);
}
pub fn appendSelectedSquareHighlight(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
selected: ?board_input.SquareCoord,
) !void {
const square = selected orelse return;
try appendSquareOverlay(vertices, allocator, board_rect, square, SelectedHighlightColor);
}
pub fn appendPalettePieceHighlight(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
selected_piece: ?u4,
) !void {
const encoded = selected_piece orelse return;
const index = piece_render.paletteIndexForEncoded(encoded) orelse return;
const palette_rect = piece_render.paletteRectForBoard(board_rect);
const cell_height = palette_rect.height / @as(f32, @floatFromInt(piece_render.palette_entries.len));
const y = palette_rect.bottom + (@as(f32, @floatFromInt(piece_render.palette_entries.len - 1 - index)) * cell_height);
try geometry.appendQuad(
vertices,
allocator,
.{ palette_rect.left, y },
.{ palette_rect.left + palette_rect.width, y },
.{ palette_rect.left + palette_rect.width, y + cell_height },
.{ palette_rect.left, y + cell_height },
.{ 0.45, 0.90, 0.45, 0.38 },
);
}
pub fn appendHoveredSquareHighlight(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
hovered: ?board_input.SquareCoord,
) !void {
const square = hovered orelse return;
try appendSquareOverlay(vertices, allocator, board_rect, square, HoverHighlightColor);
}
pub fn appendCheckBorder(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
checked: ?board_input.SquareCoord,
) !void {
const square = checked orelse return;
const file: f32 = @floatFromInt(square.file);
const rank: f32 = @floatFromInt(square.rank);
const thickness: f32 = 0.08;
const color = [4]f32{ 1.0, 0.05, 0.05, 0.85 };
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, file, rank), geometry.boardToNdc(board_rect, file + 1.0, rank), geometry.boardToNdc(board_rect, file + 1.0, rank + thickness), geometry.boardToNdc(board_rect, file, rank + thickness), color);
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, file, rank + 1.0 - thickness), geometry.boardToNdc(board_rect, file + 1.0, rank + 1.0 - thickness), geometry.boardToNdc(board_rect, file + 1.0, rank + 1.0), geometry.boardToNdc(board_rect, file, rank + 1.0), color);
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, file, rank), geometry.boardToNdc(board_rect, file + thickness, rank), geometry.boardToNdc(board_rect, file + thickness, rank + 1.0), geometry.boardToNdc(board_rect, file, rank + 1.0), color);
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, file + 1.0 - thickness, rank), geometry.boardToNdc(board_rect, file + 1.0, rank), geometry.boardToNdc(board_rect, file + 1.0, rank + 1.0), geometry.boardToNdc(board_rect, file + 1.0 - thickness, rank + 1.0), color);
}
pub fn appendCheckmateMarker(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
winning_king: ?board_input.SquareCoord,
) !void {
const square = winning_king orelse return;
const square_w = board_rect.width / 8.0;
const square_h = board_rect.height / 8.0;
const pixel_size = @min(square_w, square_h) * 0.045;
const glyph_w = pixel_size * 5.0;
const glyph_h = pixel_size * 7.0;
const padding_x = square_w * 0.10;
const padding_y = square_h * 0.10;
const top_right = geometry.boardToNdc(
board_rect,
@as(f32, @floatFromInt(square.file)) + 1.0,
@as(f32, @floatFromInt(square.rank)) + 1.0,
);
try appendText(
vertices,
allocator,
"#",
top_right[0] - padding_x - glyph_w,
top_right[1] - padding_y - glyph_h,
.{ .pixel_size = pixel_size, .color = .{ 1.0, 0.92, 0.18, 1.0 } },
);
}
pub fn appendValidMoveDots(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
state: chess_board.BoardState,
valid_moves: bitboard.Bitboard,
) !void {
const square_w = board_rect.width / 8.0;
const square_h = board_rect.height / 8.0;
const radius_x = square_w * 0.11;
const radius_y = square_h * 0.11;
const color = SelectedHighlightColor;
const segments = 48;
var moves = valid_moves;
while (moves != 0) {
const move_square: bitboard.Square = @intCast(@ctz(moves));
moves &= moves - 1;
const file: u3 = @intCast(move_square % 8);
const rank: u3 = @intCast(move_square / 8);
if (state.getSquare(@intCast((@as(u6, rank) * 8) + @as(u6, file))) != 0) {
const x0 = @as(f32, @floatFromInt(file));
const x1 = x0 + 1.0;
const y0 = @as(f32, @floatFromInt(rank));
const y1 = y0 + 1.0;
const thickness: f32 = 0.07;
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, x0, y0), geometry.boardToNdc(board_rect, x1, y0), geometry.boardToNdc(board_rect, x1, y0 + thickness), geometry.boardToNdc(board_rect, x0, y0 + thickness), color);
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, x0, y1 - thickness), geometry.boardToNdc(board_rect, x1, y1 - thickness), geometry.boardToNdc(board_rect, x1, y1), geometry.boardToNdc(board_rect, x0, y1), color);
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, x0, y0), geometry.boardToNdc(board_rect, x0 + thickness, y0), geometry.boardToNdc(board_rect, x0 + thickness, y1), geometry.boardToNdc(board_rect, x0, y1), color);
try geometry.appendQuad(vertices, allocator, geometry.boardToNdc(board_rect, x1 - thickness, y0), geometry.boardToNdc(board_rect, x1, y0), geometry.boardToNdc(board_rect, x1, y1), geometry.boardToNdc(board_rect, x1 - thickness, y1), color);
} else {
const center = geometry.boardToNdc(
board_rect,
@as(f32, @floatFromInt(file)) + 0.5,
@as(f32, @floatFromInt(rank)) + 0.5,
);
var i: usize = 0;
while (i < segments) : (i += 1) {
const angle0 = (@as(f32, @floatFromInt(i)) / @as(f32, @floatFromInt(segments))) * std.math.tau;
const angle1 = (@as(f32, @floatFromInt(i + 1)) / @as(f32, @floatFromInt(segments))) * std.math.tau;
const p0 = [2]f32{ center[0] + (@cos(angle0) * radius_x), center[1] + (@sin(angle0) * radius_y) };
const p1 = [2]f32{ center[0] + (@cos(angle1) * radius_x), center[1] + (@sin(angle1) * radius_y) };
try vertices.append(allocator, .{ .position = center, .color = color, .uv = .{ 0.0, 0.0 } });
try vertices.append(allocator, .{ .position = p0, .color = color, .uv = .{ 0.0, 0.0 } });
try vertices.append(allocator, .{ .position = p1, .color = color, .uv = .{ 0.0, 0.0 } });
}
}
}
}
fn appendRectBorder(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
rect: geometry.BoardRect,
thickness: f32,
color: [4]f32,
) !void {
const x0 = rect.left;
const x1 = rect.left + rect.width;
const y0 = rect.bottom;
const y1 = rect.bottom + rect.height;
try geometry.appendQuad(vertices, allocator, .{ x0, y0 }, .{ x1, y0 }, .{ x1, y0 + thickness }, .{ x0, y0 + thickness }, color);
try geometry.appendQuad(vertices, allocator, .{ x0, y1 - thickness }, .{ x1, y1 - thickness }, .{ x1, y1 }, .{ x0, y1 }, color);
try geometry.appendQuad(vertices, allocator, .{ x0, y0 }, .{ x0 + thickness, y0 }, .{ x0 + thickness, y1 }, .{ x0, y1 }, color);
try geometry.appendQuad(vertices, allocator, .{ x1 - thickness, y0 }, .{ x1, y0 }, .{ x1, y1 }, .{ x1 - thickness, y1 }, color);
}
pub fn appendPromotionPopup(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
square: bitboard.Square,
) !void {
const popup_rect = piece_render.promotionPopupRectForSquare(board_rect, square);
try geometry.appendQuad(
vertices,
allocator,
.{ popup_rect.left, popup_rect.bottom },
.{ popup_rect.left + popup_rect.width, popup_rect.bottom },
.{ popup_rect.left + popup_rect.width, popup_rect.bottom + popup_rect.height },
.{ popup_rect.left, popup_rect.bottom + popup_rect.height },
.{ 0.04, 0.04, 0.04, 0.98 },
);
for (piece_render.promotion_piece_types, 0..) |_, i| {
const cell = piece_render.promotionChoiceRectForIndex(board_rect, square, i);
const border_color = geometry.White;
const thickness = board_rect.width / 220.0;
try appendRectBorder(vertices, allocator, cell, thickness, border_color);
}
}
pub fn appendModeMenu(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
edit_mode: bool,
) !void {
const button_w: f32 = 0.16;
const button_h: f32 = 0.08;
const reset_button_w: f32 = 0.22;
const gap: f32 = 0.025;
const top: f32 = 0.96;
const bottom = top - button_h;
const play_left: f32 = -0.95;
const edit_left = play_left + button_w + gap;
const reset_left = edit_left + button_w + gap;
const inactive = [4]f32{ 0.18, 0.18, 0.18, 1.0 };
const active = [4]f32{ 0.25, 0.45, 0.25, 1.0 };
const reset_color = [4]f32{ 0.25, 0.25, 0.35, 1.0 };
try geometry.appendQuad(
vertices,
allocator,
.{ play_left, bottom },
.{ play_left + button_w, bottom },
.{ play_left + button_w, top },
.{ play_left, top },
if (edit_mode) inactive else active,
);
try geometry.appendQuad(
vertices,
allocator,
.{ edit_left, bottom },
.{ edit_left + button_w, bottom },
.{ edit_left + button_w, top },
.{ edit_left, top },
if (edit_mode) active else inactive,
);
try geometry.appendQuad(
vertices,
allocator,
.{ reset_left, bottom },
.{ reset_left + reset_button_w, bottom },
.{ reset_left + reset_button_w, top },
.{ reset_left, top },
reset_color,
);
const pixel_size: f32 = button_h / 13.0;
const y = bottom + (button_h - pixel_size * 7.0) / 2.0;
try appendText(vertices, allocator, "PLAY", play_left + 0.018, y, .{ .pixel_size = pixel_size, .color = geometry.White });
try appendText(vertices, allocator, "EDIT", edit_left + 0.018, y, .{ .pixel_size = pixel_size, .color = geometry.White });
try appendText(vertices, allocator, "RESET", reset_left + 0.018, y, .{ .pixel_size = pixel_size, .color = geometry.White });
}
pub fn appendBoardCoordinateLabels(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
) !void {
const square_w = board_rect.width / 8.0;
const square_h = board_rect.height / 8.0;
const square_size = if (square_w < square_h) square_w else square_h;
const pixel_size = square_size / 36.0;
const padding = square_size / 24.0;
const glyph_w = pixel_size * 5.0;
const glyph_h = pixel_size * 7.0;
const files = "abcdefgh";
for (files, 0..) |file_ch, file| {
const is_dark = (file % 2) == 0;
const color = if (is_dark) geometry.Light else geometry.Dark;
const x = board_rect.left + (@as(f32, @floatFromInt(file + 1)) * square_w) - padding - glyph_w;
const y = board_rect.bottom + padding;
try appendText(vertices, allocator, files[file .. file + 1], x, y, .{
.pixel_size = pixel_size,
.color = color,
});
_ = file_ch;
}
const ranks = "12345678";
for (ranks, 0..) |rank_ch, rank| {
const is_dark = (rank % 2) == 0;
const color = if (is_dark) geometry.Light else geometry.Dark;
const x = board_rect.left + padding;
const y = board_rect.bottom + (@as(f32, @floatFromInt(rank + 1)) * square_h) - padding - glyph_h;
try appendText(vertices, allocator, ranks[rank .. rank + 1], x, y, .{
.pixel_size = pixel_size,
.color = color,
});
_ = rank_ch;
}
}
pub fn appendFenText(
vertices: *std.ArrayList(geometry.Vertex),
allocator: std.mem.Allocator,
board_rect: geometry.BoardRect,
fen_text: []const u8,
) !void {
const max_text_width = board_rect.width;
const pixel_aspect = board_rect.width / board_rect.height;
const natural_pixel_size = board_rect.height / 95.0;
const fit_pixel_size = max_text_width / (@as(f32, @floatFromInt(fen_text.len)) * 6.0 * pixel_aspect);
const pixel_size = if (fit_pixel_size < natural_pixel_size) fit_pixel_size else natural_pixel_size;
const gap = board_rect.height / 18.0;
const y = board_rect.bottom - gap - (pixel_size * 7.0);
try appendText(vertices, allocator, fen_text, board_rect.left, y, .{
.pixel_size = pixel_size,
.pixel_aspect = pixel_aspect,
.color = geometry.White,
});
}
test "appendText appends vertices for supported glyph pixels" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendText(&vertices, std.testing.allocator, "1", 0.0, 0.0, .{
.pixel_size = 0.01,
.color = geometry.White,
});
try std.testing.expect(vertices.items.len > 0);
try std.testing.expectEqual(@as(usize, 0), vertices.items.len % 6);
}
test "appendSelectedSquareHighlight appends one quad when selected" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendSelectedSquareHighlight(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
.{ .file = 4, .rank = 1 },
);
try std.testing.expectEqual(@as(usize, 6), vertices.items.len);
}
test "appendSelectedSquareHighlight appends nothing without selection" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendSelectedSquareHighlight(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
null,
);
try std.testing.expectEqual(@as(usize, 0), vertices.items.len);
}
test "appendHoveredSquareHighlight appends one quad when hovered" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendHoveredSquareHighlight(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
.{ .file = 2, .rank = 2 },
);
try std.testing.expectEqual(@as(usize, 6), vertices.items.len);
}
test "appendCheckBorder appends four border quads when checked" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendCheckBorder(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
.{ .file = 4, .rank = 0 },
);
try std.testing.expectEqual(@as(usize, 4 * 6), vertices.items.len);
}
test "appendCheckBorder appends nothing without checked square" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendCheckBorder(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
null,
);
try std.testing.expectEqual(@as(usize, 0), vertices.items.len);
}
test "appendCheckmateMarker appends hash glyph when winner is present" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendCheckmateMarker(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
.{ .file = 5, .rank = 5 },
);
try std.testing.expect(vertices.items.len > 0);
}
test "appendCheckmateMarker appends nothing without winner" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
try appendCheckmateMarker(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
null,
);
try std.testing.expectEqual(@as(usize, 0), vertices.items.len);
}
test "appendValidMoveDots appends one circular triangle fan per move" {
var vertices: std.ArrayList(geometry.Vertex) = .empty;
defer vertices.deinit(std.testing.allocator);
const state = chess_board.BoardState.empty();
const moves = bitboard.bit(20) | bitboard.bit(28);
try appendValidMoveDots(
&vertices,
std.testing.allocator,
geometry.boardRectForExtent(800, 600),
state,
moves,
);
try std.testing.expectEqual(@as(usize, 2 * 48 * 3), vertices.items.len);
}