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); }