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

2023-11-07 20:40

本文主要是介绍游戏开发(三)——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的棋盘,所以定义两个常量表示:

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;
}


这篇关于游戏开发(三)——WIN32 黑白棋(一)——棋局逻辑的设计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/366180

相关文章

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

Python基于wxPython和FFmpeg开发一个视频标签工具

《Python基于wxPython和FFmpeg开发一个视频标签工具》在当今数字媒体时代,视频内容的管理和标记变得越来越重要,无论是研究人员需要对实验视频进行时间点标记,还是个人用户希望对家庭视频进行... 目录引言1. 应用概述2. 技术栈分析2.1 核心库和模块2.2 wxpython作为GUI选择的优

利用Python开发Markdown表格结构转换为Excel工具

《利用Python开发Markdown表格结构转换为Excel工具》在数据管理和文档编写过程中,我们经常使用Markdown来记录表格数据,但它没有Excel使用方便,所以本文将使用Python编写一... 目录1.完整代码2. 项目概述3. 代码解析3.1 依赖库3.2 GUI 设计3.3 解析 Mark

利用Go语言开发文件操作工具轻松处理所有文件

《利用Go语言开发文件操作工具轻松处理所有文件》在后端开发中,文件操作是一个非常常见但又容易出错的场景,本文小编要向大家介绍一个强大的Go语言文件操作工具库,它能帮你轻松处理各种文件操作场景... 目录为什么需要这个工具?核心功能详解1. 文件/目录存javascript在性检查2. 批量创建目录3. 文件

最新Spring Security实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)

《最新SpringSecurity实战教程之表单登录定制到处理逻辑的深度改造(最新推荐)》本章节介绍了如何通过SpringSecurity实现从配置自定义登录页面、表单登录处理逻辑的配置,并简单模拟... 目录前言改造准备开始登录页改造自定义用户名密码登陆成功失败跳转问题自定义登出前后端分离适配方案结语前言

基于Python开发批量提取Excel图片的小工具

《基于Python开发批量提取Excel图片的小工具》这篇文章主要为大家详细介绍了如何使用Python中的openpyxl库开发一个小工具,可以实现批量提取Excel图片,有需要的小伙伴可以参考一下... 目前有一个需求,就是批量读取当前目录下所有文件夹里的Excel文件,去获取出Excel文件中的图片,并

Java逻辑运算符之&&、|| 与&、 |的区别及应用

《Java逻辑运算符之&&、||与&、|的区别及应用》:本文主要介绍Java逻辑运算符之&&、||与&、|的区别及应用的相关资料,分别是&&、||与&、|,并探讨了它们在不同应用场景中... 目录前言一、基本概念与运算符介绍二、短路与与非短路与:&& 与 & 的区别1. &&:短路与(AND)2. &:非短