#include #include #include // Todo: // - gradiant from head to tail // - reduce thickness from head to tail std::mt19937 Snake::s_rng{std::random_device{}()}; Snake::Snake() { static_assert(max_map_width <= sizeof(m_body_bitmap[0])*8); static_assert(max_map_height <= sizeof(m_body_bitmap[0])*8); } void Snake::Start(int32_t map_width, int32_t map_height) { m_dt_remaining_seconds = 0.0f; m_tlast_milliseconds = SDL_GetTicks(); m_direction = right; m_last_advanced_direction = right; assert(map_width <= max_map_width); assert(map_height <= max_map_height); m_map_width = map_width; m_map_height = map_height; m_tail = 0; m_head = 1; int32_t head_x = m_map_width / 2; int32_t head_y = m_map_height / 2; m_body_positions[0] = {head_x -1, head_y}; m_body_positions[1] = {head_x, head_y}; memset(m_body_bitmap, 0, sizeof(m_body_bitmap)); m_dist = std::uniform_int_distribution(0, m_map_width*m_map_height - 3); SpawnFood(); m_game_status = game_resuming; } bool Snake::Update(std::vector &events) { Color clear_color = {0.3f, 0.3f, 0.3f, 1.0f}; g_renderer.SetCameraSize(4.0f, 3.0f); g_renderer.Clear(clear_color); if (m_game_status == game_starting) { int32_t map_width = 16; int32_t map_height = 16; Start(map_width, map_height); } for (SDL_Event &event : events) { if (m_game_status == game_resuming) { ProcessEventDuringResume(event); } else if (m_game_status == game_paused) { ProcessEventDuringPause(event); } } float dt = ProcessDt(); switch (m_game_status) { case game_starting: { } break; case game_resuming: { MaybeMoveSnake(dt); } break; case game_over: { DrawDefaultGameOverMenu(); } break; case game_paused: { DrawDefaultGamePausedMenu(); } break; case game_exit: { return false; } break; } Draw(); return true; } void Snake::MaybeMoveSnake(float dt) { float seconds_per_tile = 1.0f / tiles_per_second; while (dt > seconds_per_tile) { V2I32 head_pos = m_body_positions[m_head]; V2I32 tail_pos = m_body_positions[m_tail]; // find head_pos if (m_direction == up) { head_pos.y += 1; } else if (m_direction == down) { head_pos.y -= 1; } else if (m_direction == right) { head_pos.x += 1; } else if (m_direction == left) { head_pos.x -= 1; } if ((head_pos.x < 0 || head_pos.x >= m_map_width) || (head_pos.y < 0 || head_pos.y >= m_map_height)) { m_game_status = game_over; return; } uint64_t head_bit = 1 << head_pos.x; uint64_t body_bits = m_body_bitmap[head_pos.y]; if (head_pos.y == tail_pos.y) { body_bits &= ~(1 << tail_pos.x); } if (head_bit & body_bits) { m_game_status = game_over; return; } // advance head int32_t max_positions = ARRAY_COUNT(m_body_positions); m_head += 1; if (m_head >= max_positions) { m_head = 0; } m_body_positions[m_head] = head_pos; m_body_bitmap[head_pos.y] |= (1 << head_pos.x); if (m_body_positions[m_head] == m_food_position) { SpawnFood(); } else { // advance tail V2I32 next_tail_pos = m_body_positions[m_tail]; m_body_bitmap[next_tail_pos.y] &= ~(1 << next_tail_pos.x); m_tail += 1; if (m_tail >= max_positions) { m_tail = 0; } } m_last_advanced_direction = m_direction; dt -= seconds_per_tile; } m_dt_remaining_seconds = dt; } void Snake::ProcessEventDuringPause(SDL_Event &event) { switch (event.type) { case SDL_EVENT_KEY_DOWN: { if (event.key.key == SDLK_ESCAPE) { m_game_status = game_resuming; } } default:; } } void Snake::ProcessEventDuringResume(SDL_Event &event) { switch (event.type) { case SDL_EVENT_KEY_DOWN: { if (event.key.key == SDLK_UP) { if (m_last_advanced_direction == right || m_last_advanced_direction == left) { m_direction = up; } } else if (event.key.key == SDLK_DOWN) { if (m_last_advanced_direction == right || m_last_advanced_direction == left) { m_direction = down; } } else if (event.key.key == SDLK_RIGHT) { if (m_last_advanced_direction == up || m_last_advanced_direction == down) { m_direction = right; } } else if (event.key.key == SDLK_LEFT) { if (m_last_advanced_direction == up || m_last_advanced_direction == down) { m_direction = left; } } else if (event.key.key == SDLK_ESCAPE) { m_game_status = game_paused; } } 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_map_height; y++) { int32_t bit1_count = 0; uint64_t bitmap_row = m_body_bitmap[y]; while (bitmap_row != 0) { bitmap_row = bitmap_row & (bitmap_row - 1); bit1_count += 1; } int32_t bit0_count = m_map_width - 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::param_type(0, bit0_count_total - 1)); int32_t bit0_index = m_dist(s_rng); int32_t bit0_x = 0; int32_t bit0_y = 0; // find y for (int32_t y = 0; y < m_map_height; y++) { if (bit0_index < bit0_counts[y]) { bit0_y = y; break; } bit0_index -= bit0_counts[y]; } // find x uint64_t bitmap_row_not = ~m_body_bitmap[bit0_y]; for (int32_t x = 0; x < m_map_width; x++) { if (bitmap_row_not & 1) { if (bit0_index == 0) { bit0_x = x; break; } bit0_index--; } bitmap_row_not >>= 1; } m_food_position = {bit0_x, bit0_y}; } void Snake::Draw() { 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_map_width; float map_view_height = tile_size * (float)m_map_height; float map_x = (world_width - map_view_width) / 2; float map_y = (world_height - map_view_height) / 2; int32_t max_positions = ARRAY_COUNT(m_body_positions); /* draw map background */ V3F32 map_world_pos = {map_x, map_y, 0.0f}; V2F32 map_world_dim = {map_view_width, map_view_height}; Rectangle map_world_rect = { map_world_pos.x, map_world_pos.y, map_world_pos.x + map_world_dim.x, map_world_pos.y + map_world_dim.y }; Color bg_color = {0.0f, 0.0f, 0.0f, 1.0f}; g_renderer.PushRectangle(map_world_rect, bg_color, map_world_pos.z); /* 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_body_positions[tail].x * tile_size + bodypart_offset, (float)m_body_positions[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; Rectangle world_rect = { world_pos.x, world_pos.y, world_pos.x + world_dim.x, world_pos.y + world_dim.y, }; Color color = {0.3f, 0.3f, 0.3f, 1.0f}; g_renderer.PushRectangle(world_rect, color, world_pos.z); tail++; } tail = 0; } // 2) advance to head while (tail <= m_head) { V3F32 local_pos = { (float)m_body_positions[tail].x * tile_size + bodypart_offset, (float)m_body_positions[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; Rectangle world_rect = { world_pos.x, world_pos.y, world_pos.x + world_dim.x, world_pos.y + world_dim.y, }; Color color = {0.3f, 0.3f, 0.3f, 1.0f}; g_renderer.PushRectangle(world_rect, color, world_pos.z); tail++; } /* draw food */ V3F32 pos = { map_world_pos.x + (float)m_food_position.x * tile_size + bodypart_offset, map_world_pos.y + (float)m_food_position.y * tile_size + bodypart_offset, 1.0f }; V2F32 dim = {bodypart_size, bodypart_size}; Rectangle rect = { pos.x, pos.y, pos.x + dim.x, pos.y + dim.y }; Color color = {0.3f, 0.6f, 0.4f, 1.0f}; g_renderer.PushRectangle(rect, color, pos.z); }