diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b83e20..98793ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,7 @@ project(snake) set(SOURCES src/main.cpp - src/element.cpp - src/window.cpp + src/engine.cpp ) add_executable(snake ${SOURCES}) diff --git a/include/element.hpp b/include/engine.hpp similarity index 61% rename from include/element.hpp rename to include/engine.hpp index 8b21630..dd23ced 100644 --- a/include/element.hpp +++ b/include/engine.hpp @@ -1,12 +1,10 @@ #ifndef ELEMENT_HPP #define ELEMENT_HPP -#include -#include #include -#include -class Window; +constexpr int ROW = 11; +constexpr int COL = 11; enum class Direction { UP, DOWN, LEFT, RIGHT @@ -14,32 +12,35 @@ enum class Direction { struct vec2 { int x, y; - vec2() : x(0), y(0) {} vec2(int x, int y) : x(x), y(y) {} void set(int _x, int _y) { x = _x; y = _y; } auto operator <=> (const vec2&) const = default; }; -class SnakeGame { +struct Window { + std::array, ROW> view; + void draw_display(); + void update_display(vec2 &head, vec2 &fruit, std::vector &buf); +}; + +class Engine { public: - SnakeGame(); - ~SnakeGame(); + Engine(); + ~Engine(); void run(); private: - std::unique_ptr win; + Window win; std::vector buf; vec2 head { COL / 2, ROW / 2 }; vec2 fruit { rand() % COL, rand() % ROW }; vec2 prev_head { head.x, head.y }; - struct { - Direction dir = Direction::RIGHT; - uint8_t m_score = 0; - } state; + Direction dir = Direction::RIGHT; + unsigned int m_score = 0; bool process_input(); void determine_index_zero_pos(); void move_tail_indicies(); - void add_tail_and_respawn_fruit(std::array, ROW> view); + void add_tail_and_respawn_fruit(); bool exit_summary(); }; diff --git a/include/globals.hpp b/include/globals.hpp deleted file mode 100644 index 90a0ed6..0000000 --- a/include/globals.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef GLOBALS_HPP -#define GLOBALS_HPP -constexpr int ROW = 11; -constexpr int COL = 11; -constexpr int MAX_LEN = (ROW * COL) + 3; -constexpr int HEAD = 0; -constexpr int PREVIOUS_HEAD = 1; -constexpr int FRUIT = 2; -#endif // GLOBALS_HPP diff --git a/include/window.hpp b/include/window.hpp deleted file mode 100644 index 427fd92..0000000 --- a/include/window.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef WINDOW_HPP -#define WINDOW_HPP - -#include -#include -#include - -struct Window { - std::array, ROW> view; - void draw_display(); - void update_display(vec2 &head, vec2 &fruit, std::vector &buf); -}; - -#endif // WINDOW_HPP diff --git a/src/element.cpp b/src/element.cpp deleted file mode 100644 index 64a6c57..0000000 --- a/src/element.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -SnakeGame::SnakeGame() { - std::srand(std::time(0)); - initscr(); - cbreak(); - noecho(); - curs_set(0); - nodelay(stdscr, true); - win = std::make_unique(); -} - -SnakeGame::~SnakeGame() { - endwin(); -} - -void SnakeGame::run() { - while (exit_summary() && process_input()) { - add_tail_and_respawn_fruit(win->view); - determine_index_zero_pos(); - move_tail_indicies(); - - win->update_display(head, fruit, buf); - win->draw_display(); - std::this_thread::sleep_for(std::chrono::milliseconds((int) (200))); - } -} - -bool SnakeGame::process_input() { - int ch; - while((ch = getch()) != ERR) { - switch(ch) { - case 'w': if(state.dir != Direction::DOWN) state.dir = Direction::UP; break; - case 's': if(state.dir != Direction::UP) state.dir = Direction::DOWN; break; - case 'd': if(state.dir != Direction::LEFT) state.dir = Direction::RIGHT; break; - case 'a': if(state.dir != Direction::RIGHT) state.dir = Direction::LEFT; break; - case 'q': return false; - } - } - return true; -} - -bool SnakeGame::exit_summary() { - if(buf.size() >= MAX_LEN - 1) { - std::cout << "You filled the whole board and won! Score: " << state.m_score << "\n"; - return false; - } - for(int i : std::ranges::views::iota(0, (int)buf.size())) { - if(buf[i] == head) { - std::cout << "You collided with yourself and died! Score: " << state.m_score << "\n"; - return false; - } - } - return true; -} - -void SnakeGame::determine_index_zero_pos() { - prev_head.set(head.x, head.y); - - switch(state.dir) { - case Direction::LEFT: head.x = (head.x + COL - 1) % COL; break; - case Direction::RIGHT: head.x = (head.x + 1) % COL; break; - case Direction::UP: head.y = (head.y + ROW - 1) % ROW; break; - case Direction::DOWN: head.y = (head.y + 1) % ROW; break; - } -} - -void SnakeGame::move_tail_indicies() { - if(buf.empty()) return; - buf.pop_back(); - buf.insert(buf.begin(), prev_head); -} - -void SnakeGame::add_tail_and_respawn_fruit(std::array, ROW> view) { - if(fruit != head) return; - int row, col; - do { - row = (rand() % ROW); - col = (rand() % COL); - } while (view[row][col] != '.'); - fruit.set(row, col); - buf.insert(buf.begin(), vec2 { ROW, COL }); - state.m_score++; -} diff --git a/src/engine.cpp b/src/engine.cpp new file mode 100644 index 0000000..c95ab99 --- /dev/null +++ b/src/engine.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void Window::draw_display() { + for(int i : std::ranges::views::iota(0, ROW)) { + for(int j : std::ranges::views::iota(0, COL)) { + mvaddch(i, j * 2, view[i][j]); + } + } + refresh(); +} + +void Window::update_display(vec2 &head, vec2 &fruit, std::vector &buf) { + for(auto& row : view) row.fill('.'); + view[head.y][head.x] = '*'; + view[fruit.y][fruit.x] = 'o'; + + for(const auto& segment : buf) { + if(segment.x < COL && segment.y < ROW) { + view[segment.y][segment.x] = '~'; + } + } +} + +Engine::Engine() { + std::srand(std::time(0)); + initscr(); + cbreak(); + noecho(); + curs_set(0); + nodelay(stdscr, true); +} + +Engine::~Engine() { + endwin(); +} + +void Engine::run() { + while (exit_summary() && process_input()) { + add_tail_and_respawn_fruit(); + determine_index_zero_pos(); + move_tail_indicies(); + + win.update_display(head, fruit, buf); + win.draw_display(); + std::this_thread::sleep_for(std::chrono::milliseconds((int) (200))); + } +} + +bool Engine::process_input() { + int ch; + while((ch = getch()) != ERR) { + if(ch == 'q') { return false; } + else if(ch == 'w') { if(dir != Direction::DOWN) dir = Direction::UP; } + else if(ch == 's') { if(dir != Direction::UP) dir = Direction::DOWN; } + else if(ch == 'd') { if(dir != Direction::LEFT) dir = Direction::RIGHT; } + else if(ch == 'a') { if(dir != Direction::RIGHT) dir = Direction::LEFT; } + } + return true; +} + +bool Engine::exit_summary() { + if(buf.size() >= ROW * COL) { + std::cout << "You filled the whole board and won! Score: " << m_score << "\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + return false; + } + if(std::any_of(buf.begin(), buf.end(), [&](const vec2& segment) { return segment == head; })) { + std::cout << "You collided with yourself and died! Score: " << m_score << "\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + return false; + } + return true; +} + +void Engine::determine_index_zero_pos() { + prev_head.set(head.x, head.y); + + if(dir == Direction::LEFT) { head.x = (head.x + COL - 1) % COL; } + else if(dir == Direction::RIGHT) { head.x = (head.x + 1) % COL; } + else if(dir == Direction::UP) { head.y = (head.y + ROW - 1) % ROW; } + else if(dir == Direction::DOWN) { head.y = (head.y + 1) % ROW; } +} + +void Engine::move_tail_indicies() { + if(buf.empty()) return; + buf.pop_back(); + buf.insert(buf.begin(), prev_head); +} + +void Engine::add_tail_and_respawn_fruit() { + if(fruit != head) return; + vec2 new_pos { 0, 0 }; + do { new_pos.set((rand() % ROW), (rand() % COL)); } + while (new_pos == head || std::any_of(buf.begin(), buf.end(), [&](const vec2& seg) { return seg == new_pos; })); + fruit = new_pos; + buf.insert(buf.begin(), vec2 { ROW, COL }); + m_score++; +} diff --git a/src/main.cpp b/src/main.cpp index e5dda5d..b7f50dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,7 +1,7 @@ -#include +#include int main () { - SnakeGame engine; + Engine engine; engine.run(); return 0; } diff --git a/src/window.cpp b/src/window.cpp deleted file mode 100644 index 8a7fafd..0000000 --- a/src/window.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include -#include -#include - -void Window::draw_display() { - for(int i : std::ranges::views::iota(0, ROW)) { - for(int j : std::ranges::views::iota(0, COL)) { - mvaddch(i, j * 2, view[i][j]); - } - } - refresh(); -} - -void Window::update_display(vec2 &head, vec2 &fruit, std::vector &buf) { - for(auto& row : view) row.fill('.'); - view[head.y][head.x] = '*'; - view[fruit.y][fruit.x] = 'o'; - - for(int i : std::ranges::views::iota(0, (int)buf.size())) { - const auto &segments = buf.at(i); - if(segments.x < COL && segments.y < ROW) { - view[segments.y][segments.x] = '~'; - } - } -}