#include #include #include #include #include #include #include #include Tetris::Tetris() : m_active_tetromino{m_board.m_bitmap} { } void Tetris::Start() { m_game_status = game_resuming; m_tlast_milliseconds = SDL_GetTicks(); m_dt_remaining_seconds = 0.0f; m_board.Reset(); m_active_tetromino.Reset(Tetromino::GenerateRandomId()); m_next_tetromino_id = Tetromino::GenerateRandomId(); memset(m_tetromino_counters, 0, sizeof(m_tetromino_counters)); m_tetromino_counters[m_active_tetromino.GetId()] += 1; m_score = 0; m_line_counter = 0; m_starting_level = 0; m_level = 0; m_softdrop_counter = 0; } bool Tetris::Update(std::vector& events) { Color clear_color = {0.2f, 0.2f, 0.2f, 1.0f}; g_renderer.SetCameraSize(4.0f, 3.0f); g_renderer.Clear(clear_color); if (m_game_status == game_starting) { Start(); } float dt = ProcessDt(); if (m_game_status == game_resuming) { uint32_t softdrop_count = GetSoftdropCount(dt); // side-effect: m_dt_remaining_seconds for (uint32_t i{0}; i < softdrop_count; i++) { bool moved_down = m_active_tetromino.MaybeMoveDown(); if (!moved_down) { HandleTetrominoPlacement(); } } } for (auto& event : events) { switch (m_game_status) { case game_resuming: UpdateResumeState(event); break; case game_paused: UpdatePauseState(event); break; default:; } } if (m_game_status == game_exit) { return false; } Draw(); return true; } void Tetris::UpdateResumeState(SDL_Event& event) { switch (event.type) { case SDL_EVENT_KEY_DOWN: { auto key = event.key.key; if (key == SDLK_RIGHT) { m_active_tetromino.MaybeMoveHorizontally(Tetromino::right); } else if (key == SDLK_LEFT) { m_active_tetromino.MaybeMoveHorizontally(Tetromino::left); } else if (key == SDLK_DOWN) { bool moved_down = m_active_tetromino.MaybeMoveDown(); if (!moved_down) { HandleTetrominoPlacement(); } else { m_softdrop_counter++; } } else if (key == SDLK_X) { m_active_tetromino.MaybeRotate(Tetromino::rotate_clockwise); } else if (key == SDLK_Z || key == SDLK_Y) { m_active_tetromino.MaybeRotate(Tetromino::rotate_counter_clockwise); } else if (key == SDLK_ESCAPE) { m_game_status = game_paused; } } default:; } } void Tetris::UpdatePauseState(SDL_Event& event) { switch (event.type) { case SDL_EVENT_KEY_DOWN: { auto key = event.key.key; if (key == SDLK_ESCAPE) { m_game_status = game_resuming; } } default:; } } void Tetris::HandleTetrominoPlacement() { int32_t rows_cleared = m_board.PlaceTetromino(m_active_tetromino); m_active_tetromino.Reset(m_next_tetromino_id); m_next_tetromino_id = Tetromino::GenerateRandomId(); if (m_active_tetromino.IsCollisionWithBoard()) { HandleGameOver(); return; } m_line_counter += rows_cleared; m_tetromino_counters[m_active_tetromino.GetId()] += 1; if (rows_cleared == 1) { m_score += 40 * (m_level + 1); } else if (rows_cleared == 2) { m_score += 100 * (m_level + 1); } else if (rows_cleared == 3) { m_score += 300 * (m_level + 1); } else if (rows_cleared == 4) { m_score += 1200 * (m_level + 1); } m_score += m_softdrop_counter; m_softdrop_counter = 0; m_level = m_starting_level + m_line_counter / 10; } uint32_t Tetris::GetSoftdropCount(float dt) { float nes_frame_time = 1.0f / 60; int32_t nes_frames_per_cell; if (m_level <= 8) nes_frames_per_cell = 48 - m_level * 5; else if (m_level == 9) nes_frames_per_cell = 6; else if (m_level <= 12) nes_frames_per_cell = 5; else if (m_level <= 15) nes_frames_per_cell = 4; else if (m_level <= 18) nes_frames_per_cell = 3; else if (m_level <= 28) nes_frames_per_cell = 2; else nes_frames_per_cell = 1; float dt_level = static_cast(nes_frames_per_cell) * nes_frame_time; uint32_t softdrop_count = 0; while (dt > dt_level) { softdrop_count += 1; dt -= dt_level; } m_dt_remaining_seconds = dt; return softdrop_count; } void Tetris::HandleGameOver() { m_game_status = game_over; const char *filepath = "tetris_highscore.txt"; int32_t highscore = 0; std::ifstream highscore_file_in { filepath }; if (highscore_file_in) { highscore_file_in >> highscore; highscore_file_in.close(); } else { SDL_LogInfo(0, "Tetris: cannot open tetris_highscore.txt for reading"); } if (highscore > 0 && highscore > m_highscore) { m_highscore = highscore; } if (m_score > m_highscore) { m_highscore = m_score; std::ofstream highscore_file_out { filepath }; if (highscore_file_out) { highscore_file_out << m_highscore << std::endl; highscore_file_out.close(); } else { SDL_LogInfo(0, "Tetris: cannot open tetris_highscore.txt for writing"); } } } void Tetris::Draw() { m_board.Draw(m_level); m_active_tetromino.Draw(); DrawNextTetromino(); DrawStatistics(); DrawLineCounter(); DrawLevel(); DrawScore(); if (m_game_status == game_paused) { DrawGamePausedMenu(); } else if (m_game_status == game_over) { DrawGameOverMenu(); } } void Tetris::DrawGameOverMenu() { ImGui::Begin("TetrisGameOver", nullptr, s_MenuImGuiWindowFlags); ImGui::Text("Score = %d", m_score); ImGui::Text("HighScore = %d", m_highscore); if (ImGui::Button("Restart")) { m_game_status = game_starting; } if (ImGui::Button("Exit")) { m_game_status = game_exit; } ImGui::End(); } void Tetris::DrawLineCounter() { V2F32 view_pos = {0.5f, 2.6f}; ImVec2 screen_pos = g_renderer.ViewPosToScreenPosImGui(view_pos); ImGui::SetNextWindowPos(screen_pos); ImGui::Begin("TetrisLines", nullptr, s_DefaultImGuiWindowFlags); ImGui::Text("LINES - %d", m_line_counter); ImGui::End(); } void Tetris::DrawStatistics() { V2F32 view_tetrominoes_pos = {0.4f, 1.8f}; V2F32 view_advance = {0.0f, 0.2f}; V2F32 view_text_title_pos = view_tetrominoes_pos + V2F32(0.02f, 0.4f); ImVec2 screen_text_title_pos = g_renderer.ViewPosToScreenPosImGui(view_text_title_pos); V2F32 view_text_pos = view_tetrominoes_pos + V2F32(0.4f, 0.16f); V2F32 view_text_gap = {0.0f, 0.124f}; ImVec2 screen_text_pos = g_renderer.ViewPosToScreenPosImGui(view_text_pos); ImVec2 screen_text_gap = g_renderer.ViewSizeToScreenSizeImGui(view_text_gap); Tetromino::Draw(Tetromino::t_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; Tetromino::Draw(Tetromino::j_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; Tetromino::Draw(Tetromino::z_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; Tetromino::Draw(Tetromino::o_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; Tetromino::Draw(Tetromino::s_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; Tetromino::Draw(Tetromino::l_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; Tetromino::Draw(Tetromino::i_piece, 0, view_tetrominoes_pos, 0.5f); view_tetrominoes_pos.y -= view_advance.y; ImGui::SetNextWindowPos(screen_text_title_pos); ImGui::Begin("TetrisStatisticsTitle", nullptr, s_DefaultImGuiWindowFlags); ImGui::Text("STATISTICS"); ImGui::End(); ImGui::SetNextWindowPos(screen_text_pos); ImGui::Begin("TetrisStatistics", nullptr, s_DefaultImGuiWindowFlags); ImGui::Text("%d", m_tetromino_counters[Tetromino::t_piece]); ImGui::Dummy(screen_text_gap); ImGui::Text("%d", m_tetromino_counters[Tetromino::j_piece]); ImGui::Dummy(screen_text_gap); ImGui::Text("%d", m_tetromino_counters[Tetromino::z_piece]); ImGui::Dummy(screen_text_gap); ImGui::Text("%d", m_tetromino_counters[Tetromino::o_piece]); ImGui::Dummy(screen_text_gap); ImGui::Text("%d", m_tetromino_counters[Tetromino::s_piece]); ImGui::Dummy(screen_text_gap); ImGui::Text("%d", m_tetromino_counters[Tetromino::l_piece]); ImGui::Dummy(screen_text_gap); ImGui::Text("%d", m_tetromino_counters[Tetromino::i_piece]); ImGui::Dummy(screen_text_gap); ImGui::End(); } void Tetris::DrawScore() { V2F32 view_pos = {3.0f, 2.2f}; ImVec2 screen_pos = g_renderer.ViewPosToScreenPosImGui(view_pos); ImGui::SetNextWindowPos(screen_pos); ImGui::Begin("TetrisScore", nullptr, s_DefaultImGuiWindowFlags); ImGui::Text("Score"); ImGui::Text("%d", m_score); ImGui::End(); } void Tetris::DrawNextTetromino() { V2F32 text_view_pos = {3.0f, 1.8f}; ImVec2 text_screen_pos = g_renderer.ViewPosToScreenPosImGui(text_view_pos); ImGui::SetNextWindowPos(text_screen_pos); ImGui::Begin("TetrisNextTetromino", nullptr, s_DefaultImGuiWindowFlags); ImGui::Text("Next"); ImGui::End(); V2F32 tetromino_view_pos = {3.0, 1.4f}; Tetromino::Draw(m_next_tetromino_id, 0, tetromino_view_pos, 0.5f); } void Tetris::DrawLevel() { V2F32 view_pos = {3.0f, 1.2f}; ImVec2 screen_pos = g_renderer.ViewPosToScreenPosImGui(view_pos); ImGui::SetNextWindowPos(screen_pos); ImGui::Begin("TetrisLevel", nullptr, s_DefaultImGuiWindowFlags); ImGui::Text("Level"); ImGui::Text("%d", m_level); ImGui::End(); }