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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# VS Code settings
.vscode/

# Prerequisites
*.d

Expand Down
21 changes: 15 additions & 6 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
#include <thread>
#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;
}
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;
}
Binary file added snake
Binary file not shown.
247 changes: 169 additions & 78 deletions snake.h
Original file line number Diff line number Diff line change
@@ -1,101 +1,192 @@
#ifndef SNAKE_H
#define SNAKE_H

#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h> // for system clear
#include <cstdlib>
#include <map>
#include <deque>
#include <algorithm>
#include <atomic>

#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#else
#include <termios.h>
#include <unistd.h>
#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<char, char> 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<int, int> computeNextHead(const pair<int, int>& currentHead, char moveDirection, int boardSize) {
pair<int, int> 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<pair<int, int>>& body() const { return bodySegments_; }
pair<int, int> head() const { return bodySegments_.back(); }

bool contains(const pair<int, int>& cellPos) const {
return find(bodySegments_.begin(), bodySegments_.end(), cellPos) != bodySegments_.end();
}

bool willCollideWithSelf(const pair<int, int>& nextHead) const {
return contains(nextHead);
}

void growTo(const pair<int, int>& nextHead) {
bodySegments_.push_back(nextHead);
}

void moveTo(const pair<int, int>& nextHead) {
bodySegments_.push_back(nextHead);
bodySegments_.pop_front();
}
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}

private:
int boardSize_;
deque<pair<int, int>> bodySegments_;
};

void render_game(int size, deque<pair<int, int>> &snake, pair<int, int> food){
for(size_t i=0;i<size;i++){
for(size_t 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()) {
cout << "🐍";
}else{
cout << "⬜";
class Renderer {
public:
void render(int boardSize, const deque<pair<int, int>>& snakeBody, const pair<int, int>& foodPos) {
cout << "Snake Game" << endl;
for (size_t row = 0; row < static_cast<size_t>(boardSize); row++) {
for (size_t col = 0; col < static_cast<size_t>(boardSize); col++) {
if (static_cast<int>(row) == foodPos.first && static_cast<int>(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<int,int> get_next_head(pair<int,int> current, char direction){
pair<int, int> 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<char, char> 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<char, char> 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<char> 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<int, int> 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<pair<int, int>> snake;
snake.push_back(make_pair(0,0));

pair<int, int> food = make_pair(rand() % 10, rand() % 10);
for(pair<int, int> 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<int, int> foodPos_;
};

inline pair<int,int> get_next_head(pair<int,int> currentHead, char moveDirection){
return Snake::computeNextHead(currentHead, moveDirection, 10);
}

#endif // SNAKE_H
45 changes: 25 additions & 20 deletions snake_test.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
#include <gtest/gtest.h>
#include "snake.h"


TEST(SnakeBehaviour, NextHeadRight) {
pair<int, int> 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<int, int> currentPos = make_pair(3, 9);
EXPECT_EQ(Snake::computeNextHead(currentPos, 'r', 10), make_pair(3, 0));
}


TEST(SnakeBehaviour, NextHeadLeft) {
pair<int, int> 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<int, int> currentPos = make_pair(4, 0);
EXPECT_EQ(Snake::computeNextHead(currentPos, 'l', 10), make_pair(4, 9));
}

TEST(SnakeBehaviour, NextHeadUp) {
pair<int, int> 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<int, int> currentPos = make_pair(0, 7);
EXPECT_EQ(Snake::computeNextHead(currentPos, 'u', 10), make_pair(9, 7));
}

TEST(SnakeBehaviour, NextHeadDown) {
pair<int, int> 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<int, int> 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<int,int> 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;
Expand All @@ -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.
Expand All @@ -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.
*
*/
*/
3 changes: 3 additions & 0 deletions top_scores.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
20
20
0