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


Posted in Javascript onJune 19, 2017

一、功能模块

先看下现在做完的效果:

原生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 * 30 , 15);
        context.lineTo(15 + i * 30 , 435);
        context.stroke();
        context.moveTo(15 , 15 + i * 30);
        context.lineTo(435 , 15 + i * 30);
        context.stroke();
      }
    }

知道格子数后,我们先看五子棋有多少种赢法:

//赢法数组
      var wins = [];
      for(var i = 0; i < 15; i++){
        wins[i] = [];
        for(var j = 0; j < 15; j++){
          wins[i][j] = [];
        }
      }
      var count = 0; //赢法总数
      //横线赢法
      for(var i = 0; i < 15; i++){
        for(var j = 0; j < 11; j++){
          for(var k = 0; k < 5; k++){
            wins[i][j+k][count] = true;
          }
          count++;
        }
      }

      //竖线赢法
      for(var i = 0; i < 15; i++){
        for(var j = 0; j < 11; j++){
          for(var k = 0; k < 5; k++){
            wins[j+k][i][count] = true;
          }
          count++;
        }
      }

      //正斜线赢法
      for(var i = 0; i < 11; i++){
        for(var j = 0; j < 11; j++){
          for(var k = 0; k < 5; k++){
            wins[i+k][j+k][count] = true;
          }
          count++;
        }
      }

      //反斜线赢法
      for(var i = 0; i < 11; i++){ 
        for(var j = 14; j > 3; j--){
          for(var k = 0; k < 5; k++){
            wins[i+k][j-k][count] = true;
          }
          count++;
        }
      }

根据赢法总数定义分别保存计算机和人赢法的数组:

for(var i = 0; i < count; i++){
        myWin[i] = 0;
        _myWin[i] = 0;
        computerWin[i] = 0;
        _compWin[i] = 0;
   }

然后就是人开始下棋:

 

// 我,下棋
  chess.onclick = function(e){
    if(over){ // 游戏结束
      return;
    }
    if(!me){
      return;
    }
    var x = e.offsetX;
    var y = e.offsetY;
    var i = Math.floor(x / 30);
    var j = Math.floor(y / 30);
    _nowi = i;
    _nowj = j;
    if(chressBord[i][j] == 0){
      oneStep(i,j,me);
      chressBord[i][j] = 1; //我,已占位置    

      for(var k = 0; k < count; k++){ // 将可能赢的情况都加1
        if(wins[i][j][k]){
          myWin[k]++;
          _compWin[k] = computerWin[k]; // 为悔棋做准备
          computerWin[k] = 6;//这个位置对方不可能赢了
          if(myWin[k] == 5){
            resultTxt.innerHTML = '恭喜,你赢了!';
            over = true;
          }
        }
      }
      if(!over){
        me = !me;
        computerAI();
      }
    }
    // 悔棋功能可用
    backbtn.className = backbtn.className.replace( new RegExp( "(\\s|^)unable(\\s|$)" )," " );     
  }

oneStep() 方法为落子,要在棋盘上画一个棋子:

//画棋子
    var oneStep = function(i,j,me){
      // debugger;
      context.beginPath();
      context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI);//画圆
      context.closePath();
      //渐变
      var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0);

      if(me){
        gradient.addColorStop(0,'#0a0a0a');
        gradient.addColorStop(1,'#636766');
      }else{
        gradient.addColorStop(0,'#d1d1d1');
        gradient.addColorStop(1,'#f9f9f9');
      }
      context.fillStyle = gradient;
      context.fill();
    }

接着看计算机怎么下棋,具体看computerAI()方法:

// 计算机下棋
    var computerAI = function (){
      var myScore = [];
      var computerScore = [];
      var max = 0;
      var u = 0, v = 0;
      for(var i = 0; i < 15; i++){
        myScore[i] = [];
        computerScore[i] = [];
        for(var j = 0; j < 15; j++){
          myScore[i][j] = 0;
          computerScore[i][j] = 0;
        }
      }
      for(var i = 0; i < 15; i++){
        for(var j = 0; j < 15; j++){
          if(chressBord[i][j] == 0){
            for(var k = 0; k < count; k++){
              if(wins[i][j][k]){
                if(myWin[k] == 1){
                  myScore[i][j] += 200;
                }else if(myWin[k] == 2){
                  myScore[i][j] += 400;
                }else if(myWin[k] == 3){
                  myScore[i][j] += 2000;
                }else if(myWin[k] == 4){
                  myScore[i][j] += 10000;
                }

                if(computerWin[k] == 1){
                  computerScore[i][j] += 220;
                }else if(computerWin[k] == 2){
                  computerScore[i][j] += 420;
                }else if(computerWin[k] == 3){
                  computerScore[i][j] += 2100;
                }else if(computerWin[k] == 4){
                  computerScore[i][j] += 20000;
                }            
              }
            }

            if(myScore[i][j] > max){
              max = myScore[i][j];
              u = i;
              v = j;
            }else if(myScore[i][j] == max){
              if(computerScore[i][j] > computerScore[u][v]){
                u = i;
                v = j;  
              }
            }

            if(computerScore[i][j] > max){
              max = computerScore[i][j];
              u = i;
              v = j;
            }else if(computerScore[i][j] == max){
              if(myScore[i][j] > myScore[u][v]){
                u = i;
                v = j;  
              }
            }

          }
        }
      }
      _compi = u;
      _compj = v;
      oneStep(u,v,false);
      chressBord[u][v] = 2; //计算机占据位置
      for(var k = 0; k < count; k++){
        if(wins[u][v][k]){
          computerWin[k]++;
          _myWin[k] = myWin[k];
          myWin[k] = 6;//这个位置对方不可能赢了
          if(computerWin[k] == 5){
            resultTxt.innerHTML = 'o(?□?)o,计算机赢了,继续加油哦!';
            over = true;
          }
        }
      }
      if(!over){
        me = !me;
      }
    }

根据相应的权重,计算出计算机应该落子的位置。

2.2 悔棋功能

要提的是,这里暂时只能悔一步棋。悔棋功能主要关键点是:1、销毁刚刚下的棋子;2、将之前不可能赢的状态还原;看下具体的代码:

// 悔棋
      backbtn.onclick = function(e){
        if(!backAble) { return;}
        over = false;
        me = true;

        // 我,悔棋
        chressBord[_nowi][_nowj] = 0; //我,已占位置 还原
        minusStep(_nowi, _nowj); //销毁棋子                 
        for(var k = 0; k < count; k++){ // 将可能赢的情况都减1
          if(wins[_nowi][_nowj][k]){
            myWin[k]--;
            computerWin[k] = _compWin[k];//这个位置对方可能赢
          }
        }

        // 计算机相应的悔棋
        chressBord[_compi][_compj] = 0; //计算机,已占位置 还原
        minusStep(_compi, _compj); //销毁棋子                 
        for(var k = 0; k < count; k++){ // 将可能赢的情况都减1
          if(wins[_compi][_compj][k]){
            computerWin[k]--;
            myWin[k] = _myWin[i];//这个位置对方可能赢
          }
        }
        resultTxt.innerHTML = '--益智五子棋--';
        returnAble = true;
        backAble = false;
         // 撤销悔棋功能可用
        returnbtn.className = returnbtn.className.replace( new RegExp( "(\\s|^)unable(\\s|$)" )," " ); 
      }

minusStep()为销毁棋子的方法,我们看下是怎么销毁的。

//销毁棋子
     var minusStep = function(i,j) {
      //擦除该圆
      context.clearRect((i) * 30, (j) * 30, 30, 30);

      // 重画该圆周围的格子
      context.beginPath();
      context.moveTo(15+i*30 , j*30);
      context.lineTo(15+i*30 , j*30 + 30);

      context.moveTo(i*30, j*30+15);
      context.lineTo((i+1)*30 , j*30+15);

      context.stroke();
    }

首先通过clearRect()擦掉该圆,然后再重新画该圆周围的格子,注意相应的位置,这里花了些时间折腾。

2.3 撤销悔棋功能

悔棋过后,再撤销,相当于还原悔棋之前的状态。代码比较简单:

// 撤销悔棋
    returnbtn.onclick = function(e){
      if(!returnAble) { return; }
      // 我,撤销悔棋
      chressBord[_nowi][_nowj] = 1; //我,已占位置 
      oneStep(_nowi,_nowj,me);               
      for(var k = 0; k < count; k++){ 
        if(wins[_nowi][_nowj][k]){
          myWin[k]++;
          _compWin[k] = computerWin[k];
          computerWin[k] = 6;//这个位置对方不可能赢
        }
        if(myWin[k] == 5){      
          resultTxt.innerHTML = '恭喜,你赢了!';
          over = true;
        }
      }

      // 计算机撤销相应的悔棋
      chressBord[_compi][_compj] = 2; //计算机,已占位置  
      oneStep(_compi,_compj,false);                
      for(var k = 0; k < count; k++){ 
        if(wins[_compi][_compj][k]){
          computerWin[k]++;
          _myWin[k] = myWin[k];
          myWin[k] = 6;//这个位置对方不可能赢
        }
        if(computerWin[k] == 5){
          resultTxt.innerHTML = 'o(?□?)o,计算机赢了,继续加油哦!';
          over = true;
        }
      }
      returnbtn.className += 'unable';
      returnAble = false;
      backAble = true;
    }

至此,比较简单的完成了这三个功能。

三、总结

五子棋游戏的核心关键点是:1、弄清楚有多少种赢法;2、怎么判断是否已经赢了;3、计算机下棋算法。这里巧妙地运用数组存储赢法,判断是否赢了,通过权重比较,计算出计算机该下棋的位置。

过程中用到canvas,之前有学习过,虽然很久没用,查了些资料,复习了怎么画线,画圆,学会了怎么如何清除一个圆等。
然后要注意的是,用原生Js怎么为元素添加、删除class。

最后代码放到github上了,地址:https://github.com/wj704/wj704.github.io/blob/master/five_game.html

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

Javascript 相关文章推荐
Javascript 读书笔记索引贴
Jan 11 Javascript
利用百度地图JSAPI生成h7n9禽流感分布图实现代码
Apr 15 Javascript
一个JavaScript获取元素当前高度的实例
Oct 29 Javascript
把Node.js程序加入服务实现随机启动
Jun 25 Javascript
基于Vue实现tab栏切换内容不断实时刷新数据功能
Apr 13 Javascript
ionic环境配置及问题详解
Jun 27 Javascript
详解设置Webstorm 利用babel将ES6自动转码成ES5
Dec 20 Javascript
Express进阶之log4js实用入门指南
Feb 10 Javascript
微信小程序实现顶部下拉菜单栏
Nov 04 Javascript
关于微信小程序获取小程序码并接受buffer流保存为图片的方法
Jun 07 Javascript
javascript实现文字跑马灯效果
Jun 18 Javascript
微信小程序实现时间戳格式转换
Jul 20 Javascript
Node.js环境下Koa2添加travis ci持续集成工具的方法
Jun 19 #Javascript
Node.js中 __dirname 的使用介绍
Jun 19 #Javascript
JavaScript 上传文件(psd,压缩包等),图片,视频的实现方法
Jun 19 #Javascript
AngularJS实现自定义指令与控制器数据交互的方法示例
Jun 19 #Javascript
jQuery自定义多选下拉框效果
Jun 19 #jQuery
JS实现自动轮播图效果(自适应屏幕宽度+手机触屏滑动)
Jun 19 #Javascript
JS仿淘宝搜索框用户输入事件的实现
Jun 19 #Javascript
You might like
php中require和require_once的区别说明
2014/02/27 PHP
PHP判断指定时间段的2个方法
2014/03/14 PHP
php实现URL加密解密的方法
2016/11/17 PHP
2017年最新PHP经典面试题目汇总(上篇)
2017/03/17 PHP
PHP 实现字符串翻转(包含中文汉字)的实现代码
2017/04/01 PHP
Ajax+Jpgraph实现的动态折线图功能示例
2019/02/11 PHP
laravel框架上传图片实现实时预览功能
2019/10/14 PHP
ThinkPHP类似AOP思想的参数验证的实现方法
2019/12/18 PHP
JavaScript 脚本将当地时间转换成其它时区
2009/03/19 Javascript
jquery关于图形报表的运用实现代码
2011/01/06 Javascript
基于jquery &amp; json的省市区联动代码
2012/06/26 Javascript
微信小程序  生命周期详解
2016/10/27 Javascript
js实现弹窗暗层效果
2017/01/16 Javascript
js基于FileSaver.js 浏览器导出Excel文件的示例
2017/08/15 Javascript
vue使用rem实现 移动端屏幕适配
2018/09/26 Javascript
Vue 动态添加路由及生成菜单的方法示例
2019/06/20 Javascript
[01:23]一分钟告诉你 DOTA2为什么叫信仰2
2014/06/20 DOTA
python为tornado添加recaptcha验证码功能
2014/02/26 Python
python正则表达式中的括号匹配问题
2014/12/14 Python
在cmder下安装ipython以及环境的搭建
2018/10/19 Python
详解Python logging调用Logger.info方法的处理过程
2019/02/12 Python
django之使用celery-把耗时程序放到celery里面执行的方法
2019/07/12 Python
Python 面向对象静态方法、类方法、属性方法知识点小结
2020/03/09 Python
jupyter notebook远程访问不了的问题解决方法
2021/01/11 Python
django使用多个数据库的方法实例
2021/03/04 Python
使用Canvas操作像素的方法
2018/06/14 HTML / CSS
Lookfantastic瑞典:英国知名美妆购物网站
2018/04/06 全球购物
销售自我评价
2013/10/22 职场文书
英文版销售经理个人求职信
2013/11/20 职场文书
小区保洁员岗位职责
2015/04/10 职场文书
师德承诺书2015
2015/04/28 职场文书
2015年保洁员工作总结
2015/05/04 职场文书
圣贤教育改变命运观后感
2015/06/16 职场文书
导游词之大雁塔景区
2019/09/17 职场文书
Redis主从配置和底层实现原理解析(实战记录)
2021/06/30 Redis
Spring boot实现上传文件到本地服务器
2022/08/14 Java/Android