Created
August 20, 2024 04:40
-
-
Save Bahaaio/8695ca46385dee1c59a0fd182e62a90f to your computer and use it in GitHub Desktop.
Console C++ classic snake game
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
#include <thread> // to have a second thread for input | |
using namespace std; | |
#ifdef _WIN32 | |
#include <conio.h> | |
int getCharInput() { | |
return (int) getch(); | |
} | |
#else | |
// UNIX specific non-buffered input | |
#include <termios.h> | |
#include <unistd.h> | |
#include <cstdio> | |
// gets non-buffered input character | |
int getCharInput() { | |
struct termios oldattr{}, newattr{}; | |
tcgetattr(STDIN_FILENO, &oldattr); // Get current terminal attributes | |
newattr = oldattr; | |
newattr.c_lflag &= ~(ICANON | ECHO); // Disable canonical mode and echoing | |
tcsetattr(STDIN_FILENO, TCSANOW, &newattr); // Apply new attributes immediately | |
const int ch = getchar(); // Read a single character | |
tcsetattr(STDIN_FILENO, TCSANOW, &oldattr); // Restore original attributes | |
return ch; | |
} | |
#endif | |
// clears the screen | |
void clearScreen() { | |
#ifdef _WIN32 // For Windows | |
system("cls"); | |
#else // for linux and macOS | |
system("clear"); | |
#endif | |
} | |
// gets arrow input | |
int getKeyboardInput() { | |
#ifndef _WIN32 | |
getCharInput(); | |
#endif | |
getCharInput(); | |
return getCharInput(); | |
} | |
#define FOOD '*' | |
#define BODY '+' | |
#define HEAD 'O' | |
#define EDGE '#' | |
#define SCREEN_WIDTH 80 | |
#define SCREEN_HEIGHT 25 | |
// keyboard arrow keys | |
enum key { | |
#ifdef _WIN32 | |
up = 72, left = 75, right = 77, down = 80 | |
#else | |
up = 65, down, right, left | |
#endif | |
}; | |
// represents a character coordinates on the screen | |
struct coordinate { | |
int x; | |
int y; | |
coordinate(const int a, const int b) : x(a), y(b) {} | |
bool operator==(const coordinate& other) const { | |
return x == other.x and y == other.y; | |
} | |
}; | |
deque<coordinate> snake; | |
int foodX, foodY; // food position | |
int direction; // snake direction | |
bool isActive; | |
auto sleepTime = 0.13s; | |
int score; | |
// raplaces a character on the screen | |
void replace(coordinate cor, const char c) { | |
auto& [a, b] = cor; | |
cout << "\033[" << a << ";" << b << "H" << c << flush; | |
} | |
void print() { | |
cout << "\033[1;32m"; // colors snake body green | |
for (const auto& coordinate : snake) | |
replace(coordinate, BODY); // color reset | |
cout << "\033[0m" << flush; | |
replace(snake.back(), HEAD); | |
} | |
void getDir() { | |
while (isActive) | |
direction = getKeyboardInput(); | |
} | |
void drawFruit(coordinate cor, char c) { | |
cout << "\033[1;31m"; // colors fruit red | |
replace({cor.x, cor.y}, FOOD); | |
cout << "\033[0m" << flush; | |
} | |
void gameLoop() { | |
// second thread to get input without interrupting the main thread | |
thread inputDir(getDir); | |
while (true) { | |
print(); // prints the snake and score changes | |
// creating new head based on the current head | |
coordinate newHead = snake.back(); | |
auto& [x, y] = newHead; | |
// wait time | |
this_thread::sleep_for(sleepTime); | |
// changes direction based on arrow input | |
switch (direction) { | |
case key::up: | |
x -= 1; | |
break; | |
case key::down: | |
x += 1; | |
break; | |
case key::right: | |
y += 1; | |
break; | |
case key::left: | |
y -= 1; | |
break; | |
default: | |
cerr << "error"; // wrong input | |
} | |
// checks for game end when snake touches edges or hits itself | |
if (x <= 1 or x >= SCREEN_HEIGHT | |
or y <= 1 or y >= SCREEN_WIDTH - 1 | |
or find(snake.begin(), snake.end(), newHead) != snake.end()) { | |
const string over = "game over!"; | |
replace({SCREEN_HEIGHT + 1, (SCREEN_WIDTH / 2) - static_cast<int>(over.length()) + 1}, ' '); | |
cout << over << endl; | |
isActive = false; | |
inputDir.detach(); // stops the input thread | |
break; | |
} | |
// adds the new head | |
snake.emplace_back(newHead); | |
// snake eats the fruit | |
if (x == foodX and y == foodY) { | |
++score; | |
replace({SCREEN_HEIGHT, 11}, ' '); // updating score | |
cout << score; | |
auto generateRandomNumber = [](const int min, const int max) { | |
std::random_device dev; | |
std::mt19937 rng(dev()); | |
std::uniform_int_distribution<std::mt19937::result_type> dist6(min, max); | |
return static_cast<int>(dist6(rng)); | |
}; | |
// new random food position | |
foodX = generateRandomNumber(2, SCREEN_HEIGHT - 1); | |
foodY = generateRandomNumber(2, SCREEN_WIDTH - 1); | |
drawFruit({foodX, foodY}, FOOD); | |
} | |
else { | |
replace(snake.front(), ' '); | |
snake.pop_front(); // removes the tail | |
} | |
} | |
} | |
void setup() { | |
foodX = SCREEN_HEIGHT / 2; | |
foodY = SCREEN_WIDTH / 2; | |
isActive = true; | |
score = 0; | |
// initializing snake direction | |
direction = key::right; | |
// initial snake | |
snake.emplace_back(2, 2); | |
snake.emplace_back(2, 3); | |
snake.emplace_back(2, 4); | |
// initail screen clear | |
clearScreen(); | |
// draws the 4 edges | |
for (int i = 1; i < SCREEN_WIDTH - 2; i += 2) | |
replace({1, i}, EDGE); | |
for (int i = 1; i < SCREEN_WIDTH; i += 2) | |
replace({SCREEN_HEIGHT, i}, EDGE); | |
for (int i = 1; i < SCREEN_HEIGHT; ++i) | |
replace({i, 1}, EDGE); | |
for (int i = 1; i < SCREEN_HEIGHT; ++i) | |
replace({i, SCREEN_WIDTH - 1}, EDGE); | |
// drawing the fruit for the first time | |
drawFruit({foodX, foodY}, FOOD); | |
// draws score | |
replace({SCREEN_HEIGHT, 4}, ' '); | |
cout << "Score: " << score; | |
// removes an extra character after score | |
replace({SCREEN_HEIGHT, 13}, ' '); | |
} | |
int main() { | |
cout << "\033[?25l"; // hides the cursor | |
clearScreen(); | |
cout << R"( | |
____ _ ____ | |
/ ___| _ __ __ _| | _____ / ___| __ _ _ __ ___ ___ | |
\___ \| '_ \ / _` | |/ / _ \ | | _ / _` | '_ ` _ \ / _ \ | |
___) | | | | (_| | < __/ | |_| | (_| | | | | | | __/ | |
|____/|_| |_|\__,_|_|\_\___| \____|\__,_|_| |_| |_|\___| | |
)" << "Press any key to start!"; | |
getCharInput(); // waits for input to start the game | |
setup(); | |
gameLoop(); | |
// FREE PALESTINE | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment