携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情 >>
前言
前几天写了中秋主题贪吃蛇后,突发奇想,既然贪吃蛇实现起来这么简单,那么五子棋,理论上也不会太难,假如写一个算法,自己和算法下五子棋,那不就相当于是在左右手互搏吗?既锻炼了自己的思维,还提高了编程能力,说干就干,今天终于闲下来了,开始进行实现
棋盘
首先当然是需要一个棋盘,我们这次的话,依然是借助上次贪吃蛇的代码,来个中秋主题的五子棋
<template>
<div class="home">
<div class="pan">
<template v-for="(item) in xArr" >
<template v-for="(v) in yArr" >
<div :key="`${item.id}_${v.id}`" :id="`${item.x}_${v.y}`" ></div>
</template>
</template>
</div>
</div>
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name: 'HomeView',
data () {
return {
xArr: [],
yArr: []
}
},
methods: {
},
mounted () {
},
created () {
this.xArr = []
this.yArr = []
for (let i = 0; i < 20; i++) {
this.xArr.push({ id: nanoid(), x: i + 1 })
}
for (let i = 0; i < 20; i++) {
this.yArr.push({ id: nanoid(), y: i + 1 })
}
},
components: {
}
}
</script>
<style lang="less" scoped>
.home{
width: 100%;
height: 100%;
overflow: hidden;
background-image: url('~@/assets/bg.jpeg');
background-size: cover;
background-position: center;
position: relative;
.pan{
width: 601px;
height: 601px;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
background: rgba(255, 255, 255, 0.8);
display: flex;
flex-wrap: wrap;
border-left: 1px solid #333;
border-top: 1px solid #333;
&>div{
width: 30px;
height: 30px;
border-right: 1px solid #333;
position: relative;
border-bottom: 1px solid #333;
}
}
}
</style>
这里也没啥说的,通过循环生成20x20的棋盘,每个格子大小为30px
棋子
接下来就是生成棋子,这里需要一个动态的变量去判断下下去的应该是黑子还是白子,然后还需要两个对象去各自存储棋子坐标。 这里我们现在data里声明一个对象Hei,一个对象Bai,还有一个isHei初始值为true,Hei和Bai里面Key的形式是X_Y,值是true,这样我们就可以知道哪些是黑子,哪些是白子了,接下来,就是注册点击事件,在对应区域内形成黑白子,黑白子我们依然利用棋盘div伪类的CSS背景去实现。
判定胜负
众所周知,五子棋的规则就是任意五颗棋子,在横纵斜三个方向上向下,联线为五颗,即为胜,接下来,我们就需要在落子的点击方法内,判定是否取胜,是黑子胜还是白子胜。这里的话,每个棋子均有三个方向,每个方向还有向上与向下,所以需要判断两个方向。 首先我们的判断在落子后进行,所以在点击事件结束时调用胜负判断
judgeWin (isHei) {
for (const i in this.Hei) {
const x = Number(i.split('_')[0])
const y = Number(i.split('_')[1])
let YInx = 1
let XInx = 1
let XYInx = 1
let YInx1 = 1
let XInx1 = 1
let XYInx1 = 1
for (let j = 1; j < 5; j++) {
if (this.Hei[`${x}_${y + j}`]) {
YInx += 1
}
if (this.Hei[`${x}_${y - j}`]) {
YInx1 += 1
}
if (this.Hei[`${x + j}_${y}`]) {
XInx += 1
}
if (this.Hei[`${x - j}_${y}`]) {
XInx1 += 1
}
if (this.Hei[`${x + j}_${y + j}`]) {
XYInx += 1
}
if (this.Hei[`${x - j}_${y - j}`]) {
XYInx1 += 1
}
}
console.log(YInx, XInx, XYInx, YInx1, XInx1, XYInx1)
if (XInx === 5 || YInx === 5 || XYInx === 5 || XInx1 === 5 || YInx1 === 5 || XYInx1 === 5) {
alert('黑棋赢了')
this.isWin = true
return false
} else {
if (!this.isHei) {
this.AIClick()
}
}
}
...
}
这里,三个方向,横纵就是判断X,Y,斜着就是判断X和Y同时自增自减,所以还是要循环两次,这里省略掉判断白子的代码。 到这里,就实现了两个人可以玩儿的五子棋,那么AI自走如何实现呢?
AI自走的实现
首先,我们的策略设定为,要先不允许对手赢,其次是要优先让自己形成赢的局势,这里算法的基础就有了。假设玩家执黑先行,AI执白。我们来写一下策略。 首先就是遍历判断黑子是否存在大于3的子,大于3则进行对应的围堵,否则就优先自己形成3子,这里就需要两头判断了
这里的逻辑我们首先要判断黑子是否已经形成3子,如果有三子,则要进行围堵了。
然后,假如没有形成三子,我们就要让我们的棋子形成3子了
let newX, newY
for (const i in this.Hei) {
const x = Number(i.split('_')[0])
const y = Number(i.split('_')[1])
let YInx = 1
let XInx = 1
let XYInx = 1
for (let j = 1; j < 5; j++) {
if (this.Hei[`${x}_${y + j}`]) {
YInx += 1
}
if (this.Hei[`${x + j}_${y}`]) {
XInx += 1
}
if (this.Hei[`${x + j}_${y + j}`]) {
XYInx += 1
}
}
const max = Math.max(YInx, XInx, XYInx)
if (max > 2) {
if (max === YInx) {
newX = x
newY = y + YInx
} else if (max === XInx) {
newX = x + XInx
newY = y
} else {
newX = x + XYInx
newY = y + XYInx
}
}
}
if (newX && newY && !this.Bai[`${newX}_${newY}`]) {
this.$set(this.Bai, `${newX}_${newY}`, true)
this.isHei = true
} else {
...
}
这里我们判断了黑子是否有3个的,然后进行围堵,围堵时还要判断当前位置自己是否已经落子,已经落子则要走入自己优先形成3子的判断,自己优先的话,同样要看每个棋子横纵斜那条线最多,并且要落子的地方没有黑子,才能下,否则就下次要的,否则就跟在白子最后一个子后面进行落子,假如这样的点也不存在,则跟在黑子最后一个点后进行落子
写到判断时,发现黑子赢了白子还会走,所以把AIClick放在judge判断内,只有黑子没赢的时候,白子才继续走
结语
自走棋伪AI其实就是面向过程编程,处理各种各样的情况,一大堆if else ,其实并不AI, 也有很多corner case 没处理到, 希望各位大佬能帮助完善自走的算法。