Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ A classic snake game implemented in C++.
* Threaded input and game logic
* Unit tests using Google Test framework

- **Three difficulty levels**: Easy, Medium, and Hard (affects speed and score multiplier)
- **Score tracking**: Earn points based on difficulty (Easy: 10pts, Medium: 20pts, Hard: 30pts per food)
- **Pause functionality**: Press 'P' to pause/resume the game
- **Clean game over screen**: Shows final score, snake length, and difficulty
- **Real-time stats display**: Score, length, and difficulty shown during gameplay

## Getting Started

### Prerequisites
Expand Down Expand Up @@ -43,15 +49,22 @@ This will create two executables in the `build` directory: `snake_game` and `sna
To play the game, run the following command from the `build` directory:

```bash
./snake_game
g++ -o snake main.cpp snake.cpp -pthread
./snake
```

You can also specify difficulty via command line (1=Easy, 2=Medium, 3=Hard):
```bash
./snake 2 # Start with Medium difficulty
```

### Running Tests

To run the tests, execute the following command from the `build` directory:

```bash
./snake_tests
g++ -o snake_tests snake_test.cpp snake_input_test.cpp snake.cpp -lgtest -lgtest_main -pthread
./my_tests
```

## How to Play
Expand Down
48 changes: 42 additions & 6 deletions src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,48 @@
#include "snake.h"

void display_menu(){
system("clear");
cout << "╔════════════════════════════════╗" << endl;
cout << "║ SNAKE GAME CLI ║" << endl;
cout << "╚════════════════════════════════╝" << endl;
cout << endl;
cout << "Select Difficulty:" << endl;
cout << "1. Easy (Slow)" << endl;
cout << "2. Medium (Normal)" << endl;
cout << "3. Hard (Fast)" << endl;
cout << endl;
cout << "Enter your choice (1-3): ";
}

int main(int argc, char *argv[]) {
shared_ptr<InputManager> input_manager = make_shared<KeyboardInputManager>();
Game game = Game(input_manager);
int difficulty = 1;
bool play_again = true;

while(play_again){
// Show menu and get difficulty selection
if(argc > 1){
difficulty = atoi(argv[1]);
if(difficulty < 1 || difficulty > 3) difficulty = 1;
} else {
display_menu();
char choice;
cin >> choice;
difficulty = choice - '0';
if(difficulty < 1 || difficulty > 3) difficulty = 1;
}

shared_ptr<InputManager> input_manager = make_shared<KeyboardInputManager>();
Game game = Game(input_manager, difficulty);

thread input_thread(input_handler, std::ref(game));
thread game_thread(game_play, std::ref(game));
input_thread.join();
game_thread.join();
thread input_thread(input_handler, std::ref(game));
thread game_thread(game_play, std::ref(game));
input_thread.join();
game_thread.join();

// After threads join, check if we want to play again
// The game_play function handles the exit if user chooses not to play
// If we reach here after game_play exits normally, user chose to play again
play_again = true;
}
return 0;
}
38 changes: 36 additions & 2 deletions src/snake.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ void handle_single_input(Game& game){
game.set_direction(keymap[input]);
}else if (input == 'q'){
exit(0);
}else if (input == 'p' || input == 'P'){
game.togglePause();
}
}

Expand All @@ -25,14 +27,46 @@ void move_snake(deque<Cell> &snake, Cell new_head){
snake.pop_front();
}

bool display_game_over(Game& game){
system("clear");
cout << endl;
cout << "╔════════════════════════════════╗" << endl;
cout << "║ GAME OVER! ║" << endl;
cout << "╚════════════════════════════════╝" << endl;
cout << endl;
cout << "Final Score: " << game.getScore() << endl;
cout << "Snake Length: " << game.getSize() << endl;
cout << "Difficulty: ";
if(game.getDifficulty() == 1) cout << "Easy";
else if(game.getDifficulty() == 2) cout << "Medium";
else cout << "Hard";
cout << endl << endl;
cout << "Thanks for playing! 🐍" << endl;
cout << endl;
cout << "Would you like to play again?" << endl;
cout << "1. Play Again" << endl;
cout << "2. Exit" << endl;
cout << endl;
cout << "Enter your choice (1-2): ";

char choice;
cin >> choice;
return choice == '1';
}

void game_play(Game& game){
system("clear");

game.set_direction('r');
while(true){
while(!game.isGameOver()){
reset_cursor();
game.update();
game.render();

}

// Display game over and return whether to play again
bool play_again = display_game_over(game);
if(!play_again){
exit(0);
}
}
61 changes: 58 additions & 3 deletions src/snake.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,20 +149,61 @@ class Game{
// speed
std::chrono::milliseconds speed_timer = 200ms;
shared_ptr<InputManager> input_manager;
int score = 0;
int difficulty = 1; // 1=easy, 2=medium, 3=hard
bool paused = false;
bool game_over = false;

public:

Game(shared_ptr<InputManager> input_manager) : input_manager(input_manager) {
this->snake = Snake();
this->food = this->generate_random_cell();
}

Game(shared_ptr<InputManager> input_manager, int difficulty) : input_manager(input_manager), difficulty(difficulty) {
this->snake = Snake();
this->food = this->generate_random_cell();
setDifficulty(difficulty);
}

std::chrono::milliseconds getSpeed(){
return this->speed_timer;
}
int getSize(){
return this->size;
}

int getScore(){
return this->score;
}

void setDifficulty(int diff){
this->difficulty = diff;
if(diff == 1) this->speed_timer = 200ms; // Easy
else if(diff == 2) this->speed_timer = 100ms; // Medium
else if(diff == 3) this->speed_timer = 50ms; // Hard
}

int getDifficulty(){
return this->difficulty;
}

void togglePause(){
this->paused = !this->paused;
}

bool isPaused(){
return this->paused;
}

bool isGameOver(){
return this->game_over;
}

void setGameOver(){
this->game_over = true;
}

Cell generate_random_cell(){
Cell new_pos = make_pair(rand() % this->getSize(), rand() % this->getSize());
Expand All @@ -184,21 +225,34 @@ class Game{
return this->snake.get_direction();
}
void update(){
if(this->paused) return;

// check self collision
if (this->checkCollission(this->snake.get_next_position())) {
system("clear");
cout << "Game Over" << endl;
exit(0);
this->game_over = true;
return;
}else if (this->snake.contains(food)) {
this->food = this->generate_random_cell();
this->snake.grow();
this->score += (10 * this->difficulty); // Higher difficulty = more points

}else{
this->snake.move();
}
}

void render(){
// Display score and info
cout << "Score: " << this->score << " | Length: " << this->snake.getSize() << " | Difficulty: ";
if(this->difficulty == 1) cout << "Easy";
else if(this->difficulty == 2) cout << "Medium";
else cout << "Hard";

if(this->paused) cout << " | PAUSED";
cout << endl;
cout << "Controls: WASD=Move, P=Pause, Q=Quit" << endl;
cout << "───────────────────────" << endl;

for(size_t i=0;i<this->getSize();i++){
for(size_t j=0;j<this->getSize();j++){
if (snake.contains(make_pair(int(i), int(j)))) {
Expand All @@ -225,4 +279,5 @@ void input_handler(Game& game);
void reset_cursor();
void move_snake(deque<Cell> &snake, Cell new_head);
void game_play(Game& game);
bool display_game_over(Game& game);