diff --git a/main.cpp b/main.cpp index ef65093..e042375 100644 --- a/main.cpp +++ b/main.cpp @@ -1,9 +1,14 @@ #include "snake.h" +#include + +int main(int argc, char *argv[]) +{ + srand((unsigned)time(nullptr)); -int main(int argc, char *argv[]) { thread input_thread(input_handler); - thread game_thread(game_play); + thread game_thread(game_play); + input_thread.join(); game_thread.join(); -return 0; + return 0; } \ No newline at end of file diff --git a/snake.h b/snake.h index ebe1192..cc511d3 100644 --- a/snake.h +++ b/snake.h @@ -4,98 +4,289 @@ #include #include #include -#include // for system clear +#include #include #include #include +#include +#include using namespace std; using std::chrono::system_clock; -using namespace std::this_thread; -char direction='r'; +constexpr int BOARD_SIZE = 10; // Board size -void input_handler(){ - // change terminal settings +char direction = 'r'; + +// Input handler (runs in a separate thread) +void input_handler() +{ struct termios oldt, newt; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; - // turn off canonical mode and echo - newt.c_lflag &= ~(ICANON | ECHO); + newt.c_lflag &= ~(ICANON | ECHO); // turn off canonical mode and echo tcsetattr(STDIN_FILENO, TCSANOW, &newt); + map keymap = {{'d', 'r'}, {'a', 'l'}, {'w', 'u'}, {'s', 'd'}, {'q', 'q'}}; - while (true) { + while (true) + { char input = getchar(); - if (keymap.find(input) != keymap.end()) { - // This now correctly modifies the single, shared 'direction' variable + if (keymap.find(input) != keymap.end()) + { direction = keymap[input]; - }else if (input == 'q'){ + } + else if (input == 'q') + { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); exit(0); } - // You could add an exit condition here, e.g., if (input == 'q') break; + else if (input == 'p') + { + if (direction != 'P') + direction = 'P'; // pause + else + direction = 'r'; // resume moving right or previous direction + } } tcsetattr(STDIN_FILENO, TCSANOW, &oldt); } - -void render_game(int size, deque> &snake, pair food){ - for(size_t i=0;i> &snake, pair food, pair poison, int score) +{ + for (int i = 0; i < size; ++i) + { + for (int j = 0; j < size; ++j) + { + if (i == food.first && j == food.second) + { cout << "🍎"; - }else if (find(snake.begin(), snake.end(), make_pair(int(i), int(j))) != snake.end()) { + } + else if (i == poison.first && j == poison.second) + { + cout << "☠️"; + } + else if (find(snake.begin(), snake.end(), make_pair(i, j)) != snake.end()) + { cout << "🐍"; - }else{ + } + else + { cout << "⬜"; } + } + cout << '\n'; } - cout << endl; + cout << "Length: " << snake.size() << " Score: " << score << "\n"; } + +// Compute next head (wraps around) +pair get_next_head(pair current, char direction) +{ + pair next; + if (direction == 'r') + { + next = make_pair(current.first, (current.second + 1) % BOARD_SIZE); + } + else if (direction == 'l') + { + next = make_pair(current.first, current.second == 0 ? BOARD_SIZE - 1 : current.second - 1); + } + else if (direction == 'd') + { + next = make_pair((current.first + 1) % BOARD_SIZE, current.second); + } + else + { // 'u' + next = make_pair(current.first == 0 ? BOARD_SIZE - 1 : current.first - 1, current.second); + } + return next; } -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') +// Spawn food at a location not occupied by the snake +pair spawn_food(const deque> &snake) +{ + vector> freeCells; + for (int i = 0; i < BOARD_SIZE; ++i) { - 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); + for (int j = 0; j < BOARD_SIZE; ++j) + { + pair pos = make_pair(i, j); + if (find(snake.begin(), snake.end(), pos) == snake.end()) + { + freeCells.push_back(pos); + } } - return next; - + } + + if (freeCells.empty()) + return make_pair(-1, -1); // board full + + int idx = rand() % freeCells.size(); + return freeCells[idx]; +} +// ✅ Save score to file +void save_score(int score) +{ + ofstream file("scores.txt", ios::app); // append mode + if (file.is_open()) + { + file << score << "\n"; + file.close(); + } } +// ✅ Load scores and return top 10 +vector load_top_scores() +{ + ifstream file("scores.txt"); + vector scores; + int s; + while (file >> s) + { + scores.push_back(s); + } + file.close(); + + // Sort descending + sort(scores.begin(), scores.end(), greater()); + + // Keep only top 10 + if (scores.size() > 10) + { + scores.resize(10); + } -void game_play(){ + return scores; +} + +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()) { + snake.push_back(make_pair(0, 0)); // starting segment + + pair food = spawn_food(snake); + pair poison = spawn_food(snake); // spawn initial poison + + int score = 0; + int level = 1; // starting level + int baseDelay = 500; // initial delay in ms + + while (true) + { + pair currentHead = snake.back(); + pair nextHead = get_next_head(currentHead, direction); + + cout << "\033[H"; // move cursor to top-left + + bool willGrow = (nextHead == food); + bool collision = false; + + // ✅ Pause logic + if (direction == 'P') + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + continue; // skip movement, freeze game + } + + if (willGrow) + { + if (find(snake.begin(), snake.end(), nextHead) != snake.end()) + collision = true; + } + else + { + auto itStart = snake.begin(); + if (!snake.empty()) + ++itStart; // skip tail + if (find(itStart, snake.end(), nextHead) != snake.end()) + collision = true; + } + + // Snake collides with itself + if (collision) + { system("clear"); - cout << "Game Over" << endl; + cout << "Game Over\n"; + cout << "Final Length: " << snake.size() << " Final Score: " << score << "\n"; + + save_score(score); + + cout << "\n=== Top 10 Scores ===\n"; + vector topScores = load_top_scores(); + for (int i = 0; i < topScores.size(); i++) + { + cout << (i + 1) << ". " << topScores[i] << "\n"; + } + + exit(0); + } + + // Snake eats poison + if (nextHead == poison) + { + system("clear"); + cout << "Snake ate poison ☠️ Game Over!\n"; + cout << "Final Length: " << snake.size() << " Final Score: " << score << "\n"; + + save_score(score); + + cout << "\n=== Top 10 Scores ===\n"; + vector topScores = load_top_scores(); + for (int i = 0; i < topScores.size(); i++) + { + cout << (i + 1) << ". " << topScores[i] << "\n"; + } + 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.push_back(nextHead); + + if (willGrow) + { + score += 1; + food = spawn_food(snake); + + // 🔹 Respawn poison every 3 points + if (score % 3 == 0) + { + poison = spawn_food(snake); + } + + if (food.first == -1) + { + system("clear"); + cout << "You Win! Final Length: " << snake.size() << " Final Score: " << score << "\n"; + + save_score(score); + + cout << "\n=== Top 10 Scores ===\n"; + vector topScores = load_top_scores(); + for (int i = 0; i < topScores.size(); i++) + { + cout << (i + 1) << ". " << topScores[i] << "\n"; + } + + exit(0); + } + } + else + { snake.pop_front(); } - render_game(10, snake, food); - cout << "length of snake: " << snake.size() << endl; - - sleep_for(500ms); + + // Level calculation: increase level every 5 points + level = (score / 5) + 1; + + // Render game (with poison + score) + render_game(BOARD_SIZE, snake, food, poison, score); + + cout << "Level: " << level << "\n"; + + // Dynamic speed: faster as level increases (min 50ms) + int delay_ms = max(50, baseDelay - (level - 1) * 50 - (int)snake.size() * 5); + std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms)); } -} +} \ No newline at end of file