社恐福音: 基于Vue实现伪AI自走五子棋

261 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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背景去实现。

image.png image.png image.png

image.png

判定胜负

众所周知,五子棋的规则就是任意五颗棋子,在横纵斜三个方向上向下,联线为五颗,即为胜,接下来,我们就需要在落子的点击方法内,判定是否取胜,是黑子胜还是白子胜。这里的话,每个棋子均有三个方向,每个方向还有向上与向下,所以需要判断两个方向。 首先我们的判断在落子后进行,所以在点击事件结束时调用胜负判断

image.png

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子,这里就需要两头判断了

image.png 这里的逻辑我们首先要判断黑子是否已经形成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判断内,只有黑子没赢的时候,白子才继续走

image.png

结语

自走棋伪AI其实就是面向过程编程,处理各种各样的情况,一大堆if else ,其实并不AI, 也有很多corner case 没处理到, 希望各位大佬能帮助完善自走的算法。