diff --git a/.gitignore b/.gitignore index d4fb281..145dafa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# VS Code settings +.vscode/ + # Prerequisites *.d diff --git a/main.cpp b/main.cpp index ef65093..c46cbff 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); - input_thread.join(); - game_thread.join(); -return 0; -} \ No newline at end of file + int boardSize = 8; + if (argc > 1) { + int requestedSize = atoi(argv[1]); + if (requestedSize == 7 || requestedSize == 8) { + boardSize = requestedSize; + } + } + Game snakeGame(boardSize); + std::thread inputThread(&Game::runInput, &snakeGame); + std::thread gameThread(&Game::runLoop, &snakeGame); + inputThread.join(); + gameThread.join(); + return 0; +} 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..fa97efe 100644 --- a/snake.h +++ b/snake.h @@ -1,101 +1,192 @@ +#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 boardSize) + : boardSize_(boardSize) { + bodySegments_.push_back(make_pair(0, 0)); + } + + static pair computeNextHead(const pair& currentHead, char moveDirection, int boardSize) { + pair nextHead = currentHead; + if (moveDirection == 'r') { + nextHead = make_pair(currentHead.first, (currentHead.second + 1) % boardSize); + } else if (moveDirection == 'l') { + nextHead = make_pair(currentHead.first, currentHead.second == 0 ? boardSize - 1 : currentHead.second - 1); + } else if (moveDirection == 'd') { + nextHead = make_pair((currentHead.first + 1) % boardSize, currentHead.second); + } else if (moveDirection == 'u') { + nextHead = make_pair(currentHead.first == 0 ? boardSize - 1 : currentHead.first - 1, currentHead.second); } - // You could add an exit condition here, e.g., if (input == 'q') break; + return nextHead; + } + + const deque>& body() const { return bodySegments_; } + pair head() const { return bodySegments_.back(); } + + bool contains(const pair& cellPos) const { + return find(bodySegments_.begin(), bodySegments_.end(), cellPos) != bodySegments_.end(); + } + + bool willCollideWithSelf(const pair& nextHead) const { + return contains(nextHead); + } + + void growTo(const pair& nextHead) { + bodySegments_.push_back(nextHead); + } + + void moveTo(const pair& nextHead) { + bodySegments_.push_back(nextHead); + bodySegments_.pop_front(); } - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); -} +private: + int boardSize_; + deque> bodySegments_; +}; -void render_game(int size, deque> &snake, pair food){ - for(size_t i=0;i>& snakeBody, const pair& foodPos) { + cout << "Snake Game" << endl; + for (size_t row = 0; row < static_cast(boardSize); row++) { + for (size_t col = 0; col < static_cast(boardSize); col++) { + if (static_cast(row) == foodPos.first && static_cast(col) == foodPos.second) { + cout << "🍎"; + } else if (find(snakeBody.begin(), snakeBody.end(), make_pair(int(row), int(col))) != snakeBody.end()) { + cout << "🐍"; + } else { + cout << "⬜"; + } } + cout << endl; + } } - cout << endl; -} -} +}; + +class InputHandler { +public: + InputHandler() : currentDirection_('r') {} + + char direction() const { return currentDirection_.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 userInput = _getch(); + if (keyMap.find(userInput) != keyMap.end()) { + currentDirection_.store(keyMap[userInput]); + } else if (userInput == 'q') { + system("cls"); + exit(0); + } + } + sleep_for(10ms); } - return next; - -} +#else + struct termios oldSettings, newSettings; + tcgetattr(STDIN_FILENO, &oldSettings); + newSettings = oldSettings; + newSettings.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newSettings); + map keyMap = {{'d', 'r'}, {'a', 'l'}, {'w', 'u'}, {'s', 'd'}, {'q', 'q'}}; + while (true) { + char userInput = getchar(); + if (keyMap.find(userInput) != keyMap.end()) { + currentDirection_.store(keyMap[userInput]); + } else if (userInput == 'q') { + tcsetattr(STDIN_FILENO, TCSANOW, &oldSettings); + exit(0); + } + } + tcsetattr(STDIN_FILENO, TCSANOW, &oldSettings); +#endif + } + +private: + atomic currentDirection_; +}; + +class Game { +public: + Game(int boardSize = 8) + : boardSize_(boardSize), + snake_(boardSize_), + inputHandler_(), + renderer_(), + foodPos_(make_pair(rand() % boardSize_, rand() % boardSize_)) {} + + void runInput() { inputHandler_.run(); } + void runLoop() { +#ifdef _WIN32 + system("cls"); +#else + system("clear"); +#endif + pair currentHead = make_pair(0, 1); + for (;;) { + cout << "\033[H"; + char moveDir = inputHandler_.direction(); + currentHead = Snake::computeNextHead(currentHead, moveDir, boardSize_); + if (snake_.willCollideWithSelf(currentHead)) { +#ifdef _WIN32 + system("cls"); +#else + system("clear"); +#endif + cout << "Game Over" << endl; + exit(0); + } else if (currentHead.first == foodPos_.first && currentHead.second == foodPos_.second) { + foodPos_ = make_pair(rand() % boardSize_, rand() % boardSize_); + snake_.growTo(currentHead); + } else { + snake_.moveTo(currentHead); + } -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(boardSize_, snake_.body(), foodPos_); + cout << "Snake length: " << snake_.body().size() << endl; + sleep_for(500ms); } - render_game(10, snake, food); - cout << "length of snake: " << snake.size() << endl; - - sleep_for(500ms); } + +private: + int boardSize_; + Snake snake_; + InputHandler inputHandler_; + Renderer renderer_; + pair foodPos_; +}; + +inline pair get_next_head(pair currentHead, char moveDirection){ + return Snake::computeNextHead(currentHead, moveDirection, 10); } + +#endif // SNAKE_H diff --git a/snake_test.cpp b/snake_test.cpp index 42f8561..ff133b4 100644 --- a/snake_test.cpp +++ b/snake_test.cpp @@ -1,31 +1,38 @@ #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 currentPos = make_pair(3, 9); + EXPECT_EQ(Snake::computeNextHead(currentPos, '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 currentPos = make_pair(4, 0); + EXPECT_EQ(Snake::computeNextHead(currentPos, '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 currentPos = make_pair(0, 7); + EXPECT_EQ(Snake::computeNextHead(currentPos, '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 currentPos = make_pair(9, 2); + EXPECT_EQ(Snake::computeNextHead(currentPos, 'd', 10), make_pair(0, 2)); } +TEST(SnakeClass, GrowAndMoveBehaviour) { + Snake testSnake(10); + // Start with one segment at (0,0); grow to (0,1), then move to (0,2) + pair nextPos = Snake::computeNextHead(make_pair(0,0), 'r', 10); + testSnake.growTo(nextPos); + EXPECT_EQ(testSnake.body().size(), 2u); + + nextPos = Snake::computeNextHead(testSnake.head(), 'r', 10); + testSnake.moveTo(nextPos); + EXPECT_EQ(testSnake.body().size(), 2u); + EXPECT_EQ(testSnake.head(), make_pair(0,2)); +} /** * g++ -o my_tests snake_test.cpp -lgtest -lgtest_main -pthread; @@ -34,7 +41,6 @@ TEST(SnakeBehaviour, NextHeadDown) { The first part is the compilation: g++ -o my_tests hello_gtest.cpp -lgtest -lgtest_main -pthread - * g++: This invokes the GNU C++ compiler. * -o my_tests: This tells the compiler to create an executable file named my_tests. @@ -45,5 +51,4 @@ TEST(SnakeBehaviour, NextHeadDown) { Test, which saves you from writing your own main() to run the tests. * -pthread: This links the POSIX threads library, which is required by Google Test for its operation. - * -*/ \ No newline at end of file + */ diff --git a/top_scores.txt b/top_scores.txt new file mode 100644 index 0000000..d5de79d --- /dev/null +++ b/top_scores.txt @@ -0,0 +1,3 @@ +20 +20 +0