diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..0e60d09 Binary files /dev/null and b/.DS_Store differ diff --git a/main.cpp b/main.cpp index ef65093..42fbdbb 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,7 @@ #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; + Game game(10); + game.run(); + return 0; } \ No newline at end of file diff --git a/my_tests b/my_tests new file mode 100755 index 0000000..a1a1243 Binary files /dev/null and b/my_tests differ diff --git a/snake b/snake new file mode 100755 index 0000000..7e96615 Binary files /dev/null and b/snake differ diff --git a/snake.cpp b/snake.cpp new file mode 100644 index 0000000..1b8111b --- /dev/null +++ b/snake.cpp @@ -0,0 +1,174 @@ +#include "snake.h" + +// Snake Class Implementation +Snake::Snake(int board_size) : board_size(board_size), direction('r') { + body.push_front(Point{0, 0}); +} + +void Snake::move() { + Point next_head = get_next_head(); + body.push_front(next_head); + body.pop_back(); +} + +void Snake::grow() { + Point next_head = get_next_head(); + body.push_front(next_head); +} + +bool Snake::check_collision() { + Point head = get_head(); + for (deque::const_iterator it = body.begin() + 1; it != body.end(); ++it) { + if (head == *it) { + return true; + } + } + return false; +} + +Point Snake::get_head() { + return body.front(); +} + +const deque& Snake::get_body() const { + return body; +} + +void Snake::set_direction(char new_direction) { + // FIX: Only prevent reversal if the snake is longer than 1 segment + if (body.size() > 1 && + ((direction == 'r' && new_direction == 'l') || + (direction == 'l' && new_direction == 'r') || + (direction == 'u' && new_direction == 'd') || + (direction == 'd' && new_direction == 'u'))) { + return; + } + direction = new_direction; +} + +char Snake::get_direction() { + return direction; +} + +Point Snake::get_next_head() { + Point current_head = get_head(); + Point next_head = current_head; + if (direction == 'r') { + next_head.y = (current_head.y + 1) % board_size; + } else if (direction == 'l') { + next_head.y = (current_head.y == 0) ? board_size - 1 : current_head.y - 1; + } else if (direction == 'd') { + next_head.x = (current_head.x + 1) % board_size; + } else if (direction == 'u') { + next_head.x = (current_head.x == 0) ? board_size - 1 : current_head.x - 1; + } + return next_head; +} + +// Food Class Implementation +Food::Food(int board_size, const deque& snake_body) : board_size(board_size) { + generate_new_food(snake_body); +} + +void Food::generate_new_food(const deque& snake_body) { + while (true) { + position = Point{rand() % board_size, rand() % board_size}; + bool on_snake = false; + for (deque::const_iterator it = snake_body.begin(); it != snake_body.end(); ++it) { + if (position == *it) { + on_snake = true; + break; + } + } + if (!on_snake) { + break; + } + } +} + +Point Food::get_position() { + return position; +} + +// Game Class Implementation +Game::Game(int size) : board_size(size), snake(size), food(size, snake.get_body()), game_over(false), input_thread(&Game::process_input, this) {} + +void Game::run() { + while (!game_over) { + cout << "\033[H"; + update(); + render(); + cout << "Length of snake: " << snake.get_body().size() << endl; + sleep_for(chrono::milliseconds(500)); + } + + input_thread.join(); + system("clear"); + cout << "Game Over" << endl; +} + +void Game::render() { + system("clear"); + for (int i = 0; i < board_size; ++i) { + for (int j = 0; j < board_size; ++j) { + Point p = {i, j}; + if (p == food.get_position()) { + cout << "🍎"; + } else { + bool is_snake_part = false; + const deque& snake_body = snake.get_body(); + for (deque::const_iterator it = snake_body.begin(); it != snake_body.end(); ++it) { + if (p == *it) { + is_snake_part = true; + break; + } + } + if (is_snake_part) { + cout << "🐍"; + } else { + cout << "⬜"; + } + } + } + cout << endl; + } +} + +void Game::update() { + if (snake.check_collision()) { + game_over = true; + return; + } + + if (snake.get_head() == food.get_position()) { + snake.grow(); + food.generate_new_food(snake.get_body()); + } else { + snake.move(); + } +} + +void Game::process_input() { + struct termios oldt, newt; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + + map keymap; + keymap['d'] = 'r'; + keymap['a'] = 'l'; + keymap['w'] = 'u'; + keymap['s'] = 'd'; + + while (!game_over) { + char input = getchar(); + if (keymap.count(input)) { + snake.set_direction(keymap[input]); + } else if (input == 'q') { + game_over = true; + } + } + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); +} \ No newline at end of file diff --git a/snake.h b/snake.h index ebe1192..3a07a1c 100644 --- a/snake.h +++ b/snake.h @@ -4,98 +4,64 @@ #include #include #include -#include // for system clear +#include #include #include #include + using namespace std; using std::chrono::system_clock; 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); - } - // You could add an exit condition here, e.g., if (input == 'q') break; +struct Point { + int x, y; + bool operator==(const Point& other) const { + return x == other.x && y == other.y; } - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); -} +}; +class Snake { +public: + Snake(int board_size); + void move(); + void grow(); + bool check_collision(); + Point get_head(); + const deque& get_body() const; + void set_direction(char new_direction); + char get_direction(); -void render_game(int size, deque> &snake, pair food){ - for(size_t i=0;i body; + char direction; + int board_size; + Point get_next_head(); +}; -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); - } - return next; - -} +class Food { +public: + Food(int board_size, const deque& snake_body); + void generate_new_food(const deque& snake_body); + Point get_position(); +private: + Point position; + int board_size; +}; +class Game { +public: + Game(int size); + void run(); -void game_play(){ - system("clear"); - deque> snake; - snake.push_back(make_pair(0,0)); +private: + void render(); + void update(); + void process_input(); - 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(); - } - render_game(10, snake, food); - cout << "length of snake: " << snake.size() << endl; - - sleep_for(500ms); - } -} + int board_size; + Snake snake; + Food food; + bool game_over; + thread input_thread; +}; \ No newline at end of file diff --git a/snake_test.cpp b/snake_test.cpp index 42f8561..6e7a6a7 100644 --- a/snake_test.cpp +++ b/snake_test.cpp @@ -1,49 +1,78 @@ #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(SnakeTest, InitialPosition) { + Snake snake(10); + ASSERT_EQ(snake.get_body().size(), 1); + EXPECT_EQ(snake.get_head().x, 0); + EXPECT_EQ(snake.get_head().y, 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(SnakeTest, MoveRight) { + Snake snake(10); + snake.set_direction('r'); + snake.move(); + EXPECT_EQ(snake.get_head().x, 0); + EXPECT_EQ(snake.get_head().y, 1); } -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(SnakeTest, MoveLeftWrapAround) { + Snake snake(10); + snake.set_direction('l'); + snake.move(); + EXPECT_EQ(snake.get_head().x, 0); + EXPECT_EQ(snake.get_head().y, 9); } -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(SnakeTest, MoveDown) { + Snake snake(10); + snake.set_direction('d'); + snake.move(); + EXPECT_EQ(snake.get_head().x, 1); + EXPECT_EQ(snake.get_head().y, 0); } +TEST(SnakeTest, MoveUpWrapAround) { + Snake snake(10); + snake.set_direction('u'); + snake.move(); + EXPECT_EQ(snake.get_head().x, 9); + EXPECT_EQ(snake.get_head().y, 0); +} -/** - * g++ -o my_tests snake_test.cpp -lgtest -lgtest_main -pthread; - * This command is a two-part shell command. Let's break it down. +TEST(SnakeTest, Grow) { + Snake snake(10); + snake.grow(); + ASSERT_EQ(snake.get_body().size(), 2); + EXPECT_EQ(snake.get_head().x, 0); + EXPECT_EQ(snake.get_head().y, 1); +} - The first part is the compilation: - g++ -o my_tests hello_gtest.cpp -lgtest -lgtest_main -pthread +TEST(SnakeTest, Collision) { + Snake snake(10); + snake.grow(); // {0,1}, {0,0} + snake.grow(); // {0,2}, {0,1}, {0,0} + snake.grow(); // {0,3}, {0,2}, {0,1}, {0,0} + snake.set_direction('d'); + snake.grow(); // {1,3}, {0,3}, {0,2}, {0,1}, {0,0} + snake.set_direction('l'); + snake.grow(); // {1,2}, {1,3}, {0,3}, {0,2}, {0,1}, {0,0} + snake.set_direction('u'); + snake.grow(); // {0,2}, {1,2}, {1,3}, {0,3}, {0,2}, {0,1}, {0,0} + EXPECT_TRUE(snake.check_collision()); +} +TEST(FoodTest, InitialPosition) { + Snake snake(10); + Food food(10, snake.get_body()); + Point pos = food.get_position(); + EXPECT_GE(pos.x, 0); + EXPECT_LT(pos.x, 10); + EXPECT_GE(pos.y, 0); + EXPECT_LT(pos.y, 10); +} - * g++: This invokes the GNU C++ compiler. - * -o my_tests: This tells the compiler to create an executable file named - my_tests. - * hello_gtest.cpp: This is the C++ source file containing your tests. - * -lgtest: This links the Google Test library, which provides the core testing - framework. - * -lgtest_main: This links a pre-compiled main function provided by Google - 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 +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file