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
15 changes: 12 additions & 3 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);
int size = 8;
if (argc > 1) {
int requested = atoi(argv[1]);
if (requested == 7 || requested == 8) {
size = requested;
}
}
Game game(size);
std::thread input_thread(&Game::runInput, &game);
std::thread game_thread(&Game::runLoop, &game);
input_thread.join();
game_thread.join();
return 0;
return 0;
}
Binary file added snake
Binary file not shown.
244 changes: 166 additions & 78 deletions snake.h
Original file line number Diff line number Diff line change
@@ -1,101 +1,189 @@
#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 gridSize)
: gridSize_(gridSize) {
body_.push_back(make_pair(0, 0));
}

static pair<int, int> computeNextHead(const pair<int, int>& current, char direction, int gridSize) {
pair<int, int> next = current;
if (direction == 'r') {
next = make_pair(current.first, (current.second + 1) % gridSize);
} else if (direction == 'l') {
next = make_pair(current.first, current.second == 0 ? gridSize - 1 : current.second - 1);
} else if (direction == 'd') {
next = make_pair((current.first + 1) % gridSize, current.second);
} else if (direction == 'u') {
next = make_pair(current.first == 0 ? gridSize - 1 : current.first - 1, current.second);
}
// You could add an exit condition here, e.g., if (input == 'q') break;
return next;
}

const deque<pair<int, int>>& body() const { return body_; }
pair<int, int> head() const { return body_.back(); }

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

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

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

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

private:
int gridSize_;
deque<pair<int, int>> body_;
};

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 size, const deque<pair<int, int>>& snake, const pair<int, int>& food) {
cout << "Snake Game" << endl;
for (size_t i = 0; i < static_cast<size_t>(size); i++) {
for (size_t j = 0; j < static_cast<size_t>(size); j++) {
if (static_cast<int>(i) == food.first && static_cast<int>(j) == food.second) {
cout << "🍎";
} else if (find(snake.begin(), snake.end(), make_pair(int(i), int(j))) != snake.end()) {
cout << "🐍";
} else {
cout << "⬜";
}
}
cout << endl;
}
}
cout << endl;
}
}
};

class InputHandler {
public:
InputHandler() : direction_('r') {}

char direction() const { return direction_.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 input = _getch();
if (keymap.find(input) != keymap.end()) {
direction_.store(keymap[input]);
} else if (input == 'q') {
system("cls");
exit(0);
}
}
sleep_for(10ms);
}
return next;

}
#else
struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
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()) {
direction_.store(keymap[input]);
} else if (input == 'q') {
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
exit(0);
}
}
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
#endif
}

private:
atomic<char> direction_;
};

class Game {
public:
Game(int size = 8)
: size_(size), snake_(size_), input_(), renderer_(), food_(make_pair(rand() % size_, rand() % size_)) {}

void runInput() { input_.run(); }

void runLoop() {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
pair<int, int> head = make_pair(0, 1);
for (;;) {
cout << "\033[H";
char dir = input_.direction();
head = Snake::computeNextHead(head, dir, size_);

if (snake_.willCollideWithSelf(head)) {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
cout << "Game Over" << endl;
exit(0);
} else if (head.first == food_.first && head.second == food_.second) {
food_ = make_pair(rand() % size_, rand() % size_);
snake_.growTo(head);
} else {
snake_.moveTo(head);
}

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(size_, snake_.body(), food_);
cout << "length of snake: " << snake_.body().size() << endl;
sleep_for(500ms);
}
render_game(10, snake, food);
cout << "length of snake: " << snake.size() << endl;

sleep_for(500ms);
}

private:
int size_;
Snake snake_;
InputHandler input_;
Renderer renderer_;
pair<int, int> food_;
};

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

#endif // SNAKE_H

41 changes: 25 additions & 16 deletions snake_test.cpp
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
#include <gtest/gtest.h>
#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> current = make_pair(3, 9);
EXPECT_EQ(Snake::computeNextHead(current, '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> current = make_pair(4, 0);
EXPECT_EQ(Snake::computeNextHead(current, '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> current = make_pair(0, 7);
EXPECT_EQ(Snake::computeNextHead(current, '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> current = make_pair(9, 2);
EXPECT_EQ(Snake::computeNextHead(current, 'd', 10), make_pair(0, 2));
}

TEST(SnakeClass, GrowAndMoveBehaviour) {
Snake s(10);
// Start with one segment at (0,0); grow to (0,1), then move to (0,2)
pair<int,int> next = Snake::computeNextHead(make_pair(0,0), 'r', 10);
s.growTo(next);
EXPECT_EQ(s.body().size(), 2u);
next = Snake::computeNextHead(s.head(), 'r', 10);
s.moveTo(next);
EXPECT_EQ(s.body().size(), 2u);
EXPECT_EQ(s.head(), make_pair(0,2));
}


Expand Down