转载-游戏开发(三)——WIN32 黑白棋(一)——棋局逻辑的设计
今天以黑白棋为例,开始给一个win32的小游戏设计,
分3部分介绍。
其中第一部分为黑白棋游戏的主要逻辑:
1、棋盘,以及棋盘上的棋子的存储形式。这里用到了位图。
2、是否可以落子的判断(黑白棋是只有你落子的位置,在横竖斜八个方向中任意一个方向,能吃掉对方的子,你才可以落在该位置,八个方向都吃不掉对方子的位置是不能下的),以及吃子的逻辑(吃子的逻辑同样是八个方向,两个己方棋子之间夹住的对方棋子,可以被吃掉,翻转为己方棋子)。这里为了使得代码简介一点,使用了函数指针(不同方向上坐标的变化逻辑不一样)。
3、某一方下了一个子之后,交换手的判断(黑白棋中存在可能,一方下了一个子,并吃掉对方的子,之后对方无子可下,没有一个位置能使得对方能吃掉己方的棋子,所以黑白棋并不一定始终是一人一步来的,它可能存在一方连续落子的情况)。
4、游戏是否结束的判断(由于存在无子可下的情况,有可能双方都无子可以下,即一方将另一方全部吃光,所以黑白棋不一定是下满棋盘才分出胜负)。
第二部分主要为了写AI:
黑白棋的AI其实蛮复杂,有专门的研究黑白棋的AI的算法文章,这里只介绍一下,然后简单实现了一个AI,主要是最大最小算法,以及枝剪算法。
第三部分主要是游戏画面的显示:
涉及到windows消息机制,鼠标事件,键盘事件,菜单事件,定时器事件;以及简单的图形、文字绘制,涉及到画笔、画刷填充、绘图层HDC、画线、画圆、显示文字、双缓冲的位图拷贝。
阅读第三部分前,读者可以先行阅读《windows程序设计》一书打个基础。也可以看完博文之后,再将涉及到的图形API,消息机制等windows程序设计中涉及到的点带回到书中去详细了解。
黑白棋游戏在设计中需要注意的几点:
1、惯例,首先要定义好棋盘的坐标,定义为左上角那一格为(0,0),向右为x正方向,向下为y正方向,黑白棋棋盘是一个8*8的棋盘,所以定义两个常量表示:
-
const
int REVERSI_MAX_ROW =
8;
-
const
int REVERSI_MAX_COLUMN =
8;
2、棋盘上棋子的类型分三种:黑子,白子,空白无子,枚举表示
-
enum EnumReversiPiecesType
-
{
-
enum_ReversiPieces_Null =
0x00,
-
enum_ReversiPieces_Black =
0x01,
-
enum_ReversiPieces_White =
0x02,
-
};
这三种情况,其实用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);
是否可以吃子的伪代码:
-
定义一个坐标对象point,初值为当前点row_y, column_x
-
记录该方向上的搜索次数search,初值为
0
-
point向forward方向移动
-
搜索次数search++
-
while (point是一个合法的坐标,不能移出棋盘外面去了)
-
{
-
取point当前位置的棋子类型
-
(此时已经是forward移动一次之后的位置了,不是row_y, column_x了)
-
if (当前位置有棋子)
-
{
-
if (当前位置的棋子类型等于传入参数type,type就是要下的棋子类型)
-
{
-
if (搜索次数search大于
1次)
-
{
-
说明找到的同色棋子与当前棋子坐标差超过
1,point至少移动了
2次
-
则两子之间夹有不同色的棋子
-
符合翻转规则,
return
true
-
}
-
else
-
{
-
说明找到的同色棋子与当前棋子,两子是紧挨着的
-
该方向两子之间无子可以翻转
-
不符合翻转规则,
return
false
-
}
-
}
-
else
-
{
-
说明找到的是不同色的棋子,继续向下一个位置搜
-
point向forward方向移动
-
搜索次数search++
-
}
-
}
-
else
-
{
-
一直找到空位也没找到,该方向没有同色棋子,无法翻转
-
}
-
}
-
超出棋盘范围都没有找到同色棋子,该方向没有同色棋子,无法翻转
6、某一方的棋子,在某一坐标位置,向某一方向,吃掉对方的棋子
void DoReversi(EnumReversiPiecesType type, char row_y, char column_x, ReversiPointForward forward);
伪代码实现
-
定义一个坐标对象point,初值为当前点row_y, column_x
-
point向forward方向移动
-
while (point是一个合法的坐标,不能移出棋盘外面去了)
-
{
-
取point当前位置的棋子类型
-
(此时已经是forward移动一次之后的位置了,不是row_y, column_x了)
-
if (当前位置的棋子类型不等于传入参数type,type就是下的棋子类型)
-
{
-
将该位置的棋子类型翻转为type一方的棋子
-
point向forward方向移动
-
因为在翻转之前做了ReversiCheck的判断
-
即这个方向肯定是符合翻转规则,有子可吃的
-
所以这里不再判断当前位置的棋子类型是否为空
-
}
-
}
有了上面两个基本函数
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
-
#ifndef _ReversiCommon_h_
-
#define _ReversiCommon_h_
-
-
#include <windows.h>
-
-
//棋盘大小
-
const
int REVERSI_MAX_ROW =
8;
-
const
int REVERSI_MAX_COLUMN =
8;
-
-
enum EnumReversiPiecesType
-
{
-
enum_ReversiPieces_Null =
0x00,
-
enum_ReversiPieces_Black =
0x01,
-
enum_ReversiPieces_White =
0x02,
-
};
-
-
EnumReversiPiecesType SwapType(EnumReversiPiecesType type);
-
-
enum EnumReversiResult
-
{
-
enum_Reversi_Playing =
0,
-
enum_Reversi_Draw,
-
enum_Reversi_Win_Black,
-
enum_Reversi_Win_White,
-
};
-
-
//权值表
-
const
int g_Weight[REVERSI_MAX_ROW][REVERSI_MAX_COLUMN] = {
-
{
0x1 <<
24,
0x1,
0x1 <<
20,
0x1 <<
16,
0x1 <<
16,
0x1 <<
20,
0x1,
0x1 <<
24},
-
{
0x1,
0x1,
0x1 <<
16,
0x1 <<
4,
0x1 <<
4,
0x1 <<
16,
0x1,
0x1 },
-
{
0x1 <<
20,
0x1 <<
16,
0x1 <<
12,
0x1 <<
8,
0x1 <<
8,
0x1 <<
12,
0x1 <<
16,
0x1 <<
20},
-
{
0x1 <<
16,
0x1 <<
4,
0x1 <<
8,
0,
0,
0x1 <<
8,
0x1 <<
4,
0x1 <<
16},
-
{
0x1 <<
16,
0x1 <<
4,
0x1 <<
8,
0,
0,
0x1 <<
8,
0x1 <<
4,
0x1 <<
16},
-
{
0x1 <<
20,
0x1 <<
16,
0x1 <<
12,
0x1 <<
8,
0x1 <<
8,
0x1 <<
12,
0x1 <<
16,
0x1 <<
20},
-
{
0x1,
0x1,
0x1 <<
16,
0x1 <<
4,
0x1 <<
4,
0x1 <<
16,
0x1,
0x1 },
-
{
0x1 <<
24,
0x1,
0x1 <<
20,
0x1 <<
16,
0x1 <<
16,
0x1 <<
20,
0x1,
0x1 <<
24}
-
};
-
-
//按权值表降序排列的坐标顺序表
-
const BYTE g_WeightOrder[REVERSI_MAX_ROW * REVERSI_MAX_COLUMN -
4][
2] = {
-
{
0,
0}, {
0,
7}, {
7,
0}, {
7,
7},
//0x01000000
-
{
0,
2}, {
0,
5}, {
2,
0}, {
2,
7},
//0x00100000
-
{
7,
2}, {
7,
5}, {
5,
0}, {
5,
7},
-
{
0,
3}, {
0,
4}, {
1,
2}, {
1,
5},
//0x00010000
-
{
2,
1}, {
2,
6}, {
3,
0}, {
3,
7},
-
{
4,
0}, {
4,
7}, {
5,
1}, {
5,
6},
-
{
6,
2}, {
6,
5}, {
7,
3}, {
7,
4},
-
{
2,
2}, {
2,
5}, {
5,
2}, {
5,
5},
//0x00001000
-
{
2,
3}, {
2,
4}, {
3,
2}, {
3,
5},
//0x00000100
-
{
4,
2}, {
4,
5}, {
5,
3}, {
5,
4},
-
{
1,
3}, {
1,
4}, {
3,
1}, {
3,
6},
//0x00000010
-
{
4,
1}, {
4,
6}, {
6,
3}, {
6,
4},
-
{
0,
1}, {
0,
6}, {
1,
0}, {
1,
7},
//0x00000001
-
{
6,
0}, {
6,
7}, {
7,
1}, {
7,
6},
-
{
1,
1}, {
1,
6}, {
6,
1}, {
6,
6}
//0x00000001
-
//{ 3, 3}, { 3, 4}, { 4, 3}, { 4, 4}, 初始4个位置不用判断
-
};
-
-
#endif
ReversiCommon.cpp
-
#include "ReversiCommon.h"
-
-
EnumReversiPiecesType SwapType(EnumReversiPiecesType type)
-
{
-
if (enum_ReversiPieces_Black == type)
-
{
-
return enum_ReversiPieces_White;
-
}
-
else
if (enum_ReversiPieces_White == type)
-
{
-
return enum_ReversiPieces_Black;
-
}
-
else
-
{
-
return enum_ReversiPieces_Null;
-
}
-
}
ReversiPoint.h
-
#ifndef _ReversiPoint_h_
-
#define _ReversiPoint_h_
-
-
#include "ReversiCommon.h"
-
-
typedef
struct ReversiPoint
-
{
-
char m_row_y;
-
char m_column_x;
-
-
ReversiPoint&
operator= (
const ReversiPoint& temp)
-
{
-
m_row_y = temp.m_row_y;
-
m_column_x = temp.m_column_x;
-
return *
this;
-
}
-
-
bool
operator!= (
const ReversiPoint& temp)
-
{
-
if (m_row_y == temp.m_row_y &&
-
m_column_x == temp.m_column_x)
-
{
-
return
false;
-
}
-
else
-
{
-
return
true;
-
}
-
}
-
-
bool IsValid()
-
{
-
if (
0 <= m_row_y &&
-
0 <= m_column_x &&
-
m_row_y < REVERSI_MAX_ROW &&
-
m_column_x < REVERSI_MAX_COLUMN)
-
{
-
return
true;
-
}
-
else
-
{
-
return
false;
-
}
-
}
-
-
void UL()
-
{
-
m_row_y--;
-
m_column_x--;
-
}
-
-
void U()
-
{
-
m_row_y--;
-
}
-
-
void UR()
-
{
-
m_row_y--;
-
m_column_x++;
-
}
-
-
void L()
-
{
-
m_column_x--;
-
}
-
-
void R()
-
{
-
m_column_x++;
-
}
-
-
void DL()
-
{
-
m_row_y++;
-
m_column_x--;
-
}
-
-
void D()
-
{
-
m_row_y++;
-
}
-
-
void DR()
-
{
-
m_row_y++;
-
m_column_x++;
-
}
-
}ReversiPoint;
-
-
typedef void (ReversiPoint::*ReversiPointForward)();
-
-
#endif
ReversiBitBoard.h
-
#ifndef _ReversiBitBoard_h_
-
#define _ReversiBitBoard_h_
-
-
#include <Windows.h>
-
-
#include "TArray.h"
-
-
#include "ReversiCommon.h"
-
#include "ReversiPoint.h"
-
-
class ReversiBitBoard
-
{
-
public:
-
ReversiBitBoard();
-
~ReversiBitBoard();
-
-
void Init();
-
-
ReversiBitBoard&
operator= (
const ReversiBitBoard& temp);
-
-
void SetPieces(EnumReversiPiecesType type, char row_y, char column_x);
-
-
EnumReversiPiecesType GetPieces(char row_y, char column_x);
-
-
EnumReversiResult IsGameOver();
-
-
bool CanPlay(EnumReversiPiecesType type);
-
-
bool CanPlay(EnumReversiPiecesType type, char row_y, char column_x);
-
-
bool ReversiCheck(EnumReversiPiecesType type, char row_y, char column_x);
-
-
void DoReversi(EnumReversiPiecesType type, char row_y, char column_x);
-
-
int GetCount(EnumReversiPiecesType type);
-
-
void SwapPlayer();
-
-
EnumReversiPiecesType GetCurrType();
-
-
private:
-
void DoReversi(EnumReversiPiecesType type, char row_y, char column_x,
-
ReversiPointForward forward);
-
-
bool ReversiCheck(EnumReversiPiecesType type, char row_y, char column_x,
-
ReversiPointForward forward);
-
-
TArray1<WORD, REVERSI_MAX_ROW> m_Map;
-
-
EnumReversiPiecesType m_CurrType;
-
};
-
-
#endif
ReversiBitBoard.cpp
-
#include "ReversiBitBoard.h"
-
-
ReversiBitBoard::ReversiBitBoard()
-
{
-
-
}
-
-
ReversiBitBoard::~ReversiBitBoard()
-
{
-
-
}
-
-
void ReversiBitBoard::Init()
-
{
-
m_CurrType = enum_ReversiPieces_Black;
//规定黑先
-
-
for (
int i =
0; i < REVERSI_MAX_ROW; i++)
-
{
-
m_Map[i] =
0;
-
}
-
SetPieces(enum_ReversiPieces_White,
3,
3);
-
SetPieces(enum_ReversiPieces_Black,
3,
4);
-
SetPieces(enum_ReversiPieces_Black,
4,
3);
-
SetPieces(enum_ReversiPieces_White,
4,
4);
-
}
-
-
ReversiBitBoard& ReversiBitBoard::
operator=(
const ReversiBitBoard& temp)
-
{
-
m_Map = temp.m_Map;
-
m_CurrType = temp.m_CurrType;
-
return *
this;
-
}
-
-
void ReversiBitBoard::SetPieces(EnumReversiPiecesType type,
char row_y,
char column_x)
-
{
-
m_Map[row_y] = m_Map[row_y] & (~(
0x0003 << (column_x *
2)));
-
m_Map[row_y] = m_Map[row_y] | (type << (column_x *
2));
-
}
-
-
EnumReversiPiecesType ReversiBitBoard::GetPieces(
char row_y,
char column_x)
-
{
-
WORD value = m_Map[row_y] & (
0x0003 << (column_x *
2));
-
value = value >> (column_x *
2);
-
EnumReversiPiecesType type =
static_cast<EnumReversiPiecesType>(value);
-
return type;
-
}
-
-
int ReversiBitBoard::GetCount(EnumReversiPiecesType type)
-
{
-
int count =
0;
-
for (
int i =
0; i < REVERSI_MAX_ROW; i++)
-
{
-
for (
int j =
0; j < REVERSI_MAX_COLUMN; j++)
-
{
-
if (type == GetPieces(i, j))
-
{
-
count++;
-
}
-
}
-
}
-
return count;
-
}
-
-
EnumReversiResult ReversiBitBoard::IsGameOver()
-
{
-
if (!CanPlay(enum_ReversiPieces_Black) &&
-
!CanPlay(enum_ReversiPieces_White))
-
{
-
int black = GetCount(enum_ReversiPieces_Black);
-
int white = GetCount(enum_ReversiPieces_White);
-
-
if (black > white)
-
{
-
return enum_Reversi_Win_Black;
-
}
-
else
if (black < white)
-
{
-
return enum_Reversi_Win_White;
-
}
-
else
-
{
-
return enum_Reversi_Draw;
-
}
-
}
-
else
-
{
-
return enum_Reversi_Playing;
-
}
-
}
-
-
bool ReversiBitBoard::CanPlay(EnumReversiPiecesType type)
-
{
-
for (
int i =
0; i < REVERSI_MAX_ROW; i++)
-
{
-
for (
int j =
0; j < REVERSI_MAX_COLUMN; j++)
-
{
-
if (CanPlay(type, i, j))
-
{
-
return
true;
-
}
-
}
-
}
-
-
return
false;
-
}
-
-
bool ReversiBitBoard::CanPlay(EnumReversiPiecesType type,
char row_y,
char column_x)
-
{
-
if (enum_ReversiPieces_Null == GetPieces(row_y, column_x))
-
{
-
if (ReversiCheck(type, row_y, column_x))
-
{
-
return
true;
-
}
-
}
-
-
return
false;
-
}
-
-
bool ReversiBitBoard::ReversiCheck(EnumReversiPiecesType type,
-
char row_y,
char column_x)
-
{
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::UL) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::U) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::UR) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::L) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::R) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::DL) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::D) ||
-
ReversiCheck(type, row_y, column_x, &ReversiPoint::DR))
-
{
-
return
true;
-
}
-
-
return
false;
-
}
-
-
bool ReversiBitBoard::ReversiCheck(EnumReversiPiecesType type,
-
char row_y,
char column_x,
-
ReversiPointForward forward)
-
{
-
ReversiPoint point = {row_y, column_x};
-
EnumReversiPiecesType currType;
-
int search =
0;
-
-
(point.*forward)();
//向某方向搜寻
-
search++;
-
while(point.IsValid())
-
{
-
currType = GetPieces(point.m_row_y, point.m_column_x);
-
if (enum_ReversiPieces_Null != currType)
-
{
-
if (type == currType)
-
{
-
if (search >
1)
-
{
-
//找到的同色棋子与当前棋子坐标差超过1,则两子之间夹有不同色的棋子
-
return
true;
-
}
-
else
-
{
-
//否则两子是紧挨着的,该方向两子之间无子可以翻转
-
return
false;
-
}
-
}
-
else
-
{
-
//找到的是不同色的棋子,继续
-
(point.*forward)();
-
search++;
-
}
-
}
-
else
-
{
-
//一直找到空位也没找到,该方向没有同色棋子,无法翻转
-
return
false;
-
}
-
}
-
-
//超出棋盘范围都没有找到同色棋子,该方向没有同色棋子,无法翻转
-
return
false;
-
}
-
-
void ReversiBitBoard::DoReversi(EnumReversiPiecesType type,
-
char row_y,
char column_x)
-
{
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::UL))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::UL);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::U))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::U);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::UR))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::UR);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::L))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::L);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::R))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::R);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::DL))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::DL);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::D))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::D);
-
}
-
if (ReversiCheck(type, row_y, column_x, &ReversiPoint::DR))
-
{
-
DoReversi(type, row_y, column_x, &ReversiPoint::DR);
-
}
-
}
-
-
void ReversiBitBoard::DoReversi(EnumReversiPiecesType type,
-
char row_y,
char column_x,
-
ReversiPointForward forward)
-
{
-
ReversiPoint point = {row_y, column_x};
-
-
(point.*forward)();
-
while(point.IsValid())
-
{
-
if (type != GetPieces(point.m_row_y, point.m_column_x))
-
{
-
SetPieces(type, point.m_row_y, point.m_column_x);
-
(point.*forward)();
-
}
-
else
-
{
-
break;
-
}
-
}
-
}
-
-
void ReversiBitBoard::SwapPlayer()
-
{
-
EnumReversiPiecesType nexttype = SwapType(m_CurrType);
-
if (CanPlay(nexttype))
-
{
-
m_CurrType = nexttype;
-
}
-
}
-
-
EnumReversiPiecesType ReversiBitBoard::GetCurrType()
-
{
-
return m_CurrType;
-
}