小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
五子棋
以前我曾经看视频跟着做过一个井字棋的人机对战,现在又在书栈上看到了一篇和五子棋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:{
}
})
棋盘界面
棋盘的界面本来我准备使用画线的方式来构成,但是后来一想,之后棋子落点都是要按鼠标点击位置进行判断,那干脆直接拿图当背景吧
棋盘:
在元素中,棋盘元素大小我设置为了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>
效果:
胜利判断
棋子的胜利判断咋一看没找到头绪,但是其实是有逻辑可循的,需要遍历所有棋子,这里棋子集合就需要进行重新排序了。
棋子排序规则:最左上角到最右下角,并且left距左边距离的优先级大于top距顶部距离优先级。
排序完成后,胜利的条件无非就是五个棋子连在一起。
所以选择一个棋子为基准后,从该棋子的右方、下方、右下、左下四个方向开始判断 是否有连续的棋子4个,并且颜色相同。如果存在则游戏结束,该颜色方胜利。
至于这四个方向相对烦的方向就不需要判断了,因为棋子经过排序之后是从最左上角开始进行遍历的,如果在该棋子前的都没有成功,那么该棋子左方、上方、左上这三是不会有成功的条件的(右上可能会存在)。棋子某一方向的连续棋子判断时我使用了递归,如果存在则继续往此方向进行判断直到棋子为5个。
棋子的胜利判断方法这里也不展示了。然后可以在棋盘界面中添加胜利遮罩,当某一方胜利时进行显示
具体效果:
可以添加点击重开方法,此方法只需将之前修改过的一些data中对象重置即可,包括棋子集合置空。
// 重开游戏
replay() {
this.chessInfos = [];
this.curColor = true;
this.successChess = [];
this.isFlag = false;
this.ifGenWhite = true;
this.curIndex = 1;
}
以上就是一款无AI的五子棋简单项目了,当然之后的重点肯定是添加智能的下棋对象。
就先到这里了,结束...