diff --git a/main.cpp b/main.cpp index ef65093..c67e527 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,18 @@ +#include #include "snake.h" int main(int argc, char *argv[]) { - thread input_thread(input_handler); - thread game_thread(game_play); + int size = 8; + if (argc > 1) { + int requested = atoi(argv[1]); + if (requested == 7 || requested == 8) { + size = requested; + } + } + Game game(size); + std::thread input_thread(&Game::runInput, &game); + std::thread game_thread(&Game::runLoop, &game); input_thread.join(); game_thread.join(); -return 0; + return 0; } \ No newline at end of file diff --git a/snake b/snake new file mode 100644 index 0000000..f38cafc Binary files /dev/null and b/snake differ diff --git a/snake.h b/snake.h index ebe1192..009fa7e 100644 --- a/snake.h +++ b/snake.h @@ -1,101 +1,189 @@ +#ifndef SNAKE_H +#define SNAKE_H + #include #include #include #include -#include -#include -#include // for system clear +#include #include #include #include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + using namespace std; -using std::chrono::system_clock; +using namespace std::chrono_literals; using namespace std::this_thread; -char direction='r'; - - -void input_handler(){ - // change terminal settings - struct termios oldt, newt; - tcgetattr(STDIN_FILENO, &oldt); - newt = oldt; - // turn off canonical mode and echo - newt.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &newt); - map keymap = {{'d', 'r'}, {'a', 'l'}, {'w', 'u'}, {'s', 'd'}, {'q', 'q'}}; - while (true) { - char input = getchar(); - if (keymap.find(input) != keymap.end()) { - // This now correctly modifies the single, shared 'direction' variable - direction = keymap[input]; - }else if (input == 'q'){ - exit(0); + +class Snake { +public: + explicit Snake(int gridSize) + : gridSize_(gridSize) { + body_.push_back(make_pair(0, 0)); + } + + static pair computeNextHead(const pair& current, char direction, int gridSize) { + pair next = current; + if (direction == 'r') { + next = make_pair(current.first, (current.second + 1) % gridSize); + } else if (direction == 'l') { + next = make_pair(current.first, current.second == 0 ? gridSize - 1 : current.second - 1); + } else if (direction == 'd') { + next = make_pair((current.first + 1) % gridSize, current.second); + } else if (direction == 'u') { + next = make_pair(current.first == 0 ? gridSize - 1 : current.first - 1, current.second); } - // You could add an exit condition here, e.g., if (input == 'q') break; + return next; + } + + const deque>& body() const { return body_; } + pair head() const { return body_.back(); } + + bool contains(const pair& cell) const { + return find(body_.begin(), body_.end(), cell) != body_.end(); + } + + bool willCollideWithSelf(const pair& nextHead) const { + return contains(nextHead); + } + + void growTo(const pair& nextHead) { + body_.push_back(nextHead); + } + + void moveTo(const pair& nextHead) { + body_.push_back(nextHead); + body_.pop_front(); } - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); -} +private: + int gridSize_; + deque> body_; +}; -void render_game(int size, deque> &snake, pair food){ - for(size_t i=0;i>& snake, const pair& food) { + cout << "Snake Game" << endl; + for (size_t i = 0; i < static_cast(size); i++) { + for (size_t j = 0; j < static_cast(size); j++) { + if (static_cast(i) == food.first && static_cast(j) == food.second) { + cout << "🍎"; + } else if (find(snake.begin(), snake.end(), make_pair(int(i), int(j))) != snake.end()) { + cout << "🐍"; + } else { + cout << "⬜"; + } } + cout << endl; + } } - cout << endl; -} -} +}; + +class InputHandler { +public: + InputHandler() : direction_('r') {} + + char direction() const { return direction_.load(); } -pair get_next_head(pair current, char direction){ - pair next; - if(direction =='r'){ - next = make_pair(current.first,(current.second+1) % 10); - }else if (direction=='l') - { - next = make_pair(current.first, current.second==0?9:current.second-1); - }else if(direction =='d'){ - next = make_pair((current.first+1)%10,current.second); - }else if (direction=='u'){ - next = make_pair(current.first==0?9:current.first-1, current.second); + void run() { +#ifdef _WIN32 + map keymap = {{'d', 'r'}, {'a', 'l'}, {'w', 'u'}, {'s', 'd'}, {'q', 'q'}}; + for (;;) { + if (_kbhit()) { + char input = _getch(); + if (keymap.find(input) != keymap.end()) { + direction_.store(keymap[input]); + } else if (input == 'q') { + system("cls"); + exit(0); + } + } + sleep_for(10ms); } - return next; - -} +#else + struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + map keymap = {{'d', 'r'}, {'a', 'l'}, {'w', 'u'}, {'s', 'd'}, {'q', 'q'}}; + while (true) { + char input = getchar(); + if (keymap.find(input) != keymap.end()) { + direction_.store(keymap[input]); + } else if (input == 'q') { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + exit(0); + } + } + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); +#endif + } + +private: + atomic direction_; +}; + +class Game { +public: + Game(int size = 8) + : size_(size), snake_(size_), input_(), renderer_(), food_(make_pair(rand() % size_, rand() % size_)) {} + void runInput() { input_.run(); } + void runLoop() { +#ifdef _WIN32 + system("cls"); +#else + system("clear"); +#endif + pair head = make_pair(0, 1); + for (;;) { + cout << "\033[H"; + char dir = input_.direction(); + head = Snake::computeNextHead(head, dir, size_); + + if (snake_.willCollideWithSelf(head)) { +#ifdef _WIN32 + system("cls"); +#else + system("clear"); +#endif + cout << "Game Over" << endl; + exit(0); + } else if (head.first == food_.first && head.second == food_.second) { + food_ = make_pair(rand() % size_, rand() % size_); + snake_.growTo(head); + } else { + snake_.moveTo(head); + } -void game_play(){ - system("clear"); - deque> snake; - snake.push_back(make_pair(0,0)); - - pair food = make_pair(rand() % 10, rand() % 10); - for(pair head=make_pair(0,1);; head = get_next_head(head, direction)){ - // send the cursor to the top - cout << "\033[H"; - // check self collision - if (find(snake.begin(), snake.end(), head) != snake.end()) { - system("clear"); - cout << "Game Over" << endl; - exit(0); - }else if (head.first == food.first && head.second == food.second) { - // grow snake - food = make_pair(rand() % 10, rand() % 10); - snake.push_back(head); - }else{ - // move snake - snake.push_back(head); - snake.pop_front(); + renderer_.render(size_, snake_.body(), food_); + cout << "length of snake: " << snake_.body().size() << endl; + sleep_for(500ms); } - render_game(10, snake, food); - cout << "length of snake: " << snake.size() << endl; - - sleep_for(500ms); } + +private: + int size_; + Snake snake_; + InputHandler input_; + Renderer renderer_; + pair food_; +}; + +inline pair get_next_head(pair current, char direction){ + return Snake::computeNextHead(current, direction, 10); } + +#endif // SNAKE_H + diff --git a/snake_test.cpp b/snake_test.cpp index 42f8561..15138a4 100644 --- a/snake_test.cpp +++ b/snake_test.cpp @@ -1,29 +1,38 @@ -#include + #include #include "snake.h" -TEST(SnakeBehaviour, NextHeadRight) { - pair current = make_pair(rand() % 10, rand() % 10); - EXPECT_EQ(get_next_head(current, 'r'),make_pair(current.first,current.second+1)); - +TEST(SnakeBehaviour, NextHeadRightWraps) { + pair current = make_pair(3, 9); + EXPECT_EQ(Snake::computeNextHead(current, 'r', 10), make_pair(3, 0)); } -TEST(SnakeBehaviour, NextHeadLeft) { - pair current = make_pair(rand() % 10, rand() % 10); - EXPECT_EQ(get_next_head(current, 'l'),make_pair(current.first,current.second-1)); - +TEST(SnakeBehaviour, NextHeadLeftWraps) { + pair current = make_pair(4, 0); + EXPECT_EQ(Snake::computeNextHead(current, 'l', 10), make_pair(4, 9)); } -TEST(SnakeBehaviour, NextHeadUp) { - pair current = make_pair(rand() % 10, rand() % 10); - EXPECT_EQ(get_next_head(current, 'u'),make_pair(current.first-1,current.second)); +TEST(SnakeBehaviour, NextHeadUpWraps) { + pair current = make_pair(0, 7); + EXPECT_EQ(Snake::computeNextHead(current, 'u', 10), make_pair(9, 7)); } -TEST(SnakeBehaviour, NextHeadDown) { - pair current = make_pair(rand() % 10, rand() % 10); - EXPECT_EQ(get_next_head(current, 'd'),make_pair(current.first+1,current.second)); - +TEST(SnakeBehaviour, NextHeadDownWraps) { + pair current = make_pair(9, 2); + EXPECT_EQ(Snake::computeNextHead(current, 'd', 10), make_pair(0, 2)); +} + +TEST(SnakeClass, GrowAndMoveBehaviour) { + Snake s(10); + // Start with one segment at (0,0); grow to (0,1), then move to (0,2) + pair next = Snake::computeNextHead(make_pair(0,0), 'r', 10); + s.growTo(next); + EXPECT_EQ(s.body().size(), 2u); + next = Snake::computeNextHead(s.head(), 'r', 10); + s.moveTo(next); + EXPECT_EQ(s.body().size(), 2u); + EXPECT_EQ(s.head(), make_pair(0,2)); }