functional bots, small refactoring, readme edit

This commit is contained in:
programsnail 2024-07-26 22:36:12 +03:00
parent 5299e89633
commit 7e67a3bd30
13 changed files with 179 additions and 91 deletions

View file

@ -1,10 +1,21 @@
# Game (Linux) # Game (Linux) - Slither.io offline clone
Game, based on https://github.com/imp5imp5/game_template_linux. - Snake should eat apples to grow
- Current score displayed on top of the screen
- There are bots that eat apples and can not be destroyed by player. Snake is destroyed when its head intersects bot
### Build Game is based on https://github.com/imp5imp5/game_template_linux.
### Build (xmake)
``sudo apt install g++ cmake libx11-dev xmake`` \ ``sudo apt install g++ cmake libx11-dev xmake`` \
``xmake`` ``xmake``
### Run ### Run (xmake)
``xmake run`` ``xmake run``
### Build (cmake)
``sudo apt install g++ cmake libx11-dev`` \
``mkdir build && cd build`` \
``cmake -DCMAKE_BUILD_TYPE=Release ..`` \
``make``

View file

@ -28,16 +28,41 @@ protected:
target_ = map_.find_nearest_food(get_pos()); target_ = map_.find_nearest_food(get_pos());
} }
// check for case when no food found
if (target_.has_value()) { if (target_.has_value()) {
direction_ = (Vecf(target_.value()) - real_pos_).norm(); if (target_ != prev_target_) {
// TODO: manually change direction direction_ = (Vecf(target_.value()) - real_pos_).norm();
}
} else {
direction_ = {};
} }
prev_target_ = target_;
} }
protected: protected:
const Config bot_config_; const Config bot_config_;
std::optional<Veci> target_ = {}; std::optional<Veci> target_ = {};
std::optional<Veci> prev_target_ = {};
float time_from_target_set_ = 0; float time_from_target_set_ = 0;
}; };
//
inline Bot::Config default_bot_config = {
.time_between_targets = 0.5,
};
inline Bot default_bot(Veci pos, Map &map) {
return Bot(
{
.obj =
{
.pos = pos,
.color = {color::GRAY},
},
.initial_length = 20,
.radius = 10,
},
default_snake_config, default_bot_config, map);
}

View file

@ -16,11 +16,11 @@ struct Object {
}; };
struct Square : public Object { struct Square : public Object {
int side; int64_t side;
}; };
struct Circle : public Object { struct Circle : public Object {
int radius; int64_t radius;
}; };
} // namespace canvas } // namespace canvas

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <deque> #include <deque>
#include <iostream>
#include <optional> #include <optional>
#include "Canvas.hpp" #include "Canvas.hpp"
@ -37,7 +36,7 @@ public:
} }
} }
int eat(Veci pos, int dist) { // TODO: faster variant ? int eat(Veci pos, int64_t dist) {
size_t eaten_weight = 0; size_t eaten_weight = 0;
for (auto &food : food_) { for (auto &food : food_) {
if ((pos - food.pos).len_sq() < dist * dist and not food.eaten) { if ((pos - food.pos).len_sq() < dist * dist and not food.eaten) {
@ -48,7 +47,7 @@ public:
return eaten_weight; return eaten_weight;
} }
void draw(Veci offset) override { void draw(Veci offset) const override {
for (const auto &food : food_) { for (const auto &food : food_) {
Veci food_pos = utils::to_world_coord(food.pos - offset); Veci food_pos = utils::to_world_coord(food.pos - offset);
if (utils::is_on_screen(food_pos) and not food.eaten) { if (utils::is_on_screen(food_pos) and not food.eaten) {
@ -92,14 +91,13 @@ private:
.x = utils::rand_to(config_.size.x), .x = utils::rand_to(config_.size.x),
.y = utils::rand_to(config_.size.y), .y = utils::rand_to(config_.size.y),
}, },
.weight = config_.min_food_weight + .weight = utils::rand_in_range(config_.min_food_weight,
utils::rand_to(std::abs(config_.max_food_weight - config_.max_food_weight),
config_.min_food_weight)),
}); });
} }
} }
double food_lasted(const Food &food) { double food_lasted(const Food &food) const {
return ((current_gen_ - food.gen) * config_.gen_interval + return ((current_gen_ - food.gen) * config_.gen_interval +
time_from_last_gen_) / time_from_last_gen_) /
(config_.food_exists_gens * config_.gen_interval); (config_.food_exists_gens * config_.gen_interval);
@ -107,6 +105,7 @@ private:
private: private:
const Config config_; const Config config_;
double time_from_last_gen_ = 0; double time_from_last_gen_ = 0;
size_t current_gen_ = 0; size_t current_gen_ = 0;
std::deque<Food> food_ = {}; std::deque<Food> food_ = {};

View file

@ -5,3 +5,9 @@
constexpr Veci WORLD_SIZE = {.x = 10000, .y = 10000}; constexpr Veci WORLD_SIZE = {.x = 10000, .y = 10000};
constexpr double MIN_CONTROL_DISTANCE = 10; constexpr double MIN_CONTROL_DISTANCE = 10;
constexpr int64_t BOT_GEN_INTERVAL = 1000;
constexpr int64_t BOT_GEN_RAND_OFFSET = 200;
constexpr int64_t MIN_BOT_GEN_DISTANCE = 200;

View file

@ -14,6 +14,8 @@ class Player : public Snake {
public: public:
using Snake::Snake; using Snake::Snake;
uint get_score() { return get_length() - get_initial_length(); }
void act(float dt) override { void act(float dt) override {
int eaten = map_.eat( int eaten = map_.eat(
utils::to_world_coord(get_pos() + utils::get_screen_center()), 20); utils::to_world_coord(get_pos() + utils::get_screen_center()), 20);
@ -24,10 +26,14 @@ public:
move(dt); move(dt);
} }
void draw(Veci offset) override { void draw(Veci offset) const override {
Snake::draw(offset - utils::get_screen_center()); Snake::draw(offset - utils::get_screen_center());
} }
bool touches(const SnakeObject &other, Veci offset = {}) override {
return Snake::touches(other, offset - utils::get_screen_center());
}
protected: protected:
void change_direction(float) override { void change_direction(float) override {
Veci cursor = utils::get_cursor(); Veci cursor = utils::get_cursor();

View file

@ -12,12 +12,13 @@ public:
struct Config { struct Config {
Object obj; Object obj;
uint initial_length; uint initial_length;
int radius; int64_t radius;
}; };
SnakeObject(Config config) SnakeObject(Config config)
: canvas_config_(config), length_{config.initial_length}, : canvas_config_(config), length_{config.initial_length},
track_{config.obj.pos} {} track_{config.obj.pos} {}
//
void add(Veci pos); void add(Veci pos);
@ -50,10 +51,10 @@ public:
// //
bool touches(const SnakeObject &other); virtual bool touches(const SnakeObject &other, Veci offset = {});
protected: protected:
const Config canvas_config_; Config canvas_config_;
size_t length_; size_t length_;
std::deque<Veci> track_; std::deque<Veci> track_;
@ -82,7 +83,7 @@ public:
move(dt); move(dt);
} }
void draw(Veci offset) override { SnakeObject::draw(offset); } void draw(Veci offset) const override { SnakeObject::draw(offset); }
protected: protected:
virtual void change_direction(float dt) = 0; virtual void change_direction(float dt) = 0;
@ -97,3 +98,21 @@ protected:
Vecf direction_ = {}; Vecf direction_ = {};
float move_time_delta_ = 0; float move_time_delta_ = 0;
}; };
//
template <Color color = color::WHITE, Veci pos = {}>
inline canvas::SnakeObject::Config default_snake_object_config = {
.obj =
{
.pos = pos,
.color = color,
},
.initial_length = 20,
.radius = 10,
};
inline Snake::Config default_snake_config = {
.speed = 100.0,
.move_interval = 0.01,
};

View file

@ -5,7 +5,7 @@
namespace canvas { namespace canvas {
struct Text : public Object { struct Text : public Object {
uint scale; uint64_t scale;
uint value; uint value;
}; };

View file

@ -65,6 +65,14 @@ inline int rand_to(int x) {
return (is_negative ? -1 : 1) + rand() % std::abs(x); return (is_negative ? -1 : 1) + rand() % std::abs(x);
} }
inline int rand_in_range(int x, int y) {
if (x > y) {
std::swap(x, y);
}
return rand_to(y - x) + x;
}
} // namespace utils } // namespace utils
namespace color { namespace color {
@ -107,7 +115,7 @@ using color::Color;
class GameObject { class GameObject {
public: public:
virtual void act(float dt) = 0; virtual void act(float dt) = 0;
virtual void draw(Veci offset) = 0; virtual void draw(Veci offset) const = 0;
virtual ~GameObject() {} virtual ~GameObject() {}
}; };

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <cmath> #include <cmath>
#include <cstdint>
template <typename T> struct Vec { template <typename T> struct Vec {
T x = {}; T x = {};
@ -78,7 +79,7 @@ template <typename T> struct Vec {
// //
int len_sq() const { return dot(*this, *this); } T len_sq() const { return dot(*this, *this); }
double len() const { return std::sqrt(len_sq()); } double len() const { return std::sqrt(len_sq()); }
@ -86,14 +87,14 @@ template <typename T> struct Vec {
// //
int static dot(Vec left, Vec right) { T static dot(Vec left, Vec right) {
return left.x * right.x + left.y * right.y; return left.x * right.x + left.y * right.y;
} }
int static cross(Vec left, Vec right) { T static cross(Vec left, Vec right) {
return left.x * right.y - left.y * right.x; return left.x * right.y - left.y * right.x;
} }
}; };
using Veci = Vec<int>; using Veci = Vec<int64_t>;
using Vecf = Vec<double>; using Vecf = Vec<double>;

View file

@ -1,9 +0,0 @@
#pragma once
#include "Vec.hpp"
struct World {
float game_time = {};
Veci prev_cursor = {};
Veci cursor = {};
};

View file

@ -1,37 +1,21 @@
#include "Engine.h" #include <cstdlib>
#include <cstring>
#include <ctime> #include <ctime>
#include <memory.h> #include <iostream>
#include <stdlib.h> #include <vector>
// #include <stdio.h> // #include <stdio.h>
#include "Bot.hpp"
#include "Engine.h"
#include "Map.hpp" #include "Map.hpp"
#include "Params.hpp" #include "Params.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "Snake.hpp" #include "Snake.hpp"
#include "Text.hpp" #include "Text.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "World.hpp"
//
// You are free to modify this file
//
// is_key_pressed(int button_vk_code) - check if a key is pressed,
// use keycodes (VK_SPACE, VK_RIGHT,
// VK_LEFT, VK_UP, VK_DOWN, VK_RETURN)
//
// get_cursor_x(), get_cursor_y() - get mouse cursor position
// is_mouse_button_pressed(int button) - check if mouse button is pressed (0 -
// left button, 1 - right button) schedule_quit_game() - quit game after act()
// initialize game data in this function
void initialize() { std::srand(std::time(0)); }
float game_time = 0.0f;
World world;
namespace world {
Map map{{ Map map{{
.gen_interval = 1.0, .gen_interval = 1.0,
.food_exists_gens = 10, .food_exists_gens = 10,
@ -42,18 +26,46 @@ Map map{{
.food_color = {color::RED}, .food_color = {color::RED},
}}; }};
Player player{canvas::SnakeObject::Config{ Player player{default_snake_object_config<{color::GREEN}>,
.obj = {.pos = {}, .color = {color::GREEN}}, {
.initial_length = 10,
.radius = 10,
},
Snake::Config{
.speed = 200.0, .speed = 200.0,
.move_interval = 0.01, .move_interval = 0.01,
}, },
map}; map};
// std::vector<Bot> bots; // TODO std::vector<Bot> bots;
} // namespace world
std::vector<Bot> gen_bots(Veci player_pos, Map &map) {
std::vector<Bot> bots;
bots.reserve(WORLD_SIZE.x * WORLD_SIZE.y /
(BOT_GEN_INTERVAL * BOT_GEN_INTERVAL));
for (int x = 0; x < WORLD_SIZE.x; x += BOT_GEN_INTERVAL) {
for (int y = 0; y < WORLD_SIZE.y; y += BOT_GEN_INTERVAL) {
Veci pos{
.x = x +
utils::rand_in_range(-BOT_GEN_RAND_OFFSET, BOT_GEN_RAND_OFFSET),
.y = y -
utils::rand_in_range(-BOT_GEN_RAND_OFFSET, BOT_GEN_RAND_OFFSET),
};
if ((player_pos - pos).len_sq() >=
MIN_BOT_GEN_DISTANCE * MIN_BOT_GEN_DISTANCE) {
bots.push_back(default_bot(pos, map));
}
}
}
return bots;
}
// initialize game data in this function
void initialize() {
std::srand(std::time(0));
world::bots = gen_bots(world::player.get_pos() + utils::get_screen_center(),
world::map);
}
// this function is called to update game data, // this function is called to update game data,
// dt - time elapsed since the previous update (in seconds) // dt - time elapsed since the previous update (in seconds)
@ -62,22 +74,31 @@ void act(float dt) {
schedule_quit_game(); schedule_quit_game();
} }
map.act(dt); world::player.act(dt);
player.act(dt); for (auto it = world::bots.begin(); it != world::bots.end();) {
it->act(dt);
game_time += dt; if (world::player.touches(*it)) { // GAME OVER
world.prev_cursor = world.cursor; std::cout << "game over :(\nfinal score is " << world::player.get_score()
<< std::endl;
schedule_quit_game();
}
++it;
}
// TODO world::map.act(dt);
// for (const auto &bot : bots) { }
// if (snake.touches(bot)) { // GAME OVER
// schedule_quit_game(); void draw_score() {
// } paint::text({
// if (bot.touches(snake)) { {
// // regen bot .pos = {.x = 10, .y = 10},
// } .color = {color::BLUE},
// } },
5,
world::player.get_score(),
});
} }
// fill buffer in this function // fill buffer in this function
@ -85,18 +106,19 @@ void act(float dt) {
// (8 bits per R, G, B) // (8 bits per R, G, B)
void draw() { void draw() {
// clear backbuffer // clear backbuffer
memset(buffer, 0, SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint32_t)); std::memset(buffer, 0, SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(uint32_t));
Veci map_offset = Veci(player.get_pos()); Veci map_offset = Veci(world::player.get_pos());
player.draw(map_offset); world::player.draw(map_offset);
map.draw(map_offset);
paint::text({ for (const auto &bot : world::bots) {
{.pos = {.x = 10, .y = 10}, .color = {color::BLUE}}, bot.draw(map_offset);
5, }
player.get_length() - player.get_initial_length(),
}); world::map.draw(map_offset);
draw_score();
} }
// free game data in this function // free game data in this function

View file

@ -16,10 +16,10 @@ void SnakeObject::draw(Veci offset) const {
} }
} }
bool SnakeObject::touches(const SnakeObject &other) { bool SnakeObject::touches(const SnakeObject &other, Veci offset) {
int dist = canvas_config_.radius + other.canvas_config_.radius; int64_t dist = canvas_config_.radius + other.canvas_config_.radius;
for (const auto &elem : other.track_) { for (const auto &elem : other.track_) {
if ((get_pos() - elem).len_sq() < dist * dist) { if ((get_pos() - elem - offset).len_sq() < dist * dist) {
return true; return true;
} }
} }