This commit is contained in:
Admin
2023-04-13 14:02:08 +08:00
commit 33c813630f
57 changed files with 27736 additions and 0 deletions

7
Make/2048/2048.cpp Normal file
View File

@@ -0,0 +1,7 @@
#include "2048.hpp"
#include "menu.hpp"
int main() {
Menu::startMenu();
return 0;
}

275
Make/2048/game-graphics.cpp Normal file
View File

@@ -0,0 +1,275 @@
#include "game-graphics.hpp"
#include "color.hpp"
#include "global.hpp"
#include <array>
#include <iomanip>
#include <sstream>
namespace Game {
namespace Graphics {
std::string AsciiArt2048() {
constexpr auto title_card_2048 = R"(
/\\\\\\\\\ /\\\\\\\ /\\\ /\\\\\\\\\
/\\\///////\\\ /\\\/////\\\ /\\\\\ /\\\///////\\\
\/// \//\\\ /\\\ \//\\\ /\\\/\\\ \/\\\ \/\\\
/\\\/ \/\\\ \/\\\ /\\\/\/\\\ \///\\\\\\\\\/
/\\\// \/\\\ \/\\\ /\\\/ \/\\\ /\\\///////\\\
/\\\// \/\\\ \/\\\ /\\\\\\\\\\\\\\\\ /\\\ \//\\\
/\\\/ \//\\\ /\\\ \///////////\\\// \//\\\ /\\\
/\\\\\\\\\\\\\\\ \///\\\\\\\/ \/\\\ \///\\\\\\\\\/
\/////////////// \/////// \/// \/////////
)";
std::ostringstream title_card_richtext;
title_card_richtext << green << bold_on << title_card_2048 << bold_off << def;
title_card_richtext << "\n\n\n";
return title_card_richtext.str();
}
std::string BoardInputPrompt() {
const auto board_size_prompt_text = {
"(NOTE: Scores and statistics will be saved only for the 4x4 gameboard)\n",
"Enter gameboard size - (Enter '0' to go back): "};
constexpr auto sp = " ";
std::ostringstream board_size_prompt_richtext;
board_size_prompt_richtext
<< bold_on << sp << std::begin(board_size_prompt_text)[0] << sp
<< std::begin(board_size_prompt_text)[1] << bold_off;
return board_size_prompt_richtext.str();
}
std::string YouWinPrompt() {
constexpr auto win_game_text = "You win! Congratulations!";
constexpr auto sp = " ";
std::ostringstream win_richtext;
win_richtext << green << bold_on << sp << win_game_text << def << bold_off
<< "\n\n\n";
return win_richtext.str();
}
std::string GameOverPrompt() {
constexpr auto lose_game_text = "Game over! You lose.";
constexpr auto sp = " ";
std::ostringstream lose_richtext;
lose_richtext << red << bold_on << sp << lose_game_text << def << bold_off
<< "\n\n\n";
return lose_richtext.str();
}
std::string EndOfEndlessPrompt() {
constexpr auto endless_mode_text =
"End of endless mode! Thank you for playing!";
constexpr auto sp = " ";
std::ostringstream endless_mode_richtext;
endless_mode_richtext << red << bold_on << sp << endless_mode_text << def
<< bold_off << "\n\n\n";
return endless_mode_richtext.str();
}
std::string QuestionEndOfWinningGamePrompt() {
constexpr auto win_but_what_next =
"You Won! Continue playing current game? [y/n]";
constexpr auto sp = " ";
std::ostringstream win_richtext;
win_richtext << green << bold_on << sp << win_but_what_next << def << bold_off
<< ": ";
return win_richtext.str();
}
std::string GameStateNowSavedPrompt() {
constexpr auto state_saved_text =
"The game has been saved. Feel free to take a break.";
constexpr auto sp = " ";
std::ostringstream state_saved_richtext;
state_saved_richtext << green << bold_on << sp << state_saved_text << def
<< bold_off << "\n\n";
return state_saved_richtext.str();
}
std::string GameBoardNoSaveErrorPrompt() {
constexpr auto no_save_found_text =
"No saved game found. Starting a new game.";
constexpr auto sp = " ";
std::ostringstream no_save_richtext;
no_save_richtext << red << bold_on << sp << no_save_found_text << def
<< bold_off << "\n\n";
return no_save_richtext.str();
}
std::string InvalidInputGameBoardErrorPrompt() {
constexpr auto invalid_prompt_text = "Invalid input. Please try again.";
constexpr auto sp = " ";
std::ostringstream invalid_prompt_richtext;
invalid_prompt_richtext << red << sp << invalid_prompt_text << def << "\n\n";
return invalid_prompt_richtext.str();
}
std::string BoardSizeErrorPrompt() {
const auto invalid_prompt_text = {
"Invalid input. Gameboard size should range from ", " to ", "."};
// constexpr auto num_of_invalid_prompt_text = 3;
constexpr auto sp = " ";
std::ostringstream error_prompt_richtext;
error_prompt_richtext << red << sp << std::begin(invalid_prompt_text)[0]
<< MIN_GAME_BOARD_PLAY_SIZE
<< std::begin(invalid_prompt_text)[1]
<< MAX_GAME_BOARD_PLAY_SIZE
<< std::begin(invalid_prompt_text)[2] << def << "\n\n";
return error_prompt_richtext.str();
}
std::string InputCommandListPrompt() {
constexpr auto sp = " ";
const auto input_commands_list_text = {
"W or K or ↑ => Up", "A or H or ← => Left", "S or J or ↓ => Down",
"D or L or → => Right", "Z or P => Save"};
std::ostringstream str_os;
for (const auto txt : input_commands_list_text) {
str_os << sp << txt << "\n";
}
return str_os.str();
}
std::string EndlessModeCommandListPrompt() {
constexpr auto sp = " ";
const auto endless_mode_list_text = {"X => Quit Endless Mode"};
std::ostringstream str_os;
for (const auto txt : endless_mode_list_text) {
str_os << sp << txt << "\n";
}
return str_os.str();
}
std::string InputCommandListFooterPrompt() {
constexpr auto sp = " ";
const auto input_commands_list_footer_text = {
"", "Press the keys to start and continue.", "\n"};
std::ostringstream str_os;
for (const auto txt : input_commands_list_footer_text) {
str_os << sp << txt << "\n";
}
return str_os.str();
}
std::string GameScoreBoardBox(scoreboard_display_data_t scdd) {
std::ostringstream str_os;
constexpr auto score_text_label = "SCORE:";
constexpr auto bestscore_text_label = "BEST SCORE:";
constexpr auto moves_text_label = "MOVES:";
// * border padding: vvv
// | l-outer: 2, r-outer: 0
// | l-inner: 1, r-inner: 1
// * top border / bottom border: vvv
// | tl_corner + horizontal_sep + tr_corner = length: 1 + 27 + 1
// | bl_corner + horizontal_sep + br_corner = length: 1 + 27 + 1
enum {
UI_SCOREBOARD_SIZE = 27,
UI_BORDER_OUTER_PADDING = 2,
UI_BORDER_INNER_PADDING = 1
}; // length of horizontal board - (corners + border padding)
constexpr auto border_padding_char = ' ';
constexpr auto vertical_border_pattern = "";
constexpr auto top_board =
"┌───────────────────────────┐"; // Multibyte character set
constexpr auto bottom_board =
"└───────────────────────────┘"; // Multibyte character set
const auto outer_border_padding =
std::string(UI_BORDER_OUTER_PADDING, border_padding_char);
const auto inner_border_padding =
std::string(UI_BORDER_INNER_PADDING, border_padding_char);
const auto inner_padding_length =
UI_SCOREBOARD_SIZE - (std::string{inner_border_padding}.length() * 2);
enum ScoreBoardDisplayDataFields {
IDX_COMPETITION_MODE,
IDX_GAMEBOARD_SCORE,
IDX_BESTSCORE,
IDX_MOVECOUNT,
MAX_SCOREBOARDDISPLAYDATA_INDEXES
};
const auto competition_mode = std::get<IDX_COMPETITION_MODE>(scdd);
const auto gameboard_score = std::get<IDX_GAMEBOARD_SCORE>(scdd);
const auto temp_bestscore = std::get<IDX_BESTSCORE>(scdd);
const auto movecount = std::get<IDX_MOVECOUNT>(scdd);
str_os << outer_border_padding << top_board << "\n";
str_os << outer_border_padding << vertical_border_pattern
<< inner_border_padding << bold_on << score_text_label << bold_off
<< std::string(inner_padding_length -
std::string{score_text_label}.length() -
gameboard_score.length(),
border_padding_char)
<< gameboard_score << inner_border_padding << vertical_border_pattern
<< "\n";
if (competition_mode) {
str_os << outer_border_padding << vertical_border_pattern
<< inner_border_padding << bold_on << bestscore_text_label
<< bold_off
<< std::string(inner_padding_length -
std::string{bestscore_text_label}.length() -
temp_bestscore.length(),
border_padding_char)
<< temp_bestscore << inner_border_padding << vertical_border_pattern
<< "\n";
}
str_os << outer_border_padding << vertical_border_pattern
<< inner_border_padding << bold_on << moves_text_label << bold_off
<< std::string(inner_padding_length -
std::string{moves_text_label}.length() -
movecount.length(),
border_padding_char)
<< movecount << inner_border_padding << vertical_border_pattern
<< "\n";
str_os << outer_border_padding << bottom_board << "\n \n";
return str_os.str();
}
std::string GameScoreBoardOverlay(scoreboard_display_data_t scdd) {
std::ostringstream str_os;
DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardBox));
return str_os.str();
}
std::string GameEndScreenOverlay(end_screen_display_data_t esdd) {
enum EndScreenDisplayDataFields {
IDX_FLAG_WIN,
IDX_FLAG_ENDLESS_MODE,
MAX_ENDSCREENDISPLAYDATA_INDEXES
};
const auto did_win = std::get<IDX_FLAG_WIN>(esdd);
const auto is_endless_mode = std::get<IDX_FLAG_ENDLESS_MODE>(esdd);
std::ostringstream str_os;
const auto standardWinLosePrompt = [=] {
std::ostringstream str_os;
DrawOnlyWhen(str_os, did_win, YouWinPrompt);
// else..
DrawOnlyWhen(str_os, !did_win, GameOverPrompt);
return str_os.str();
};
DrawOnlyWhen(str_os, !is_endless_mode, standardWinLosePrompt);
// else..
DrawOnlyWhen(str_os, is_endless_mode, EndOfEndlessPrompt);
return str_os.str();
}
std::string GameInputControlsOverlay(input_controls_display_data_t gamestatus) {
const auto is_in_endless_mode = std::get<0>(gamestatus);
const auto is_in_question_mode = std::get<1>(gamestatus);
std::ostringstream str_os;
const auto InputControlLists = [=] {
std::ostringstream str_os;
DrawAlways(str_os, Graphics::InputCommandListPrompt);
DrawOnlyWhen(str_os, is_in_endless_mode,
Graphics::EndlessModeCommandListPrompt);
DrawAlways(str_os, Graphics::InputCommandListFooterPrompt);
return str_os.str();
};
// When game is paused to ask a question, hide regular inut prompts..
DrawOnlyWhen(str_os, !is_in_question_mode, InputControlLists);
return str_os.str();
}
} // namespace Graphics
} // namespace Game

69
Make/2048/game-input.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include "game-input.hpp"
#include "global.hpp"
namespace Game {
namespace Input {
bool check_input_ansi(char c, intendedmove_t &intendedmove) {
using namespace Keypress::Code;
if (c == CODE_ANSI_TRIGGER_1) {
getKeypressDownInput(c);
if (c == CODE_ANSI_TRIGGER_2) {
getKeypressDownInput(c);
switch (c) {
case CODE_ANSI_UP:
intendedmove[FLAG_MOVE_UP] = true;
return false;
case CODE_ANSI_DOWN:
intendedmove[FLAG_MOVE_DOWN] = true;
return false;
case CODE_ANSI_RIGHT:
intendedmove[FLAG_MOVE_RIGHT] = true;
return false;
case CODE_ANSI_LEFT:
intendedmove[FLAG_MOVE_LEFT] = true;
return false;
}
}
}
return true;
}
bool check_input_vim(char c, intendedmove_t &intendedmove) {
using namespace Keypress::Code;
switch (toupper(c)) {
case CODE_VIM_UP:
intendedmove[FLAG_MOVE_UP] = true;
return false;
case CODE_VIM_LEFT:
intendedmove[FLAG_MOVE_LEFT] = true;
return false;
case CODE_VIM_DOWN:
intendedmove[FLAG_MOVE_DOWN] = true;
return false;
case CODE_VIM_RIGHT:
intendedmove[FLAG_MOVE_RIGHT] = true;
return false;
}
return true;
}
bool check_input_wasd(char c, intendedmove_t &intendedmove) {
using namespace Keypress::Code;
switch (toupper(c)) {
case CODE_WASD_UP:
intendedmove[FLAG_MOVE_UP] = true;
return false;
case CODE_WASD_LEFT:
intendedmove[FLAG_MOVE_LEFT] = true;
return false;
case CODE_WASD_DOWN:
intendedmove[FLAG_MOVE_DOWN] = true;
return false;
case CODE_WASD_RIGHT:
intendedmove[FLAG_MOVE_RIGHT] = true;
return false;
}
return true;
}
} // namespace Input
} // namespace Game

View File

@@ -0,0 +1,173 @@
#include "game-pregamemenu.hpp"
#include "game-graphics.hpp"
#include "game-input.hpp"
#include "game.hpp"
#include "gameboard.hpp"
#include "global.hpp"
#include "loadresource.hpp"
#include "menu.hpp"
#include <array>
#include <iostream>
#include <limits>
#include <sstream>
namespace Game {
namespace PreGameSetup {
namespace {
enum PreGameSetupStatusFlag {
FLAG_NULL,
FLAG_START_GAME,
FLAG_RETURN_TO_MAIN_MENU,
MAX_NO_PREGAME_SETUP_STATUS_FLAGS
};
using pregamesetup_status_t =
std::array<bool, MAX_NO_PREGAME_SETUP_STATUS_FLAGS>;
pregamesetup_status_t pregamesetup_status{};
ull stored_game_size{1};
bool FlagInputErrornousChoice{};
bool noSave{};
void process_PreGameMenu() {
if (pregamesetup_status[FLAG_START_GAME]) {
playGame(PlayGameFlag::BrandNewGame, GameBoard{stored_game_size},
stored_game_size);
}
if (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU]) {
Menu::startMenu();
}
}
int Receive_Input_Playsize(std::istream &is) {
int userInput_PlaySize{};
if (!(is >> userInput_PlaySize)) {
constexpr auto INVALID_INPUT_VALUE_FLAG = -1;
userInput_PlaySize = INVALID_INPUT_VALUE_FLAG;
is.clear();
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
return userInput_PlaySize;
}
void receive_input_flags(std::istream &is) {
using namespace Input::Keypress::Code;
// Reset ErrornousChoice flag...
FlagInputErrornousChoice = bool{};
// If not number, emits -1.
// it should emit bool (valid / invalid value) and a valid number if ok input.
// WARN:: "0-3" breaks this code!
const auto c = Receive_Input_Playsize(is);
const auto is_valid_game_size =
(c >= MIN_GAME_BOARD_PLAY_SIZE) && (c <= MAX_GAME_BOARD_PLAY_SIZE);
// Regular case;
if (is_valid_game_size) {
stored_game_size = c;
pregamesetup_status[FLAG_START_GAME] = true;
}
// Special Case;
bool is_not_special_case{};
switch (c) {
case CODE_HOTKEY_PREGAMEMENU_BACK_TO_MAINMENU:
pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true;
break;
default:
is_not_special_case = true;
break;
}
if (!is_valid_game_size && is_not_special_case) {
FlagInputErrornousChoice = true;
}
}
bool soloLoop() {
bool invalidInputValue = FlagInputErrornousChoice;
const auto QuestionAboutBoardSizePrompt = [&invalidInputValue]() {
std::ostringstream str_os;
// Prints only if "invalidInputValue" is true
DrawOnlyWhen(str_os, invalidInputValue, Graphics::BoardSizeErrorPrompt);
DrawAlways(str_os, Graphics::BoardInputPrompt);
return str_os.str();
};
pregamesetup_status = pregamesetup_status_t{};
clearScreen();
DrawAlways(std::cout, Game::Graphics::AsciiArt2048);
DrawAsOneTimeFlag(std::cout, noSave, Graphics::GameBoardNoSaveErrorPrompt);
DrawAlways(std::cout, QuestionAboutBoardSizePrompt);
receive_input_flags(std::cin);
process_PreGameMenu();
return FlagInputErrornousChoice;
}
void endlessLoop() {
while (soloLoop())
;
}
enum class NewGameFlag { NewGameFlagNull, NoPreviousSaveAvailable };
void SetUpNewGame(NewGameFlag ns) {
noSave = (ns == NewGameFlag::NoPreviousSaveAvailable) ? true : false;
endlessLoop();
}
load_gameboard_status_t initialiseContinueBoardArray() {
using namespace Loader;
constexpr auto gameboard_data_filename = "../data/previousGame";
constexpr auto game_stats_data_filename = "../data/previousGameStats";
auto loaded_gameboard{false};
auto loaded_game_stats{false};
auto tempGBoard = GameBoard{1};
// Note: Reserved for gameboard.score and gameboard.moveCount!
// TODO: Combine data into one resource file.
auto score_and_movecount =
std::tuple<decltype(tempGBoard.score), decltype(tempGBoard.moveCount)>{};
std::tie(loaded_gameboard, tempGBoard) =
load_GameBoard_data_from_file(gameboard_data_filename);
std::tie(loaded_game_stats, score_and_movecount) =
load_game_stats_from_file(game_stats_data_filename);
std::tie(tempGBoard.score, tempGBoard.moveCount) = score_and_movecount;
const auto all_files_loaded_ok = (loaded_gameboard && loaded_game_stats);
return std::make_tuple(all_files_loaded_ok, tempGBoard);
}
void DoContinueOldGame() {
bool load_old_game_ok;
GameBoard oldGameBoard;
std::tie(load_old_game_ok, oldGameBoard) = initialiseContinueBoardArray();
if (load_old_game_ok) {
playGame(PlayGameFlag::ContinuePreviousGame, oldGameBoard);
} else {
SetUpNewGame(NewGameFlag::NoPreviousSaveAvailable);
}
}
} // namespace
void SetUpNewGame() {
SetUpNewGame(NewGameFlag::NewGameFlagNull);
}
void ContinueOldGame() {
DoContinueOldGame();
}
} // namespace PreGameSetup
} // namespace Game

430
Make/2048/game.cpp Normal file
View File

@@ -0,0 +1,430 @@
#include "game.hpp"
#include "game-graphics.hpp"
#include "game-input.hpp"
#include "game-pregamemenu.hpp"
#include "gameboard-graphics.hpp"
#include "gameboard.hpp"
#include "global.hpp"
#include "loadresource.hpp"
#include "menu.hpp"
#include "saveresource.hpp"
#include "scores.hpp"
#include "statistics.hpp"
#include <array>
#include <chrono>
#include <iostream>
#include <sstream>
namespace Game {
namespace {
enum Directions { UP, DOWN, RIGHT, LEFT };
enum GameStatusFlag {
FLAG_WIN,
FLAG_END_GAME,
FLAG_ONE_SHOT,
FLAG_SAVED_GAME,
FLAG_INPUT_ERROR,
FLAG_ENDLESS_MODE,
FLAG_GAME_IS_ASKING_QUESTION_MODE,
FLAG_QUESTION_STAY_OR_QUIT,
MAX_NO_GAME_STATUS_FLAGS
};
using gamestatus_t = std::array<bool, MAX_NO_GAME_STATUS_FLAGS>;
using gamestatus_gameboard_t = std::tuple<gamestatus_t, GameBoard>;
gamestatus_gameboard_t process_gamelogic(gamestatus_gameboard_t gsgb) {
gamestatus_t gamestatus;
GameBoard gb;
std::tie(gamestatus, gb) = gsgb;
unblockTilesOnGameboard(gb);
if (gb.moved) {
addTileOnGameboard(gb);
registerMoveByOneOnGameboard(gb);
}
if (!gamestatus[FLAG_ENDLESS_MODE]) {
if (hasWonOnGameboard(gb)) {
gamestatus[FLAG_WIN] = true;
gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE] = true;
gamestatus[FLAG_QUESTION_STAY_OR_QUIT] = true;
}
}
if (!canMoveOnGameboard(gb)) {
gamestatus[FLAG_END_GAME] = true;
}
return std::make_tuple(gamestatus, gb);
}
using competition_mode_t = bool;
Graphics::scoreboard_display_data_t
make_scoreboard_display_data(ull bestScore, competition_mode_t cm,
GameBoard gb) {
const auto gameboard_score = gb.score;
const auto tempBestScore = (bestScore < gb.score ? gb.score : bestScore);
const auto comp_mode = cm;
const auto movecount = MoveCountOnGameBoard(gb);
const auto scdd =
std::make_tuple(comp_mode, std::to_string(gameboard_score),
std::to_string(tempBestScore), std::to_string(movecount));
return scdd;
};
Graphics::input_controls_display_data_t
make_input_controls_display_data(gamestatus_t gamestatus) {
const auto icdd = std::make_tuple(gamestatus[FLAG_ENDLESS_MODE],
gamestatus[FLAG_QUESTION_STAY_OR_QUIT]);
return icdd;
};
std::string DisplayGameQuestionsToPlayerPrompt(gamestatus_t gamestatus) {
using namespace Graphics;
std::ostringstream str_os;
DrawOnlyWhen(str_os, gamestatus[FLAG_QUESTION_STAY_OR_QUIT],
QuestionEndOfWinningGamePrompt);
return str_os.str();
}
// NOTE: current_game_session_t
// : (bestScore, is_competition_mode, gamestatus, gamePlayBoard)
using current_game_session_t =
std::tuple<ull, competition_mode_t, gamestatus_t, GameBoard>;
enum tuple_cgs_t_idx {
IDX_BESTSCORE,
IDX_COMP_MODE,
IDX_GAMESTATUS,
IDX_GAMEBOARD
};
std::string drawGraphics(current_game_session_t cgs) {
// Graphical Output has a specific ordering...
using namespace Graphics;
using namespace Gameboard::Graphics;
using tup_idx = tuple_cgs_t_idx;
const auto bestScore = std::get<tup_idx::IDX_BESTSCORE>(cgs);
const auto comp_mode = std::get<tup_idx::IDX_COMP_MODE>(cgs);
const auto gb = std::get<tup_idx::IDX_GAMEBOARD>(cgs);
const auto gamestatus = std::get<tup_idx::IDX_GAMESTATUS>(cgs);
std::ostringstream str_os;
// 1. Clear screen
clearScreen();
// 2. Draw Game Title Art
DrawAlways(str_os, AsciiArt2048);
// 3. Draw Scoreboard of current game session
const auto scdd = make_scoreboard_display_data(bestScore, comp_mode, gb);
DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay));
// 4. Draw current 2048 game active gameboard
DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput));
// 5. Draw anyinstant status feedback, like
// "Game saved!" (which disappers after next key input).
DrawOnlyWhen(str_os, gamestatus[FLAG_SAVED_GAME], GameStateNowSavedPrompt);
// 6. Draw any "questions to the player" (from the game) text output
DrawOnlyWhen(str_os, gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE],
DataSuppliment(gamestatus, DisplayGameQuestionsToPlayerPrompt));
// 7. Draw Keyboard / Input Keycodes to the player
const auto input_controls_display_data =
make_input_controls_display_data(gamestatus);
DrawAlways(str_os, DataSuppliment(input_controls_display_data,
GameInputControlsOverlay));
// 8. Draw any game error messages to the player (to do with keyboard input)
DrawOnlyWhen(str_os, gamestatus[FLAG_INPUT_ERROR],
InvalidInputGameBoardErrorPrompt);
return str_os.str();
}
gamestatus_t update_one_shot_display_flags(gamestatus_t gamestatus) {
const auto disable_one_shot_flag = [](bool &trigger) { trigger = !trigger; };
if (gamestatus[FLAG_ONE_SHOT]) {
disable_one_shot_flag(gamestatus[FLAG_ONE_SHOT]);
// Turn off display flag: [Saved Game]
if (gamestatus[FLAG_SAVED_GAME]) {
disable_one_shot_flag(gamestatus[FLAG_SAVED_GAME]);
}
// Turn off display flag: [Input Error]
if (gamestatus[FLAG_INPUT_ERROR]) {
disable_one_shot_flag(gamestatus[FLAG_INPUT_ERROR]);
}
}
return gamestatus;
}
using bool_gamestatus_t = std::tuple<bool, gamestatus_t>;
bool_gamestatus_t check_input_other(char c, gamestatus_t gamestatus) {
using namespace Input::Keypress::Code;
auto is_invalid_keycode{true};
switch (toupper(c)) {
case CODE_HOTKEY_ACTION_SAVE:
case CODE_HOTKEY_ALTERNATE_ACTION_SAVE:
gamestatus[FLAG_ONE_SHOT] = true;
gamestatus[FLAG_SAVED_GAME] = true;
is_invalid_keycode = false;
break;
case CODE_HOTKEY_QUIT_ENDLESS_MODE:
if (gamestatus[FLAG_ENDLESS_MODE]) {
gamestatus[FLAG_END_GAME] = true;
is_invalid_keycode = false;
}
break;
}
return std::make_tuple(is_invalid_keycode, gamestatus);
}
using intendedmove_gamestatus_t =
std::tuple<Input::intendedmove_t, gamestatus_t>;
intendedmove_gamestatus_t
receive_agent_input(Input::intendedmove_t intendedmove,
gamestatus_t gamestatus) {
using namespace Input;
const bool game_still_in_play =
!gamestatus[FLAG_END_GAME] && !gamestatus[FLAG_WIN];
if (game_still_in_play) {
// Game still in play. Take input commands for next turn.
char c;
getKeypressDownInput(c);
// Update agent's intended move flags per control scheme (if flagged).
const auto is_invalid_keypress_code = check_input_ansi(c, intendedmove) &&
check_input_wasd(c, intendedmove) &&
check_input_vim(c, intendedmove);
bool is_invalid_special_keypress_code;
std::tie(is_invalid_special_keypress_code, gamestatus) =
check_input_other(c, gamestatus);
if (is_invalid_keypress_code && is_invalid_special_keypress_code) {
gamestatus[FLAG_ONE_SHOT] = true;
gamestatus[FLAG_INPUT_ERROR] = true;
}
}
return std::make_tuple(intendedmove, gamestatus);
}
GameBoard decideMove(Directions d, GameBoard gb) {
switch (d) {
case UP:
tumbleTilesUpOnGameboard(gb);
break;
case DOWN:
tumbleTilesDownOnGameboard(gb);
break;
case LEFT:
tumbleTilesLeftOnGameboard(gb);
break;
case RIGHT:
tumbleTilesRightOnGameboard(gb);
break;
}
return gb;
}
using bool_gameboard_t = std::tuple<bool, GameBoard>;
bool_gameboard_t process_agent_input(Input::intendedmove_t intendedmove,
GameBoard gb) {
using namespace Input;
if (intendedmove[FLAG_MOVE_LEFT]) {
gb = decideMove(LEFT, gb);
}
if (intendedmove[FLAG_MOVE_RIGHT]) {
gb = decideMove(RIGHT, gb);
}
if (intendedmove[FLAG_MOVE_UP]) {
gb = decideMove(UP, gb);
}
if (intendedmove[FLAG_MOVE_DOWN]) {
gb = decideMove(DOWN, gb);
}
return std::make_tuple(true, gb);
}
bool check_input_check_to_end_game(char c) {
using namespace Input::Keypress::Code;
switch (std::toupper(c)) {
case CODE_HOTKEY_CHOICE_NO:
return true;
}
return false;
}
bool continue_playing_game(std::istream &in_os) {
char letter_choice;
in_os >> letter_choice;
if (check_input_check_to_end_game(letter_choice)) {
return false;
}
return true;
}
bool_gamestatus_t process_gameStatus(gamestatus_gameboard_t gsgb) {
gamestatus_t gamestatus;
GameBoard gb;
std::tie(gamestatus, gb) = gsgb;
auto loop_again{true};
if (!gamestatus[FLAG_ENDLESS_MODE]) {
if (gamestatus[FLAG_WIN]) {
if (continue_playing_game(std::cin)) {
gamestatus[FLAG_ENDLESS_MODE] = true;
gamestatus[FLAG_QUESTION_STAY_OR_QUIT] = false;
gamestatus[FLAG_WIN] = false;
} else {
loop_again = false;
}
}
}
if (gamestatus[FLAG_END_GAME]) {
// End endless_mode;
loop_again = false;
}
if (gamestatus[FLAG_SAVED_GAME]) {
Saver::saveGamePlayState(gb);
}
// New loop cycle: reset question asking event trigger
gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE] = false;
return std::make_tuple(loop_again, gamestatus);
}
using bool_current_game_session_t = std::tuple<bool, current_game_session_t>;
bool_current_game_session_t soloGameLoop(current_game_session_t cgs) {
using namespace Input;
using tup_idx = tuple_cgs_t_idx;
const auto gamestatus =
std::addressof(std::get<tup_idx::IDX_GAMESTATUS>(cgs));
const auto gb = std::addressof(std::get<tup_idx::IDX_GAMEBOARD>(cgs));
std::tie(*gamestatus, *gb) =
process_gamelogic(std::make_tuple(*gamestatus, *gb));
DrawAlways(std::cout, DataSuppliment(cgs, drawGraphics));
*gamestatus = update_one_shot_display_flags(*gamestatus);
intendedmove_t player_intendedmove{};
std::tie(player_intendedmove, *gamestatus) =
receive_agent_input(player_intendedmove, *gamestatus);
std::tie(std::ignore, *gb) = process_agent_input(player_intendedmove, *gb);
bool loop_again;
std::tie(loop_again, *gamestatus) =
process_gameStatus(std::make_tuple(*gamestatus, *gb));
return std::make_tuple(loop_again, cgs);
}
Graphics::end_screen_display_data_t
make_end_screen_display_data(gamestatus_t world_gamestatus) {
const auto esdd = std::make_tuple(world_gamestatus[FLAG_WIN],
world_gamestatus[FLAG_ENDLESS_MODE]);
return esdd;
};
std::string drawEndGameLoopGraphics(current_game_session_t finalgamestatus) {
// Graphical Output has a specific ordering...
using namespace Graphics;
using namespace Gameboard::Graphics;
using tup_idx = tuple_cgs_t_idx;
const auto bestScore = std::get<tup_idx::IDX_BESTSCORE>(finalgamestatus);
const auto comp_mode = std::get<tup_idx::IDX_COMP_MODE>(finalgamestatus);
const auto gb = std::get<tup_idx::IDX_GAMEBOARD>(finalgamestatus);
const auto end_gamestatus =
std::get<tup_idx::IDX_GAMESTATUS>(finalgamestatus);
std::ostringstream str_os;
// 1. Clear screen
clearScreen();
// 2. Draw Game Title Art
DrawAlways(str_os, AsciiArt2048);
// 3. Draw Scoreboard of ending current game session
const auto scdd = make_scoreboard_display_data(bestScore, comp_mode, gb);
DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay));
// 4. Draw snapshot of ending 2048 session's gameboard
DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput));
// 5. Draw "You win!" or "You Lose" prompt, only if not in endless mode.
const auto esdd = make_end_screen_display_data(end_gamestatus);
DrawAlways(str_os, DataSuppliment(esdd, GameEndScreenOverlay));
return str_os.str();
}
GameBoard endlessGameLoop(ull currentBestScore, competition_mode_t cm,
GameBoard gb) {
auto loop_again{true};
auto currentgamestatus =
std::make_tuple(currentBestScore, cm, gamestatus_t{}, gb);
while (loop_again) {
std::tie(loop_again, currentgamestatus) = soloGameLoop(currentgamestatus);
}
DrawAlways(std::cout,
DataSuppliment(currentgamestatus, drawEndGameLoopGraphics));
return gb;
}
Scoreboard::Score make_finalscore_from_game_session(double duration,
GameBoard gb) {
Scoreboard::Score finalscore{};
finalscore.score = gb.score;
finalscore.win = hasWonOnGameboard(gb);
finalscore.moveCount = MoveCountOnGameBoard(gb);
finalscore.largestTile = gb.largestTile;
finalscore.duration = duration;
return finalscore;
}
void DoPostGameSaveStuff(Scoreboard::Score finalscore, competition_mode_t cm) {
if (cm) {
Statistics::CreateFinalScoreAndEndGameDataFile(std::cout, std::cin,
finalscore);
}
}
} // namespace
void playGame(PlayGameFlag cont, GameBoard gb, ull userInput_PlaySize) {
const auto is_this_a_new_game = (cont == PlayGameFlag::BrandNewGame);
const auto is_competition_mode =
(userInput_PlaySize == COMPETITION_GAME_BOARD_PLAY_SIZE);
const auto bestScore = Statistics::load_game_best_score();
if (is_this_a_new_game) {
gb = GameBoard(userInput_PlaySize);
addTileOnGameboard(gb);
}
const auto startTime = std::chrono::high_resolution_clock::now();
gb = endlessGameLoop(bestScore, is_competition_mode, gb);
const auto finishTime = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> elapsed = finishTime - startTime;
const auto duration = elapsed.count();
if (is_this_a_new_game) {
const auto finalscore = make_finalscore_from_game_session(duration, gb);
DoPostGameSaveStuff(finalscore, is_competition_mode);
}
}
void startGame() {
PreGameSetup::SetUpNewGame();
}
void continueGame() {
PreGameSetup::ContinueOldGame();
}
} // namespace Game

View File

@@ -0,0 +1,77 @@
#include "gameboard-graphics.hpp"
#include "gameboard.hpp"
#include "point2d.hpp"
#include "tile-graphics.hpp"
#include "tile.hpp"
#include <algorithm>
#include <array>
#include <sstream>
namespace Game {
namespace Gameboard {
namespace Graphics {
namespace {
template<size_t num_of_bars>
std::array<std::string, num_of_bars> make_patterned_bars(int playsize) {
auto temp_bars = std::array<std::string, num_of_bars>{};
using bar_pattern_t = std::tuple<std::string, std::string, std::string>;
const auto bar_pattern_list = {std::make_tuple("", "", ""),
std::make_tuple("", "", ""),
std::make_tuple("", "", "")};
// generate types of horizontal bars...
const auto generate_x_bar_pattern = [playsize](const bar_pattern_t t) {
enum { PATTERN_HEAD, PATTERN_MID, PATTERN_TAIL };
constexpr auto sp = " ";
constexpr auto separator = "──────";
std::ostringstream temp_richtext;
temp_richtext << sp << std::get<PATTERN_HEAD>(t);
for (auto i = 0; i < playsize; i++) {
const auto is_not_last_column = (i < playsize - 1);
temp_richtext << separator
<< (is_not_last_column ? std::get<PATTERN_MID>(t) :
std::get<PATTERN_TAIL>(t));
}
temp_richtext << "\n";
return temp_richtext.str();
};
std::transform(std::begin(bar_pattern_list), std::end(bar_pattern_list),
std::begin(temp_bars), generate_x_bar_pattern);
return temp_bars;
}
std::string drawSelf(GameBoard::gameboard_data_array_t gbda) {
enum { TOP_BAR, XN_BAR, BASE_BAR, MAX_TYPES_OF_BARS };
const int playsize = getPlaySizeOfGameboardDataArray(gbda);
const auto vertibar = make_patterned_bars<MAX_TYPES_OF_BARS>(playsize);
std::ostringstream str_os;
for (auto y = 0; y < playsize; y++) {
const auto is_first_row = (y == 0);
str_os << (is_first_row ? std::get<TOP_BAR>(vertibar) :
std::get<XN_BAR>(vertibar));
for (auto x = 0; x < playsize; x++) {
const auto is_first_col = (x == 0);
const auto sp = (is_first_col ? " " : " ");
const auto tile = getTileOnGameboardDataArray(gbda, point2D_t{x, y});
str_os << sp;
str_os << "";
str_os << drawTileString(tile);
}
str_os << "";
str_os << "\n";
}
str_os << std::get<BASE_BAR>(vertibar);
str_os << "\n";
return str_os.str();
}
} // namespace
std::string GameBoardTextOutput(GameBoard gb) {
return drawSelf(gb.gbda);
}
} // namespace Graphics
} // namespace Gameboard
} // namespace Game

415
Make/2048/gameboard.cpp Normal file
View File

@@ -0,0 +1,415 @@
#include "gameboard.hpp"
#include "gameboard-graphics.hpp"
#include "point2d.hpp"
#include <algorithm>
#include <array>
#include <chrono>
#include <random>
#include <sstream>
#include <utility>
namespace Game {
namespace {
class RandInt {
public:
using clock = std::chrono::system_clock;
RandInt() : dist{0, std::numeric_limits<int>::max()} {
seed(clock::now().time_since_epoch().count());
}
RandInt(const int low, const int high) : dist{low, high} {
seed(clock::now().time_since_epoch().count());
}
int operator()() { return dist(re); }
void seed(const unsigned int s) { re.seed(s); }
private:
std::minstd_rand re;
std::uniform_int_distribution<> dist;
};
using gameboard_data_array_t = GameBoard::gameboard_data_array_t;
enum gameboard_data_array_fields { IDX_PLAYSIZE, IDX_BOARD, MAX_NO_INDEXES };
struct gameboard_data_point_t {
static int point2D_to_1D_index(gameboard_data_array_t gbda, point2D_t pt) {
int x, y;
std::tie(x, y) = pt.get();
return x + getPlaySizeOfGameboardDataArray(gbda) * y;
}
tile_t operator()(gameboard_data_array_t gbda, point2D_t pt) const {
return std::get<IDX_BOARD>(gbda)[point2D_to_1D_index(gbda, pt)];
}
tile_t &operator()(gameboard_data_array_t &gbda, point2D_t pt) {
return std::get<IDX_BOARD>(gbda)[point2D_to_1D_index(gbda, pt)];
}
};
void setTileOnGameboardDataArray(gameboard_data_array_t &gbda, point2D_t pt,
tile_t tile) {
gameboard_data_point_t{}(gbda, pt) = tile;
}
ull getTileValueOnGameboardDataArray(gameboard_data_array_t gbda,
point2D_t pt) {
return gameboard_data_point_t{}(gbda, pt).value;
}
void setTileValueOnGameboardDataArray(gameboard_data_array_t &gbda,
point2D_t pt, ull value) {
gameboard_data_point_t{}(gbda, pt).value = value;
}
bool getTileBlockedOnGameboardDataArray(gameboard_data_array_t gbda,
point2D_t pt) {
return gameboard_data_point_t{}(gbda, pt).blocked;
}
std::string printStateOfGameBoardDataArray(gameboard_data_array_t gbda) {
const int playsize = getPlaySizeOfGameboardDataArray(gbda);
std::ostringstream os;
for (auto y = 0; y < playsize; y++) {
for (auto x = 0; x < playsize; x++) {
const auto current_point = point2D_t{x, y};
os << getTileValueOnGameboardDataArray(gbda, current_point) << ":"
<< getTileBlockedOnGameboardDataArray(gbda, current_point) << ",";
}
os << "\n";
}
return os.str();
}
bool is_point_in_board_play_area(point2D_t pt, int playsize) {
int x, y;
std::tie(x, y) = pt.get();
return !(y < 0 || y > playsize - 1 || x < 0 || x > playsize - 1);
}
using delta_t = std::pair<point2D_t, point2D_t>;
// NOTE: delta_t.first = focal point, delta_t.second = offset distance
bool check_recursive_offset_in_game_bounds(delta_t dt_point, int playsize) {
int x, y, x2, y2;
std::tie(x, y) = dt_point.first.get();
std::tie(x2, y2) = dt_point.second.get();
const auto positive_direction = (y2 + x2 == 1);
const auto negative_direction = (y2 + x2 == -1);
const auto is_positive_y_direction_flagged = (y2 == 1);
const auto is_negative_y_direction_flagged = (y2 == -1);
const auto is_inside_outer_bounds =
(positive_direction &&
(is_positive_y_direction_flagged ? y : x) < playsize - 2);
const auto is_inside_inner_bounds =
(negative_direction && (is_negative_y_direction_flagged ? y : x) > 1);
return (is_inside_outer_bounds || is_inside_inner_bounds);
}
gameboard_data_array_t
unblockTilesOnGameboardDataArray(gameboard_data_array_t gbda) {
using tile_data_array_t = GameBoard::tile_data_array_t;
auto new_board_data_array =
tile_data_array_t(std::get<IDX_BOARD>(gbda).size());
std::transform(std::begin(std::get<IDX_BOARD>(gbda)),
std::end(std::get<IDX_BOARD>(gbda)),
std::begin(new_board_data_array), [](const tile_t t) {
return tile_t{t.value, false};
});
return gameboard_data_array_t{std::get<IDX_PLAYSIZE>(gbda),
new_board_data_array};
}
bool canMoveOnGameboardDataArray(gameboard_data_array_t gbda) {
auto index_counter{0};
const auto can_move_to_offset = [=, &index_counter](const tile_t t) {
const int playsize = getPlaySizeOfGameboardDataArray(gbda);
const auto current_point =
point2D_t{index_counter % playsize, index_counter / playsize};
index_counter++;
const auto list_of_offsets = {point2D_t{1, 0}, point2D_t{0, 1}};
const auto current_point_value = t.value;
const auto offset_in_range_with_same_value = [=](const point2D_t offset) {
const auto offset_check = {
current_point + offset, // Positive adjacent check
current_point - offset}; // Negative adjacent Check
for (const auto current_offset : offset_check) {
if (is_point_in_board_play_area(current_offset, playsize)) {
return getTileValueOnGameboardDataArray(gbda, current_offset) ==
current_point_value;
}
}
return false;
};
return ((current_point_value == 0u) ||
std::any_of(std::begin(list_of_offsets), std::end(list_of_offsets),
offset_in_range_with_same_value));
};
return std::any_of(std::begin(std::get<IDX_BOARD>(gbda)),
std::end(std::get<IDX_BOARD>(gbda)), can_move_to_offset);
}
std::vector<size_t>
collectFreeTilesOnGameboardDataArray(gameboard_data_array_t gbda) {
std::vector<size_t> freeTiles;
auto index_counter{0};
for (const auto t : std::get<IDX_BOARD>(gbda)) {
if (!t.value) {
freeTiles.push_back(index_counter);
}
index_counter++;
}
return freeTiles;
}
bool addTileOnGameboardDataArray(gameboard_data_array_t &gbda) {
constexpr auto CHANCE_OF_VALUE_FOUR_OVER_TWO = 89; // Percentage
const auto index_list_of_free_tiles =
collectFreeTilesOnGameboardDataArray(gbda);
if (!index_list_of_free_tiles.size()) {
return true;
}
const int playsize = getPlaySizeOfGameboardDataArray(gbda);
const int rand_selected_index = index_list_of_free_tiles.at(
RandInt{}() % index_list_of_free_tiles.size());
const auto rand_index_as_point_t =
point2D_t{rand_selected_index % playsize, rand_selected_index / playsize};
const auto value_four_or_two =
RandInt{}() % 100 > CHANCE_OF_VALUE_FOUR_OVER_TWO ? 4 : 2;
setTileValueOnGameboardDataArray(gbda, rand_index_as_point_t,
value_four_or_two);
return false;
}
bool collaspeTilesOnGameboardDataArray(gameboard_data_array_t &gbda,
delta_t dt_point) {
tile_t currentTile = getTileOnGameboardDataArray(gbda, dt_point.first);
tile_t targetTile =
getTileOnGameboardDataArray(gbda, dt_point.first + dt_point.second);
currentTile.value = 0;
targetTile.value *= 2;
targetTile.blocked = true;
setTileOnGameboardDataArray(gbda, dt_point.first, currentTile);
setTileOnGameboardDataArray(gbda, dt_point.first + dt_point.second,
targetTile);
return true;
}
bool shiftTilesOnGameboardDataArray(gameboard_data_array_t &gbda,
delta_t dt_point) {
tile_t currentTile = getTileOnGameboardDataArray(gbda, dt_point.first);
tile_t targetTile =
getTileOnGameboardDataArray(gbda, dt_point.first + dt_point.second);
targetTile.value = currentTile.value;
currentTile.value = 0;
setTileOnGameboardDataArray(gbda, dt_point.first, currentTile);
setTileOnGameboardDataArray(gbda, dt_point.first + dt_point.second,
targetTile);
return true;
}
enum class COLLASPE_OR_SHIFT_T {
ACTION_NONE,
ACTION_COLLASPE,
ACTION_SHIFT,
MAX_NUM_OF_ACTIONS
};
using bool_collaspe_shift_t = std::tuple<bool, COLLASPE_OR_SHIFT_T>;
bool_collaspe_shift_t
collasped_or_shifted_tilesOnGameboardDataArray(gameboard_data_array_t gbda,
delta_t dt_point) {
const auto currentTile = getTileOnGameboardDataArray(gbda, dt_point.first);
const auto targetTile =
getTileOnGameboardDataArray(gbda, dt_point.first + dt_point.second);
const auto does_value_exist_in_target_point = targetTile.value;
const auto is_value_same_as_target_value =
(currentTile.value == targetTile.value);
const auto no_tiles_are_blocked =
(!currentTile.blocked && !targetTile.blocked);
const auto is_there_a_current_value_but_no_target_value =
(currentTile.value && !targetTile.value);
const auto do_collapse =
(does_value_exist_in_target_point && is_value_same_as_target_value &&
no_tiles_are_blocked);
const auto do_shift = is_there_a_current_value_but_no_target_value;
const auto action_taken = (do_collapse || do_shift);
if (do_collapse) {
return std::make_tuple(action_taken, COLLASPE_OR_SHIFT_T::ACTION_COLLASPE);
} else if (do_shift) {
return std::make_tuple(action_taken, COLLASPE_OR_SHIFT_T::ACTION_SHIFT);
}
return std::make_tuple(action_taken, COLLASPE_OR_SHIFT_T::ACTION_NONE);
}
bool updateGameBoardStats(GameBoard &gb, ull target_tile_value) {
gb.score += target_tile_value;
// Discover the largest tile value on the gameboard...
gb.largestTile = std::max(gb.largestTile, target_tile_value);
// Discover the winning tile value on the gameboard...
if (!hasWonOnGameboard(gb)) {
constexpr auto GAME_TILE_WINNING_SCORE = 2048;
if (target_tile_value == GAME_TILE_WINNING_SCORE) {
gb.win = true;
}
}
return true;
}
void moveOnGameboard(GameBoard &gb, delta_t dt_point) {
auto did_gameboard_collaspe_or_shift_anything = bool{};
auto action_was_taken = COLLASPE_OR_SHIFT_T::ACTION_NONE;
std::tie(did_gameboard_collaspe_or_shift_anything, action_was_taken) =
collasped_or_shifted_tilesOnGameboardDataArray(gb.gbda, dt_point);
if (did_gameboard_collaspe_or_shift_anything) {
gb.moved = true;
if (action_was_taken == COLLASPE_OR_SHIFT_T::ACTION_COLLASPE) {
collaspeTilesOnGameboardDataArray(gb.gbda, dt_point);
const auto targetTile = getTileOnGameboardDataArray(
gb.gbda, dt_point.first + dt_point.second);
updateGameBoardStats(gb, targetTile.value);
}
if (action_was_taken == COLLASPE_OR_SHIFT_T::ACTION_SHIFT) {
shiftTilesOnGameboardDataArray(gb.gbda, dt_point);
}
}
if (check_recursive_offset_in_game_bounds(
dt_point, getPlaySizeOfGameboardDataArray(gb.gbda))) {
moveOnGameboard(
gb, std::make_pair(dt_point.first + dt_point.second, dt_point.second));
}
}
void doTumbleTilesUpOnGameboard(GameBoard &gb) {
const int playsize = getPlaySizeOfGameboardDataArray(gb.gbda);
for (auto x = 0; x < playsize; x++) {
auto y = 1;
while (y < playsize) {
const auto current_point = point2D_t{x, y};
if (getTileValueOnGameboardDataArray(gb.gbda, current_point)) {
moveOnGameboard(gb, std::make_pair(current_point, point2D_t{0, -1}));
}
y++;
}
}
}
void doTumbleTilesDownOnGameboard(GameBoard &gb) {
const int playsize = getPlaySizeOfGameboardDataArray(gb.gbda);
for (auto x = 0; x < playsize; x++) {
auto y = playsize - 2;
while (y >= 0) {
const auto current_point = point2D_t{x, y};
if (getTileValueOnGameboardDataArray(gb.gbda, current_point)) {
moveOnGameboard(gb, std::make_pair(current_point, point2D_t{0, 1}));
}
y--;
}
}
}
void doTumbleTilesLeftOnGameboard(GameBoard &gb) {
const int playsize = getPlaySizeOfGameboardDataArray(gb.gbda);
for (auto y = 0; y < playsize; y++) {
auto x = 1;
while (x < playsize) {
const auto current_point = point2D_t{x, y};
if (getTileValueOnGameboardDataArray(gb.gbda, current_point)) {
moveOnGameboard(gb, std::make_pair(current_point, point2D_t{-1, 0}));
}
x++;
}
}
}
void doTumbleTilesRightOnGameboard(GameBoard &gb) {
const int playsize = getPlaySizeOfGameboardDataArray(gb.gbda);
for (auto y = 0; y < playsize; y++) {
auto x = playsize - 2;
while (x >= 0) {
const auto current_point = point2D_t{x, y};
if (getTileValueOnGameboardDataArray(gb.gbda, current_point)) {
moveOnGameboard(gb, std::make_pair(current_point, point2D_t{1, 0}));
}
x--;
}
}
}
} // namespace
GameBoard::GameBoard(ull playsize)
: GameBoard{playsize, tile_data_array_t(playsize * playsize)} {
}
GameBoard::GameBoard(ull playsize, tile_data_array_t prempt_board)
: gbda{playsize, prempt_board} {
}
size_t getPlaySizeOfGameboardDataArray(gameboard_data_array_t gbda) {
return std::get<IDX_PLAYSIZE>(gbda);
}
tile_t getTileOnGameboardDataArray(gameboard_data_array_t gbda, point2D_t pt) {
return gameboard_data_point_t{}(gbda, pt);
}
bool hasWonOnGameboard(GameBoard gb) {
return gb.win;
}
long long MoveCountOnGameBoard(GameBoard gb) {
return gb.moveCount;
}
void unblockTilesOnGameboard(GameBoard &gb) {
gb.gbda = unblockTilesOnGameboardDataArray(gb.gbda);
}
bool canMoveOnGameboard(GameBoard &gb) {
return canMoveOnGameboardDataArray(gb.gbda);
}
void registerMoveByOneOnGameboard(GameBoard &gb) {
gb.moveCount++;
gb.moved = false;
}
bool addTileOnGameboard(GameBoard &gb) {
return addTileOnGameboardDataArray(gb.gbda);
}
void tumbleTilesUpOnGameboard(GameBoard &gb) {
doTumbleTilesUpOnGameboard(gb);
}
void tumbleTilesDownOnGameboard(GameBoard &gb) {
doTumbleTilesDownOnGameboard(gb);
}
void tumbleTilesLeftOnGameboard(GameBoard &gb) {
doTumbleTilesLeftOnGameboard(gb);
}
void tumbleTilesRightOnGameboard(GameBoard &gb) {
doTumbleTilesRightOnGameboard(gb);
}
std::string printStateOfGameBoard(GameBoard gb) {
return printStateOfGameBoardDataArray(gb.gbda);
}
} // namespace Game

76
Make/2048/global.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "global.hpp"
#include "color.hpp"
#include <iostream>
#include <sstream>
#ifdef _WIN32
void getKeypressDownInput(char &c) {
std::cin >> c;
}
#else
# include <termios.h>
# include <unistd.h>
char getch() {
char buf = 0;
struct termios old = {0};
if (tcgetattr(0, &old) < 0)
perror("tcsetattr()");
old.c_lflag &= ~ICANON;
old.c_lflag &= ~ECHO;
old.c_cc[VMIN] = 1;
old.c_cc[VTIME] = 0;
if (tcsetattr(0, TCSANOW, &old) < 0)
perror("tcsetattr ICANON");
if (read(0, &buf, 1) < 0)
perror("read()");
old.c_lflag |= ICANON;
old.c_lflag |= ECHO;
if (tcsetattr(0, TCSADRAIN, &old) < 0)
perror("tcsetattr ~ICANON");
return (buf);
}
void getKeypressDownInput(char &c) {
c = getch();
}
#endif
void pause_for_keypress() {
char c{};
getKeypressDownInput(c);
}
void wait_for_any_letter_input(std::istream &is) {
char c;
is >> c;
}
void clearScreen() {
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
};
std::string secondsFormat(double sec) {
double s = sec;
int m = s / 60;
s -= m * 60;
int h = m / 60;
m %= 60;
s = (int)s;
std::ostringstream oss;
if (h) {
oss << h << "h ";
}
if (m) {
oss << m << "m ";
}
oss << s << "s";
return oss.str();
}

View File

@@ -0,0 +1 @@
//#include "game.hpp"

View File

@@ -0,0 +1,64 @@
#ifndef COLOR_H
#define COLOR_H
#include <ostream>
namespace Color {
enum class Code {
BOLD = 1,
RESET = 0,
BG_BLUE = 44,
BG_DEFAULT = 49,
BG_GREEN = 42,
BG_RED = 41,
FG_BLACK = 30,
FG_BLUE = 34,
FG_CYAN = 36,
FG_DARK_GRAY = 90,
FG_DEFAULT = 39,
FG_GREEN = 32,
FG_LIGHT_BLUE = 94,
FG_LIGHT_CYAN = 96,
FG_LIGHT_GRAY = 37,
FG_LIGHT_GREEN = 92,
FG_LIGHT_MAGENTA = 95,
FG_LIGHT_RED = 91,
FG_LIGHT_YELLOW = 93,
FG_MAGENTA = 35,
FG_RED = 31,
FG_WHITE = 97,
FG_YELLOW = 33,
};
class Modifier {
Code code;
public:
Modifier(Code pCode) : code(pCode) {}
friend std::ostream &operator<<(std::ostream &os, const Modifier &mod) {
return os << "\033[" << static_cast<int>(mod.code) << "m";
}
};
} // namespace Color
static Color::Modifier bold_off(Color::Code::RESET);
static Color::Modifier bold_on(Color::Code::BOLD);
static Color::Modifier def(Color::Code::FG_DEFAULT);
static Color::Modifier red(Color::Code::FG_RED);
static Color::Modifier green(Color::Code::FG_GREEN);
static Color::Modifier yellow(Color::Code::FG_YELLOW);
static Color::Modifier blue(Color::Code::FG_BLUE);
static Color::Modifier magenta(Color::Code::FG_MAGENTA);
static Color::Modifier cyan(Color::Code::FG_CYAN);
static Color::Modifier lightGray(Color::Code::FG_LIGHT_GRAY);
static Color::Modifier darkGray(Color::Code::FG_DARK_GRAY);
static Color::Modifier lightRed(Color::Code::FG_LIGHT_RED);
static Color::Modifier lightGreen(Color::Code::FG_LIGHT_GREEN);
static Color::Modifier lightYellow(Color::Code::FG_LIGHT_YELLOW);
static Color::Modifier lightBlue(Color::Code::FG_LIGHT_BLUE);
static Color::Modifier lightMagenta(Color::Code::FG_LIGHT_MAGENTA);
static Color::Modifier lightCyan(Color::Code::FG_LIGHT_CYAN);
#endif

View File

@@ -0,0 +1,39 @@
#ifndef GAMEGRAPHICS_H
#define GAMEGRAPHICS_H
#include <string>
#include <tuple>
enum GameBoardDimensions {
MIN_GAME_BOARD_PLAY_SIZE = 3,
MAX_GAME_BOARD_PLAY_SIZE = 10
};
enum { COMPETITION_GAME_BOARD_PLAY_SIZE = 4 };
namespace Game {
namespace Graphics {
std::string AsciiArt2048();
std::string BoardInputPrompt();
std::string YouWinPrompt();
std::string GameOverPrompt();
std::string EndOfEndlessPrompt();
std::string InvalidInputGameBoardErrorPrompt();
std::string QuestionEndOfWinningGamePrompt();
std::string GameStateNowSavedPrompt();
std::string GameBoardNoSaveErrorPrompt();
std::string BoardSizeErrorPrompt();
std::string InputCommandListPrompt();
std::string EndlessModeCommandListPrompt();
std::string InputCommandListFooterPrompt();
using scoreboard_display_data_t =
std::tuple<bool, std::string, std::string, std::string>;
std::string GameScoreBoardBox(scoreboard_display_data_t scdd);
std::string GameScoreBoardOverlay(scoreboard_display_data_t scdd);
using end_screen_display_data_t = std::tuple<bool, bool>;
std::string GameEndScreenOverlay(end_screen_display_data_t esdd);
using input_controls_display_data_t = std::tuple<bool, bool>;
std::string GameInputControlsOverlay(input_controls_display_data_t gamestatus);
} // namespace Graphics
} // namespace Game
#endif

View File

@@ -0,0 +1,71 @@
#ifndef GAMEINPUT_H
#define GAMEINPUT_H
#include <array>
namespace Game {
namespace Input {
namespace Keypress {
namespace Code {
enum { CODE_ESC = 27, CODE_LSQUAREBRACKET = '[' };
// Hotkey bindings:
// Style: ANSI (Arrow Keys)
enum {
CODE_ANSI_TRIGGER_1 = CODE_ESC,
CODE_ANSI_TRIGGER_2 = CODE_LSQUAREBRACKET
};
enum {
CODE_ANSI_UP = 'A',
CODE_ANSI_DOWN = 'B',
CODE_ANSI_LEFT = 'D',
CODE_ANSI_RIGHT = 'C'
};
// Style: WASD
enum {
CODE_WASD_UP = 'W',
CODE_WASD_DOWN = 'S',
CODE_WASD_LEFT = 'A',
CODE_WASD_RIGHT = 'D'
};
// Style: Vim
enum {
CODE_VIM_UP = 'K',
CODE_VIM_DOWN = 'J',
CODE_VIM_LEFT = 'H',
CODE_VIM_RIGHT = 'L'
};
enum {
CODE_HOTKEY_ACTION_SAVE = 'Z',
CODE_HOTKEY_ALTERNATE_ACTION_SAVE = 'P',
CODE_HOTKEY_QUIT_ENDLESS_MODE = 'X',
CODE_HOTKEY_CHOICE_NO = 'N',
CODE_HOTKEY_CHOICE_YES = 'Y',
CODE_HOTKEY_PREGAMEMENU_BACK_TO_MAINMENU = 0
};
} // namespace Code
} // namespace Keypress
enum IntendedMoveFlag {
FLAG_MOVE_LEFT,
FLAG_MOVE_RIGHT,
FLAG_MOVE_UP,
FLAG_MOVE_DOWN,
MAX_NO_INTENDED_MOVE_FLAGS
};
using intendedmove_t = std::array<bool, MAX_NO_INTENDED_MOVE_FLAGS>;
bool check_input_ansi(char c, intendedmove_t &intendedmove);
bool check_input_vim(char c, intendedmove_t &intendedmove);
bool check_input_wasd(char c, intendedmove_t &intendedmove);
} // namespace Input
} // namespace Game
#endif

View File

@@ -0,0 +1,11 @@
#ifndef GAMEPREGAMEMENU_H
#define GAMEPREGAMEMENU_H
namespace Game {
namespace PreGameSetup {
void SetUpNewGame();
void ContinueOldGame();
} // namespace PreGameSetup
} // namespace Game
#endif

View File

@@ -0,0 +1,15 @@
#ifndef GAME_H
#define GAME_H
namespace Game {
struct GameBoard;
enum class PlayGameFlag { BrandNewGame, ContinuePreviousGame };
void playGame(PlayGameFlag cont, GameBoard gb,
unsigned long long userInput_PlaySize = 1);
void startGame();
void continueGame();
}; // namespace Game
#endif

View File

@@ -0,0 +1,16 @@
#ifndef GAMEBOARDGRAPHICS_H
#define GAMEBOARDGRAPHICS_H
#include <string>
namespace Game {
struct GameBoard;
namespace Gameboard {
namespace Graphics {
std::string GameBoardTextOutput(GameBoard gb);
}
} // namespace Gameboard
} // namespace Game
#endif

View File

@@ -0,0 +1,49 @@
#ifndef GAMEBOARD_H
#define GAMEBOARD_H
#include "tile.hpp"
#include <tuple>
#include <vector>
struct point2D_t;
namespace Game {
struct GameBoard {
using tile_data_array_t = std::vector<tile_t>;
using gameboard_data_array_t = std::tuple<size_t, tile_data_array_t>;
gameboard_data_array_t gbda;
bool win{};
bool moved{true};
ull score{};
ull largestTile{2};
long long moveCount{-1};
GameBoard() = default;
explicit GameBoard(ull playsize);
explicit GameBoard(ull playsize, tile_data_array_t prempt_board);
};
size_t getPlaySizeOfGameboardDataArray(GameBoard::gameboard_data_array_t gbda);
tile_t getTileOnGameboardDataArray(GameBoard::gameboard_data_array_t gbda,
point2D_t pt);
bool hasWonOnGameboard(GameBoard gb);
long long MoveCountOnGameBoard(GameBoard gb);
void unblockTilesOnGameboard(GameBoard &gb);
bool canMoveOnGameboard(GameBoard &gb);
bool addTileOnGameboard(GameBoard &gb);
void registerMoveByOneOnGameboard(GameBoard &gb);
void tumbleTilesUpOnGameboard(GameBoard &gb);
void tumbleTilesDownOnGameboard(GameBoard &gb);
void tumbleTilesLeftOnGameboard(GameBoard &gb);
void tumbleTilesRightOnGameboard(GameBoard &gb);
std::string printStateOfGameBoard(GameBoard gb);
} // namespace Game
#endif

View File

@@ -0,0 +1,54 @@
#ifndef GLOBAL_H
#define GLOBAL_H
#include <iosfwd>
#include <string>
using ull = unsigned long long;
void getKeypressDownInput(char &);
template<typename T>
void DrawAlways(std::ostream &os, T f) {
os << f();
}
template<typename T>
void DrawOnlyWhen(std::ostream &os, bool trigger, T f) {
if (trigger) {
DrawAlways(os, f);
}
}
template<typename T>
void DrawAsOneTimeFlag(std::ostream &os, bool &trigger, T f) {
if (trigger) {
DrawAlways(os, f);
trigger = !trigger;
}
}
template<typename suppliment_t>
struct DataSupplimentInternalType {
suppliment_t suppliment_data;
template<typename function_t>
std::string operator()(function_t f) const {
return f(suppliment_data);
}
};
template<typename suppliment_t, typename function_t>
auto DataSuppliment(suppliment_t needed_data, function_t f) {
using dsit_t = DataSupplimentInternalType<suppliment_t>;
const auto lambda_f_to_return = [=]() {
const dsit_t depinject_func = dsit_t{needed_data};
return depinject_func(f);
};
return lambda_f_to_return;
}
void pause_for_keypress();
void wait_for_any_letter_input(std::istream &is);
void clearScreen();
std::string secondsFormat(double);
#endif

View File

@@ -0,0 +1,21 @@
#ifndef LOADRESOURCE_H
#define LOADRESOURCE_H
#include <string>
#include <tuple>
namespace Game {
using load_gameboard_status_t = std::tuple<bool, struct GameBoard>;
namespace Loader {
load_gameboard_status_t load_GameBoard_data_from_file(std::string filename);
// Output: {[loadfile_ok_status], [decltype(gameboard.score)],
// [decltype(gameboard.moveCount)]}
std::tuple<bool, std::tuple<unsigned long long, long long>>
load_game_stats_from_file(std::string filename);
} // namespace Loader
} // namespace Game
#endif

View File

@@ -0,0 +1,18 @@
#ifndef MENUGRAPHICS_H
#define MENUGRAPHICS_H
#include <string>
namespace Game {
namespace Graphics {
namespace Menu {
std::string MainMenuTitlePrompt();
std::string MainMenuOptionsPrompt();
std::string InputMenuErrorInvalidInputPrompt();
std::string InputMenuPrompt();
std::string MainMenuGraphicsOverlay(bool input_error_choice_invalid);
} // namespace Menu
} // namespace Graphics
} // namespace Game
#endif

View File

@@ -0,0 +1,8 @@
#ifndef MENU_H
#define MENU_H
namespace Menu {
void startMenu();
} // namespace Menu
#endif

View File

@@ -0,0 +1,56 @@
#ifndef POINT_2D_H
#define POINT_2D_H
#include <tuple>
class point2D_t {
// Simple {x,y} datastructure = std::tuple<int, int>...
using point_datatype_t = typename std::tuple<int, int>;
point_datatype_t point_vector{};
explicit point2D_t(const point_datatype_t pt) : point_vector{pt} {}
public:
enum class PointCoord { COORD_X, COORD_Y };
point2D_t() = default;
point2D_t(const int x, const int y) : point2D_t(std::make_tuple(x, y)) {}
template<PointCoord dimension>
int get() const {
return std::get<static_cast<int>(dimension)>(point_vector);
}
template<PointCoord dimension>
void set(int value) {
std::get<static_cast<int>(dimension)>(point_vector) = value;
}
point_datatype_t get() const { return point_vector; }
void set(point_datatype_t value) { point_vector = value; }
void set(const int x, const int y) { set(std::make_tuple(x, y)); }
point2D_t &operator+=(const point2D_t &pt) {
this->point_vector = std::make_tuple(
get<PointCoord::COORD_X>() + pt.get<PointCoord::COORD_X>(),
get<PointCoord::COORD_Y>() + pt.get<PointCoord::COORD_Y>());
return *this;
}
point2D_t &operator-=(const point2D_t &pt) {
this->point_vector = std::make_tuple(
get<PointCoord::COORD_X>() - pt.get<PointCoord::COORD_X>(),
get<PointCoord::COORD_Y>() - pt.get<PointCoord::COORD_Y>());
return *this;
}
};
inline point2D_t operator+(point2D_t l, const point2D_t &r) {
l += r;
return l;
}
inline point2D_t operator-(point2D_t l, const point2D_t &r) {
l -= r;
return l;
}
#endif

View File

@@ -0,0 +1,15 @@
#ifndef SAVERESOURCE_H
#define SAVERESOURCE_H
#include <string>
#include <tuple>
namespace Game {
struct GameBoard;
namespace Saver {
void saveGamePlayState(GameBoard gb);
} // namespace Saver
} // namespace Game
#endif

View File

@@ -0,0 +1,23 @@
#ifndef SCORESGRAPHICS_H
#define SCORESGRAPHICS_H
#include <string>
#include <tuple>
#include <vector>
namespace Scoreboard {
namespace Graphics {
using scoreboard_display_data_t =
std::tuple<std::string, std::string, std::string, std::string, std::string,
std::string, std::string>;
using scoreboard_display_data_list_t = std::vector<scoreboard_display_data_t>;
std::string ScoreboardOverlay(scoreboard_display_data_list_t sbddl);
using finalscore_display_data_t =
std::tuple<std::string, std::string, std::string, std::string>;
std::string EndGameStatisticsPrompt(finalscore_display_data_t finalscore);
} // namespace Graphics
} // namespace Scoreboard
#endif

View File

@@ -0,0 +1,34 @@
#ifndef SCORES_H
#define SCORES_H
#include "global.hpp"
#include <iosfwd>
#include <string>
#include <tuple>
#include <vector>
namespace Scoreboard {
struct Score {
std::string name;
ull score;
bool win;
ull largestTile;
long long moveCount;
double duration;
};
bool operator>(const Score &a, const Score &b);
using Scoreboard_t = std::vector<Score>;
using load_score_status_t = std::tuple<bool, Scoreboard_t>;
// List of scores read until "exhausted".
// Note: returns a tuple containing a std::vector<Score> of all read scores.
load_score_status_t loadFromFileScore(std::string filename);
void saveScore(Score finalscore);
} // namespace Scoreboard
std::istream &operator>>(std::istream &is, Scoreboard::Score &s);
std::ostream &operator<<(std::ostream &os, Scoreboard::Score &s);
#endif

View File

@@ -0,0 +1,19 @@
#ifndef STATISTICSGRAPHICS_H
#define STATISTICSGRAPHICS_H
#include <string>
#include <tuple>
namespace Statistics {
namespace Graphics {
std::string AskForPlayerNamePrompt();
std::string MessageScoreSavedPrompt();
using total_stats_display_data_t =
std::tuple<bool, std::string, std::string, std::string, std::string,
std::string>;
std::string TotalStatisticsOverlay(total_stats_display_data_t tsdd);
} // namespace Graphics
} // namespace Statistics
#endif

View File

@@ -0,0 +1,34 @@
#ifndef STATISTICS_H
#define STATISTICS_H
#include "global.hpp"
#include <iosfwd>
#include <string>
#include <tuple>
namespace Scoreboard {
struct Score;
}
namespace Statistics {
struct total_game_stats_t {
ull bestScore{};
ull totalMoveCount{};
int gameCount{};
double totalDuration{};
int winCount{};
};
using load_stats_status_t = std::tuple<bool, total_game_stats_t>;
load_stats_status_t loadFromFileStatistics(std::string filename);
ull load_game_best_score();
void saveEndGameStats(Scoreboard::Score finalscore);
void CreateFinalScoreAndEndGameDataFile(std::ostream &os, std::istream &is,
Scoreboard::Score finalscore);
} // namespace Statistics
std::istream &operator>>(std::istream &is, Statistics::total_game_stats_t &s);
std::ostream &operator<<(std::ostream &os, Statistics::total_game_stats_t &s);
#endif

View File

@@ -0,0 +1,11 @@
#ifndef TILEGRAPHICS_H
#define TILEGRAPHICS_H
#include <string>
namespace Game {
struct tile_t;
std::string drawTileString(tile_t currentTile);
} // namespace Game
#endif

View File

@@ -0,0 +1,13 @@
#ifndef TILE_H
#define TILE_H
#include "global.hpp"
namespace Game {
struct tile_t {
ull value{};
bool blocked{};
};
} // namespace Game
#endif

135
Make/2048/loadresource.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "loadresource.hpp"
#include "gameboard.hpp"
#include "global.hpp"
#include <algorithm>
#include <array>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
namespace Game {
namespace Loader {
namespace {
int GetLines(std::string filename) {
std::ifstream stateFile(filename);
using iter = std::istreambuf_iterator<char>;
const auto noOfLines = std::count(iter{stateFile}, iter{}, '\n');
return noOfLines;
}
std::vector<std::string> get_file_tile_data(std::istream &buf) {
std::vector<std::string> tempbuffer;
enum { MAX_WIDTH = 10, MAX_HEIGHT = 10 };
auto i{0};
for (std::string tempLine; std::getline(buf, tempLine) && i < MAX_WIDTH;
i++) {
std::istringstream temp_filestream(tempLine);
auto j{0};
for (std::string a_word;
std::getline(temp_filestream, a_word, ',') && j < MAX_HEIGHT; j++) {
tempbuffer.push_back(a_word);
}
}
return tempbuffer;
}
std::vector<tile_t>
process_file_tile_string_data(std::vector<std::string> buf) {
std::vector<tile_t> result_buf;
auto tile_processed_counter{0};
const auto prime_tile_data =
[&tile_processed_counter](const std::string tile_data) {
enum FieldIndex { IDX_TILE_VALUE, IDX_TILE_BLOCKED, MAX_NO_TILE_IDXS };
std::array<int, MAX_NO_TILE_IDXS> tile_internal{};
std::istringstream blocks(tile_data);
auto idx_id{0};
for (std::string temptiledata; std::getline(
blocks, temptiledata, ':') /*&& idx_id < MAX_NO_TILE_IDXS*/;
idx_id++) {
switch (idx_id) {
case IDX_TILE_VALUE:
std::get<IDX_TILE_VALUE>(tile_internal) = std::stoi(temptiledata);
break;
case IDX_TILE_BLOCKED:
std::get<IDX_TILE_BLOCKED>(tile_internal) = std::stoi(temptiledata);
break;
default:
std::cout << "ERROR: [tile_processed_counter: "
<< tile_processed_counter
<< "]: Read past MAX_NO_TILE_IDXS! (idx no:"
<< MAX_NO_TILE_IDXS << ")\n";
}
}
tile_processed_counter++;
const unsigned long long tile_value =
std::get<IDX_TILE_VALUE>(tile_internal);
const bool tile_blocked = std::get<IDX_TILE_BLOCKED>(tile_internal);
return tile_t{tile_value, tile_blocked};
};
std::transform(std::begin(buf), std::end(buf), std::back_inserter(result_buf),
prime_tile_data);
return result_buf;
}
// TODO: Refactor and combine to one "gameboard" loader function!
std::tuple<bool, std::tuple<unsigned long long, long long>>
get_and_process_game_stats_string_data(std::istream &stats_file) {
if (stats_file) {
unsigned long long score;
long long moveCount;
for (std::string tempLine; std::getline(stats_file, tempLine);) {
enum GameStatsFieldIndex {
IDX_GAME_SCORE_VALUE,
IDX_GAME_MOVECOUNT,
MAX_NO_GAME_STATS_IDXS
};
std::istringstream line(tempLine);
auto idx_id{0};
for (std::string temp; std::getline(line, temp, ':'); idx_id++) {
switch (idx_id) {
case IDX_GAME_SCORE_VALUE:
score = std::stoi(temp);
break;
case IDX_GAME_MOVECOUNT:
moveCount = std::stoi(temp) - 1;
break;
default:
// Error: No fields to process!
break;
}
}
}
return std::make_tuple(true, std::make_tuple(score, moveCount));
}
return std::make_tuple(false, std::make_tuple(0, 0));
}
} // namespace
load_gameboard_status_t load_GameBoard_data_from_file(std::string filename) {
std::ifstream stateFile(filename);
if (stateFile) {
const ull savedBoardPlaySize = GetLines(filename);
const auto file_tile_data = get_file_tile_data(stateFile);
const auto processed_tile_data =
process_file_tile_string_data(file_tile_data);
return std::make_tuple(true,
GameBoard(savedBoardPlaySize, processed_tile_data));
}
return std::make_tuple(false, GameBoard{});
}
// Output: [loadfile_ok_status, gameboard.score, gameboard.moveCount]
std::tuple<bool, std::tuple<unsigned long long, long long>>
load_game_stats_from_file(std::string filename) {
std::ifstream stats(filename);
return get_and_process_game_stats_string_data(stats);
}
} // namespace Loader
} // namespace Game

View File

@@ -0,0 +1,79 @@
#include "menu-graphics.hpp"
#include "color.hpp"
#include "global.hpp"
#include <sstream>
namespace Game {
namespace Graphics {
namespace Menu {
std::string MainMenuTitlePrompt() {
constexpr auto greetings_text = "Welcome to ";
constexpr auto gamename_text = "2048!";
constexpr auto sp = " ";
std::ostringstream str_os;
std::ostringstream title_richtext;
title_richtext << bold_on << sp << greetings_text << blue << gamename_text
<< def << bold_off << "\n";
str_os << title_richtext.str();
return str_os.str();
}
std::string MainMenuOptionsPrompt() {
const auto menu_list_txt = {"1. Play a New Game", "2. Continue Previous Game",
"3. View Highscores and Statistics", "4. Exit"};
constexpr auto sp = " ";
std::ostringstream str_os;
str_os << "\n";
for (const auto txt : menu_list_txt) {
str_os << sp << txt << "\n";
}
str_os << "\n";
return str_os.str();
}
std::string InputMenuErrorInvalidInputPrompt() {
constexpr auto err_input_text = "Invalid input. Please try again.";
constexpr auto sp = " ";
std::ostringstream str_os;
std::ostringstream err_input_richtext;
err_input_richtext << red << sp << err_input_text << def << "\n\n";
str_os << err_input_richtext.str();
return str_os.str();
}
std::string InputMenuPrompt() {
constexpr auto prompt_choice_text = "Enter Choice: ";
constexpr auto sp = " ";
std::ostringstream str_os;
std::ostringstream prompt_choice_richtext;
prompt_choice_richtext << sp << prompt_choice_text;
str_os << prompt_choice_richtext.str();
return str_os.str();
}
std::string MainMenuGraphicsOverlay(bool input_error_choice_invalid) {
std::ostringstream str_os;
DrawAlways(str_os, MainMenuTitlePrompt);
DrawAlways(str_os, MainMenuOptionsPrompt);
// Only outputs if there is an input error...
DrawOnlyWhen(str_os, input_error_choice_invalid,
InputMenuErrorInvalidInputPrompt);
DrawAlways(str_os, InputMenuPrompt);
return str_os.str();
}
} // namespace Menu
} // namespace Graphics
} // namespace Game

162
Make/2048/menu.cpp Normal file
View File

@@ -0,0 +1,162 @@
#include "menu.hpp"
#include "color.hpp"
#include "game-graphics.hpp"
#include "game.hpp"
#include "global.hpp"
#include "menu-graphics.hpp"
#include "scores-graphics.hpp"
#include "scores.hpp"
#include "statistics-graphics.hpp"
#include "statistics.hpp"
#include <algorithm>
#include <array>
#include <iostream>
#include <sstream>
namespace {
enum MainMenuStatusFlag {
FLAG_NULL,
FLAG_START_GAME,
FLAG_CONTINUE_GAME,
FLAG_DISPLAY_HIGHSCORES,
FLAG_EXIT_GAME,
MAX_NO_MAIN_MENU_STATUS_FLAGS
};
using mainmenustatus_t = std::array<bool, MAX_NO_MAIN_MENU_STATUS_FLAGS>;
mainmenustatus_t mainmenustatus{};
bool FlagInputErrornousChoice{};
void startGame() {
Game::startGame();
}
void continueGame() {
Game::continueGame();
}
Scoreboard::Graphics::scoreboard_display_data_list_t
make_scoreboard_display_data_list() {
using namespace Scoreboard::Graphics;
auto scoreList = Scoreboard::Scoreboard_t{};
// bool loaded_scorelist;
// Warning: Does not care if file exists or not!
std::tie(std::ignore, scoreList) =
Scoreboard::loadFromFileScore("../data/scores.txt");
auto counter{1};
const auto convert_to_display_list_t = [&counter](const Scoreboard::Score s) {
const auto data_stats = std::make_tuple(
std::to_string(counter), s.name, std::to_string(s.score),
s.win ? "Yes" : "No", std::to_string(s.moveCount),
std::to_string(s.largestTile), secondsFormat(s.duration));
counter++;
return data_stats;
};
auto scoreboard_display_list = scoreboard_display_data_list_t{};
std::transform(std::begin(scoreList), std::end(scoreList),
std::back_inserter(scoreboard_display_list),
convert_to_display_list_t);
return scoreboard_display_list;
};
Statistics::Graphics::total_stats_display_data_t
make_total_stats_display_data() {
Statistics::total_game_stats_t stats;
bool stats_file_loaded{};
std::tie(stats_file_loaded, stats) =
Statistics::loadFromFileStatistics("../data/statistics.txt");
const auto tsdd = std::make_tuple(
stats_file_loaded, std::to_string(stats.bestScore),
std::to_string(stats.gameCount), std::to_string(stats.winCount),
std::to_string(stats.totalMoveCount), secondsFormat(stats.totalDuration));
return tsdd;
};
void showScores() {
using namespace Game::Graphics;
using namespace Scoreboard::Graphics;
using namespace Statistics::Graphics;
const auto sbddl = make_scoreboard_display_data_list();
const auto tsdd = make_total_stats_display_data();
clearScreen();
DrawAlways(std::cout, AsciiArt2048);
DrawAlways(std::cout, DataSuppliment(sbddl, ScoreboardOverlay));
DrawAlways(std::cout, DataSuppliment(tsdd, TotalStatisticsOverlay));
std::cout << std::flush;
pause_for_keypress();
::Menu::startMenu();
}
void receive_input_flags(std::istream &in_os) {
// Reset ErrornousChoice flag...
FlagInputErrornousChoice = bool{};
char c;
in_os >> c;
switch (c) {
case '1':
mainmenustatus[FLAG_START_GAME] = true;
break;
case '2':
mainmenustatus[FLAG_CONTINUE_GAME] = true;
break;
case '3':
mainmenustatus[FLAG_DISPLAY_HIGHSCORES] = true;
break;
case '4':
mainmenustatus[FLAG_EXIT_GAME] = true;
break;
default:
FlagInputErrornousChoice = true;
break;
}
}
void process_MainMenu() {
if (mainmenustatus[FLAG_START_GAME]) {
startGame();
}
if (mainmenustatus[FLAG_CONTINUE_GAME]) {
continueGame();
}
if (mainmenustatus[FLAG_DISPLAY_HIGHSCORES]) {
showScores();
}
if (mainmenustatus[FLAG_EXIT_GAME]) {
exit(EXIT_SUCCESS);
}
}
bool soloLoop() {
// No choice in Menu selected, reset all flags...
mainmenustatus = mainmenustatus_t{};
clearScreen();
DrawAlways(std::cout, Game::Graphics::AsciiArt2048);
DrawAlways(std::cout,
DataSuppliment(FlagInputErrornousChoice,
Game::Graphics::Menu::MainMenuGraphicsOverlay));
receive_input_flags(std::cin);
process_MainMenu();
return FlagInputErrornousChoice;
}
void endlessLoop() {
while (soloLoop())
;
}
} // namespace
namespace Menu {
void startMenu() {
endlessLoop();
}
} // namespace Menu

View File

@@ -0,0 +1,48 @@
#include "saveresource.hpp"
#include "gameboard.hpp"
#include <fstream>
namespace Game {
namespace Saver {
namespace {
bool generateFilefromPreviousGameStatisticsData(std::ostream &os,
const GameBoard &gb) {
os << gb.score << ":" << MoveCountOnGameBoard(gb);
return true;
}
bool generateFilefromPreviousGameStateData(std::ostream &os,
const GameBoard &gb) {
os << printStateOfGameBoard(gb);
return true;
}
void saveToFilePreviousGameStateData(std::string filename,
const GameBoard &gb) {
std::ofstream stateFile(filename, std::ios_base::app);
generateFilefromPreviousGameStateData(stateFile, gb);
}
void saveToFilePreviousGameStatisticsData(std::string filename,
const GameBoard &gb) {
std::ofstream stats(filename, std::ios_base::app);
generateFilefromPreviousGameStatisticsData(stats, gb);
}
} // namespace
void saveGamePlayState(GameBoard gb) {
// Currently two datafiles for now.
// Will be merged into one datafile in a future PR.
constexpr auto filename_game_data_state = "../data/previousGame";
constexpr auto filename_game_data_statistics = "../data/previousGameStats";
std::remove(filename_game_data_state);
std::remove(filename_game_data_statistics);
saveToFilePreviousGameStateData(filename_game_data_state, gb);
saveToFilePreviousGameStatisticsData(filename_game_data_statistics, gb);
}
} // namespace Saver
} // namespace Game

View File

@@ -0,0 +1,115 @@
#include "scores-graphics.hpp"
#include "color.hpp"
#include <array>
#include <iomanip>
#include <sstream>
namespace Scoreboard {
namespace Graphics {
std::string ScoreboardOverlay(scoreboard_display_data_list_t sbddl) {
constexpr auto no_save_text = "No saved scores.";
const auto score_attributes_text = {
"No.", "Name", "Score", "Won?", "Moves", "Largest Tile", "Duration"};
// constexpr auto num_of_score_attributes_text = 7;
constexpr auto header_border_text =
"┌─────┬────────────────────┬──────────┬──────┬───────┬──────────────┬──────────────┐";
constexpr auto mid_border_text =
"├─────┼────────────────────┼──────────┼──────┼───────┼──────────────┼──────────────┤";
constexpr auto bottom_border_text =
"└─────┴────────────────────┴──────────┴──────┴───────┴──────────────┴──────────────┘";
constexpr auto score_title_text = "SCOREBOARD";
constexpr auto divider_text = "──────────";
constexpr auto sp = " ";
std::ostringstream str_os;
str_os << green << bold_on << sp << score_title_text << bold_off << def
<< "\n";
str_os << green << bold_on << sp << divider_text << bold_off << def << "\n";
const auto number_of_scores = sbddl.size();
if (number_of_scores) {
str_os << sp << header_border_text << "\n";
str_os << std::left;
str_os << sp << "" << bold_on << std::begin(score_attributes_text)[0]
<< bold_off << "" << bold_on << std::setw(18)
<< std::begin(score_attributes_text)[1] << bold_off << ""
<< bold_on << std::setw(8) << std::begin(score_attributes_text)[2]
<< bold_off << "" << bold_on
<< std::begin(score_attributes_text)[3] << bold_off << ""
<< bold_on << std::begin(score_attributes_text)[4] << bold_off
<< "" << bold_on << std::begin(score_attributes_text)[5]
<< bold_off << "" << bold_on << std::setw(12)
<< std::begin(score_attributes_text)[6] << bold_off << ""
<< "\n";
str_os << std::right;
str_os << sp << mid_border_text << "\n";
const auto print_score_stat = [&](const scoreboard_display_data_t i) {
str_os << sp << "" << std::setw(2) << std::get<0>(i) << ". │ "
<< std::left << std::setw(18) << std::get<1>(i) << std::right
<< "" << std::setw(8) << std::get<2>(i) << "" << std::setw(4)
<< std::get<3>(i) << "" << std::setw(5) << std::get<4>(i)
<< "" << std::setw(12) << std::get<5>(i) << ""
<< std::setw(12) << std::get<6>(i) << ""
<< "\n";
};
for (const auto s : sbddl) {
print_score_stat(s);
}
str_os << sp << bottom_border_text << "\n";
} else {
str_os << sp << no_save_text << "\n";
}
str_os << "\n\n";
return str_os.str();
}
std::string EndGameStatisticsPrompt(finalscore_display_data_t finalscore) {
std::ostringstream str_os;
constexpr auto stats_title_text = "STATISTICS";
constexpr auto divider_text = "──────────";
constexpr auto sp = " ";
const auto stats_attributes_text = {
"Final score:", "Largest Tile:", "Number of moves:", "Time taken:"};
enum FinalScoreDisplayDataFields {
IDX_FINAL_SCORE_VALUE,
IDX_LARGEST_TILE,
IDX_MOVE_COUNT,
IDX_DURATION,
MAX_NUM_OF_FINALSCOREDISPLAYDATA_INDEXES
};
const auto data_stats =
std::array<std::string, MAX_NUM_OF_FINALSCOREDISPLAYDATA_INDEXES>{
std::get<IDX_FINAL_SCORE_VALUE>(finalscore),
std::get<IDX_LARGEST_TILE>(finalscore),
std::get<IDX_MOVE_COUNT>(finalscore),
std::get<IDX_DURATION>(finalscore)};
std::ostringstream stats_richtext;
stats_richtext << yellow << sp << stats_title_text << def << "\n";
stats_richtext << yellow << sp << divider_text << def << "\n";
auto counter{0};
const auto populate_stats_info = [=, &counter,
&stats_richtext](const std::string) {
stats_richtext << sp << std::left << std::setw(19)
<< std::begin(stats_attributes_text)[counter] << bold_on
<< std::begin(data_stats)[counter] << bold_off << "\n";
counter++;
};
for (const auto s : stats_attributes_text) {
populate_stats_info(s);
}
str_os << stats_richtext.str();
str_os << "\n\n";
return str_os.str();
}
} // namespace Graphics
} // namespace Scoreboard

66
Make/2048/scores.cpp Normal file
View File

@@ -0,0 +1,66 @@
#include "scores.hpp"
#include "color.hpp"
#include <algorithm>
#include <array>
#include <fstream>
#include <sstream>
namespace {
using namespace Scoreboard;
bool generateFilefromScoreData(std::ostream &os, Score score) {
os << score;
return true;
}
Scoreboard_t generateScorefromFileData(std::istream &is) {
Score tempscore{};
Scoreboard_t scoreList{};
while (is >> tempscore) {
scoreList.push_back(tempscore);
};
return scoreList;
}
bool saveToFileScore(std::string filename, Score s) {
std::ofstream os(filename, std::ios_base::app);
return generateFilefromScoreData(os, s);
}
} // namespace
namespace Scoreboard {
bool operator>(const Score &a, const Score &b) {
return a.score > b.score;
}
load_score_status_t loadFromFileScore(std::string filename) {
std::ifstream scores(filename);
if (scores) {
Scoreboard_t scoreList = generateScorefromFileData(scores);
std::sort(std::begin(scoreList), std::end(scoreList),
std::greater<Score>{});
return load_score_status_t{true, scoreList};
}
return load_score_status_t{false, Scoreboard_t{}};
}
void saveScore(Score finalscore) {
saveToFileScore("../data/scores.txt", finalscore);
}
} // namespace Scoreboard
using namespace Scoreboard;
std::istream &operator>>(std::istream &is, Score &s) {
is >> s.name >> s.score >> s.win >> s.moveCount >> s.largestTile >>
s.duration;
return is;
}
std::ostream &operator<<(std::ostream &os, Score &s) {
os << "\n"
<< s.name << " " << s.score << " " << s.win << " " << s.moveCount << " "
<< s.largestTile << " " << s.duration;
return os;
}

View File

@@ -0,0 +1,97 @@
#include "statistics-graphics.hpp"
#include "color.hpp"
#include <array>
#include <iomanip>
#include <sstream>
namespace Statistics {
namespace Graphics {
std::string AskForPlayerNamePrompt() {
constexpr auto score_prompt_text =
"Please enter your name to save this score: ";
constexpr auto sp = " ";
std::ostringstream score_prompt_richtext;
score_prompt_richtext << bold_on << sp << score_prompt_text << bold_off;
return score_prompt_richtext.str();
}
std::string MessageScoreSavedPrompt() {
constexpr auto score_saved_text = "Score saved!";
constexpr auto sp = " ";
std::ostringstream score_saved_richtext;
score_saved_richtext << "\n"
<< green << bold_on << sp << score_saved_text << bold_off
<< def << "\n";
return score_saved_richtext.str();
}
std::string TotalStatisticsOverlay(total_stats_display_data_t tsdd) {
constexpr auto stats_title_text = "STATISTICS";
constexpr auto divider_text = "──────────";
constexpr auto header_border_text = "┌────────────────────┬─────────────┐";
constexpr auto footer_border_text = "└────────────────────┴─────────────┘";
const auto stats_attributes_text = {"Best Score", "Game Count",
"Number of Wins", "Total Moves Played",
"Total Duration"};
constexpr auto no_save_text = "No saved statistics.";
constexpr auto any_key_exit_text =
"Press any key to return to the main menu... ";
constexpr auto sp = " ";
enum TotalStatsDisplayDataFields {
IDX_DATA_AVAILABLE,
IDX_BEST_SCORE,
IDX_GAME_COUNT,
IDX_GAME_WIN_COUNT,
IDX_TOTAL_MOVE_COUNT,
IDX_TOTAL_DURATION,
MAX_TOTALSTATSDISPLAYDATA_INDEXES
};
std::ostringstream stats_richtext;
const auto stats_file_loaded = std::get<IDX_DATA_AVAILABLE>(tsdd);
if (stats_file_loaded) {
constexpr auto num_of_stats_attributes_text = 5;
auto data_stats = std::array<std::string, num_of_stats_attributes_text>{};
data_stats = {std::get<IDX_BEST_SCORE>(tsdd),
std::get<IDX_GAME_COUNT>(tsdd),
std::get<IDX_GAME_WIN_COUNT>(tsdd),
std::get<IDX_TOTAL_MOVE_COUNT>(tsdd),
std::get<IDX_TOTAL_DURATION>(tsdd)};
auto counter{0};
const auto populate_stats_info = [=, &counter,
&stats_richtext](const std::string) {
stats_richtext << sp << "" << bold_on << std::left << std::setw(18)
<< std::begin(stats_attributes_text)[counter] << bold_off
<< "" << std::right << std::setw(11)
<< data_stats[counter] << ""
<< "\n";
counter++;
};
stats_richtext << green << bold_on << sp << stats_title_text << bold_off
<< def << "\n";
stats_richtext << green << bold_on << sp << divider_text << bold_off << def
<< "\n";
stats_richtext << sp << header_border_text << "\n";
for (const auto s : stats_attributes_text) {
populate_stats_info(s);
}
stats_richtext << sp << footer_border_text << "\n";
} else {
stats_richtext << sp << no_save_text << "\n";
}
stats_richtext << "\n\n\n";
stats_richtext << sp << any_key_exit_text;
return stats_richtext.str();
}
} // namespace Graphics
} // namespace Statistics

116
Make/2048/statistics.cpp Normal file
View File

@@ -0,0 +1,116 @@
#include "statistics.hpp"
#include "color.hpp"
#include "scores-graphics.hpp"
#include "scores.hpp"
#include "statistics-graphics.hpp"
#include <algorithm>
#include <array>
#include <fstream>
#include <sstream>
namespace Statistics {
namespace {
std::string receive_input_player_name(std::istream &is) {
std::string name;
is >> name;
return name;
}
total_game_stats_t generateStatsFromInputData(std::istream &is) {
total_game_stats_t stats;
is >> stats;
return stats;
}
bool generateFilefromStatsData(std::ostream &os, total_game_stats_t stats) {
os << stats;
return true;
}
bool saveToFileEndGameStatistics(std::string filename, total_game_stats_t s) {
std::ofstream filedata(filename);
return generateFilefromStatsData(filedata, s);
}
Scoreboard::Graphics::finalscore_display_data_t
make_finalscore_display_data(Scoreboard::Score finalscore) {
const auto fsdd = std::make_tuple(
std::to_string(finalscore.score), std::to_string(finalscore.largestTile),
std::to_string(finalscore.moveCount), secondsFormat(finalscore.duration));
return fsdd;
};
} // namespace
load_stats_status_t loadFromFileStatistics(std::string filename) {
std::ifstream statistics(filename);
if (statistics) {
total_game_stats_t stats = generateStatsFromInputData(statistics);
return load_stats_status_t{true, stats};
}
return load_stats_status_t{false, total_game_stats_t{}};
}
ull load_game_best_score() {
total_game_stats_t stats;
bool stats_file_loaded{};
ull tempscore{0};
std::tie(stats_file_loaded, stats) =
loadFromFileStatistics("../data/statistics.txt");
if (stats_file_loaded) {
tempscore = stats.bestScore;
}
return tempscore;
}
void saveEndGameStats(Scoreboard::Score finalscore) {
total_game_stats_t stats;
// Need some sort of stats data values only.
// No need to care if file loaded successfully or not...
std::tie(std::ignore, stats) =
loadFromFileStatistics("../data/statistics.txt");
stats.bestScore =
stats.bestScore < finalscore.score ? finalscore.score : stats.bestScore;
stats.gameCount++;
stats.winCount = finalscore.win ? stats.winCount + 1 : stats.winCount;
stats.totalMoveCount += finalscore.moveCount;
stats.totalDuration += finalscore.duration;
saveToFileEndGameStatistics("../data/statistics.txt", stats);
}
void CreateFinalScoreAndEndGameDataFile(std::ostream &os, std::istream &is,
Scoreboard::Score finalscore) {
const auto finalscore_display_data = make_finalscore_display_data(finalscore);
DrawAlways(os, DataSuppliment(finalscore_display_data,
Scoreboard::Graphics::EndGameStatisticsPrompt));
DrawAlways(os, Graphics::AskForPlayerNamePrompt);
const auto name = receive_input_player_name(is);
finalscore.name = name;
Scoreboard::saveScore(finalscore);
saveEndGameStats(finalscore);
DrawAlways(os, Graphics::MessageScoreSavedPrompt);
}
} // namespace Statistics
using namespace Statistics;
std::istream &operator>>(std::istream &is, total_game_stats_t &s) {
is >> s.bestScore >> s.gameCount >> s.winCount >> s.totalMoveCount >>
s.totalDuration;
return is;
}
std::ostream &operator<<(std::ostream &os, total_game_stats_t &s) {
os << s.bestScore << "\n"
<< s.gameCount << "\n"
<< s.winCount << "\n"
<< s.totalMoveCount << "\n"
<< s.totalDuration;
return os;
}

View File

@@ -0,0 +1,32 @@
#include "tile-graphics.hpp"
#include "color.hpp"
#include "tile.hpp"
#include <cmath>
#include <iomanip>
#include <sstream>
#include <vector>
namespace Game {
namespace {
Color::Modifier tileColor(ull value) {
std::vector<Color::Modifier> colors{red, yellow, magenta, blue, cyan, yellow,
red, yellow, magenta, blue, green};
int log = log2(value);
int index = log < 12 ? log - 1 : 10;
return colors[index];
}
} // namespace
std::string drawTileString(tile_t currentTile) {
std::ostringstream tile_richtext;
if (!currentTile.value) {
tile_richtext << " ";
} else {
tile_richtext << tileColor(currentTile.value) << bold_on << std::setw(4)
<< currentTile.value << bold_off << def;
}
return tile_richtext.str();
}
} // namespace Game

3
Make/2048/tile.cpp Normal file
View File

@@ -0,0 +1,3 @@
#include "tile.hpp"
namespace Game {} // namespace Game

BIN
Make/img/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

18393
Make/make.html Normal file

File diff suppressed because it is too large Load Diff

1901
Make/make.md Normal file

File diff suppressed because it is too large Load Diff

3553
Make/make_md.html Normal file

File diff suppressed because one or more lines are too long

63
Make/sudoku/block.cpp Normal file
View File

@@ -0,0 +1,63 @@
#include <cassert>
#include <iostream>
#include "common.h"
#include "block.h"
#include "color.h"
CBlock::CBlock()
: _count(0) {}
bool CBlock::isValid() const
{
assert(MAX_COUNT == _count);
for (int i = 0; i < _count - 1; ++i)
{
for (int j = i + 1; j < _count; ++j)
{
if (UNSELECTED == _numbers[i]->value || UNSELECTED == _numbers[j]->value)
continue;
if (_numbers[i]->value == _numbers[j]->value)
return false;
}
}
return true;
}
bool CBlock::isFull() const
{
for (int i = 0; i < _count; ++i)
{
point_value_t *p_point_value = _numbers[i];
if (nullptr == p_point_value || UNSELECTED == p_point_value->value)
return false;
}
return true;
}
void CBlock::print() const
{
std::cout << "\u2503" << " ";
for (int i = 0; i < _count; ++i)
{
auto number = *(_numbers[i]);
if (0 == number.value)
std::cout << ' ' << " \u2503 ";
else
{
if (number.state == State::ERASED)
std::cout << Color::Modifier(Color::FG_GREEN) << number.value << Color::Modifier(Color::RESET) << " \u2503 ";
else
std::cout << number.value << " \u2503 ";
}
}
std::cout << std::endl;
}
void CBlock::push_back(point_value_t *point)
{
assert(nullptr != point);
_numbers[_count++] = point;
}

22
Make/sudoku/block.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef _SUDOKU_BLOCK_H_
#define _SUDOKU_BLOCK_H_
////
// 数独行政区域类
class CBlock
{
static const int MAX_COUNT = 9;
public:
CBlock();
bool isValid() const;
bool isFull() const;
void print() const;
void push_back(point_value_t *point);
private:
int _count;
point_value_t *_numbers[MAX_COUNT];
};
#endif

49
Make/sudoku/color.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef _SUDOKU_COLOR_H
#define _SUDOKU_COLOR_H
#include <iostream>
namespace Color
{
enum Code
{
BOLD = 1,
RESET = 0,
BG_BLUE = 44,
BG_DEFAULT = 49,
BG_GREEN = 42,
BG_RED = 41,
FG_BLACK = 30,
FG_BLUE = 34,
FG_CYAN = 36,
FG_DARK_GRAY = 90,
FG_DEFAULT = 39,
FG_GREEN = 32,
FG_LIGHT_BLUE = 94,
FG_LIGHT_CYAN = 96,
FG_LIGHT_GRAY = 37,
FG_LIGHT_GREEN = 92,
FG_LIGHT_MAGENTA = 95,
FG_LIGHT_RED = 91,
FG_LIGHT_YELLOW = 93,
FG_MAGENTA = 35,
FG_RED = 31,
FG_WHITE = 97,
FG_YELLOW = 33,
};
class Modifier
{
Code code;
public:
Modifier(Code pCode) : code(pCode) {}
friend std::ostream &
operator<<(std::ostream &os, const Modifier &mod)
{
return os << "\033[" << mod.code << "m";
}
};
} // namespace Color
#endif

36
Make/sudoku/command.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "scene.h"
#include "command.h"
CCommand::CCommand(CScene *pOwner) : _pOwner(pOwner)
{}
CCommand::CCommand(CScene *pOwner, const point_t &point, int preValue, int curValue)
: _pOwner(pOwner), _stPoint(point), _nPreValue(preValue), _nCurValue(curValue) {}
CCommand::CCommand(const CCommand &rhs)
: _pOwner(rhs._pOwner)
, _stPoint(rhs._stPoint)
, _nPreValue(rhs._nPreValue)
, _nCurValue(rhs._nCurValue)
{}
CCommand::~CCommand(){}
bool CCommand::execute(int nInputValue)
{
if (!_pOwner)
return false;
_stPoint = _pOwner->getCurPoint();
return _pOwner->setCurValue(nInputValue, _nPreValue);
}
void CCommand::undo()
{
if (_pOwner)
{
_pOwner->setPointValue(_stPoint, _nPreValue);
}
return;
}

32
Make/sudoku/command.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef _SUDOKU_COMMAND_H_
#define _SUDOKU_COMMAND_H_
#include <memory>
#include "common.h"
class CScene;
class CCommand
{
public:
CCommand(CScene* pOwner);
CCommand(CScene *pOwner, const point_t &point, int preValue, int curValue);
CCommand(const CCommand &);
~CCommand();
bool execute(int nInputValue);
void undo();
point_t getPoint() { return _stPoint; }
int getPreValue() { return _nPreValue; }
int getCurValue() { return _nCurValue; }
void setPoint(const point_t &point) { _stPoint = point; }
void setPreValue(int preValue) { _nPreValue = preValue; }
void setCurValue(int curValue) { _nCurValue = curValue; }
private:
CScene* _pOwner;
point_t _stPoint;
int _nPreValue;
int _nCurValue; // actually the member is never used
};
#endif

79
Make/sudoku/common.h Normal file
View File

@@ -0,0 +1,79 @@
#ifndef _SUDOKU_COMMON_H_
#define _SUDOKU_COMMON_H_
static const unsigned int UNSELECTED = 0;
enum class Difficulty : int
{
EASY = 1,
NORMAL,
HARD
};
enum class State : int
{
INITED = 0,
ERASED,
};
enum class KeyMode : int
{
NORMAL = 1,
VIM
};
struct KeyMap
{
const char ESC = 0x1B;
const char U = 0x75;
char UP;
char LEFT;
char DOWN;
char RIGHT;
const char ENTER = 0x0D;
};
struct Normal : KeyMap
{
Normal()
{
UP = 0x77;
LEFT = 0x61;
DOWN = 0x73;
RIGHT = 0x64;
}
};
struct Vim : KeyMap {
Vim()
{
UP = 0x6B;
LEFT = 0x68;
DOWN = 0x6A;
RIGHT = 0x6C;
}
};
using point_t = struct point_t {
int x;
int y;
};
using point_value_t = struct point_value_t {
int value;
State state;
};
class CPointSort
{
public:
bool operator()(const point_t &lhs, const point_t &rhs) const
{
if ((lhs.x == rhs.x) && (lhs.y == rhs.y))
return false;
else
return true;
}
};
#endif

8
Make/sudoku/hello.cpp Normal file
View File

@@ -0,0 +1,8 @@
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World" << endl;
return 0;
}

70
Make/sudoku/input.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include <iostream>
#include <sstream>
#include <string>
#include "common.h"
#include "utility.inl"
// return number of grids to be erased
// 嘿嘿加个注释
int inputDifficulty()
{
cls();
std::string cmd;
int need_erase_grids = 0;
while (true)
{
std::cout << "设置难度1简单 2普通 3困难" << std::endl;
std::cin >> cmd;
try
{
Difficulty difficulty = static_cast<Difficulty>(std::stoi(cmd));
switch (difficulty)
{
case Difficulty::EASY:
need_erase_grids = 20;
break;
case Difficulty::NORMAL:
need_erase_grids = 35;
break;
case Difficulty::HARD:
need_erase_grids = 50;
break;
}
}
catch(...)
{
need_erase_grids = 0;
}
if (need_erase_grids > 0)
break;
std::cout << "输入错误!" << std::endl;
}
return need_erase_grids;
}
KeyMode inputKeyMode()
{
std::string mode;
do
{
message("设置按键模式1正常 2VIM");
std::cin >> mode;
try
{
KeyMode kmd = static_cast<KeyMode>(std::stoi(mode));
return kmd;
} catch (...)
{}
message("输入错误!");
} while (true);
}

7
Make/sudoku/input.h Normal file
View File

@@ -0,0 +1,7 @@
#ifndef _SUDOKU_INPUT_H_
#define _SUDOKU_INPUT_H_
int inputDifficulty();
KeyMode inputKeyMode();
#endif

49
Make/sudoku/main.cpp Normal file
View File

@@ -0,0 +1,49 @@
#include <iostream>
#include <string>
#include <cstring>
#include "scene.h"
#include "input.h"
#include "test.h"
#define _TEST_ 0
static void printHelp() {
std::cout << std::endl;
std::cout << "sudoku - a little game in command line" << std::endl
<< std::endl;
std::cout << "Usage:" << std::endl;
std::cout << "\t sudoku [-l <progressFile>]" << std::endl << std::endl;
std::cout << "Options:" << std::endl;
std::cout << "\t -l <path> \t specify path of progress file to load, optional." << std::endl
<< std::endl;
}
int main(int argc, char **argv)
{
#if _TEST_
test_case1();
getchar();
#else
CScene scene;
if (argc == 1) {
int eraseGridNumber = inputDifficulty();
scene.generate();
scene.eraseRandomGrids(eraseGridNumber);
}
else if (argc == 3 && !strcmp(argv[1], "-l")) {
// load saved game progress
scene.load(argv[2]);
}
else {
printHelp();
return 0;
}
scene.setMode(inputKeyMode());
scene.play();
#endif
return 0;
}

369
Make/sudoku/scene.cpp Normal file
View File

@@ -0,0 +1,369 @@
#include <cmath>
#include <iostream>
#include <fstream>
#include <memory.h>
#include <map>
#include <unordered_map>
#include <vector>
#include "common.h"
#include "scene.h"
#include "utility.inl"
CScene::CScene(int index)
: _max_column(pow(index, 2))
, _cur_point({0, 0})
{
init();
}
CScene::~CScene()
{
if(keyMap) delete keyMap;
}
void CScene::show() const
{
cls();
printUnderline();
for (int row = 0; row < _max_column; ++row)
{
CBlock block = _row_block[row];
block.print();
printUnderline(row);
}
}
void CScene::setMode(KeyMode mode)
{
switch (mode)
{
case KeyMode::NORMAL:
keyMap = new Normal;
break;
case KeyMode::VIM:
keyMap = new Vim;
break;
}
}
void CScene::printUnderline(int line_no) const
{
for (int colunm = 0; colunm < 9; ++colunm)
std::cout << "\u254B" << "\u2501" << ((_cur_point.y == line_no && _cur_point.x == colunm)?"^":"\u2501") << "\u2501";
std::cout << "\u254B" << std::endl;
}
void CScene::init()
{
memset(_map, UNSELECTED, sizeof _map);
// column_block 所有列
for (int col = 0; col < _max_column; ++col)
{
CBlock column_block;
for (int row = 0; row < _max_column; ++row)
{
column_block.push_back(_map + row * 9 + col);
}
_column_block[col] = column_block;
}
// row_block 所有行
for (int row = 0; row < _max_column; ++row)
{
CBlock row_block;
for (int col = 0; col < _max_column; ++col)
{
row_block.push_back(_map + row * 9 + col);
}
_row_block[row] = row_block;
}
// xy_block 所有九宫格, [行][列]
for (int block_index = 0; block_index < _max_column; ++block_index)
{
CBlock xy_block;
int xy_begin = block_index / 3 * 27 + block_index % 3 * 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
xy_block.push_back(_map + xy_begin + i * 9 + j);
}
}
_xy_block[block_index / 3][block_index % 3] = xy_block;
}
return;
}
bool CScene::setCurValue(const int nCurValue, int &nLastValue)
{
auto point = _map[_cur_point.x + _cur_point.y * 9];
if (point.state == State::ERASED)
{
nLastValue = point.value;
setValue(nCurValue);
return true;
}
else
return false;
}
void CScene::setValue(const point_t& p, const int value)
{
_map[p.x + p.y * 9].value = value;
}
void CScene::setValue(const int value)
{
auto p = _cur_point;
this->setValue(p, value);
}
// 选择count个格子清空
void CScene::eraseRandomGrids(const int count)
{
point_value_t p = {UNSELECTED, State::ERASED};
std::vector<int> v(81);
for (int i = 0; i < 81; ++i) {
v[i] = i;
}
for (int i = 0; i < count; ++i) {
int r = random(0, v.size() - 1);
_map[v[r]] = p;
v.erase(v.begin() + r);
}
}
bool CScene::isComplete()
{
// 任何一个block未被填满则肯定未完成
for (size_t i = 0; i < 81; ++i)
{
if (_map[i].value == UNSELECTED)
return false;
}
// 同时block里的数字还要符合规则
for (int row = 0; row < 9; ++row)
{
for (int col = 0; col < 9; ++col)
{
if (!_row_block[row].isValid() ||
!_column_block[col].isValid() ||
!_xy_block[row / 3][col / 3].isValid())
return false;
}
}
return true;
}
void CScene::save(const char *filename) {
std::fstream fs;
// TODO: check whether the file has existed
fs.open(filename, std::fstream::in | std::fstream::out | std::fstream::app);
// save _map
for (int i = 0; i < 81; i++) {
fs << _map[i].value << ' ' << static_cast<int>(_map[i].state) << std::endl;
}
// save _cur_point
fs << _cur_point.x << ' ' << _cur_point.y << std::endl;
// save _vCommand
fs << _vCommand.size() << std::endl;
for (CCommand command : _vCommand) {
point_t point = command.getPoint();
fs << point.x << ' ' << point.y << ' '
<< command.getPreValue() << ' '
<< command.getCurValue() << std::endl;
}
fs.close();
}
void CScene::load(const char *filename) {
std::fstream fs;
// TODO: check whether the file has existed
fs.open(filename, std::fstream::in | std::fstream::out | std::fstream::app);
// load _map
for (int i = 0; i < 81; i++) {
int tmpState;
fs >> _map[i].value >> tmpState;
_map[i].state = static_cast<State>(tmpState);
}
// load _cur_point
fs >> _cur_point.x >> _cur_point.y;
// load _vCommand
int commandSize;
fs >> commandSize;
for (int i = 0; i < commandSize; i++) {
point_t point;
int preValue, curValue;
fs >> point.x >> point.y >> preValue >> curValue;
_vCommand.emplace_back(this, point, preValue, curValue);
}
}
void CScene::play()
{
show();
char key = '\0';
while (1)
{
key = getch();
if (key >= '0' && key <= '9')
{
CCommand oCommand(this);
if (!oCommand.execute(key - '0'))
{
std::cout << "this number can't be modified." << std::endl;
}
else
{
_vCommand.push_back(std::move(oCommand)); // XXX: move without move constructor
show();
continue;
}
}
if (key == keyMap->ESC)
{
message("quit game ? [Y/N]");
std::string strInput;
std::cin >> strInput;
if (strInput[0] == 'y' || strInput[0] == 'Y')
{
message("do you want to save the game progress ? [Y/N]");
std::cin >> strInput;
if (strInput[0] == 'y' || strInput[0] == 'Y')
{
message("input path of the progress file: ", false);
std::cin >> strInput;
save(strInput.c_str());
}
exit(0);
} else
{
message("continue.");
}
}
else if (key == keyMap->U)
{
if (_vCommand.empty())
message("no more action to undo.");
else
{
CCommand& oCommand = _vCommand.back();
oCommand.undo();
_vCommand.pop_back();
show();
}
}
else if (key == keyMap->LEFT)
{
_cur_point.x = (_cur_point.x - 1) < 0 ? 0 : _cur_point.x - 1;
show();
}
else if (key == keyMap->RIGHT)
{
_cur_point.x = (_cur_point.x + 1) > 8 ? 8 : _cur_point.x + 1;
show();
}
else if (key == keyMap->DOWN)
{
_cur_point.y = (_cur_point.y + 1) > 8 ? 8 : _cur_point.y + 1;
show();
}
else if (key == keyMap->UP)
{
_cur_point.y = (_cur_point.y - 1) < 0 ? 0 : _cur_point.y - 1;
show();
}
else if (key == keyMap->ENTER)
{
if (isComplete())
{
message("congratulation! you win!");
getchar();
exit(0);
}
else
{
message("sorry, not completed.");
}
}
}
}
// 一个场景可以多次被初始化
void CScene::generate()
{
// XXX: pseudo random
static char map_pattern[10][10] = {
"ighcabfde",
"cabfdeigh",
"fdeighcab",
"ghiabcdef",
"abcdefghi",
"defghiabc",
"higbcaefd",
"bcaefdhig",
"efdhigbca"};
std::vector<char> v = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'};
// 产生字母到数字的随机映射
std::unordered_map<char, int> hash_map;
for (int i = 1; i <= 9; ++i)
{
int r = random(0, v.size() - 1);
hash_map[v[r]] = i;
v.erase(v.begin() + r);
}
// 填入场景
for (int row = 0; row < 9; ++row)
{
for (int col = 0; col < 9; ++col)
{
point_t point = {row, col};
char key = map_pattern[row][col];
setValue(point, hash_map[key]);
}
}
assert(isComplete());
return;
}
bool CScene::setPointValue(const point_t &stPoint, const int nValue)
{
auto point = _map[stPoint.x + stPoint.y * 9];
if (State::ERASED == point.state)
{
_cur_point = stPoint;
setValue(nValue);
return true;
}
else
return false;
}
point_t CScene::getCurPoint()
{
return _cur_point;
}

52
Make/sudoku/scene.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef _SUDOKU_SCENE_H_
#define _SUDOKU_SCENE_H_
#include <iostream>
#include <vector>
#include "common.h"
#include "block.h"
#include "command.h"
// 加个东西
//数独场景类
class CScene
{
public:
CScene(int index = 3);
virtual ~CScene();
void generate();
void show() const;
bool setCurValue(const int nCurValue, int& nLastValue);
bool setPointValue(const point_t&, const int);
point_t getCurPoint();
void eraseRandomGrids(const int count);
bool isComplete();
void play();
void save(const char *filename);
void load(const char *filename);
void setMode(KeyMode mode);
private:
void init(); // 将每个格子的指针放到block里面
void setValue(const int);
void setValue(const point_t &, const int);
void printUnderline(int line_no = -1) const;
private:
KeyMap *keyMap{};
int _max_column;
point_t _cur_point;
CBlock _column_block[9];
CBlock _row_block[9];
CBlock _xy_block[3][3];
point_value_t _map[81];
std::vector<CCommand> _vCommand;
};
#endif

19
Make/sudoku/test.cpp Normal file
View File

@@ -0,0 +1,19 @@
#include "test.h"
#include <time.h>
#include <iostream>
using namespace std;
//生成棋盘,10s
void test_case1()
{
CScene scene;
time_t begin, end;
time(&begin);
scene.generate();
time(&end);
scene.show();
cout << end - begin << endl;
}

9
Make/sudoku/test.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef _SUDOKU_TEST_H_
#define _SUDOKU_TEST_H_
#include "scene.h"
void test_case1();
#endif

78
Make/sudoku/utility.inl Normal file
View File

@@ -0,0 +1,78 @@
#ifndef _SUDOKU_UTILITY_INL_
#define _SUDOKU_UTILITY_INL_
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <iostream>
//not real random,return number between [begin,end]
inline unsigned int random(int begin, int end)
{
assert(end >= begin && begin >= 0);
srand(time(NULL));
return (unsigned int)rand() % (end - begin + 1) + begin;
}
//网上找的均匀化随机数算法,不含max,非随机,弃用
inline int AverageRandom(int min, int max)
{
int minInteger = min * 10000;
int maxInteger = max * 10000;
srand(time(NULL));
int randInteger = rand() * rand();
int diffInteger = maxInteger - minInteger;
int resultInteger = randInteger % diffInteger + minInteger;
return (resultInteger / 10000);
}
inline void message(const char* msg = "", bool lf = true)
{
std::cout << msg;
if (lf) std::cout << std::endl;
}
#ifdef _WIN32
#include <conio.h>
#else
#ifdef __linux__
#include <termio.h>
#include <cstdio>
#elif __APPLE__
#include <termios.h>
#endif
inline char getch(void)
{
struct termios tmtemp, tm;
char c;
int fd = 0;
if (tcgetattr(fd, &tm) != 0)
{ /*获取当前的终端属性设置并保存到tm结构体中*/
return -1;
}
tmtemp = tm;
cfmakeraw(&tmtemp); /*将tetemp初始化为终端原始模式的属性设置*/
if (tcsetattr(fd, TCSANOW, &tmtemp) != 0)
{ /*将终端设置为原始模式的设置*/
return -1;
}
c = getchar();
if (tcsetattr(fd, TCSANOW, &tm) != 0)
{ /*接收字符完毕后将终端设置回原来的属性*/
return 0;
}
return c;
}
#endif
inline void cls(void)
{
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
#endif