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
12 changes: 8 additions & 4 deletions main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include "snake.h"
#include <ctime>
#include <thread>

int main(int argc, char *argv[]) {
char direction = 'r';

int main(int argc, char *argv[])
{
thread input_thread(input_handler);
thread game_thread(game_play);
game_play();
input_thread.join();
game_thread.join();
return 0;
return 0;
}
288 changes: 233 additions & 55 deletions snake.h
Original file line number Diff line number Diff line change
@@ -1,101 +1,279 @@
#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 <unistd.h>
#include <map>
#include <deque>
#include <algorithm>
#include <utility>
#include <fstream>
using namespace std;
using std::chrono::system_clock;
using namespace std::this_thread;
char direction='r';

constexpr int BOARD_SIZE = 10;
extern char direction;

// ------------------ Snake Class ------------------
class Snake
{
private:
deque<pair<int, int>> body;

public:
Snake()
{
body.push_back({0, 0}); // start at (0,0)
}

void grow(pair<int, int> newHead)
{
body.push_back(newHead);
}

void move(pair<int, int> newHead)
{
body.push_back(newHead);
body.pop_front();
}

pair<int, int> getHead() const
{
return body.back();
}

int getSize() const
{
return body.size();
}

const deque<pair<int, int>> &getBody() const
{
return body;
}
};

void input_handler(){
// change terminal settings
// ------------------ Input Handling ------------------
inline void input_handler()
{
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) {
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';
else
direction = 'r';
}
}
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){
// ------------------ Rendering ------------------
inline void render_game(int size, const Snake &snake, pair<int, int> food,
pair<int, int> poison, int score)
{
const auto &body = snake.getBody();
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(body.begin(), body.end(), make_pair(i, j)) != body.end())
cout << "🐍";
}else{
else
cout << "⬜";
}
}
cout << '\n';
}
cout << endl;
cout << "Length: " << snake.getSize() << " Score: " << score << "\n";
}

// ------------------ Game Helpers ------------------
inline pair<int, int> get_next_head(pair<int, int> current, char direction)
{
if (direction == 'r')
return {current.first, (current.second + 1) % BOARD_SIZE};
else if (direction == 'l')
return {current.first, current.second == 0 ? BOARD_SIZE - 1 : current.second - 1};
else if (direction == 'd')
return {(current.first + 1) % BOARD_SIZE, current.second};
else if (direction == 'u')
return {current.first == 0 ? BOARD_SIZE - 1 : current.first - 1, current.second};
else if (direction == 'P') // <-- pause means no movement
return current;
else
return current; // safe fallback
}

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')
inline pair<int, int> spawn_food(const deque<pair<int, int>> &body)
{
vector<pair<int, int>> freeCells;
for (int i = 0; i < BOARD_SIZE; ++i)
for (int j = 0; j < BOARD_SIZE; ++j)
if (find(body.begin(), body.end(), make_pair(i, j)) == body.end())
freeCells.push_back({i, j});

if (freeCells.empty())
return {-1, -1};

return freeCells[rand() % freeCells.size()];
}

// ------------------ Score Management ------------------
inline void save_score(int score)
{
ofstream file("scores.txt", ios::app);
if (file.is_open())
{
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);
}
return next;

file << score << "\n";
file.close();
}
}

inline vector<int> load_top_scores()
{
ifstream file("scores.txt");
vector<int> scores;
int s;
while (file >> s)
scores.push_back(s);
file.close();

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

return scores;
}

void game_play(){
// ------------------ Game Loop ------------------
inline void game_play()
{
system("clear");
deque<pair<int, int>> snake;
snake.push_back(make_pair(0,0));
Snake snake;
pair<int, int> food = spawn_food(snake.getBody());
pair<int, int> poison = spawn_food(snake.getBody());

int score = 0, level = 1, baseDelay = 500;

while (true)
{
auto currentHead = snake.getHead();
auto nextHead = get_next_head(currentHead, direction);

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

if (direction == 'P')
{
this_thread::sleep_for(chrono::milliseconds(100));
continue;
}

bool willGrow = (nextHead == food);
bool collision = false;
const auto &body = snake.getBody();

if (willGrow)
{
if (find(body.begin(), body.end(), nextHead) != body.end())
collision = true;
}
else
{
auto itStart = body.begin();
if (!body.empty())
++itStart;
if (find(itStart, body.end(), nextHead) != body.end())
collision = true;
}

if (collision)
{
system("clear");
cout << "Game Over" << endl;
cout << "Game Over\n";
cout << "Final Length: " << snake.getSize() << " Final Score: " << score << "\n";
save_score(score);

cout << "\n=== Top 10 Scores ===\n";
auto 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.pop_front();
}
render_game(10, snake, food);
cout << "length of snake: " << snake.size() << endl;

sleep_for(500ms);

if (nextHead == poison)
{
system("clear");
cout << "Snake ate poison ☠️ Game Over!\n";
cout << "Final Length: " << snake.getSize() << " Final Score: " << score << "\n";
save_score(score);

cout << "\n=== Top 10 Scores ===\n";
auto topScores = load_top_scores();
for (int i = 0; i < topScores.size(); i++)
cout << (i + 1) << ". " << topScores[i] << "\n";
exit(0);
}

if (willGrow)
{
snake.grow(nextHead);
score++;
food = spawn_food(snake.getBody());

if (score % 3 == 0)
poison = spawn_food(snake.getBody());

if (food.first == -1)
{
system("clear");
cout << "You Win! Final Length: " << snake.getSize() << " Final Score: " << score << "\n";
save_score(score);

cout << "\n=== Top 10 Scores ===\n";
auto topScores = load_top_scores();
for (int i = 0; i < topScores.size(); i++)
cout << (i + 1) << ". " << topScores[i] << "\n";
exit(0);
}
}
else
{
snake.move(nextHead);
}

level = (score / 5) + 1;
render_game(BOARD_SIZE, snake, food, poison, score);
cout << "Level: " << level << "\n";

int delay_ms = max(50, baseDelay - (level - 1) * 50 - snake.getSize() * 5);
this_thread::sleep_for(chrono::milliseconds(delay_ms));
}
}

#endif
Loading