Skip to content

Instantly share code, notes, and snippets.

@b3x206
Last active February 8, 2024 09:29
Show Gist options
  • Save b3x206/bd64ae205e38bbea2298b915451e0f59 to your computer and use it in GitHub Desktop.
Save b3x206/bd64ae205e38bbea2298b915451e0f59 to your computer and use it in GitHub Desktop.
a bad snake for console
// This is snake for windows console that can be compiled using mingw
// Compile using => $ g++ ./terrible_snake.cpp -o snake.exe <optional flags>
// (msvc will probably work, didn't test though)
// Features :
// * Has color
// * Is playable (wow!!1!)
// * Has argument options
// * Runs fine (unless you make the console larger because buffering is not done by the winapi but by std::cout which sucks, but the winapi requires buffer sizing-juggling so idk)
// * Single-file (but that is indeed a stretch, AppArgument is a .h + .cpp file because the linker says no [symbol AAÆ@@@@=DFAghsdao234<<<__<<< on terrible_snake.obj not found or some thing idk])
// * Github gist editor also has a new feature called "mix tabs with spaces™", thanks.
#include <iostream> // I/O console
#include <thread> // Threading
#include <chrono> // Time (threading)
#include <vector> // Arrays (why call it vector)
#include <cmath> // Math (unused except for debug)
// #include <fstream> // Serialization (for high score, this is still TODO)
#include <functional> // Argument delegates
#include <string>
#include <sstream>
#include <windows.h> // Windows API (could use for console buffer, used for console cursor instead lol)
#include <conio.h> // Input side of I/O console
// Because winapi is very good and defines these as macros
#undef min
#undef max
// Winapi mostly uses 'WORD' for colors, not for pointers (which the stdint.h numbers are incompatible with)
typedef uint16_t WORD;
// TODO : Lower code re-use (by doing methods/prev-next methods and/or having a snake::update() method)
// Settings
int ms_delay_draw = 80; // Delay (in milliseconds)
int64_t area_sz_x = 20, area_sz_y = 10; // 20 x 10 area
std::string exit_message = "You lost."; // Message printing for exit. (game over message by default)
// TODO 2 : stage implement
// Note : The 'V0G0N' escape sequence is for allowing () characters in the string
// const char* stage = R"V0G0N(
//
// )V0G0N";
// Windows stuff
// (can be ported to linux / terminals using ansi escape chars, i compile this using gcc anyways)
// (look who forgot about conio lol)
namespace console
{
// @brief The StdOut handle used for the console, for the winapi methods.
// This can be gathered in functions (as it's a light api call) as well but it is ok as is.a
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
// @brief Color defined for the windows consoles. Unlike ansi escapes, these are more limited.
// (but ansi terminal escapes are hell to implement so have to live with this subset..)
enum class Color : WORD
{
BLACK = 0,
DARKBLUE = FOREGROUND_BLUE,
DARKGREEN = FOREGROUND_GREEN,
DARKCYAN = FOREGROUND_GREEN | FOREGROUND_BLUE,
DARKRED = FOREGROUND_RED,
DARKMAGENTA = FOREGROUND_RED | FOREGROUND_BLUE,
DARKYELLOW = FOREGROUND_RED | FOREGROUND_GREEN,
DARKGRAY = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
GRAY = FOREGROUND_INTENSITY,
BLUE = FOREGROUND_INTENSITY | FOREGROUND_BLUE,
GREEN = FOREGROUND_INTENSITY | FOREGROUND_GREEN,
CYAN = FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE,
RED = FOREGROUND_INTENSITY | FOREGROUND_RED,
MAGENTA = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE,
YELLOW = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN,
WHITE = FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
};
// Note : Smaller data structures are fine to pass-by-copy, in fact it is faster to pass by copy for smaller datas.
// @brief Clears the console screen with the given 'fill' parameter.
// @param fill : The character to fill the screen with for clearing. This is an ascii character.
void clear(const char fill = ' ')
{
CONSOLE_SCREEN_BUFFER_INFO screen;
DWORD written;
// Should use an actual buffer like this instead of std cout
GetConsoleScreenBufferInfo(handle, &screen);
FillConsoleOutputCharacterA(handle, fill, screen.dwSize.X * screen.dwSize.Y, { 0, 0 }, &written);
FillConsoleOutputAttribute(handle, (WORD)Color::WHITE, screen.dwSize.X * screen.dwSize.Y, { 0, 0 }, &written);
SetConsoleCursorPosition(handle, { 0, 0 });
}
// @brief Sets the console cursor/caret visibility
// @param visible : Whether to hide/show the cursor.
void visibility_cursor(const bool visible)
{
CONSOLE_CURSOR_INFO info;
GetConsoleCursorInfo(handle, &info);
info.bVisible = visible;
info.dwSize = 100; // dunno what this is
SetConsoleCursorInfo(handle, &info);
}
// @brief Sets the console cursor position.
void set_cursor(const short int x, const short int y)
{
SetConsoleCursorPosition(handle, { x, y });
}
// @brief Sets the console colors, will affect characters printed after this thing's call.
void set_color(const Color c)
{
SetConsoleTextAttribute(handle, (WORD)c);
}
// @brief Waits for an enter input with 10ms delays. Uses <conio.h>'s methods.
bool wait_enter(void)
{
if (_kbhit())
{
switch (_getch())
{
case 'Q':
case 'q':
return true;
// Enter is apparently '\r'
case '\r':
return true;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
return false;
}
}
// Math / string utils
namespace util
{
template<typename TInt = int64_t>
TInt clamp(const TInt value, const TInt min, const TInt max)
{
static_assert(std::is_integral<TInt>::value || std::is_floating_point<TInt>::value, "[util::clamp] Type isn't an integer / float.");
// Fix common overflow bugs (with unsigned, signed should be more careful)
// This can be an 'if constexpr' but c++ features, who knows who supports what so we are stuck with this :(
if (std::is_unsigned<TInt>::value)
{
const TInt& max_int = std::numeric_limits<TInt>::max();
// Overflow to std::numeric_limits<TInt>::max;
if (min == 0U)
{
// Check if value is bigger than signed int range
// Signed range : max_int / 2
if (value > max_int / 2)
return min;
}
// Overflow to 0
if (max == max_int)
{
// Check if value is lower than max
// This check is pointless but it will do
if (value < max_int / 2)
return max;
}
}
if (value > max)
return max;
if (value < min)
return min;
return value;
}
// @brief Mathematically wraps the number. Does not work great with unsigned integers (may cause overflows).
template<typename TInt = int64_t>
TInt wrapi(TInt value, const TInt min, const TInt max)
{
static_assert(!std::is_floating_point<TInt>::value, "[util::wrapi] Type has to be an integer.");
static_assert(std::is_integral<TInt>::value, "[util::wrapi] Type isn't an integer.");
TInt minMaxRange = max - min;
if (minMaxRange == 0)
{
return min;
}
return min + ((((value - min) % minMaxRange) + minMaxRange) % minMaxRange);
}
constexpr const char* whitespace = " \t\n\v\f\r";
// @brief Trims 'util::whitespace' from the left of the string.
template<typename TChar = char>
std::basic_string<TChar> ltrim(const std::basic_string<TChar>& str, const TChar* trim_chars = whitespace)
{
size_t start = str.find_first_not_of(trim_chars);
return start == std::string::npos ? "" : str.substr(start);
}
// @brief Trims 'util::whitespace' from the right of the string.
template<typename TChar = char>
std::basic_string<TChar> rtrim(const std::basic_string<TChar>& str, const TChar* trim_chars = whitespace)
{
size_t end = str.find_last_not_of(trim_chars);
return end == std::string::npos ? "" : str.substr(0, end + 1);
}
// @brief Trims 'util::whitespace' from both the left and the right of the string.
template<typename TChar = char>
std::basic_string<TChar> trim(const std::basic_string<TChar>& str, const TChar* trim_chars = whitespace)
{
return rtrim(ltrim(str, trim_chars), trim_chars);
}
}
// @brief Out of bounds kill snake (loops around otherwise)
bool bounds_kill = false;
// @brief Color used for the normal bounds that is always shown.
const console::Color bounds_normal_color = console::Color::GRAY;
// @brief Color used for the killing bounds.
const console::Color bounds_killer_color = console::Color::WHITE;
// @brief Snake no longer has a tail (if true, used for debug)
bool no_tail = false;
// @brief Manages argv + argc
// Doesn't handle special cases like piping the stdout of your app into another app or stuff like that
// Probably the most overengineered class in this whole mess.
class AppArgument
{
protected:
std::string arg_name;
std::string arg_desc;
const int64_t arg_capture_options_count = 0;
const bool arg_help_printable = true;
std::function<bool(const std::vector<std::string>&)> arg_delegate;
public:
// Standard setting (capture_options = false, help_printable = true)
AppArgument(const std::string& name, const std::string& desc, const std::function<bool(const std::vector<std::string>&)> delegate)
: arg_name(name), arg_desc(desc), arg_delegate(delegate) {}
// Capture options setting
AppArgument(const std::string& name, const std::string& desc, const int64_t& capture_options_count, const std::function<bool(const std::vector<std::string>&)> delegate)
: arg_name(name), arg_desc(desc), arg_delegate(delegate), arg_capture_options_count(capture_options_count) {}
// Help-printable setting
AppArgument(const std::string& name, const std::string& desc, const int64_t& capture_options_count, const bool& help_printable, const std::function<bool(const std::vector<std::string>&)> delegate)
: arg_name(name), arg_desc(desc), arg_delegate(delegate), arg_capture_options_count(capture_options_count), arg_help_printable(help_printable) {}
protected:
// Anything except arg_list can be defined inline.
static std::vector<AppArgument> arg_list;
public:
static bool is_error;
static void print_help(const std::string& info, std::ostream& os)
{
if (!info.empty())
os << "Info : " << info << '\n';
os << "Arguments help : \n";
for (AppArgument& elem : arg_list)
{
// Indent the argument start once
// Indent the name and description twice
if (elem.arg_help_printable)
os << "\tArgument =>\n\t\tName: " << elem.arg_name << "\n\t\tDescription: " << elem.arg_desc << '\n';
if (elem.arg_capture_options_count > 0)
os << "\t\tThis argument captures '" << elem.arg_capture_options_count << "' options, the following passed next argument(s) is the option.\n";
}
}
// Registers a 'AppArgument'
// uses an internal array.
static void add(const AppArgument& arg)
{
arg_list.push_back(arg);
}
// clears the arguments
static void clear()
{
arg_list.clear();
}
// parses & runs arguments
static void parse(const int& argc, const char* argv[])
{
// No arguments? (except for the executable name)
if (argc <= 1)
return;
is_error = false;
std::stringstream ss;
// Ignore first argument (even though it could be empty / non-existent i don't care)
for (int i = 1; i < argc; i++)
{
// Get trimmed argument
bool found_arg = false;
std::string current_arg = util::trim(std::string(argv[i]));
for (AppArgument& elem : arg_list)
{
// Find the position of the argument name in the passed argument name
size_t pos = current_arg.find(elem.arg_name);
// Found a matching argument for current argument. (pos isn't npos)
if (pos != std::string::npos)
{
found_arg = true;
// Capture next argument as option
std::vector<std::string> arg_option;
arg_option.reserve(elem.arg_capture_options_count);
for (int64_t j = 0; j < elem.arg_capture_options_count; j++)
{
i++; // add 1 to i here because doing it inline in the if statement depends on the compiler
if (i < argc)
{
// Get next argument
arg_option.emplace_back(util::trim(std::string(argv[i])));
}
else // not enough arguments
{
is_error = true;
// can pass 'j' as got parameter because it's 1 less than expected
ss << "Argument '" << current_arg << "' couldn't capture options. (Not enough options, expected " <<
elem.arg_capture_options_count << ", got " << j << ")\n";
break;
}
}
// Invoke using the passed options
// If the delegate returns false, this means the invocation failed
// There will be no exception handling in here, do it in the delegate
if (!elem.arg_delegate(arg_option))
{
is_error = true;
ss << "Error occured while executing argument : " << current_arg << '\n';
}
break;
}
}
// Unknown argument
if (!found_arg)
{
is_error = true;
ss << "Unknown argument : " << current_arg << '\n';
}
}
if (is_error)
{
// Display help text
print_help(ss.str(), std::cout);
}
}
};
// Define the 'std::vector' here, for some reason methods can be inline though
// Probably the 'extern/linking' stuff, the c++ linker scares me anyways.
// tl;dr : put AppArgument to a seperate .hpp + .cpp file + name the class better
bool AppArgument::is_error = false;
std::vector<AppArgument> AppArgument::arg_list;
// -- Snake
// `enum class` is strong typing for the enum type
// meaning that this enum type can be only accessed by doing Direction:: and can be only stored in Direction.
// Usual enums act like 'C' enums and can have their constants referred without the 'Direction::' type name.
// @brief Defines a direction for the snake.
enum class Direction
{
Up, Down, Left, Right
};
// @brief Contains the stats about the current snake.
namespace stats
{
bool is_paused = false;
bool is_game_over = false;
int64_t score = 0;
}
// @brief Contains the globals for the snake.
namespace snake
{
// snake player's character
constexpr char pl_char = 'o';
constexpr char pl_tail_char = 'O';
constexpr console::Color pl_color = console::Color::GREEN; // Primary color
constexpr console::Color pl_tail_dark_color = console::Color::DARKGREEN; // Set color every tail index divisible by 2
// snake direction
Direction dir = Direction::Right;
// snake position
int64_t x, y;
// tail positions
std::vector<std::pair<int64_t, int64_t>> tail;
// loop counts to wait until the frames end
// Basically doesn't instantly kill the player, unless the ::snake::tick_grace ends.
int64_t grace_frames = 2; // grace frames to wait
int64_t waited_grace_frames = 0; // tried to make this private (using class abuse) and it didn't work because linker
// Player is in danger, tick the grace frames.
void tick_grace(const char* msg_on_dead = nullptr)
{
waited_grace_frames++;
if (waited_grace_frames > grace_frames)
{
stats::is_game_over = true;
if (msg_on_dead != nullptr)
{
exit_message = std::string(msg_on_dead);
}
}
}
// @brief Used when the Player is no longer in danger, this resets the grace frames.
void reset_grace()
{
waited_grace_frames = 0;
}
// Note : This method will kill the snake
// Check only once before you move.
bool is_next_tile_safe(const bool& allow_tick_grace = true)
{
bool is_safe = true;
// Check blocks
// TODO : Map feature impl, will not do it because
// 1. I don't care
// 2. I am lazy and this code is not very flexible. Ignore the 'flexibility' part of it because it is actually concise but i am more lazy.
// Check snake itself
int64_t sn_next_x = x, sn_next_y = y;
switch (dir)
{
case Direction::Up:
sn_next_y = util::wrapi<int64_t>(sn_next_y - 1, 1, area_sz_y - 1);
break;
case Direction::Down:
sn_next_y = util::wrapi<int64_t>(sn_next_y + 1, 1, area_sz_y - 1);
break;
case Direction::Left:
sn_next_x = util::wrapi<int64_t>(sn_next_x - 1, 1, area_sz_x - 1);
break;
case Direction::Right:
sn_next_x = util::wrapi<int64_t>(sn_next_x + 1, 1, area_sz_x - 1);
break;
}
// bounds
if (bounds_kill)
{
// snake moved more than expected
// snake can only move 1 tile per frame
is_safe = std::abs(snake::x - sn_next_x) <= 1 && std::abs(snake::y - sn_next_y) <= 1;
}
for (const std::pair<int64_t, int64_t>& tail_pos : tail)
{
if (sn_next_x == tail_pos.first && sn_next_y == tail_pos.second)
{
is_safe = false;
break;
}
}
if (allow_tick_grace)
{
if (!is_safe)
snake::tick_grace();
else
snake::reset_grace();
}
return is_safe;
}
}
namespace fruit
{
// fruit character
constexpr char f_char = (unsigned char)30;
constexpr console::Color f_color = console::Color::YELLOW;
// fruit position
int64_t x, y;
void spawn()
{
// Do this as the '0' and the last 'area_sz_x' is reserved for the characters of the border
// Play area is essentially smaller than promised. (but not important for crappy console snake.)
x = util::wrapi<int64_t>(rand() % area_sz_x, 1, area_sz_x - 1);
y = util::wrapi<int64_t>(rand() % area_sz_y, 1, area_sz_y - 1);
}
void collect()
{
// Increment score and spawn
stats::score++;
spawn();
}
}
// initilaze the game
void init()
{
// Runtime environment
console::clear();
console::visibility_cursor(false);
srand(time(nullptr));
// Snake
snake::x = area_sz_x / 2;
snake::y = area_sz_y / 2;
// Fruit
fruit::spawn();
}
// snake / fruit logic
void logic()
{
// Snake position before changing
int64_t sn_prev_x = snake::x, sn_prev_y = snake::y;
if (!snake::is_next_tile_safe())
return;
switch (snake::dir)
{
// Up and down is reverted
// Roll clamp sets the value to min/max, if the values set are bigger than max/smaller than min
case Direction::Up:
snake::y = util::wrapi<int64_t>(snake::y - 1, 1, area_sz_y - 1);
break;
case Direction::Down:
snake::y = util::wrapi<int64_t>(snake::y + 1, 1, area_sz_y - 1);
break;
case Direction::Left:
snake::x = util::wrapi<int64_t>(snake::x - 1, 1, area_sz_x - 1);
break;
case Direction::Right:
snake::x = util::wrapi<int64_t>(snake::x + 1, 1, area_sz_x - 1);
break;
}
// Eat froot if the snek collects it
if (snake::x == fruit::x && snake::y == fruit::y)
{
fruit::collect();
// Tail has space reserved.
if (!no_tail)
{
// Maybe the issue is in the placement of the tail
// The 0'th index tail is in this position
// TODO : Try placing tails correctly.
auto snake_tail_pos = std::pair<int64_t, int64_t>(sn_prev_x, sn_prev_y);
if (snake::tail.size() > 1)
{
snake_tail_pos.first = snake::tail.at(snake::tail.size() - 1).first;
snake_tail_pos.second = snake::tail.at(snake::tail.size() - 1).second;
switch (snake::dir)
{
case Direction::Up:
snake_tail_pos.second -= 1;
break;
case Direction::Down:
snake_tail_pos.second += 1;
break;
case Direction::Left:
snake_tail_pos.first += 1;
break;
case Direction::Right:
snake_tail_pos.first -= 1;
break;
}
}
snake::tail.push_back(snake_tail_pos);
}
}
// Finish game if the player was too good
// Area size has 2 border, so the size is actually smaller than promised
// add 1 as the player head doesn't count as score.
if (stats::score + 1 >= (area_sz_x - 2) * (area_sz_y - 2))
{
exit_message = "You won!\nTo try bigger area challenge, use the --area-size with (size_x, size_y) parameters as program argument.";
stats::is_game_over = true;
}
// Update tail
if (!no_tail)
{
size_t tail_size = snake::tail.size();
if (tail_size == 1)
{
// Since the for loop won't execute
snake::tail[0].first = sn_prev_x;
snake::tail[0].second = sn_prev_y;
}
// Position tail (if more than 1)
for (size_t i = 1; i < tail_size; i++)
{
// this is what happens when you are lazy and not write a vector2I struct
static std::pair<int64_t, int64_t> pair_dir_reference; // Copy of Previous (for ref purposes)
std::pair<int64_t, int64_t>& pair_prev = snake::tail[i - 1]; // Previous
std::pair<int64_t, int64_t>& pair_current = snake::tail[i]; // Current
// Follow previous one
// Tail should follow the player
// The first pair_prev[i == 1 - 1] tail should be the previous position of the player
// Make the current pair follow the previous tail
// The tail can be at 4 different positions
// O // Up if X is same but Y is 1 less
// O C O // Left Or Right if Y is same, Left => X is 1 less, Right => X is 1 more
// O // Down if X same but Y is 1 more
// Using the direction, manipulate the current tail accordingly
if (i == 1)
{
// Keep direction reference
pair_dir_reference = pair_current;
// For the first 2 tails, we do have directions
// The other ones are unknown.
pair_current = pair_prev;
pair_prev.first = sn_prev_x;
pair_prev.second = sn_prev_y;
}
else
{
// Other tails (except for the current) is set correctly, but 1 behind.
auto pair_prev_current = pair_current; // Keep previous position.
pair_current = pair_dir_reference; // Set position
pair_dir_reference = pair_prev_current; // Set direction reference to current.
// Perhaps we could use pair_prev but it doesn't work, and this works fine so keep
// Oh well.
}
// Check self collision with the both tails
// If that's the case, oh well.
// Collision check is done in 'snake::is_next_tile_safe()'
// if (snake::x == pair_current.first && snake::y == pair_current.second)
// {
// snake::x = sn_prev_x;
// snake::y = sn_prev_y;
// // stats::is_game_over = true;
// }
}
}
}
// conio input
void input()
{
// return if no kbhit
if (!_kbhit())
return;
bool has_tail = !no_tail && snake::tail.size() > 0;
// Apparently mingw _getch is non-standard
// so the special chars are
// Get current ascii character
int input_ch = _getch();
if (input_ch != 0 && input_ch != 224)
{
// Keyboard non-special chars
switch (std::tolower(input_ch))
{
// Change direction
case 'w':
if (has_tail && snake::dir == Direction::Down)
break;
snake::dir = Direction::Up;
break;
case 'a':
if (has_tail && snake::dir == Direction::Right)
break;
snake::dir = Direction::Left;
break;
case 's':
if (has_tail && snake::dir == Direction::Up)
break;
snake::dir = Direction::Down;
break;
case 'd':
if (has_tail && snake::dir == Direction::Left)
break;
snake::dir = Direction::Right;
break;
case 'p':
stats::is_paused = !stats::is_paused;
break;
// Exit if q is pressed
case 'q':
stats::is_game_over = true;
exit_message = "You quit the game.";
break;
default:
break;
}
}
else // Arrow keys (or any special key in this matter) are outputted as special characters
{
// For unix : Arrow keys == \033 + [ + key code (A, B, C or D)
// For windows (mingw + msvc) : Arrow keys == 0 or 224 + key code (72, 75, 77 or 80)
// Special chars
input_ch = _getch();
switch (input_ch)
{
case 72: // up
if (has_tail && snake::dir == Direction::Down)
break;
snake::dir = Direction::Up;
break;
case 75: // left
if (has_tail && snake::dir == Direction::Right)
break;
snake::dir = Direction::Left;
break;
case 80: // down
if (has_tail && snake::dir == Direction::Up)
break;
snake::dir = Direction::Down;
break;
case 77: // right
if (has_tail && snake::dir == Direction::Left)
break;
snake::dir = Direction::Right;
break;
}
}
}
// draw() method debugs
#define DRAW_DEBUG_STATS 0
// draw into console
void draw()
{
// Draw play area & players
if (!stats::is_paused) // Only draw stat / help text if paused.
{
int64_t tail_current_index = 0; // Drawn Tail count (for different tail colors)
for (int64_t y = 0; y < area_sz_y; y++)
{
for (int64_t x = 0; x < area_sz_x; x++)
{
// Print border if in edges
bool is_newline = x == area_sz_x - 1;
if (y == 0 || y == area_sz_y - 1 ||
x == 0 || is_newline)
{
console::set_color(bounds_kill ? bounds_killer_color : bounds_normal_color);
std::cout << '#';
if (is_newline)
std::cout << '\n';
continue;
}
// Print player
console::set_color(snake::pl_color);
if (snake::x == x && snake::y == y)
{
std::cout << snake::pl_char;
continue;
}
// Tail (is slow)
// But i really don't care
if (!no_tail)
{
bool printed_tail = false;
for (const std::pair<int64_t, int64_t>& tail : snake::tail)
{
if (tail.first == x && tail.second == y)
{
if (tail_current_index % 2 == 0)
console::set_color(snake::pl_tail_dark_color);
std::cout << snake::pl_tail_char;
tail_current_index++;
printed_tail = true;
break;
}
}
if (printed_tail)
continue;
}
console::set_color(fruit::f_color);
// Print fruit
if (fruit::x == x && fruit::y == y)
{
std::cout << fruit::f_char;
continue;
}
// Print space normally
console::set_color(console::Color::WHITE);
std::cout << ' ';
}
}
}
// Draw stats
console::set_color(console::Color::WHITE);
console::set_cursor(area_sz_x + 1, 0);
std::cout << "Score : " << stats::score << '\n';
console::set_cursor(area_sz_x + 1, 1);
std::cout << "Press Q to quit." << '\n';
console::set_cursor(area_sz_x + 1, 2);
std::cout << (!stats::is_paused ? "Press P to pause." : "[PAUSED !!] Press P to unpause.") << '\n';
console::set_cursor(area_sz_x + 1, 3);
// Debug
#if DRAW_DEBUG_STATS
std::cout << "Snake X " << snake::x << ", Snake Y " << snake::y << '\n';
console::set_cursor(area_sz_x + 1, 4);
std::cout << "Fruit X " << fruit::x << ", Fruit Y " << fruit::y << '\n';
console::set_cursor(area_sz_x + 1, 5);
std::cout << "Tail Length : " << snake::tail.size() << '\n';
console::set_cursor(area_sz_x + 1, 6);
size_t current_cursor = 7;
for (const auto& tailPos : snake::tail) // the following terrible code is for debug purposes
{
std::cout << "Tail[" << current_cursor - 4 << "] = X:" << tailPos.first << " Y:" << tailPos.second << '\n';
current_cursor++;
console::set_cursor(area_sz_x + 1, current_cursor);
}
#endif
// Delay drawing
console::set_cursor(0, 0); // Reset cursor
std::this_thread::sleep_for(std::chrono::milliseconds(ms_delay_draw));
}
// this probably is a world record of most lines of c++ code to create snake in the console
int main(int argc, char* argv[])
{
// -- AppArgument
AppArgument::add(AppArgument("--snake-no-tail", "Removes the snake's tail.",
[] (const std::vector<std::string>& _)
{
no_tail = true;
// Return true if the setting succeeded
return true;
}));
AppArgument::add(AppArgument("--borders-kill", "Borders kill the snake.",
[] (const std::vector<std::string>& _)
{
bounds_kill = true;
// Return true if the setting succeeded
return true;
}));
AppArgument::add(AppArgument("--snake-speed", "Snake's speed multiplier. Pass as single double (can be number with decimal) value without any special things.", 1,
[] (const std::vector<std::string>& param)
{
const std::string& text = param.at(0);
double speed_mul = 1.0;
try
{
speed_mul = util::clamp<double>(std::stod(text), 0.0, std::numeric_limits<double>::max());
}
catch (const std::exception& e)
{
std::cerr << "\nError: --snake-speed: '" << e.what() << "' failed. Passed text was '" << text << "'.\n";
return false;
}
ms_delay_draw /= speed_mul;
// Return true if the setting succeeded
return true;
}));
AppArgument::add(AppArgument("--area-size", "Size of the area. Pass as integers formatted like (x size, y size).", 2,
[] (const std::vector<std::string>& param)
{
// parse param (with integers)
// apparently windows parses parameters before giving it in as an argument
// why??
for (int64_t i = 0; i < param.size(); i++)
{
const std::string& fmt_param = util::trim(param[i], "()");
int64_t size_param = 0;
try
{
size_param = std::stoll(fmt_param);
}
catch(const std::exception& e)
{
std::cerr << "\nError: --area-size: '" << e.what() << "' failed. Passed text was '" << fmt_param << "'.\n";
return false;
}
switch (i)
{
case 0:
area_sz_x = util::clamp<int64_t>(size_param, 4, std::numeric_limits<int64_t>::max());
break;
case 1:
area_sz_y = util::clamp<int64_t>(size_param, 4, std::numeric_limits<int64_t>::max());
break;
default:
std::cerr << "[--area-size] No such size case as " << i << '\n';
return false;
}
}
// Return true if the setting succeeded
return true;
}));
AppArgument::parse(argc, (const char**)argv);
if (AppArgument::is_error)
{
std::cout << "Press enter to continue.\n";
while (!console::wait_enter());
}
init();
while (!stats::is_game_over)
{
// Only pause logic
if (!stats::is_paused)
logic();
input();
draw();
}
console::clear();
std::cout << exit_message << '\n' <<
"Score was : " << stats::score << '\n' <<
"Press enter to exit.\n";
while (!console::wait_enter());
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment