diff --git a/.gitignore b/.gitignore index d4fb281..544f547 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# VS Code settings +.vscode/ + # Prerequisites *.d @@ -38,4 +41,4 @@ *.app # debug information files -*.dwo +*.dwo \ No newline at end of file diff --git a/main.cpp b/main.cpp index ef65093..a5ee391 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,13 @@ #include "snake.h" +#include + +int main() { + Game game; + thread input_thread(&Game::handle_input, &game); + thread game_thread(&Game::play, &game); -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 + + return 0; +} diff --git a/snake.h b/snake.h index ebe1192..ac25d62 100644 --- a/snake.h +++ b/snake.h @@ -1,101 +1,169 @@ +#pragma once #include #include +#include +#include +#include +#include #include #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; +using namespace std::chrono; + +constexpr int GRID_SIZE = 10; +constexpr int INITIAL_SPEED = 500; // milliseconds +constexpr int POISON_INTERVAL = 5000; // milliseconds +constexpr int MAX_TOP_SCORES = 10; + +class Game { +private: + char direction = 'r'; + bool paused = false; + vector top_scores; + deque> snake; + pair food; + pair poison; + int speed = INITIAL_SPEED; + int score = 0; + int food_eaten = 0; + int poison_timer = 0; + +public: + Game() { + snake.push_back({0,0}); + food = generate_food(); + poison = generate_poison(); + load_top_scores(); } - tcsetattr(STDIN_FILENO, TCSANOW, &oldt); -} - - -void render_game(int size, deque> &snake, pair food){ - for(size_t i=0;i> s) top_scores.push_back(s); + infile.close(); + sort(top_scores.rbegin(), top_scores.rend()); + if (top_scores.size() > MAX_TOP_SCORES) top_scores.resize(MAX_TOP_SCORES); + } + + void save_top_scores() { + ofstream outfile("top_scores.txt"); + for (int s : top_scores) outfile << s << endl; + outfile.close(); + } + + pair get_next_head(pair current, char dir) { + if(dir == 'r') return {current.first, (current.second + 1) % GRID_SIZE}; + if(dir == 'l') return {current.first, current.second == 0 ? GRID_SIZE - 1 : current.second - 1}; + if(dir == 'd') return {(current.first + 1) % GRID_SIZE, current.second}; + if(dir == 'u') return {current.first == 0 ? GRID_SIZE - 1 : current.first - 1, current.second}; + return current; + } + + pair generate_food() { + pair f; + do { f = {rand() % GRID_SIZE, rand() % GRID_SIZE}; } + while (find(snake.begin(), snake.end(), f) != snake.end()); + return f; + } + + pair generate_poison() { + pair p; + do { p = {rand() % GRID_SIZE, rand() % GRID_SIZE}; } + while (find(snake.begin(), snake.end(), p) != snake.end() || p == food); + return p; + } + + void render() { + for(int i = 0; i < GRID_SIZE; ++i) { + for(int j = 0; j < GRID_SIZE; ++j) { + pair cell = {i,j}; + if(cell == food) cout << "🍎"; + else if(cell == poison) cout << "☠️"; + else if(find(snake.begin(), snake.end(), cell) != snake.end()) cout << "🐍"; + else cout << "⬜"; } + cout << endl; + } + cout << "Length: " << snake.size() << " | Score: " << score << " | Food eaten: " << food_eaten + << " | Speed: " << speed << "ms" << endl; } - cout << endl; -} -} - -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 handle_input() { + 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'}}; + + while(true) { + char input = getchar(); + if(keymap.find(input) != keymap.end()) direction = keymap[input]; + else if(input == 'p') paused = !paused; + else if(input == 'q') exit(0); } - return next; - -} - - - -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(); + + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + } + + void play() { + pair head = {0,1}; + + while(true) { + cout << "\033[H"; // move cursor to top + + while(paused) { + system("clear"); + render(); + cout << "=== PAUSED ===\nPress 'p' to resume...\n"; + sleep_for(milliseconds(200)); + } + + head = get_next_head(head, direction); + + if(find(snake.begin(), snake.end(), head) != snake.end() || head == poison) { + end_game(head == poison ? "Snake ate poison ☠️." : "Snake hit itself."); + } + + if(head == food) { + snake.push_back(head); + food = generate_food(); + food_eaten++; + score += 10; + if(food_eaten % 10 == 0 && speed > 100) speed += 50; + } else { + snake.push_back(head); + snake.pop_front(); + } + + poison_timer += speed; + if(poison_timer >= POISON_INTERVAL) { + poison = generate_poison(); + poison_timer = 0; + } + + render(); + sleep_for(milliseconds(speed)); } - render_game(10, snake, food); - cout << "length of snake: " << snake.size() << endl; - - sleep_for(500ms); } -} + +private: + void end_game(const string& message) { + system("clear"); + top_scores.push_back(score); + sort(top_scores.rbegin(), top_scores.rend()); + if(top_scores.size() > MAX_TOP_SCORES) top_scores.resize(MAX_TOP_SCORES); + save_top_scores(); + + cout << "Game Over! " << message << "\nTop 10 Scores:\n"; + for(int s : top_scores) cout << s << endl; + exit(0); + } +}; diff --git a/top_scores.txt b/top_scores.txt new file mode 100644 index 0000000..cf8ba84 --- /dev/null +++ b/top_scores.txt @@ -0,0 +1,3 @@ +20 +20 +0 \ No newline at end of file