欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS+canvas五子棋人机对战实现步骤详解

程序员文章站 2022-04-09 16:12:14
1. 创建实例function gobang () { this.over = false; // 是否结束 this.player = true; // true:我 false:电脑 thi...

1. 创建实例

function gobang () {
  this.over = false; // 是否结束
  this.player = true; // true:我 false:电脑
  this.allchesses = []; // 所有棋子
  this.existchesses = [] // 已经落下的棋子
  this.winscount = 0; // 赢法总数
  this.wins = []; // 所有赢法统计
  this.mywins = []; //我的赢法统计
  this.computerwins = []; //电脑赢法统计
}

2. 初始化

//初始化
gobang.prototype.init = function(opts) {
  // 生成canvas棋盘
  this.createcanvas(opts);

  //棋盘初始化 
  this.boardinit();

  // 鼠标移动聚焦功能实现
  this.mousemove();

  //算法初始化
  this.algorithminit();

  //落子功能实现
  this.dorpchess();
}

3. 生成canvas棋盘

//初始化
//生成canvas
gobang.prototype.createcanvas = function(opts) {
  var opts = opts || {};
  if (opts.width && opts.width%30 !== 0) throw new rangeerror(opts.width+'不是30的倍数');
  this.col = (opts.width && opts.width/30) || 15; // 棋盘列

  var ocanvas = document.createelement('canvas');
  ocanvas.width = ocanvas.height = opts.width || 450;
  this.canvas = ocanvas;
  document.queryselector(opts.container || 'body').appendchild(this.canvas);
  this.ctx = ocanvas.getcontext('2d');
}

4. 初始化棋盘

//棋盘初始化
gobang.prototype.boardinit = function(opts){
  this.drawboard();
}

// 画棋盘
gobang.prototype.drawboard = function(){
  this.ctx.strokestyle = "#bfbfbf";
  for (var i = 0; i < this.col; i++) {
    this.ctx.moveto(15+ 30*i, 15);
    this.ctx.lineto(15+ 30*i, this.col*30-15);
    this.ctx.stroke();
    this.ctx.moveto(15, 15+ 30*i);
    this.ctx.lineto(this.col*30-15, 15+ 30*i);
    this.ctx.stroke();
  }
}

JS+canvas五子棋人机对战实现步骤详解

5. 画棋子

// 画棋子
gobang.prototype.drawchess = function(x, y, player){
  var x = 15 + x * 30,
    y = 15 + y * 30;
  this.ctx.beginpath();
  this.ctx.arc(x, y, 13, 0, math.pi*2);
  var grd = this.ctx.createradialgradient(x + 2, y - 2, 13 , x + 2, y - 2, 0);
  if (player) { //我 == 黑棋 
    grd.addcolorstop(0, '#0a0a0a');
    grd.addcolorstop(1, '#636766');
  }else{ //电脑 == 白棋
    grd.addcolorstop(0, '#d1d1d1');
    grd.addcolorstop(1, '#f9f9f9');
  }
  this.ctx.fillstyle = grd;
  this.ctx.fill()
}

JS+canvas五子棋人机对战实现步骤详解

6. 移动聚焦

// 鼠标移动时触发聚焦效果, 需要前面的聚焦效果消失, 所有需要重绘canvas
gobang.prototype.mousemove = function(){
  var that = this;
  this.canvas.addeventlistener('mousemove', function (e) {
    that.ctx.clearrect(0, 0, that.col*30, that.col*30);
    var x = math.floor((e.offsetx)/30),
      y = math.floor((e.offsety)/30);

    //重绘棋盘
    that.drawboard();

    //移动聚焦效果
    that.focuschess(x, y);

    //重绘已经下好的棋子
    that.redrawedchess()
  });
}

//鼠标移动聚焦
gobang.prototype.focuschess = function(x, y){
  this.ctx.beginpath();
  this.ctx.fillstyle = '#e74343';
  this.ctx.arc(15 + x * 30, 15 + y * 30, 6, 0, math.pi*2);
  this.ctx.fill();
}

//重绘当前下好的棋子
gobang.prototype.redrawedchess = function(x, y){
  for (var i = 0; i < this.existchesses.length; i++) {
    this.drawchess(this.existchesses[i].x, this.existchesses[i].y, this.existchesses[i].player);
  }
}

JS+canvas五子棋人机对战实现步骤详解

7. 算法初始化

//算法初始化
gobang.prototype.algorithminit = function(){
  //初始化棋盘的每个位置和赢法
  for (var x = 0; x < this.col; x++) {
    this.allchesses[x] = [];
    this.wins[x] = [];
    for (var y = 0; y < this.col; y++) {
      this.allchesses[x][y] = false;
      this.wins[x][y] = [];
    }
  }
  //获取所有赢法
  this.computedwins();

  // 初始化电脑和我每个赢法当前拥有的棋子数
  for (var i = 0; i < this.winscount; i++) {
    this.mywins[i] = 0;
    this.computerwins[i] = 0;
  }
}

8. 获取所有赢法

gobang.prototype.computedwins = function(){
  /*
    直线赢法
    以15列为准
  */
  for (var x = 0; x < this.col; x++) { //纵向所有赢法
    for (var y = 0; y < this.col-4; y ++) {
      this.winscount ++; 

      /*
        如: 
        1.组成的第一种赢法
          [0,0]
          [0,1]
          [0,2]
          [0,3]
          [0,4]
        
        2.组成的第二种赢法
          [0,1]
          [0,2]
          [0,3]
          [0,4]
          [0,5]
        以此类推一列最多也就11种赢法, 所有纵向x有15列 每列最多11种, 所有纵向总共15 * 11种
      */
      //以下for循环给每种赢法的位置信息储存起来
      for (var k = 0; k < 5; k ++) {
        this.wins[x][y+k][this.winscount] = true;
        /*
          位置信息
          第一种赢法的时候:
            this.wins = [
                    [
                      [1:true],
                      [1:true],
                      [1:true],
                      [1:true],
                      [1:true]
                    ],
                    [
                      ......
                    ]
                  ]

            虽然这是一个三维数组, 我们把它拆分下就好理解了
            相当于 this.wins[0][0][1], this.wins[0][4][1], this.wins[0][5][1], this.wins[0][6][1], this.wins[0][7][1]
            
            因为对象可以这样取值:
              var obj = {
                a: 10,
                b: 'demo'
              }
              obj['a'] === obj.a

            所有也就相当于 this.wins[0][0].1, this.wins[0][8].1, this.wins[0][9].1, this.wins[0][10].1, this.wins[0][11].1 

            虽然数组不能这么取值,可以这么理解

            所以   this.wins[0][0].1 就可以理解为 在 x=0, y=0, 上有第一种赢法
                this.wins[0][12].1 就可以理解为 在 x=0, y=1, 上有第一种赢法
                ......

            以上this.wins[0][0],this.wins[0][13]...可以看作是 this.wins[x][y]
            所以第一种赢法的坐标就是: [0,0] [0,1] [0,2] [0,3] [0,4] 
        */
      }
    }
  }

  for (var y = 0; y < this.col; y++) { //横向所有赢法, 同纵向赢法一样,也是15 * 11种
    for (var x = 0; x < this.col-4; x ++) {
      this.winscount ++;
      for (var k = 0; k < 5; k ++) {
        this.wins[x+k][y][this.winscount] = true;
      }
    }
  }

交叉赢法

JS+canvas五子棋人机对战实现步骤详解

  /*
    交叉赢法
  */
  for (var x = 0; x < this.col-4; x++) { // 左 -> 右 开始的所有交叉赢法 总共11 * 11种
    for (var y = 0; y < this.col-4; y ++) {
      this.winscount ++;

      /*
      如:
      1. [0,0]
        [1,1]
        [2,2]
        [3,3]
        [4,4]
      
      2. [0,1]
        [1,2]
        [2,3]
        [3,4]
        [4,5]

      3. [0,2]
        [1,3]
        [2,4]
        [3,5]
        [4,6]
      ...

        [1,0]
        [2,1]
        [3,2]
        [4,3]
        [5,5]

      相当于从左至右 一列列计算过去

      */

      for (var k = 0; k < 5; k ++) {
        this.wins[x+k][y+k][this.winscount] = true;
      }
    }
  }

  for (var x = this.col-1; x >= 4; x --) { //右 -> 左 开始的所有交叉赢法 总共11 * 11种
    for (var y = 0; y < this.col-4; y ++) {
      this.winscount ++;
      for (var k = 0; k < 5; k ++) {
        this.wins[x-k][y+k][this.winscount] = true;
      }
    }
  }
}

9. 落子实现

//落子实现
gobang.prototype.dorpchess = function(){
  var that = this;
  this.canvas.addeventlistener('click', function(e) {
    // 判断是否结束
    if (that.over) return;

    var x = math.floor((e.offsetx)/30),
      y = math.floor((e.offsety)/30);

    //判断该棋子是否已存在
    if (that.allchesses[x][y]) return;

    // 检查落子情况
    that.checkchess(x, y)

    if (!that.over) {
      that.player = false;
      that.computerdropchess();//计算机落子
    }
  })
}


//检查落子情况
gobang.prototype.checkchess = function(x, y){
  //画棋
  this.drawchess(x, y, this.player);
  //记录落下的棋子
  this.existchesses.push({
    x: x,
    y: y,
    player: this.player
  });
  //该位置棋子置为true,证明已经存在
  this.allchesses[x][y] = true;

  this.currwinchesses(x, y, this.player);
}

//判断当前坐标赢的方法各自拥有几粒棋子
gobang.prototype.currwinchesses = function(x, y, player){
  var currobj = player ? this.mywins : this.computerwins;
  var enemyobj = player ? this.computerwins : this.mywins;
  var currtext = player ? '我' : '电脑';
  for (var i = 1; i <= this.winscount; i++) {
    if (this.wins[x][y][i]) { //因为赢法统计是从1开始的 所以对应我的赢法需要减1
      currobj[i-1] ++; // 每个经过这个点的赢法都增加一个棋子;

      enemyobj[i-1] = 6; //这里我下好棋了,证明电脑不可能在这种赢法上取得胜利了, 置为6就永远不会到5

      if (currobj[i-1] === 5) { //当达到 5 的时候,证明我胜利了
        alert(currtext+'赢了')
        this.over = true;
      }
    }
  }
}

10. 计算机落子实现

// 计算机落子
gobang.prototype.computerdropchess = function(){
  var myscore = [], //玩家比分
    computerscore = [], // 电脑比分
    maxscore = 0; //最大比分
  

  //比分初始化
  var scoreinit = function(){
    for( var x = 0; x < this.col; x ++) { 
      myscore[x] = [];
      computerscore[x] = [];
      for (var y = 0; y < this.col; y ++) {
        myscore[x][y] = 0;
        computerscore[x][y] = 0;
      }
    }
  }
  scoreinit.call(this);

  //电脑待会落子的坐标
  var x = 0, y = 0; 

  // 基于我和电脑的每种赢法拥有的棋子来返回对应的分数
  function formatscore(o, n) { 
    if (o < 6 && o > 0) {
      var n = 10;
      for (var i = 0; i < o; i++) {
        n *= 3;
      }
      return n
    }
    return 0
  }

  // 获取没有落子的棋盘区域
  function existchess(arr) { 
    var existarr = [];
    for (var i = 0; i < arr.length; i++) {
      for (var j = 0; j < arr[i].length; j++) {
        if (!arr[i][j]) {
          existarr.push({x:i, y:j})
        }
      }
    }
    return existarr;
  }

  var exceptarr = existchess(this.allchesses);

  // 循环未落子区域,找出分数最大的位置
  for (var i = 0; i < exceptarr.length; i++) { 
    var o = exceptarr[i];

    // 循环所有赢的方法
    for (var k = 0; k < this.winscount; k++) {

      //判断每个坐标对应的赢法是否存在
      if (this.wins[o.x][o.y][k]) {

        // 计算每种赢法,拥有多少棋子,获取对应分数
        // 电脑起始分数需要高一些,因为现在是电脑落子, 优先权大
        myscore[o.x][o.y] += formatscore(this.mywins[k-1], 10);
        computerscore[o.x][o.y] += formatscore(this.computerwins[k-1], 11); 
      }
    }

    //我的分数判断
    if (myscore[o.x][o.y] > maxscore) { //当我的分数大于最大分数时, 证明这个位置的是对我最有利的
      maxscore = myscore[o.x][o.y];
      x = o.x;
      y = o.y;
    }else if (myscore[o.x][o.y] === maxscore) { //当我的分数与最大分数一样时, 证明我在这两个位置下的效果一样, 所以我们应该去判断在这两个位置时,电脑方对应的分数
      if (computerscore[o.x][o.y] > computerscore[x][y]) {
        x = o.x;
        y = o.y;
      }
    }

    // 电脑分数判断, 因为是电脑落子, 所以优先权大
    if (computerscore[o.x][o.y] > maxscore) {
      maxscore = computerscore[o.x][o.y];
      x = o.x;
      y = o.y;
    }else if (computerscore[o.x][o.y] === maxscore) {
      if (myscore[o.x][o.y] > myscore[x][y]) {
        x = o.x;
        y = o.y;
      }
    }
  }

  this.checkchess(x, y)

  if (!this.over) {
    this.player = true;
  }
}
var gobang = new gobang();
gobang.init()

github地址

线上地址

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。