#include #include #include #include #include #include // Todo: winning condition (maybe: total_cells - uncovered_cells = mine_count) 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'); } } void Minesweeper::Reset(Difficulty difficulty) { 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 = 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 = { (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_cell_outer_size = {cell_size, cell_size}; m_cell_inner_size = {cell_size_without_border, cell_size_without_border}; 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_grid_width * m_grid_height); memset(m_is_mine_bitmap, 0 , sizeof(m_is_mine_bitmap)); std::mt19937 rng((std::random_device()())); std::uniform_int_distribution dist(0, m_grid_width * m_grid_height - 1); while (mine_count) { int32_t random_pos = dist(rng); int32_t y = random_pos / m_grid_width; int32_t x = random_pos % m_grid_width; if (!IsMine(x, y)) { m_is_mine_bitmap[y] |= 1 << x; mine_count--; } } } 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_grid_height-1 ? y+1 : y; for (int32_t x = 0; x < m_grid_width; x++) { int32_t x0 = x > 0 ? 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++) { for (int32_t inner_x = x0; inner_x <= x1; inner_x++) { if (IsMine(inner_x, inner_y)) { adjacent_mine_counter++; } } } if (IsMine(x, y)) { adjacent_mine_counter = -1; } m_adjacent_mine_counts[y * m_grid_width + x] = adjacent_mine_counter; } } } bool Minesweeper::Update(std::vector& events) { g_renderer.SetCameraSize(4.0f, 3.0f); g_renderer.Clear({0.3f, 0.2f, 0.3f}); for (SDL_Event &event : events) { if (m_run_state == exit) { return false; } else if (m_run_state == pause) { ProcessEventDuringPause(event); } else if (m_run_state == resume) { ProcessEventDuringResume(event); } } if (m_run_state == pause) { DrawBoard(); DrawPauseMenu(); } else if (m_run_state == start_menu) { DrawStartMenu(); } else if (m_run_state == resume) { DrawBoard(); } else if (m_run_state == game_over) { DrawBoard(); DrawGameOverMenu(); } bool keep_running = m_run_state != exit; return keep_running; } void Minesweeper::ProcessEventDuringPause(SDL_Event &event) { switch (event.type) { case SDL_EVENT_KEY_DOWN: { if (event.key.key == SDLK_ESCAPE) { m_run_state = resume; } } break; default:; } } void Minesweeper::ProcessEventDuringResume(SDL_Event &event) { switch (event.type) { case SDL_EVENT_KEY_DOWN: { if (event.key.key == SDLK_ESCAPE) { m_run_state = pause; } } break; case SDL_EVENT_MOUSE_BUTTON_DOWN: { V2F32 click_screen_pos = {event.button.x, (float)g_renderer.m_screen_h-1 - event.button.y}; V2F32 click_view_pos = ScreenPosToViewPos(click_screen_pos); 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; } if (y_adjusted < 0.0f) { break; } 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_grid_height) { break; } if (event.button.button == 1) { if (IsCovered(x, y)) { if (IsMine(x, y)) { m_is_covered_bitmap[y] &= ~(1 << x); UncoverMines(); m_run_state = game_over; } else { Uncover(x, y); } } } else if (event.button.button == 3) { if (IsCovered(x, y)) { ToggleFlag(x ,y); } } } break; default:; } } void Minesweeper::Uncover(int32_t x, int32_t y) { if (x < 0) return; if (x >= m_grid_width) return; if (y < 0) return; if (y >= m_grid_height) return; if (!IsCovered(x, y)) return; m_is_covered_bitmap[y] &= ~(1 << x); if (IsFlagged(x, y)) { ToggleFlag(x, y); } if (m_adjacent_mine_counts[y*m_grid_width + x] > 0) { return; } Uncover(x-1, y-1); Uncover(x , y-1); Uncover(x+1, y-1); Uncover(x-1, y); Uncover(x+1, y); Uncover(x-1, y+1); Uncover(x , y+1); Uncover(x+1, y+1); } 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_is_covered_bitmap[y] & 1 << x; return is_covered; } 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_is_mine_bitmap[y] & 1 << x; return is_mine; } V2F32 Minesweeper::ScreenPosToViewPos(V2F32 screen_pos) { // e.g. [0, 1024] -> [0, 1] -> [0, 4] // e.g. [0, 768] -> [0, 1] -> [0, 3] float screen_width = (float)g_renderer.m_screen_w; float screen_height = (float)g_renderer.m_screen_h; V2F32 view_pos; 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() { ImGui::Begin("MinesweeperPause"); if (ImGui::Button("Resume")) { m_run_state = resume; } if (ImGui::Button("Restart")) { m_run_state = start_menu; } if (ImGui::Button("Exit")) { m_run_state = exit; } ImGui::End(); } 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_run_state = exit; } ImGui::End(); } 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(); } 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 }; 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, }; Rectangle 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) { g_renderer.PushRectangle(cell_rect, 0.0f, covered_cell_color); if (is_flagged) { V3F32 flag_pos = { cell_pos.x + flag_offset.x, cell_pos.y + flag_offset.y, 1.0f }; Rectangle flag_rect = { flag_pos.x, flag_pos.y, flag_pos.x + flag_size.x, flag_pos.y + flag_size.y, }; g_renderer.PushRectangle(flag_rect, flag_pos.z, flag_color); } } else { if (is_mine) { V3F32 mine_pos = { cell_pos.x, cell_pos.y, 1.0f }; Rectangle mine_rect = { mine_pos.x, mine_pos.y, mine_pos.x + m_cell_inner_size.x, mine_pos.y + m_cell_inner_size.y, }; g_renderer.PushRectangle(mine_rect, mine_pos.z, mine_color); } else { g_renderer.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 }; 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, mine_count_pos, Color{0.0f, 0.0f, 0.0f, 0.0f}); } } } } }