#include "games/snake/Snake.hpp" #include "games/Game.hpp" #include "renderer/Renderer.hpp" #include "common/defs.hpp" #include #include 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() { m_dt_remaining_seconds = 0.0f; m_tlast_milliseconds = SDL_GetTicks(); m_direction = right; m_last_advanced_direction = right; m_map_width = m_starting_map_width; m_map_height = m_starting_map_height; assert(m_map_width <= max_map_width); assert(m_map_height <= max_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_resume; } void Snake::FinishUpdate(float dt) { MaybeMoveSnake(dt); } 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 &= (uint32_t)~(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] &= (uint32_t)~(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::ProcessEvent(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; } } } 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; int32_t max_bodypart_count = ARRAY_COUNT(m_body_positions); float map_width = tile_size * (float)m_map_width; float map_height = tile_size * (float)m_map_height; float map_x = (world_width - map_width) / 2; float map_y = (world_height - map_height) / 2; uint32_t z_bg = z_layer1; uint32_t z_food = z_layer2; uint32_t z_snake = z_layer3; Color color_snake = {0.0f, 0.5f, 0.0f, 1.0f}; Color color_food = {0.5f, 0.0f, 0.0f, 1.0f}; Color color_bg = {0.0f, 0.0f, 0.0f, 1.0f}; /* draw map background */ Rectangle map_world_rect = { map_x, map_y, map_x + map_width, map_y + map_height }; g_renderer.PushRectangle(map_world_rect, color_bg, z_bg); /* draw snake */ // 1) if tail > head: advance to end first int32_t tail = m_tail; if (tail > m_head) { while (tail < max_bodypart_count) { float xoff = (float)m_body_positions[tail].x * tile_size + bodypart_offset; float yoff = (float)m_body_positions[tail].y * tile_size + bodypart_offset; float x = map_x + xoff; float y = map_y + yoff; Rectangle rect = { x, y, x + bodypart_size, y + bodypart_size }; g_renderer.PushRectangle(rect, color_snake, z_snake); tail++; } tail = 0; } // 2) advance to head while (tail <= m_head) { float xoff = (float)m_body_positions[tail].x * tile_size + bodypart_offset; float yoff = (float)m_body_positions[tail].y * tile_size + bodypart_offset; float x = map_x + xoff; float y = map_y + yoff; Rectangle rect = { x, y, x + bodypart_size, y + bodypart_size }; g_renderer.PushRectangle(rect, color_snake, z_snake); tail++; } /* draw food */ float food_x = map_x + (float)m_food_position.x * tile_size + bodypart_offset; float food_y = map_y + (float)m_food_position.y * tile_size + bodypart_offset; Rectangle rect = { food_x, food_y, food_x + bodypart_size, food_y + bodypart_size }; g_renderer.PushRectangle(rect, color_food, z_food); }