aboutsummaryrefslogtreecommitdiff
path: root/main.cpp
diff options
context:
space:
mode:
authorroy <[email protected]>2026-07-07 04:30:15 +0300
committerroy <[email protected]>2026-07-07 04:30:15 +0300
commite9bea125cce5d6ff81feca41598fb3d8e57de254 (patch)
tree266a017b82b3537ab27a66bfb04f005fef383716 /main.cpp
parenta581945aa7c77251420489f4b22222ec15abcd7a (diff)
Updated dep. Added apple, collision and losing.
Diffstat (limited to 'main.cpp')
-rw-r--r--main.cpp298
1 files changed, 232 insertions, 66 deletions
diff --git a/main.cpp b/main.cpp
index 7f6a1f3..537750c 100644
--- a/main.cpp
+++ b/main.cpp
@@ -1,4 +1,5 @@
#include "terminal-game-utils.hpp"
+#include <algorithm>
#include <chrono>
#include <cstdint>
#include <future>
@@ -22,20 +23,39 @@ class Snake {
std::string appearance;
};
+ struct mCell {
+ int x;
+ int y;
+
+ bool operator==(const mCell &other) const {
+ return (x == other.x) && (y == other.y);
+ }
+ };
+
const std::map<char, std::string> HEAD_ARROWS = {
{'l', "◁"}, {'d', "▽"}, {'u', "△"}, {'r', "▷"}};
- const std::string SNAKE_BODY = "═║╔╗╚╝";
- bool mSnakeAteApple = false;
- char mSnakeDirection = 'r';
- bool mRunning = true;
- int mDelay = 500;
- int mWidth, mHeight, mStartingSpeed;
- double mAcceleration;
+ const int GRACE_FRAMES = 3;
+ const int BOARD_OFFSET_X = 2;
+ const int BOARD_OFFSET_Y = 1;
+ const int WIDTH, HEIGHT, STARTING_DELAY, MAX_SPEED;
+ const double ACCELERATION = 0;
+
mSnakeUnit mLastDequeued;
+ mSnakeUnit mApple;
+
std::deque<mSnakeUnit> mSnake;
std::queue<tgu::GameKey> mKeyQueue;
+ std::vector<mCell> mUnoccupied;
+ std::vector<mCell> mOccupied;
+
+ int mGraceFrames = GRACE_FRAMES;
+ char mSnakeDirection = 'r';
+ int mApplesAte = 0;
+
+ bool mRunning = true;
+
std::string mRepeat(int times, std::string str) {
std::string returned;
for (int i = 0; i < times; i++)
@@ -55,9 +75,48 @@ class Snake {
return 0b0000;
}
+ mSnakeUnit mPopSnake() {
+ mSnakeUnit tr = mSnake.front();
+ mCell val(tr.x, tr.y);
+
+ mUnoccupy(val);
+ mSnake.pop_front();
+ return tr;
+ }
+
+ void mPushSnake(mSnakeUnit input) {
+ mCell val(input.x, input.y);
+
+ mOccupy(val);
+ mSnake.push_back(input);
+ }
+
+ void mOccupy(int x, int y) {
+ mCell val(x, y);
+
+ std::erase(mUnoccupied, val);
+ mOccupied.push_back(val);
+ }
+
+ void mOccupy(mCell val) {
+ std::erase(mUnoccupied, val);
+ mOccupied.push_back(val);
+ }
+
+ void mUnoccupy(int x, int y) {
+ mCell val(x, y);
+
+ std::erase(mOccupied, val);
+ mUnoccupied.push_back(val);
+ }
+
+ void mUnoccupy(mCell val) {
+ std::erase(mOccupied, val);
+ mUnoccupied.push_back(val);
+ }
+
std::string mGetTurningCharacter(mSnakeUnit Before, mSnakeUnit Current,
mSnakeUnit After) {
-
std::uint8_t BeforeDirection = mGetDirectionByte(Current, Before);
std::uint8_t AfterDirection = mGetDirectionByte(Current, After);
@@ -79,31 +138,35 @@ class Snake {
return "";
}
- void mPrintBoardOutlines() {
+ void mDrawBoardOutlines() {
+ tgu::MoveCursor(0, 0);
+ tgu::ClearTerminal();
+ tgu::ClearTerminal();
tgu::MoveCursor(0, 0);
- std::string horizontalLine = mRepeat(mWidth * 2 - 1, "═");
- std::string horizontalSpaces = std::string(mWidth * 2 - 1, ' ');
+ std::string horizontalLine = mRepeat(WIDTH * 2 + 1, "═");
+ std::string horizontalSpaces = std::string(WIDTH * 2 - 1, ' ');
std::cout << "╔" << horizontalLine + "╗\n\r";
- for (int i = 0; i < mHeight; i++) {
- std::cout << "║" + horizontalSpaces << "║\n\r";
+ for (int i = 0; i < HEIGHT; i++) {
+ std::cout << "║ " + horizontalSpaces << " ║\n\r";
}
- std::cout << "╚" << horizontalLine + "╝\n\r";
+ std::cout << "╚" << horizontalLine + "╝";
}
char mFlipDirection(char c) {
if (c == 'l')
return 'r';
- if (c == 'r')
- return 'l';
- if (c == 'd')
+ else if (c == 'd')
return 'u';
- if (c == 'u')
+ else if (c == 'u')
return 'd';
- return ' ';
+ else if (c == 'r')
+ return 'l';
+ else
+ return ' ';
}
void mSetSnakeDirection() {
@@ -121,6 +184,10 @@ class Snake {
else if (popped == tgu::GameKey::D ||
popped == tgu::GameKey::RIGHTARROW) {
PotentialSnakeDirection = 'r';
+ } else if (popped == tgu::GameKey::Q) {
+ tgu::ClearTerminal();
+ tgu::MoveCursor(0, 0);
+ exit(0);
} else
return;
@@ -132,7 +199,6 @@ class Snake {
std::tuple<int, int> mGetDirectionOffset(char c) {
tgu::MoveCursor(30, 30);
- std::cout << c;
if (c == 'l')
return std::tuple<int, int>(-1, 0);
if (c == 'd')
@@ -143,40 +209,78 @@ class Snake {
return std::tuple<int, int>(1, 0);
}
- std::cout << "Some ting wong. Direction is " << +c;
+ std::cout << "Some ting wong. Direction is " << c;
return std::tuple<int, int>(0, 0);
}
- void mSnakeStep() {
- if (!mSnakeAteApple) {
- mLastDequeued = mSnake.front();
- mSnake.pop_front();
+ void mDie() {
+ std::cout << "Die about it";
+ tgu::ClearTerminal();
+ tgu::MoveCursor(0, 0);
+ exit(0);
+ }
+
+ bool mProcessApple(int &PotentialX, int &PotentialY) {
+ if (mApple.x == PotentialX && mApple.y == PotentialY) {
+ mAddApple();
+ return true;
+ } else {
+ return false;
}
+ }
+
+ void mAdvanceSnake(int &PotentialX, int &PotentialY) {
+ mPushSnake({PotentialX, PotentialY, HEAD_ARROWS.at(mSnakeDirection)});
+ mSnake[mSnake.size() - 2].appearance = mGetTurningCharacter(
+ mSnake[mSnake.size() - 3], mSnake[mSnake.size() - 2],
+ mSnake[mSnake.size() - 1]);
+ }
+ bool IsInSnake(int x, int y) {
+ for (mSnakeUnit &u : mSnake) {
+ if (u.x == x && u.y == y)
+ return true;
+ }
+ return false;
+ }
+
+ bool mCheckDeathTick(int &PotentialX, int &PotentialY) {
+ if (PotentialX <= -1 || PotentialY <= -1 || PotentialX >= WIDTH ||
+ PotentialY >= HEIGHT || IsInSnake(PotentialX, PotentialY)) {
+ if (mGraceFrames <= 0) {
+ mRunning = false;
+ mDie();
+ } else {
+ mGraceFrames = 0;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void mSnakeStep() {
std::tuple<int, int> Offset = mGetDirectionOffset(mSnakeDirection);
int PotentialX = mSnake.back().x + std::get<0>(Offset);
int PotentialY = mSnake.back().y + std::get<1>(Offset);
- std::cout << "Potentials: " << PotentialX << ", " << PotentialY;
- std::cout << "Actual: " << mSnake.back().x << ", " << mSnake.back().y;
+ bool AteApple = mProcessApple(PotentialX, PotentialY);
- if (mSnake[mSnake.size() - 2].x != PotentialX ||
- mSnake[mSnake.size() - 2].y != PotentialY) {
+ bool DeathTicked = mCheckDeathTick(PotentialX, PotentialY);
- mSnake.push_back(
- {PotentialX, PotentialY, HEAD_ARROWS.at(mSnakeDirection)});
+ if (!DeathTicked) {
+ mGraceFrames = mGraceFrames + 1 >= GRACE_FRAMES ? GRACE_FRAMES
+ : mGraceFrames + 1;
+ mAdvanceSnake(PotentialX, PotentialY);
- mSnake[mSnake.size() - 2].appearance = mGetTurningCharacter(
- mSnake[mSnake.size() - 3], mSnake[mSnake.size() - 2],
- mSnake[mSnake.size() - 1]);
+ if (!AteApple) {
+ mLastDequeued = mPopSnake();
+ }
} else {
- mRunning = false;
- std::cout << "Stop the game. Snake somehow ate itself????";
+ mSnake.back().appearance = HEAD_ARROWS.at(mSnakeDirection);
+ mLastDequeued = mSnakeUnit(-1, -1, " ");
}
-
- tgu::MoveCursor(mHeight + 2, 0);
- std::cout << "Brother. " << mSnake.back().x << ", " << mSnake.back().y;
}
void mGameTick() {
@@ -196,35 +300,81 @@ class Snake {
}
}
- bool mHorizontalConnectors(mSnakeUnit, mSnakeUnit) { return false; }
+ bool mHorizontalConnectors(mSnakeUnit a, mSnakeUnit b) {
+ std::string horizontals = "╔╗╚╝◁▷";
+ if (a.y != b.y)
+ return false;
+
+ if (horizontals.find(a.appearance) != std::string::npos &&
+ horizontals.find(b.appearance) != std::string::npos)
+ return true;
+
+ return false;
+ }
void mDrawSnake() {
+ tgu::TextRGB(0, 255, 0);
+ if (mLastDequeued.x != -1) {
+ tgu::MoveCursor(mLastDequeued.y + BOARD_OFFSET_Y,
+ (mLastDequeued.x * 2) + BOARD_OFFSET_X - 1);
+ std::cout << " ";
+ }
+
for (int i = 0; i < mSnake.size(); i++) {
mSnakeUnit &s = mSnake[i];
if (s.appearance == "═") {
- tgu::MoveCursor(s.y + 2, (s.x * 2) + 1);
+ tgu::MoveCursor(s.y + BOARD_OFFSET_Y,
+ (s.x * 2) + BOARD_OFFSET_X - 1);
std::cout << mRepeat(3, s.appearance);
- } else if (i != 0 && mHorizontalConnectors(s, mSnake[i - 1])) {
- std::cout << "";
} else {
- tgu::MoveCursor(s.y + 2, (s.x * 2) + 2);
+ tgu::MoveCursor(s.y + BOARD_OFFSET_Y,
+ (s.x * 2) + BOARD_OFFSET_X);
std::cout << s.appearance;
}
- }
- tgu::MoveCursor(mHeight + 2, 0);
- std::cout << "Brother. " << mSnake.back().x << ", " << mSnake.back().y;
+ if (i != 0 && mHorizontalConnectors(s, mSnake[i - 1])) {
+ int x = std::max(s.x, mSnake[i - 1].x) * 2 + BOARD_OFFSET_X - 1;
+ tgu::MoveCursor(s.y + BOARD_OFFSET_Y, x);
+ std::cout << "═";
+ }
+ }
- tgu::MoveCursor(mLastDequeued.y + 2, (mLastDequeued.x * 2) + 1);
- std::cout << " ";
+ tgu::TextRed();
+ tgu::MoveCursor(mApple.y + BOARD_OFFSET_Y,
+ mApple.x * 2 + BOARD_OFFSET_X);
+ std::cout << mApple.appearance;
+ tgu::TerminalColorReset();
std::cout << std::flush;
}
+ void mInitializeApple() {
+ int iPos = rand() % mUnoccupied.size();
+ mCell apple(mUnoccupied[iPos].x, mUnoccupied[iPos].y);
+
+ mOccupy(apple.x, apple.y);
+
+ mApple = {apple.x, apple.y, "■"};
+ }
+
+ void mAddApple() {
+ mApplesAte++;
+ int iPos = rand() % mUnoccupied.size();
+ mCell apple(mUnoccupied[iPos].x, mUnoccupied[iPos].y);
+
+ mUnoccupy(mApple.x, mApple.y);
+ mOccupy(apple.x, apple.y);
+
+ mApple = {mUnoccupied[iPos].x, mUnoccupied[iPos].y, mApple.appearance};
+ }
+
void mGameLoop() {
while (mRunning) {
- std::this_thread::sleep_for(std::chrono::milliseconds(mDelay));
+ int CalculatedDelay = STARTING_DELAY - (mApplesAte * ACCELERATION);
+ int TickDelay =
+ CalculatedDelay >= MAX_SPEED ? CalculatedDelay : MAX_SPEED;
+ std::this_thread::sleep_for(std::chrono::milliseconds(TickDelay));
mGameTick();
mDrawSnake();
@@ -235,41 +385,57 @@ class Snake {
struct GameSettings {
int width = 14;
int height = 14;
- int startingSpeed = 3;
- double acceleration = 0.3;
+ int startingdelay = 400;
+ int maxspeed = 200;
+ int acceleration = 10;
};
Snake(const GameSettings &settings)
- : mWidth(settings.width), mHeight(settings.height),
- mStartingSpeed(settings.startingSpeed),
- mAcceleration(settings.acceleration) {
- if (mWidth < 5)
- mWidth = 5;
- if (mHeight < 5)
- mHeight = 5;
+ : WIDTH(settings.width), HEIGHT(settings.height),
+ STARTING_DELAY(settings.startingdelay),
+ ACCELERATION(settings.acceleration), MAX_SPEED(settings.maxspeed) {
+ if (WIDTH < 5 || HEIGHT < 5) {
+ std::cout << "Minimum board size of 5x5 not met.";
+ return;
+ }
+ }
+
+ void mFillUnoccupied() {
+ for (int i = 0; i < WIDTH; i++) {
+ for (int j = 0; j < HEIGHT; j++) {
+ mUnoccupied.push_back({i, j});
+ }
+ }
}
void StartGame() {
tgu::EnableRawMode();
tgu::MakeCursorInvisible();
tgu::EnterGameBuffer();
- mPrintBoardOutlines();
+ tgu::ClearTerminal();
+ std::cout << std::flush;
+
+ mDrawBoardOutlines();
- mSnake.push_back({0, 2, "═"});
- mSnake.push_back({1, 2, "═"});
- mSnake.push_back({2, 2, "═"});
- mSnake.push_back({3, 2, "⇒"});
+ mFillUnoccupied();
+ mPushSnake({1, 2, "═"});
+ mPushSnake({2, 2, "═"});
+ mPushSnake({3, 2, "═"});
+ mPushSnake({4, 2, "⇒"});
mSnakeDirection = 'r';
+ mInitializeApple();
+
+ mDrawSnake();
+
auto InputLoopResponse =
std::async(std::launch::async, [this] { mInputLoop(); });
mGameLoop();
- tgu::LeaveGameBuffer();
}
};
int main() {
- Snake hi({});
+ Snake hi({.startingdelay = 100});
hi.StartGame();
}