Files
snek/src/main.cpp
Aryadev Chavali e92871066a Timer for new fruit and player updating
Instead of changing the FPS we'll manage it ourselves and keep a
constant 60fps.
2024-05-08 13:18:27 +05:30

394 lines
9.3 KiB
C++

/* Copyright (C) 2024 Aryadev Chavali
* You may distribute and modify this code under the terms of the MIT
* license. You should have received a copy of the MIT license with
* this file. If not, please write to: aryadev@aryadevchavali.com.
* Created: 2024-05-08
* Author: Aryadev Chavali
* Description: Entrypoint
*/
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <iostream>
#include <raylib.h>
#include <raymath.h>
#include <sstream>
#include <string>
#include <vector>
#define WIDTH 800
#define HEIGHT 600
long mod(long a, long b)
{
return (a % b + b) % b;
}
enum class Type
{
EMPTY,
WALL,
FRUIT,
};
enum class Direction
{
UP = 1,
LEFT = 2,
RIGHT = -2,
DOWN = -1
};
struct Point
{
int x, y;
bool operator==(const Point &p) const
{
return x == p.x && y == p.y;
}
Point operator+(const Point &p) const
{
return {x + p.x, y + p.y};
}
Point operator-(const Point &p) const
{
return {x - p.x, y - p.y};
}
Point operator*(const Point &p) const
{
return {x * p.x, y * p.y};
}
Point operator*(int m) const
{
return {x * m, y * m};
}
Point() : x{0}, y{0}
{}
Point(int x, int y) : x{x}, y{y}
{}
Point(Direction dir)
{
switch (dir)
{
case Direction::UP:
*this = Point{0, -1};
break;
case Direction::DOWN:
*this = Point{0, 1};
break;
case Direction::LEFT:
*this = Point{-1, 0};
break;
case Direction::RIGHT:
*this = Point{1, 0};
break;
}
}
};
template <size_t a, size_t b>
struct State
{
static constexpr double mx = WIDTH / a;
static constexpr double my = HEIGHT / b;
static constexpr double square_size = mx < my ? mx : my;
struct Player
{
Direction dir;
std::vector<Point> points;
} player;
Type grid[a][b];
bool is_player(size_t x, size_t y) const
{
return std::find(player.points.begin(), player.points.end(),
Point{(int)x, (int)y}) != player.points.end();
}
double rescale(size_t grid, bool smaller, double translation) const
{
double x = grid * square_size;
if (smaller)
x += translation;
return x;
}
void draw_grid() const
{
for (size_t x = 0; x < a; ++x)
{
double x_ = rescale(x, HEIGHT < WIDTH, (WIDTH - HEIGHT) / 2);
for (size_t y = 0; y < b; ++y)
{
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)
DrawRectangle(x_, y_, square_size, square_size, WHITE);
else if (grid[x][y] == Type::FRUIT)
DrawRectangle(x_, y_, square_size, square_size, RED);
}
}
double x_ =
rescale(player.points.begin()->x, HEIGHT < WIDTH, (WIDTH - HEIGHT) / 2);
double y_ =
rescale(player.points.begin()->y, WIDTH < HEIGHT, (HEIGHT - WIDTH) / 2);
DrawRectangle(x_, y_, square_size, square_size, YELLOW);
// Eyes based on direction
double x_eye_1 = x_;
double y_eye_1 = y_;
double x_eye_2 = x_;
double y_eye_2 = y_;
switch (player.dir)
{
case Direction::UP:
x_eye_2 += (9.0 / 10) * square_size;
break;
case Direction::DOWN:
y_eye_1 += (9.0 / 10) * square_size;
y_eye_2 += (9.0 / 10) * square_size;
x_eye_2 += (9.0 / 10) * square_size;
break;
case Direction::RIGHT:
x_eye_1 += (9.0 / 10) * square_size;
x_eye_2 += (9.0 / 10) * square_size;
y_eye_2 += (9.0 / 10) * square_size;
break;
case Direction::LEFT:
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);
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);
}
}
std::vector<Point>::iterator player_head(void)
{
return player.points.begin();
}
bool update_player_head()
{
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);
if (is_player(new_position.x, new_position.y) ||
grid[new_position.x][new_position.y] == Type::WALL)
return true;
*head = new_position;
for (size_t i = 1; i < player.points.size(); ++i)
{
Point cpy = player.points[i];
player.points[i] = old_position;
old_position = cpy;
}
return false;
}
void make_rand_fruit(void)
{
size_t x = rand() % a;
size_t y = rand() % b;
while (grid[x][y] == Type::WALL || is_player(x, y))
{
x = rand() % a;
y = rand() % b;
}
grid[x][y] = Type::FRUIT;
}
void player_fruit_collision()
{
const auto point = player_head();
if (grid[point->x][point->y] == Type::FRUIT)
{
// If only one point currently then put it anywhere
grid[point->x][point->y] = Type::EMPTY;
if (player.points.size() == 1)
{
player.points.push_back((Point{player.dir} * -1) + *point);
}
// Otherwise look at the last two points, calculate a direction
// vector then make the new one
else
{
auto last_1 = player.points.end() - 1;
auto last_2 = player.points.end() - 2;
player.points.push_back(*last_1 + (*last_1 - *last_2));
}
}
}
void reset(void)
{
player.points.clear();
player.points.push_back({a / 2, b / 2});
player.dir = Direction::LEFT;
memset(grid, 0, sizeof(Type) * a * b);
}
};
namespace chrono = std::chrono;
struct Time
{
size_t hours, minutes, seconds;
Time(size_t s)
{
hours = s / 3600;
s -= (hours * 3600);
minutes = s / 60;
s -= (minutes * 60);
seconds = s;
}
std::string to_str(void)
{
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();
}
};
using Clock = chrono::steady_clock;
int main(void)
{
srand(time(NULL));
constexpr size_t X = 10, Y = 10;
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 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()};
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()};
Direction dir = Direction::LEFT;
bool paused = false;
while (!WindowShouldClose())
{
if (!paused)
{
if (IsKeyDown(KEY_J))
dir = Direction::DOWN;
else if (IsKeyDown(KEY_K))
dir = Direction::UP;
else if (IsKeyDown(KEY_H))
dir = Direction::LEFT;
else if (IsKeyDown(KEY_L))
dir = Direction::RIGHT;
update_cur = Clock::now();
if (chrono::duration_cast<chrono::milliseconds>(update_cur - update_prev)
.count() >= update_delta(state.player.points.size()))
{
update_prev = update_cur;
if (!(state.player.points.size() > 1 &&
((Point{dir} + (*state.player.points.begin())) ==
(*(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();
}
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;
state.make_rand_fruit();
}
}
BeginDrawing();
ClearBackground(BLACK);
state.draw_grid();
DrawText("TIME", 0, 0, 25, YELLOW);
DrawText(
Time(chrono::duration_cast<chrono::seconds>(Clock::now() - beg).count())
.to_str()
.c_str(),
0, 25, 25, YELLOW);
std::stringstream ss;
ss << "Score: ";
ss << state.player.points.size() - 1;
DrawText(ss.str().c_str(), 0, 50, 20, YELLOW);
EndDrawing();
}
CloseWindow();
return 0;
}