From abb22cda9a82a323fd8f1d077adefd6970a1abaa Mon Sep 17 00:00:00 2001 From: fschildt Date: Mon, 13 Oct 2025 13:59:54 +0200 Subject: minesweeper: draw colored mine counters --- src/common/Font.cpp | 90 ++++++++++++++++++++++++----------- src/common/Font.hpp | 25 +++++++--- src/games/Game.cpp | 4 +- src/games/Game.hpp | 6 +++ src/games/minesweeper/Minesweeper.cpp | 33 +++++++++---- src/games/minesweeper/Minesweeper.hpp | 15 +++--- src/games/tetris/Tetris.cpp | 19 ++++---- src/games/tetris/Tetris.hpp | 15 +++--- src/renderer/RSoftwareBackend.cpp | 47 +++++++++--------- src/renderer/RSoftwareBackend.hpp | 2 +- src/renderer/Renderer.cpp | 4 +- src/renderer/Renderer.hpp | 10 ++-- 12 files changed, 169 insertions(+), 101 deletions(-) (limited to 'src') diff --git a/src/common/Font.cpp b/src/common/Font.cpp index b5b177c..82f6ad8 100644 --- a/src/common/Font.cpp +++ b/src/common/Font.cpp @@ -2,44 +2,40 @@ #include #include +#include -Font::~Font() +static inline bool +is_ch_ascii(char32_t ch) { - Deinit(); + bool result = ch >= ' ' && ch <= '~'; + return result; } +Font::~Font() +{ + delete[] m_file_content; +} bool Font::Init(const char* path, int font_size) { - std::ifstream file(path, std::ios::in|std::ios::binary|std::ios::ate); - if (!file.is_open()) { + if (!ReadFile(path)) { return false; } - std::streampos end = file.tellg(); - size_t size = static_cast(end); - char *content = new char[size + 1]; - - file.seekg(0, std::ios::beg); - file.read(content, end); - file.close(); - content[size] = '\0'; - - // set font info - if (!stbtt_InitFont(&m_font_info, (unsigned char*)content, 0)) + if (!stbtt_InitFont(&m_font_info, (unsigned char*)m_file_content, 0)) { std::cout << "stbtt_InitFont failed.\n"; - printf("stbtt_InitFont failed\n"); - delete[] content; + delete[] m_file_content; + m_file_content = nullptr; return false; } - // font settings (scale + vmetrics) + // set font settings (scale + vmetrics) float scale = stbtt_ScaleForPixelHeight(&m_font_info, (float)font_size); int baseline, ascent, descent, line_gap; stbtt_GetFontVMetrics(&m_font_info, &ascent, &descent, &line_gap); @@ -49,21 +45,48 @@ Font::Init(const char* path, int font_size) line_gap = int(scale * (float)line_gap); + // init members m_font_scale = scale; m_font_baseline = baseline; m_font_yadvance = ascent - descent + line_gap; - m_file_content = content; + + + // load glyphs + for (char c = first_ascii_ch; c <= last_ascii_ch; ++c) { + LoadGlyph(m_glyphs[c-first_ascii_ch], static_cast(c)); + } + memset((void*)&m_fail_glyph, 0, sizeof(m_fail_glyph)); return true; } +bool +Font::ReadFile(const char* path) +{ + std::ifstream file(path, std::ios::in|std::ios::binary|std::ios::ate); + if (!file.is_open()) { + return false; + } + + std::streampos end = file.tellg(); + size_t size = static_cast(end); + char *content = new char[size + 1]; + + file.seekg(0, std::ios::beg); + file.read(content, end); + file.close(); + content[size] = '\0'; + + m_file_content = content; + return m_file_content; +} void -Font::LoadGlyph(Glyph& glyph, uint32_t codepoint) +Font::LoadGlyph(Glyph& glyph, char32_t c) { int bbx0, bby0, bbx1, bby1; - stbtt_GetCodepointBitmapBox(&m_font_info, (int)codepoint, m_font_scale, m_font_scale, &bbx0, &bby0, &bbx1, &bby1); + stbtt_GetCodepointBitmapBox(&m_font_info, (int)c, m_font_scale, m_font_scale, &bbx0, &bby0, &bbx1, &bby1); int width = bbx1 - bbx0; int height = bby1 - bby0; @@ -73,7 +96,7 @@ Font::LoadGlyph(Glyph& glyph, uint32_t codepoint) uint8_t* bitmap_correct = new uint8_t[size]; - stbtt_MakeCodepointBitmap(&m_font_info, bitmap_flipped, width, height, width, m_font_scale, m_font_scale, (int)codepoint); + stbtt_MakeCodepointBitmap(&m_font_info, bitmap_flipped, width, height, width, m_font_scale, m_font_scale, (int)c); uint8_t* dest = bitmap_correct; for (int y = 0; y < height; ++y) @@ -93,7 +116,7 @@ Font::LoadGlyph(Glyph& glyph, uint32_t codepoint) int xadvance; int left_side_bearing; - stbtt_GetCodepointHMetrics(&m_font_info, (int)codepoint, &xadvance, &left_side_bearing); + stbtt_GetCodepointHMetrics(&m_font_info, (int)c, &xadvance, &left_side_bearing); xadvance = (int)(m_font_scale * (float)xadvance); left_side_bearing = (int)(m_font_scale * (float)left_side_bearing); @@ -104,14 +127,23 @@ Font::LoadGlyph(Glyph& glyph, uint32_t codepoint) } - -void -Font::Deinit() +AlphaBitmap& +Font::GetAlphaBitmap(char32_t c) { - if (m_file_content) { - delete[] m_file_content; - m_file_content = nullptr; + if (is_ch_ascii(c)) { + return m_glyphs[c - first_ascii_ch].bitmap; } + + return m_fail_glyph.bitmap; } +Glyph& +Font::GetGlyph(char32_t c) +{ + if (is_ch_ascii(c)) { + return m_glyphs[c - first_ascii_ch]; + } + + return m_fail_glyph; +} diff --git a/src/common/Font.hpp b/src/common/Font.hpp index 91afe06..af4ddb5 100644 --- a/src/common/Font.hpp +++ b/src/common/Font.hpp @@ -1,10 +1,12 @@ #pragma once -#include +#include #include +#include -struct MonoBitmap { + +struct AlphaBitmap { int32_t w; int32_t h; std::unique_ptr pixels; @@ -15,7 +17,7 @@ struct Glyph { int32_t xoff; int32_t yoff; int32_t xadvance; - MonoBitmap bitmap; + AlphaBitmap bitmap; }; @@ -24,12 +26,22 @@ public: ~Font(); bool Init(const char* path, int font_size); - void Deinit(); + AlphaBitmap& GetAlphaBitmap(char32_t c); + Glyph& GetGlyph(char32_t c); + + - void LoadGlyph(Glyph& glyph, uint32_t codepoint); +private: + bool ReadFile(const char* path); + void LoadGlyph(Glyph& glyph, char32_t c); private: + static constexpr char first_ascii_ch = ' '; + static constexpr char last_ascii_ch = '~'; + static constexpr char ascii_glyph_count = last_ascii_ch - first_ascii_ch + 1; + + const char* m_file_content = nullptr; float m_font_scale; @@ -37,7 +49,8 @@ private: int m_font_yadvance; stbtt_fontinfo m_font_info; - Glyph m_glyphs['~' - ' ' + 1]; + Glyph m_glyphs[ascii_glyph_count]; + Glyph m_fail_glyph; }; diff --git a/src/games/Game.cpp b/src/games/Game.cpp index 0520a36..0e6af1c 100644 --- a/src/games/Game.cpp +++ b/src/games/Game.cpp @@ -52,7 +52,7 @@ Game::ProcessDt() void Game::DrawGameOverMenu() { - ImGui::Begin("DefaultGameOverMenu"); + ImGui::Begin("DefaultGameOverMenu", nullptr, s_imgui_window_flags_menu); ImGui::Text("Game Over."); if (ImGui::Button("Play Again")) { m_game_status = game_starting; @@ -66,7 +66,7 @@ Game::DrawGameOverMenu() void Game::DrawGamePausedMenu() { - ImGui::Begin("DefaultGamePauseMenu"); + ImGui::Begin("DefaultGamePauseMenu", nullptr, s_imgui_window_flags_menu); if (ImGui::Button("Resume")) { m_game_status = game_resuming; } diff --git a/src/games/Game.hpp b/src/games/Game.hpp index 94d50cb..7de3b66 100644 --- a/src/games/Game.hpp +++ b/src/games/Game.hpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -42,5 +43,10 @@ protected: GameStatus m_game_status {game_starting}; float m_dt_remaining_seconds {0.0f}; uint64_t m_tlast_milliseconds {SDL_GetTicks()}; + + +protected: + static constexpr ImGuiWindowFlags s_imgui_window_flags_menu = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_AlwaysAutoResize; + static constexpr ImGuiWindowFlags s_imgui_window_flags_default = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoScrollbar; }; diff --git a/src/games/minesweeper/Minesweeper.cpp b/src/games/minesweeper/Minesweeper.cpp index f6b02ac..9b07362 100644 --- a/src/games/minesweeper/Minesweeper.cpp +++ b/src/games/minesweeper/Minesweeper.cpp @@ -8,13 +8,27 @@ #include #include + // Todo: winning condition (maybe: total_cells - uncovered_cells = mine_count) + +static constexpr Color s_mine_count_colors[8] = { + {0.0f, 0.0f, 1.0f, 1.0f}, // Blue + {0.0f, 0.5f, 0.0f, 1.0f}, // Green + {1.0f, 0.0f, 0.0f, 1.0f}, // Red + {0.0f, 0.0f, 0.5f, 1.0f}, // Dark Blue + {0.5f, 0.0f, 0.0f, 1.0f}, // Dark Red + {0.0f, 0.5f, 0.5f, 1.0f}, // Cyan + {0.0f, 0.0f, 0.0f, 1.0f}, // Black + {0.5f, 0.5f, 0.5f, 1.0f}, // Gray +}; + + Minesweeper::Minesweeper() { - m_font.Init("fonts/dejavu_ttf/DejaVuSansMono.ttf", 16); - for (uint32_t i = 0; i < m_digit_glyphs.size(); ++i) { - m_font.LoadGlyph(m_digit_glyphs[i], i + '0'); + bool font_init = m_font.Init(s_font_filepath, 22); + if (!font_init) { + printf("m_font.Init(...) failed\n"); } } @@ -39,7 +53,7 @@ Minesweeper::Reset(Difficulty difficulty) } - float cell_size = 1.2f * std::min(m_world_height / MAX_MAP_HEIGHT, m_world_width / MAX_MAP_WIDTH); + float cell_size = 1.2f * std::min(m_world_height / max_map_height, m_world_width / max_map_width); float cell_size_without_border = 0.8f * cell_size; V2F32 grid_size = { @@ -399,11 +413,14 @@ Minesweeper::DrawBoard() 256.0f * cell_pos.y, 256.0f }; - size_t mine_count_val = (size_t)m_adjacent_mine_counts[y*m_grid_width + x]; - g_renderer.PushMonoBitmap( - m_digit_glyphs[mine_count_val].bitmap, + int32_t mine_count = m_adjacent_mine_counts[y*m_grid_width + x]; + + Color color = s_mine_count_colors[mine_count-1]; + Glyph& glyph = m_font.GetGlyph('0' + (char32_t)mine_count); + g_renderer.PushAlphaBitmap( + glyph.bitmap, mine_count_pos, - Color{0.0f, 0.0f, 0.0f, 0.0f}); + color); } } } diff --git a/src/games/minesweeper/Minesweeper.hpp b/src/games/minesweeper/Minesweeper.hpp index 867ca6a..24672f0 100644 --- a/src/games/minesweeper/Minesweeper.hpp +++ b/src/games/minesweeper/Minesweeper.hpp @@ -48,9 +48,9 @@ private: private: - static constexpr int32_t MAX_MAP_HEIGHT = 32; - static constexpr int32_t MAX_MAP_WIDTH = 32; - static constexpr std::string_view s_FontFilepath = "./fonts/dejavu_ttf/DejaVuSans.ttf"; + static constexpr int32_t max_map_height = 32; + static constexpr int32_t max_map_width = 32; + static constexpr const char* s_font_filepath = "./fonts/dejavu_ttf/DejaVuSans.ttf"; private: @@ -64,13 +64,12 @@ private: V2F32 m_cell_outer_size; V2F32 m_cell_inner_size; - uint32_t m_is_covered_bitmap[MAX_MAP_HEIGHT] {}; - uint32_t m_is_flagged_bitmap[MAX_MAP_HEIGHT] {}; - uint32_t m_is_mine_bitmap[MAX_MAP_HEIGHT] {}; - int32_t m_adjacent_mine_counts[MAX_MAP_WIDTH * MAX_MAP_HEIGHT] {}; + uint32_t m_is_covered_bitmap[max_map_height] {}; + uint32_t m_is_flagged_bitmap[max_map_height] {}; + uint32_t m_is_mine_bitmap[max_map_height] {}; + int32_t m_adjacent_mine_counts[max_map_width * max_map_height] {}; Font m_font; - std::array m_digit_glyphs; }; diff --git a/src/games/tetris/Tetris.cpp b/src/games/tetris/Tetris.cpp index 1e82822..aefdb41 100644 --- a/src/games/tetris/Tetris.cpp +++ b/src/games/tetris/Tetris.cpp @@ -10,7 +10,8 @@ #include Tetris::Tetris() - : m_active_tetromino{m_board.m_bitmap} + : m_font{} + , m_active_tetromino{m_board.m_bitmap} { } @@ -187,7 +188,7 @@ void Tetris::HandleGameOver() { m_game_status = game_over; - const char *filepath = "tetris_highscore.txt"; + const char* filepath = "tetris_highscore.txt"; int32_t highscore = 0; @@ -241,7 +242,7 @@ Tetris::Draw() void Tetris::DrawGameOverMenu() { - ImGui::Begin("TetrisGameOver", nullptr, s_MenuImGuiWindowFlags); + ImGui::Begin("TetrisGameOver", nullptr, s_imgui_window_flags_menu); ImGui::Text("Score = %d", m_score); ImGui::Text("HighScore = %d", m_highscore); if (ImGui::Button("Restart")) { @@ -260,7 +261,7 @@ Tetris::DrawLineCounter() ImVec2 screen_pos = g_renderer.ViewPosToScreenPosImGui(view_pos); ImGui::SetNextWindowPos(screen_pos); - ImGui::Begin("TetrisLines", nullptr, s_DefaultImGuiWindowFlags); + ImGui::Begin("TetrisLines", nullptr, s_imgui_window_flags_default); ImGui::Text("LINES - %d", m_line_counter); ImGui::End(); } @@ -303,13 +304,13 @@ Tetris::DrawStatistics() ImGui::SetNextWindowPos(screen_text_title_pos); - ImGui::Begin("TetrisStatisticsTitle", nullptr, s_DefaultImGuiWindowFlags); + ImGui::Begin("TetrisStatisticsTitle", nullptr, s_imgui_window_flags_default); ImGui::Text("STATISTICS"); ImGui::End(); ImGui::SetNextWindowPos(screen_text_pos); - ImGui::Begin("TetrisStatistics", nullptr, s_DefaultImGuiWindowFlags); + ImGui::Begin("TetrisStatistics", nullptr, s_imgui_window_flags_default); ImGui::Text("%d", m_tetromino_counters[Tetromino::t_piece]); ImGui::Dummy(screen_text_gap); @@ -336,7 +337,7 @@ Tetris::DrawScore() ImVec2 screen_pos = g_renderer.ViewPosToScreenPosImGui(view_pos); ImGui::SetNextWindowPos(screen_pos); - ImGui::Begin("TetrisScore", nullptr, s_DefaultImGuiWindowFlags); + ImGui::Begin("TetrisScore", nullptr, s_imgui_window_flags_default); ImGui::Text("Score"); ImGui::Text("%d", m_score); ImGui::End(); @@ -349,7 +350,7 @@ Tetris::DrawNextTetromino() ImVec2 text_screen_pos = g_renderer.ViewPosToScreenPosImGui(text_view_pos); ImGui::SetNextWindowPos(text_screen_pos); - ImGui::Begin("TetrisNextTetromino", nullptr, s_DefaultImGuiWindowFlags); + ImGui::Begin("TetrisNextTetromino", nullptr, s_imgui_window_flags_default); ImGui::Text("Next"); ImGui::End(); @@ -365,7 +366,7 @@ Tetris::DrawLevel() ImVec2 screen_pos = g_renderer.ViewPosToScreenPosImGui(view_pos); ImGui::SetNextWindowPos(screen_pos); - ImGui::Begin("TetrisLevel", nullptr, s_DefaultImGuiWindowFlags); + ImGui::Begin("TetrisLevel", nullptr, s_imgui_window_flags_default); ImGui::Text("Level"); ImGui::Text("%d", m_level); ImGui::End(); diff --git a/src/games/tetris/Tetris.hpp b/src/games/tetris/Tetris.hpp index 5dd6fde..03966fd 100644 --- a/src/games/tetris/Tetris.hpp +++ b/src/games/tetris/Tetris.hpp @@ -1,23 +1,23 @@ #pragma once -#include #include #include #include +#include class Tetris : public Game { public: Tetris(); - bool Update(std::vector &events) override; - void HandleTetrominoPlacement(); + bool Update(std::vector& events) override; private: void Start(); - void UpdateResumeState(SDL_Event &event); - void UpdatePauseState(SDL_Event &event); + void UpdateResumeState(SDL_Event& event); + void UpdatePauseState(SDL_Event& event); uint32_t GetSoftdropCount(float dt); + void HandleTetrominoPlacement(); void HandleGameOver(); void Draw(); @@ -29,12 +29,9 @@ private: void DrawGameOverMenu(); -private: - static constexpr ImGuiWindowFlags s_MenuImGuiWindowFlags = ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_AlwaysAutoResize; - static constexpr ImGuiWindowFlags s_DefaultImGuiWindowFlags = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoScrollbar; - private: + Font m_font; Board m_board; Tetromino m_active_tetromino; Tetromino::Id m_next_tetromino_id; diff --git a/src/renderer/RSoftwareBackend.cpp b/src/renderer/RSoftwareBackend.cpp index 217354b..7bcdd8f 100644 --- a/src/renderer/RSoftwareBackend.cpp +++ b/src/renderer/RSoftwareBackend.cpp @@ -75,8 +75,8 @@ RSoftwareBackend::Draw() DrawRectangle(entity.rect); } break; - case REntityType_MonoBitmap: { - DrawMonoBitmap(entity.bitmap); + case REntityType_AlphaBitmap: { + DrawAlphaBitmap(entity.bitmap); } break; default:; @@ -146,7 +146,7 @@ RSoftwareBackend::DrawRectangle(REntity_Rectangle& entity) void -RSoftwareBackend::DrawMonoBitmap(REntity_MonoBitmap& entity) +RSoftwareBackend::DrawAlphaBitmap(REntity_AlphaBitmap& entity) { int32_t x0 = (int32_t)entity.pos.x; int32_t y0 = (int32_t)entity.pos.y; @@ -176,34 +176,37 @@ RSoftwareBackend::DrawMonoBitmap(REntity_MonoBitmap& entity) uint32_t gshift = m_canvas.gshift; uint32_t bshift = m_canvas.bshift; - uint8_t* grayscale = (uint8_t*)entity.bitmap.pixels.get() + (-cut_bot * entity.bitmap.w) + (-cut_left); - uint32_t* rgba = m_canvas.pixels + y0 * m_canvas.w + x0; + uint8_t* alpha_row = (uint8_t*)entity.bitmap.pixels.get() + (-cut_bot * entity.bitmap.w) + (-cut_left); + uint32_t* rgba_row = m_canvas.pixels + y0 * m_canvas.w + x0; for (int32_t y = y0; y <= y1; y++) { - uint8_t *grayscale_pixels = grayscale; - uint32_t *rgba_pixels = rgba; + uint8_t* alpha = alpha_row; + uint32_t* rgba = rgba_row; for (int32_t x = x0; x <= x1; x++) { - float alpha = *grayscale_pixels / 255.0f; + if (*alpha == 0) { + alpha++; + rgba++; + continue; + } - // Todo: we do not want to blend with existing color! + float alphaf = *alpha / 255.0f; - uint32_t rgba_result = *rgba_pixels; - float r0 = (rgba_result >> rshift) & 0xff; - float g0 = (rgba_result >> gshift) & 0xff; - float b0 = (rgba_result >> bshift) & 0xff; + float r1 = entity.color.r * alphaf; + float g1 = entity.color.g * alphaf; + float b1 = entity.color.b * alphaf; - float r1 = r0 + (entity.color.r - r0)*alpha; - float g1 = g0 + (entity.color.g - g0)*alpha; - float b1 = b0 + (entity.color.b - b0)*alpha; + uint32_t r2 = uint32_t(r1 * 255.0f) << rshift; + uint32_t g2 = uint32_t(g1 * 255.0f) << gshift; + uint32_t b2 = uint32_t(b1 * 255.0f) << bshift; - rgba_result = (uint32_t)r1 << rshift | (uint32_t)g1 << gshift | (uint32_t)b1 << bshift; - *rgba_pixels = rgba_result; + uint32_t rgba_result = r2 | g2 | b2; + *rgba = rgba_result; - grayscale_pixels++; - rgba_pixels++; + alpha++; + rgba++; } - grayscale += entity.bitmap.w; - rgba += m_canvas.w; + alpha_row += entity.bitmap.w; + rgba_row += m_canvas.w; } } diff --git a/src/renderer/RSoftwareBackend.hpp b/src/renderer/RSoftwareBackend.hpp index 53cca9b..89d6a52 100644 --- a/src/renderer/RSoftwareBackend.hpp +++ b/src/renderer/RSoftwareBackend.hpp @@ -28,7 +28,7 @@ private: void SortRenderEntities(); void DrawRectangle(REntity_Rectangle& entity); - void DrawMonoBitmap(REntity_MonoBitmap& entity); + void DrawAlphaBitmap(REntity_AlphaBitmap& entity); private: diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index f10319a..4e449ba 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -70,10 +70,10 @@ Renderer::Clear(Color color) } void -Renderer::PushMonoBitmap(MonoBitmap& bitmap, V3F32 pos, Color color) +Renderer::PushAlphaBitmap(AlphaBitmap& bitmap, V3F32 pos, Color color) { m_render_entities.emplace_back(REntity{.bitmap{ - REntityType_MonoBitmap, + REntityType_AlphaBitmap, pos, bitmap, color diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index fc037d6..b825681 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -20,14 +20,14 @@ extern Renderer g_renderer; enum REntityType : int32_t { REntityType_Rectangle, - REntityType_MonoBitmap, + REntityType_AlphaBitmap, REntityType_Circle, }; -struct REntity_MonoBitmap { +struct REntity_AlphaBitmap { REntityType type; V3F32 pos; - MonoBitmap& bitmap; + AlphaBitmap& bitmap; Color color; }; @@ -47,7 +47,7 @@ struct REntity_Circle { union REntity { REntityType type; - REntity_MonoBitmap bitmap; + REntity_AlphaBitmap bitmap; REntity_Rectangle rect; REntity_Circle circle; }; @@ -70,7 +70,7 @@ public: void Clear(Color color); void PushRectangle(Rectangle rect, float z, Color color); - void PushMonoBitmap(MonoBitmap& bitmap, V3F32 pos, Color color); + void PushAlphaBitmap(AlphaBitmap& bitmap, V3F32 pos, Color color); void PushCircle(Circle circle, float z, Color color); -- cgit v1.2.3