From 893f99f2e9f1aeee1e0564ab82f4d0693b1e4bcf Mon Sep 17 00:00:00 2001 From: Zilong Date: Tue, 8 Sep 2026 01:19:17 -0500 Subject: [PATCH 1/3] Use PNG sprites for chess pieces --- ocean/chess/chess.h | 169 +++++++++++++------------------------ resources/chess/pieces.png | Bin 0 -> 32672 bytes 2 files changed, 60 insertions(+), 109 deletions(-) create mode 100644 resources/chess/pieces.png diff --git a/ocean/chess/chess.h b/ocean/chess/chess.h index 7672b86fb6..ba6ba90ffb 100644 --- a/ocean/chess/chess.h +++ b/ocean/chess/chess.h @@ -152,14 +152,6 @@ static const char* PIECE_CHARS[] = { "p", "n", "b", "r", "q", "k" }; -static const char* PIECE_FILLED[] = { - "", - "♟", "♞", "♝", "♜", "♛", "♚", - "", "", - "♟", "♞", "♝", "♜", "♛", "♚" -}; - - static uint64_t prng_state = 1070372; static inline uint64_t prng_rand(void) { prng_state ^= prng_state >> 12; @@ -420,9 +412,8 @@ struct Log { }; typedef struct { - int cell_size; - Font piece_font; - int use_unicode_pieces; + Texture2D pieces; + Rectangle piece_bounds[16]; } Client; typedef struct { @@ -2821,44 +2812,23 @@ void puf_step(Chess* env) { populate_observations(env); } -static Font load_piece_font(int cell_size, int* loaded) { - const char* candidates[] = { - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/truetype/noto/NotoSansSymbols2-Regular.ttf", - "/System/Library/Fonts/Supplemental/Apple Symbols.ttf", - "C:\\Windows\\Fonts\\seguisym.ttf" - }; - - int codepoints[] = {0x2654, 0x2655, 0x2656, 0x2657, 0x2658, 0x2659, 0x265A, 0x265B, 0x265C, 0x265D, 0x265E, 0x265F}; - Font font = (Font){0}; - size_t candidate_count = sizeof(candidates) / sizeof(candidates[0]); - size_t codepoint_count = sizeof(codepoints) / sizeof(codepoints[0]); - - for (size_t i = 0; i < candidate_count; i++) { - if (!FileExists(candidates[i])) { - continue; - } - font = LoadFontEx(candidates[i], cell_size, codepoints, (int)codepoint_count); - if (font.texture.id != 0) { - if (loaded) { - *loaded = 1; - } - SetTextureFilter(font.texture, TEXTURE_FILTER_BILINEAR); - return font; - } - } - - if (loaded) { - *loaded = 0; - } - return GetFontDefault(); -} - -static void draw_piece(Chess* env, Piece pc, int file, int rank, int cell_size) { +static void draw_piece_icon(Chess* env, Piece pc, Rectangle box) { if (pc == NO_PIECE) { return; } + if (env->client && env->client->pieces.id != 0) { + Rectangle source = env->client->piece_bounds[pc]; + float scale = box.width / (env->client->pieces.width / 6.0f); + Rectangle dest = { + box.x + (box.width - source.width * scale) / 2.0f, + box.y + (box.height - source.height * scale) / 2.0f, + source.width * scale, source.height * scale + }; + DrawTexturePro(env->client->pieces, source, dest, (Vector2){0}, 0, WHITE); + return; + } + Color pc_color = color_of(pc) == CHESS_WHITE ? (Color){255, 255, 255, 255} : (Color){0, 0, 0, 255}; @@ -2867,37 +2837,22 @@ static void draw_piece(Chess* env, Piece pc, int file, int rank, int cell_size) ? (Color){0, 0, 0, 220} : (Color){255, 255, 255, 180}; - int draw_x = file * cell_size; - int draw_y = (7 - rank) * cell_size; - - if (env->client && env->client->use_unicode_pieces) { - float icon_size = cell_size * 0.85f; - Vector2 pos = (Vector2){ - draw_x + (cell_size - icon_size) / 2.0f, - draw_y + (cell_size - icon_size) / 2.0f - cell_size * 0.05f - }; - const char* str = PIECE_FILLED[pc]; - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - if (dx != 0 || dy != 0) { - Vector2 opos = (Vector2){pos.x + dx, pos.y + dy}; - DrawTextEx(env->client->piece_font, str, opos, icon_size, 0, outline); - } + int size = (int)(box.height / 2); + int x = (int)(box.x + (box.width - MeasureText(PIECE_CHARS[pc], size)) / 2); + int y = (int)(box.y + (box.height - size) / 2); + for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + if (dx != 0 || dy != 0) { + DrawText(PIECE_CHARS[pc], x + dx, y + dy, size, outline); } } - DrawTextEx(env->client->piece_font, str, pos, icon_size, 0, pc_color); - } else { - int x = draw_x + cell_size / 4; - int y = draw_y + cell_size / 8; - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - if (dx != 0 || dy != 0) { - DrawText(PIECE_CHARS[pc], x + dx, y + dy, cell_size / 2, outline); - } - } - } - DrawText(PIECE_CHARS[pc], x, y, cell_size / 2, pc_color); } + DrawText(PIECE_CHARS[pc], x, y, size, pc_color); +} + +static void draw_piece(Chess* env, Piece pc, int file, int rank, int cell_size) { + draw_piece_icon(env, pc, (Rectangle){file * cell_size, (7 - rank) * cell_size, + cell_size, cell_size}); } static void init_chess_client(Chess* env, int cell_size) { @@ -2910,10 +2865,30 @@ static void init_chess_client(Chess* env, int cell_size) { SetTargetFPS(env->render_fps > 0 ? env->render_fps : 30); #endif env->client = (Client*)calloc(1, sizeof(Client)); - env->client->cell_size = cell_size; - int font_loaded = 0; - env->client->piece_font = load_piece_font(cell_size, &font_loaded); - env->client->use_unicode_pieces = font_loaded; + // pieces.png: unchanged Jurgenwesterhof/Cburnett artwork, CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/). + // Source: https://commons.wikimedia.org/wiki/File:Chess_Pieces_Sprite.svg + Image image = LoadImage("resources/chess/pieces.png"); + if (image.data && image.width % 6 == 0 && image.height == image.width / 3) { + env->client->pieces = LoadTextureFromImage(image); + if (env->client->pieces.id != 0) { + SetTextureFilter(env->client->pieces, TEXTURE_FILTER_BILINEAR); + // Columns: king, queen, bishop, knight, rook, pawn; white above black. + const int columns[] = {0, 5, 3, 2, 4, 1, 0}; + int tile = image.width / 6; + for (int color = CHESS_WHITE; color <= CHESS_BLACK; color++) { + for (int pt = PAWN; pt <= KING; pt++) { + Rectangle cell = {columns[pt] * tile, color * tile, tile, tile}; + Image piece = ImageFromImage(image, cell); + Rectangle bounds = GetImageAlphaBorder(piece, 0.0f); + UnloadImage(piece); + bounds.x += cell.x; + bounds.y += cell.y; + env->client->piece_bounds[make_piece(color, pt)] = bounds; + } + } + } + } + if (image.data) UnloadImage(image); } void puf_render(Chess* env) { @@ -3170,14 +3145,9 @@ void puf_render(Chess* env) { int wc = env->white_captured[pt]; if (wc > 0) { Piece wpc = (Piece)(W_PAWN + pt); - if (env->client && env->client->use_unicode_pieces) { - DrawTextEx(env->client->piece_font, PIECE_FILLED[wpc], - (Vector2){(float)white_x, (float)(cap_y - 1)}, 16.0f, 0.0f, white_cap_color); - white_x += 16; - } else { - DrawText(PIECE_CHARS[wpc], white_x, cap_y, 14, white_cap_color); - white_x += 12; - } + DrawRectangle(white_x, cap_y, 16, 16, LIGHTGRAY); + draw_piece_icon(env, wpc, (Rectangle){white_x, cap_y, 16, 16}); + white_x += 16; if (wc > 1) { char mult[8]; snprintf(mult, sizeof(mult), "x%d", wc); @@ -3192,28 +3162,9 @@ void puf_render(Chess* env) { if (bc > 0) { Piece bpc = (Piece)(B_PAWN + pt); Color outline = (Color){255, 255, 255, 180}; - if (env->client && env->client->use_unicode_pieces) { - Vector2 pos = {(float)black_x, (float)(cap_y + 17)}; - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - if (dx != 0 || dy != 0) { - DrawTextEx(env->client->piece_font, PIECE_FILLED[bpc], - (Vector2){pos.x + dx, pos.y + dy}, 16.0f, 0.0f, outline); - } - } - } - DrawTextEx(env->client->piece_font, PIECE_FILLED[bpc], pos, 16.0f, 0.0f, black_cap_color); - black_x += 16; - } else { - for (int dx = -1; dx <= 1; dx++) { - for (int dy = -1; dy <= 1; dy++) { - if (dx != 0 || dy != 0) - DrawText(PIECE_CHARS[bpc], black_x + dx, cap_y + 18 + dy, 14, outline); - } - } - DrawText(PIECE_CHARS[bpc], black_x, cap_y + 18, 14, black_cap_color); - black_x += 12; - } + DrawRectangle(black_x, cap_y + 18, 16, 16, LIGHTGRAY); + draw_piece_icon(env, bpc, (Rectangle){black_x, cap_y + 18, 16, 16}); + black_x += 16; if (bc > 1) { char mult[8]; snprintf(mult, sizeof(mult), "x%d", bc); @@ -3325,8 +3276,8 @@ void puf_render(Chess* env) { void puf_close(Chess* env) { if (env->client != NULL) { - if (env->client->use_unicode_pieces && env->client->piece_font.texture.id != 0) { - UnloadFont(env->client->piece_font); + if (env->client->pieces.id != 0) { + UnloadTexture(env->client->pieces); } if (IsWindowReady()) { CloseWindow(); diff --git a/resources/chess/pieces.png b/resources/chess/pieces.png new file mode 100644 index 0000000000000000000000000000000000000000..f84d3fabfb8b6b74bd16e557ab3e53f68ce0e91e GIT binary patch literal 32672 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 literal 0 HcmV?d00001 From 784bc08defee4d2fca106391b439680e622bcc84 Mon Sep 17 00:00:00 2001 From: Zilong Date: Tue, 8 Sep 2026 10:15:32 -0500 Subject: [PATCH 2/3] Preserve captured-piece letter fallback styling --- ocean/chess/chess.h | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/ocean/chess/chess.h b/ocean/chess/chess.h index ba6ba90ffb..c0074a60ad 100644 --- a/ocean/chess/chess.h +++ b/ocean/chess/chess.h @@ -2812,7 +2812,7 @@ void puf_step(Chess* env) { populate_observations(env); } -static void draw_piece_icon(Chess* env, Piece pc, Rectangle box) { +static void draw_piece_icon(Chess* env, Piece pc, Rectangle box, int size, Color color) { if (pc == NO_PIECE) { return; } @@ -2829,15 +2829,10 @@ static void draw_piece_icon(Chess* env, Piece pc, Rectangle box) { return; } - Color pc_color = color_of(pc) == CHESS_WHITE - ? (Color){255, 255, 255, 255} - : (Color){0, 0, 0, 255}; - Color outline = (color_of(pc) == CHESS_WHITE) ? (Color){0, 0, 0, 220} : (Color){255, 255, 255, 180}; - int size = (int)(box.height / 2); int x = (int)(box.x + (box.width - MeasureText(PIECE_CHARS[pc], size)) / 2); int y = (int)(box.y + (box.height - size) / 2); for (int dx = -1; dx <= 1; dx++) { @@ -2847,12 +2842,13 @@ static void draw_piece_icon(Chess* env, Piece pc, Rectangle box) { } } } - DrawText(PIECE_CHARS[pc], x, y, size, pc_color); + DrawText(PIECE_CHARS[pc], x, y, size, color); } static void draw_piece(Chess* env, Piece pc, int file, int rank, int cell_size) { + Color color = color_of(pc) == CHESS_WHITE ? WHITE : (Color){0, 0, 0, 255}; draw_piece_icon(env, pc, (Rectangle){file * cell_size, (7 - rank) * cell_size, - cell_size, cell_size}); + cell_size, cell_size}, cell_size / 2, color); } static void init_chess_client(Chess* env, int cell_size) { @@ -3146,7 +3142,7 @@ void puf_render(Chess* env) { if (wc > 0) { Piece wpc = (Piece)(W_PAWN + pt); DrawRectangle(white_x, cap_y, 16, 16, LIGHTGRAY); - draw_piece_icon(env, wpc, (Rectangle){white_x, cap_y, 16, 16}); + draw_piece_icon(env, wpc, (Rectangle){white_x, cap_y, 16, 16}, 14, white_cap_color); white_x += 16; if (wc > 1) { char mult[8]; @@ -3163,7 +3159,7 @@ void puf_render(Chess* env) { Piece bpc = (Piece)(B_PAWN + pt); Color outline = (Color){255, 255, 255, 180}; DrawRectangle(black_x, cap_y + 18, 16, 16, LIGHTGRAY); - draw_piece_icon(env, bpc, (Rectangle){black_x, cap_y + 18, 16, 16}); + draw_piece_icon(env, bpc, (Rectangle){black_x, cap_y + 18, 16, 16}, 14, black_cap_color); black_x += 16; if (bc > 1) { char mult[8]; From 53d82bf7c303b397518a72c9bdae36ece8708dfc Mon Sep 17 00:00:00 2001 From: Zilong Date: Tue, 8 Sep 2026 12:05:41 -0500 Subject: [PATCH 3/3] Clarify chess artwork attribution --- ocean/chess/chess.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ocean/chess/chess.h b/ocean/chess/chess.h index c0074a60ad..00b8c7b783 100644 --- a/ocean/chess/chess.h +++ b/ocean/chess/chess.h @@ -2861,7 +2861,8 @@ static void init_chess_client(Chess* env, int cell_size) { SetTargetFPS(env->render_fps > 0 ? env->render_fps : 30); #endif env->client = (Client*)calloc(1, sizeof(Client)); - // pieces.png: unchanged Jurgenwesterhof/Cburnett artwork, CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/). + // pieces.png: "Chess Pieces Sprite" by Jurgenwesterhof, adapted from Cburnett; unmodified PNG reproduction. + // Artwork license: CC BY-SA 3.0, https://creativecommons.org/licenses/by-sa/3.0/ // Source: https://commons.wikimedia.org/wiki/File:Chess_Pieces_Sprite.svg Image image = LoadImage("resources/chess/pieces.png"); if (image.data && image.width % 6 == 0 && image.height == image.width / 3) {