diff options
author | Aryadev Chavali <aryadev@aryadevchavali.com> | 2024-05-08 16:42:34 +0530 |
---|---|---|
committer | Aryadev Chavali <aryadev@aryadevchavali.com> | 2024-05-08 16:42:34 +0530 |
commit | 53ae6b99622163a7112548dfa82aeb592485a33a (patch) | |
tree | c5e85646fa7851be104aa5ee6934797214b3741b | |
parent | 40b0feb0a97293866911eb9aab4cb1f495315dc8 (diff) | |
download | snek-53ae6b99622163a7112548dfa82aeb592485a33a.tar.gz snek-53ae6b99622163a7112548dfa82aeb592485a33a.tar.bz2 snek-53ae6b99622163a7112548dfa82aeb592485a33a.zip |
Added game over screen and a few wall layouts for grid
Also tuned the speed and fruit generation
-rw-r--r-- | src/main.cpp | 305 |
1 files changed, 215 insertions, 90 deletions
diff --git a/src/main.cpp b/src/main.cpp index a43c68e..d22360b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,6 +68,11 @@ struct Point return {x * p.x, y * p.y}; } + Point operator%(const Point &p) const + { + return {(int)mod(x, p.x), (int)mod(y, p.y)}; + } + Point operator*(int m) const { return {x * m, y * m}; @@ -106,6 +111,13 @@ struct State static constexpr double my = HEIGHT / b; static constexpr double square_size = mx < my ? mx : my; + enum class Layout + { + UNLIMITED, + WALLS, + WALLED_GARDEN + } layout; + struct Player { Direction dir; @@ -137,12 +149,13 @@ struct State { double y_ = rescale(y, WIDTH < HEIGHT, (HEIGHT - WIDTH) / 2); - if (grid[x][y] == Type::EMPTY) - DrawRectangleLines(x_, y_, square_size, square_size, WHITE); - else if (grid[x][y] == Type::WALL) + DrawRectangleLines(x_, y_, square_size, square_size, WHITE); + + if (grid[x][y] == Type::WALL) DrawRectangle(x_, y_, square_size, square_size, WHITE); else if (grid[x][y] == Type::FRUIT) - DrawRectangle(x_, y_, square_size, square_size, RED); + DrawCircle(x_ + square_size / 2, y_ + square_size / 2, + square_size / 2, RED); } } @@ -175,14 +188,16 @@ struct State y_eye_2 += (9.0 / 10) * square_size; break; } - DrawRectangle(x_eye_1, y_eye_1, square_size / 10, square_size / 10, BLACK); - DrawRectangle(x_eye_2, y_eye_2, square_size / 10, square_size / 10, BLACK); + + DrawRectangle(x_eye_1, y_eye_1, square_size / 10, square_size / 10, RED); + DrawRectangle(x_eye_2, y_eye_2, square_size / 10, square_size / 10, RED); for (size_t i = 1; i < player.points.size(); ++i) { const auto &p = player.points[i]; x_ = rescale(p.x, HEIGHT < WIDTH, (WIDTH - HEIGHT) / 2); y_ = rescale(p.y, WIDTH < HEIGHT, (HEIGHT - WIDTH) / 2); - DrawRectangle(x_, y_, square_size, square_size, GREEN); + DrawCircle(x_ + square_size / 2, y_ + square_size / 2, square_size / 2, + GREEN); } } @@ -195,11 +210,7 @@ struct State { auto head = player_head(); Point old_position = *head; - Point new_position = old_position; - new_position.y += Point{player.dir}.y; - new_position.x += Point{player.dir}.x; - new_position.x = mod(new_position.x, a); - new_position.y = mod(new_position.y, b); + Point new_position = (old_position + Point{player.dir}) % Point{a, b}; if (is_player(new_position.x, new_position.y) || grid[new_position.x][new_position.y] == Type::WALL) @@ -228,6 +239,18 @@ struct State grid[x][y] = Type::FRUIT; } + void make_rand_wall(void) + { + size_t x = rand() % a; + size_t y = rand() % b; + while (grid[x][y] == Type::FRUIT || is_player(x, y)) + { + x = rand() % a; + y = rand() % b; + } + grid[x][y] = Type::WALL; + } + void player_fruit_collision() { const auto point = player_head(); @@ -256,140 +279,242 @@ struct State player.points.push_back({a / 2, b / 2}); player.dir = Direction::LEFT; memset(grid, 0, sizeof(Type) * a * b); + switch (layout) + { + case Layout::UNLIMITED: + break; + case Layout::WALLS: { + size_t i = 0; + for (size_t j = 0; j < b; ++j) + grid[i][j] = Type::WALL; + i = a - 1; + for (size_t j = 0; j < b; ++j) + grid[i][j] = Type::WALL; + size_t j = 0; + for (i = 0; i < a; ++i) + grid[i][j] = Type::WALL; + j = b - 1; + for (i = 0; i < a; ++i) + grid[i][j] = Type::WALL; + break; + } + case Layout::WALLED_GARDEN: { + size_t i = 0; + for (size_t j = 0; j < b; ++j) + if (j > (b * 2 / 3) || j < (b / 3)) + grid[i][j] = Type::WALL; + i = a - 1; + for (size_t j = 0; j < b; ++j) + if (j > (b * 2 / 3) || j < (b / 3)) + grid[i][j] = Type::WALL; + size_t j = 0; + for (i = 0; i < a; ++i) + if (i > (a * 2 / 3) || i < (a / 3)) + grid[i][j] = Type::WALL; + j = b - 1; + for (i = 0; i < a; ++i) + if (i > (a * 2 / 3) || i < (a / 3)) + grid[i][j] = Type::WALL; + break; + } + } } }; namespace chrono = std::chrono; +using Clock = chrono::steady_clock; -struct Time +struct Timer { - size_t hours, minutes, seconds; + double (*delta)(size_t); + chrono::time_point<Clock> prev; - Time(size_t s) - { - hours = s / 3600; - s -= (hours * 3600); - minutes = s / 60; - s -= (minutes * 60); - seconds = s; - } + Timer(double (*delta_fn)(size_t)) : delta{delta_fn}, prev{Clock::now()} + {} - std::string to_str(void) + bool triggered(size_t player_size) { - std::stringstream s; - if (hours < 10) - s << "0"; - s << std::to_string(hours) + ":"; - if (minutes < 10) - s << "0"; - s << std::to_string(minutes) + ":"; - if (seconds < 10) - s << "0"; - s << std::to_string(seconds); - return s.str(); + chrono::time_point<Clock> current = Clock::now(); + if (chrono::duration_cast<chrono::milliseconds>(current - prev).count() > + delta(player_size)) + { + prev = current; + return true; + } + return false; } }; -using Clock = chrono::steady_clock; +template <size_t min, size_t max, size_t max_score> +constexpr auto make_delta() +{ + return [](size_t player_size) + { + return max - ((max - min) * (player_size < max_score + ? (double)player_size / (double)max_score + : 1)); + }; +} + +template <size_t X, size_t Y> +void wall_layout(State<X, Y> &state) +{ + state.reset(); + size_t i = 0; + for (size_t j = 0; j < Y; ++j) + state.grid[i][j] = Type::WALL; + i = X - 1; + for (size_t j = 0; j < Y; ++j) + state.grid[i][j] = Type::WALL; + size_t j = 0; + for (i = 0; i < X; ++i) + state.grid[i][j] = Type::WALL; + j = Y - 1; + for (i = 0; i < X; ++i) + state.grid[i][j] = Type::WALL; +} + int main(void) { srand(time(NULL)); - constexpr size_t X = 10, Y = 10; + constexpr size_t X = 20, Y = 20; State<X, Y> state; state.reset(); InitWindow(WIDTH, HEIGHT, "snek"); SetTargetFPS(60); - chrono::time_point<Clock> time_start{Clock::now()}; - constexpr double max_score = 100; + constexpr size_t update_max_score = 50; + constexpr size_t wall_min_score = 40; - constexpr double fruit_delta_max = 5; - constexpr double fruit_delta_min = 1; - constexpr auto fruit_delta = [](size_t player_size) - { - return fruit_delta_min + - ((fruit_delta_max - fruit_delta_min) * - (1 - (player_size < max_score ? player_size / max_score : 1))); - }; - - chrono::time_point<Clock> fruit_cur{Clock::now()}; - chrono::time_point<Clock> fruit_prev{Clock::now()}; + Timer update_timer{make_delta<80, 300, update_max_score>()}; + Timer fruit_timer{make_delta<1000, 5000, update_max_score>()}; + Timer wall_timer{make_delta<5000, 10000, 100>()}; - constexpr double update_delta_max = 500; - constexpr double update_delta_min = 50; - constexpr auto update_delta = [](size_t player_size) - { - return update_delta_min + - ((update_delta_max - update_delta_min) * - (1 - (player_size < max_score ? player_size / max_score : 1))); - }; - chrono::time_point<Clock> update_cur{Clock::now()}; - chrono::time_point<Clock> update_prev{Clock::now()}; + chrono::time_point<Clock> time_start{Clock::now()}; + chrono::time_point<Clock> time_cur{Clock::now()}; Direction dir = Direction::LEFT; bool paused = false; + bool failed = false; + bool details = false; while (!WindowShouldClose()) { - if (!paused) + if (IsKeyPressed(KEY_P)) + { + paused = !paused; + } + else if (IsKeyPressed(KEY_ENTER)) + { + if (failed) + { + state.reset(); + time_start = Clock::now(); + failed = false; + paused = false; + } + } + else if (IsKeyPressed(KEY_GRAVE)) + details = !details; + else if (IsKeyPressed(KEY_ONE)) + { + state.layout = State<X, Y>::Layout::UNLIMITED; + state.reset(); + } + else if (IsKeyPressed(KEY_TWO)) { - if (IsKeyDown(KEY_J)) + state.layout = State<X, Y>::Layout::WALLS; + state.reset(); + } + else if (IsKeyPressed(KEY_THREE)) + { + state.layout = State<X, Y>::Layout::WALLED_GARDEN; + state.reset(); + } + if (!paused && !failed) + { + time_cur = Clock::now(); + bool fast = false; + if (IsKeyPressed(KEY_J)) dir = Direction::DOWN; - else if (IsKeyDown(KEY_K)) + else if (IsKeyPressed(KEY_K)) dir = Direction::UP; - else if (IsKeyDown(KEY_H)) + else if (IsKeyPressed(KEY_H)) dir = Direction::LEFT; - else if (IsKeyDown(KEY_L)) + else if (IsKeyPressed(KEY_L)) dir = Direction::RIGHT; + else if (IsKeyDown(KEY_SPACE)) + fast = true; - update_cur = Clock::now(); - if (chrono::duration_cast<chrono::milliseconds>(update_cur - update_prev) - .count() >= update_delta(state.player.points.size())) + if (update_timer.triggered(fast ? update_max_score + : state.player.points.size())) { - update_prev = update_cur; if (!(state.player.points.size() > 1 && - ((Point{dir} + (*state.player.points.begin())) == + (((Point{dir} + (*state.player.points.begin())) % Point{X, Y}) == (*(state.player.points.begin() + 1))))) state.player.dir = dir; bool collide = state.update_player_head(); if (collide) { - paused = false; - // state.reset(); - time_start = Clock::now(); + failed = true; } state.player_fruit_collision(); } - fruit_cur = Clock::now(); - - if (chrono::duration_cast<chrono::seconds>(fruit_cur - fruit_prev) - .count() >= fruit_delta(state.player.points.size())) - { - fruit_prev = fruit_cur; + if (fruit_timer.triggered(state.player.points.size())) state.make_rand_fruit(); - } + + if (state.player.points.size() > wall_min_score && + wall_timer.triggered(state.player.points.size())) + state.make_rand_wall(); } BeginDrawing(); ClearBackground(BLACK); state.draw_grid(); - DrawText( - Time(chrono::duration_cast<chrono::seconds>(Clock::now() - time_start) - .count()) - .to_str() - .c_str(), - 0, 0, 25, YELLOW); + + size_t seconds = + chrono::duration_cast<chrono::seconds>(time_cur - time_start).count(); + size_t hours = seconds / 3600; + seconds = seconds % 3600; + size_t minutes = seconds / 60; + seconds = seconds % 60; std::stringstream ss; + ss << (hours < 10 ? "0" : "") << hours << ":" << (minutes < 10 ? "0" : "") + << minutes << ":" << (seconds < 10 ? "0" : "") << seconds; + DrawText(ss.str().c_str(), 0, 0, 25, YELLOW); + + ss.str(""); ss << "Score: "; ss << state.player.points.size() - 1; - DrawText(ss.str().c_str(), 0, 25, 20, YELLOW); + DrawText(ss.str().c_str(), 0, 30, 20, YELLOW); - ss.str(""); - ss << "Next: "; - ss << fruit_delta(state.player.points.size()); - DrawText(ss.str().c_str(), 0, 50, 20, YELLOW); + if (details) + { + ss.str(""); + ss << "Next: " << fruit_timer.delta(state.player.points.size()) / 1000 + << "s"; + DrawText(ss.str().c_str(), 0, 80, 18, YELLOW); + + ss.str(""); + ss << 1 / (update_timer.delta(state.player.points.size()) / 1000) + << " f/s"; + DrawText(ss.str().c_str(), 0, 100, 20, YELLOW); + } + + if (failed) + { + size_t x_top = state.rescale(1, (HEIGHT < WIDTH), (WIDTH - HEIGHT) / 2); + size_t y_top = state.rescale(1, (WIDTH < HEIGHT), (HEIGHT - WIDTH) / 2); + size_t x_size = + state.rescale(X - 1, (HEIGHT < WIDTH), (WIDTH - HEIGHT) / 2) - x_top; + size_t y_size = + state.rescale(Y - 1, (WIDTH < HEIGHT), (HEIGHT - WIDTH) / 2) - y_top; + DrawRectangle(x_top, y_top, x_size, y_size, GRAY); + DrawText("GAME OVER", x_top + x_size / 5, y_top + y_size / 3, x_size / 10, + RED); + } EndDrawing(); } |