五子棋程序设计实现技术文档

news/2024/10/17 20:26:53/

五子棋程序设计实现文档

文章目录

  • 五子棋程序设计实现文档
  • 前言
  • 一、运行截图
  • 二、基本思路
    • 1.实现过程
    • 2.落子
    • 3.悔棋
    • 4.人机对战的实现
      • 1.机器人落子逻辑**
      • 2.改进胜负判断方法
      • 3.计算目标点的权值(白棋ai使用)
      • 4.计算目标点的权值(黑棋ai使用)**
    • 4.扩展功能
      • 1.智能“提示”功能


前言

博弈算法的设计。通过五子棋,国际象棋,跳棋等博弈游戏的软件设计,巩固本课程所学的知识,包括状态空间搜索方法、与或树的一般搜索方法、博弈树的搜索及α-β剪枝技术等。

一、运行截图

双人对战界面(此处的双人对战指两人用同一手机轮流点击落子)
在这里插入图片描述

人机对战界面
在这里插入图片描述
主页面:
在这里插入图片描述
在这里插入图片描述
项目结构目录
在这里插入图片描述

二、基本思路

先考虑双人对战的实现思路,基本逻辑为:
· 黑方点击屏幕,反馈相应坐标
· 在对应坐标上显示黑子
· 对这个黑子判断其是否造成五子相连
· 如果没有造成五子相连,则游戏继续
· 白方点击屏幕,反馈坐标
· 在对应坐标显示白子
· 对这个白子判断其是否造成五子相连
· 如果没有造成五子相连,则游戏继续
以此不断循环

1.实现过程

首先我们需要实现一些基本功能
棋盘的生成
首先我们需要生成棋盘

这里我使用了wx:for循环生成了15*15共225个小方格

<!--index.wxml-->
<view class="bg"><view class="container_1"><view class="chessBoard"><view class="block" wx:for="{{chessBoard}}" wx:key="{{index}}" bindtap="step" data-pos="{{index}}"><view class="{{item}}"></view></view></view></view><button bindtap="restart" type="warning" class="restart-but">重新开始</button><button bindtap="regretChess" type="warning" class="restart-but">悔棋</button><button bindtap="getBackToMenu" type="warning" class="restart-but">返回</button><text>developed by zhx</text>
</view>
//棋盘数组初始化
function initChessBoard() {  let arr = [];//选择使用一维数组来存储棋子,因为使用二维数组较为复杂for (let i = 0; i < 225; i++) {arr.push('0')}return arr;
}
var Pi = Page({data: {logs: [],chessBoard: initChessBoard(),   //棋盘result: "",count: 0,},directions: [[1, 0],[0, 1],[1, 1],[-1, 1]],

这里使用一个一维数组chessBoard储存225个位置的棋子情况,0为空,white为白子,black为黑子。

生成的小格子的样式在正常运行时应该设置为透明的,为了清楚地向读者展示小格子的生成与排布情况, 我暂时将小格子设置为半透明,如下图
在这里插入图片描述
可以看到,每个格子都位于棋盘的交叉点(棋盘是背景图,这一步需要通过不断调整大小与位置来实现),而棋子其实是格子内部的不同显示样式。这部分的wxss代码如下,block是透明小方格,white是白棋,black是黑棋。

.block{width:6%;height:6%;background-color:rgba(0, 0, 0, 0);margin-left:0.6%;margin-top:0.6%;float:left;
}.white{width:100%;height:100%;border-radius:50%;background-color:#ffffff;box-shadow: gray 3px 3px 5px;
}
.black{width:100%;height:100%;border-radius:50%;background-color:#000000;box-shadow: gray 3px 3px 5px;
}

js代码为:

//落子step(event) {var pos = event.currentTarget.dataset.pos;if (this.data.chessBoard[pos] == "white" || this.data.chessBoard[pos] == "black") return;this.data.count++;if (this.data.count % 2)    //这里认为1代表黑棋,2代表白棋this.data.chessBoard[pos] = "black";else this.data.chessBoard[pos] = "white";stack.push(pos)     //压入栈中,用于处理悔棋this.setData({    //将逻辑层数据传到视图层chessBoard: this.data.chessBoard})this.judge(pos);},

2.落子

每个棋盘小格子都有点击事件step,用于落子操作
点击后,先判断对应位置是否已经有棋子
如果没有,则落入相应颜色的子(写入chessBoard数组)

判断胜负
具体实现看以下代码及注释即可

其实这里使用一维数组来判断是有bug的,会导致把最左边一个与上一行的最右边一格认为是在同一行。要改进也很简单,就是把一维数组转化为二维数组。

  //判断胜负judge(pos) {var that = this;var color = this.data.chessBoard[pos];var x0 = parseInt(pos / 15),  //第几列y0 = pos % 15, x, y, round;   //第几行//以x0*15+y0便可得到棋子的下标for (var i = 0; i < 4; i++) {   //统计四个方向var number = 0;//正向先检测五个round = 0;for (x = x0, y = y0; round < 5; x += this.directions[i][0], y += this.directions[i][1], round++) {if (this.data.chessBoard[15 * x + y] == color) {number++;}else {break;}}//相反方向再检测五个round = 0;for (x = x0, y = y0; round < 5; x -= this.directions[i][0], y -= this.directions[i][1], round++) {if (this.data.chessBoard[15 * x + y] == color) {number++;}else {break;}}//检测到五子连珠if (number >= 6) {    //6个是因为落子被统计了两次if(color=="black")var t_color="黑方"elsevar t_color="白方"//跳出对话框wx.showModal({title: t_color + '获胜!',content: '再来一局?',success: function (res) {if (res.confirm) {that.goplaygame ();}else if(res.cancel){wx.navigateTo({url: '../Menu/Menu',})}},})}}},

3.悔棋

然后是悔棋模块,简单地利用一下栈即可。
(入栈操作在落子函数中)这里我定义了黑方白方都只有5次的悔棋机会。

  data: {logs: [],chessBoard: initChessBoard(),   //棋盘result: "",count: 0,whitenum:5,//白棋悔棋次数blacknum:5 //黑棋悔棋次数},
  //悔棋regretChess(){var getnum =0if(stack.length!=0){//从栈中删除两个元素//判断是黑棋悔棋还是白棋悔棋if (this.data.count % 2) {//判断当前是否还有机会悔棋if(this.data.blacknum!=0){var returnChessPos=stack.pop();this.data.chessBoard[returnChessPos]="0";getnum=this.data.blacknum-1;this.setData({    chessBoard: this.data.chessBoard,blacknum:getnum  })this.data.count--;}else{wx.showModal({content: '你已经没有悔棋机会',})}}else{if(this.data.whitenum!=0){var returnChessPos=stack.pop();this.data.chessBoard[returnChessPos]="0";getnum=this.data.whitenum-1;this.setData({    chessBoard: this.data.chessBoard,whitenum:getnum  })this.data.count--;}else{wx.showModal({content: '你已经没有悔棋机会',})}} //将逻辑层数据传到视图层}else{wx.showModal({content: '当前没有落子',})return;}},

4.人机对战的实现

人机对战中的电脑应该如何落子?这里我选择了最简单的权值法,即赋予每种棋子排列方式不同的权值,每次电脑落子时,先测算所有可落子点的权值,在权值最大的点进行落子。

至于权值表里要设置哪些排列以及权值如何设置,可以参考我下面这个表格,数据经过测试与微调得来。

权值表
以1代表黑棋,2代表白棋。例如:112即代表“黑黑白”排列。由于玩家总是黑棋,所以认为黑棋是敌方棋子。
在这里插入图片描述

var weightDic=[   //权值表,默认1黑2白,共20种情况'1',20,'11',410,     '111',500,'1111',8000,"12",4,"112", 70,'1112', 450,"11112", 8000,"2", 8,"22", 80,"222", 470,"2222", 9000,"22221",10000,"21", 6,"221", 60,"2221", 600,"121", 5,"1221", 5,"2112", 5,"212", 5,
];

1.机器人落子逻辑**

为了便于操作以及修正人人对战中胜负判断的BUG,我们先把一维棋子数组转化为二维的

//初始化二维数组for(var i=0;i<15;i++){this.data.chessBoard_2d.push([])for(var j=0;j<15;j++){this.data.chessBoard_2d[i].push('0')}}
 //落子step(event) {var pos = event.currentTarget.dataset.pos;console.log(pos)if (this.data.chessBoard[pos] == "white" || this.data.chessBoard[pos] == "black") return;this.data.chessBoard[pos] = "black";stack.push(pos)     //压入栈中this.setData({    //将逻辑层数据传到视图层chessBoard: this.data.chessBoard})//将棋盘转化为二维数组,便于胜负判定与AI计算for(var i=0;i<15;i++){for(var j=0;j<15;j++){this.data.chessBoard_2d[i][j]=this.data.chessBoard[i*15+j];}}this.judge(pos);// console.log(this.data.chessBoard_2d)if(win==0){this.AI_step()//机器人落子跟在玩家之后}},

每当玩家落子后,判断胜负,然后紧接着执行机器人落子AI_step():

//AI落子AI_step(){for(let i=0;i<225;i++){this.data.chessScore[i]=0;  //权值清零}//遍历棋盘的所有位置for(let i=0;i<225;i++){if(this.data.chessBoard[i]=='0'){   //选出空点进行权值计算this.calculatePositionWeight_white(i);}}//选出权值最大的落子点var max,maxPos=0;// console.log(this.data.chessScore)for(let i=1,max=this.data.chessScore[0];i<225;i++){if(max<this.data.chessScore[i]){max=this.data.chessScore[i];maxPos=i;}}// console.log(max)//落子this.data.chessBoard[maxPos]="white";  stack.push(maxPos)   //压入栈中//将棋盘转化为二维数组,便于胜负判定与AI计算for(var i=0;i<15;i++){for(var j=0;j<15;j++){this.data.chessBoard_2d[i][j]=this.data.chessBoard[i*15+j];}}this.judge(maxPos)this.setData({    //将逻辑层数据传到视图层chessBoard: this.data.chessBoard})console.log('AI:'+maxPos)},

其中chessScore数组储存每个空点的权值。那么这里最关键的部分就是这个chessScore数组中各个点的权值的计算方法。

2.改进胜负判断方法

在分析上面这个问题之前,我们先把之前人人对战里的胜负判断BUG解决一下,其实就是把一维数组改为二维数组再进行判断。

//判断胜负judge(pos) {var color = this.data.chessBoard[pos];var y0 = parseInt(pos / 15),  //第几行x0 = pos % 15, x, y, round;   //第几列//以y0*15+x0便可得到棋子的下标for (var i = 0; i < 4; i++) {   //统计四个方向var number = 0;//正向先检测五个round = 0;for (x = x0, y = y0; round < 5; x += this.directions[i][0], y += this.directions[i][1], round++) {if (x>=0&&x<=14&&y>=0&&y<=14&&this.data.chessBoard_2d[y][x] == color) {number++;}else {break;}}//相反方向再检测五个round = 0;for (x = x0, y = y0; round < 5; x -= this.directions[i][0], y -= this.directions[i][1], round++) {if (x>=0&&x<=14&&y>=0&&y<=14&&this.data.chessBoard_2d[y][x] == color) {number++;}else {break;}}//检测到五子连珠if (number >= 6) {    //6个是因为落子棋子被统计了两次win=1;if(color=="black")var t_color="黑方"elsevar t_color="白方"//跳出对话框wx.showModal({title: t_color + '获胜!',content: '再来一局?',success: function (res) {if (res.confirm) {wx.navigateTo({url: "./Player_VS_AI"});}else{win=0;}},})}}},

回到权值计算方法,我利用了calculatePositionWeight_white(i)函数,对于每个输入的空点坐标,这个函数都计算并返回了它的权值。(函数后面的“_white”表示这个是当机器人执白子时使用的函数,如果要对游戏功能进行扩展,比如加入提示功能,机器人就会执黑子,算法就需要做一些微调)
下面是这个函数的实现过程:

3.计算目标点的权值(白棋ai使用)

 //计算目标点的权值(白棋ai使用)calculatePositionWeight_white(position){var y0 = parseInt(position / 15),  //第几行x0 = position % 15,   //第几列maxWeight=0;//计算目标点权值var right=this.countChessWeightInOneDirection_white(x0,y0,1,0)var left=this.countChessWeightInOneDirection_white(x0,y0,-1,0)var down=this.countChessWeightInOneDirection_white(x0,y0,0,1)var up=this.countChessWeightInOneDirection_white(x0,y0,0,-1)var right_up=this.countChessWeightInOneDirection_white(x0,y0,1,-1)var left_up=this.countChessWeightInOneDirection_white(x0,y0,-1,-1)var left_down=this.countChessWeightInOneDirection_white(x0,y0,-1,1)var right_down=this.countChessWeightInOneDirection_white(x0,y0,1,1)var weightArr=[right,left,down,up,right_up,left_down,left_up,right_down]//这里对于权值的计算可以优化,从而解决双活三的类似问题//当前仅把目标点八个方向上的权值最大的布局作为目标点的权值//效果:无法处理‘11-11’型问题,容易被下套// for(var i=0;i<8;i++){ //   if(maxWeight<=weightArr[i]){//     maxWeight=weightArr[i]//   }// }//思路:将目标点两端的布局权重相加再求最大值//效果:可以解决一些类似双活三的问题,但是存在部分落子不正常的情况,这里必须要根据实际情况对权值进行一些修改,处理一些比较明显的问题之后效果就得到很大改善,目前对于隔子下套已经可以较好应对,但是双活三问题依旧存在//这里可以设置五子棋人机难度for(var i=0;i<4;i++){if(maxWeight<=(weightArr[2*i]+weightArr[2*i+1])){maxWeight=(weightArr[2*i]+weightArr[2*i+1])}}this.data.chessScore[position]=maxWeight},

4.计算目标点的权值(黑棋ai使用)**

  //计算目标点的权值(黑棋ai使用)calculatePositionWeight_black(position){var y0 = parseInt(position / 15),  //第几行x0 = position % 15,   //第几列maxWeight=0;//计算目标点权值var right=this.countChessWeightInOneDirection_black(x0,y0,1,0)var left=this.countChessWeightInOneDirection_black(x0,y0,-1,0)var down=this.countChessWeightInOneDirection_black(x0,y0,0,1)var up=this.countChessWeightInOneDirection_black(x0,y0,0,-1)var right_up=this.countChessWeightInOneDirection_black(x0,y0,1,-1)var left_up=this.countChessWeightInOneDirection_black(x0,y0,-1,-1)var left_down=this.countChessWeightInOneDirection_black(x0,y0,-1,1)var right_down=this.countChessWeightInOneDirection_black(x0,y0,1,1)var weightArr=[right,left,down,up,right_up,left_down,left_up,right_down]//这里对于权值的计算可以优化,从而解决双活三的类似问题//当前仅把目标点八个方向上的权值最大的布局作为目标点的权值//效果:无法处理‘11-11’型问题,容易被下套// for(var i=0;i<8;i++){ //   if(maxWeight<=weightArr[i]){//     maxWeight=weightArr[i]//   }// }//思路:将目标点两端的布局权重相加再求最大值//效果:可以解决一些类似双活三的问题,但是存在部分落子不正常的情况,这里必须要根据实际情况对权值进行一些修改,处理一些比较明显的问题之后效果就得到很大改善,目前对于隔子下套已经可以较好应对,但是双活三问题依旧存在//这里可以设置五子棋人机难度for(var i=0;i<4;i++){if(maxWeight<=(weightArr[2*i]+weightArr[2*i+1])){maxWeight=(weightArr[2*i]+weightArr[2*i+1])}}this.data.chessScore[position]=maxWeight},

其中countChessWeightInOneDirection_white函数如下。weightDic是权值表。
这个函数先获取空点的某个方向上的相连的棋子的布局,如“空黑黑白白白”则获得shape的值为11222,但是如果中间不相连则指获得相连部分的布局,如“空黑黑空白白”则只获得前两个黑子的布局,shape即为11.
然后再将获得的shape与权值表比对,获得权值。这里的权值只是这一个方向上的权值,所以为了对所有方向都进行扫描,我们需要改变方向并执行八次。

 //计算某一方向上的棋子情况,必须与目标点相邻,返回权值(白棋ai使用)countChessWeightInOneDirection_white(x,y,direction_x,direction_y){this.data.count=0this.data.shape=''while(this.data.count<=4){x+=direction_x;y+=direction_y;this.data.count++;if(x>=0&&x<=14&&y>=0&&y<=14&&this.data.chessBoard_2d[y][x]=='black'){this.data.shape+='1'}else if(x>=0&&x<=14&&y>=0&&y<=14&&this.data.chessBoard_2d[y][x]=='white'){this.data.shape+='2'}else {break}}while(1){var i;for(i=0;i<40;i++){if(this.data.shape==weightDic[i]){return weightDic[i+1]}}return 0;}},

当我们获得了一个空点的周围八个方向的权值,那我们如何计算这个点的最终权值呢?最初我的办法是将八个权值的最大值作为这个点的权值,但是这种方法有个比较严重的问题,就是当存在形如“黑黑空黑黑”时,中间这个点白方必须落子,但这种方法获得的这个点的权值只有410,也就是说如果这个棋盘上黑方在某处还有一个“空黑黑黑空”,对应权值为500,白棋机器人就会把棋子落在这三个黑子旁边,那么这样的机器人就宛如人工智障显得很傻。

我的改进方法其实很简单,就是把相反方向的两个权值两两相加,在把这四对想法方向的权值和的最大值作为这个点的权值,这样就较好地解决了“被下套”的问题。

4.扩展功能

1.智能“提示”功能

我利用这个机器人算法给自己添加一个“提示”功能,让机器人帮你(黑棋)落子。(这样就实现了左右互搏)
只要在扫描获取shape值时,把黑棋当成2,白棋当成1即可
在这里插入图片描述


http://www.ppmy.cn/news/563044.html

相关文章

Windows平台下C++五子棋项目实战开发

1. 项目目标 2. 效果演示 3. 创建项目 4. 项目框架设计 4.1 设计项目框架 4.2 根据设计框架创建类 5. 给类添加主要接口 5.1 设计棋盘类Chess的主要接口 5.2 设计AI类的主要接口 5.3 设计Man类的主要接口 5.4 设计ChessGame的主要接口 5.5 添加各个接口的具体实现 6. 实…

基于java的五子棋游戏设计

技术&#xff1a;Java、JSP等摘要&#xff1a;随着互联网迅速的发展&#xff0c;网络游戏已经成为人们普遍生活中不可或缺的一部分&#xff0c;它不仅能使人娱乐&#xff0c;也能够开发人的智力&#xff0c;就像本文所主要讲的五子棋游戏一样能挖掘人们聪明的才干与脑袋的机灵程…

用Java实现五子棋对弈

目录 题目展示 题目分析 代码实现 结果展示 题目展示 1.使用二维数组存储五子棋棋盘 如下图 2.在控制台通过Scanner输入黑白棋坐标( 表示二维数组坐标),使用实心五角星和空心五角星表示黑白棋子。 如下图: 输入后重新输出棋盘如下图: 白棋输入后如下图&#xff1a; 黑白棋…

C++实现基于博弈树的5x5一子棋人机对战

基于博弈树的5x5一子棋人机对战 919106840637实验2 这是智能计算三个课程实验的第二个实验&#xff0c;即博弈树搜索 。我之前对博弈树的了解不多&#xff0c;所以实现起来比较的简略&#xff0c;仅仅是基本达到了要求 实验语言 C 实验内容 实践博弈树搜索——“5x5格子的一…

Java五子棋(人机版),昨天买的棋子今天就用不上了

Java五子棋&#xff0c;老程序员也花了3天 作者简介 作者名&#xff1a;编程界明世隐 简介&#xff1a;CSDN博客专家&#xff0c;从事软件开发多年&#xff0c;精通Java、JavaScript&#xff0c;博主也是从零开始一步步把学习成长、深知学习和积累的重要性&#xff0c;喜欢跟广…

文科生从0学Python转数据分析学习建议避坑指南

我本科是财务管理&#xff0c;文科专业&#xff0c;零基础学习Python转行数分后&#xff0c;现在我的日常工作都离不开它。 接下来&#xff0c;给各位跟我一样无编程经验的朋友一些学习的建议 目标导向&#xff1a;先搞清楚为啥要学 Python几乎可以做任何事&#xff0c;但我…

数据库是如何工作的

数据库是如何工作的 注&#xff1a; 本文翻译自db_tutorial. 数据库计算机世界的一个基础软件&#xff0c;要想深入了解数据库&#xff0c;就不得不思考如下几个问题&#xff1a; 数据以什么格式保存&#xff1f;&#xff08;在内存和磁盘上&#xff09;它何时从内存移动到磁…

日立医疗影像诊断业务正式加入富士胶片集团

富士胶片株式会社宣布&#xff0c;于2021年3月31日完成对株式会社日立制作所&#xff08;以下简称“日立”&#xff09;旗下影像诊断相关业务的收购程序&#xff0c;标志着日立为继承相关业务而成立的新公司“富士胶片医疗健康”正式成为富士胶片集团全资子公司&#xff0c;全新…