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

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();
  }
}

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()
}

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);
  }
}

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;
      }
    }
  }

交叉赢法

  /*
    交叉赢法
  */
  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地址

线上地址

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

(0)

相关推荐

  • JavaScript canvas绘制圆弧与圆形

    本文实例为大家分享了canvas绘制圆弧与圆形的具体代码,供大家参考,具体内容如下 canvas 绘制圆弧 绘制圆弧使用 context.arc( ) 函数,包含六个参数. context.arc( centerx,centery,radius, startingAngle,endingAngle, anticlockwise = false ) 分别代表:圆心 x 值,圆心 y 值,半径,开始的弧度值,结束的弧度值,(是否逆时针). 例如: window: onload = function(

  • JavaScript canvas绘制折线图

    本文实例为大家分享了canvas绘制折线图的具体代码,供大家参考,具体内容如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> canvas { border: 1px solid #ccc; } </style> </head&g

  • js+canvas实现两张图片合并成一张图片的方法

    本文实例讲述了js+canvas实现两张图片合并成一张图片的方法.分享给大家供大家参考,具体如下: JS和canvas的合成方式 function drawAndShareImage(){ var canvas = document.createElement("canvas"); canvas.width = 700; canvas.height = 700; var context = canvas.getContext("2d"); context.rect(

  • 原生JS+Canvas实现五子棋游戏实例

    一.功能模块 先看下现在做完的效果: 线上体验:https://wj704.github.io/five_game.html 主要功能模块为: 1.人机对战功能 2.悔棋功能 3.撤销悔棋功能 二.代码详解 2.1 人机对战功能实现 从效果图可以看到,棋盘的横竖可以放的位置为15*15,通过canvas画棋盘: //绘画棋盘 var drawChessBoard = function(){ for(var i = 0; i < 15; i++){ context.moveTo(15 + i *

  • js+canvas实现纸牌游戏

    本文实例为大家分享了js+canvas实现纸牌游戏的具体代码,供大家参考,具体内容如下 废话不多说,先上地址 运行截图 最后如果完成了会有结束动画,我技术比较渣,难以玩到结束,就不上完成动画截图了. 游戏介绍 好吧,可能有些好学生在做上机实验时没有玩纸牌游戏,所以容我介绍一下这个游戏,这个游戏是win7自带的一个纸牌类游戏,游戏规则是:将牌按一定的规则码放,最终将所有的牌牌面朝上胜利.有两个地方可以用来码牌,上:以A开头,即将码放的牌的花色相同,且牌面值比原来的牌面大1,则可以码放成功:下:以K

  • 原生JS+Canvas实现五子棋游戏

    本文实例为大家分享了JS  Canvas实现五子棋游戏的具体代码,供大家参考,具体内容如下 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>五子棋</title> <style type='text/css'> canvas { display: block; margin: 50px auto; box-shadow: -2p

  • JS+canvas实现的五子棋游戏【人机大战版】

    本文实例讲述了JS+canvas实现的五子棋游戏.分享给大家供大家参考,具体如下: 运行效果图: html代码如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>五子棋</title> <link rel="stylesheet" type="text/css" href="css

  • JS canvas绘制五子棋的棋盘

    本文为大家分享了JS canvas绘制五子棋棋盘的具体代码,供大家参考,具体内容如下 box-shadow:给元素块周边添加阴影效果. 语法:box-shadow: h-shadow v-shadow blur spread color inset: h-shadow: (必须)阴影的水平偏移量,如果是正值,则阴影在元素块右边:如果是负值,则阴影在元素块左边. v-shadow: (必须)阴影的垂直偏移量,如果是正值,则阴影在元素块底部:如果是负值,则阴影在元素块顶部. blur: (可选)阴影

  • Javascript和HTML5利用canvas构建Web五子棋游戏实现算法

    这只是一个简单的JAVAscript和HTML5小程序,没有实现人机对战. 五子棋棋盘落子点对应的二维数组.数组的元素对应落子点.比如数组元素值为0表示该元素对应的落子点没有棋子,数组元素值为1表示该元素对应的落子点有白棋子,数组元素值为2表示该元素对应的落子点有黑棋子: 判断五子棋赢棋的算法是通过对五子棋棋盘落子点对应的二维数组的操作来实现的. 判断五子棋赢棋算法 下边的函数可以实现判断五子棋赢棋的算法,也可以按照教材中相应的算法实现. 其中函数的参数xx.yy为数组下标,chess数组实现五

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

    1. 创建实例 function Gobang () { this.over = false; // 是否结束 this.player = true; // true:我 false:电脑 this.allChesses = []; // 所有棋子 this.existChesses = [] // 已经落下的棋子 this.winsCount = 0; // 赢法总数 this.wins = []; // 所有赢法统计 this.myWins = []; //我的赢法统计 this.compu

  • 基于Python实现五子棋-(人机对战)

    目录 前言 人人对战 动态演示 源码分享 人机对战 前言 快520了,咱们来玩玩五子棋陶冶情操.快拿这个和你女朋友去对线.(分了别来找我哇).多的不说直接进入正题 人人对战 游戏规则:p1为黑子,p2为白子,黑子先手,一方达到五子相连即为获胜. 动态演示 源码分享 cheackboard.py 定义黑白子,落子位置以及获胜规则. from collections import namedtuple Chessman = namedtuple('Chessman', 'Name Value Col

  • Python实现五子棋人机对战 和人人对战

    目录 人人对战 动态演示 源码分享 人人对战.py 人机对战 动态演示 前言: 过完520,咱们来玩玩五子棋陶冶情操.快拿这个和你女朋友去对线.多的不说直接进入正题 人人对战 游戏规则:p1为黑子,p2为白子,黑子先手,一方达到五子相连即为获胜. 动态演示 源码分享 定义黑白子,落子位置以及获胜规则. from collections import namedtuple Chessman = namedtuple('Chessman', 'Name Value Color') Point = n

  • JS写XSS cookie stealer来窃取密码的步骤详解

    JavaScript是web中最常用的脚本开发语言,js可以自动执行站点组件,管理站点内容,在web业内实现其他有用的函数.JS可以有很多的函数可以用做恶意用途,包括窃取含有密码等内容的用户cookie. Cookie是站点请求和保持特定访问页面的信息.Cookie含有访问的方式.时间.用户名密码等认证信息等.当用户访问给定站点时,必须使用cookie:如果攻击者可以拦截cookie,就可以利用cookie窃取用户的一些信息.对某个特定的域名,使用JS可以保存或修改用户的cookie.也就是说,

  • vue使用AES.js的步骤详解

    AES加密的使用 对数据传输加密.解密处理---AES.js 第一步: 在vue中安装crypto-js依赖 npm install crypto-js --save-dev 第二步: 在static目录下新建一个AES.js文件,例如: 第三步: 在AES.js中填写如下代码 import CryptoJS from "crypto-js"; // npm install crypto-js --save-dev //随机生成指定数量的32进制key export default {

  • JS实现canvas仿ps橡皮擦刮卡效果详解

    目录 效果演示: 主要JS代码实现 <div class="box" id="bb"> <canvas id="cas" width="1366" height="651"></canvas> </div> <script type="text/javascript" charset="utf-8"> var

  • JS技巧Canvas 性能优化脏矩形渲染实例详解

    目录 正文 画布该如何更新? 脏矩形渲染原理 脏矩形渲染实现 性能测试 结尾 正文 使用 Canvas 做图形编辑器时,我们需要自己维护自己的图形树,来保存图形的信息,并定义元素之间的关系. 我们改变画布中的某个图形,去更新画布,最简单的是清空画布,然后根据图形树将所有图形再绘制一遍,这在图形较少的情况下是没什么问题的.但如果图形数量很多,那绘制起来可能就出现卡顿了. 那么,有没有什么办法来优化一下?有,脏矩形渲染. 画布该如何更新? 这里我们假设这么一个场景,画布上绘制了随机位置大量的绿球,然

  • JS之if语句对接事件动作逻辑(详解)

    if 函数的实现步骤: function +名字() 指定id , 指定开关(display: none or block) if + else 构成逻辑 控制开关 决定在哪里安置一个灯泡, 指定一个id给某个标签 把开关用电线连着灯泡, 安装开关#+id名称{ 属性1= 赋值, 属性 2 = 赋值 , 属性3 = 赋值 } 所有的赋值都可以成为一个开关. 如果是一个手动版的, 这里已经完成了. 更改属性的赋值就可以变更id的样式 帮开关装上感应元件, 一旦有动静, 开关便自动switch 建立

  • jQuery正则表达式的使用方法步骤详解

    本文主要和大家分享的就是jQuery学习中正则的使用,正则在jquery里面并没有比JavaScript多哪些知识,基本上是一样的,只是选择器更好了一点,一起来看看吧. 基础正则 1.正则表达式的创建 a) var checkNum = /^[A-Za-z0-9]+$/; b) var re=new RegExp("["+s1+"]","g"); 2.常用规则 a) 用户密码:/^[a-zA-Z][a-zA-Z0-9_]{5,20}$/ b) 邮

  • Vue使用枚举类型实现HTML下拉框步骤详解

    下拉框包含option中的Value和用来显示的选项, 一般后台都是使用的Value值,而不是显示在前台的选项 第一步: 编写下拉框需要的枚举类型 StatusEnum.java public enum StatusEnum { RED, YELLOW, GREEN } 第二步: 编写用来存放下拉框中对应的option中的Value和显示的选项 StatusDTO.java public class StatusDTO { private String code; private String

随机推荐