aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAryadev Chavali <aryadev@aryadevchavali.com>2024-05-08 16:42:34 +0530
committerAryadev Chavali <aryadev@aryadevchavali.com>2024-05-08 16:42:34 +0530
commit53ae6b99622163a7112548dfa82aeb592485a33a (patch)
treec5e85646fa7851be104aa5ee6934797214b3741b
parent40b0feb0a97293866911eb9aab4cb1f495315dc8 (diff)
downloadsnek-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.cpp305
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();
}