前言:

本博文是我本科毕业论文的节选,目录和论文目录不对应。

有极少量的内容改动。

因为源代码已丢失,所以我决定重写代码。


目录

​1,摘要​

​2,主要研究内容​

​3,棋盘​

​4,棋位​

​5,棋面符号​

​6,着法​

​7,特殊规则​

​8,设计​

​9,代码​

​10,使用方法​

​11,实例​



1,摘要

目前市场上还只出现了很少一部分的棋类游戏的程序,大部分棋类没有对应的程序。这些棋类游戏的程序都是专门针对一个游戏开发出来的,没办法扩展成其他棋类游戏,因此每开发一种新的棋类游戏都需要不少的成本。实际上棋类游戏共性很强,因此设计并实现一个通用游戏框架是有必要的。

本课题对常见的100种棋类游戏进行了分析和抽象,提炼出了棋类游戏的通用逻辑和流程,并对流程中每个步骤的规则进行了总结和实现常见规则,利用回调函数机制和设计模式实现了可拓展性较强的框架,并在此框架基础上实现了五子棋、黑白棋和老虎棋这三种规则差别较大的棋,从而验证了框架的功能完备性。

利用本框架实现棋类游戏,简单的游戏可以自动生成整个游戏,复杂的游戏只需要提供描述特殊规则的函数也可以生成整个游戏,为了验证框架的性能本课题又实现了另外三种棋类游戏,结果表明本框架可以完成自动生成。

2,主要研究内容

(1)研究如何根据棋类游戏的分类和共性,抽象、提炼出棋类游戏的通用逻辑和流程。

(2)实现棋类游戏的通用编程框架。

(3)将每个步骤的规则进行总结并实现常见规则。

(4)在框架和已实现的规则基础上实现五子棋、黑白棋和老虎棋。

3,棋盘

棋盘不能太过于复杂,要选择可以用若干矩阵表示出来的棋盘。

常见的棋盘分为网络棋盘、点阵棋盘、容器棋盘。其中网络棋盘包括四边形棋盘、圆弧类棋盘、三角形棋盘、凸多边形棋盘、凹多边形棋盘、分枝类棋盘。对于某些棋盘可以做几何拓扑变换。例如下图展示了如何将西瓜棋的棋盘作拓扑变换。

本科毕业设计——基于C++的棋类游戏自动生成工具的设计与实现_黑白棋

这样的拓扑变换之后就可以用一个7*7的数组记录棋盘中的格点,要想记录连线还需要邻接矩阵。据《棋类游戏100种》一书中统计,在410种棋中,有186种是四边形棋盘。四边形棋盘是不需要拓扑变换的,其他224种棋类绝大部分也是可以通过拓扑变换变成矩阵可存储的格式的。

4,棋位

对于网络棋盘来说,棋位分为三种,顶点式棋位、格式棋位、线段式棋位。其中线段式棋位非常少见,不纳入考虑范围。

顶点式棋位(以网络棋盘的各个顶点作为棋位,如五子棋)和格式棋位(以网络棋盘的各个格作为棋位,如圈叉棋)都很常见,本质上差别不大,实际上即使是线段式棋位也可以通过变换变成顶点式棋位,但是会影响下棋的思路,不可取。

5,棋面符号

按照棋面符号的数量,可以分为无符号棋类、单一符号棋类和多符号棋类。无符号棋类就是所有棋子都是双方共用,单一符号棋类就是双方各一种棋子,多符号棋类就是类似象棋有多种棋子。无符号棋类不太常见,但是和单一符号棋类共性很强,可以纳入考虑范围,多符号棋类不纳入考虑范围。

例如五子棋这种只有黑棋和白棋的游戏,再例如老虎棋,只有老虎和羊2种棋子,都可以纳入考虑范围,而像象棋这种棋子复杂的棋本课题不考虑。

6,着法

棋类游戏的着法包括布子方式、走子方式、吃子方式等。

布子方式有一次性布子、轮流布子、分段布子三种,三种布子方式的不同只在于游戏初始化和下棋之后对棋盘的更新不同,所以都可以纳入考虑范围。

走子方式分为步行和跳行两类,而步行中最常见的两种是直行和斜行,这两种走子方式要考虑,其他的走子方式暂时可以不考虑。

吃子方式包括很多种,其中较常见的是围吃、走吃、跳吃、夹吃等,吃子方式不用限制,框架中有一个专门的函数判断每种局面情况下,被吃掉的所有棋子,并返回一个数组,所以这个不需要限制约束。

7,特殊规则

特殊规则分为两大类:第一类是为了保证游戏正常进行所必须的,例如围棋中的劫争禁手点,第二类是为了维护游戏的公平性和趣味性等,例如五子棋中的黑棋禁手。

如果去掉第一类特殊规则,那么游戏规则体系将是不完备的,会出现下棋下到程序崩溃的情况,但是如果去掉第二类特殊规则,游戏规则体系还是完备的。

在简化复杂度的需求之下,可以考虑去掉第二类特殊规则。实际上,市场上很多很多五子棋程序也是不考虑黑棋禁手的。可能是因为考虑到很多用户并不了解黑棋禁手,也有可能是有些程序员自己都不懂黑棋禁手,又或者可能是为了降低程序复杂度(尤其是带AI的程序)而不考虑黑棋禁手。

8,设计

本科毕业设计——基于C++的棋类游戏自动生成工具的设计与实现_黑白棋_02

8.1,解析模块的设计

(1)棋盘

目前只考虑2类最常见的棋盘,一种是矩形棋盘,另一种是在水平竖直线的基础上加上若干个米字型格点的棋盘,这2种棋盘涵盖的范围非常广,对于没有涵盖到的情况,也可以直接拓展,不影响框架的使用。这样,只需要初始化board[maxr][maxc]和ismi[maxr][maxc]这2个数组,即可记录棋盘的所有信息。board数组记录每个格点的信息,有4种取值,0表示一方的棋子,1表示另外一方的棋子,-1表示空,-2表示无效格子。ismi记录每个格点是否是米字型格点,有了board和ismi这2各数组,就可以确定所有的相邻关系,从而确定整个棋盘。

(2)棋规

棋规对应的是下棋模块的功能,下棋模块涉及到非常复杂的函数调用关系,这也是本框架的核心所在,即将复杂的逻辑总结并实现出来,使用本框架时就不需要再考虑这些复杂的逻辑了。如图显示了下棋模块的详细流程。

本科毕业设计——基于C++的棋类游戏自动生成工具的设计与实现_i++_03

本框架并没有实现将各种不同的行棋规则抽象成数据可描述的程度,本框架只是将棋类游戏的核心逻辑和流程抽象并实现,而流程的每一个具体的步骤,每个棋类游戏都不尽相同,本框架采用桥接模式解决这个问题,在主流程中有若干抽象函数,而各个棋类游戏负责提供具体函数,这样就完成了整个游戏。然而实际上,每个步骤对应的规则都有常见的几种,不同的棋类游戏其实就是每个步骤的不同规则的组合,所以本框架拟总结并实现每个步骤中常见的规则,这样,描述一个棋类游戏的棋规只需要一些id和参数,每个id对应一个步骤的某个具体规则,这样,由解析模块解析这些id就可以描述整个棋规。为了实现这个架构,本框架采用函数指针数组和回调机制,每个步骤就对应一个函数指针数组,数组中每个函数指针都是一个具体函数,在抽象函数中利用解析到的id即可回调。

8.2 棋盘模块的设计

根据解析模块得到的棋盘信息(board[maxr][maxc]和ismi[maxr][maxc]这2个数组)即可完成自动绘制棋盘,棋盘有2大类(一种是矩形棋盘,另一种是在水平竖直线的基础上加上若干个米字型格点的棋盘),分别采取不同的方案进行绘制,都是采用制表符,一种方案是紧密型棋盘,每个格点要么画棋子,要么画棋盘,格点之间紧密相连,这种方案适合矩形棋盘。另一种方案是松散型棋盘,每个格点要么画棋盘,要么打印空格,格点和相邻的格点之间全部用线条连接,这种方案适合在水平竖直线的基础上加上若干个米字型格点的棋盘,实际上所有棋盘都可以用这种方案来绘制,只是对于相邻关系比较复杂的棋盘需要拓展数据结构才能描述相邻关系,但这个绘制棋盘的方案仍然是可行的。

8.3 下棋模块的设计

下棋模块包括解析输入、判断能否落子、落子、判断胜负这4个步骤,每个步骤都根据解析模块解析出的id来确定对应的规则。下面一一讲述每个步骤是如何设计的。

(1)解析输入

行棋方式如果是落子就需要输入2个整数,行棋方式如果是移子就需要输入4个整数,有些游戏的双方,一方是落子而另一方是移子,所以为了统一数据结构,对于解析输入、判断能否落子、落子、判断胜负这4个步骤,每个步骤都需要2个id,分别表示下棋双方对应的规则。

(2)判断能否落子

行棋方式包括落子和移子两种,但是根据博弈论的思想,为了更加统一地表述,可以把移子理解并表述为在一个四维空间落子,所以这里的判断能否落子实际上指的是判断能否落子或移子,下面将分别讨论本框架是如何抽象出统一的逻辑的。

本框架将判断在目标坐标处能否落子的逻辑总结如下:首先,目标坐标必须在棋盘范围内,而且必须是空的,即没有双方棋子。其次,有些游戏还有其他附加规则,有些游戏没有。例如五子棋,没有其他规则,而黑白棋,附加规则是在目标坐标处落子之后,必须改变其他地方的至少一个棋子,否则不能落子。本框架将黑白棋的这种规则进行抽象,不需要具体判断落子会改变哪些棋子,而只需要调用落子函数,然后比较前后差异即可。

本框架将判断能否移子的逻辑总结如下:(为了表述方便,把移子表述成将出发坐标处的棋子移动到目标坐标处)首先,出发坐标和目标坐标必须在棋盘范围内,而且目标坐标必须是空的,而出发坐标必须有正在下棋方的棋子。其次,每种棋子移动的规则都不一样,但是可以归为三类:移动到相邻位置、跳吃子、固定方式的路线。这里的跳吃子指的是沿着一个方向跳过对方棋子并降落在空处,同时吃掉对方棋子。

(3)落子

这里的落子也是包含两种情况,一种是狭义的落子,一种是移子。

对于第一种情况,落子,落子函数只负责在指定坐标处落子,不负责其他任何功能。落子函数不做任何有关判断能否落子的计算,更不能调用判断能否落子的函数,否则会造成逻辑循环,可能会导致程序死循环。因为在判断能否落子时可能会调用落子函数,也就是落子函数接收的坐标不一定是真实可以落子的坐标,但一定是棋盘范围内的坐标,所以不需要对坐标做范围检测,但是必须保证程序不会造成死循环。

对于第二种情况,移子,因为判断能否落子时不需要调用落子函数,所以这里的落子函数可以调用判断能否落子的函数,因为有些棋子不止一种移子方式,所以需要调用判断能否落子的函数。

(4)判断胜负

判断胜负的函数,需要在任何时刻判断当前局面游戏是否已经结束,如果结束了,判断是先手胜还是后手胜还是平局。判断胜负是棋类游戏最多样的地方,几乎所有棋类游戏之间判断胜负的规则都不一样,不过本框架还是对最常见的几种情况进行了抽象并实现。最常见的几种情况是:第一种,如果还能落子就继续游戏,不能落子就判为负方。第二种,如果还能落子就继续游戏,不能落子就换对方落子,对方也不能落子就结束游戏。第三种,一方棋子数量到达某个范围就结束游戏。第四种,成形目棋类,一方形成某种棋形就结束游戏,否则就按照前三种情况的某一种来处理。除了这四种最常见的情况之外,也有其他特殊复杂规则,但是都类似于第四种情况,先判断特殊规则,如果没有结束游戏的话再按照前三种规则判断。

8.4 棋谱和悔棋模块的设计

完整的棋谱,应该包括两部分,每一步是谁下,每一步下的位置。因为有些棋并不是严格地双方轮流下,例如黑白棋,所以记录每一步是谁下能更好地显示信息。但是这并不是必须的,因为棋谱的最重要两个功能是方便调试和用来实现悔棋功能,调试的时候需要支持把整个棋谱粘贴到控制台,然后自动地执行每一步行棋,而悔棋就是直接读取棋谱中的数据然后执行每一步行棋。为了方便调试(实际上不仅是调试,当下棋者需要回顾之前的某一步的时候,也是同样的道理,利用棋谱直接粘贴跳转到需要的那一步,比不断悔棋方便些),在显示棋谱的时候要依次输出每一步下的位置,然后调试者(或者下棋者)可以直接复制棋谱,然后粘贴即可。所以,显示棋谱的时候是不能带每一步是谁下的信息的,棋谱可以存这个信息,但不能打印出来,而把棋谱直接粘贴到控制台的时候,程序就必须支持运行时自动判断每一步是谁下,所以棋谱是完全不需要存每一步是谁下的。这样,棋谱的设计就完成了,只存储每一步下的位置。下一章将详细介绍各个功能是如何实现的。

悔棋功能就是从开局开始,按照棋谱处理除掉最后一步的每一步,这样就实现了悔棋功能。有些游戏可以按照落子规则进行回溯,不需要棋谱即可实现悔棋,例如五子棋,只需要知道最后一步下的位置即可实现悔棋,当然,要实现连续不限次数的悔棋的话还是需要存棋谱,但是仍然不需要从开局开始一步步处理,这样时间效率就比较高。但是,有些游戏是无法按照落子规则进行回溯的,例如黑白棋,即使知道当前局面和最后落子位置,也无法推断出落子之前的局面,因为可能会有多种情况。所以,为了实现统一的悔棋功能,本框架采用的方案是所有游戏悔棋都利用棋谱从开局开始一步步处理。

9,代码

#include<iostream>
#include<string>
#include<vector>
#include<windows.h>
#include<iomanip>
#include<algorithm>
using namespace std;

#define MAXR 20 //棋盘最大行数
#define MAXC 20 //棋盘最大列数
#define PLAYER 2 //下棋人数
#define cinInt(x) while(!(cin>>x)){cin.clear();cin.ignore();}
#define cinInt2(x,y) cinInt(x)cinInt(y)

typedef void(*func_void)();
typedef void(*func_void2)(int, int);
typedef void(*func_void4)(int, int, int, int);
typedef string(*func_string1)(int);
typedef string(*func_string2)(int,int);
typedef bool(*func_bool)();
typedef bool(*func_bool2)(int, int);
typedef bool(*func_bool4)(int, int, int, int);

const char* dataInTxt = "D:\init.txt";//存放本地配置的文件
const int dr[8] = { -1, -1, 0, 1, 1, 1, 0, -1 };//用8个方向向量实现flat技术
const int dc[8] = { 0, 1, 1, 1, 0, -1, -1, -1 };

//begin 配置数据,缩进代表子参数
int row, col;//整个棋盘一共row行,编号1-row,一共col列,编号1-col
int board[MAXR + 2][MAXC + 2];//棋盘的每个格点,=1表示一方的棋子,=2表示另外一方的棋子,0表示空,-1表示无效格子
int boardStyle;//棋盘样式,=0表示只有横线和竖线的棋盘,=1表示除了横竖还有米字的棋盘,=2表示其他棋盘
int isMi[MAXR + 2][MAXC + 2];//每个格点是不是米字型,当boardStyle为一时有此参数
int chessman;//棋子样式
int displayContent;//额外显示内容,=0表示显示各方棋子数量,=1表示不显示
int turn;//轮到谁下,=1,=2
bool isEnd;//游戏是否结束
int howToChangeTurn;//如何换下棋方,=0表示正常轮换
int playMod;//表示行棋的方式,=0表示落子,=1表示移子,=2表示其他
int placeMod;//表示落子的具体规则,=0表示仅落子,=1表示夹吃
int placeOkMod;//表示能否落子的具体规则,=0表示有空格即可,=1表示有空格而且还要落子改变其他棋子
int moveMod;//表示移动棋子的具体规则,=0表示移到相邻处,=1表示跳跃并吃子,=2表示老虎棋
int moveOkMod;//表示能否移动棋子的具体规则,=0表示移到相邻处,=1表示跳跃并吃子,=2表示老虎棋
int ifEndMod;//表示是否结束具体规则,=1表示一方不可下就换另一方,双方都不能下就结束,=2表示五子棋,=3表示老虎棋
//end 配置数据
vector<vector<int>>manual;
int temp[MAXR + 2][MAXC + 2]; //board的备份

//begin 初始化
void initBoard()
{
for (int i = 0; i <= MAXR + 1; i++)for (int j = 0; j <= MAXC + 1; j++)
{
board[i][j] = -1, isMi[i][j] = 0;
}
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
{
cinInt(board[i][j]);
}
cinInt(boardStyle);
if (boardStyle == 1)
{
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
{
cinInt(isMi[i][j]);
}
}
}
void init()//读取配置,完成变量初始化
{
cinInt2(row,col);
if (row <= 0 || col <= 0)
{
isEnd = true;
return;
}
initBoard();
cinInt2(chessman, displayContent);
cinInt(turn);
isEnd = false;
cinInt2(howToChangeTurn, playMod);
if (playMod == 0)
{
cinInt2(placeMod, placeOkMod);
}
else if (playMod == 1)
{
cinInt2(moveMod, moveOkMod);
}
cinInt(ifEndMod);
}
//end 初始化

//begin 棋子样式
string getChessman0(int num)//黑白棋
{
if (num == 1)return "●";
if (num == 2)return "▲";
return "?";
}
string getChessman1(int num)
{
return "?";
}
func_string1 f_getChessman[] = { getChessman0, getChessman1 };
string getChessman(int num)
{
return f_getChessman[chessman](num);
}
//end 棋子样式

//begin 棋子数量
int getNum(int key)
{
int ans = 0;
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
ans += (board[i][j] == key);
return ans;
}
//end 棋子数量

//begin 棋盘
string getString0(int r, int c)
{
if (board[r][c] > 0)return getChessman(board[r][c]);
if (r == 1)
{
if (c == 1)return "┌";
if (c == col)return "┐";
return "┬";
}
if (r == row)
{
if (c == 1)return "└";
if (c == col)return "┘";
return "┴";
}
if (c == 1)return "├";
if (c == col)return "┤";
return "┼";
}
string getString1(int r, int c)
{
if (board[r][c] > 0)return getChessman(board[r][c]);
return " ";
}
func_string2 f_getString[] = { getString0, getString1 };
string getString(int r, int c)
{
return f_getString[boardStyle](r, c);
}
//end 棋盘

//begin 显示界面
void addedDisplay0()
{
cout << getChessman(1) << "有" << getNum(1) << "个,";
cout << getChessman(2) << "有" << getNum(2) << "个\n";
}
void addedDisplay1()
{
return;
}
int getOddEven(int k1,int k2)
{
int ans = 0;
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
{
if (board[i][j] > 0)ans += (i + k1) % 2 * ((j + k2) % 2);
}
return ans;
}
void addedDisplay2()
{
cout << "(奇,奇)有" << getOddEven(0, 0) << "个,(偶,偶)有" << getOddEven(1, 1) << "个,";
cout << "(奇,偶)有" << getOddEven(0, 1) << "个,(偶,奇)有" << getOddEven(1, 0) << "个\n";
}
func_void f_addedDisplay[] = { addedDisplay0, addedDisplay1, addedDisplay2 };

void display0()
{
cout << ' ';
for (int i = 1; i <= col; i++)cout << ' ' << i % 10;
for (int i = 1; i <= row; i++)
{
cout << endl << setw(2) << i;
for (int j = 1; j <= col; j++)
{
cout << getString(i, j);
}
}
cout << endl;
}
void display1()
{
for (int i = 1; i <= col; i++)cout << " " << i % 10;
for (int i = 1; i <= row; i++)
{
cout << endl << setw(2) << i;
for (int j = 1; j <= col; j++)
{
cout << getString(i, j);
if (j == col)continue;
if (board[i][j] != -1 && board[i][j + 1] != -1)cout << "┄";
else cout << " ";
}
if (i == row)continue;
cout << endl << " ";
for (int j = 1; j <= col; j++)
{
if(board[i][j] != -1 && board[i+1][j] != -1)cout << "┆";
else cout << " ";
if (isMi[i][j] || isMi[i + 1][j + 1])cout << "╲";
else if (isMi[i + 1][j] || isMi[i][j + 1])cout << "╱";
else cout << " ";
}
}
cout << endl;
}
func_void f_display[] = { display0, display1 };
void display()
{
system("cls");
f_display[boardStyle]();
f_addedDisplay[displayContent]();
cout << "轮到" << turn << getChessman(turn) << "下\n";
}

void displayManual()//显示棋谱
{
vector<int>tmp;
for (auto it = manual.begin(); it != manual.end(); it++)
{
tmp = *it;
for (auto it = tmp.begin(); it != tmp.end(); it++)cout << *it << " ";
}
Sleep(5000);
}
//end 显示界面

//begin 下棋方控制
void changeTurn0() //黑白棋
{
turn = 3 - turn; //turn在1,2之间轮换
}
void changeTurn1()
{
return;
}
func_void f_changeTurn[] = { changeTurn0, changeTurn1 };
void changeTurn()
{
return f_changeTurn[howToChangeTurn]();
}
//end 下棋方控制

//begin 落子
void place0(int r, int c) //仅落子,如五子棋
{
return;
}
void place1(int r, int c)//夹吃,如黑白棋
{
int k = board[r][c], tr, tc;
for (int i = 0; i < 8; i++)
{
tr = r, tc = c;
while (true)
{
tr += dr[i], tc += dc[i];
if (tr <= 0 || tr >= row + 1 || tc <= 0 || tc >= col + 1)break;
if (board[tr][tc] == 3 - k)continue;
if (board[tr][tc] == k)
{
for (int jr = r + dr[i], jc = c + dc[i]; jr != tr || jc != tc; jr += dr[i], jc += dc[i])
board[jr][jc] = k;
}
break;
}
}
}
func_void2 f_place[] = { place0, place1 };
void place(int r, int c)
{
board[r][c] = turn;
f_place[placeMod](r, c);
}
//end 落子

//begin 移子
void move0(int r, int c, int r2, int c2)//仅移子
{
int tmp = board[r][c];
board[r][c] = board[r2][c2], board[r2][c2] = tmp;
}
void move1(int r, int c, int r2, int c2)//移子或跳吃子
{
move0(r, c, r2, c2);
if (abs(r - r2)>1 || abs(c-c2)>1)board[(r + r2) / 2][(c + c2) / 2] = 0;
}
void move2(int r, int c, int r2, int c2)//双方不同
{
if (turn == 1)move0(r, c, r2, c2);
else move1(r, c, r2, c2);
}
func_void4 f_move[] = { move0, move1, move2 };
void move(int r, int c, int r2, int c2)
{
f_move[moveMod](r, c, r2, c2);
}
//end 移子

//begin 能否落子
bool placeOk0(int r, int c)//五子棋,圈叉棋
{
return true;
}
int getChangeNum(int r, int c)//落子前后有多少棋子改变
{
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
temp[i][j] = board[i][j];
place(r, c);
int changeNum = 0;
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
changeNum += (temp[i][j] != board[i][j]), board[i][j] = temp[i][j];
return changeNum;
}
bool placeOk1(int r, int c)//黑白棋
{
return getChangeNum(r, c) > 1;
}
func_bool2 f_placeOk[] = { placeOk0, placeOk1 };
bool placeOk(int r, int c) //能否落子
{
if (r<1 || r>row || c<1 || c>col)return false;
if (board[r][c] !=0)return false;
return f_placeOk[placeOkMod](r, c);
}
//end 能否落子

//begin 能否移子
bool moveOk0(int r, int c, int r2, int c2)//移到相邻处
{
if (board[r][c] != turn)return false;//只能移自己的棋
if (r == r2 && c == c2)return false;
if (abs(r - r2) > 1 || abs(c - c2) > 1)return false;//不是8邻居
if (r == r2 || c == c2)return true;//上下左右
return isMi[r][c] || isMi[r2][c2];//有斜线
}
bool moveOkJump(int r, int c, int r2, int c2)
{
bool ans = false;
if (r == r2 && abs(c - c2) == 2 || c == c2 && abs(r - r2) == 2 || abs(c - c2) == 2 && abs(r - r2) == 2 && isMi[(r + r2) / 2][(c + c2) / 2])
{
ans = board[(r + r2) / 2][(c + c2) / 2] == 3 - board[r][c];
}
return ans;
}
bool moveOk1(int r, int c, int r2, int c2)//移子或跳吃
{
if (board[r][c] != turn)return false;//只能移自己的棋
return moveOk0(r, c, r2, c2) || moveOkJump(r, c, r2, c2);
}
bool moveOk2(int r, int c, int r2, int c2)
{
if (board[r][c] != turn)return false;//只能移自己的棋
if (turn == 1)return moveOk0(r, c, r2, c2);
else return moveOk1(r, c, r2, c2);
}
bool moveOk3(int r, int c, int r2, int c2)
{
if (board[r][c] != turn)return false;//只能移自己的棋
return true;
}
bool moveOk4(int r, int c, int r2, int c2)
{
return moveOkJump(r, c, r2, c2);
}
func_bool4 f_moveOk[] = { moveOk0, moveOk1, moveOk2, moveOk3, moveOk4 };
bool moveOk(int r, int c, int r2, int c2)
{
if (r<1 || r>row || c<1 || c>col)return false;
if (r2<1 || r2>row || c2<1 || c2>col)return false;
if (board[r2][c2] != 0)return false;
return f_moveOk[moveOkMod](r,c,r2,c2);
}
//end 能否移子

//begin 是否结束
bool hasPlaceOk()
{
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
if (placeOk(i, j))return true;
return false;
}
bool ifEnd0()//不可下就结束
{
return isEnd = !hasPlaceOk();
}
bool ifEnd1()//黑白棋,一方不可下就换另一方,双方都不能下就结束
{
isEnd = !hasPlaceOk();
if (!isEnd)return false;
changeTurn();
return isEnd = !hasPlaceOk();
}
int numInLine(int row, int col, int u)//坐标(row,col),方向向量u,返回该方向有多少连续同色棋子,不包括此坐标本身
{
int i = row + dr[u], j = col + dc[u], sum = 0, ref = board[row][col];
if (ref == 0)return 0;
while (board[i][j]==ref)sum++, i += dr[u], j += dc[u];
return sum;
}
int maxNumInLine(int row, int col)//坐标(row,col),返回四条直线最多的有多少连续同色棋子,包括此坐标本身
{
int ans = 0;
for (int u = 0; u < 4; u++)ans = max(ans, numInLine(row, col, u) + numInLine(row, col, u + 4));
return ans+1;
}
bool ifEnd2()//五子棋
{
if (!hasPlaceOk())
{
return isEnd = true;
}
vector<int>tmp = *(manual.end() - 1);
if (maxNumInLine(tmp[0], tmp[1]) >= 5)return isEnd = true;
return false;
}
bool hasMoveOk()
{
for (int i = 1; i <= row; i++)for (int j = 1; j <= col; j++)
for (int ii = 1; ii <= row; ii++)for (int jj = 1; jj <= col; jj++)
if (moveOk(i, j, ii, jj))return true;
return false;
}
bool ifEnd3()
{
if (turn == 1 && getNum(1) <= 5)return isEnd = true;
return isEnd = !hasMoveOk();
}
bool ifEnd4()//圈叉棋
{
vector<int>tmp = *(manual.end() - 1);
if (maxNumInLine(tmp[tmp.size() - 2], tmp[tmp.size() - 1]) >= 3)return isEnd = true;
return false;
}
bool ifEnd5()
{
if (getNum(2) == 3)playMod = 1, moveMod = moveOkMod = 0;
return ifEnd4();
}
bool ifEnd6()
{
if (getNum(2) == 3)playMod = 1, moveMod = 0, moveOkMod = 3;
return ifEnd4();
}
bool ifEnd7()//独立钻石棋
{
if (getNum(1)+getNum(2) <= 1)return isEnd = true;
return isEnd = !hasMoveOk();
}
func_bool f_ifEnd[] = { ifEnd0, ifEnd1, ifEnd2, ifEnd3, ifEnd4, ifEnd5, ifEnd6, ifEnd7 };
bool ifEnd()
{
return f_ifEnd[ifEndMod]();
}
//end 是否结束

//begin 行棋
void play0()//输入坐标落子
{
display();
int r=-1, c=-1;
cout << "输入落子坐标(行,列)\n输入0 0可查看棋谱\n";
cinInt2(r,c);
if (r == 0 && c == 0)
{
displayManual();
return play0();
}
if (placeOk(r, c))
{
place(r, c);
vector<int>tmp;
tmp.insert(tmp.end(), r);
tmp.insert(tmp.end(), c);
manual.insert(manual.end(), tmp);
}
else
{
cout << "输入坐标有误,请重新输入";
Sleep(1500);//暂停...ms
play0();
}
}
void play1()//输入坐标移子
{
display();
int r, c, r2, c2;
cout << "输入移子坐标(行,列)->(行,列)\n输入0 0可查看棋谱\n";
cinInt2(r, c);
if (r == 0 && c == 0)
{
displayManual();
return play1();
}
cinInt2(r2, c2);
if (moveOk(r, c, r2, c2))
{
move(r, c, r2, c2);
vector<int>tmp;
tmp.insert(tmp.end(), r);
tmp.insert(tmp.end(), c);
tmp.insert(tmp.end(), r2);
tmp.insert(tmp.end(), c2);
manual.insert(manual.end(), tmp);
}
else
{
cout << "输入坐标有误,请重新输入";
Sleep(1500);//暂停...ms
play1();
}
}
func_void f_play[] = { play0, play1 };
void play()
{
while (isEnd == false)
{
f_play[playMod]();
display();
changeTurn();
ifEnd();
}
cout << "游戏结束";
Sleep(5000);
}
//end 行棋

int main()
{
freopen(dataInTxt, "r", stdin);
init();
freopen("CON", "r", stdin);
cin.clear();
play();
return 0;
}

代码持续更新中


10,使用方法

在"D:\init.txt"中写配置数据,运行程序即可。

本程序只读其中的数字,中文和字母可以随便写。


11,实例

本科毕设系列(1)——五子棋

​javascript:void(0)​

本科毕设系列(2)——黑白棋

​javascript:void(0)​

本科毕设系列(3)——老虎棋

 ​​javascript:void(0)​

本科毕设系列(4)——圈叉棋

​javascript:void(0)​

本科毕设系列(5)——Three Man Morris(米字棋)

​javascript:void(0)​

本科毕设系列(6)——Three Man Morris 2(米字棋2)

​javascript:void(0)​

本科毕设系列(7)——独立钻石棋

​javascript:void(0)​

本科毕设系列(8)——标准独立钻石棋

​javascript:void(0)​