#include "terminal-game-utils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include struct termios orig_termios; class Snake { private: struct mSnakeUnit { int x; int y; std::string appearance; }; const std::map 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; mSnakeUnit mLastDequeued; std::deque mSnake; std::queue mKeyQueue; std::string mRepeat(int times, std::string str) { std::string returned; for (int i = 0; i < times; i++) returned += str; return returned; } std::uint8_t mGetDirectionByte(mSnakeUnit pos, mSnakeUnit goal) { if (pos.x > goal.x) return 0b0001; if (pos.x < goal.x) return 0b1000; if (pos.y > goal.y) return 0b0100; if (pos.y < goal.y) return 0b0010; return 0b0000; } std::string mGetTurningCharacter(mSnakeUnit Before, mSnakeUnit Current, mSnakeUnit After) { std::uint8_t BeforeDirection = mGetDirectionByte(Current, Before); std::uint8_t AfterDirection = mGetDirectionByte(Current, After); std::uint8_t CharacterBytes = BeforeDirection + AfterDirection; if (CharacterBytes == 0b1001) return "═"; if (CharacterBytes == 0b0110) return "║"; if (CharacterBytes == 0b1010) return "╔"; if (CharacterBytes == 0b0011) return "╗"; if (CharacterBytes == 0b1100) return "╚"; if (CharacterBytes == 0b0101) return "╝"; return ""; } void mPrintBoardOutlines() { tgu::MoveCursor(0, 0); std::string horizontalLine = mRepeat(mWidth * 2 - 1, "═"); std::string horizontalSpaces = std::string(mWidth * 2 - 1, ' '); std::cout << "╔" << horizontalLine + "╗\n\r"; for (int i = 0; i < mHeight; i++) { std::cout << "║" + horizontalSpaces << "║\n\r"; } std::cout << "╚" << horizontalLine + "╝\n\r"; } char mFlipDirection(char c) { if (c == 'l') return 'r'; if (c == 'r') return 'l'; if (c == 'd') return 'u'; if (c == 'u') return 'd'; return ' '; } void mSetSnakeDirection() { tgu::GameKey popped = mKeyQueue.front(); mKeyQueue.pop(); char PotentialSnakeDirection; if (popped == tgu::GameKey::W || popped == tgu::GameKey::UPARROW) PotentialSnakeDirection = 'u'; else if (popped == tgu::GameKey::A || popped == tgu::GameKey::LEFTARROW) PotentialSnakeDirection = 'l'; else if (popped == tgu::GameKey::S || popped == tgu::GameKey::DOWNARROW) PotentialSnakeDirection = 'd'; else if (popped == tgu::GameKey::D || popped == tgu::GameKey::RIGHTARROW) { PotentialSnakeDirection = 'r'; } else return; if (PotentialSnakeDirection == mFlipDirection(mSnakeDirection)) return; else mSnakeDirection = PotentialSnakeDirection; } std::tuple mGetDirectionOffset(char c) { tgu::MoveCursor(30, 30); std::cout << c; if (c == 'l') return std::tuple(-1, 0); if (c == 'd') return std::tuple(0, 1); if (c == 'u') return std::tuple(0, -1); if (c == 'r') { return std::tuple(1, 0); } std::cout << "Some ting wong. Direction is " << +c; return std::tuple(0, 0); } void mSnakeStep() { if (!mSnakeAteApple) { mLastDequeued = mSnake.front(); mSnake.pop_front(); } std::tuple 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; if (mSnake[mSnake.size() - 2].x != PotentialX || mSnake[mSnake.size() - 2].y != PotentialY) { mSnake.push_back( {PotentialX, PotentialY, HEAD_ARROWS.at(mSnakeDirection)}); mSnake[mSnake.size() - 2].appearance = mGetTurningCharacter( mSnake[mSnake.size() - 3], mSnake[mSnake.size() - 2], mSnake[mSnake.size() - 1]); } else { mRunning = false; std::cout << "Stop the game. Snake somehow ate itself????"; } tgu::MoveCursor(mHeight + 2, 0); std::cout << "Brother. " << mSnake.back().x << ", " << mSnake.back().y; } void mGameTick() { if (mKeyQueue.size() != 0) { mSetSnakeDirection(); } mSnakeStep(); } void mInputLoop() { tgu::GameKey key; while (mRunning) { key = tgu::GetKeyPress(); if (mKeyQueue.size() <= 2) mKeyQueue.push(key); } } bool mHorizontalConnectors(mSnakeUnit, mSnakeUnit) { return false; } void mDrawSnake() { for (int i = 0; i < mSnake.size(); i++) { mSnakeUnit &s = mSnake[i]; if (s.appearance == "═") { tgu::MoveCursor(s.y + 2, (s.x * 2) + 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); std::cout << s.appearance; } } tgu::MoveCursor(mHeight + 2, 0); std::cout << "Brother. " << mSnake.back().x << ", " << mSnake.back().y; tgu::MoveCursor(mLastDequeued.y + 2, (mLastDequeued.x * 2) + 1); std::cout << " "; std::cout << std::flush; } void mGameLoop() { while (mRunning) { std::this_thread::sleep_for(std::chrono::milliseconds(mDelay)); mGameTick(); mDrawSnake(); } } public: struct GameSettings { int width = 14; int height = 14; int startingSpeed = 3; double acceleration = 0.3; }; 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; } void StartGame() { tgu::EnableRawMode(); tgu::MakeCursorInvisible(); tgu::EnterGameBuffer(); mPrintBoardOutlines(); mSnake.push_back({0, 2, "═"}); mSnake.push_back({1, 2, "═"}); mSnake.push_back({2, 2, "═"}); mSnake.push_back({3, 2, "⇒"}); mSnakeDirection = 'r'; auto InputLoopResponse = std::async(std::launch::async, [this] { mInputLoop(); }); mGameLoop(); tgu::LeaveGameBuffer(); } }; int main() { Snake hi({}); hi.StartGame(); }