diff options
Diffstat (limited to 'src/games/snake/Snake.cpp')
-rw-r--r-- | src/games/snake/Snake.cpp | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/src/games/snake/Snake.cpp b/src/games/snake/Snake.cpp new file mode 100644 index 0000000..9764a9e --- /dev/null +++ b/src/games/snake/Snake.cpp @@ -0,0 +1,345 @@ +#include <games/snake/Snake.hpp> +#include <imgui.h> + + +Snake::Snake () { + m_IsPaused = false; + m_IsRunning = true; + + m_DtInSecondsRemaining = 0.0f; + m_LastMillisecondsSinceT0 = SDL_GetTicks(); + + m_TilesPerSecond = 4.0f; + m_Direction = DIRECTION_RIGHT; + m_LastAdvancedDirection = DIRECTION_RIGHT; + + m_MapWidth = 16; + m_MapHeight = 16; + assert(MAX_MAP_WIDTH <= 64); // m_BodyBitmap is uint64_t[]. We can't exceed that! + assert(MAX_MAP_HEIGHT <= 64); + assert(m_MapWidth <= MAX_MAP_WIDTH); + assert(m_MapHeight <= MAX_MAP_WIDTH); + + m_Tail = 0; + m_Head = 1; + memset(m_BodyBitmap, 0, sizeof(m_BodyBitmap)); + + int32_t head_x = m_MapWidth / 2; + int32_t head_y = m_MapHeight / 2; + m_BodyPositions[0] = {head_x -1, head_y}; + m_BodyPositions[1] = {head_x, head_y}; + + m_Rng = std::mt19937((std::random_device()())); + m_Dist = std::uniform_int_distribution<int32_t>(0, m_MapWidth * m_MapHeight - 3); + + SpawnFood(); + } + +bool Snake::Update(std::vector<SDL_Event> &events, RenderGroup &render_group) { + uint64_t milliseconds_since_t0 = SDL_GetTicks(); + uint64_t milliseconds_since_t0_last = m_LastMillisecondsSinceT0; + uint64_t dt_in_milliseconds = milliseconds_since_t0 - milliseconds_since_t0_last; + float dt_in_seconds = (float)dt_in_milliseconds / 1000.0f; + m_LastMillisecondsSinceT0 = milliseconds_since_t0; + + + V3F32 clear_color = V3F32(0.3f, 0.3f, 0.3f); + render_group.SetCameraSize(4.0f, 3.0f); + render_group.Clear(clear_color); + + + for (SDL_Event &event : events) { + if (!m_IsRunning) { + printf("event loop is running just false\n"); + return false; + } + if (m_IsPaused) { + ProcessEventDuringPause(event); + } + else { + ProcessEventDuringResume(event); + } + } + + if (!m_IsPaused) { + MaybeMoveSnake(dt_in_seconds); + } + + + Draw(render_group); + DoImgui(); + + return m_IsRunning; +} + +void Snake::MaybeMoveSnake(float dt_in_seconds) { + float dt_in_seconds_to_use = m_DtInSecondsRemaining + dt_in_seconds; + float tiles_per_second = m_TilesPerSecond; + float seconds_per_tile = 1.0f / tiles_per_second; + while (dt_in_seconds_to_use > seconds_per_tile) { + V2I32 head_pos = m_BodyPositions[m_Head]; + V2I32 tail_pos = m_BodyPositions[m_Tail]; + + + // find head_pos + if (m_Direction == DIRECTION_UP) { + head_pos.y += 1; + } + else if (m_Direction == DIRECTION_DOWN) { + head_pos.y -= 1; + } + else if (m_Direction == DIRECTION_RIGHT) { + head_pos.x += 1; + } + else if (m_Direction == DIRECTION_LEFT) { + head_pos.x -= 1; + } + if ((head_pos.x < 0 || head_pos.x >= m_MapWidth) || + (head_pos.y < 0 || head_pos.y >= m_MapHeight)) + { + m_IsRunning = false; + return; + } + uint64_t head_bit = 1 << head_pos.x; + uint64_t body_bits = m_BodyBitmap[head_pos.y]; + if (head_pos.y == tail_pos.y) { + body_bits &= ~(1 << tail_pos.x); + } + if (head_bit & body_bits) { + m_IsRunning = false; + return; + } + + + // advance head + int32_t max_positions = sizeof(m_BodyPositions) / sizeof(m_BodyPositions[0]); + m_Head += 1; + if (m_Head >= max_positions) { + m_Head = 0; + } + + m_BodyPositions[m_Head] = head_pos; + m_BodyBitmap[head_pos.y] |= (1 << head_pos.x); + + + if (m_BodyPositions[m_Head] == m_FoodPosition) { + SpawnFood(); + } + else { + // advance tail + V2I32 next_tail_pos = m_BodyPositions[m_Tail]; + m_BodyBitmap[next_tail_pos.y] &= ~(1 << next_tail_pos.x); + + m_Tail += 1; + if (m_Tail >= max_positions) { + m_Tail = 0; + } + } + + + m_LastAdvancedDirection = m_Direction; + dt_in_seconds_to_use -= seconds_per_tile; + } + + + m_DtInSecondsRemaining = dt_in_seconds_to_use; +} + +void Snake::ProcessEventDuringPause(SDL_Event &event) { + switch (event.type) { + case SDL_EVENT_KEY_DOWN: { + if (event.key.key == SDLK_ESCAPE) { + m_IsPaused = false; + } + } + default:; + } +} + +void Snake::ProcessEventDuringResume(SDL_Event &event) { + switch (event.type) { + case SDL_EVENT_KEY_DOWN: { + if (event.key.key == SDLK_UP) { + if (m_LastAdvancedDirection == DIRECTION_RIGHT || + m_LastAdvancedDirection == DIRECTION_LEFT) + { + m_Direction = DIRECTION_UP; + } + } + else if (event.key.key == SDLK_DOWN) { + if (m_LastAdvancedDirection == DIRECTION_RIGHT || + m_LastAdvancedDirection == DIRECTION_LEFT) + { + m_Direction = DIRECTION_DOWN; + } + } + else if (event.key.key == SDLK_RIGHT) { + if (m_LastAdvancedDirection == DIRECTION_UP || + m_LastAdvancedDirection == DIRECTION_DOWN) + { + m_Direction = DIRECTION_RIGHT; + } + } + else if (event.key.key == SDLK_LEFT) { + if (m_LastAdvancedDirection == DIRECTION_UP || + m_LastAdvancedDirection == DIRECTION_DOWN) + { + m_Direction = DIRECTION_LEFT; + } + } + else if (event.key.key == SDLK_ESCAPE) { + m_IsPaused = true; + } + } + + default:; + } +} + +void Snake::SpawnFood() { + int32_t bit0_counts[MAX_MAP_HEIGHT]; + int32_t bit0_count_total = 0; + + // count bits + for (int32_t y = 0; y < m_MapHeight; y++) { + int32_t bit1_count = 0; + + uint64_t bitmap_row = m_BodyBitmap[y]; + while (bitmap_row != 0) { + bitmap_row = bitmap_row & (bitmap_row - 1); + bit1_count += 1; + } + + int32_t bit0_count = m_MapWidth - bit1_count; + bit0_counts[y] = bit0_count; + bit0_count_total += bit0_count; + } + + if (bit0_count_total == 0) { + return; + } + + m_Dist.param(std::uniform_int_distribution<int32_t>::param_type(0, bit0_count_total - 1)); + int32_t bit0_index = m_Dist(m_Rng); + int32_t bit0_x = 0; + int32_t bit0_y = 0; + + // find y + for (int32_t y = 0; y < m_MapHeight; y++) { + if (bit0_index < bit0_counts[y]) { + bit0_y = y; + break; + } + bit0_index -= bit0_counts[y]; + } + + // find x + uint64_t bitmap_row_not = ~m_BodyBitmap[bit0_y]; + for (int32_t x = 0; x < m_MapWidth; x++) { + if (bitmap_row_not & 1) { + if (bit0_index == 0) { + bit0_x = x; + break; + } + bit0_index--; + } + bitmap_row_not >>= 1; + } + + m_FoodPosition = {bit0_x, bit0_y}; +} + +void Snake::Draw(RenderGroup &render_group) { + float world_width = 4.0f; + float world_height = 3.0f; + + float tile_size = (world_width / 2) / MAX_MAP_WIDTH; + float bodypart_size = 0.8f * tile_size; + float bodypart_offset = (tile_size - bodypart_size) / 2; + + float map_view_width = tile_size * (float)m_MapWidth; + float map_view_height = tile_size * (float)m_MapHeight; + float map_x = (world_width - map_view_width) / 2; + float map_y = (world_height - map_view_height) / 2; + + int32_t max_positions = sizeof(m_BodyPositions) / sizeof(m_BodyPositions[0]); + + + /* draw map background */ + V3F32 map_world_pos = {map_x, map_y, 0.0f}; + V2F32 map_world_dim = {map_view_width, map_view_height}; + V3F32 bg_color = {0.0f, 0.0f, 0.0f}; + render_group.PushRectangle(map_world_pos, map_world_dim, bg_color); + + + /* draw snake */ + // 1) if tail > head: advance to end first + int32_t tail = m_Tail; + if (tail > m_Head) { + while (tail < max_positions) { + V3F32 local_pos = { + (float)m_BodyPositions[tail].x * tile_size + bodypart_offset, + (float)m_BodyPositions[tail].y * tile_size + bodypart_offset, + 1.0f + }; + V2F32 local_dim = {bodypart_size, bodypart_size}; + + V3F32 world_pos = { + map_world_pos.x + local_pos.x, + map_world_pos.y + local_pos.y, + 1.0f + }; + V2F32 world_dim = local_dim; + + V3F32 color = {0.3f, 0.3f, 0.3f}; + render_group.PushRectangle(world_pos, world_dim, color); + tail++; + } + tail = 0; + } + // 2) advance to head + while (tail <= m_Head) { + V3F32 local_pos = { + (float)m_BodyPositions[tail].x * tile_size + bodypart_offset, + (float)m_BodyPositions[tail].y * tile_size + bodypart_offset, + 1.0f + }; + V2F32 local_dim = {bodypart_size, bodypart_size}; + + V3F32 world_pos = { + map_world_pos.x + local_pos.x, + map_world_pos.y + local_pos.y, + 1.0f + }; + V2F32 world_dim = local_dim; + + V3F32 color = {0.3f, 0.3f, 0.3f}; + render_group.PushRectangle(world_pos, world_dim, color); + tail++; + } + + + /* draw food */ + V3F32 pos = { + map_world_pos.x + (float)m_FoodPosition.x * tile_size + bodypart_offset, + map_world_pos.y + (float)m_FoodPosition.y * tile_size + bodypart_offset, + 1.0f + }; + V2F32 dim = {bodypart_size, bodypart_size}; + V3F32 color = {0.3f, 0.6f, 0.4f}; + render_group.PushRectangle(pos, dim, color); +} + +void Snake::DoImgui() { + if (m_IsPaused) { + ImGui::Begin("SnakePause"); + if (ImGui::Button("Resume")) { + m_IsPaused = false; + } + if (ImGui::Button("Exit")) { + m_IsRunning = false; + } + ImGui::End(); + } +} + |