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
114 changes: 108 additions & 6 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,111 @@
#include "snake.h"
#include <iostream>
#include <thread>
#include <atomic>
#include <map>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <chrono>

using namespace std::chrono_literals;

static void render_game(int size, const std::deque<SnakeGame::Pos> &snake,
const SnakeGame::Pos &food, const SnakeGame::Pos &poison) {
// Move cursor to top-left and draw
std::cout << "\033[H";
for (int i = 0; i < size; ++i) {
for (int j = 0; j < size; ++j) {
if (i == food.first && j == food.second) {
std::cout << "🍎";
} else if (i == poison.first && j == poison.second) {
std::cout << "☠️";
} else if (std::find(snake.begin(), snake.end(), std::make_pair(i,j)) != snake.end()) {
std::cout << "🐍";
} else {
std::cout << "⬜";
}
}
std::cout << "\n";
}
}

int main(int argc, char *argv[]) {
thread input_thread(input_handler);
thread game_thread(game_play);
input_thread.join();
game_thread.join();
return 0;
}
SnakeGame game(10);
std::atomic<char> direction('r');
std::atomic<bool> paused(false);
std::atomic<bool> stopFlag(false);

// Input thread: non-blocking read from stdin (raw mode)
std::thread input_thread([&](){
struct termios oldt, newt;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &newt);

// make stdin non-blocking
int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

std::map<char,char> keymap = { {'d','r'}, {'a','l'}, {'w','u'}, {'s','d'} };

while (!stopFlag.load()) {
int ch = getchar();
if (ch == EOF) {
std::this_thread::sleep_for(10ms);
continue;
}
char c = static_cast<char>(ch);
if (keymap.find(c) != keymap.end()) {
direction.store(keymap[c]);
} else if (c == 'p') {
paused = !paused.load();
} else if (c == 'q') {
stopFlag = true;
break;
}
}

// restore flags and termios
fcntl(STDIN_FILENO, F_SETFL, oldf);
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
});

// clear screen once
std::cout << "\033[2J\033[H";

// main game loop
while (!stopFlag.load()) {
if (paused.load()) {
std::cout << "Game Paused - press 'p' to resume\n";
std::this_thread::sleep_for(200ms);
continue;
}

auto status = game.step(direction.load());

if (status == SnakeGame::Status::SelfCollision) {
std::cout << "Game Over! You ate yourself 🐍\n";
std::cout << "Final Score: " << game.getScore() << "\n";
game.updateHighScores("highscores.txt");
break;
} else if (status == SnakeGame::Status::AtePoison) {
std::cout << "Game Over! You ate poison ☠️\n";
std::cout << "Final Score: " << game.getScore() << "\n";
game.updateHighScores("highscores.txt");
break;
}

render_game(game.getGridSize(), game.getSnake(), game.getFood(), game.getPoison());
std::cout << "Score: " << game.getScore() << " Length: " << game.getSnake().size()
<< " Speed: " << game.getSpeed() << "ms\n";

std::this_thread::sleep_for(std::chrono::milliseconds(game.getSpeed()));
}

// make sure input thread finishes
if (input_thread.joinable()) input_thread.join();

return 0;
}
233 changes: 140 additions & 93 deletions snake.h
Original file line number Diff line number Diff line change
@@ -1,101 +1,148 @@
#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <stdlib.h>
#include <termios.h>
#include <unistd.h> // for system clear
#include <map>
#ifndef SNAKE_H
#define SNAKE_H

#include <deque>
#include <vector>
#include <utility>
#include <random>
#include <algorithm>
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<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);
#include <fstream>
#include <string>

/*
Header-only refactor of the game logic into SnakeGame.
All methods defined inline so there's no separate .cpp file required.
*/

class SnakeGame {
public:
using Pos = std::pair<int,int>;
enum class Status { OK, AteFood, AtePoison, SelfCollision };

// Construct with grid size and optional RNG seed (useful for deterministic tests)
explicit SnakeGame(int gridSize = 10, unsigned rngSeed = std::random_device{}())
: gridSize_(gridSize),
snake_(),
food_(-1, -1),
poison_(-1, -1),
score_(0),
foodEaten_(0),
speed_(500),
rng_(rngSeed),
dist_(0, gridSize - 1)
{
snake_.push_back({0,0});
food_ = generateUniqueFood(snake_, {-1,-1});
poison_ = generateUniqueFood(snake_, food_);
}

// Compute next head with wrap-around
Pos getNextHead(const Pos &current, char direction) const {
int r = current.first;
int c = current.second;
if (direction == 'r') {
c = (c + 1) % gridSize_;
} else if (direction == 'l') {
c = (c + gridSize_ - 1) % gridSize_;
} else if (direction == 'd') {
r = (r + 1) % gridSize_;
} else if (direction == 'u') {
r = (r + gridSize_ - 1) % gridSize_;
}
// You could add an exit condition here, e.g., if (input == 'q') break;
return {r, c};
}
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}


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 << "⬜";
}

// Generate a food position that is not overlapping the snake or 'other'
Pos generateFood(const std::deque<Pos> &snake, const Pos &other) {
return generateUniqueFood(snake, other);
}
cout << endl;
}
}

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);

// Single simulation step; returns status
Status step(char direction) {
Pos currentHead = snake_.back();
Pos next = getNextHead(currentHead, direction);

// Self collision?
if (std::find(snake_.begin(), snake_.end(), next) != snake_.end()) {
return Status::SelfCollision;
}
return next;

}



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();

if (next == food_) {
// grow
snake_.push_back(next);
score_ += 10;
++foodEaten_;
// regenerate food and ensure poison doesn't collide
food_ = generateUniqueFood(snake_, poison_);
poison_ = generateUniqueFood(snake_, food_);
if (foodEaten_ % 10 == 0 && speed_ > 100) speed_ -= 50;
return Status::AteFood;
} else if (next == poison_) {
return Status::AtePoison;
} else {
// normal move
snake_.push_back(next);
snake_.pop_front();
return Status::OK;
}
render_game(10, snake, food);
cout << "length of snake: " << snake.size() << endl;

sleep_for(500ms);
}
}

// Accessors
const std::deque<Pos>& getSnake() const { return snake_; }
Pos getFood() const { return food_; }
Pos getPoison() const { return poison_; }
int getScore() const { return score_; }
int getSpeed() const { return speed_; }
int getGridSize() const { return gridSize_; }

// Update highscores file and return top scores (descending)
std::vector<int> updateHighScores(const std::string &path) const {
std::vector<int> scores;
std::ifstream infile(path);
int s;
while (infile >> s) scores.push_back(s);
infile.close();

scores.push_back(score_);
std::sort(scores.begin(), scores.end(), std::greater<int>());
if (scores.size() > 10) scores.resize(10);

std::ofstream outfile(path);
for (int sc : scores) outfile << sc << "\n";
outfile.close();

return scores;
}

// Test helpers (to set internal state deterministically in tests)
void setFood(const Pos &p) { food_ = p; }
void setPoison(const Pos &p) { poison_ = p; }
void setSnake(const std::deque<Pos> &s) { snake_ = s; }
void setScore(int sc) { score_ = sc; }
void setSpeed(int sp) { speed_ = sp; }

private:
int gridSize_;
std::deque<Pos> snake_;
Pos food_;
Pos poison_;
int score_;
int foodEaten_;
int speed_;

// RNG for food/poison generation
mutable std::mt19937 rng_;
std::uniform_int_distribution<int> dist_;

// internal helper to pick a free cell
Pos generateUniqueFood(const std::deque<Pos> &snake, const Pos &other) const {
// If grid is full, this would loop indefinitely; tests will avoid that case.
Pos candidate;
do {
candidate = { dist_(rng_), dist_(rng_) };
} while (std::find(snake.begin(), snake.end(), candidate) != snake.end() || candidate == other);
return candidate;
}
};

#endif // SNAKE_H
Loading