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

Posted 沉默着忍受

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五子棋程序设计实现技术文档相关的知识,希望对你有一定的参考价值。

五子棋程序设计实现文档

文章目录


前言

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

一、运行截图

双人对战界面(此处的双人对战指两人用同一手机轮流点击落子)

人机对战界面

主页面:


项目结构目录

二、基本思路

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

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="黑方"
        else
          var 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 =0
    if(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以上是关于五子棋程序设计实现技术文档的主要内容,如果未能解决你的问题,请参考以下文章

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

数组实例——实现棋盘落子

AI五子棋_08 五子棋落子规则对应的价值

结对编程-设计文档-五子棋

结对-结对编项目作业名称-设计文档

三子棋游戏