mirror of
https://gitee.com/yanmu_ym/cpp.git
synced 2026-02-03 01:23:14 +08:00
MAKE
This commit is contained in:
7
Make/2048/2048.cpp
Normal file
7
Make/2048/2048.cpp
Normal 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
275
Make/2048/game-graphics.cpp
Normal 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
69
Make/2048/game-input.cpp
Normal 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
|
||||
173
Make/2048/game-pregamemenu.cpp
Normal file
173
Make/2048/game-pregamemenu.cpp
Normal 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
430
Make/2048/game.cpp
Normal 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
|
||||
77
Make/2048/gameboard-graphics.cpp
Normal file
77
Make/2048/gameboard-graphics.cpp
Normal 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
415
Make/2048/gameboard.cpp
Normal 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
76
Make/2048/global.cpp
Normal 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();
|
||||
}
|
||||
1
Make/2048/headers/2048.hpp
Normal file
1
Make/2048/headers/2048.hpp
Normal file
@@ -0,0 +1 @@
|
||||
//#include "game.hpp"
|
||||
64
Make/2048/headers/color.hpp
Normal file
64
Make/2048/headers/color.hpp
Normal 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
|
||||
39
Make/2048/headers/game-graphics.hpp
Normal file
39
Make/2048/headers/game-graphics.hpp
Normal 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
|
||||
71
Make/2048/headers/game-input.hpp
Normal file
71
Make/2048/headers/game-input.hpp
Normal 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
|
||||
11
Make/2048/headers/game-pregamemenu.hpp
Normal file
11
Make/2048/headers/game-pregamemenu.hpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef GAMEPREGAMEMENU_H
|
||||
#define GAMEPREGAMEMENU_H
|
||||
|
||||
namespace Game {
|
||||
namespace PreGameSetup {
|
||||
void SetUpNewGame();
|
||||
void ContinueOldGame();
|
||||
} // namespace PreGameSetup
|
||||
} // namespace Game
|
||||
|
||||
#endif
|
||||
15
Make/2048/headers/game.hpp
Normal file
15
Make/2048/headers/game.hpp
Normal 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
|
||||
16
Make/2048/headers/gameboard-graphics.hpp
Normal file
16
Make/2048/headers/gameboard-graphics.hpp
Normal 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
|
||||
49
Make/2048/headers/gameboard.hpp
Normal file
49
Make/2048/headers/gameboard.hpp
Normal 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
|
||||
54
Make/2048/headers/global.hpp
Normal file
54
Make/2048/headers/global.hpp
Normal 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
|
||||
21
Make/2048/headers/loadresource.hpp
Normal file
21
Make/2048/headers/loadresource.hpp
Normal 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
|
||||
18
Make/2048/headers/menu-graphics.hpp
Normal file
18
Make/2048/headers/menu-graphics.hpp
Normal 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
|
||||
8
Make/2048/headers/menu.hpp
Normal file
8
Make/2048/headers/menu.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef MENU_H
|
||||
#define MENU_H
|
||||
|
||||
namespace Menu {
|
||||
void startMenu();
|
||||
} // namespace Menu
|
||||
|
||||
#endif
|
||||
56
Make/2048/headers/point2d.hpp
Normal file
56
Make/2048/headers/point2d.hpp
Normal 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
|
||||
15
Make/2048/headers/saveresource.hpp
Normal file
15
Make/2048/headers/saveresource.hpp
Normal 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
|
||||
23
Make/2048/headers/scores-graphics.hpp
Normal file
23
Make/2048/headers/scores-graphics.hpp
Normal 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
|
||||
34
Make/2048/headers/scores.hpp
Normal file
34
Make/2048/headers/scores.hpp
Normal 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
|
||||
19
Make/2048/headers/statistics-graphics.hpp
Normal file
19
Make/2048/headers/statistics-graphics.hpp
Normal 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
|
||||
34
Make/2048/headers/statistics.hpp
Normal file
34
Make/2048/headers/statistics.hpp
Normal 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
|
||||
11
Make/2048/headers/tile-graphics.hpp
Normal file
11
Make/2048/headers/tile-graphics.hpp
Normal 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
|
||||
13
Make/2048/headers/tile.hpp
Normal file
13
Make/2048/headers/tile.hpp
Normal 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
135
Make/2048/loadresource.cpp
Normal 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
|
||||
79
Make/2048/menu-graphics.cpp
Normal file
79
Make/2048/menu-graphics.cpp
Normal 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
162
Make/2048/menu.cpp
Normal 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
|
||||
48
Make/2048/saveresource.cpp
Normal file
48
Make/2048/saveresource.cpp
Normal 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
|
||||
115
Make/2048/scores-graphics.cpp
Normal file
115
Make/2048/scores-graphics.cpp
Normal 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
66
Make/2048/scores.cpp
Normal 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;
|
||||
}
|
||||
97
Make/2048/statistics-graphics.cpp
Normal file
97
Make/2048/statistics-graphics.cpp
Normal 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
116
Make/2048/statistics.cpp
Normal 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;
|
||||
}
|
||||
32
Make/2048/tile-graphics.cpp
Normal file
32
Make/2048/tile-graphics.cpp
Normal 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
3
Make/2048/tile.cpp
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "tile.hpp"
|
||||
|
||||
namespace Game {} // namespace Game
|
||||
BIN
Make/img/1.png
Normal file
BIN
Make/img/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
18393
Make/make.html
Normal file
18393
Make/make.html
Normal file
File diff suppressed because it is too large
Load Diff
1901
Make/make.md
Normal file
1901
Make/make.md
Normal file
File diff suppressed because it is too large
Load Diff
3553
Make/make_md.html
Normal file
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
63
Make/sudoku/block.cpp
Normal 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
22
Make/sudoku/block.h
Normal 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
49
Make/sudoku/color.h
Normal 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
36
Make/sudoku/command.cpp
Normal 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
32
Make/sudoku/command.h
Normal 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
79
Make/sudoku/common.h
Normal 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
8
Make/sudoku/hello.cpp
Normal 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
70
Make/sudoku/input.cpp
Normal 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
7
Make/sudoku/input.h
Normal 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
49
Make/sudoku/main.cpp
Normal 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
369
Make/sudoku/scene.cpp
Normal 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
52
Make/sudoku/scene.h
Normal 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
19
Make/sudoku/test.cpp
Normal 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
9
Make/sudoku/test.h
Normal 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
78
Make/sudoku/utility.inl
Normal 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
|
||||
Reference in New Issue
Block a user