mirror of
https://gitee.com/yanmu_ym/cpp.git
synced 2026-02-03 09:33:16 +08:00
431 lines
13 KiB
C++
431 lines
13 KiB
C++
#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
|