From 7e67a3bd30e18a6273a5d3f24cc04a43c04f03d5 Mon Sep 17 00:00:00 2001 From: programsnail Date: Fri, 26 Jul 2024 22:36:12 +0300 Subject: [PATCH] functional bots, small refactoring, readme edit --- README.md | 19 +++++-- include/Bot.hpp | 31 +++++++++-- include/Canvas.hpp | 4 +- include/Map.hpp | 13 +++-- include/Params.hpp | 6 +++ include/Player.hpp | 8 ++- include/Snake.hpp | 27 ++++++++-- include/Text.hpp | 2 +- include/Utils.hpp | 10 +++- include/Vec.hpp | 9 ++-- include/World.hpp | 9 ---- src/Game.cpp | 126 ++++++++++++++++++++++++++------------------- src/Snake.cpp | 6 +-- 13 files changed, 179 insertions(+), 91 deletions(-) delete mode 100644 include/World.hpp diff --git a/README.md b/README.md index 708d5f5..da275f8 100644 --- a/README.md +++ b/README.md @@ -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`` \ ``xmake`` -### Run +### Run (xmake) ``xmake run`` + +### Build (cmake) +``sudo apt install g++ cmake libx11-dev`` \ +``mkdir build && cd build`` \ +``cmake -DCMAKE_BUILD_TYPE=Release ..`` \ +``make`` diff --git a/include/Bot.hpp b/include/Bot.hpp index a7e2378..083268b 100644 --- a/include/Bot.hpp +++ b/include/Bot.hpp @@ -28,16 +28,41 @@ protected: target_ = map_.find_nearest_food(get_pos()); } - // check for case when no food found if (target_.has_value()) { - direction_ = (Vecf(target_.value()) - real_pos_).norm(); - // TODO: manually change direction + if (target_ != prev_target_) { + direction_ = (Vecf(target_.value()) - real_pos_).norm(); + } + } else { + direction_ = {}; } + + prev_target_ = target_; } protected: const Config bot_config_; std::optional target_ = {}; + std::optional prev_target_ = {}; 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); +} diff --git a/include/Canvas.hpp b/include/Canvas.hpp index 4d123fe..69f0ab2 100644 --- a/include/Canvas.hpp +++ b/include/Canvas.hpp @@ -16,11 +16,11 @@ struct Object { }; struct Square : public Object { - int side; + int64_t side; }; struct Circle : public Object { - int radius; + int64_t radius; }; } // namespace canvas diff --git a/include/Map.hpp b/include/Map.hpp index 9030a4b..5be33ba 100644 --- a/include/Map.hpp +++ b/include/Map.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #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; for (auto &food : food_) { if ((pos - food.pos).len_sq() < dist * dist and not food.eaten) { @@ -48,7 +47,7 @@ public: return eaten_weight; } - void draw(Veci offset) override { + void draw(Veci offset) const override { for (const auto &food : food_) { Veci food_pos = utils::to_world_coord(food.pos - offset); if (utils::is_on_screen(food_pos) and not food.eaten) { @@ -92,14 +91,13 @@ private: .x = utils::rand_to(config_.size.x), .y = utils::rand_to(config_.size.y), }, - .weight = config_.min_food_weight + - utils::rand_to(std::abs(config_.max_food_weight - - config_.min_food_weight)), + .weight = utils::rand_in_range(config_.min_food_weight, + config_.max_food_weight), }); } } - double food_lasted(const Food &food) { + double food_lasted(const Food &food) const { return ((current_gen_ - food.gen) * config_.gen_interval + time_from_last_gen_) / (config_.food_exists_gens * config_.gen_interval); @@ -107,6 +105,7 @@ private: private: const Config config_; + double time_from_last_gen_ = 0; size_t current_gen_ = 0; std::deque food_ = {}; diff --git a/include/Params.hpp b/include/Params.hpp index 6889156..b0e69da 100644 --- a/include/Params.hpp +++ b/include/Params.hpp @@ -5,3 +5,9 @@ constexpr Veci WORLD_SIZE = {.x = 10000, .y = 10000}; 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; diff --git a/include/Player.hpp b/include/Player.hpp index 3216b05..1c2dfd6 100644 --- a/include/Player.hpp +++ b/include/Player.hpp @@ -14,6 +14,8 @@ class Player : public Snake { public: using Snake::Snake; + uint get_score() { return get_length() - get_initial_length(); } + void act(float dt) override { int eaten = map_.eat( utils::to_world_coord(get_pos() + utils::get_screen_center()), 20); @@ -24,10 +26,14 @@ public: move(dt); } - void draw(Veci offset) override { + void draw(Veci offset) const override { 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: void change_direction(float) override { Veci cursor = utils::get_cursor(); diff --git a/include/Snake.hpp b/include/Snake.hpp index 3d079e1..e2782ee 100644 --- a/include/Snake.hpp +++ b/include/Snake.hpp @@ -12,12 +12,13 @@ public: struct Config { Object obj; uint initial_length; - int radius; + int64_t radius; }; SnakeObject(Config config) : canvas_config_(config), length_{config.initial_length}, track_{config.obj.pos} {} + // void add(Veci pos); @@ -50,10 +51,10 @@ public: // - bool touches(const SnakeObject &other); + virtual bool touches(const SnakeObject &other, Veci offset = {}); protected: - const Config canvas_config_; + Config canvas_config_; size_t length_; std::deque track_; @@ -82,7 +83,7 @@ public: move(dt); } - void draw(Veci offset) override { SnakeObject::draw(offset); } + void draw(Veci offset) const override { SnakeObject::draw(offset); } protected: virtual void change_direction(float dt) = 0; @@ -97,3 +98,21 @@ protected: Vecf direction_ = {}; float move_time_delta_ = 0; }; + +// + +template +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, +}; diff --git a/include/Text.hpp b/include/Text.hpp index cb51da9..ddb7997 100644 --- a/include/Text.hpp +++ b/include/Text.hpp @@ -5,7 +5,7 @@ namespace canvas { struct Text : public Object { - uint scale; + uint64_t scale; uint value; }; diff --git a/include/Utils.hpp b/include/Utils.hpp index d936fb6..1d8b51a 100644 --- a/include/Utils.hpp +++ b/include/Utils.hpp @@ -65,6 +65,14 @@ inline int rand_to(int 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 color { @@ -107,7 +115,7 @@ using color::Color; class GameObject { public: virtual void act(float dt) = 0; - virtual void draw(Veci offset) = 0; + virtual void draw(Veci offset) const = 0; virtual ~GameObject() {} }; diff --git a/include/Vec.hpp b/include/Vec.hpp index 4f35b1f..8bbc1f4 100644 --- a/include/Vec.hpp +++ b/include/Vec.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include template struct Vec { T x = {}; @@ -78,7 +79,7 @@ template 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()); } @@ -86,14 +87,14 @@ template 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; } - int static cross(Vec left, Vec right) { + T static cross(Vec left, Vec right) { return left.x * right.y - left.y * right.x; } }; -using Veci = Vec; +using Veci = Vec; using Vecf = Vec; diff --git a/include/World.hpp b/include/World.hpp deleted file mode 100644 index 3571cca..0000000 --- a/include/World.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "Vec.hpp" - -struct World { - float game_time = {}; - Veci prev_cursor = {}; - Veci cursor = {}; -}; diff --git a/src/Game.cpp b/src/Game.cpp index 8e0c39d..cecc874 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,37 +1,21 @@ -#include "Engine.h" +#include +#include #include -#include -#include +#include +#include // #include +#include "Bot.hpp" +#include "Engine.h" #include "Map.hpp" #include "Params.hpp" #include "Player.hpp" #include "Snake.hpp" #include "Text.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{{ .gen_interval = 1.0, .food_exists_gens = 10, @@ -42,18 +26,46 @@ Map map{{ .food_color = {color::RED}, }}; -Player player{canvas::SnakeObject::Config{ - .obj = {.pos = {}, .color = {color::GREEN}}, - .initial_length = 10, - .radius = 10, - }, - Snake::Config{ +Player player{default_snake_object_config<{color::GREEN}>, + { .speed = 200.0, .move_interval = 0.01, }, map}; -// std::vector bots; // TODO +std::vector bots; +} // namespace world + +std::vector gen_bots(Veci player_pos, Map &map) { + std::vector 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, // dt - time elapsed since the previous update (in seconds) @@ -62,22 +74,31 @@ void act(float dt) { 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; - world.prev_cursor = world.cursor; + if (world::player.touches(*it)) { // GAME OVER + std::cout << "game over :(\nfinal score is " << world::player.get_score() + << std::endl; + schedule_quit_game(); + } + ++it; + } - // TODO - // for (const auto &bot : bots) { - // if (snake.touches(bot)) { // GAME OVER - // schedule_quit_game(); - // } - // if (bot.touches(snake)) { - // // regen bot - // } - // } + world::map.act(dt); +} + +void draw_score() { + paint::text({ + { + .pos = {.x = 10, .y = 10}, + .color = {color::BLUE}, + }, + 5, + world::player.get_score(), + }); } // fill buffer in this function @@ -85,18 +106,19 @@ void act(float dt) { // (8 bits per R, G, B) void draw() { // 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); - map.draw(map_offset); + world::player.draw(map_offset); - paint::text({ - {.pos = {.x = 10, .y = 10}, .color = {color::BLUE}}, - 5, - player.get_length() - player.get_initial_length(), - }); + for (const auto &bot : world::bots) { + bot.draw(map_offset); + } + + world::map.draw(map_offset); + + draw_score(); } // free game data in this function diff --git a/src/Snake.cpp b/src/Snake.cpp index a41c3d0..db6d3a5 100644 --- a/src/Snake.cpp +++ b/src/Snake.cpp @@ -16,10 +16,10 @@ void SnakeObject::draw(Veci offset) const { } } -bool SnakeObject::touches(const SnakeObject &other) { - int dist = canvas_config_.radius + other.canvas_config_.radius; +bool SnakeObject::touches(const SnakeObject &other, Veci offset) { + int64_t dist = canvas_config_.radius + other.canvas_config_.radius; 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; } }