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 | |
| parent | a581945aa7c77251420489f4b22222ec15abcd7a (diff) | |
Updated dep. Added apple, collision and losing.
| -rwxr-xr-x | main | bin | 270200 -> 505752 bytes | |||
| -rw-r--r-- | main.cpp | 298 | ||||
| -rw-r--r-- | terminal-game-utils.hpp | 253 |
3 files changed, 361 insertions, 190 deletions
| Binary files differ @@ -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(); } diff --git a/terminal-game-utils.hpp b/terminal-game-utils.hpp index d7d9522..aa540bb 100644 --- a/terminal-game-utils.hpp +++ b/terminal-game-utils.hpp @@ -14,142 +14,142 @@ inline struct termios orig_termios; inline std::string newline = "\n\r"; enum class GameKey { - INVALID, - LEFTARROW, - DOWNARROW, - UPARROW, - RIGHTARROW, - ESCAPE, - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - SPACE, + INVALID, + LEFTARROW, + DOWNARROW, + UPARROW, + RIGHTARROW, + ESCAPE, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + SPACE, }; inline std::map<std::string, GameKey> KeyMap = {{"a", GameKey::A}, - {"b", GameKey::B}, - {"c", GameKey::C}, - {"d", GameKey::D}, - {"e", GameKey::E}, - {"f", GameKey::F}, - {"g", GameKey::G}, - {"h", GameKey::H}, - {"i", GameKey::I}, - {"j", GameKey::J}, - {"k", GameKey::K}, - {"l", GameKey::L}, - {"m", GameKey::M}, - {"n", GameKey::N}, - {"o", GameKey::O}, - {"p", GameKey::P}, - {"q", GameKey::Q}, - {"r", GameKey::R}, - {"s", GameKey::S}, - {"t", GameKey::T}, - {"u", GameKey::U}, - {"v", GameKey::V}, - {"w", GameKey::W}, - {"x", GameKey::X}, - {"y", GameKey::Y}, - {"z", GameKey::Z}, - {"A", GameKey::A}, - {"B", GameKey::B}, - {"C", GameKey::C}, - {"D", GameKey::D}, - {"E", GameKey::E}, - {"F", GameKey::F}, - {"G", GameKey::G}, - {"H", GameKey::H}, - {"I", GameKey::I}, - {"J", GameKey::J}, - {"K", GameKey::K}, - {"L", GameKey::L}, - {"M", GameKey::M}, - {"N", GameKey::N}, - {"O", GameKey::O}, - {"P", GameKey::P}, - {"Q", GameKey::Q}, - {"R", GameKey::R}, - {"S", GameKey::S}, - {"T", GameKey::T}, - {"U", GameKey::U}, - {"V", GameKey::V}, - {"W", GameKey::W}, - {"X", GameKey::X}, - {"Y", GameKey::Y}, - {"Z", GameKey::Z}, - {"\x1b", GameKey::ESCAPE}, - {"\x1b[D", GameKey::LEFTARROW}, - {"\x1b[B", GameKey::DOWNARROW}, - {"\x1b[A", GameKey::UPARROW}, - {"\x1b[C", GameKey::RIGHTARROW}, - {" ", GameKey::SPACE}}; + {"b", GameKey::B}, + {"c", GameKey::C}, + {"d", GameKey::D}, + {"e", GameKey::E}, + {"f", GameKey::F}, + {"g", GameKey::G}, + {"h", GameKey::H}, + {"i", GameKey::I}, + {"j", GameKey::J}, + {"k", GameKey::K}, + {"l", GameKey::L}, + {"m", GameKey::M}, + {"n", GameKey::N}, + {"o", GameKey::O}, + {"p", GameKey::P}, + {"q", GameKey::Q}, + {"r", GameKey::R}, + {"s", GameKey::S}, + {"t", GameKey::T}, + {"u", GameKey::U}, + {"v", GameKey::V}, + {"w", GameKey::W}, + {"x", GameKey::X}, + {"y", GameKey::Y}, + {"z", GameKey::Z}, + {"A", GameKey::A}, + {"B", GameKey::B}, + {"C", GameKey::C}, + {"D", GameKey::D}, + {"E", GameKey::E}, + {"F", GameKey::F}, + {"G", GameKey::G}, + {"H", GameKey::H}, + {"I", GameKey::I}, + {"J", GameKey::J}, + {"K", GameKey::K}, + {"L", GameKey::L}, + {"M", GameKey::M}, + {"N", GameKey::N}, + {"O", GameKey::O}, + {"P", GameKey::P}, + {"Q", GameKey::Q}, + {"R", GameKey::R}, + {"S", GameKey::S}, + {"T", GameKey::T}, + {"U", GameKey::U}, + {"V", GameKey::V}, + {"W", GameKey::W}, + {"X", GameKey::X}, + {"Y", GameKey::Y}, + {"Z", GameKey::Z}, + {"\x1b", GameKey::ESCAPE}, + {"\x1b[D", GameKey::LEFTARROW}, + {"\x1b[B", GameKey::DOWNARROW}, + {"\x1b[A", GameKey::UPARROW}, + {"\x1b[C", GameKey::RIGHTARROW}, + {" ", GameKey::SPACE}}; inline void DisableRawMode() { tcsetattr(0, TCSAFLUSH, &orig_termios); } inline void EnableRawMode() { - tcgetattr(0, &orig_termios); + tcgetattr(0, &orig_termios); - atexit(DisableRawMode); - struct termios raw = orig_termios; - raw.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON); - raw.c_oflag &= ~(OPOST); - raw.c_cflag |= (CS8); - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); - raw.c_cc[VMIN] = 1; - raw.c_cc[VTIME] = 0; + atexit(DisableRawMode); + struct termios raw = orig_termios; + raw.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; - tcsetattr(0, TCSAFLUSH, &raw); + tcsetattr(0, TCSAFLUSH, &raw); } inline bool InputAvailable(int timeout_ms) { - pollfd pfd{}; - pfd.fd = STDIN_FILENO; - pfd.events = POLLIN; + pollfd pfd{}; + pfd.fd = STDIN_FILENO; + pfd.events = POLLIN; - return poll(&pfd, 1, timeout_ms) > 0; + return poll(&pfd, 1, timeout_ms) > 0; } inline GameKey GetKeyPress() { - std::string keypress_string; - char c; + std::string keypress_string; + char c; - read(STDIN_FILENO, &c, 1); - keypress_string += c; + read(STDIN_FILENO, &c, 1); + keypress_string += c; - if (c == 27) - while (InputAvailable(10)) { - read(STDIN_FILENO, &c, 1); - keypress_string += c; - } + if (c == 27) + while (InputAvailable(10)) { + read(STDIN_FILENO, &c, 1); + keypress_string += c; + } - if (KeyMap.find(keypress_string) != KeyMap.end()) - return KeyMap[keypress_string]; - return GameKey::INVALID; + if (KeyMap.find(keypress_string) != KeyMap.end()) + return KeyMap[keypress_string]; + return GameKey::INVALID; } // Text printers: @@ -161,7 +161,7 @@ inline void TextRed() { std::cout << "\033[31m"; } inline void TextBlue() { std::cout << "\033[34m"; } inline void TextRGB(int r, int g, int b) { - std::cout << std::format("\033[38;2;{};{};{}m", r, g, b); + std::cout << std::format("\033[38;2;{};{};{}m", r, g, b); }; // Background printers: @@ -173,26 +173,31 @@ inline void BGRed() { std::cout << "\033[41m"; } inline void BGBlue() { std::cout << "\033[44m"; } inline void BGRGB(int r, int g, int b) { - std::cout << std::format("\033[48;2;{};{};{}m", r, g, b); + std::cout << std::format("\033[48;2;{};{};{}m", r, g, b); } inline void TerminalColorReset() { std::cout << "\033[0m"; } // Game buffer printers: - -inline void EnterGameBuffer() { std::cout << "\033[?1049h"; } - +// inline void LeaveGameBuffer() { std::cout << "\033[?1049l"; } +inline void EnterGameBuffer() { + std::cout << "\033[?1049h"; + atexit(LeaveGameBuffer); +} + inline void MoveCursor(int row, int col) { - std::cout << "\033[" << (row + 1) << ";" << (col + 1) << "H" << std::flush; + std::cout << "\033[" << (row + 1) << ";" << (col + 1) << "H" << std::flush; } inline void MakeCursorVisible() { std::cout << "\033[?25h"; } inline void MakeCursorInvisible() { - std::cout << "\033[?25l"; - atexit(MakeCursorVisible); + std::cout << "\033[?25l"; + atexit(MakeCursorVisible); } +inline void ClearTerminal() { std::cout << "\033[H\033[J"; } + }; // namespace tgu |
