diff options
| author | roy <[email protected]> | 2026-07-07 04:30:15 +0300 |
|---|---|---|
| committer | roy <[email protected]> | 2026-07-07 04:30:15 +0300 |
| commit | e9bea125cce5d6ff81feca41598fb3d8e57de254 (patch) | |
| tree | 266a017b82b3537ab27a66bfb04f005fef383716 /main.cpp | |
| parent | a581945aa7c77251420489f4b22222ec15abcd7a (diff) | |
Updated dep. Added apple, collision and losing.
Diffstat (limited to 'main.cpp')
| -rw-r--r-- | main.cpp | 298 |
1 files changed, 232 insertions, 66 deletions
@@ -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(); } |
