2048游戏项目源码阅读、学习、分析、复现
 
前言 写作目的 记录我对2048游戏项目的学习过程。本项目源自github(传送门 )。
我的工作 阅读项目源码,学习规范编程风格与技巧,梳理代码逻辑,在理解原项目内容后模仿原项目写一个自己的2048游戏。
注意 :后续文章里所涉及代码为按我理解修改后的版本,相较于原项目代码,我的版本部分函数、变量名称不同,注释更多,自认为我的版本更适合阅读学习。
项目介绍 本项目实现2048小游戏。
游戏介绍 玩家每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方随机出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。不断的叠加最终拼凑出2048这个数字就算成功。
游戏特色 
提供3 x 3 到 10 x 10 不同规格的棋盘尺寸。 
游戏提供保存功能,玩家可随时从之前保存的对局继续游戏。 
游戏提供排行榜功能,记录玩家历史最佳成绩(仅限4 x 4规格棋盘对局),统计多项游戏数据。 
 
游戏结束条件 当棋盘被铺满(任意方块均有一个数字值,且所有相邻方块之间数值均不同)时,游戏结束。
胜利条件 游戏结束时,若场上数值最大的方块的值大于等于2048,则玩家胜利,否则失败。
游戏胜利后,玩家可选择继续(进入无尽模式)或是退出。
项目成果预览 静态 菜单  
游戏过程  
排行榜  
动态 新游戏  
继续游戏  
排行榜  
项目构建 阶段一 构建 既然是2048游戏,咱们的项目自然从2048.cpp的main()函数处开始。
1 2 3 4 5 6 7 8 9 #include  "menu.hpp"  int  main ()   {     	Menu::startMenu(); 	return  0 ; } 
 
本.cpp文件包含头文件menu.hpp,其内容为:
1 2 3 4 5 6 7 8 9 #ifndef  MENU_H #define  MENU_H namespace  Menu { 	void  startMenu ()  ; }  #endif   
 
需注意的是,所有的头文件都需要使用上述这种防卫式声明。
我们的menu只向外提供startMenu()一个函数,即提供游戏菜单。于是需要在menu.cpp中实现基础菜单功能。
menu.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include  "my_menu.hpp"  namespace { 	bool  soloLoop ()  	 {		 	} 	void  endlessLoop ()   	 {		while  (soloLoop()) 			; 	} }  namespace  Menu{ 	void  startMenu ()   	 {		endlessLoop(); 	} }  
 
游戏逻辑十分清晰简单:startMenu()函数调用endlessLoop()函数,后者通过while循环无尽地调用soloLoop()函数,而soloLoop()里会做一些事情,例如要求玩家进行输入,做完后返回一个布尔值,决定endlessLoop()是否继续循环下去。例如玩家输入非预期值,则soloLoop()返回true值,循环继续,再次要求玩家输入,否则调用输入对应函数,提供服务。
我们希望能够在soloLoop()中实现以下几个功能:
取一个布尔变量,将其作为函数返回值,该变量记录玩家输入是否为非法值 
清空屏幕,供后续打印游戏画面 
打印游戏画面,提供选项供用户选择 
处理选项 
 
为实现清屏、打印画面等功能,我们设计实现global.hpp,该文件提供一些通用功能供其余所有文件使用。
global.hpp实现如下。
首先,提供一个unsigned long long的缩写ull,供将来使用:
1 using  ull = unsigned  long  long ;
 
然后是三个与画面呈现相关的函数模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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;     } } 
 
DrawAlways()需要两个参数,一个为输出流os,另一个为一函数f。DrawAlways()调用函数f并将其返回值输出至os。
DrawOnlyWhen()相较于前者多了一个trigger条件值。只有在满足条件的情况下才调用DrawAlways()。
DrawAsOneTimeFlag()同样只在满足条件时调用DrawAlways(),并且在调用后翻转条件值。
再接着,是下面这坨东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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; } 
 
初看有点复杂,分析下来这里其实是使用模板提供了一个通用的接口。DataSuppliment()需要两个参数,第二个参数是一个函数f,第一个参数是f需要的参数。分析知参数f的返回类型必须是std::string。
如果存在某含一个参数的函数如:
 
通常调用如下:
 
而通过上述接口可写为:
1 DataSuppliment(2022 , foo); 
 
使用统一接口并没有效率上的提升(实际反而降低了效率),只是为了风格的统一?是否有更深层的含义?我暂时不太清楚,只是沿用原项目做法。
最后,global.hpp还需要提供一个清屏函数供项目其他部分使用:
 
该函数定义在global.cpp中:
1 2 3 4 5 6 7 8 void  clearScreen ()   {#ifdef  _WIN32 	system("cls" ); #else  	system("clear" ); #endif  } 
 
由于global.hpp中涉及到了string和ostream,需在开头声明相应头文件。最终成品如下:
global.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #ifndef  GLOBAL_H #define  GLOBAL_H #include  <iosfwd>  #include  <string>  using  ull = unsigned  long  long ;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  clearScreen ()  ;#endif   
 
global.cpp
1 2 3 4 5 6 7 8 9 10 #include  "global.hpp"  void  clearScreen ()   {#ifdef  _WIN32 	system("cls" ); #else  	system("clear" ); #endif  } 
 
有了以上辅助函数、接口后,可进一步完善menu.cpp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include  "menu.hpp"  #include  "global.hpp"  #include  <array>  #include  <iostream>  namespace { 	enum  MenuStatusFlag   	{ 		FLAG_NULL, 		FLAG_START_GAME, 		FLAG_CONTINUE_GAME, 		FLAG_DISPLAY_HIGHSCORES, 		FLAG_EXIT_GAME, 		MAX_NO_MAIN_MENU_STATUS_FLAGS 	}; 	using  menuStatus_t = std ::array <bool , MAX_NO_MAIN_MENU_STATUS_FLAGS>; 	 	menuStatus_t menustatus{}; 	 	bool  flagInputErroneousChoice{ false  }; 	void  receiveInputFromPlayer (std ::istream& in_os)   	 {		 		flagInputErroneousChoice = bool {}; 		char  c; 		in_os >> c; 		switch  (c)  		{ 		case  '1' : 			menustatus[FLAG_START_GAME] = true ; 			break ; 		case  '2' : 			menustatus[FLAG_CONTINUE_GAME] = true ; 			break ; 		case  '3' : 			menustatus[FLAG_DISPLAY_HIGHSCORES] = true ; 			break ; 		case  '4' : 			menustatus[FLAG_EXIT_GAME] = true ; 			break ; 		default : 			flagInputErroneousChoice = true ; 			break ; 		} 	} 	void  processPlayerInput ()   	 {		if  (menustatus[FLAG_START_GAME]) 		{              			 		} 		if  (menustatus[FLAG_CONTINUE_GAME]) 		{              			 		} 		if  (menustatus[FLAG_DISPLAY_HIGHSCORES]) 		{              			 		} 		if  (menustatus[FLAG_EXIT_GAME]) 		{ 			exit (EXIT_SUCCESS); 		} 	} 	bool  soloLoop ()  	 {		 		menustatus = menuStatus_t{}; 		clearScreen(); 		 		receiveInputFromPlayer(std ::cin ); 		processPlayerInput(); 		return  flagInputErroneousChoice; 	} 	void  endlessLoop ()   	 {		while  (soloLoop()) 			; 	} }  namespace  Menu{ 	void  startMenu ()   	 {		endlessLoop(); 	} }  
 
我们使用枚举MenuStatusFlag记录所有可能的选择,该枚举类型有六个不同元素,中间四个元素:
1 2 3 4 FLAG_START_GAME FLAG_CONTINUE_GAME FLAG_DISPLAY_HIGHSCORES FLAG_EXIT_GAME 
 
真正记录选择状态,MAX_NO_MAIN_MENU_STATUS_FLAGS的NO应理解为NO.1,NO.2的number含义,而非yes/no中no的含义。
用一个bool类型的数组menustatus记录玩家本轮选择;flagInputErroneousChoice记录玩家输入是否非法,若非法,则该值为真,soloLoop()返回该值,于是又进行下一轮的soloLoop()。直至用户输入1~4中某一数字,于是在processPlayerInput()中,或是跳转至对应函数执行后续功能,或是结束游戏退出程序。
需注意的是,processPlayerInput()中的几个判断不能写为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void  processPlayerInput ()   {	if  (menustatus[FLAG_START_GAME]) 	{             		 	} 	else  if  (menustatus[FLAG_CONTINUE_GAME]) 	{             		 	} 	else  if  (menustatus[FLAG_DISPLAY_HIGHSCORES]) 	{             		 	} 	else  (menustatus[FLAG_EXIT_GAME]) 	{ 		exit (EXIT_SUCCESS); 	} } 
 
如果写成上述形式,当用户输入错误时游戏直接结束,程序退出。
我们的soloLoop()还需最后一步完善——draw something.
需要在clearScreen()函数后加上这两句:
1 2 3 4 DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); DrawAlways(std ::cout ,            DataSuppliment(flagInputErroneousChoice,                           Game::Graphics::Menu::MenuGraphicsOverlay)); 
 
其中AsciiArt2048()声明于game-graphics.hpp中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef  GAMEGRAPHICS_H #define  GAMEGRAPHICS_H #include  <string>  namespace  Game{ 	namespace  Graphics 	{ 		std ::string  AsciiArt2048 ()  ; 	}  }  #endif  !GAMEGRAPHICS_H 
 
定义于game-graphics.cpp中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include  "game-graphics.hpp"  #include  <stringstream>  namespace  Game{ 	namespace  Graphics 	{         std ::string  AsciiArt2048 ()            {            constexpr  auto  title_card_2048 = R"(     /\\\\\\\\\          /\\\\\\\                /\\\         /\\\\\\\\\   /\\\///////\\\      /\\\/////\\\            /\\\\\       /\\\///////\\\   \///      \//\\\    /\\\    \//\\\         /\\\/\\\      \/\\\     \/\\\              /\\\/    \/\\\     \/\\\       /\\\/\/\\\      \///\\\\\\\\\/            /\\\//      \/\\\     \/\\\     /\\\/  \/\\\       /\\\///////\\\          /\\\//         \/\\\     \/\\\   /\\\\\\\\\\\\\\\\   /\\\      \//\\\         /\\\/            \//\\\    /\\\   \///////////\\\//   \//\\\      /\\\         /\\\\\\\\\\\\\\\   \///\\\\\\\/              \/\\\      \///\\\\\\\\\/         \///////////////      \///////                \///         \/////////   )" ;            std ::ostringstream  title_card_richtext;             title_card_richtext << title_card_2048 << "\n\n\n" ;                          return  title_card_richtext.str();         } 	}  }  
 
AsciiArt2048()功能非常简单,只是单纯的输出艺术字字符串,其中字符串前的R表示后面被分隔符包围的整个字符串都是原生字符, 不用转义, 所见即所得。
而MenuGraphicsOverlay()则声明于menu-graphics.hpp中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef  MENUGRAPHICS_H #define  MENUGRAPHICS_H #include  <string>  namespace  Game {     namespace  Graphics      {         namespace  Menu          {             std ::string  MenuGraphicsOverlay (bool  ifInputInvalid)  ;         }      }  }  #endif   
 
该函数需要一个布尔值作为参数,该值为真则说明上一轮循环中用户输入非法,需提示其重新输入。
MenuGraphicsOverlay()定义于menu-graphics.cpp中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 namespace  Game {     namespace  Graphics      {         namespace  Menu          {                          std ::string  MenuGraphicsOverlay (bool  input_error_choice_invalid)                {                std ::ostringstream  str_os;                 DrawAlways(str_os, MenuTitlePrompt);                 DrawAlways(str_os, MenuOptionsPrompt);                                  DrawOnlyWhen(str_os, input_error_choice_invalid,                              InputMenuErrorInvalidInputPrompt);                 DrawAlways(str_os, InputMenuPrompt);                 return  str_os.str(); 			}                      }      }  }  
 
MenuGraphicsOverlay()又分别调用了四个函数。MenuTitlePrompt()和MenuOptionsPrompt()分别负责标题与选项的呈现,InputMenuErrorInvalidInputPrompt()仅在用户输入非法时被调用,用以提示用户重新输入,InputMenuPrompt()提示用户键入选项。
以上四个函数均声明于menu-graphics.hpp,定义于menu-graphics.cpp。
menu-graphics.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef  MENUGRAPHICS_H #define  MENUGRAPHICS_H #include  <string>  namespace  Game{     namespace  Graphics     {         namespace  Menu         {             std ::string  MenuTitlePrompt ()  ;             std ::string  MenuOptionsPrompt ()  ;             std ::string  InputMenuErrorInvalidInputPrompt ()  ;             std ::string  InputMenuPrompt ()  ;             std ::string  MenuGraphicsOverlay (bool  ifInputInvalid)  ;         }      }  }  #endif   
 
menu-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include  "menu-graphics.hpp"  #include  "global.hpp"  #include  <sstream>  namespace  Game{ 	namespace  Graphics 	{ 		namespace  Menu 		{ 			std ::string  MenuTitlePrompt ()   			 {				constexpr  auto  greetings_text = "Welcome to " ; 				constexpr  auto  gamename_text = "2048!" ; 				constexpr  auto  sp = "  " ; 				std ::ostringstream  str_os; 				std ::ostringstream  title_richtext; 				title_richtext << sp << greetings_text << gamename_text << "\n" ; 				str_os << title_richtext.str(); 				return  str_os.str(); 			} 			std ::string  MenuOptionsPrompt ()   			 {				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 << sp << err_input_text << "\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  MenuGraphicsOverlay (bool  ifInputInvalid)  			 {				 			} 		} 	} }  
 
至此,一个基础的框架就搭建起来了。
代码 2048.cpp 1 2 3 4 5 6 7 8 9 #include  "menu.hpp"  int  main ()  {	 	Menu::startMenu(); 	return  0 ; } 
 
1 2 3 4 5 6 7 8 9 #ifndef  MENU_H #define  MENU_H namespace  Menu { 	void  startMenu ()  ; }  #endif   
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 #include  "menu.hpp"  #include  "menu-graphics.hpp"  #include  "global.hpp"  #include  "game-graphics.hpp"  #include  <array>  #include  <iostream>  namespace { 	enum  MenuStatusFlag   	{ 		FLAG_NULL, 		FLAG_START_GAME, 		FLAG_CONTINUE_GAME, 		FLAG_DISPLAY_HIGHSCORES, 		FLAG_EXIT_GAME, 		MAX_NO_MAIN_MENU_STATUS_FLAGS 	}; 	using  menuStatus_t = std ::array <bool , MAX_NO_MAIN_MENU_STATUS_FLAGS>; 	 	menuStatus_t menustatus{}; 	 	bool  flagInputErroneousChoice{ false  }; 	void  receiveInputFromPlayer (std ::istream& in_os)   	 {		 		flagInputErroneousChoice = bool {}; 		char  c; 		in_os >> c; 		switch  (c)  		{ 		case  '1' : 			menustatus[FLAG_START_GAME] = true ; 			break ; 		case  '2' : 			menustatus[FLAG_CONTINUE_GAME] = true ; 			break ; 		case  '3' : 			menustatus[FLAG_DISPLAY_HIGHSCORES] = true ; 			break ; 		case  '4' : 			menustatus[FLAG_EXIT_GAME] = true ; 			break ; 		default : 			flagInputErroneousChoice = true ; 			break ; 		} 	} 	void  processPlayerInput ()   	 {		if  (menustatus[FLAG_START_GAME]) 		{ 			 		} 		if  (menustatus[FLAG_CONTINUE_GAME]) 		{ 			 		} 		if  (menustatus[FLAG_DISPLAY_HIGHSCORES]) 		{ 			 		} 		if  (menustatus[FLAG_EXIT_GAME]) 		{ 			exit (EXIT_SUCCESS); 		} 	} 	bool  soloLoop ()  	 {		 		menustatus = menuStatus_t{}; 		clearScreen(); 		DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); 		DrawAlways(std ::cout , 			DataSuppliment(flagInputErroneousChoice, 				Game::Graphics::Menu::MenuGraphicsOverlay)); 		receiveInputFromPlayer(std ::cin ); 		processPlayerInput(); 		return  flagInputErroneousChoice; 	} 	void  endlessLoop ()   	 {		while  (soloLoop()) 			; 	} }  namespace  Menu{ 	void  startMenu ()   	 {		endlessLoop(); 	} }  
 
global.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #ifndef  GLOBAL_H #define  GLOBAL_H #include  <iosfwd>  #include  <string>  using  ull = unsigned  long  long ;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  clearScreen ()  ;#endif   
 
global.cpp 1 2 3 4 5 6 7 8 9 10 #include  "global.hpp"  void  clearScreen ()   {#ifdef  _WIN32 	system("cls" ); #else  	system("clear" ); #endif  } 
 
game-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #ifndef  GAME_GRAPHICS_H #define  GAME_GRAPHICS_H #include  <string>  namespace  Game{ 	namespace  Graphics 	{ 		std ::string  AsciiArt2048 ()  ; 	}  }  #endif   
 
game-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include  "game-graphics.hpp"  #include  <sstream>  namespace  Game{ 	namespace  Graphics 	{         std ::string  AsciiArt2048 ()            {            constexpr  auto  title_card_2048 = R"(     /\\\\\\\\\          /\\\\\\\                /\\\         /\\\\\\\\\   /\\\///////\\\      /\\\/////\\\            /\\\\\       /\\\///////\\\   \///      \//\\\    /\\\    \//\\\         /\\\/\\\      \/\\\     \/\\\              /\\\/    \/\\\     \/\\\       /\\\/\/\\\      \///\\\\\\\\\/            /\\\//      \/\\\     \/\\\     /\\\/  \/\\\       /\\\///////\\\          /\\\//         \/\\\     \/\\\   /\\\\\\\\\\\\\\\\   /\\\      \//\\\         /\\\/            \//\\\    /\\\   \///////////\\\//   \//\\\      /\\\         /\\\\\\\\\\\\\\\   \///\\\\\\\/              \/\\\      \///\\\\\\\\\/         \///////////////      \///////                \///         \/////////   )" ;            std ::ostringstream  title_card_richtext;             title_card_richtext << title_card_2048 << "\n\n\n" ;             return  title_card_richtext.str();         } 	}  }  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef  MENUGRAPHICS_H #define  MENUGRAPHICS_H #include  <string>  namespace  Game{     namespace  Graphics     {         namespace  Menu         {             std ::string  MenuTitlePrompt ()  ;             std ::string  MenuOptionsPrompt ()  ;             std ::string  InputMenuErrorInvalidInputPrompt ()  ;             std ::string  InputMenuPrompt ()  ;             std ::string  MenuGraphicsOverlay (bool  ifInputInvalid)  ;         }      }  }  #endif   
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include  "menu-graphics.hpp"  #include  "global.hpp"  #include  <sstream>  namespace  Game{ 	namespace  Graphics 	{ 		namespace  Menu 		{ 			std ::string  MenuTitlePrompt ()   			 {				constexpr  auto  greetings_text = "Welcome to " ; 				constexpr  auto  gamename_text = "2048!" ; 				constexpr  auto  sp = "  " ; 				std ::ostringstream  str_os; 				std ::ostringstream  title_richtext; 				title_richtext << sp << greetings_text << gamename_text << "\n" ; 				str_os << title_richtext.str(); 				return  str_os.str(); 			} 			std ::string  MenuOptionsPrompt ()   			 {				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 << sp << err_input_text << "\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  MenuGraphicsOverlay (bool  ifInputInvalid)  			 {				std ::ostringstream  str_os; 				DrawAlways(str_os, MenuTitlePrompt); 				DrawAlways(str_os, MenuOptionsPrompt); 				 				DrawOnlyWhen(str_os, ifInputInvalid, 					InputMenuErrorInvalidInputPrompt); 				DrawAlways(str_os, InputMenuPrompt); 				return  str_os.str(); 			} 		} 	} }  
 
阶段二 构建 首先考虑丰富一下已有功能的视觉呈现——添加色彩。实现color.hpp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #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" ;         }     }; }  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   
 
格式:
“\033[字背景颜色;字体颜色m字符串/033[0m”
例如: “\033[41;36m something here /033[0m”
其中41的位置代表底色, 36的位置是代表字的颜色。
在enum class Code中,FG开头的颜色代表设置字体颜色,BG开头的颜色代表设置背景颜色。
有了color.hpp后,给之前几个负责绘制图案的函数加上颜色:
game-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include  "color.hpp"  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(); } 
 
menu-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include  "color.hpp"  std ::string  MenuTitlePrompt ()   {    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 << yellow << gamename_text         << bold_off << def << "\n" ;     str_os << title_richtext.str();     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(); } 
 
接着完善“开启一局新游戏”代码。
在menu.cpp中完善startGame()函数:
1 2 3 4 void  startGame ()  {    Game::startGame(); } 
 
该函数调用Game名字空间里的同名函数,需创建game.hpp、game.cpp实现相应功能。
game.hpp
1 2 3 4 5 6 7 8 9 10 #ifndef  GAME_H #define  GAME_H namespace  Game {     void  startGame ()  ;      };  #endif   
 
game.cpp
1 2 3 4 5 6 7 8 9 10 #include  "game.hpp"  #include  "game-pregame.hpp"  namespace  Game{ 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} }  
 
在正式开始一局游戏前,需要做一些准备工作。比如设置游戏棋盘大小(我们最终实现的游戏支持从3 x 3到10 x 10大小的棋盘)等工作。故还需创建game-pregame.hpp与game-pregame.cpp两文件负责游戏正式开局前的准备工作。
game-pregame.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef  GAME_PREGAME_H #define  GAME_PREGAME_H namespace  Game{ 	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  ; 	}  }  #endif   
 
game-pregame.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include  "game-pregame.hpp"  namespace  Game{ 	namespace  	{ 		enum  class  NewGameFlag  		{ 			NewGameFlagNull, 			NoPreviousSaveAvailable 		}; 		bool  noSave{ false  }; 		bool  soloLoop ()  		 {			 			return  false ; 		} 		void  endlessLoop ()  		 {			while  (soloLoop()) 				; 		} 		void  SetupNewGame (NewGameFlag ns)  		 {			noSave = (ns == NewGameFlag::NoPreviousSaveAvailable) ? true  : false ; 			endlessLoop(); 		} 	}  	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  		 {			SetupNewGame(NewGameFlag::NewGameFlagNull); 		} 	}  }  
 
NewGameFlag::NoPreviousSaveAvailable和noSave都与主菜单界面选项二的continueGame相关,其逻辑为:若玩家在主菜单界面选择选项二:Continue Previous Game,需判断是否存在Previous Game数据。若存在,则加载之前的游戏数据(目前尚未实现),否则直接视为开启一轮新游戏。
现在考虑完善soloLoop()。因为我们的游戏能向玩家提供不同尺寸的规格,因此在正式游玩前需要:
输出一些基本的提示语,引导玩家输入棋盘尺寸 
读入玩家输入 
根据玩家输入,执行相应策略 
若输入非法,提示玩家重新输入 
 
具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 bool  soloLoop ()  {    bool  invalidInputValue = flagInputErroneousChoice;     const  auto  questionAboutGameBoardSizePrompt = [&invalidInputValue]() {         std ::ostringstream  str_os;                  DrawOnlyWhen(str_os, invalidInputValue, Graphics::BoardSizeErrorPrompt);         DrawAlways(str_os, Graphics::BoardInputPrompt);         return  str_os.str();     };          pregamesetup_status = pregameesetup_status_t {};     clearScreen();     DrawAlways(std ::cout , Game::Graphics::AsciiArt2048);     DrawAsOneTimeFlag(std ::cout , noSave, Graphics::GameBoardNoSaveErrorPrompt);     DrawAlways(std ::cout , questionAboutGameBoardSizePrompt);     receiveInputFromPlayer(std ::cin );     processPreGame();     return  flagInputErroneousChoice; } 
 
逐行分析该函数。
1 bool  invalidInputValue = flagInputErroneousChoice;
 
首先,我们使用布尔变量invalidInputValue记录用户输入是否非法。变量flagInputErroneousChoice定义在函数体外,初值为false,充当全局变量使用。在函数receiveInputFromPlayer()中,当用户输入非法时flagInputErroneousChoice被设为true。
1 2 3 4 5 6 7 8 9 const  auto  questionAboutGameBoardSizePrompt = [&invalidInputValue]() {    std ::ostringstream  str_os;          DrawOnlyWhen(str_os, invalidInputValue, Graphics::BoardSizeErrorPrompt);     DrawAlways(str_os, Graphics::BoardInputPrompt);     return  str_os.str(); }; 
 
该lambda表达式返回提示用户输入棋盘尺寸的字符串,当用户输入非法时(即invalidInputValue为真),BoardSizeErrorPrompt()提醒用户输入错误。
BoardSizeErrorPrompt()与BoardInputPrompt()均声明于game-graphics.hpp,定义于game-graphics.cpp。
game-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 std ::string  BoardSizeErrorPrompt ()   {    const  auto  invalid_prompt_text =      {         "Invalid input. Gameboard size should range from " ,         " to " ,          "."       };          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  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(); } 
 
常量MIN_GAME_BOARD_PLAY_SIZE和MAX_GAME_BOARD_PLAY_SIZE定义于game-graphics.hpp中。
game-graphics.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef  GAME_GRAPHICS_H #define  GAME_GRAPHICS_H #include  <string>  enum  GameBoardDimensions { 	MIN_GAME_BOARD_PLAY_SIZE = 3 , 	MAX_GAME_BOARD_PLAY_SIZE = 10  }; namespace  Game{ 	 }  #endif   
 
BoardInputPrompt() 中board_size_prompt_text的内容指的是,虽然我们的游戏提供从3 x 3到10 x 10尺寸的规格,但最终只有4 x 4 规格棋盘大小的局次会记录分数。
soloLoop()的接下来两行:
1 2  pregamesetup_status = pregameesetup_status_t {}; 
 
pregameesetup_status_t定义于本文件的匿名namespace内,如下:
1 2 3 4 5 6 7 8 9 10 11 enum  PreGameSetupStatusFlag {     FLAG_NULL,     FLAG_START_GAME,     FLAG_RETURN_TO_MAIN_MENU,     MAX_NO_PREGAME_SETUP_STATUS_FLAGS }; using  pregameesetup_status_t  =    std ::array <bool , MAX_NO_PREGAME_SETUP_STATUS_FLAGS>; pregameesetup_status_t  pregamesetup_status{};
 
至于soloLoop()的最后几行:
1 2 3 4 5 6 7 8 9    clearScreen();    DrawAlways(std ::cout , Game::Graphics::AsciiArt2048);    DrawAsOneTimeFlag(std ::cout , noSave, Graphics::GameNoSaveErrorPrompt);    DrawAlways(std ::cout , questionAboutBoardSizePrompt);    receiveInputFromPlayer(std ::cin );    processPreGame(); return  flagInputErroneousChoice;
 
清屏,输出必要信息,接受并处理用户输入,最后返回flagInputErroneousChoice,没什么值得特别说明的。GameNoSaveErrorPrompt()定义于game-graphics.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 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(); } 
 
processPreGame()内容十分简单:
1 2 3 4 5 6 7 8 9 10 11 12 void  processPreGame ()  {    if  (pregamesetup_status[FLAG_START_GAME])     {                       }     if  (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU])     {         Menu::startMenu();     } } 
 
receiveInputFromPlayer()稍微复杂一些:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 int  receiveGameBoardSize (std ::istream& is)  {    int  playerInputBoardSize{ 0  };     if  (!(is >> playerInputBoardSize))     {         constexpr  auto  INVALID_INPUT_VALUE_FLAG = -1 ;         playerInputBoardSize = INVALID_INPUT_VALUE_FLAG;         is.clear();         is.ignore(std ::numeric_limits<std ::streamsize>::max(), '\n' );     }     return  playerInputBoardSize; } void  receiveInputFromPlayer (std ::istream& is)  {         flagInputErroneousChoice = bool { false  };          const  auto  gbsize = receiveGameBoardSize(is);     const  auto  isValidBoardSize =         (gbsize >= MIN_GAME_BOARD_PLAY_SIZE) && (gbsize <= MAX_GAME_BOARD_PLAY_SIZE);          if  (isValidBoardSize)     {         storedGameBoardSize = gbsize;         pregamesetup_status[FLAG_START_GAME] = true ;     }          bool  isSpecialCase{ true  };     switch  (gbsize)     {                  case  0 :             pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true ;             break ;         default :             isSpecialCase = false ;             break ;     }     if  (!isValidBoardSize && !isSpecialCase)     {         flagInputErroneousChoice = true ;     } } 
 
receiveInputFromPlayer()调用receiveGameBoardSize()来读取用户输入,然后处理输入数据。如果用户输入为3~10之间数字,满足isValidBoardSize条件(符合要求的棋盘大小),设置状态为FLAG_START_GAME;若用户输入为数字0,则代表回退到主界面菜单,此为special case,设置状态为FLAG_RETURN_TO_MAIN_MENU。以上两种外的任何情况均视为非法输入。
1 is.ignore(std ::numeric_limits<std ::streamsize>::max(), '\n' ); 
 
这行代码的作用为清空输入缓冲。
std::istream::ignore
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Extract and  discard characters Extracts characters from the input sequence and  discards them, until either n characters have been extracted, or  one compares equal to delim. #include  <iostream>     // std::cin, std::cout  int  main  ()   {  char  first, last;   std ::cout  << "Please, enter your first name followed by your surname: " ;   first = std ::cin .get();        std ::cin .ignore(256 ,' ' );      last = std ::cin .get();         std ::cout  << "Your initials are "  << first << last << '\n' ;   return  0 ; } Possible output: Please, enter your first name followed by your surname: John Smith Your initials are JS 
 
std::streamsize
1 2 3 4 5 6 7 8 9 10 11 12 13 The type std ::streamsize is an implementation-defined signed  integral type used to represent the number of characters transferred in an I/O operation or  the size of an I/O buffer. It is used as a signed  counterpart of std ::size_t , similar to the POSIX type ssize_t . #include  <limits>  #include  <cstddef>  #include  <iostream>  int  main ()  {    std ::cout  << "streamsize: "  << std ::dec << std ::numeric_limits<std ::streamsize>::max()               << " or "  << std ::hex << std ::numeric_limits<std ::streamsize>::max() << '\n' ; } Possible output: streamsize: 9223372036854775807  or  0x7fffffffffffffff  
 
std::numeric_limits<T>::max
1 Returns the maximum finite value representable by the numeric type  T. Meaningful for  all bounded types. 
 
至此,我们的游戏已经能够向玩家提供选取不同规格棋盘的功能。
代码 2048.cpp same as before
same as before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 #include  "menu.hpp"  #include  "menu-graphics.hpp"  #include  "game.hpp"  #include  "global.hpp"  #include  "game-graphics.hpp"  #include  <array>  #include  <iostream>  namespace { 	enum  MenuStatusFlag   	{ 		FLAG_NULL, 		FLAG_START_GAME, 		FLAG_CONTINUE_GAME, 		FLAG_DISPLAY_HIGHSCORES, 		FLAG_EXIT_GAME, 		MAX_NO_MAIN_MENU_STATUS_FLAGS 	}; 	using  menuStatus_t = std ::array <bool , MAX_NO_MAIN_MENU_STATUS_FLAGS>; 	 	menuStatus_t menustatus{}; 	 	bool  flagInputErroneousChoice{ false  }; 	void  startGame ()  	 {		Game::startGame(); 	} 	void  receiveInputFromPlayer (std ::istream& in_os)   	 {		 	} 	void  processPlayerInput ()   	 {		if  (menustatus[FLAG_START_GAME]) 		{ 			startGame(); 		} 		if  (menustatus[FLAG_CONTINUE_GAME]) 		{ 			 		} 		if  (menustatus[FLAG_DISPLAY_HIGHSCORES]) 		{ 			 		} 		if  (menustatus[FLAG_EXIT_GAME]) 		{ 			exit (EXIT_SUCCESS); 		} 	} 	bool  soloLoop ()  	 {		 	} 	void  endlessLoop ()   	 {		 	} }  namespace  Menu{ 	void  startMenu ()   	 {		 	} }  
 
global.hpp same as before
global.cpp same as before
game-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #ifndef  GAME_GRAPHICS_H #define  GAME_GRAPHICS_H #include  <string>  enum  GameBoardDimensions { 	MIN_GAME_BOARD_PLAY_SIZE = 3 , 	MAX_GAME_BOARD_PLAY_SIZE = 10  }; namespace  Game{ 	namespace  Graphics 	{ 		std ::string  AsciiArt2048 ()  ; 		std ::string  BoardSizeErrorPrompt ()  ; 		std ::string  BoardInputPrompt ()  ; 		std ::string  GameBoardNoSaveErrorPrompt ()  ; 	}  }  #endif   
 
game-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include  "game-graphics.hpp"  #include  "color.hpp"  #include  <sstream>  namespace  Game{ 	namespace  Graphics 	{         std ::string  AsciiArt2048 ()            {                     }         std ::string  BoardSizeErrorPrompt ()            {            const  auto  invalid_prompt_text =              {                 "Invalid input. Gameboard size should range from " ,                 " to " ,                  "."               };                          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  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  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();         } 	}  }  
 
same as before
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include  "menu-graphics.hpp"  #include  "global.hpp"  #include  "color.hpp"  #include  <sstream>  namespace  Game{ 	namespace  Graphics 	{ 		namespace  Menu 		{ 			std ::string  MenuTitlePrompt ()   			 {				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 << yellow << gamename_text 					<< bold_off << def << "\n" ; 				str_os << title_richtext.str(); 				return  str_os.str(); 			} 			std ::string  MenuOptionsPrompt ()   			 {				 			} 			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 ()   			 {				 			} 			std ::string  MenuGraphicsOverlay (bool  ifInputInvalid)  			 {				 			} 		} 	} }  
 
game.hpp 1 2 3 4 5 6 7 8 9 10 #ifndef  GAME_H #define  GAME_H namespace  Game {     void  startGame ()  ;      };  #endif   
 
game.cpp 1 2 3 4 5 6 7 8 9 10 #include  "game.hpp"  #include  "game-pregame.hpp"  namespace  Game{ 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} }  
 
game-pregame.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 #ifndef  GAME_PREGAME_H #define  GAME_PREGAME_H namespace  Game{ 	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  ; 	}  }  #endif   
 
game-pregame.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 #include  "game-pregame.hpp"  #include  "game-graphics.hpp"  #include  "game-input.hpp"  #include  "menu.hpp"  #include  "global.hpp"  #include  <array>  #include  <sstream>  #include  <iostream>  #include  <limits>  namespace  Game{ 	namespace  	{ 		enum  PreGameSetupStatusFlag  		{ 			FLAG_NULL, 			FLAG_START_GAME, 			FLAG_RETURN_TO_MAIN_MENU, 			MAX_NO_PREGAME_SETUP_STATUS_FLAGS 		}; 		using  pregameesetup_status_t  = 			std ::array <bool , MAX_NO_PREGAME_SETUP_STATUS_FLAGS>; 		 		pregameesetup_status_t  pregamesetup_status{}; 		enum  class  NewGameFlag  		{ 			NewGameFlagNull, 			NoPreviousSaveAvailable 		}; 		 		bool  noSave{ false  }; 		 		bool  flagInputErroneousChoice{ false  }; 		 		ull storedGameBoardSize{ 1  }; 		int  receiveGameBoardSize (std ::istream& is)  		 {			int  playerInputBoardSize{ 0  }; 			if  (!(is >> playerInputBoardSize)) 			{ 				constexpr  auto  INVALID_INPUT_VALUE_FLAG = -1 ; 				playerInputBoardSize = INVALID_INPUT_VALUE_FLAG; 				is.clear(); 				is.ignore(std ::numeric_limits<std ::streamsize>::max(), '\n' ); 			} 			return  playerInputBoardSize; 		} 		void  receiveInputFromPlayer (std ::istream& is)  		 {			using  namespace  Input::Keypress::Code; 			 			flagInputErroneousChoice = bool { false  }; 			 			 			const  auto  gbsize = receiveGameBoardSize(is); 			const  auto  isValidBoardSize = 				(gbsize >= MIN_GAME_BOARD_PLAY_SIZE) && (gbsize <= MAX_GAME_BOARD_PLAY_SIZE); 			 			if  (isValidBoardSize) 			{ 				storedGameBoardSize = gbsize; 				pregamesetup_status[FLAG_START_GAME] = true ; 			} 			 			bool  goBackToMainMenu{ true  }; 			switch  (gbsize) 			{ 			case  CODE_HOTKEY_PREGAME_BACK_TO_MENU: 				pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true ; 				break ; 			default : 				goBackToMainMenu = false ; 				break ; 			} 			if  (!isValidBoardSize && !goBackToMainMenu) 			{ 				flagInputErroneousChoice = true ; 			} 		} 		void  processPreGame ()  		 {			if  (pregamesetup_status[FLAG_START_GAME]) 			{ 				 				 			} 			if  (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU]) 			{ 				Menu::startMenu(); 			} 		} 		bool  soloLoop ()  		 {			bool  invalidInputValue = flagInputErroneousChoice; 			const  auto  questionAboutGameBoardSizePrompt = [&invalidInputValue]() { 				std ::ostringstream  str_os; 				 				DrawOnlyWhen(str_os, invalidInputValue, Graphics::BoardSizeErrorPrompt); 				DrawAlways(str_os, Graphics::BoardInputPrompt); 				return  str_os.str(); 			}; 			 			pregamesetup_status = pregameesetup_status_t {}; 			clearScreen(); 			DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); 			DrawAsOneTimeFlag(std ::cout , noSave, Graphics::GameBoardNoSaveErrorPrompt); 			DrawAlways(std ::cout , questionAboutGameBoardSizePrompt); 			receiveInputFromPlayer(std ::cin ); 			processPreGame(); 			return  flagInputErroneousChoice; 		} 		void  endlessLoop ()  		 {			while  (soloLoop()) 				; 		} 		void  SetupNewGame (NewGameFlag ns)  		 {			noSave = (ns == NewGameFlag::NoPreviousSaveAvailable) ? true  : false ; 			endlessLoop(); 		} 	}  	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  		 {			SetupNewGame(NewGameFlag::NewGameFlagNull); 		} 	}  }  
 
color.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #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" ;         }     }; }  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   
 
阶段三 构建 首先考虑改善一下上一阶段的部分代码。
game-pregame.cpp
1 2 3 4 5 6 7 8 9 10 switch  (gbsize){ case  0 :	pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true ; 	break ; default :	goBackToMainMenu = false ; 	break ; } 
 
我们的设计思路是,当玩家键入数字0时,返回主菜单。但也可以将数字0改为字母’A’, ‘B’, ‘C’…等其他任意字符。一个更好的方式是创建game-input.hpp文件,将不同按键对应的功能统一定义在此文件中。
game-input.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #ifndef  GAME_INPUT_H #define  GAME_INPUT_H namespace  Game{ 	namespace  Input 	{ 		namespace  Keypress 		{ 			namespace  Code 			{ 				enum  				{ 					CODE_HOTKEY_PREGAME_BACK_TO_MENU = 0 , 				}; 			}  		}  	}  }  #endif   
 
然后修改game-graphics.cpp如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include  "game-input.hpp"  void  receiveInputFromPlayer (std ::istream& is)  {    using  namespace  Input::Keypress::Code;                    switch  (gbsize)     {         case  CODE_HOTKEY_PREGAME_BACK_TO_MENU:             pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true ;             break ;         default :             goBackToMainMenu = false ;             break ;     }           } 
 
经过之前的努力,棋盘尺寸选好了,应该可以正式开始游戏了。但光有尺寸还不够,还需要有对应尺寸的棋盘才行,于是创建文件gameboard.hpp负责棋盘相关工作。但在这之前,需要先创立tile.hpp负责棋盘创建前的准备工作。
tile.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef  TILE_H #define  TILE_H #include  "global.hpp"  namespace  Game{ 	struct  tile_t  	{ 		ull value{ 0  }; 		bool  blocked{ false  }; 	}; }  #endif   
 
tile,即地砖。我们的棋盘就是用一片片地砖铺出来的。每一块砖有两个属性,分别是此砖的对应的分值value和是布尔值blocked判断是否被阻挡。
有了地砖之后,才能搭建起棋盘。
gameboard.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #ifndef  GAMEBOARD_H #define  GAMEBOARD_H #include  "tile.hpp"  #include  <tuple>  #include  <vector>  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{ false  }; 		bool  moved{ false  }; 		ull score{ 0  }; 		 		ull largestTile{ 2  }; 		long  long  moveCount{ -1  }; 		GameBoard() = default ; 		explicit  GameBoard (ull boardsize)  ; 		explicit  GameBoard (ull boardsize, tile_data_array_t  existboard)  ; 	}; }  #endif   
 
gameboard.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 #include  "gameboard.hpp"  namespace  Game{ 	GameBoard::GameBoard(ull boardsize) 		: GameBoard{ boardsize, tile_data_array_t (boardsize * boardsize)} 	{ } 	GameBoard::GameBoard(ull boardsize, tile_data_array_t  existboard) 		: gbda{ boardsize, existboard }  	{ } }  
 
现在,假设玩家Mike选择开始一局新游戏,棋盘选择4 x 4规格的。我们为其创建一个GameBoard对象,该棋盘含16个tile对象。它们呈现的布局应如下:
1 2 3 4 (0 , 0 ) (0 , 1 ) (0 , 2 ) (0 , 3 ) (1 , 0 ) (1 , 1 ) (1 , 2 ) (1 , 3 ) (2 , 0 ) (2 , 1 ) (2 , 2 ) (2 , 3 ) (3 , 0 ) (3 , 1 ) (3 , 2 ) (3 , 3 ) 
 
我们需要将16个tile对象的每一个与其在棋盘中对应的(x, y)坐标对应起来,比如在该例中,第0个tile对象的坐标为(0, 0),第4个tile对象的坐标为(1, 0)等等。这就需要一个负责将对象数组索引与坐标相互转换的文件,point2d.hpp。
point2d.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #ifndef  POINT2D_H #define  POINT2D_H #include  <tuple>  class  point2D_t { 	 	using  point_datatype_t  = typename  std ::tuple<int , int >; 	point_datatype_t  pointVector{ 0 , 0  }; 	explicit  point2D_t (const  point_datatype_t  pt)  		: pointVector { 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)>(pointVector); 	} 	template <PointCoord dimension> 	void  set (int  val)  	 {		std ::get<static_cast <int >(dimension)>(pointVector) = val; 	} 	point_datatype_t  get ()  const  	 {		return  pointVector; 	} 	void  set (point_datatype_t  val)  	 {		pointVector = val; 	} 	void  set (const  int  x, const  int  y)  	 {		set (std ::make_tuple(x, y)); 	} 	point2D_t& operator +=(const  point2D_t& rhs)  	{ 		this ->pointVector = std ::make_tuple( 			get<PointCoord::COORD_X>() + rhs.get<PointCoord::COORD_X>(), 			get<PointCoord::COORD_Y>() + rhs.get<PointCoord::COORD_Y>()); 		return  *this ; 	} 	point2D_t& operator -=(const  point2D_t& rhs)  	{ 		this ->pointVector = std ::make_tuple( 			get<PointCoord::COORD_X>() - rhs.get<PointCoord::COORD_X>(), 			get<PointCoord::COORD_Y>() - rhs.get<PointCoord::COORD_Y>()); 		return  *this ; 	} }; inline  point2D_t operator +(point2D_t lhs, const  point2D_t& rhs) {	lhs += rhs; 	return  lhs; } inline  point2D_t operator -(point2D_t lhs, const  point2D_t& rhs) {	lhs -= rhs; 	return  lhs; } #endif   
 
以上内容都比较简单,不过多介绍。
现在,我们进一步丰富gameboard.*,添加一些必要的函数供后续使用。
首先考虑在gameboard.hpp中声明以下两个函数:
1 2 size_t  getSizeOfGameboard (GameBoard::gameboard_data_array_t  gbda)  ;tile_t  getTileOnGameboard (GameBoard::gameboard_data_array_t & gbda, point2D_t& pt)  ;
 
前者返回棋盘尺寸多大,后者根据坐标获取相应的地砖。下面是gameboard.cpp新增内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include  "gameboard.hpp"  #include  "tile.hpp"  #include  "point2d.hpp"  namespace  Game{ 	namespace  	{ 		 		using  gameboard_data_array_t  = GameBoard::gameboard_data_array_t ; 		enum  gameboard_data_array_fields  		{ 			IDX_BOARDSIZE, 			IDX_TILE_ARRAY, 			MAX_NO_INDEX 		}; 		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 * getSizeOfGameboard(gbda) + y; 			} 			tile_t  operator () (gameboard_data_array_t & gbda, point2D_t& pt)  const  			 {				return  std ::get<IDX_TILE_ARRAY>(gbda)[point2D_to_1D_index(gbda, pt)]; 			} 			tile_t & operator () (gameboard_data_array_t & gbda, point2D_t& pt)  			 {				return  std ::get<IDX_TILE_ARRAY>(gbda)[point2D_to_1D_index(gbda, pt)]; 			} 		}; 	}  	GameBoard::GameBoard(ull boardsize) 		: GameBoard{ boardsize, tile_data_array_t (boardsize * boardsize)} 	{ } 	GameBoard::GameBoard(ull boardsize, tile_data_array_t  existboard) 		: gbda{ boardsize, existboard }  	{ } 	size_t  getSizeOfGameboard (gameboard_data_array_t & gbda)   	 {		return  std ::get<IDX_BOARDSIZE>(gbda); 	} 	tile_t  getTileOnGameboard (gameboard_data_array_t & gbda, point2D_t& pt)   	 {		return  gameboard_data_point_t {}(gbda, pt); 	} }  
 
struct gameboard_data_point_t中的point2D_to_1D_index()实现了从二维坐标到一维索引的转换。剩余新增内容较为简单,不再赘述。
接下来是两个获取棋盘基本信息的函数:
1 2 3 4 5 6 7 8 9 bool  hasWonOnGameboard (GameBoard& gb)  {    return  gb.win; } long  long  MoveCountOnGameBoard (GameBoard& gb)  {    return  gb.moveCount; } 
 
再往后是四个进阶函数:
1 2 3 4 void  registerMoveByOneOnGameboard (GameBoard &gb)  ;bool  addTileOnGameboard (GameBoard &gb)  ;void  unblockTilesOnGameboard (GameBoard &gb)  ;bool  canMoveOnGameboard (GameBoard &gb)  ;
 
我们一一分析之。
一、registerMoveByOneOnGameboard()
1 2 3 4 5 void  registerMoveByOneOnGameboard (GameBoard& gb)  {    gb.moveCount++;     gb.moved = false ; } 
 
这个函数是十分易于理解的——每当玩家行动一次后,累加当前行动次数,复位布尔值moved。
二、addTileOnGameboard()
游戏刚开始时及每轮玩家行动后,需在棋盘上随机空余地砖生成数字4或2.当棋盘仍有空余地砖供生成新砖块时,函数返回false,否则返回true,此时游戏结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 bool  addTileOnGameboardDataArray (gameboard_data_array_t & gbda)   {         constexpr  auto  CHANCE_OF_VALUE_FOUR_OVER_TWO = 75 ;     const  auto  index_list_of_free_tiles =         collectFreeTilesOnGameboardDataArray(gbda);     if  (!index_list_of_free_tiles.size())      {                  return  true ;     }     const  int  boardSize = getSizeOfGameboard(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 / boardSize, rand_selected_index % boardSize };     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  addTileOnGameboard (GameBoard& gb)  {	return  addTileOnGameboardDataArray(gb.gbda); } 
 
此函数的辅助函数略长,看起来有些复杂,涉及到一些对象与函数暂时还未实现,不过不影响理解。
1 2 3 4 5 constexpr  auto  CHANCE_OF_VALUE_FOUR_OVER_TWO = 75 ;
 
通过修改该值,可左右数字生成概率。
1 2 const  auto  index_list_of_free_tiles =        collectFreeTilesOnGameboardDataArray(gbda); 
 
函数collectFreeTilesOnGameboardDataArray()返回类型为std::vector,该向量中的每一元素均为tile_t对象列表中空余地砖的索引。 
RandInt暂未实现,其作用为生成随机数。
setTileValueOnGameboardDataArray()将最终取得的值置于随机所得的地砖上。
RandInt、setTileValueOnGameboardDataArray()与collectFreeTilesOnGameboardDataArray()均定义于gameboard.cpp的匿名空间中,具体内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 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; }; void  setTileValueOnGameboardDataArray (gameboard_data_array_t & gbda,                                       point2D_t pt, ull value)   {    gameboard_data_point_t {}(gbda, pt).value = value; } 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_TILE_ARRAY>(gbda))      {         if  (!t.value)          {             freeTiles.push_back(index_counter);         }         index_counter++;     }     return  freeTiles; } 
 
 三、unblockTilesOnGameboard()
该函数负责将棋盘所有地砖的blocked值全设为false。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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_TILE_ARRAY>(gbda).size());          std ::transform(std ::begin(std ::get<IDX_TILE_ARRAY>(gbda)),                    std ::end(std ::get<IDX_TILE_ARRAY>(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_BOARDSIZE>(gbda),                                   new_board_data_array }; } void  unblockTilesOnGameboard (GameBoard& gb)   {    gb.gbda = unblockTilesOnGameboardDataArray(gb.gbda); } 
 
如果对std::transform不熟悉,请参考这里 。
四、canMoveOnGameboard()
本函数用以判断玩家是否还可继续行动。若是,返回true,游戏可以继续进行,否则返回false,游戏结束。判断的逻辑也比较简单:依次遍历所有tile_t对象,对于每一tile_t对象,检查其上下左右四个方向相邻的砖块是否为空(为空意味着canMove,游戏尚未结束)或是相邻砖块的值相同(值相同意味着两块砖的值可以合并,canMove,游戏尚未结束)。只要尚存上述两条件之一,游戏就可继续进行下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 ull getTileValueOnGameboardDataArray (gameboard_data_array_t & gbda,  			point2D_t pt)   {    return  gameboard_data_point_t {}(gbda, pt).value; } bool  isPointInBoardArea (point2D_t pt, int  boardSize)   {    int  x, y;     std ::tie(x, y) = pt.get();     return  !(y < 0  || y > boardSize - 1  || x < 0  || x > boardSize - 1 ); } bool  canMoveOnGameboardDataArray (gameboard_data_array_t & gbda)   {    auto  indexCounter{ 0  };     const  auto  canMoveToOffset = [=, &indexCounter](const  tile_t  t)      {         const  int  boardSize = getSizeOfGameboard(gbda);         const  auto  currentPoint =             point2D_t{ indexCounter / boardSize, indexCounter % boardSize };         indexCounter++;         const  auto  listOfOffsets = { point2D_t{1 , 0 }, point2D_t{0 , 1 } };         const  auto  currentPointValue = t.value;         const  auto  offsetInRangeWithSameValue = [=](const  point2D_t offset)          {             const  auto  offsetCheck =              {                 currentPoint + offset,		                 currentPoint - offset		             };	             for  (const  auto  currentOffset : offsetCheck)              {                 if  (isPointInBoardArea(currentOffset, boardSize))                  {                     return  getTileValueOnGameboardDataArray(gbda, currentOffset) ==                         currentPointValue;                 }             }             return  false ;         };          return  ((currentPointValue == 0u ) ||                 std ::any_of(std ::begin(listOfOffsets), std ::end(listOfOffsets),                             offsetInRangeWithSameValue));     };      return  std ::any_of(std ::begin(std ::get<IDX_TILE_ARRAY>(gbda)),                        std ::end(std ::get<IDX_TILE_ARRAY>(gbda)), canMoveToOffset); }  bool  canMoveOnGameboard (GameBoard& gb)   {    return  canMoveOnGameboardDataArray(gb.gbda); } 
 
canMoveOnGameboard()有三个辅助函数。getTileValueOnGameboardDataArray()与isPointInBoardArea()较为简单,不多赘述。重点说一下canMoveOnGameboardDataArray(),这个函数初看较为复杂——函数体内含lambda表达式,lambda表达式内又嵌套lambda表达式。共三层,我们从里往外分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const  auto  offsetInRangeWithSameValue = [=, &gbda](const  point2D_t offset) {     const  auto  offsetCheck =      {         currentPoint + offset,		         currentPoint - offset		     };	     for  (const  auto  currentOffset : offsetCheck)      {         if  (isPointInBoardArea(currentOffset, boardSize))          {             return  getTileValueOnGameboardDataArray(gbda, currentOffset) ==                 currentPointValue;         }     }     return  false ; };  
 
最里边这层需结合第一行注释的语句理解,你给offsetInRangeWithSameValue()一个点(currentPoint)和一个偏移量({1, 0} or {0, 1}),它判断该点的上下/左右两侧是否存在与该点值相同的砖块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const  auto  canMoveToOffset = [=, &indexCounter, &gbda](const  tile_t  t) {     const  int  boardSize = getSizeOfGameboard(gbda);     const  auto  currentPoint =         point2D_t{ indexCounter / boardSize, indexCounter % boardSize };     indexCounter++;     const  auto  listOfOffsets = { point2D_t{1 , 0 }, point2D_t{0 , 1 } };     const  auto  currentPointValue = t.value;     const  auto  offsetInRangeWithSameValue = [=](const  point2D_t offset)      {              };      return  ((currentPointValue == 0u ) ||             std ::any_of(std ::begin(listOfOffsets), std ::end(listOfOffsets),                         offsetInRangeWithSameValue)); };  
 
这中间层嵌套需要一个tile_t对象作为参数,然后利用最里层嵌套检查该tile对象四周是否存在满足条件的对象。注意return语句中的条件:如果该tile_t对象本身处于空余状态,必然canMove。
1 2 3 4 5 6 7 8 9 10 11 12 bool  canMoveOnGameboardDataArray (gameboard_data_array_t & gbda)   {    auto  indexCounter{ 0  };     const  auto  canMoveToOffset = [=, &indexCounter, &gbda](const  tile_t  t)      {              };      return  std ::any_of(std ::begin(std ::get<IDX_TILE_ARRAY>(gbda)),                        std ::end(std ::get<IDX_TILE_ARRAY>(gbda)), canMoveToOffset); }  
 
最终,canMoveOnGameboardDataArray()使用std::any_of遍历所有tile_t对象,即可获知游戏结束与否。
接下来是一组关于游戏逻辑执行的函数。
1 2 3 4 void  tumbleTilesUpOnGameboard (GameBoard& gb)  ;void  tumbleTilesDownOnGameboard (GameBoard& gb)  ;void  tumbleTilesLeftOnGameboard (GameBoard& gb)  ;void  tumbleTilesRightOnGameboard (GameBoard& gb)  ;
 
游戏过程中,玩家选择一个方向后,将由这四个函数中对应方向者实现棋盘上全体砖块的移动逻辑。这四个函数的本质相同,我们挑一个来分析即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void  doTumbleTilesUpOnGameboard (GameBoard& gb)   {    const  int  boardSize = getSizeOfGameboard(gb.gbda);     for  (auto  x = 1 ; x < boardSize; x++)      {         auto  y = 0 ;         while  (y < boardSize)          {             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  }));             }             y++;         }     } } 
 
以 向上移动为例,我们从上至下一层层遍历棋盘,如果当前坐标处的tile对象值非空,则通过moveOnGameboard()尝试将其上移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 enum  class  COLLASPE_OR_SHIFT_T  {     ACTION_NONE,     ACTION_COLLASPE,     ACTION_SHIFT,     MAX_NUM_OF_ACTIONS }; using  delta_t  = std ::pair <point2D_t, point2D_t>;void  moveOnGameboard (GameBoard& gb, delta_t  dt_point)   {    auto  didGameboardCollaspeOrShift = bool {false };     auto  actionTaken = COLLASPE_OR_SHIFT_T::ACTION_NONE;     std ::tie(didGameboardCollaspeOrShift, actionTaken) =         collaspedOrShiftedTilesOnGameboardDataArray(gb.gbda, dt_point);     if  (didGameboardCollaspeOrShift)      {         gb.moved = true ;         if  (actionTaken == COLLASPE_OR_SHIFT_T::ACTION_COLLASPE)          {             collaspeTilesOnGameboardDataArray(gb.gbda, dt_point);             const  auto  targetTile = getTileOnGameboard(                 gb.gbda, dt_point.first + dt_point.second);             updateGameBoardStats(gb, targetTile.value);         }         if  (actionTaken == COLLASPE_OR_SHIFT_T::ACTION_SHIFT)          {             shiftTilesOnGameboardDataArray(gb.gbda, dt_point);         }     }     if  (checkRecursiveOffsetInGameBounds(         dt_point, getSizeOfGameboard(gb.gbda)))      {         moveOnGameboard(             gb, std ::make_pair (dt_point.first + dt_point.second, dt_point.second));     } } 
 
moveOnGameboard()用到了几个暂时还未实现的函数,但并不影响我们理解该函数的作用。需要记住,moveOnGameboard()是针对单块砖而言的。其第一个参数是棋盘,第二个参数是个pair<point_2D, point_2D>类型对象(第一个分量代表该本砖块坐标,第二个分量代表所欲移动方向的偏移量)。
一块砖若要能够向指定方向移动一格,要么该方向上相邻的砖块value为0,则可直接移动过去(SHIFT);要么该方向上相邻的砖块value值与本砖块value相当,则可以合二为一(COLLASPE)。
本函数前三分之二篇幅的代码正是检测该砖是否满足上述两种情况之一,并对各情况执行相应行动,而余下三分之一代码:
1 2 3 4 5 6 if  (checkRecursiveOffsetInGameBounds(        dt_point, getSizeOfGameboard(gb.gbda)))  {     moveOnGameboard(         gb, std ::make_pair (dt_point.first + dt_point.second, dt_point.second)); } 
 
如果没有这段代码,我们的游戏逻辑将会有缺陷,举例如下:
1 2 3 4      (0 , 0 ) (0 , 1 ) (0 , 2 ) (0 , 3 ) (1 , 0 ) (1 , 1 ) (1 , 2 ) (1 , 3 ) (2 , 0 ) (2 , 1 ) (2 , 2 ) (2 , 3 ) (3 , 0 ) (3 , 1 ) (3 , 2 ) (3 , 3 ) 
 
假设用户Mike选择4 x 4规格棋盘,开局在坐标(0, 0)处有值2,其余位置value均为0。此时,Mike键入命令,让地砖向右移动。我们期待(0, 0)处的值能最终落位(0, 3)处,但实际其只停留在(0, 1)处。因为我们的moveOnGameboard()判断可以右移后只会移动一格而不再检测能否继续右移。需要补充一个机制:若地砖能向某方向移动,则不能只是走一个,而必须走到底,所谓“不撞南墙不回头”。
以下是checkRecursiveOffsetInGameBounds()定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 bool  checkRecursiveOffsetInGameBounds (delta_t  dt_point, int  boardSize)   {    int  focal_x, focal_y, offset_x, offset_y;     std ::tie(focal_x, focal_y) = dt_point.first.get();     std ::tie(offset_x, offset_y) = dt_point.second.get();     const  auto  positiveDirection = (offset_y + offset_x == 1 );     const  auto  negativeDirection = (offset_y + offset_x == -1 );     const  auto  is_positive_y_direction_flagged = (offset_y == 1 );     const  auto  is_negative_y_direction_flagged = (offset_y == -1 );     const  auto  isInsideOuterBounds =         (positiveDirection &&          (is_positive_y_direction_flagged ? focal_y : focal_x) < boardSize - 2 );     const  auto  isInsideInnerBounds =         (negativeDirection && (is_negative_y_direction_flagged ? focal_y : focal_x) > 1 );          return  (isInsideOuterBounds || isInsideInnerBounds); } 
 
该函数检测一个tile_t对象是否能向指定方向移动,若是,则返回true,于是在moveOnGameboard()里递归地调用moveOnGameboard()让地砖移动下去,直至不可再移。如此一来游戏逻辑便如我们期待般一样。
moveOnGameboard()中所涉及的另几个函数定义如下,功能都相对简单,不多赘述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 using  bool_collaspe_shift_t  = std ::tuple<bool , COLLASPE_OR_SHIFT_T>;bool_collaspe_shift_t collaspedOrShiftedTilesOnGameboardDataArray(gameboard_data_array_t  gbda,                                                 delta_t  dt_point)  {     const  auto  currentTile = getTileOnGameboard(gbda, dt_point.first);     const  auto  targetTile =         getTileOnGameboard(gbda, dt_point.first + dt_point.second);     const  auto  valueExistInTargetPoint = targetTile.value;     const  auto  isValueSameAsTargetValue =         (currentTile.value == targetTile.value);     const  auto  noTilesAreBlocked =         (!currentTile.blocked && !targetTile.blocked);     const  auto  is_there_a_current_value_but_no_target_value =         (currentTile.value && !targetTile.value);     const  auto  doCollapse =         (valueExistInTargetPoint && isValueSameAsTargetValue &&          noTilesAreBlocked);     const  auto  doShift = is_there_a_current_value_but_no_target_value;     const  auto  action_taken = (doCollapse || doShift);     if  (doCollapse)      {         return  std ::make_tuple(action_taken, COLLASPE_OR_SHIFT_T::ACTION_COLLASPE);     }     else  if  (doShift)      {         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  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 = getTileOnGameboard(gbda, dt_point.first);     tile_t  targetTile =         getTileOnGameboard(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 ; } bool  updateGameBoardStats (GameBoard& gb, ull target_tile_value)  {    gb.score += target_tile_value;          gb.largestTile = std ::max(gb.largestTile, target_tile_value);          if  (!hasWonOnGameboard(gb))     {         constexpr  auto  GAME_TILE_WINNING_SCORE = 2048 ;         if  (target_tile_value == GAME_TILE_WINNING_SCORE)         {             gb.win = true ;         }     }     return  true ; } 
 
至此,游戏底层逻辑这一重要部分基本完结。
最后补充一个测试用函数printStateOfGameBoardDataArray(),该函数同样声明定义于gameboard.*内,用于开发过程中监控测试棋盘状态信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 std ::string  printStateOfGameBoardDataArray (gameboard_data_array_t  gbda)   {    const  int  boardSize = getSizeOfGameboard(gbda);     std ::ostringstream  os;     for  (auto  x = 0 ; x < boardSize; x++) {         for  (auto  y = 0 ; y < boardSize; y++) {             const  auto  current_point = point2D_t{ x, y };             os << getTileValueOnGameboardDataArray(gbda, current_point) << ":"                  << getTileBlockedOnGameboardDataArray(gbda, current_point) << "," ;         }         os << "\n" ;     }     return  os.str(); } std ::string  printStateOfGameBoard (GameBoard gb)  {	return  printStateOfGameBoardDataArray(gb.gbda); } 
 
代码 由于现在项目愈加庞大,只展示本阶段新增、更新所涉及的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #ifndef  GAME_INPUT_H #define  GAME_INPUT_H namespace  Game{ 	namespace  Input 	{ 		namespace  Keypress 		{ 			namespace  Code 			{ 				enum  				{ 					CODE_HOTKEY_PREGAME_BACK_TO_MENU = 0 , 				}; 			}  		}  	}  }  #endif   
 
game-pregame.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 #include  "game-pregame.hpp"  #include  "game-graphics.hpp"  #include  "game-input.hpp"  #include  "gameboard.hpp"  #include  "menu.hpp"  #include  "global.hpp"  #include  <array>  #include  <sstream>  #include  <iostream>  #include  <limits>  namespace  Game{ 	namespace  	{ 		enum  PreGameSetupStatusFlag  		{ 			FLAG_NULL, 			FLAG_START_GAME, 			FLAG_RETURN_TO_MAIN_MENU, 			MAX_NO_PREGAME_SETUP_STATUS_FLAGS 		}; 		using  pregameesetup_status_t  = 			std ::array <bool , MAX_NO_PREGAME_SETUP_STATUS_FLAGS>; 		 		pregameesetup_status_t  pregamesetup_status{}; 		enum  class  NewGameFlag  		{ 			NewGameFlagNull, 			NoPreviousSaveAvailable 		}; 		 		bool  noSave{ false  }; 		 		bool  flagInputErroneousChoice{ false  }; 		 		ull storedGameBoardSize{ 1  }; 		int  receiveGameBoardSize (std ::istream& is)  		 {			int  playerInputBoardSize{ 0  }; 			if  (!(is >> playerInputBoardSize)) 			{ 				constexpr  auto  INVALID_INPUT_VALUE_FLAG = -1 ; 				playerInputBoardSize = INVALID_INPUT_VALUE_FLAG; 				is.clear(); 				is.ignore(std ::numeric_limits<std ::streamsize>::max(), '\n' ); 			} 			return  playerInputBoardSize; 		} 		void  receiveInputFromPlayer (std ::istream& is)  		 {			using  namespace  Input::Keypress::Code; 			 			flagInputErroneousChoice = bool { false  }; 			 			 			const  auto  gbsize = receiveGameBoardSize(is); 			const  auto  isValidBoardSize = 				(gbsize >= MIN_GAME_BOARD_PLAY_SIZE) && (gbsize <= MAX_GAME_BOARD_PLAY_SIZE); 			 			if  (isValidBoardSize) 			{ 				storedGameBoardSize = gbsize; 				pregamesetup_status[FLAG_START_GAME] = true ; 			} 			 			bool  goBackToMainMenu{ true  }; 			switch  (gbsize) 			{ 			case  CODE_HOTKEY_PREGAME_BACK_TO_MENU: 				pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true ; 				break ; 			default : 				goBackToMainMenu = false ; 				break ; 			} 			if  (!isValidBoardSize && !goBackToMainMenu) 			{ 				flagInputErroneousChoice = true ; 			} 		} 		void  processPreGame ()  		 {			if  (pregamesetup_status[FLAG_START_GAME]) 			{ 				 				 			} 			if  (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU]) 			{ 				Menu::startMenu(); 			} 		} 		bool  soloLoop ()  		 {			bool  invalidInputValue = flagInputErroneousChoice; 			const  auto  questionAboutGameBoardSizePrompt = [&invalidInputValue]() { 				std ::ostringstream  str_os; 				 				DrawOnlyWhen(str_os, invalidInputValue, Graphics::BoardSizeErrorPrompt); 				DrawAlways(str_os, Graphics::BoardInputPrompt); 				return  str_os.str(); 			}; 			 			pregamesetup_status = pregameesetup_status_t {}; 			clearScreen(); 			DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); 			DrawAsOneTimeFlag(std ::cout , noSave, Graphics::GameBoardNoSaveErrorPrompt); 			DrawAlways(std ::cout , questionAboutGameBoardSizePrompt); 			receiveInputFromPlayer(std ::cin ); 			processPreGame(); 			return  flagInputErroneousChoice; 		} 		void  endlessLoop ()  		 {			while  (soloLoop()) 				; 		} 		void  SetupNewGame (NewGameFlag ns)  		 {			noSave = (ns == NewGameFlag::NoPreviousSaveAvailable) ? true  : false ; 			endlessLoop(); 		} 	}  	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  		 {			SetupNewGame(NewGameFlag::NewGameFlagNull); 		} 	}  }  
 
game-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include  "game-graphics.hpp"  #include  "color.hpp"  #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  BoardSizeErrorPrompt ()            {            const  auto  invalid_prompt_text =              {                 "Invalid input. Gameboard size should range from " ,                 " to " ,                  "."               };                          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  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  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();         } 	}  }  
 
tile.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #ifndef  TILE_H #define  TILE_H #include  "global.hpp"  namespace  Game{ 	struct  tile_t  	{ 		ull value{ 0  }; 		bool  blocked{ false  }; 	}; }  #endif   
 
point2d.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 #ifndef  POINT2D_H #define  POINT2D_H #include  <tuple>  class  point2D_t { 	 	using  point_datatype_t  = typename  std ::tuple<int , int >; 	point_datatype_t  pointVector{ 0 , 0  }; 	explicit  point2D_t (const  point_datatype_t  pt)  		: pointVector { 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)>(pointVector); 	} 	template <PointCoord dimension> 	void  set (int  val)  	 {		std ::get<static_cast <int >(dimension)>(pointVector) = val; 	} 	point_datatype_t  get ()  const  	 {		return  pointVector; 	} 	void  set (point_datatype_t  val)  	 {		pointVector = val; 	} 	void  set (const  int  x, const  int  y)  	 {		set (std ::make_tuple(x, y)); 	} 	point2D_t& operator +=(const  point2D_t& rhs)  	{ 		this ->pointVector = std ::make_tuple( 			get<PointCoord::COORD_X>() + rhs.get<PointCoord::COORD_X>(), 			get<PointCoord::COORD_Y>() + rhs.get<PointCoord::COORD_Y>()); 		return  *this ; 	} 	point2D_t& operator -=(const  point2D_t& rhs)  	{ 		this ->pointVector = std ::make_tuple( 			get<PointCoord::COORD_X>() - rhs.get<PointCoord::COORD_X>(), 			get<PointCoord::COORD_Y>() - rhs.get<PointCoord::COORD_Y>()); 		return  *this ; 	} }; inline  point2D_t operator +(point2D_t lhs, const  point2D_t& rhs) {	lhs += rhs; 	return  lhs; } inline  point2D_t operator -(point2D_t lhs, const  point2D_t& rhs) {	lhs -= rhs; 	return  lhs; } #endif   
 
gameboard.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #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{ false  }; 		bool  moved{ false  }; 		ull score{ 0  }; 		 		ull largestTile{ 2  }; 		long  long  moveCount{ -1  }; 		GameBoard() = default ; 		explicit  GameBoard (ull boardsize)  ; 		explicit  GameBoard (ull boardsize, tile_data_array_t  existboard)  ; 	}; 	 	size_t  getSizeOfGameboard (GameBoard::gameboard_data_array_t  gbda)  ; 	tile_t  getTileOnGameboard (GameBoard::gameboard_data_array_t & gbda, point2D_t pt)  ; 	bool  hasWonOnGameboard (GameBoard& gb)  ; 	long  long  MoveCountOnGameBoard (GameBoard& gb)  ; 	 	void  registerMoveByOneOnGameboard (GameBoard& gb)  ; 	bool  addTileOnGameboard (GameBoard& gb)  ; 	void  unblockTilesOnGameboard (GameBoard& gb)  ; 	bool  canMoveOnGameboard (GameBoard& gb)  ; 	 	void  tumbleTilesUpOnGameboard (GameBoard& gb)  ; 	void  tumbleTilesDownOnGameboard (GameBoard& gb)  ; 	void  tumbleTilesLeftOnGameboard (GameBoard& gb)  ; 	void  tumbleTilesRightOnGameboard (GameBoard& gb)  ; 	 	std ::string  printStateOfGameBoard (GameBoard gb)  ; }  #endif   
 
gameboard.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 #include  "gameboard.hpp"  #include  "tile.hpp"  #include  "point2d.hpp"  #include  <chrono>  #include  <random>  #include  <sstream>  #include  <algorithm>  #include  <array>  namespace  Game{ 	namespace  	{ 		 		using  gameboard_data_array_t  = GameBoard::gameboard_data_array_t ; 		enum  gameboard_data_array_fields  		{ 			IDX_BOARDSIZE, 			IDX_TILE_ARRAY, 			MAX_NO_INDEX 		}; 		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 * getSizeOfGameboard(gbda) + y; 			} 			tile_t  operator () (gameboard_data_array_t & gbda, point2D_t& pt)  const  			 {				return  std ::get<IDX_TILE_ARRAY>(gbda)[point2D_to_1D_index(gbda, pt)]; 			} 			tile_t & operator () (gameboard_data_array_t & gbda, point2D_t& pt)  			 {				return  std ::get<IDX_TILE_ARRAY>(gbda)[point2D_to_1D_index(gbda, pt)]; 			} 		}; 		bool  getTileBlockedOnGameboardDataArray (gameboard_data_array_t  gbda,                                          point2D_t pt)  		 {			return  gameboard_data_point_t {}(gbda, pt).blocked; 		} 		void  setTileOnGameboardDataArray (gameboard_data_array_t & gbda, point2D_t pt,  			tile_t  tile)  		 {			gameboard_data_point_t {}(gbda, pt) = tile; 		} 		void  setTileValueOnGameboardDataArray (gameboard_data_array_t & gbda,  			point2D_t pt, ull value) 		 {			gameboard_data_point_t {}(gbda, pt).value = value; 		} 		ull getTileValueOnGameboardDataArray (gameboard_data_array_t & gbda,   			point2D_t pt)  		 {			return  gameboard_data_point_t {}(gbda, pt).value; 		} 		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_TILE_ARRAY>(gbda))  			{ 				if  (!t.value)  				{ 					freeTiles.push_back(index_counter); 				} 				index_counter++; 			} 			return  freeTiles; 		}  		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_TILE_ARRAY>(gbda).size()); 			std ::transform(std ::begin(std ::get<IDX_TILE_ARRAY>(gbda)), 				std ::end(std ::get<IDX_TILE_ARRAY>(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_BOARDSIZE>(gbda), 										  new_board_data_array }; 		}  		bool  isPointInBoardArea (point2D_t pt, int  boardSize)   		 {			int  x, y; 			std ::tie(x, y) = pt.get(); 			return  !(y < 0  || y > boardSize - 1  || x < 0  || x > boardSize - 1 ); 		} 		bool  canMoveOnGameboardDataArray (gameboard_data_array_t & gbda)   		 {			auto  indexCounter{ 0  }; 			const  auto  canMoveToOffset = [=, &indexCounter, &gbda](const  tile_t  t)  			{ 				const  int  boardSize = getSizeOfGameboard(gbda); 				const  auto  currentPoint = 					point2D_t{ indexCounter / boardSize, indexCounter % boardSize }; 				indexCounter++; 				const  auto  listOfOffsets = { point2D_t{1 , 0 }, point2D_t{0 , 1 } }; 				const  auto  currentPointValue = t.value; 				const  auto  offsetInRangeWithSameValue = [=, &gbda](const  point2D_t offset)  				{ 					const  auto  offsetCheck =  					{ 						currentPoint + offset,		 						currentPoint - offset		 					};	 					for  (const  auto  currentOffset : offsetCheck)  					{ 						if  (isPointInBoardArea(currentOffset, boardSize))  						{ 							return  getTileValueOnGameboardDataArray(gbda, currentOffset) == 								currentPointValue; 						} 					} 					return  false ; 				};  				return  ((currentPointValue == 0u ) || 					std ::any_of(std ::begin(listOfOffsets), std ::end(listOfOffsets), 						offsetInRangeWithSameValue)); 			};  			return  std ::any_of(std ::begin(std ::get<IDX_TILE_ARRAY>(gbda)), 				std ::end(std ::get<IDX_TILE_ARRAY>(gbda)), canMoveToOffset); 		}  		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; 		}; 		bool  addTileOnGameboardDataArray (gameboard_data_array_t & gbda)   		 {			 			constexpr  auto  CHANCE_OF_VALUE_FOUR_OVER_TWO = 75 ; 			const  auto  index_list_of_free_tiles = 				collectFreeTilesOnGameboardDataArray(gbda); 			if  (!index_list_of_free_tiles.size())  			{ 				 				return  true ; 			} 			const  int  boardSize = getSizeOfGameboard(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 / boardSize, rand_selected_index % boardSize }; 			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 ; 		} 		enum  class  COLLASPE_OR_SHIFT_T   		{ 			ACTION_NONE, 			ACTION_COLLASPE, 			ACTION_SHIFT, 			MAX_NUM_OF_ACTIONS 		}; 		 		using  delta_t  = std ::pair <point2D_t, point2D_t>; 		 		using  bool_collaspe_shift_t  = std ::tuple<bool , COLLASPE_OR_SHIFT_T>; 		bool_collaspe_shift_t  		collaspedOrShiftedTilesOnGameboardDataArray(gameboard_data_array_t & gbda, 				delta_t  dt_point)  		{ 			const  auto  currentTile = getTileOnGameboard(gbda, dt_point.first); 			const  auto  targetTile = 				getTileOnGameboard(gbda, dt_point.first + dt_point.second); 			const  auto  valueExistInTargetPoint = targetTile.value; 			const  auto  isValueSameAsTargetValue = 				(currentTile.value == targetTile.value); 			const  auto  noTilesAreBlocked = 				(!currentTile.blocked && !targetTile.blocked); 			const  auto  is_there_a_current_value_but_no_target_value = 				(currentTile.value && !targetTile.value); 			const  auto  doCollapse = 				(valueExistInTargetPoint && isValueSameAsTargetValue && 					noTilesAreBlocked); 			const  auto  doShift = is_there_a_current_value_but_no_target_value; 			const  auto  action_taken = (doCollapse || doShift); 			if  (doCollapse)  			{ 				return  std ::make_tuple(action_taken, COLLASPE_OR_SHIFT_T::ACTION_COLLASPE); 			} 			else  if  (doShift)  			{ 				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  collaspeTilesOnGameboardDataArray (gameboard_data_array_t & gbda,  			delta_t  dt_point)  		 {			tile_t  currentTile = getTileOnGameboard(gbda, dt_point.first); 			tile_t  targetTile = 				getTileOnGameboard(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 = getTileOnGameboard(gbda, dt_point.first); 			tile_t  targetTile = 				getTileOnGameboard(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 ; 		} 		bool  updateGameBoardStats (GameBoard& gb, ull target_tile_value)  		 {			gb.score += target_tile_value; 			 			gb.largestTile = std ::max(gb.largestTile, target_tile_value); 			 			if  (!hasWonOnGameboard(gb)) 			{ 				constexpr  auto  GAME_TILE_WINNING_SCORE = 2048 ; 				if  (target_tile_value == GAME_TILE_WINNING_SCORE) 				{ 					gb.win = true ; 				} 			} 			return  true ; 		} 		bool  checkRecursiveOffsetInGameBounds (delta_t  dt_point, int  boardSize)   		 {			int  focal_x, focal_y, offset_x, offset_y; 			std ::tie(focal_x, focal_y) = dt_point.first.get(); 			std ::tie(offset_x, offset_y) = dt_point.second.get(); 			const  auto  positiveDirection = (offset_y + offset_x == 1 ); 			const  auto  negativeDirection = (offset_y + offset_x == -1 ); 			const  auto  is_positive_y_direction_flagged = (offset_y == 1 ); 			const  auto  is_negative_y_direction_flagged = (offset_y == -1 ); 			const  auto  isInsideOuterBounds = 				(positiveDirection && 					(is_positive_y_direction_flagged ? focal_y : focal_x) < boardSize - 2 ); 			const  auto  isInsideInnerBounds = 				(negativeDirection && (is_negative_y_direction_flagged ? focal_y : focal_x) > 1 ); 			return  (isInsideOuterBounds || isInsideInnerBounds); 		} 		void  moveOnGameboard (GameBoard& gb, delta_t  dt_point)   		 {			auto  didGameboardCollaspeOrShift = bool {false }; 			auto  actionTaken = COLLASPE_OR_SHIFT_T::ACTION_NONE; 			std ::tie(didGameboardCollaspeOrShift, actionTaken) = 				collaspedOrShiftedTilesOnGameboardDataArray(gb.gbda, dt_point); 			if  (didGameboardCollaspeOrShift)  			{ 				gb.moved = true ; 				if  (actionTaken == COLLASPE_OR_SHIFT_T::ACTION_COLLASPE)  				{ 					collaspeTilesOnGameboardDataArray(gb.gbda, dt_point); 					const  auto  targetTile = getTileOnGameboard( 						gb.gbda, dt_point.first + dt_point.second); 					updateGameBoardStats(gb, targetTile.value); 				} 				if  (actionTaken == COLLASPE_OR_SHIFT_T::ACTION_SHIFT)  				{ 					shiftTilesOnGameboardDataArray(gb.gbda, dt_point); 				} 			} 			if  (checkRecursiveOffsetInGameBounds( 				dt_point, getSizeOfGameboard(gb.gbda)))  			{ 				moveOnGameboard( 					gb, std ::make_pair (dt_point.first + dt_point.second, dt_point.second)); 			} 		} 		void  doTumbleTilesUpOnGameboard (GameBoard& gb)   		 {			const  int  boardSize = getSizeOfGameboard(gb.gbda); 			for  (auto  x = 1 ; x < boardSize; x++)  			{ 				auto  y = 0 ; 				while  (y < boardSize)  				{ 					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  })); 					} 					y++; 				} 			} 		} 		void  doTumbleTilesDownOnGameboard (GameBoard& gb)   		 {			const  int  boardSize = getSizeOfGameboard(gb.gbda); 			for  (auto  x = boardSize - 2 ; x >= 0 ; x--)  			{ 				auto  y = 0 ; 				while  (y < boardSize)  				{ 					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  })); 					} 					y++; 				} 			} 		} 		void  doTumbleTilesLeftOnGameboard (GameBoard& gb)  		 {			const  int  boardSize = getSizeOfGameboard(gb.gbda); 			for  (auto  y = 1 ; y < boardSize; y++) 			{ 				auto  x = 0 ; 				while  (x < boardSize) 				{ 					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  })); 					} 					x++; 				} 			} 		} 		void  doTumbleTilesRightOnGameboard (GameBoard& gb)  		 {			const  int  boardSize = getSizeOfGameboard(gb.gbda); 			for  (auto  y = boardSize - 2 ; y >= 0 ; y--) 			{ 				auto  x = 0 ; 				while  (x < boardSize) 				{ 					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  })); 					} 					x++; 				} 			} 		} 		std ::string  printStateOfGameBoardDataArray (gameboard_data_array_t  gbda)   		 {			const  int  boardSize = getSizeOfGameboard(gbda); 			std ::ostringstream  os; 			for  (auto  x = 0 ; x < boardSize; x++) { 				for  (auto  y = 0 ; y < boardSize; y++) { 					const  auto  current_point = point2D_t{ x, y }; 					os << getTileValueOnGameboardDataArray(gbda, current_point) << ":"  						<< getTileBlockedOnGameboardDataArray(gbda, current_point) << "," ; 				} 				os << "\n" ; 			} 			return  os.str(); 		} 	}  	 	GameBoard::GameBoard(ull boardsize) 		: GameBoard{ boardsize, tile_data_array_t (boardsize * boardsize)} 	{ } 	GameBoard::GameBoard(ull boardsize, tile_data_array_t  existboard) 		: gbda{ boardsize, existboard }  	{ } 	 	size_t  getSizeOfGameboard (gameboard_data_array_t  gbda)   	 {		return  std ::get<IDX_BOARDSIZE>(gbda); 	} 	tile_t  getTileOnGameboard (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  registerMoveByOneOnGameboard (GameBoard& gb)  	 {		gb.moveCount++; 		gb.moved = false ; 	} 	bool  addTileOnGameboard (GameBoard& gb)  	 {		return  addTileOnGameboardDataArray(gb.gbda); 	} 	void  unblockTilesOnGameboard (GameBoard& gb)   	 {		gb.gbda = unblockTilesOnGameboardDataArray(gb.gbda); 	} 	bool  canMoveOnGameboard (GameBoard& gb)   	 {		return  canMoveOnGameboardDataArray(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); 	} }  
 
阶段四 构建 解决了一系列游戏底层逻辑相关问题后,现在我们着手完善完整的游戏过程。
game-pregame.cpp
1 2 3 4 5 6 7 8 9 10 11 12 void  processPreGame ()  {    if  (pregamesetup_status[FLAG_START_GAME])     {         playGame(PlayGameFlag::BrandNewGame, GameBoard{ storedGameBoardSize },                  storedGameBoardSize);     }     if  (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU])     {         Menu::startMenu();     } } 
 
终于,可以开始游戏啦!playGame()函数声明于game.hpp。
game.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef  GAME_H #define  GAME_H namespace  Game {     struct  GameBoard ;     enum  class  PlayGameFlag       {          BrandNewGame,          ContinuePreviousGame     };     void  playGame (PlayGameFlag flag, GameBoard gb,          unsigned  long  long  userInput_PlaySize = 1 )  ;    void  startGame ()  ;      };  #endif   
 
接下来实现在game.cpp中定义实现playGame()函数功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include  "game.hpp"  #include  "game-pregame.hpp"  #include  "gameboard.hpp"  #include  "global.hpp"  #include  "statistics.hpp"  #include  <chrono>  namespace  Game{ 	enum  {  COMPETITION_GAME_BOARD_SIZE = 4  }; 	namespace  	{ 		using  competition_mode_t  = bool ; 		GameBoard endlessGameLoop (ull currentBestScore, competition_mode_t  cm,   			GameBoard gb)  		 {			 		} 	}  	 	void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)   	 {		const  auto  isThisNewlyGame = (flag == PlayGameFlag::BrandNewGame); 		const  auto  isCompetitionMode = 			(boardSize == COMPETITION_GAME_BOARD_SIZE); 		const  auto  bestScore = Statistics::loadBestScore(); 		if  (isThisNewlyGame)  		{ 			gb = GameBoard(boardSize); 			addTileOnGameboard(gb); 		} 		const  auto  startTime = std ::chrono::high_resolution_clock::now(); 		gb = endlessGameLoop(bestScore, isCompetitionMode, gb); 		const  auto  finishTime = std ::chrono::high_resolution_clock::now(); 		const  std ::chrono::duration<double > elapsed = finishTime - startTime; 		const  auto  duration = elapsed.count(); 		if  (isThisNewlyGame)  		{ 			 		} 	} 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} }  
 
我们从上至下逐步分析。
1 enum  {  COMPETITION_GAME_BOARD_SIZE = 4  };
 
4 x 4 规格的棋盘是我们的竞技专用规格,我们也只实现该规格棋盘的存储。
1 2 3 4 void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)  {    ... } 
 
我们通过PlayGameFlag变量来区分这是一局新游戏还是继续之前的游戏。
1 const  auto  bestScore = Statistics::loadBestScore();
 
变量bestScore记录当前得分,如果是新游戏当前得分应为0,但若是继续老游戏,则应从本地数据库中加载之前的得分数据。loadBestScore()函数我们稍后实现。
1 2 3 4 5 const  auto  startTime = std ::chrono::high_resolution_clock::now();const  auto  finishTime = std ::chrono::high_resolution_clock::now();const  std ::chrono::duration<double > elapsed = finishTime - startTime;const  auto  duration = elapsed.count();
 
这几行代码负责记录本次游戏花费的时间,endlessGameLoop()负责游戏过程逻辑实现。
在开始构建endlessGameLoop()之前,我们先将完善关于游戏数据相关的部分,实现statistics.*如下:
statistics.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #ifndef  STATISTICS_H #define  STATISTICS_H #include  "global.hpp"  #include  <tuple>  #include  <string>  #include  <iosfwd>  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 loadBestScore ()  ; }  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   
 
statistics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 #include  "statistics.hpp"  #include  <fstream>  namespace  Statistics{     namespace      {         total_game_stats_t  generateStatsFromInputData (std ::istream& is)            {            total_game_stats_t  stats;             is >> stats;             return  stats;         }     }           load_stats_status_t  loadFromFileStatistics (std ::string  filename)        {        std ::ifstream statistics (filename)  ;         if  (statistics)          {             total_game_stats_t  state = generateStatsFromInputData(statistics);             return  load_stats_status_t { true , state };         }         return  load_stats_status_t { false , total_game_stats_t {} };     }     ull loadBestScore ()         {        total_game_stats_t  stats;         bool  stats_file_loaded{ false  };         ull tmpScore{ 0  };         std ::tie(stats_file_loaded, stats) =             loadFromFileStatistics("../data/statistics.txt" );         if  (stats_file_loaded)          {             tmpScore = stats.bestScore;         }         return  tmpScore;     } }  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; } 
 
以上内容都较为简单易懂,不再赘述。
再回过头完善playGame()函数。
1 2 3 4 5 6 7 8 9 10 11 12 void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)   {              if  (isThisNewlyGame)      {         const  auto  finalscore = makeFinalscoreFromGameSession(duration, gb);         doPostGameSaveStuff(finalscore, isCompetitionMode);     } } 
 
这个判断语句游戏结束后执行,玩家完成一局新游戏后,doPostGameSaveStuff()记录玩家姓名、游戏得分得分等数据。
每次游戏时都需记录玩家得分、移动步数、总耗时等基本信息,因此我们需要一个计分板来负责计分事宜。
scoreboard.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef  SCOREBOARD_H #define  SCOREBOARD_H #include  "global.hpp"  #include  <string>  namespace  Scoreboard{ 	struct  Score   	{ 		std ::string  name; 		ull score; 		bool  win; 		ull largestTile; 		long  long  moveCount; 		double  duration; 	}; }  #endif   
 
makeFinalscoreFromGameSession()定义于game.cpp的匿名空间内,使用一个Scoreboard::Score对象记录对局信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 Scoreboard::Score makeFinalscoreFromGameSession (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; } 
 
而doPostGameSaveStuff()则负责询问玩家姓名、记录本轮游戏信息等事宜,该函数同样定义于game.cpp的匿名空间内。
1 2 3 4 5 6 7 8 void  doPostGameSaveStuff (Scoreboard::Score finalscore, competition_mode_t  cm)   {    if  (cm)      {         Statistics::createFinalScoreAndEndGameDataFile(std ::cout , std ::cin ,                                                        finalscore);     } } 
 
可以看到,只有在玩家选择竞技模式,即4 x 4大小情况下,才会调用createFinalScoreAndEndGameDataFile()记录本轮游戏数据。
我们接着前往statistics.cpp查看createFinalScoreAndEndGameDataFile()定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Scoreboard::Graphics::finalscore_display_data_t      makeFinalScoreDisplayData(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; } void  createFinalScoreAndEndGameDataFile (std ::ostream& os, std ::istream& is, Scoreboard::Score finalscore)   {    const  auto  finalscoreDisplayData = makeFinalScoreDisplayData(finalscore);     DrawAlways(os, DataSuppliment(finalscoreDisplayData,                                   Scoreboard::Graphics::EndGameStatisticsPrompt));     DrawAlways(os, Graphics::AskForPlayerNamePrompt);     const  auto  playerName = receiveInputPlayerName(is);     finalscore.name = playerName;     Scoreboard::saveScore(finalscore);     saveEndGameStats(finalscore);     DrawAlways(os, Graphics::MessageScoreSavedPrompt); } 
 
secondsFormat()定义于global.cpp,将游玩时间转化为hh : mm : ss 制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 std ::string  secondsFormat (double  sec)   {	double  second = sec; 	int  minute = second / 60 ; 	int  hour = minute / 60 ; 	second -= minute * 60 ; 	minute %= 60 ; 	second = static_cast <int >(second); 	std ::ostringstream  oss; 	if  (hour)  	{ 		oss << hour << "h " ; 	} 	if  (minute)  	{ 		oss << minute << "m " ; 	} 	oss << second << "s" ; 	return  oss.str(); } 
 
saveScore()函数定于于scoreboard.cpp内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 bool  generateFilefromScoreData (std ::ostream& os, Score score)   {    os << score;     return  true ; } bool  saveToFileScore (std ::string  filename, Score s)   {    std ::ofstream os (filename, std ::ios_base::app)  ;     return  generateFilefromScoreData(os, s); } void  saveScore (Score finalscore)   {    saveToFileScore("../data/scores.txt" , finalscore); } 
 
generateFilefromScoreData()涉及到<<运算符重载,我们稍后实现。
创建scoreboard-graphics.*负责计分板相关图像输出。
scoreboard-graphics.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef  SCOREBOARD_GRAPHICS_H #define  SCOREBOARD_GRAPHICS_H #include  <string>  #include  <tuple>  namespace  Scoreboard{ 	namespace  Graphics 	{ 		 		using  finalscore_display_data_t  = 			std ::tuple<std ::string , std ::string , std ::string , std ::string >; 		std ::string  EndGameStatisticsPrompt (finalscore_display_data_t  finalscore)  ; 	}  }  #endif   
 
scoreboard-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include  "scoreboard-graphics.hpp"  #include  "global.hpp"  #include  "color.hpp"  #include  <sstream>  #include  <iomanip> // std::setw  #include  <array> // std::begin  namespace  Scoreboard{ 	namespace  Graphics 	{         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();         } 	}  }  
 
EndGameStatisticsPrompt()函数返回由玩家本轮游戏各项数据所组成的字符串。
继续分析createFinalScoreAndEndGameDataFile()函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void  createFinalScoreAndEndGameDataFile (std ::ostream& os, std ::istream& is, Scoreboard::Score finalscore)   {                   DrawAlways(os, Graphics::AskForPlayerNamePrompt);     const  auto  playerName = receiveInputPlayerName(is);     finalscore.name = playerName;     Scoreboard::saveScore(finalscore);     saveEndGameStats(finalscore);     DrawAlways(os, Graphics::MessageScoreSavedPrompt); } 
 
展示完本轮游戏各项数据后,请玩家输入其姓名以保存本轮游戏数据,然后将包含玩家姓名在内的数据保存本地。
receiveInputPlayerName()与saveEndGameStats()均定义在statistics.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 std ::string  receiveInputPlayerName (std ::istream& is)   {    std ::string  playerName;     is >> playerName;     return  playerName; } 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); } void  saveEndGameStats (Scoreboard::Score finalscore)   {    total_game_stats_t  stats;               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); } 
 
generateFilefromStatsData()中语句:
 
stats为total_game_stats_t类型对象,需重载<<运算符,同时,从文件读入数据时亦需重载>>运算符,在此一并完成。两重载函数均声明定义于statistics.*。
statistics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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; } 
 
最后提示用户输入姓名的AskForPlayerNamePrompt()函数与显示保存数据成功信息的MessageScoreSavedPrompt()函数均声明定义于statistics-graphics.*。
statistics-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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(); } 
 
至此,关于统计数据存储的部分完结。
代码 global.hpp 1 2 3 4 5 6 7 8 #ifndef  GLOBAL_H #define  GLOBAL_H std ::string  secondsFormat (double  sec)  ;#endif   
 
global.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include  "global.hpp"  #include  <sstream>  std ::string  secondsFormat (double  sec)   {	double  second = sec; 	int  minute = second / 60 ; 	int  hour = minute / 60 ; 	second -= minute * 60 ; 	minute %= 60 ; 	second = static_cast <int >(second); 	std ::ostringstream  oss; 	if  (hour)  	{ 		oss << hour << "h " ; 	} 	if  (minute)  	{ 		oss << minute << "m " ; 	} 	oss << second << "s" ; 	return  oss.str(); } 
 
statistics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #ifndef  STATISTICS_H #define  STATISTICS_H #include  "global.hpp"  #include  <tuple>  #include  <string>  #include  <iosfwd>  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 loadBestScore ()  ; 	void  saveEndGameStats (Scoreboard::Score finalscore)  ; 	void  createFinalScoreAndEndGameDataFile (std ::ostream& os, std ::istream& is,  		Scoreboard::Score finalscore)  ;}  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   
 
statistics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 #include  "statistics.hpp"  #include  "statistics-graphics.hpp"  #include  "scoreboard.hpp"  #include  "scoreboard-graphics.hpp"  #include  <fstream>  namespace  Statistics{     namespace      {         total_game_stats_t  generateStatsFromInputData (std ::istream& is)            {            total_game_stats_t  stats;             is >> stats;             return  stats;         }         Scoreboard::Graphics::finalscore_display_data_t              makeFinalScoreDisplayData(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;         }         std ::string  receiveInputPlayerName (std ::istream& is)            {            std ::string  playerName;             is >> playerName;             return  playerName;         }         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);         }     }           load_stats_status_t  loadFromFileStatistics (std ::string  filename)        {        std ::ifstream statistics (filename)  ;         if  (statistics)          {             total_game_stats_t  state = generateStatsFromInputData(statistics);             return  load_stats_status_t { true , state };         }         return  load_stats_status_t { false , total_game_stats_t {} };     }     ull loadBestScore ()         {        total_game_stats_t  stats;         bool  stats_file_loaded{ false  };         ull tmpScore{ 0  };         std ::tie(stats_file_loaded, stats) =             loadFromFileStatistics("../data/statistics.txt" );         if  (stats_file_loaded)          {             tmpScore = stats.bestScore;         }         return  tmpScore;     }     void  saveEndGameStats (Scoreboard::Score finalscore)        {        total_game_stats_t  stats;                           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  finalscoreDisplayData = makeFinalScoreDisplayData(finalscore);         DrawAlways(os, DataSuppliment(finalscoreDisplayData,             Scoreboard::Graphics::EndGameStatisticsPrompt));         DrawAlways(os, Graphics::AskForPlayerNamePrompt);         const  auto  playerName = receiveInputPlayerName(is);         finalscore.name = playerName;         Scoreboard::saveScore(finalscore);         saveEndGameStats(finalscore);         DrawAlways(os, Graphics::MessageScoreSavedPrompt);     } }  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; } 
 
statistics-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef  SCOREBOARD_GRAPHICS_H #define  SCOREBOARD_GRAPHICS_H #include  <string>  #include  <tuple>  namespace  Scoreboard{ 	namespace  Graphics 	{ 		 		using  finalscore_display_data_t  = 			std ::tuple<std ::string , std ::string , std ::string , std ::string >; 		std ::string  EndGameStatisticsPrompt (finalscore_display_data_t  finalscore)  ; 	}  }  #endif   
 
statistics-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include  "statistics-graphics.hpp"  #include  "color.hpp"  #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(); 		} 	}  }  
 
game-pregame.cpp 1 2 3 4 5 6 7 8 9 10 11 12 void  processPreGame ()  {    if  (pregamesetup_status[FLAG_START_GAME])     {         playGame(PlayGameFlag::BrandNewGame, GameBoard{ storedGameBoardSize },                  storedGameBoardSize);     }     if  (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU])     {         Menu::startMenu();     } } 
 
game.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef  GAME_H #define  GAME_H namespace  Game {     struct  GameBoard ;     enum  class  PlayGameFlag       {          BrandNewGame,          ContinuePreviousGame     };     void  playGame (PlayGameFlag flag, GameBoard gb,          unsigned  long  long  userInput_PlaySize = 1 )  ;    void  startGame ()  ;      };  #endif   
 
game.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #include  "game.hpp"  #include  "game-pregame.hpp"  #include  "gameboard.hpp"  #include  "global.hpp"  #include  "statistics.hpp"  #include  "scoreboard.hpp"  #include  <chrono>  #include  <iostream>  namespace  Game{ 	enum  {  COMPETITION_GAME_BOARD_SIZE = 4  }; 	namespace  	{ 		using  competition_mode_t  = bool ; 		Scoreboard::Score makeFinalscoreFromGameSession (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); 			} 		} 		GameBoard endlessGameLoop (ull currentBestScore, competition_mode_t  cm,   			GameBoard gb)  		 {			 			return  GameBoard{ 3  }; 		} 	}  	 	void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)   	 {		const  auto  isThisNewlyGame = (flag == PlayGameFlag::BrandNewGame); 		const  auto  isCompetitionMode = 			(boardSize == COMPETITION_GAME_BOARD_SIZE); 		const  auto  bestScore = Statistics::loadBestScore(); 		if  (isThisNewlyGame)  		{ 			gb = GameBoard(boardSize); 			addTileOnGameboard(gb); 		} 		const  auto  startTime = std ::chrono::high_resolution_clock::now(); 		gb = endlessGameLoop(bestScore, isCompetitionMode, gb); 		const  auto  finishTime = std ::chrono::high_resolution_clock::now(); 		const  std ::chrono::duration<double > elapsed = finishTime - startTime; 		const  auto  duration = elapsed.count(); 		if  (isThisNewlyGame)  		{ 			const  auto  finalscore = makeFinalscoreFromGameSession(duration, gb); 			doPostGameSaveStuff(finalscore, isCompetitionMode); 		} 	} 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} }  
 
scoreboard.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #ifndef  SCOREBOARD_H #define  SCOREBOARD_H #include  "global.hpp"  #include  <string>  #include  <vector>  namespace  Scoreboard{ 	struct  Score   	{ 		std ::string  name; 		ull score; 		bool  win; 		ull largestTile; 		long  long  moveCount; 		double  duration; 	}; 	using  Scoreboard_t = std ::vector <Score>; 	void  saveScore (Score finalscore)  ; }  std ::istream& operator >>(std ::istream& is, Scoreboard::Score& s);std ::ostream& operator <<(std ::ostream& os, Scoreboard::Score& s);#endif   
 
scoreboard.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include  "scoreboard.hpp"  #include  <fstream>  namespace { 	using  namespace  Scoreboard; 	 	bool  generateFilefromScoreData (std ::ostream& os, Score score)   	 {		os << score; 		return  true ; 	} 	bool  saveToFileScore (std ::string  filename, Score s)   	 {		std ::ofstream os (filename, std ::ios_base::app)  ; 		return  generateFilefromScoreData(os, s); 	} }  namespace  Scoreboard{ 	void  saveScore (Score finalscore)   	 {		saveToFileScore("../data/scores.txt" , finalscore); 	} }  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; } 
 
scoreboard-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef  SCOREBOARD_GRAPHICS_H #define  SCOREBOARD_GRAPHICS_H #include  <string>  #include  <tuple>  namespace  Scoreboard{ 	namespace  Graphics 	{ 		 		using  finalscore_display_data_t  = 			std ::tuple<std ::string , std ::string , std ::string , std ::string >; 		std ::string  EndGameStatisticsPrompt (finalscore_display_data_t  finalscore)  ; 	}  }  #endif   
 
scoreboard-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include  "scoreboard-graphics.hpp"  #include  "global.hpp"  #include  "color.hpp"  #include  <sstream>  #include  <iomanip> // std::setw  #include  <array> // std::begin  namespace  Scoreboard{ 	namespace  Graphics 	{         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();         } 	}  }  
 
阶段五 构建 这一阶段,我们重点实现game-play部分,目标是搭建出一个切实可玩的游戏。
首先丰富game.cpp中endlessGameLoop()函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 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>;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);     }          return  gb; } 
 
endlessGameLoop()将游戏初始各状态信息作为参数传递给soloGameLoop()函数,并通过后者的返回值决定是否继续持续循环下去。
接下来探索soloGameLoop()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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::tuple<bool, current_game_session_t> soloGameLoop(current_game_session_t cgs) {     using  tup_idx = tuple_cgs_t_idx;     const  auto  pGamestatus =         std ::addressof(std ::get<tup_idx::IDX_GAMESTATUS>(cgs));     const  auto  pGameboard = std ::addressof(std ::get<tup_idx::IDX_GAMEBOARD>(cgs));     std ::tie(*pGamestatus, *pGameboard) =         processGameLogic(std ::make_tuple(*pGamestatus, *pGameboard));     DrawAlways(std ::cout , DataSuppliment(cgs, drawGraphics));           } 
 
soloGameLoop()可以说是整个项目的最为核心的函数之一,我们逐步将其完善。
1 2 3 4 5 6 const  auto  pGamestatus =    std ::addressof(std ::get<tup_idx::IDX_GAMESTATUS>(cgs)); const  auto  pGameboard = std ::addressof(std ::get<tup_idx::IDX_GAMEBOARD>(cgs));std ::tie(*pGamestatus, *pGameboard) =    processGameLogic(std ::make_tuple(*pGamestatus, *pGameboard)); 
 
首先,每轮soloGameLoop()循环一开始,我们先判断场上局势如何,以便后续应对。为此,创建函数processGameLogic(),该函数以当前游戏的Gamestatus和Gameboard作为参数,判断目前游戏状态满足哪些条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 using  gamestatus_gameboard_t  = std ::tuple<gamestatus_t , GameBoard>;gamestatus_gameboard_t  processGameLogic (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); } 
 
processGameLogic()根据此时场上局势所满足的条件将gamestatus_t对象的对应标志置位。
知晓场上局势相关信息后,便可呈现游戏画面,该功能由下述语句实现:
1 DrawAlways(std ::cout , DataSuppliment(cgs, drawGraphics)); 
 
接着进一步查看drawGraphics()定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 std ::string  drawGraphics (current_game_session_t  cgs)   {         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  gamestatus = std ::get<tup_idx::IDX_GAMESTATUS>(cgs);     const  auto  gb = std ::get<tup_idx::IDX_GAMEBOARD>(cgs);     std ::ostringstream  str_os;          clearScreen();          DrawAlways(str_os, AsciiArt2048);          const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb);     DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay));          DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput));               DrawOnlyWhen(str_os, gamestatus[FLAG_SAVED_GAME], GameStateNowSavedPrompt);          DrawOnlyWhen(str_os, gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE],                  DataSuppliment(gamestatus, DisplayGameQuestionsToPlayerPrompt));          const  auto  input_controls_display_data =         make_input_controls_display_data(gamestatus);     DrawAlways(str_os, DataSuppliment(input_controls_display_data,                                       GameInputControlsOverlay));          DrawOnlyWhen(str_os, gamestatus[FLAG_INPUT_ERROR],                  InvalidInputGameBoardErrorPrompt);     return  str_os.str(); } 
 
该函数分八步完成游戏各功能模块的呈现,最终效果可参见“项目成果预览——游戏过程”,下面一一介绍之。
前两步clear screen和打印Title Art较为基础,略。
第三步,打印计分板,辅助函数make_scoreboard_display_data()(定义于game.cpp的匿名空间中)用以生成计分板需要用到的数据,而GameScoreBoardOverlay()(定义于game-graphics.cpp中)负责计分板视觉呈现部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Graphics::scoreboard_display_data_t      make_scoreboard_display_data(ull bestScore, competition_mode_t  cm,                                  GameBoard gb)  {     const  auto  gameboardScore = gb.score;     const  auto  tmpBestScore = (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(gameboardScore),                         std ::to_string(tmpBestScore), std ::to_string(movecount));     return  scdd; }; 
 
注意,make_scoreboard_display_data()用到了competition_mode_t类型变量cm,通过该变量判断是否处于竞技模式(即所选择的棋盘是否为4 x 4规格)。非竞技模式下计分板只显示当前得分与移动步数,而竞技模式下额外显示历史最高得分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 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:" ;          enum       {         UI_SCOREBOARD_SIZE = 27 ,         UI_BORDER_OUTER_PADDING = 2 ,         UI_BORDER_INNER_PADDING = 1      };     constexpr  auto  border_padding_char = ' ' ;     constexpr  auto  vertical_border_pattern = "│" ;     constexpr  auto  top_board =         "┌───────────────────────────┐" ;      constexpr  auto  bottom_board =         "└───────────────────────────┘" ;      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(); } 
 
GameScoreBoardOverlay()负责棋盘呈现工作,观其定义知,该函数实际是通过辅助函数GameScoreBoardBox()完成实际的呈现工作。
GameScoreBoardBox()是一个代码量较为庞大的函数,我们逐部分剖析该函数。
(此处强烈建议对照“项目成果展示——游戏过程”图片进行代码阅读与理解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enum  {     UI_SCOREBOARD_SIZE = 27 ,     UI_BORDER_OUTER_PADDING = 2 ,     UI_BORDER_INNER_PADDING = 1  }; 
 
第一个理解难点就是这段注释加代码。
请查看“项目成果展示——游戏过程”图片:我们的计分板左边(left)的竖线与屏幕的最左侧之间(计分板外部,outer)有两个字符的空隙(l-outer = 2),而右侧竖线与计分板外的距离对最终视觉呈现无影响,设其值为零(r-outer = 0);而两竖线间距为27个字符宽度(horizontal_sep = 27);计分板内部(inner)的单词与左侧(left)竖线间距为1个字符宽度(l-inner = 1),而数字与右侧(right)竖线间距同为1个字符宽度(r-inner = 1)。
1 2 3 4 5 6 7 8 9 10 11 12 constexpr  auto  border_padding_char = ' ' ;constexpr  auto  vertical_border_pattern = "│" ;constexpr  auto  top_board =    "┌───────────────────────────┐" ;  constexpr  auto  bottom_board =    "└───────────────────────────┘" ;  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 ); 
 
字符串top_board与bottom_board长度均为29(两竖线各占1个字符宽度,横线占27个字符宽度)。
1 2 3 4 5 6 7 8 9 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" ; 
 
上述规格信息说清楚后,这段代码就十分容易理解:打印计分板最顶端横线与SCORE行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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" ; 
 
这两段如法炮制,无需多言。
解决了计分板,返回drawGraphics(),看看接下来还需要做些什么。
1 2 3 4 5 6 7 8 9 10 std ::string  drawGraphics (current_game_session_t  cgs)   {         ...          DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput));    	... } 
 
有了计分板,接下来就要实现棋盘呈现了。GameBoardTextOutput()负责这方面的事宜。
(此处强烈建议对照“项目成果展示——游戏过程”图片进行代码阅读与理解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 template <size_t  num_of_bars>std::array<std::string, num_of_bars> makePatternedBars(int boardSize)  {     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("└" , "┴" , "┘" ) };          const  auto  generate_x_bar_pattern = [boardSize](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 < boardSize; i++)          {             const  auto  is_not_last_column = (i < boardSize - 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  drawGameBoard (GameBoard::gameboard_data_array_t  gbda)   {    enum       {          TOP_BAR,          XN_BAR,          BASE_BAR,          MAX_TYPES_OF_BARS      };     const  int  boardSize = getSizeOfGameboard(gbda);     const  auto  vertibar = makePatternedBars<MAX_TYPES_OF_BARS>(boardSize);     std ::ostringstream  str_os;     for  (auto  x = 0 ; x < boardSize; x++)      {         const  auto  is_first_row = (x == 0 );         str_os << (is_first_row ?                     std ::get<TOP_BAR>(vertibar) : std ::get<XN_BAR>(vertibar));         for  (auto  y = 0 ; y < boardSize; y++)          {             const  auto  is_first_col = (y == 0 );             const  auto  sp = (is_first_col ? "  "  : " " );             const  auto  tile = getTileOnGameboard(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(); } }  std ::string  GameBoardTextOutput (GameBoard gb)  {    return  drawGameBoard(gb.gbda); } 
 
可以看到,真正负责棋盘图像绘制的是drawGameBoard()函数。
1 2 3 4 5 6 7 8 9 enum  {      TOP_BAR,      XN_BAR,      BASE_BAR,      MAX_TYPES_OF_BARS  }; const  int  boardSize = getSizeOfGameboard(gbda);const  auto  vertibar = makePatternedBars<MAX_TYPES_OF_BARS>(boardSize);
 
我们将棋盘条带分为最顶端条带、最底端条带和中间条带。三种不同条带将会用到的样式不尽相同,列举如下:
1 2 3 ("┌" , "┬" , "┐" )	 ("├" , "┼" , "┤" )  ("└" , "┴" , "┘" )  
 
1 2 const  int  boardSize = getSizeOfGameboard(gbda);const  auto  vertibar = makePatternedBars<MAX_TYPES_OF_BARS>(boardSize);
 
使用变量boardSize获取棋盘尺寸后,将其作为参数,调用makePatternedBars()获得对应尺寸的棋盘边界字符串数组。
makePatternedBars()比较简单,这里不再细说。
drawGameBoard()剩余部分同样只是按既定顺序绘制棋盘,其中用到了函数drawTileString(),该函数定义于tile-graphics.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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]; } 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(); } 
 
通过tileColor()实现了不同值的砖块能有不同的颜色。
1 2 3 4 5 6 7 8 9 10 11 std ::string  drawGraphics (current_game_session_t  cgs)   {         ...      	         DrawOnlyWhen(str_os, gamestatus[FLAG_SAVED_GAME], GameStateNowSavedPrompt);    	... } 
 
第五步为条件绘制,只有当玩家选择保存游戏时,输出提示字符串,告知玩家保存成功。GameStateNowSavedPrompt()定义于game-graphics.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 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(); } 
 
分析略。
1 2 3 4 5 6 7 8 9 10 11 std ::string  drawGraphics (current_game_session_t  cgs)   {         ...          DrawOnlyWhen(str_os, gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE],                      DataSuppliment(gamestatus, DisplayGameQuestionsToPlayerPrompt));    	... } 
 
第六步同为条件绘制,若游戏处于向玩家提问状态(例如玩家胜利时),则打印提示玩家输入字符串。
DisplayGameQuestionsToPlayerPrompt()定义于game.cpp中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Graphics::scoreboard_display_data_t      make_scoreboard_display_data(ull bestScore, competition_mode_t  cm,                                  GameBoard gb)  {     const  auto  gameboardScore = gb.score;     const  auto  tmpBestScore = (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(gameboardScore),                         std ::to_string(tmpBestScore), std ::to_string(movecount));     return  scdd; }; 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(); } 
 
而QuestionEndOfWinningGamePrompt()则定义于game-graphics.cpp中。
1 2 3 4 5 6 7 8 9 10 11 12 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(); } 
 
再次回到drawGraphics()。
1 2 3 4 5 6 7 8 9 10 11 12 13 std ::string  drawGraphics (current_game_session_t  cgs)   {         ...          const  auto  input_controls_display_data =     make_input_controls_display_data(gamestatus);     DrawAlways(str_os, DataSuppliment(input_controls_display_data,                                       GameInputControlsOverlay));    	... } 
 
计分板与棋盘呈现后,我们期待玩家进行输入,开始游玩。需要告诉可用的按键有哪些,分别对应什么功能。GameInputControlsOverlay()负责此事。
game-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 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  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();     };          DrawOnlyWhen(str_os, !is_in_question_mode, InputControlLists);     return  str_os.str(); } 
 
由InputCommandListPrompt()知,我们的游戏既允许玩家使用WASD键进行方向选择,也允许玩家使用HJKL键进行方向选择,甚至还允许玩家使用四个方向键进行方向选择,是时候更新一下game-input.*了。
game-input.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #ifndef  GAME_INPUT_H #define  GAME_INPUT_H #include  <array>  namespace  Game{ 	namespace  Input 	{ 		namespace  Keypress 		{ 			namespace  Code 			{ 				enum  {  CODE_ESC = 27 , CODE_LSQUAREBRACKET = '['  }; 				 				 				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'  				}; 				 				 				enum   				{ 					CODE_WASD_UP = 'W' , 					CODE_WASD_DOWN = 'S' , 					CODE_WASD_LEFT = 'A' , 					CODE_WASD_RIGHT = 'D'  				}; 				 				enum   				{ 					CODE_VIM_UP = 'K' , 					CODE_VIM_DOWN = 'J' , 					CODE_VIM_LEFT = 'H' , 					CODE_VIM_RIGHT = 'L'  				}; 				enum  				{ 					CODE_HOTKEY_PREGAME_BACK_TO_MENU = 0 , 					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' , 				}; 			}  		}  		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)  ; 	}  }  #endif   
 
game-input.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #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 ;         }     }  }  
 
check_input_ansi()用到了getKeypressDownInput(),后者定义于global.cpp中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #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  
 
再次回到 drawGraphics()中。
1 2 3 4 5 6 7 8 9 10 11 std ::string  drawGraphics (current_game_session_t  cgs)   {         ...      	DrawOnlyWhen(str_os, gamestatus[FLAG_INPUT_ERROR], 				InvalidInputGameBoardErrorPrompt);    	... } 
 
最后,当用户输入错误时,提醒用户重新输入。InvalidInputGameBoardErrorPrompt()定义于game-graphics.cpp中。
1 2 3 4 5 6 7 8 9 10 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(); } 
 
至此,drawGraphics()分析完毕,而soloGameLoop()也完成了三分之二左右,由于本阶段drawGraphics()占用大量篇幅,soloGameLoop()剩余部分留至下一阶段完成。
代码 game.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 #include  "game.hpp"  #include  "game-graphics.hpp"  #include  "game-input.hpp"  #include  "game-pregame.hpp"  #include  "gameboard.hpp"  #include  "gameboard-graphics.hpp"  #include  "global.hpp"  #include  "statistics.hpp"  #include  "scoreboard.hpp"  #include  <array>  #include  <chrono>  #include  <iostream>  #include  <sstream>  namespace  Game{ 	namespace  	{ 		 		enum  {  COMPETITION_GAME_BOARD_SIZE = 4  }; 		using  competition_mode_t  = bool ; 		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  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 		}; 		Scoreboard::Score makeFinalscoreFromGameSession (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); 			} 		} 		 		using  gamestatus_gameboard_t  = std ::tuple<gamestatus_t , GameBoard>; 		gamestatus_gameboard_t  processGameLogic (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); 		} 		Graphics::scoreboard_display_data_t  			make_scoreboard_display_data(ull bestScore, competition_mode_t  cm, 				GameBoard gb)  		{ 			const  auto  gameboardScore = gb.score; 			const  auto  tmpBestScore = (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(gameboardScore), 					std ::to_string(tmpBestScore), std ::to_string(movecount)); 			return  scdd; 		}; 		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(); 		} 		 		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  drawGraphics (current_game_session_t  cgs)   		 {			 			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  gamestatus = std ::get<tup_idx::IDX_GAMESTATUS>(cgs); 			const  auto  gb = std ::get<tup_idx::IDX_GAMEBOARD>(cgs); 			std ::ostringstream  str_os; 			 			clearScreen(); 			 			DrawAlways(str_os, AsciiArt2048); 			 			const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb); 			DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay)); 			 			DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput)); 			 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_SAVED_GAME], GameStateNowSavedPrompt); 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE], 				DataSuppliment(gamestatus, DisplayGameQuestionsToPlayerPrompt)); 			 			const  auto  input_controls_display_data = 				make_input_controls_display_data(gamestatus); 			DrawAlways(str_os, DataSuppliment(input_controls_display_data, 				GameInputControlsOverlay)); 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_INPUT_ERROR], 				InvalidInputGameBoardErrorPrompt); 			return  str_os.str(); 		} 		std::tuple<bool, current_game_session_t> soloGameLoop(current_game_session_t cgs) 		{ 			using  namespace  Input; 			using  tup_idx = tuple_cgs_t_idx; 			const  auto  pGamestatus = 				std ::addressof(std ::get<tup_idx::IDX_GAMESTATUS>(cgs)); 			const  auto  pGameboard = std ::addressof(std ::get<tup_idx::IDX_GAMEBOARD>(cgs)); 			std ::tie(*pGamestatus, *pGameboard) = 				processGameLogic(std ::make_tuple(*pGamestatus, *pGameboard)); 			DrawAlways(std ::cout , DataSuppliment(cgs, drawGraphics)); 			 			 			 			 			 			 			 			 			 			return  std ::make_tuple(false , cgs); 		} 		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); 			} 			 			 			return  gb; 		} 	}  	 	void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)   	 {		const  auto  isThisNewlyGame = (flag == PlayGameFlag::BrandNewGame); 		const  auto  isCompetitionMode = 			(boardSize == COMPETITION_GAME_BOARD_SIZE); 		const  auto  bestScore = Statistics::loadBestScore(); 		if  (isThisNewlyGame)  		{ 			gb = GameBoard(boardSize); 			addTileOnGameboard(gb); 		} 		const  auto  startTime = std ::chrono::high_resolution_clock::now(); 		gb = endlessGameLoop(bestScore, isCompetitionMode, gb); 		const  auto  finishTime = std ::chrono::high_resolution_clock::now(); 		const  std ::chrono::duration<double > elapsed = finishTime - startTime; 		const  auto  duration = elapsed.count(); 		if  (isThisNewlyGame)  		{ 			const  auto  finalscore = makeFinalscoreFromGameSession(duration, gb); 			doPostGameSaveStuff(finalscore, isCompetitionMode); 		} 	} 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} }  
 
game-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #ifndef  GAME_GRAPHICS_H #define  GAME_GRAPHICS_H #include  <string>  #include  <tuple>  enum  GameBoardDimensions { 	MIN_GAME_BOARD_PLAY_SIZE = 3 , 	MAX_GAME_BOARD_PLAY_SIZE = 10  }; namespace  Game{ 	namespace  Graphics 	{ 		std ::string  AsciiArt2048 ()  ; 		std ::string  BoardSizeErrorPrompt ()  ; 		std ::string  BoardInputPrompt ()  ; 		std ::string  GameBoardNoSaveErrorPrompt ()  ; 		std ::string  GameStateNowSavedPrompt ()  ; 		std ::string  QuestionEndOfWinningGamePrompt ()  ; 		std ::string  InvalidInputGameBoardErrorPrompt ()  ; 		 		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)  ; 		std ::string  InputCommandListPrompt ()  ; 		std ::string  EndlessModeCommandListPrompt ()  ; 		std ::string  InputCommandListFooterPrompt ()  ; 		using  input_controls_display_data_t  = std ::tuple<bool , bool >; 		std ::string  GameInputControlsOverlay (input_controls_display_data_t  gamestatus)  ; 	}  }  #endif   
 
game-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 #include  "game-graphics.hpp"  #include  "global.hpp"  #include  "color.hpp"  #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  BoardSizeErrorPrompt ()            {            const  auto  invalid_prompt_text =              {                 "Invalid input. Gameboard size should range from " ,                 " to " ,                  "."               };                          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  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  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  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  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  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  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:" ;                          enum               {                 UI_SCOREBOARD_SIZE = 27 ,                 UI_BORDER_OUTER_PADDING = 2 ,                 UI_BORDER_INNER_PADDING = 1              };             constexpr  auto  border_padding_char = ' ' ;             constexpr  auto  vertical_border_pattern = "│" ;             constexpr  auto  top_board =                 "┌───────────────────────────┐" ;              constexpr  auto  bottom_board =                 "└───────────────────────────┘" ;              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  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  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();             };                          DrawOnlyWhen(str_os, !is_in_question_mode, InputControlLists);             return  str_os.str();         } 	}  }  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #ifndef  GAME_INPUT_H #define  GAME_INPUT_H #include  <array>  namespace  Game{ 	namespace  Input 	{ 		namespace  Keypress 		{ 			namespace  Code 			{ 				enum  {  CODE_ESC = 27 , CODE_LSQUAREBRACKET = '['  }; 				 				 				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'  				}; 				 				 				enum   				{ 					CODE_WASD_UP = 'W' , 					CODE_WASD_DOWN = 'S' , 					CODE_WASD_LEFT = 'A' , 					CODE_WASD_RIGHT = 'D'  				}; 				 				enum   				{ 					CODE_VIM_UP = 'K' , 					CODE_VIM_DOWN = 'J' , 					CODE_VIM_LEFT = 'H' , 					CODE_VIM_RIGHT = 'L'  				}; 				enum  				{ 					CODE_HOTKEY_PREGAME_BACK_TO_MENU = 0 , 					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' , 				}; 			}  		}  		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)  ; 	}  }  #endif   
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #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 ;         }     }  }  
 
global.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #ifndef  GLOBAL_H #define  GLOBAL_H #include  <iosfwd>  #include  <string>  using  ull = unsigned  long  long ;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  clearScreen ()  ;void  getKeypressDownInput (char & c)  ;std ::string  secondsFormat (double  sec)  ;#endif   
 
global.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 #include  "global.hpp"  #include  <iostream>  #include  <sstream>  void  clearScreen ()   {#ifdef  _WIN32 	system("cls" ); #else  	system("clear" ); #endif  } #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  std ::string  secondsFormat (double  sec)   {	double  second = sec; 	int  minute = second / 60 ; 	int  hour = minute / 60 ; 	second -= minute * 60 ; 	minute %= 60 ; 	second = static_cast <int >(second); 	std ::ostringstream  oss; 	if  (hour)  	{ 		oss << hour << "h " ; 	} 	if  (minute)  	{ 		oss << minute << "m " ; 	} 	oss << second << "s" ; 	return  oss.str(); } 
 
tile-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef  TILE_GRAPHICS_H #define  TILE_GRAPHICS_H #include  <string>  namespace  Game{ 	struct  tile_t ; 	std ::string  drawTileString (tile_t  currentTile)  ; }  #endif   
 
tile-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include  "tile.hpp"  #include  "tile-graphics.hpp"  #include  "color.hpp"  #include  <iomanip> // setw  #include  <sstream>  #include  <vector>  #include  <cmath>  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]; 		} 	}  	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(); 	} }  
 
阶段六 构建 接着完善soloGameLoop()剩余部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 std::tuple<bool, current_game_session_t> soloGameLoop(current_game_session_t cgs) {     ...              *pGamestatus = update_one_shot_display_flags(*pGamestatus);     intendedmove_t  player_intendedmove{};     std ::tie(player_intendedmove, *pGamestatus) =         receive_agent_input(player_intendedmove, *pGamestatus);     std ::tie(std ::ignore, *pGameboard) = process_agent_input(player_intendedmove, *pGameboard);     bool  loop_again{ false  };     std ::tie(loop_again, *pGamestatus) =         process_gameStatus(std ::make_tuple(*pGamestatus, *pGameboard));     return  std ::make_tuple(loop_again, cgs); } 
 
查看update_one_shot_display_flags()函数定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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]);                  if  (gamestatus[FLAG_SAVED_GAME])          {             disable_one_shot_flag(gamestatus[FLAG_SAVED_GAME]);         }                  if  (gamestatus[FLAG_INPUT_ERROR])          {             disable_one_shot_flag(gamestatus[FLAG_INPUT_ERROR]);         }     }     return  gamestatus; } 
 
要理解update_one_shot_display_flags(),需理解:游戏上一轮进入soloGameLoop(),假设玩家在行动时选择保存游戏或是输入非法值,(仍是在上一轮中)soloGameLoop()后续代码执行游戏存储操作或是提醒玩家重新输入,然后,上一轮soloGameLoop()便结束了。只要soloGameLoop()返回的loop_again值为真,又会开启下一轮循环。在新一轮的循环中,仍是先打印画面,接着读取输入。但必须注意的是,上一轮的一些游戏状态flags,如FLAG_SAVED_GAME或FLAG_INPUT_ERROR,在上轮循环之后并没有复位,因此update_one_shot_display_flags()被创建出来,用以复位上一轮部分标志位。
复位工作完成后,该要读取用户输入了,该工作由receive_agent_input()函数完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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)      {                  char  c;         getKeypressDownInput(c);                  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{ false  };         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); } 
 
先分析receive_agent_input()。
1 2 const  bool  game_still_in_play =        !gamestatus[FLAG_END_GAME] && !gamestatus[FLAG_WIN]; 
 
游戏结束,或是玩家胜利时,都有单独的交互逻辑,需特别处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 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{ false  };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 ; } 
 
玩家的输入按键可分为三类:或是方向移动相关按键类,或是保存等特殊按键类,或是非法按键类。上述代码便是用于判断本轮玩家输入究竟属于哪一类。辅助函数check_input_other()便是用于检测玩家按键是否为特殊按键类。
很好,现在读取了玩家输入,该进行输入处理了。process_agent_input()负责输入处理事宜。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 enum  Directions  {  UP, DOWN, RIGHT, LEFT };GameBoard decideMove (Directions dir, GameBoard gb)    {    switch  (dir)      {         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); } 
 
分析略。
现在进入soloGameLoop()最后一个阶段:根据本轮loop玩家行动后的场上局势,判断是否应开启下一轮循环。process_gameStatus()负责进行判断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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])      {                  loop_again = false ;     }     if  (gamestatus[FLAG_SAVED_GAME])      {              }          gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE] = false ;     return  std ::make_tuple(loop_again, gamestatus); } 
 
关于游戏保存的实现留到以后,现在,让我们回到endlessGameLoop()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; } 
 
while循环的条件不再满足时,便是游戏结束时,需由drawEndGameLoopGraphics()打印游戏结束字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 std ::string  drawEndGameLoopGraphics (current_game_session_t  finalgamestatus)   {         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;          clearScreen();          DrawAlways(str_os, AsciiArt2048);          const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb);     DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay));          DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput));          const  auto  esdd = make_end_screen_display_data(end_gamestatus);     DrawAlways(str_os, DataSuppliment(esdd, GameEndScreenOverlay));     return  str_os.str(); } 
 
游戏结束后,清屏,重新打印Title Art、计分板、棋盘以及游戏结束语。涉及的函数列举如下:
game.cpp
1 2 3 4 5 6 7 8 9   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; }; 
 
game-graphics.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 std ::string  GameOverWinPrompt ()   {    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  GameOverLosePrompt ()   {    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  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, GameOverWinPrompt);                  DrawOnlyWhen(str_os, !did_win, GameOverLosePrompt);         return  str_os.str();     };     DrawOnlyWhen(str_os, !is_endless_mode, standardWinLosePrompt);          DrawOnlyWhen(str_os, is_endless_mode, EndOfEndlessPrompt);     return  str_os.str(); } 
 
至此,游戏游玩的核心部分已基本完成,我们的游戏已能够顺利游玩。
(本游戏虽在windows与unix环境下均可运行,但实测windows环境下方向键输入有问题,且按键输入时必须手动回车才能读入,以上问题留到游戏整体完成后再做调整)
现在,考虑完善游戏保存功能。文件saveresource.*负责数据保存工作。
saveresource.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef  SAVERESOURCE_H #define  SAVERESOURCE_H #include  <string>  #include  <tuple>  namespace  Game { 	struct  GameBoard ; 	namespace  Saver  	{ 		void  saveGamePlayState (GameBoard gb)  ; 	}  }  #endif  
 
saveresource.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include  "saveresource.hpp"  #include  "gameboard.hpp"  #include  <fstream>  namespace  Game{ 	namespace  Saver 	{ 		namespace  		{ 			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); 			} 			bool  generateFilefromPreviousGameStatisticsData (std ::ostream& os,  				const  GameBoard& gb)  			 {				os << gb.score << ":"  << MoveCountOnGameBoard(gb); 				return  true ; 			} 			void  saveToFilePreviousGameStatisticsData (std ::string  filename,  				const  GameBoard& gb)  			 {				std ::ofstream stats (filename, std ::ios_base::app)  ; 				generateFilefromPreviousGameStatisticsData(stats, gb); 			} 		}  	}  	void  saveGamePlayState (GameBoard gb)   	 {		constexpr  auto  filename_game_data_state = "../data/previousGame.txt" ; 		constexpr  auto  filename_game_data_statistics = "../data/previousGameStats.txt" ; 		std ::remove(filename_game_data_state); 		std ::remove(filename_game_data_statistics); 		saveToFilePreviousGameStateData(filename_game_data_state, gb); 		saveToFilePreviousGameStatisticsData(filename_game_data_statistics, gb); 	} }  
 
加上这两个文件后,编译会报错,原因是generateFilefromPreviousGameStatisticsData()的参数类型为const GameBoard&,而
MoveCountOnGameBoard()函数参数为GameBoard&,只需将MoveCountOnGameBoard()声明与定义均加上const即可。
数据存储实现了,数据加载自然也不能落下。loadresource.*负责数据加载模块。
loadresource.hpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #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)  ; 		 		 		std ::tuple<bool , std ::tuple<unsigned  long  long , long  long >> 			load_game_stats_from_file(std ::string  filename); 	}  }  #endif   
 
loadresource与saveresource镜像。
loadresource.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 #include  "loadresource.hpp"  #include  "global.hpp"  #include  "tile.hpp"  #include  <fstream>  #include  <array>  #include  <string>  #include  <sstream>  #include  <vector>  #include  <iostream>  #include  <algorithm>  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++)                      {                         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;             }                          std ::tuple<bool , std ::tuple<ull, long  long >>                 get_and_process_game_stats_string_data(std ::istream& stats_file)              {                 if  (stats_file)                  {                     ull 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 :                                                                  break ;                             }                         }                     }                      return  std ::make_tuple(true , std ::make_tuple(score, moveCount));                 }                 return  std ::make_tuple(false , std ::make_tuple(0 , 0 ));             } 		}          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{});         } 		 		std ::tuple<bool , std ::tuple<ull, long  long >> 			load_game_stats_from_file(std ::string  filename)  		{ 			std ::ifstream stats (filename)  ; 			return  get_and_process_game_stats_string_data(stats); 		} 	}  }  
 
介绍略。
现在,游戏数据的存储与加载均实现了,可以开始完善continue game功能了。
menu.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 void  continueGame ()   {    Game::continueGame(); } void  processPlayerInput ()   {    if  (menustatus[FLAG_START_GAME])     {         startGame();     }     if  (menustatus[FLAG_CONTINUE_GAME])     {         continueGame();     }     if  (menustatus[FLAG_DISPLAY_HIGHSCORES])     {              }     if  (menustatus[FLAG_EXIT_GAME])     {         exit (EXIT_SUCCESS);     } } 
 
game.cpp
1 2 3 4 void  continueGame ()   {    PreGameSetup::ContinueOldGame(); } 
 
game-pregame.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 load_gameboard_status_t  initialiseContinueBoardArray ()   {    using  namespace  Loader;     constexpr  auto  gameboard_data_filename = "../data/previousGame.txt" ;     constexpr  auto  game_stats_data_filename = "../data/previousGameStats.txt" ;     auto  loaded_gameboard{ false  };     auto  loaded_game_stats{ false  };     auto  tempGBoard = GameBoard{ 1  };               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{ false  };     GameBoard oldGameBoard;     std ::tie(load_old_game_ok, oldGameBoard) = initialiseContinueBoardArray();     if  (load_old_game_ok)      {         playGame(PlayGameFlag::ContinuePreviousGame, oldGameBoard);     }     else       {         SetupNewGame(NewGameFlag::NoPreviousSaveAvailable);     } } void  ContinueOldGame ()   {    DoContinueOldGame(); } 
 
至此,old game的加载也已实现,阶段六告一段落。
代码 game.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 #include  "game.hpp"  #include  "game-graphics.hpp"  #include  "game-input.hpp"  #include  "game-pregame.hpp"  #include  "gameboard.hpp"  #include  "gameboard-graphics.hpp"  #include  "global.hpp"  #include  "saveresource.hpp"  #include  "loadresource.hpp"  #include  "statistics.hpp"  #include  "scoreboard.hpp"  #include  <array>  #include  <chrono>  #include  <iostream>  #include  <sstream>  namespace  Game{ 	namespace  	{ 		 		enum  {  COMPETITION_GAME_BOARD_SIZE = 4  }; 		using  competition_mode_t  = bool ; 		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  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 		}; 		Scoreboard::Score makeFinalscoreFromGameSession (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); 			} 		} 		 		using  gamestatus_gameboard_t  = std ::tuple<gamestatus_t , GameBoard>; 		gamestatus_gameboard_t  processGameLogic (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); 		} 		Graphics::scoreboard_display_data_t  			make_scoreboard_display_data(ull bestScore, competition_mode_t  cm, 				GameBoard gb)  		{ 			const  auto  gameboardScore = gb.score; 			const  auto  tmpBestScore = (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(gameboardScore), 					std ::to_string(tmpBestScore), std ::to_string(movecount)); 			return  scdd; 		}; 		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(); 		} 		 		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  drawGraphics (current_game_session_t  cgs)   		 {			 			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  gamestatus = std ::get<tup_idx::IDX_GAMESTATUS>(cgs); 			const  auto  gb = std ::get<tup_idx::IDX_GAMEBOARD>(cgs); 			std ::ostringstream  str_os; 			 			clearScreen(); 			 			DrawAlways(str_os, AsciiArt2048); 			 			const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb); 			DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay)); 			 			DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput)); 			 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_SAVED_GAME], GameStateNowSavedPrompt); 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE], 				DataSuppliment(gamestatus, DisplayGameQuestionsToPlayerPrompt)); 			 			const  auto  input_controls_display_data = 				make_input_controls_display_data(gamestatus); 			DrawAlways(str_os, DataSuppliment(input_controls_display_data, 				GameInputControlsOverlay)); 			 			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]); 				 				if  (gamestatus[FLAG_SAVED_GAME])  				{ 					disable_one_shot_flag(gamestatus[FLAG_SAVED_GAME]); 				} 				 				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)  			{ 				 				char  c; 				getKeypressDownInput(c); 				 				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{ false  }; 				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); 		} 		enum  Directions  {  UP, DOWN, RIGHT, LEFT }; 		GameBoard decideMove (Directions dir, GameBoard gb)    		 {			switch  (dir)  			{ 			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])  			{ 				 				loop_again = false ; 			} 			if  (gamestatus[FLAG_SAVED_GAME])  			{ 				Saver::saveGamePlayState(gb); 			} 			 			gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE] = false ; 			return  std ::make_tuple(loop_again, gamestatus); 		} 		 		std::tuple<bool, current_game_session_t> soloGameLoop(current_game_session_t cgs) 		{ 			using  namespace  Input; 			using  tup_idx = tuple_cgs_t_idx; 			const  auto  pGamestatus = 				std ::addressof(std ::get<tup_idx::IDX_GAMESTATUS>(cgs)); 			const  auto  pGameboard = std ::addressof(std ::get<tup_idx::IDX_GAMEBOARD>(cgs)); 			std ::tie(*pGamestatus, *pGameboard) = 				processGameLogic(std ::make_tuple(*pGamestatus, *pGameboard)); 			DrawAlways(std ::cout , DataSuppliment(cgs, drawGraphics)); 			*pGamestatus = update_one_shot_display_flags(*pGamestatus); 			intendedmove_t  player_intendedmove{}; 			std ::tie(player_intendedmove, *pGamestatus) = 				receive_agent_input(player_intendedmove, *pGamestatus); 			std ::tie(std ::ignore, *pGameboard) = process_agent_input(player_intendedmove, *pGameboard); 			bool  loop_again{ false  }; 			std ::tie(loop_again, *pGamestatus) = 				process_gameStatus(std ::make_tuple(*pGamestatus, *pGameboard)); 			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)  		 {			 			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; 			 			clearScreen(); 			 			DrawAlways(str_os, AsciiArt2048); 			 			const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb); 			DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay)); 			 			DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput)); 			 			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; 		} 	}  	 	void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)   	 {		const  auto  isThisNewlyGame = (flag == PlayGameFlag::BrandNewGame); 		const  auto  isCompetitionMode = 			(boardSize == COMPETITION_GAME_BOARD_SIZE); 		const  auto  bestScore = Statistics::loadBestScore(); 		if  (isThisNewlyGame)  		{ 			gb = GameBoard(boardSize); 			addTileOnGameboard(gb); 		} 		const  auto  startTime = std ::chrono::high_resolution_clock::now(); 		gb = endlessGameLoop(bestScore, isCompetitionMode, gb); 		const  auto  finishTime = std ::chrono::high_resolution_clock::now(); 		const  std ::chrono::duration<double > elapsed = finishTime - startTime; 		const  auto  duration = elapsed.count(); 		if  (isThisNewlyGame)  		{ 			const  auto  finalscore = makeFinalscoreFromGameSession(duration, gb); 			doPostGameSaveStuff(finalscore, isCompetitionMode); 		} 	} 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} 	void  continueGame ()   	 {		PreGameSetup::ContinueOldGame(); 	} }  
 
game-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #ifndef  GAME_GRAPHICS_H #define  GAME_GRAPHICS_H #include  <string>  #include  <tuple>  enum  GameBoardDimensions { 	MIN_GAME_BOARD_PLAY_SIZE = 3 , 	MAX_GAME_BOARD_PLAY_SIZE = 10  }; namespace  Game{ 	namespace  Graphics 	{ 		std ::string  AsciiArt2048 ()  ; 		std ::string  BoardSizeErrorPrompt ()  ; 		std ::string  BoardInputPrompt ()  ; 		std ::string  GameBoardNoSaveErrorPrompt ()  ; 		std ::string  GameStateNowSavedPrompt ()  ; 		std ::string  QuestionEndOfWinningGamePrompt ()  ; 		std ::string  InvalidInputGameBoardErrorPrompt ()  ; 		 		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)  ; 		std ::string  InputCommandListPrompt ()  ; 		std ::string  EndlessModeCommandListPrompt ()  ; 		std ::string  InputCommandListFooterPrompt ()  ; 		using  input_controls_display_data_t  = std ::tuple<bool , bool >; 		std ::string  GameInputControlsOverlay (input_controls_display_data_t  gamestatus)  ; 		std ::string  GameOverWinPrompt ()  ; 		std ::string  GameOverLosePrompt ()  ; 		std ::string  EndOfEndlessPrompt ()  ; 		using  end_screen_display_data_t  = std ::tuple<bool , bool >; 		std ::string  GameEndScreenOverlay (end_screen_display_data_t  esdd)  ; 	}  }  #endif   
 
game-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 #include  "game-graphics.hpp"  #include  "global.hpp"  #include  "color.hpp"  #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  BoardSizeErrorPrompt ()            {            const  auto  invalid_prompt_text =              {                 "Invalid input. Gameboard size should range from " ,                 " to " ,                  "."               };                          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  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  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  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  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  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  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:" ;                          enum               {                 UI_SCOREBOARD_SIZE = 27 ,                 UI_BORDER_OUTER_PADDING = 2 ,                 UI_BORDER_INNER_PADDING = 1              };             constexpr  auto  border_padding_char = ' ' ;             constexpr  auto  vertical_border_pattern = "│" ;             constexpr  auto  top_board =                 "┌───────────────────────────┐" ;              constexpr  auto  bottom_board =                 "└───────────────────────────┘" ;              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  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  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();             };                          DrawOnlyWhen(str_os, !is_in_question_mode, InputControlLists);             return  str_os.str();         }         std ::string  GameOverWinPrompt ()            {            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  GameOverLosePrompt ()            {            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  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, GameOverWinPrompt);                                  DrawOnlyWhen(str_os, !did_win, GameOverLosePrompt);                 return  str_os.str();             };             DrawOnlyWhen(str_os, !is_endless_mode, standardWinLosePrompt);                          DrawOnlyWhen(str_os, is_endless_mode, EndOfEndlessPrompt);             return  str_os.str();         } 	}  }  
 
saveresource.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifndef  SAVERESOURCE_H #define  SAVERESOURCE_H #include  <string>  #include  <tuple>  namespace  Game { 	struct  GameBoard ; 	namespace  Saver  	{ 		void  saveGamePlayState (GameBoard gb)  ; 	}  }  #endif   
 
saveresource.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include  "saveresource.hpp"  #include  "gameboard.hpp"  #include  <fstream>  namespace  Game{ 	namespace  Saver 	{ 		namespace  		{ 			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); 			} 			bool  generateFilefromPreviousGameStatisticsData (std ::ostream& os,  				const  GameBoard& gb)  			 {				os << gb.score << ":"  << MoveCountOnGameBoard(gb); 				return  true ; 			} 			void  saveToFilePreviousGameStatisticsData (std ::string  filename,  				const  GameBoard& gb)  			 {				std ::ofstream stats (filename, std ::ios_base::app)  ; 				generateFilefromPreviousGameStatisticsData(stats, gb); 			} 		}  		void  saveGamePlayState (GameBoard gb)  		 {			constexpr  auto  filename_game_data_state = "../data/previousGame.txt" ; 			constexpr  auto  filename_game_data_statistics = "../data/previousGameStats.txt" ; 			std ::remove(filename_game_data_state); 			std ::remove(filename_game_data_statistics); 			saveToFilePreviousGameStateData(filename_game_data_state, gb); 			saveToFilePreviousGameStatisticsData(filename_game_data_statistics, gb); 		} 	}  }  
 
loadresource.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #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)  ; 		 		 		std ::tuple<bool , std ::tuple<unsigned  long  long , long  long >> 			load_game_stats_from_file(std ::string  filename); 	}  }  #endif   
 
loadresource.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 #include  "loadresource.hpp"  #include  "global.hpp"  #include  "gameboard.hpp"  #include  "tile.hpp"  #include  <fstream>  #include  <array>  #include  <string>  #include  <sstream>  #include  <vector>  #include  <iostream>  #include  <algorithm>  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++)                      {                         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;             }                          std ::tuple<bool , std ::tuple<ull, long  long >>                 get_and_process_game_stats_string_data(std ::istream& stats_file)              {                 if  (stats_file)                  {                     ull 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 :                                                                  break ;                             }                         }                     }                      return  std ::make_tuple(true , std ::make_tuple(score, moveCount));                 }                 return  std ::make_tuple(false , std ::make_tuple(0 , 0 ));             } 		}          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{});         } 		 		std ::tuple<bool , std ::tuple<ull, long  long >> 			load_game_stats_from_file(std ::string  filename)  		{ 			std ::ifstream stats (filename)  ; 			return  get_and_process_game_stats_string_data(stats); 		} 	}  }  
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #include  "menu.hpp"  #include  "menu-graphics.hpp"  #include  "game.hpp"  #include  "global.hpp"  #include  "game-graphics.hpp"  #include  <array>  #include  <iostream>  namespace { 	enum  MenuStatusFlag   	{ 		FLAG_NULL, 		FLAG_START_GAME, 		FLAG_CONTINUE_GAME, 		FLAG_DISPLAY_HIGHSCORES, 		FLAG_EXIT_GAME, 		MAX_NO_MAIN_MENU_STATUS_FLAGS 	}; 	using  menuStatus_t = std ::array <bool , MAX_NO_MAIN_MENU_STATUS_FLAGS>; 	 	menuStatus_t menustatus{}; 	 	bool  flagInputErroneousChoice{ false  }; 	void  startGame ()  	 {		Game::startGame(); 	} 	void  continueGame ()   	 {		Game::continueGame(); 	} 	void  receiveInputFromPlayer (std ::istream& in_os)   	 {		 		flagInputErroneousChoice = bool {}; 		char  c; 		in_os >> c; 		switch  (c)  		{ 		case  '1' : 			menustatus[FLAG_START_GAME] = true ; 			break ; 		case  '2' : 			menustatus[FLAG_CONTINUE_GAME] = true ; 			break ; 		case  '3' : 			menustatus[FLAG_DISPLAY_HIGHSCORES] = true ; 			break ; 		case  '4' : 			menustatus[FLAG_EXIT_GAME] = true ; 			break ; 		default : 			flagInputErroneousChoice = true ; 			break ; 		} 	} 	void  processPlayerInput ()   	 {		if  (menustatus[FLAG_START_GAME]) 		{ 			startGame(); 		} 		if  (menustatus[FLAG_CONTINUE_GAME]) 		{ 			continueGame(); 		} 		if  (menustatus[FLAG_DISPLAY_HIGHSCORES]) 		{ 			 		} 		if  (menustatus[FLAG_EXIT_GAME]) 		{ 			exit (EXIT_SUCCESS); 		} 	} 	bool  soloLoop ()  	 {		 		menustatus = menuStatus_t{}; 		clearScreen(); 		DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); 		DrawAlways(std ::cout , 			DataSuppliment(flagInputErroneousChoice, 				Game::Graphics::Menu::MenuGraphicsOverlay)); 		receiveInputFromPlayer(std ::cin ); 		processPlayerInput(); 		return  flagInputErroneousChoice; 	} 	void  endlessLoop ()   	 {		while  (soloLoop()) 			; 	} }  namespace  Menu{ 	void  startMenu ()   	 {		endlessLoop(); 	} }  
 
game.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #ifndef  GAME_H #define  GAME_H namespace  Game {     struct  GameBoard ;     enum  class  PlayGameFlag       {          BrandNewGame,          ContinuePreviousGame     };     void  playGame (PlayGameFlag flag, GameBoard gb,               unsigned  long  long  boardSize = 1 )  ;    void  startGame ()  ;     void  continueGame ()  ; };  #endif   
 
game.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 #include  "game.hpp"  #include  "game-graphics.hpp"  #include  "game-input.hpp"  #include  "game-pregame.hpp"  #include  "gameboard.hpp"  #include  "gameboard-graphics.hpp"  #include  "global.hpp"  #include  "saveresource.hpp"  #include  "loadresource.hpp"  #include  "statistics.hpp"  #include  "scoreboard.hpp"  #include  <array>  #include  <chrono>  #include  <iostream>  #include  <sstream>  namespace  Game{ 	namespace  	{ 		 		enum  {  COMPETITION_GAME_BOARD_SIZE = 4  }; 		using  competition_mode_t  = bool ; 		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  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 		}; 		Scoreboard::Score makeFinalscoreFromGameSession (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); 			} 		} 		 		using  gamestatus_gameboard_t  = std ::tuple<gamestatus_t , GameBoard>; 		gamestatus_gameboard_t  processGameLogic (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); 		} 		Graphics::scoreboard_display_data_t  			make_scoreboard_display_data(ull bestScore, competition_mode_t  cm, 				GameBoard gb)  		{ 			const  auto  gameboardScore = gb.score; 			const  auto  tmpBestScore = (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(gameboardScore), 					std ::to_string(tmpBestScore), std ::to_string(movecount)); 			return  scdd; 		}; 		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(); 		} 		 		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  drawGraphics (current_game_session_t  cgs)   		 {			 			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  gamestatus = std ::get<tup_idx::IDX_GAMESTATUS>(cgs); 			const  auto  gb = std ::get<tup_idx::IDX_GAMEBOARD>(cgs); 			std ::ostringstream  str_os; 			 			clearScreen(); 			 			DrawAlways(str_os, AsciiArt2048); 			 			const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb); 			DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay)); 			 			DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput)); 			 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_SAVED_GAME], GameStateNowSavedPrompt); 			 			DrawOnlyWhen(str_os, gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE], 				DataSuppliment(gamestatus, DisplayGameQuestionsToPlayerPrompt)); 			 			const  auto  input_controls_display_data = 				make_input_controls_display_data(gamestatus); 			DrawAlways(str_os, DataSuppliment(input_controls_display_data, 				GameInputControlsOverlay)); 			 			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]); 				 				if  (gamestatus[FLAG_SAVED_GAME])  				{ 					disable_one_shot_flag(gamestatus[FLAG_SAVED_GAME]); 				} 				 				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)  			{ 				 				char  c; 				getKeypressDownInput(c); 				 				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{ false  }; 				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); 		} 		enum  Directions  {  UP, DOWN, RIGHT, LEFT }; 		GameBoard decideMove (Directions dir, GameBoard gb)    		 {			switch  (dir)  			{ 			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])  			{ 				 				loop_again = false ; 			} 			if  (gamestatus[FLAG_SAVED_GAME])  			{ 				Saver::saveGamePlayState(gb); 			} 			 			gamestatus[FLAG_GAME_IS_ASKING_QUESTION_MODE] = false ; 			return  std ::make_tuple(loop_again, gamestatus); 		} 		 		std::tuple<bool, current_game_session_t> soloGameLoop(current_game_session_t cgs) 		{ 			using  namespace  Input; 			using  tup_idx = tuple_cgs_t_idx; 			const  auto  pGamestatus = 				std ::addressof(std ::get<tup_idx::IDX_GAMESTATUS>(cgs)); 			const  auto  pGameboard = std ::addressof(std ::get<tup_idx::IDX_GAMEBOARD>(cgs)); 			std ::tie(*pGamestatus, *pGameboard) = 				processGameLogic(std ::make_tuple(*pGamestatus, *pGameboard)); 			DrawAlways(std ::cout , DataSuppliment(cgs, drawGraphics)); 			*pGamestatus = update_one_shot_display_flags(*pGamestatus); 			intendedmove_t  player_intendedmove{}; 			std ::tie(player_intendedmove, *pGamestatus) = 				receive_agent_input(player_intendedmove, *pGamestatus); 			std ::tie(std ::ignore, *pGameboard) = process_agent_input(player_intendedmove, *pGameboard); 			bool  loop_again{ false  }; 			std ::tie(loop_again, *pGamestatus) = 				process_gameStatus(std ::make_tuple(*pGamestatus, *pGameboard)); 			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)  		 {			 			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; 			 			clearScreen(); 			 			DrawAlways(str_os, AsciiArt2048); 			 			const  auto  scdd = make_scoreboard_display_data(bestScore, comp_mode, gb); 			DrawAlways(str_os, DataSuppliment(scdd, GameScoreBoardOverlay)); 			 			DrawAlways(str_os, DataSuppliment(gb, GameBoardTextOutput)); 			 			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; 		} 	}  	 	void  playGame (PlayGameFlag flag, GameBoard gb, ull boardSize)   	 {		const  auto  isThisNewlyGame = (flag == PlayGameFlag::BrandNewGame); 		const  auto  isCompetitionMode = 			(boardSize == COMPETITION_GAME_BOARD_SIZE); 		const  auto  bestScore = Statistics::loadBestScore(); 		if  (isThisNewlyGame)  		{ 			gb = GameBoard(boardSize); 			addTileOnGameboard(gb); 		} 		const  auto  startTime = std ::chrono::high_resolution_clock::now(); 		gb = endlessGameLoop(bestScore, isCompetitionMode, gb); 		const  auto  finishTime = std ::chrono::high_resolution_clock::now(); 		const  std ::chrono::duration<double > elapsed = finishTime - startTime; 		const  auto  duration = elapsed.count(); 		if  (isThisNewlyGame)  		{ 			const  auto  finalscore = makeFinalscoreFromGameSession(duration, gb); 			doPostGameSaveStuff(finalscore, isCompetitionMode); 		} 	} 	void  startGame ()  	 {		PreGameSetup::SetupNewGame(); 	} 	void  continueGame ()   	 {		PreGameSetup::ContinueOldGame(); 	} }  
 
game-pregame.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #ifndef  GAME_PREGAME_H #define  GAME_PREGAME_H namespace  Game{ 	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  ; 		void  ContinueOldGame ()  ; 	}  }  #endif   
 
game-pregame.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 #include  "game-pregame.hpp"  #include  "game-graphics.hpp"  #include  "game-input.hpp"  #include  "gameboard.hpp"  #include  "game.hpp"  #include  "menu.hpp"  #include  "global.hpp"  #include  "loadresource.hpp"  #include  <array>  #include  <sstream>  #include  <iostream>  #include  <limits>  namespace  Game{ 	namespace  	{ 		enum  PreGameSetupStatusFlag  		{ 			FLAG_NULL, 			FLAG_START_GAME, 			FLAG_RETURN_TO_MAIN_MENU, 			MAX_NO_PREGAME_SETUP_STATUS_FLAGS 		}; 		using  pregameesetup_status_t  = 			std ::array <bool , MAX_NO_PREGAME_SETUP_STATUS_FLAGS>; 		 		pregameesetup_status_t  pregamesetup_status{}; 		enum  class  NewGameFlag  		{ 			NewGameFlagNull, 			NoPreviousSaveAvailable 		}; 		 		bool  noSave{ false  }; 		 		bool  flagInputErroneousChoice{ false  }; 		 		ull storedGameBoardSize{ 1  }; 		int  receiveGameBoardSize (std ::istream& is)  		 {			int  playerInputBoardSize{ 0  }; 			if  (!(is >> playerInputBoardSize)) 			{ 				constexpr  auto  INVALID_INPUT_VALUE_FLAG = -1 ; 				playerInputBoardSize = INVALID_INPUT_VALUE_FLAG; 				is.clear(); 				is.ignore(std ::numeric_limits<std ::streamsize>::max(), '\n' ); 			} 			return  playerInputBoardSize; 		} 		void  receiveInputFromPlayer (std ::istream& is)  		 {			using  namespace  Input::Keypress::Code; 			 			flagInputErroneousChoice = bool { false  }; 			 			 			const  auto  gbsize = receiveGameBoardSize(is); 			const  auto  isValidBoardSize = 				(gbsize >= MIN_GAME_BOARD_PLAY_SIZE) && (gbsize <= MAX_GAME_BOARD_PLAY_SIZE); 			 			if  (isValidBoardSize) 			{ 				storedGameBoardSize = gbsize; 				pregamesetup_status[FLAG_START_GAME] = true ; 			} 			 			bool  goBackToMainMenu{ true  }; 			switch  (gbsize) 			{ 			case  CODE_HOTKEY_PREGAME_BACK_TO_MENU: 				pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU] = true ; 				break ; 			default : 				goBackToMainMenu = false ; 				break ; 			} 			if  (!isValidBoardSize && !goBackToMainMenu) 			{ 				flagInputErroneousChoice = true ; 			} 		} 		void  processPreGame ()  		 {			if  (pregamesetup_status[FLAG_START_GAME]) 			{ 				playGame(PlayGameFlag::BrandNewGame, GameBoard{ storedGameBoardSize }, 					storedGameBoardSize); 			} 			if  (pregamesetup_status[FLAG_RETURN_TO_MAIN_MENU]) 			{ 				Menu::startMenu(); 			} 		} 		bool  soloLoop ()  		 {			bool  invalidInputValue = flagInputErroneousChoice; 			const  auto  questionAboutGameBoardSizePrompt = [&invalidInputValue]() { 				std ::ostringstream  str_os; 				 				DrawOnlyWhen(str_os, invalidInputValue, Graphics::BoardSizeErrorPrompt); 				DrawAlways(str_os, Graphics::BoardInputPrompt); 				return  str_os.str(); 			}; 			 			pregamesetup_status = pregameesetup_status_t {}; 			clearScreen(); 			DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); 			DrawAsOneTimeFlag(std ::cout , noSave, Graphics::GameBoardNoSaveErrorPrompt); 			DrawAlways(std ::cout , questionAboutGameBoardSizePrompt); 			receiveInputFromPlayer(std ::cin ); 			processPreGame(); 			return  flagInputErroneousChoice; 		} 		void  endlessLoop ()  		 {			while  (soloLoop()) 				; 		} 		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.txt" ; 			constexpr  auto  game_stats_data_filename = "../data/previousGameStats.txt" ; 			auto  loaded_gameboard{ false  }; 			auto  loaded_game_stats{ false  }; 			auto  tempGBoard = GameBoard{ 1  }; 			 			 			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{ false  }; 			GameBoard oldGameBoard; 			std ::tie(load_old_game_ok, oldGameBoard) = initialiseContinueBoardArray(); 			 			if  (load_old_game_ok)  			{ 				playGame(PlayGameFlag::ContinuePreviousGame, oldGameBoard); 			} 			else   			{ 				SetupNewGame(NewGameFlag::NoPreviousSaveAvailable); 			} 		} 	}  	namespace  PreGameSetup 	{ 		void  SetupNewGame ()  		 {			SetupNewGame(NewGameFlag::NewGameFlagNull); 		} 		void  ContinueOldGame ()   		 {			DoContinueOldGame(); 		} 	}  }  
 
阶段七 构建 这一阶段我们实现排行榜系统。
menu.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void  processPlayerInput ()   {    if  (menustatus[FLAG_START_GAME])     {         startGame();     }     if  (menustatus[FLAG_CONTINUE_GAME])     {         continueGame();     }     if  (menustatus[FLAG_DISPLAY_HIGHSCORES])     {         showScores();     }     if  (menustatus[FLAG_EXIT_GAME])     {         exit (EXIT_SUCCESS);     } } 
 
(此处强烈建议对照“项目成果展示——排行榜”图片进行代码阅读与理解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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(); } 
 
若用户选择查看游戏排行榜,则打印Title Art,历史得分与各项数据统计信息,然后等待用户输入任意键后返回主菜单。
pause_for_keypress()定义于global.cpp中。
1 2 3 4 5 void  pause_for_keypress ()   {	char  c{}; 	getKeypressDownInput(c); } 
 
辅助函数make_scoreboard_display_data_list()与make_total_stats_display_data()同样定义于menu.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 Scoreboard::Graphics::scoreboard_display_data_list_t      make_scoreboard_display_data_list()  {     using  namespace  Scoreboard::Graphics;     auto  scoreList = Scoreboard::Scoreboard_t{};               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; }; 
 
简单,不多说。
ScoreboardOverlay()与TotalStatisticsOverlay()分别定义于scoreboard-graphics.cpp和statistics-graphics.cpp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 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  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  data)          {             str_os << sp << "│ "  << std ::setw(2 ) << std ::get<0 >(data) << ". │ "                  << std ::left << std ::setw(18 ) << std ::get<1 >(data) << std ::right                 << " │ "  << std ::setw(8 ) << std ::get<2 >(data) << " │ "  << std ::setw(4 )                 << std ::get<3 >(data) << " │ "  << std ::setw(5 ) << std ::get<4 >(data)                 << " │ "  << std ::setw(12 ) << std ::get<5 >(data) << " │ "                  << std ::setw(12 ) << std ::get<6 >(data) << " │"                  << "\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  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(); } 
 
两个函数内容参照项目成果预览很容易理解,不赘述。
现在我们解决一下windows环境下必须手动键入回车才能读取输入的问题。
至此,全项目施工完成。
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 #include  "menu.hpp"  #include  "menu-graphics.hpp"  #include  "game.hpp"  #include  "game-graphics.hpp"  #include  "scoreboard.hpp"  #include  "scoreboard-graphics.hpp"  #include  "statistics.hpp"  #include  "statistics-graphics.hpp"  #include  "global.hpp"  #include  "game-graphics.hpp"  #include  <array>  #include  <iostream>  #include  <sstream>  #include  <algorithm>  namespace { 	enum  MenuStatusFlag   	{ 		FLAG_NULL, 		FLAG_START_GAME, 		FLAG_CONTINUE_GAME, 		FLAG_DISPLAY_HIGHSCORES, 		FLAG_EXIT_GAME, 		MAX_NO_MAIN_MENU_STATUS_FLAGS 	}; 	using  menuStatus_t = std ::array <bool , MAX_NO_MAIN_MENU_STATUS_FLAGS>; 	 	menuStatus_t menustatus{}; 	 	bool  flagInputErroneousChoice{ false  }; 	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{}; 		 		 		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  receiveInputFromPlayer (std ::istream& in_os)   	 {		 		flagInputErroneousChoice = bool {}; 		char  c; 		in_os >> c; 		switch  (c)  		{ 		case  '1' : 			menustatus[FLAG_START_GAME] = true ; 			break ; 		case  '2' : 			menustatus[FLAG_CONTINUE_GAME] = true ; 			break ; 		case  '3' : 			menustatus[FLAG_DISPLAY_HIGHSCORES] = true ; 			break ; 		case  '4' : 			menustatus[FLAG_EXIT_GAME] = true ; 			break ; 		default : 			flagInputErroneousChoice = true ; 			break ; 		} 	} 	void  processPlayerInput ()   	 {		if  (menustatus[FLAG_START_GAME]) 		{ 			startGame(); 		} 		if  (menustatus[FLAG_CONTINUE_GAME]) 		{ 			continueGame(); 		} 		if  (menustatus[FLAG_DISPLAY_HIGHSCORES]) 		{ 			showScores(); 		} 		if  (menustatus[FLAG_EXIT_GAME]) 		{ 			exit (EXIT_SUCCESS); 		} 	} 	bool  soloLoop ()  	 {		 		menustatus = menuStatus_t{}; 		clearScreen(); 		DrawAlways(std ::cout , Game::Graphics::AsciiArt2048); 		DrawAlways(std ::cout , 			DataSuppliment(flagInputErroneousChoice, 				Game::Graphics::Menu::MenuGraphicsOverlay)); 		receiveInputFromPlayer(std ::cin ); 		processPlayerInput(); 		return  flagInputErroneousChoice; 	} 	void  endlessLoop ()   	 {		while  (soloLoop()) 			; 	} }  namespace  Menu{ 	void  startMenu ()   	 {		endlessLoop(); 	} }  
 
global.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #ifndef  GLOBAL_H #define  GLOBAL_H #include  <iosfwd>  #include  <string>  using  ull = unsigned  long  long ;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  clearScreen ()  ;void  getKeypressDownInput (char & c)  ;void  pause_for_keypress ()  ;std ::string  secondsFormat (double  sec)  ;#endif   
 
global.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 #include  "global.hpp"  #include  <iostream>  #include  <sstream>  void  clearScreen ()   {#ifdef  _WIN32 	system("cls" ); #else  	system("clear" ); #endif  } #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); } std ::string  secondsFormat (double  sec)   {	double  second = sec; 	int  minute = second / 60 ; 	int  hour = minute / 60 ; 	second -= minute * 60 ; 	minute %= 60 ; 	second = static_cast <int >(second); 	std ::ostringstream  oss; 	if  (hour)  	{ 		oss << hour << "h " ; 	} 	if  (minute)  	{ 		oss << minute << "m " ; 	} 	oss << second << "s" ; 	return  oss.str(); } 
 
scoreboard-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #ifndef  SCOREBOARD_GRAPHICS_H #define  SCOREBOARD_GRAPHICS_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)  ; 	}  }  #endif   
 
scoreboard-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #include  "scoreboard-graphics.hpp"  #include  "global.hpp"  #include  "color.hpp"  #include  <sstream>  #include  <iomanip> // std::setw  #include  <array> // std::begin  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  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  data)                  {                     str_os << sp << "│ "  << std ::setw(2 ) << std ::get<0 >(data) << ". │ "                          << std ::left << std ::setw(18 ) << std ::get<1 >(data) << std ::right                         << " │ "  << std ::setw(8 ) << std ::get<2 >(data) << " │ "  << std ::setw(4 )                         << std ::get<3 >(data) << " │ "  << std ::setw(5 ) << std ::get<4 >(data)                         << " │ "  << std ::setw(12 ) << std ::get<5 >(data) << " │ "                          << std ::setw(12 ) << std ::get<6 >(data) << " │"                          << "\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();         } 	}  }  
 
statistics-graphics.hpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #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)  ; 	}  }  #endif   
 
statistics-graphics.cpp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include  "statistics.hpp"  #include  "statistics-graphics.hpp"  #include  "color.hpp"  #include  <sstream>  #include  <iomanip>  #include  <array>  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();         } 	}  }  
 
后记 windows环境下必须回车才能读入输入的问题尝试解决了一下,但没解决掉。。。懒得管了。
这东西在windows的黑终端玩起来又丑又闪,直接在linux环境下玩就好啦~
完结撒花,累死了。
2022/2/14