转载-游戏开发(三)——WIN32 黑白棋(一)——棋局逻辑的设计

转载-游戏开发(三)——WIN32 黑白棋(一)——棋局逻辑的设计

今天以黑白棋为例,开始给一个win32的小游戏设计,

分3部分介绍。

1、棋局的现实

2、AI的现实

3、游戏画面的现实

提供一下完整项目下载


其中第一部分为黑白棋游戏的主要逻辑:

1、棋盘,以及棋盘上的棋子的存储形式。这里用到了位图。

2、是否可以落子的判断(黑白棋是只有你落子的位置,在横竖斜八个方向中任意一个方向,能吃掉对方的子,你才可以落在该位置,八个方向都吃不掉对方子的位置是不能下的),以及吃子的逻辑(吃子的逻辑同样是八个方向,两个己方棋子之间夹住的对方棋子,可以被吃掉,翻转为己方棋子)。这里为了使得代码简介一点,使用了函数指针(不同方向上坐标的变化逻辑不一样)。

3、某一方下了一个子之后,交换手的判断(黑白棋中存在可能,一方下了一个子,并吃掉对方的子,之后对方无子可下,没有一个位置能使得对方能吃掉己方的棋子,所以黑白棋并不一定始终是一人一步来的,它可能存在一方连续落子的情况)。

4、游戏是否结束的判断(由于存在无子可下的情况,有可能双方都无子可以下,即一方将另一方全部吃光,所以黑白棋不一定是下满棋盘才分出胜负)。


第二部分主要为了写AI:

黑白棋的AI其实蛮复杂,有专门的研究黑白棋的AI的算法文章,这里只介绍一下,然后简单实现了一个AI,主要是最大最小算法,以及枝剪算法。


第三部分主要是游戏画面的显示:

涉及到windows消息机制,鼠标事件,键盘事件,菜单事件,定时器事件;以及简单的图形、文字绘制,涉及到画笔、画刷填充、绘图层HDC、画线、画圆、显示文字、双缓冲的位图拷贝。

阅读第三部分前,读者可以先行阅读《windows程序设计》一书打个基础。也可以看完博文之后,再将涉及到的图形API,消息机制等windows程序设计中涉及到的点带回到书中去详细了解。


黑白棋游戏在设计中需要注意的几点:

1、惯例,首先要定义好棋盘的坐标,定义为左上角那一格为(0,0),向右为x正方向,向下为y正方向,黑白棋棋盘是一个8*8的棋盘,所以定义两个常量表示:


 
 
  1. const int REVERSI_MAX_ROW = 8;
  2. const int REVERSI_MAX_COLUMN = 8;

2、棋盘上棋子的类型分三种:黑子,白子,空白无子,枚举表示


 
 
  1. enum EnumReversiPiecesType
  2. {
  3. enum_ReversiPieces_Null = 0x00,
  4. enum_ReversiPieces_Black = 0x01,
  5. enum_ReversiPieces_White = 0x02,
  6. };

这三种情况,其实用2位2进制即可表示,一行8个位置就是16位2进制,就是一个WORD就足够了,所以:

3、棋盘的表示,位图

TArray1<WORD, REVERSI_MAX_ROW> m_Map;
 
 

位图是8行,每行是一个WORD,这个TArray1是之前实现的 一维数组模板直接用的

4、棋盘上一个位置的设计,因为这里涉及到位置(即坐标)的八方向移动的逻辑,因此将坐标位置单独抽象出来,实现坐标的上下左右以及斜的四方向的坐标变化,然后将其重定义为函数指针,使得后面在坐标变化时,不用switch...case八种情况,而是可以将方向当成参数。 

typedef void (ReversiPoint::*ReversiPointForward)();
 
 

5、某一方的棋子,在某一坐标位置,向某一方向,是否可以吃掉对方的棋子的判断

bool ReversiCheck(EnumReversiPiecesType type, char row_y, char column_x, ReversiPointForward forward);
 
 
是否可以吃子的伪代码:


 
 
  1. 定义一个坐标对象point,初值为当前点row_y, column_x
  2. 记录该方向上的搜索次数search,初值为 0
  3. point向forward方向移动
  4. 搜索次数search++
  5. while (point是一个合法的坐标,不能移出棋盘外面去了)
  6. {
  7. 取point当前位置的棋子类型
  8. (此时已经是forward移动一次之后的位置了,不是row_y, column_x了)
  9. if (当前位置有棋子)
  10. {
  11. if (当前位置的棋子类型等于传入参数type,type就是要下的棋子类型)
  12. {
  13. if (搜索次数search大于 1次)
  14. {
  15. 说明找到的同色棋子与当前棋子坐标差超过 1,point至少移动了 2
  16. 则两子之间夹有不同色的棋子
  17. 符合翻转规则, return true
  18. }
  19. else
  20. {
  21. 说明找到的同色棋子与当前棋子,两子是紧挨着的
  22. 该方向两子之间无子可以翻转
  23. 不符合翻转规则, return false
  24. }
  25. }
  26. else
  27. {
  28. 说明找到的是不同色的棋子,继续向下一个位置搜
  29. point向forward方向移动
  30. 搜索次数search++
  31. }
  32. }
  33. else
  34. {
  35. 一直找到空位也没找到,该方向没有同色棋子,无法翻转
  36. }
  37. }
  38. 超出棋盘范围都没有找到同色棋子,该方向没有同色棋子,无法翻转

6、某一方的棋子,在某一坐标位置,向某一方向,吃掉对方的棋子

void DoReversi(EnumReversiPiecesType type, char row_y, char column_x, ReversiPointForward forward);
 
 

伪代码实现


 
 
  1. 定义一个坐标对象point,初值为当前点row_y, column_x
  2. point向forward方向移动
  3. while (point是一个合法的坐标,不能移出棋盘外面去了)
  4. {
  5. 取point当前位置的棋子类型
  6. (此时已经是forward移动一次之后的位置了,不是row_y, column_x了)
  7. if (当前位置的棋子类型不等于传入参数type,type就是下的棋子类型)
  8. {
  9. 将该位置的棋子类型翻转为type一方的棋子
  10. point向forward方向移动
  11. 因为在翻转之前做了ReversiCheck的判断
  12. 即这个方向肯定是符合翻转规则,有子可吃的
  13. 所以这里不再判断当前位置的棋子类型是否为空
  14. }
  15. }

有了上面两个基本函数

7、判断某个位置是否可以落子

bool ReversiCheck(EnumReversiPiecesType type, char row_y, char column_x);
 
 

则是分别调用上面的ReversiCheck,然后forward传入不同的方向

8、判断某一方是否可以落子

bool CanPlay(EnumReversiPiecesType type);
 
 
即遍历棋盘每一个位置,任意一个位置可以落子,则该方可以落子

9、落一个子之后的吃子

void DoReversi(EnumReversiPiecesType type, char row_y, char column_x);
 
 

则是分别调用上面的DoReversi,然后forward传入不同的方向


10、最后,判断游戏是否结束的逻辑,即双方都无子可下,则游戏结束


先给个游戏截图吧


下面先贴出第一部分的代码

ReversiCommon.h


 
 
  1. #ifndef _ReversiCommon_h_
  2. #define _ReversiCommon_h_
  3. #include <windows.h>
  4. //棋盘大小
  5. const int REVERSI_MAX_ROW = 8;
  6. const int REVERSI_MAX_COLUMN = 8;
  7. enum EnumReversiPiecesType
  8. {
  9. enum_ReversiPieces_Null = 0x00,
  10. enum_ReversiPieces_Black = 0x01,
  11. enum_ReversiPieces_White = 0x02,
  12. };
  13. EnumReversiPiecesType SwapType(EnumReversiPiecesType type);
  14. enum EnumReversiResult
  15. {
  16. enum_Reversi_Playing = 0,
  17. enum_Reversi_Draw,
  18. enum_Reversi_Win_Black,
  19. enum_Reversi_Win_White,
  20. };
  21. //权值表
  22. const int g_Weight[REVERSI_MAX_ROW][REVERSI_MAX_COLUMN] = {
  23. { 0x1 << 24, 0x1, 0x1 << 20, 0x1 << 16, 0x1 << 16, 0x1 << 20, 0x1, 0x1 << 24},
  24. { 0x1, 0x1, 0x1 << 16, 0x1 << 4, 0x1 << 4, 0x1 << 16, 0x1, 0x1 },
  25. { 0x1 << 20, 0x1 << 16, 0x1 << 12, 0x1 << 8, 0x1 << 8, 0x1 << 12, 0x1 << 16, 0x1 << 20},
  26. { 0x1 << 16, 0x1 << 4, 0x1 << 8, 0, 0, 0x1 << 8, 0x1 << 4, 0x1 << 16},
  27. { 0x1 << 16, 0x1 << 4, 0x1 << 8, 0, 0, 0x1 << 8, 0x1 << 4, 0x1 << 16},
  28. { 0x1 << 20, 0x1 << 16, 0x1 << 12, 0x1 << 8, 0x1 << 8, 0x1 << 12, 0x1 << 16, 0x1 << 20},
  29. { 0x1, 0x1, 0x1 << 16, 0x1 << 4, 0x1 << 4, 0x1 << 16, 0x1, 0x1 },
  30. { 0x1 << 24, 0x1, 0x1 << 20, 0x1 << 16, 0x1 << 16, 0x1 << 20, 0x1, 0x1 << 24}
  31. };
  32. //按权值表降序排列的坐标顺序表
  33. const BYTE g_WeightOrder[REVERSI_MAX_ROW * REVERSI_MAX_COLUMN - 4][ 2] = {
  34. { 0, 0}, { 0, 7}, { 7, 0}, { 7, 7}, //0x01000000
  35. { 0, 2}, { 0, 5}, { 2, 0}, { 2, 7}, //0x00100000
  36. { 7, 2}, { 7, 5}, { 5, 0}, { 5, 7},
  37. { 0, 3}, { 0, 4}, { 1, 2}, { 1, 5}, //0x00010000
  38. { 2, 1}, { 2, 6}, { 3, 0}, { 3, 7},
  39. { 4, 0}, { 4, 7}, { 5, 1}, { 5, 6},
  40. { 6, 2}, { 6, 5}, { 7, 3}, { 7, 4},
  41. { 2, 2}, { 2, 5}, { 5, 2}, { 5, 5}, //0x00001000
  42. { 2, 3}, { 2, 4}, { 3, 2}, { 3, 5}, //0x00000100
  43. { 4, 2}, { 4, 5}, { 5, 3}, { 5, 4},
  44. { 1, 3}, { 1, 4}, { 3, 1}, { 3, 6}, //0x00000010
  45. { 4, 1}, { 4, 6}, { 6, 3}, { 6, 4},
  46. { 0, 1}, { 0, 6}, { 1, 0}, { 1, 7}, //0x00000001
  47. { 6, 0}, { 6, 7}, { 7, 1}, { 7, 6},
  48. { 1, 1}, { 1, 6}, { 6, 1}, { 6, 6} //0x00000001
  49. //{ 3, 3}, { 3, 4}, { 4, 3}, { 4, 4}, 初始4个位置不用判断
  50. };
  51. #endif


ReversiCommon.cpp


 
 
  1. #include "ReversiCommon.h"
  2. EnumReversiPiecesType SwapType(EnumReversiPiecesType type)
  3. {
  4. if (enum_ReversiPieces_Black == type)
  5. {
  6. return enum_ReversiPieces_White;
  7. }
  8. else if (enum_ReversiPieces_White == type)
  9. {
  10. return enum_ReversiPieces_Black;
  11. }
  12. else
  13. {
  14. return enum_ReversiPieces_Null;
  15. }
  16. }


ReversiPoint.h


 
 
  1. #ifndef _ReversiPoint_h_
  2. #define _ReversiPoint_h_
  3. #include "ReversiCommon.h"
  4. typedef struct ReversiPoint
  5. {
  6. char m_row_y;
  7. char m_column_x;
  8. ReversiPoint& operator= ( const ReversiPoint& temp)
  9. {
  10. m_row_y = temp.m_row_y;
  11. m_column_x = temp.m_column_x;
  12. return * this;
  13. }
  14. bool operator!= ( const ReversiPoint& temp)
  15. {
  16. if (m_row_y == temp.m_row_y &&
  17. m_column_x == temp.m_column_x)
  18. {
  19. return false;
  20. }
  21. else
  22. {
  23. return true;
  24. }
  25. }
  26. bool IsValid()
  27. {
  28. if ( 0 <= m_row_y &&
  29. 0 <= m_column_x &&
  30. m_row_y < REVERSI_MAX_ROW &&
  31. m_column_x < REVERSI_MAX_COLUMN)
  32. {
  33. return true;
  34. }
  35. else
  36. {
  37. return false;
  38. }
  39. }
  40. void UL()
  41. {
  42. m_row_y--;
  43. m_column_x--;
  44. }
  45. void U()
  46. {
  47. m_row_y--;
  48. }
  49. void UR()
  50. {
  51. m_row_y--;
  52. m_column_x++;
  53. }
  54. void L()
  55. {
  56. m_column_x--;
  57. }
  58. void R()
  59. {
  60. m_column_x++;
  61. }
  62. void DL()
  63. {
  64. m_row_y++;
  65. m_column_x--;
  66. }
  67. void D()
  68. {
  69. m_row_y++;
  70. }
  71. void DR()
  72. {
  73. m_row_y++;
  74. m_column_x++;
  75. }
  76. }ReversiPoint;
  77. typedef void (ReversiPoint::*ReversiPointForward)();
  78. #endif


ReversiBitBoard.h


 
 
  1. #ifndef _ReversiBitBoard_h_
  2. #define _ReversiBitBoard_h_
  3. #include <Windows.h>
  4. #include "TArray.h"
  5. #include "ReversiCommon.h"
  6. #include "ReversiPoint.h"
  7. class ReversiBitBoard
  8. {
  9. public:
  10. ReversiBitBoard();
  11. ~ReversiBitBoard();
  12. void Init();
  13. ReversiBitBoard& operator= ( const ReversiBitBoard& temp);
  14. void SetPieces(EnumReversiPiecesType type, char row_y, char column_x);
  15. EnumReversiPiecesType GetPieces(char row_y, char column_x);
  16. EnumReversiResult IsGameOver();
  17. bool CanPlay(EnumReversiPiecesType type);
  18. bool CanPlay(EnumReversiPiecesType type, char row_y, char column_x);
  19. bool ReversiCheck(EnumReversiPiecesType type, char row_y, char column_x);
  20. void DoReversi(EnumReversiPiecesType type, char row_y, char column_x);
  21. int GetCount(EnumReversiPiecesType type);
  22. void SwapPlayer();
  23. EnumReversiPiecesType GetCurrType();
  24. private:
  25. void DoReversi(EnumReversiPiecesType type, char row_y, char column_x,
  26. ReversiPointForward forward);
  27. bool ReversiCheck(EnumReversiPiecesType type, char row_y, char column_x,
  28. ReversiPointForward forward);
  29. TArray1<WORD, REVERSI_MAX_ROW> m_Map;
  30. EnumReversiPiecesType m_CurrType;
  31. };
  32. #endif


ReversiBitBoard.cpp


 
 
  1. #include "ReversiBitBoard.h"
  2. ReversiBitBoard::ReversiBitBoard()
  3. {
  4. }
  5. ReversiBitBoard::~ReversiBitBoard()
  6. {
  7. }
  8. void ReversiBitBoard::Init()
  9. {
  10. m_CurrType = enum_ReversiPieces_Black; //规定黑先
  11. for ( int i = 0; i < REVERSI_MAX_ROW; i++)
  12. {
  13. m_Map[i] = 0;
  14. }
  15. SetPieces(enum_ReversiPieces_White, 3, 3);
  16. SetPieces(enum_ReversiPieces_Black, 3, 4);
  17. SetPieces(enum_ReversiPieces_Black, 4, 3);
  18. SetPieces(enum_ReversiPieces_White, 4, 4);
  19. }
  20. ReversiBitBoard& ReversiBitBoard:: operator=( const ReversiBitBoard& temp)
  21. {
  22. m_Map = temp.m_Map;
  23. m_CurrType = temp.m_CurrType;
  24. return * this;
  25. }
  26. void ReversiBitBoard::SetPieces(EnumReversiPiecesType type, char row_y, char column_x)
  27. {
  28. m_Map[row_y] = m_Map[row_y] & (~( 0x0003 << (column_x * 2)));
  29. m_Map[row_y] = m_Map[row_y] | (type << (column_x * 2));
  30. }
  31. EnumReversiPiecesType ReversiBitBoard::GetPieces( char row_y, char column_x)
  32. {
  33. WORD value = m_Map[row_y] & ( 0x0003 << (column_x * 2));
  34. value = value >> (column_x * 2);
  35. EnumReversiPiecesType type = static_cast<EnumReversiPiecesType>(value);
  36. return type;
  37. }
  38. int ReversiBitBoard::GetCount(EnumReversiPiecesType type)
  39. {
  40. int count = 0;
  41. for ( int i = 0; i < REVERSI_MAX_ROW; i++)
  42. {
  43. for ( int j = 0; j < REVERSI_MAX_COLUMN; j++)
  44. {
  45. if (type == GetPieces(i, j))
  46. {
  47. count++;
  48. }
  49. }
  50. }
  51. return count;
  52. }
  53. EnumReversiResult ReversiBitBoard::IsGameOver()
  54. {
  55. if (!CanPlay(enum_ReversiPieces_Black) &&
  56. !CanPlay(enum_ReversiPieces_White))
  57. {
  58. int black = GetCount(enum_ReversiPieces_Black);
  59. int white = GetCount(enum_ReversiPieces_White);
  60. if (black > white)
  61. {
  62. return enum_Reversi_Win_Black;
  63. }
  64. else if (black < white)
  65. {
  66. return enum_Reversi_Win_White;
  67. }
  68. else
  69. {
  70. return enum_Reversi_Draw;
  71. }
  72. }
  73. else
  74. {
  75. return enum_Reversi_Playing;
  76. }
  77. }
  78. bool ReversiBitBoard::CanPlay(EnumReversiPiecesType type)
  79. {
  80. for ( int i = 0; i < REVERSI_MAX_ROW; i++)
  81. {
  82. for ( int j = 0; j < REVERSI_MAX_COLUMN; j++)
  83. {
  84. if (CanPlay(type, i, j))
  85. {
  86. return true;
  87. }
  88. }
  89. }
  90. return false;
  91. }
  92. bool ReversiBitBoard::CanPlay(EnumReversiPiecesType type, char row_y, char column_x)
  93. {
  94. if (enum_ReversiPieces_Null == GetPieces(row_y, column_x))
  95. {
  96. if (ReversiCheck(type, row_y, column_x))
  97. {
  98. return true;
  99. }
  100. }
  101. return false;
  102. }
  103. bool ReversiBitBoard::ReversiCheck(EnumReversiPiecesType type,
  104. char row_y, char column_x)
  105. {
  106. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::UL) ||
  107. ReversiCheck(type, row_y, column_x, &ReversiPoint::U) ||
  108. ReversiCheck(type, row_y, column_x, &ReversiPoint::UR) ||
  109. ReversiCheck(type, row_y, column_x, &ReversiPoint::L) ||
  110. ReversiCheck(type, row_y, column_x, &ReversiPoint::R) ||
  111. ReversiCheck(type, row_y, column_x, &ReversiPoint::DL) ||
  112. ReversiCheck(type, row_y, column_x, &ReversiPoint::D) ||
  113. ReversiCheck(type, row_y, column_x, &ReversiPoint::DR))
  114. {
  115. return true;
  116. }
  117. return false;
  118. }
  119. bool ReversiBitBoard::ReversiCheck(EnumReversiPiecesType type,
  120. char row_y, char column_x,
  121. ReversiPointForward forward)
  122. {
  123. ReversiPoint point = {row_y, column_x};
  124. EnumReversiPiecesType currType;
  125. int search = 0;
  126. (point.*forward)(); //向某方向搜寻
  127. search++;
  128. while(point.IsValid())
  129. {
  130. currType = GetPieces(point.m_row_y, point.m_column_x);
  131. if (enum_ReversiPieces_Null != currType)
  132. {
  133. if (type == currType)
  134. {
  135. if (search > 1)
  136. {
  137. //找到的同色棋子与当前棋子坐标差超过1,则两子之间夹有不同色的棋子
  138. return true;
  139. }
  140. else
  141. {
  142. //否则两子是紧挨着的,该方向两子之间无子可以翻转
  143. return false;
  144. }
  145. }
  146. else
  147. {
  148. //找到的是不同色的棋子,继续
  149. (point.*forward)();
  150. search++;
  151. }
  152. }
  153. else
  154. {
  155. //一直找到空位也没找到,该方向没有同色棋子,无法翻转
  156. return false;
  157. }
  158. }
  159. //超出棋盘范围都没有找到同色棋子,该方向没有同色棋子,无法翻转
  160. return false;
  161. }
  162. void ReversiBitBoard::DoReversi(EnumReversiPiecesType type,
  163. char row_y, char column_x)
  164. {
  165. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::UL))
  166. {
  167. DoReversi(type, row_y, column_x, &ReversiPoint::UL);
  168. }
  169. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::U))
  170. {
  171. DoReversi(type, row_y, column_x, &ReversiPoint::U);
  172. }
  173. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::UR))
  174. {
  175. DoReversi(type, row_y, column_x, &ReversiPoint::UR);
  176. }
  177. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::L))
  178. {
  179. DoReversi(type, row_y, column_x, &ReversiPoint::L);
  180. }
  181. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::R))
  182. {
  183. DoReversi(type, row_y, column_x, &ReversiPoint::R);
  184. }
  185. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::DL))
  186. {
  187. DoReversi(type, row_y, column_x, &ReversiPoint::DL);
  188. }
  189. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::D))
  190. {
  191. DoReversi(type, row_y, column_x, &ReversiPoint::D);
  192. }
  193. if (ReversiCheck(type, row_y, column_x, &ReversiPoint::DR))
  194. {
  195. DoReversi(type, row_y, column_x, &ReversiPoint::DR);
  196. }
  197. }
  198. void ReversiBitBoard::DoReversi(EnumReversiPiecesType type,
  199. char row_y, char column_x,
  200. ReversiPointForward forward)
  201. {
  202. ReversiPoint point = {row_y, column_x};
  203. (point.*forward)();
  204. while(point.IsValid())
  205. {
  206. if (type != GetPieces(point.m_row_y, point.m_column_x))
  207. {
  208. SetPieces(type, point.m_row_y, point.m_column_x);
  209. (point.*forward)();
  210. }
  211. else
  212. {
  213. break;
  214. }
  215. }
  216. }
  217. void ReversiBitBoard::SwapPlayer()
  218. {
  219. EnumReversiPiecesType nexttype = SwapType(m_CurrType);
  220. if (CanPlay(nexttype))
  221. {
  222. m_CurrType = nexttype;
  223. }
  224. }
  225. EnumReversiPiecesType ReversiBitBoard::GetCurrType()
  226. {
  227. return m_CurrType;
  228. }


阅读更多
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值