无AI的五子棋Vue项目

1,395 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

五子棋

以前我曾经看视频跟着做过一个井字棋的人机对战,现在又在书栈上看到了一篇和五子棋AI有关的教程,所以准备动手练习一下。

当然,在写AI之前肯定先要把之前的准备工作都做完。要完成的基础功能如下:

  • 先把五子棋的棋盘界面搭建起来
  • 点击棋盘,可以对应生成棋子,黑白棋子的生成交叉进行
  • 棋子的位置需要生成在网格线交叉的中间位置,并且不能重复在同一位置
  • 无论黑棋或白棋哪一方有五个字相连了,就判断游戏结束,并宣布胜利者

本文中只展示部分重点代码与方法,具体代码可参考Gitee仓库: gitee.com/wzckongchen…



项目搭建

项目的搭建比较简单,使用了Vue框架,现在页面上创建好对应Vue实例需要挂载的元素

<div id="Gobang" ref="Gobang" class="Gobang" v-cloak>
</div>

使用Ts编写,先在Vue中设置好需要的data对象,说明如下:

对象说明默认
ifGenWhite为了判断下一次生成的棋子颜色true (白色)
chessInfos棋子信息集合[] chessInfo的空数组
curIndex当前步数,在棋子生成时该值放在棋子信息中1
maxChessNum最大连续棋子数0 (发现目前不需要了)
curColor当前棋子颜色,为了遍历棋子判断胜利条件时使用true (白色)
isFlag是否当前游戏已经结束false
successChess成功连起来的5个棋子集合[] chessInfo的空数组
// 棋子属性
type chessInfo = {
    index: number,  // 第几步
    isWhite: boolean,  // 是否是白色
    position: pos // 棋子位置
}
type pos = {
    left: number, top: number
}
let chessInfos:chessInfo[] = [];
let vm:Vue = new Vue({
    el:'#Gobang',
    data: {
        ifGenWhite: true,  // 是否生成白色棋子
        chessInfos: chessInfos,  // 棋子信息集合
        curIndex: 1,  // 当前步数
        maxChessNum: 0,  // 最大连续棋子数 
        curColor: true,  // 当前棋子颜色
        isFlag: false,  //是否找到成功的棋子
        successChess: chessInfos,  // 成功的棋子集合
    },
    mounted(){ 
    },
    methods:{
    }
})

棋盘界面

棋盘的界面本来我准备使用画线的方式来构成,但是后来一想,之后棋子落点都是要按鼠标点击位置进行判断,那干脆直接拿图当背景吧

棋盘:

image.png

在元素中,棋盘元素大小我设置为了535*535,并且背景图也是相同大小。

这样能方便之后进行计算,目前棋盘最作上角落点位置为(22,22),右下角落点位置为(512,512),每个空格的间隔为35像素


点击棋盘和生成棋子

创建一个点击棋盘方法,使用@click绑定在棋盘元素上面。

// 棋盘点击
checkerBoardClick(event: any) {
    let e = event;
    // 判断点击是否在棋盘
    if (e.target.getAttribute('class') != 'Gobang') return;
    // 点击位置确定
    let curPos:pos|boolean = this.positionSure({left: e.offsetX - 13, top: e.offsetY - 13})
    if (!curPos) return ;
    // 生成棋子
    this.generateChess(curPos as pos);
},

在此方法中,首先判断鼠标按下的点击的元素是否是棋盘元素,然后使用positionSure方法进行点击位置判断与计算,返回出当前应该生成的棋子位置,如果没有可生成的棋子位置或者当前位置已经存在棋子,则返回false阻止下面的操作。(这里棋子位置计算代码可以去仓库中查看,这里就不作罗列了)

当前位置可以下棋,根据当前返回的棋子位置,调用generateChess方法生成棋子,将该棋子添加到棋子数组当中,并且改变ifGenWhite属性,让下一次生成的棋子颜色进行改变,增加curIndex当前步数的属性值。

// 生成棋子
generateChess(position:pos) {
    let chess:chessInfo = {
        index: this.curIndex,
        isWhite: this.ifGenWhite,
        position: position
    } 
    this.curIndex ++;  // 步数增加
    this.ifGenWhite = !this.ifGenWhite; // 棋子颜色改变
    this.chessInfos.push(chess);

    this.sortChessInfo();  // 根据棋子左上远近重新将棋子集合排序
    this.ergodicChess();  // 遍历所有棋子判断胜利方法
},

在棋子生成方法generateChess中,如果不考虑下面的棋子集合排序和胜利判断,已经可以做到简单的下棋步骤了。

棋盘上面的棋子生成是由chessInfos棋子集合在棋盘元素当中使用v-for遍历生成的,根据棋子元素中的属性来生成对应的位置颜色class

<!-- 棋子 -->
<div class="step" v-for="item of chessInfos" :class="item.isWhite?'white':'black'"
    :style="{ 'left': item.position.left + 'px', 'top': item.position.top + 'px'}" >
    {{ item.index }}
</div>

效果:

生成棋子.gif


胜利判断

棋子的胜利判断咋一看没找到头绪,但是其实是有逻辑可循的,需要遍历所有棋子,这里棋子集合就需要进行重新排序了。

棋子排序规则:最左上角到最右下角,并且left距左边距离的优先级大于top距顶部距离优先级。

排序完成后,胜利的条件无非就是五个棋子连在一起。

所以选择一个棋子为基准后,从该棋子的右方、下方、右下、左下四个方向开始判断 是否有连续的棋子4个,并且颜色相同。如果存在则游戏结束,该颜色方胜利。

image.png

至于这四个方向相对烦的方向就不需要判断了,因为棋子经过排序之后是从最左上角开始进行遍历的,如果在该棋子前的都没有成功,那么该棋子左方、上方、左上这三是不会有成功的条件的(右上可能会存在)。棋子某一方向的连续棋子判断时我使用了递归,如果存在则继续往此方向进行判断直到棋子为5个。

棋子的胜利判断方法这里也不展示了。然后可以在棋盘界面中添加胜利遮罩,当某一方胜利时进行显示

具体效果

下棋.gif

可以添加点击重开方法,此方法只需将之前修改过的一些data中对象重置即可,包括棋子集合置空。

// 重开游戏
replay() {
    this.chessInfos = [];
    this.curColor = true;
    this.successChess = [];
    this.isFlag = false;
    this.ifGenWhite = true;
    this.curIndex = 1;
}

以上就是一款无AI的五子棋简单项目了,当然之后的重点肯定是添加智能的下棋对象。

就先到这里了,结束...