aboutsummaryrefslogtreecommitdiff
path: root/src/games/minesweeper/Minesweeper.cpp
diff options
context:
space:
mode:
authorfschildt <florian.schildt@protonmail.com>2025-09-28 11:23:04 +0200
committerfschildt <florian.schildt@protonmail.com>2025-09-28 11:23:04 +0200
commit4537e9bee3d054786857fa92824e2d9e8385bb36 (patch)
tree92ee1fea597f8164d09885286b71d812f6890259 /src/games/minesweeper/Minesweeper.cpp
parent3d30e1ee9d1c9fb67cca8e3f178ba5fd05a2726e (diff)
minesweeper: now playable
Diffstat (limited to 'src/games/minesweeper/Minesweeper.cpp')
-rw-r--r--src/games/minesweeper/Minesweeper.cpp387
1 files changed, 236 insertions, 151 deletions
diff --git a/src/games/minesweeper/Minesweeper.cpp b/src/games/minesweeper/Minesweeper.cpp
index abb9515..8f20268 100644
--- a/src/games/minesweeper/Minesweeper.cpp
+++ b/src/games/minesweeper/Minesweeper.cpp
@@ -3,72 +3,94 @@
#include <algorithm>
#include <random>
+#include <cstdio>
-// Todo: draw a difficulty selection menu at top-mid.
+// Todo: winning condition (maybe: total_cells - uncovered_cells = mine_count)
-// Note: How many mines?
-// - Beginner: 8x8 or 9x9 grid with 10 mines.
-// - Intermediate: 16x16 grid with 40 mines.
-// - Expert: 30x16 grid with 99 mines.
+Minesweeper::Minesweeper(RenderGroup& render_group)
+ : m_render_group {render_group}
+{
+ 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');
+ }
+}
-Minesweeper::Minesweeper()
+void
+Minesweeper::Reset(Difficulty difficulty)
{
- float map_width = static_cast<float>(m_MapWidth);
- float map_height = static_cast<float>(m_MapHeight);
- float cell_size = 0.8f * std::min(m_WorldHeight / MAX_MAP_HEIGHT, m_WorldWidth / MAX_MAP_WIDTH);
+ int32_t mine_count;
+ if (difficulty == beginner) {
+ m_grid_width = 8;
+ m_grid_height = 8;
+ mine_count = 10;
+ }
+ else if(difficulty == intermediate) {
+ m_grid_width = 16;
+ m_grid_height = 16;
+ mine_count = 40;
+ }
+ else {
+ m_grid_width = 30;
+ m_grid_height = 16;
+ mine_count = 99;
+ }
+
+
+ float cell_size = 0.8f * std::min(m_world_height / MAX_MAP_HEIGHT, m_world_width / MAX_MAP_WIDTH);
float cell_size_without_border = 0.8f * cell_size;
- m_MapViewPos = {
- 0.1f * cell_size_without_border + (m_WorldWidth - cell_size * map_width) / 2,
- 0.1f * cell_size_without_border + (m_WorldHeight - cell_size * map_height) / 2
+ V2F32 grid_size = {
+ (float)m_grid_width * cell_size,
+ (float)m_grid_height * cell_size
+ };
+ m_grid_pos = {
+ (m_world_width - grid_size.x) / 2,
+ (m_world_height - grid_size.y) / 2,
};
- m_CellOuterViewSize = {cell_size, cell_size};
- m_CellInnerViewSize = {cell_size_without_border, cell_size_without_border};
- m_Font.Init("fonts/dejavu_ttf/DejaVuSansMono.ttf", 32);
- for (uint32_t i = 0; i < m_DigitGlyphs.size(); ++i) {
- m_Font.LoadGlyph(m_DigitGlyphs[i], i + '0');
- }
+ m_cell_outer_size = {cell_size, cell_size};
+ m_cell_inner_size = {cell_size_without_border, cell_size_without_border};
- Reinit();
-}
-
-void Minesweeper::Reinit() {
- int32_t mine_count = 40;
- memset(m_IsCoveredBitmap, 0xff, sizeof(m_IsCoveredBitmap));
- memset(m_IsFlaggedBitmap, 0 , sizeof(m_IsFlaggedBitmap));
+ memset(m_is_covered_bitmap, 0xff, sizeof(m_is_covered_bitmap));
+ memset(m_is_flagged_bitmap, 0 , sizeof(m_is_flagged_bitmap));
InitIsMineBitmap(mine_count);
InitAdjacentMineCounters();
}
-void Minesweeper::InitIsMineBitmap(int32_t mine_count) {
- assert(mine_count < m_MapWidth * m_MapHeight);
+void
+Minesweeper::InitIsMineBitmap(int32_t mine_count)
+{
+ assert(mine_count < m_grid_width * m_grid_height);
- memset(m_IsMineBitmap, 0 , sizeof(m_IsMineBitmap));
+ memset(m_is_mine_bitmap, 0 , sizeof(m_is_mine_bitmap));
std::mt19937 rng((std::random_device()()));
- std::uniform_int_distribution<int32_t> dist(0, m_MapWidth * m_MapHeight - 1);
+ std::uniform_int_distribution<int32_t> dist(0, m_grid_width * m_grid_height - 1);
+
while (mine_count) {
int32_t random_pos = dist(rng);
- int32_t x = random_pos / m_MapWidth;
- int32_t y = random_pos % m_MapWidth;
+ int32_t y = random_pos / m_grid_width;
+ int32_t x = random_pos % m_grid_width;
if (!IsMine(x, y)) {
- m_IsMineBitmap[y] |= 1 << x;
+ m_is_mine_bitmap[y] |= 1 << x;
mine_count--;
}
}
}
-void Minesweeper::InitAdjacentMineCounters() {
- for (int32_t y = 0; y < m_MapHeight; y++) {
+void
+Minesweeper::InitAdjacentMineCounters()
+{
+ for (int32_t y = 0; y < m_grid_height; y++) {
int32_t y0 = y > 0 ? y-1 : y;
- int32_t y1 = y < m_MapHeight-1 ? y+1 : y;
+ int32_t y1 = y < m_grid_height-1 ? y+1 : y;
- for (int32_t x = 0; x < m_MapHeight; x++) {
+ for (int32_t x = 0; x < m_grid_width; x++) {
int32_t x0 = x > 0 ? x-1 : x;
- int32_t x1 = x < m_MapWidth-1 ? x+1 : x;
+ int32_t x1 = x < m_grid_width-1 ? x+1 : x;
int32_t adjacent_mine_counter = 0;
for (int32_t inner_y = y0; inner_y <= y1; inner_y++) {
@@ -82,71 +104,78 @@ void Minesweeper::InitAdjacentMineCounters() {
adjacent_mine_counter = -1;
}
- m_AdjacentMineCounters[y * m_MapWidth + x] = adjacent_mine_counter;
+ m_adjacent_mine_counts[y * m_grid_width + x] = adjacent_mine_counter;
}
}
}
-bool Minesweeper::Update(std::vector<SDL_Event> &events, RenderGroup &render_group) {
+bool
+Minesweeper::Update(std::vector<SDL_Event>& events)
+{
Color clear_color = {0.3f, 0.2f, 0.3f};
- render_group.SetCameraSize(4.0f, 3.0f);
- render_group.Clear(clear_color);
-
- if (m_RunState == MinesweeperRunState::Restart) {
- Reinit();
- m_RunState = MinesweeperRunState::Resume;
- }
+ m_render_group.SetCameraSize(4.0f, 3.0f);
+ m_render_group.Clear(clear_color);
for (SDL_Event &event : events) {
- if (m_RunState == MinesweeperRunState::Exit) {
+ if (m_run_state == exit) {
return false;
}
- else if (m_RunState == MinesweeperRunState::Pause) {
- ProcessEventDuringPause(event, render_group);
+ else if (m_run_state == pause) {
+ ProcessEventDuringPause(event);
}
- else if (m_RunState == MinesweeperRunState::Resume) {
- ProcessEventDuringResume(event, render_group);
+ else if (m_run_state == resume) {
+ ProcessEventDuringResume(event);
}
}
- if (m_RunState == MinesweeperRunState::Pause) {
- DrawPauseMenu(render_group);
+ if (m_run_state == pause) {
+ DrawBoard();
+ DrawPauseMenu();
}
- else if (m_RunState == MinesweeperRunState::GameOver) {
- DrawGameOverMenu(render_group);
+ else if (m_run_state == start_menu) {
+ DrawStartMenu();
+ }
+ else if (m_run_state == resume) {
+ DrawBoard();
+ }
+ else if (m_run_state == game_over) {
+ DrawBoard();
+ DrawGameOverMenu();
}
- DrawBoard(render_group);
-
- bool keep_running = m_RunState != MinesweeperRunState::Exit;
+ bool keep_running = m_run_state != exit;
return keep_running;
}
-void Minesweeper::ProcessEventDuringPause(SDL_Event &event, RenderGroup &render_group) {
+void
+Minesweeper::ProcessEventDuringPause(SDL_Event &event)
+{
switch (event.type) {
case SDL_EVENT_KEY_DOWN: {
if (event.key.key == SDLK_ESCAPE) {
- m_RunState = MinesweeperRunState::Resume;
+ m_run_state = resume;
}
} break;
default:;
}
}
-void Minesweeper::ProcessEventDuringResume(SDL_Event &event, RenderGroup &render_group) {
+void
+Minesweeper::ProcessEventDuringResume(SDL_Event &event)
+{
switch (event.type) {
case SDL_EVENT_KEY_DOWN: {
if (event.key.key == SDLK_ESCAPE) {
- m_RunState = MinesweeperRunState::Pause;
+ m_run_state = pause;
}
} break;
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
- V2F32 click_screen_pos = {event.button.x, (float)render_group.m_ScreenHeight -1 - event.button.y};
- V2F32 click_view_pos = ScreenPosToViewPos(click_screen_pos, render_group);
+ V2F32 click_screen_pos = {event.button.x, (float)m_render_group.m_ScreenHeight -1 - event.button.y};
+ V2F32 click_view_pos = ScreenPosToViewPos(click_screen_pos, m_render_group);
- float x_adjusted = click_view_pos.x - m_MapViewPos.x;
- float y_adjusted = click_view_pos.y - m_MapViewPos.y;
+ float x_adjusted = click_view_pos.x - m_grid_pos.x;
+ float y_adjusted = click_view_pos.y - m_grid_pos.y;
if (x_adjusted < 0.0f) {
break;
}
@@ -154,20 +183,21 @@ void Minesweeper::ProcessEventDuringResume(SDL_Event &event, RenderGroup &render
break;
}
- int32_t x = (int32_t)(x_adjusted / m_CellOuterViewSize.x);
- int32_t y = (int32_t)(y_adjusted / m_CellOuterViewSize.y);
- if (x >= m_MapWidth) {
+ int32_t x = (int32_t)(x_adjusted / m_cell_outer_size.x);
+ int32_t y = (int32_t)(y_adjusted / m_cell_outer_size.y);
+ if (x >= m_grid_width) {
break;
}
- if (y >= m_MapHeight) {
+ if (y >= m_grid_height) {
break;
}
if (event.button.button == 1) {
if (IsCovered(x, y)) {
if (IsMine(x, y)) {
- m_IsCoveredBitmap[y] &= ~(1 << x);
- m_RunState = MinesweeperRunState::GameOver;
+ m_is_covered_bitmap[y] &= ~(1 << x);
+ UncoverMines();
+ m_run_state = game_over;
}
else {
Uncover(x, y);
@@ -186,20 +216,21 @@ void Minesweeper::ProcessEventDuringResume(SDL_Event &event, RenderGroup &render
}
}
-// Todo: maybe find a more efficient non-naive solution
-void Minesweeper::Uncover(int32_t x, int32_t y) {
+void
+Minesweeper::Uncover(int32_t x, int32_t y)
+{
if (x < 0) return;
- if (x >= m_MapWidth) return;
+ if (x >= m_grid_width) return;
if (y < 0) return;
- if (y >= m_MapHeight) return;
+ if (y >= m_grid_height) return;
if (!IsCovered(x, y)) return;
- m_IsCoveredBitmap[y] &= ~(1 << x);
+ m_is_covered_bitmap[y] &= ~(1 << x);
if (IsFlagged(x, y)) {
ToggleFlag(x, y);
}
- if (m_AdjacentMineCounters[y*m_MapWidth + x] > 0) {
+ if (m_adjacent_mine_counts[y*m_grid_width + x] > 0) {
return;
}
Uncover(x-1, y-1);
@@ -214,137 +245,191 @@ void Minesweeper::Uncover(int32_t x, int32_t y) {
Uncover(x+1, y+1);
}
-void Minesweeper::ToggleFlag(int32_t x, int32_t y) {
- m_IsFlaggedBitmap[y] ^= (1 << x);
+void
+Minesweeper::UncoverMines()
+{
+ for (int32_t y{0}; y < m_grid_height; ++y) {
+ for (int32_t x{0}; x < m_grid_width; ++x) {
+ if (IsMine(x, y) && IsCovered(x, y)) {
+ m_is_covered_bitmap[y] &= ~(1 << x);
+ }
+ }
+ }
+}
+
+void
+Minesweeper::ToggleFlag(int32_t x, int32_t y)
+{
+ m_is_flagged_bitmap[y] ^= (1 << x);
}
-bool Minesweeper::IsCovered(int32_t x, int32_t y) {
- bool is_covered = m_IsCoveredBitmap[y] & 1 << x;
+bool
+Minesweeper::IsCovered(int32_t x, int32_t y)
+{
+ bool is_covered = m_is_covered_bitmap[y] & 1 << x;
return is_covered;
}
-bool Minesweeper::IsFlagged(int32_t x, int32_t y) {
- bool is_flagged = m_IsFlaggedBitmap[y] & 1 << x;
+bool
+Minesweeper::IsFlagged(int32_t x, int32_t y)
+{
+ bool is_flagged = m_is_flagged_bitmap[y] & 1 << x;
return is_flagged;
}
-bool Minesweeper::IsMine(int32_t x, int32_t y) {
- bool is_mine = m_IsMineBitmap[y] & 1 << x;
+bool
+Minesweeper::IsMine(int32_t x, int32_t y)
+{
+ bool is_mine = m_is_mine_bitmap[y] & 1 << x;
return is_mine;
}
-V2F32 Minesweeper::ScreenPosToViewPos(V2F32 screen_pos, RenderGroup &render_group) {
+V2F32
+Minesweeper::ScreenPosToViewPos(V2F32 screen_pos, RenderGroup &render_group)
+{
// e.g. [0, 1024] -> [0, 1] -> [0, 4]
// e.g. [0, 768] -> [0, 1] -> [0, 3]
float screen_width = (float)render_group.m_ScreenWidth;
float screen_height = (float)render_group.m_ScreenHeight;
V2F32 view_pos;
- view_pos.x = (screen_pos.x / screen_width) * m_WorldWidth;
- view_pos.y = (screen_pos.y / screen_height) * m_WorldHeight;
+ view_pos.x = (screen_pos.x / screen_width) * m_world_width;
+ view_pos.y = (screen_pos.y / screen_height) * m_world_height;
return view_pos;
}
-void Minesweeper::DrawPauseMenu(RenderGroup &render_group) {
+void
+Minesweeper::DrawPauseMenu()
+{
ImGui::Begin("MinesweeperPause");
if (ImGui::Button("Resume")) {
- m_RunState = MinesweeperRunState::Resume;
+ m_run_state = resume;
+ }
+ if (ImGui::Button("Restart")) {
+ m_run_state = start_menu;
}
if (ImGui::Button("Exit")) {
- m_RunState = MinesweeperRunState::Exit;
+ m_run_state = exit;
}
ImGui::End();
}
-void Minesweeper::DrawGameOverMenu(RenderGroup &render_group) {
- ImGui::Begin("MinesweeperGameOver");
- ImGui::Text("Score = ???");
- if (ImGui::Button("Restart")) {
- m_RunState = MinesweeperRunState::Restart;
+void
+Minesweeper::DrawStartMenu()
+{
+ ImGui::Begin("MinesweeperStartMenu");
+ if (ImGui::RadioButton("beginner", m_difficulty == beginner ? true : false)) {
+ m_difficulty = beginner;
+ }
+ if (ImGui::RadioButton("intermediate", m_difficulty == intermediate ? true : false)) {
+ m_difficulty = intermediate;
+ }
+ if (ImGui::RadioButton("expert", m_difficulty == expert ? true : false)) {
+ m_difficulty = expert;
+ }
+ if (ImGui::Button("Start")) {
+ Reset(m_difficulty);
+ m_run_state = resume;
}
if (ImGui::Button("Exit")) {
- m_RunState = MinesweeperRunState::Exit;
+ m_run_state = exit;
}
ImGui::End();
}
-void Minesweeper::DrawBoard(RenderGroup &render_group) {
- Color covered_cell_color = {0.4f, 0.4f, 0.4f};
- Color uncovered_cell_color = {0.2f, 0.2f, 0.2f};
- Color flag_color = {0.6f, 0.3f, 03.f};
- Color mine_color = {0.8f, 0.2f, 0.2f};
+void
+Minesweeper::DrawGameOverMenu()
+{
+ ImGui::Begin("MinesweeperGameOverMenu");
+ if (ImGui::Button("Play Again")) {
+ m_run_state = start_menu;
+ }
+ if (ImGui::Button("Exit")) {
+ m_run_state = exit;
+ }
+ ImGui::End();
+}
- V2F32 flag_draw_size = {m_CellInnerViewSize.x * 0.5f, m_CellInnerViewSize.y * 0.5f};
- V2F32 flag_draw_offset = {
- (m_CellInnerViewSize.x - flag_draw_size.x) / 2,
- (m_CellInnerViewSize.y - flag_draw_size.y) / 2
+void
+Minesweeper::DrawBoard()
+{
+ Color covered_cell_color {0.6f, 0.6f, 0.6f};
+ Color uncovered_cell_color {0.4f, 0.4f, 0.4f};
+ Color mine_color {0.8f, 0.2f, 0.2f};
+
+ Color flag_color {0.6f, 0.3f, 03.f};
+ V2F32 flag_size = {m_cell_inner_size.x * 0.5f, m_cell_inner_size.y * 0.5f};
+ V2F32 flag_offset = {
+ (m_cell_inner_size.x - flag_size.x) / 2,
+ (m_cell_inner_size.y - flag_size.y) / 2
};
- // Temporary: Drawing Glyph Test
- render_group.PushBitmap(
- {100.0f, 100.0f, 10.0f},
- m_DigitGlyphs[1].bitmap.width,
- m_DigitGlyphs[1].bitmap.height,
- m_DigitGlyphs[1].bitmap.pixels.get());
-
-
- for (int32_t y = 0; y < m_MapHeight; y++) {
- for (int32_t x = 0; x < m_MapWidth; x++) {
- V2F32 world_pos = {
- m_MapViewPos.x + (float)x * m_CellOuterViewSize.x,
- m_MapViewPos.y + (float)y * m_CellOuterViewSize.y,
+ for (int32_t y = 0; y < m_grid_height; y++) {
+ for (int32_t x = 0; x < m_grid_width; x++) {
+ V2F32 cell_pos = {
+ m_grid_pos.x + (float)x * m_cell_outer_size.x,
+ m_grid_pos.y + (float)y * m_cell_outer_size.y,
+ };
+ RectF32 cell_rect = {
+ cell_pos.x, cell_pos.y,
+ cell_pos.x + m_cell_inner_size.x, cell_pos.y + m_cell_inner_size.y
};
+
bool is_covered = IsCovered(x, y);
bool is_flagged = IsFlagged(x, y);
bool is_mine = IsMine(x, y);
if (is_covered) {
- RectF32 cell_world_rect = {
- world_pos.x, world_pos.y,
- world_pos.x + m_CellInnerViewSize.x, world_pos.y + m_CellInnerViewSize.y
- };
- render_group.PushRectangle(cell_world_rect, 0.0f, covered_cell_color);
+ m_render_group.PushRectangle(cell_rect, 0.0f, covered_cell_color);
if (is_flagged) {
- V3F32 flag_world_pos = {
- world_pos.x + flag_draw_offset.x,
- world_pos.y + flag_draw_offset.y,
+ V3F32 flag_pos = {
+ cell_pos.x + flag_offset.x,
+ cell_pos.y + flag_offset.y,
1.0f
};
- RectF32 flag_world_rect = {
- flag_world_pos.x,
- flag_world_pos.y,
- flag_world_pos.x + m_CellInnerViewSize.x,
- flag_world_pos.y + m_CellInnerViewSize.y,
+ RectF32 flag_rect = {
+ flag_pos.x,
+ flag_pos.y,
+ flag_pos.x + flag_size.x,
+ flag_pos.y + flag_size.y,
};
- render_group.PushRectangle(flag_world_rect, flag_world_pos.z, flag_color);
+ m_render_group.PushRectangle(flag_rect, flag_pos.z, flag_color);
}
}
else {
- V3F32 mine_world_pos = {
- world_pos.x,
- world_pos.y,
- 1.0f
- };
if (is_mine) {
- RectF32 mine_world_rect = {
- mine_world_pos.x,
- mine_world_pos.y,
- mine_world_pos.x + m_CellInnerViewSize.x,
- mine_world_pos.y + m_CellInnerViewSize.y,
+ V3F32 mine_pos = {
+ cell_pos.x,
+ cell_pos.y,
+ 1.0f
+ };
+ RectF32 mine_rect = {
+ mine_pos.x,
+ mine_pos.y,
+ mine_pos.x + m_cell_inner_size.x,
+ mine_pos.y + m_cell_inner_size.y,
};
- render_group.PushRectangle(mine_world_rect, mine_world_pos.z, mine_color);
+ m_render_group.PushRectangle(mine_rect, mine_pos.z, mine_color);
}
else {
- RectF32 mine_world_rect = {
- mine_world_pos.x,
- mine_world_pos.y,
- mine_world_pos.x + m_CellInnerViewSize.x,
- mine_world_pos.y + m_CellInnerViewSize.y,
+ m_render_group.PushRectangle(cell_rect, 0.0f, uncovered_cell_color);
+
+ // Todo: Figure out how to scale this properly.
+ // 256.0f is a random number that just works for now.
+ V3F32 mine_count_pos = {
+ 256.0f * cell_pos.x,
+ 256.0f * cell_pos.y,
+ 256.0f
};
- render_group.PushRectangle(mine_world_rect, mine_world_pos.z, uncovered_cell_color);
+ size_t mine_count_val = (size_t)m_adjacent_mine_counts[y*m_grid_width + x];
+ m_render_group.PushBitmap(
+ mine_count_pos,
+ m_digit_glyphs[mine_count_val].bitmap.width,
+ m_digit_glyphs[mine_count_val].bitmap.height,
+ m_digit_glyphs[mine_count_val].bitmap.pixels.get());
}
}
}