前些日子跟着大佬试着写了个人机对战的五子棋游戏。闲暇之时对其做了一点点改进,于此进行整理;

主要过程及思路 包括以下几个方面:

1. 绘制棋盘;
         2. 确定如何在棋盘上落子;
         3. 确定 触发 游戏结束(gameover)的条件;
         4. 用户 落子触发的事件;
         5. 机器(AI) 落子触发的事件;

1. 绘制棋盘:

在网页上显示的话,canvas肯定是最合适的选项:

h5部分:

<body><h3 id="title">--五子棋--</h3><canvas class="chessB" width="450px" height="450px"></canvas><div><a id="restart">重新开始</a></div>
</body>

CSS部分:

<style>body{background-color:#fff;}h3{text-align:center;font-family:"楷体";}.chessB{cursor:pointer;display:block;margin:50px auto;box-shadow:5px 5px 5px #B9B9B9,-2px -2px 2px #efefef;}div{text-align:center;}div>a{cursor:pointer;font-family:"宋体";color:#fff;padding:10px 20px;background-color:#F3F;border-radius:7%;}
</style>

JS部分:

var chess = document.getElementsByClassName("chessB")[0];
var context = chess.getContext("2d");
context.strokeStyle="#7A7A7A";for(var i=0;i<15;i++){//设置横线//设置起始点的坐标context.moveTo(15,15+i*30);//设置结束点的坐标context.lineTo(435,15+i*30);//将两点连接起来context.stroke();//设置竖线//设置起始点的坐标context.moveTo(15+i*30,15);//设置结束点的坐标context.lineTo(15+i*30,435);//将两点连接起来context.stroke();
}

2. 确定如何在棋盘上落子:

需要封装一个带三个参数的函数;
         三个参数分别为 落子坐标的x,y值,以及判断此时是 用户落子(黑子) 还是 AI落子(红子);

//落子方法
function oneStep(i,j,me){context.beginPath();context.arc(15+i*30,15+j*30,13,0,2*Math.PI);context.closePath();var color;if(me){color="#000";}else{color="red";   }context.fillStyle=color;context.fill();
}//oneStep

3.确定 触发 游戏结束(gameover)的条件

智商正常的人都晓得五子棋四个方向(横,竖,上斜,下斜)上五个相同颜色棋子连成一片即为获胜;
        所以这里需要创建一个二维数组来统计棋盘上每个点可能的每种获胜办法。为了后边更方便的遍历所有的获胜方法,给数组再加上一个维度——用以存储该获胜方法的编号;

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[j+k][i][count]=true;}count++;//找到一个赢法后,count++;}
}
//统计竖线赢法
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<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++;   }
}

这样,后边去写用户落子检测是否达到胜利条件的时候,只需判断当前落下的棋子 所在的 行 列 斜线 上,是否有五个win值为true的节点连在一起即可;
         酱讲明起来可能会有点抽象,迷糊的话可以看 之后的图示。

4. 用户 落子触发的事件:

用户落子时要满足以下条件:(1)落子的坐标上没有别的棋子;(2)此刻是用户落子的回合;(3)游戏尚未结束;

//定义二维数组--标记棋盘上的每个坐标上是否已经下了棋子
var chessBoard=[];
for(var i=0;i<15;i++){chessBoard[i]=[];for(var j=0;j<15;j++){chessBoard[i][j]=0;      }
}var me=true;//判断人是否可以下棋
var over=false;//判断游戏是否结束

同时,为了更加具象地表明 当前的落子 在 所处的 所有获胜方法中,已经有多少个先前落下的棋子满足获胜条件了(将其视作“分值”),需创建一个数组用以存储分值;

var myWin=[];//记录用户在某获胜方法上的分值
var aiWin=[];//记录计算机在某获胜方法上的分值
for(var t=0;t<count;t++){myWin[t]=0; aiWin[t]=0;
}

接下来就是用户落子的具体实现:

chess.onclick=function(e){//如果游戏已经结束就不能继续下棋if(over){return;}//是否轮到人下棋if(!me){return;    }//获取x轴坐标var x=e.offsetX;//获取y轴坐标var y=e.offsetY;var i=Math.floor(x/30);var j=Math.floor(y/30);if(chessBoard[i][j]==0){//下一个子oneStep(i,j,me);//已经落子chessBoard[i][j]=1;   for(var k=0;k<count;k++){if(wins[i][j][k]){myWin[k]++;if(myWin[k]==5){title.innerHTML="--恭喜您获胜了!--";over=true;}}   }}//计算机落子方法//玩家还没有赢的时候if(!over){me=!me;//玩家此时不能下棋AI();}
}//chess.onclick

举个 myWin[ ] 值变化的例子:

         这里原先有一个棋子,设它坐标为(x,y),我们假设此时要让它向右连成5个(此种获胜方法编号设为k)
         则,myWin[k] 从原来的0 ++,变成1(代表该种赢法上已经有一个棋子在了);

         我们在原来棋子的右侧又下了一个子,则该子的 win[x][y][k] 值会变为true (x,y为原来棋子的坐标,k为原来棋子向右连续5个的赢法);
        而一旦检测到 k 赢法上多了一个值为true的棋子后,myWin[k] 值再次加一,从原来的1 变成 2(代表该种赢法上有两个个棋子了);
        以此推论,当我在原来棋子的向右 4格之内的位置继续摆棋,myWin[k] 的值也始终在增加,当摆到5个棋子时,即myWin[]的值达到5时,即宣告游戏结束,用户方获得了最后胜利;
        事实上,当某处落下一枚棋子时,它影响到的绝对不止一种算法,
        例如此图,当该子落下时,在所有的赢法里,有三种赢法对应的myWin 值等于2,而又有n种赢法的值等于1(n可以数出来的,我懒~);
        当然,在用户落子的函数最后,不要忘记添加一个游戏尚未结束的判断,以便让计算机收到可以开始落子的讯息;

//玩家还没有赢的时候if(!over){me=!me;//玩家此时不能下棋AI();//此为计算机落子的函数}

5. 机器(AI) 落子触发的事件:

PS:AI落子涉及到一个权重问题,我看到很多大佬写的AI落子风格更加激进,以进攻为首要目标,其次才是防守拦截;当我按着这个思路去完成代码并测试时,发现AI经常莫名其妙地变成了一个比IG还IG的莽夫,有时候甚至无视用户已经落下的三子(会玩五子棋的都懂这是啥意思),而选择强行进攻~为了避免这点,我更倾向于AI以防守为主,虽然有时候会铁憨憨似的放弃绝妙的进攻机会,但延长游戏时间也算给用户带来了更好的体验;)
        为了让AI落子更有逻辑性,我们需要创建两个二维数组,分别代表用户棋子所在位置的“威胁度”,以及AI棋子所在位置的“威胁度”(“威胁度”不难理解,某个赢法上myWin值到达4时,即为较高威胁度)

var myScore=[];//存储 未落子处 用户的赢法的威胁度
var aiScore=[];//存储 未落子处计算机的赢法的威胁度//初始化威胁度
for(var i=0;i<15;i++){myScore[i]=[];aiScore[i]=[];              for(var j=0;j<15;j++){myScore[i][j]=0;aiScore[i][j]=0;  }
}

每一次AI落子时,都要遍历整个棋盘,对所有未落子的点进行威胁度检测,即 己方四子就绪 威胁度最高,其次为 对方四子就绪…以此推类:
        这里还涉及到一个问题,例如,当用户有两处地方所落下的棋子威胁度相同时:AI要在其中选择出一处更利于己方进攻的;同理,当进攻选择出现相同威胁度时,要挑选出一处更利于防守的;

for(var i=0;i<15;i++){for(var j=0;j<15;j++){//判断是否是空白子if(chessBoard[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(aiWin[k]==1){aiScore[i][j]+=190;  }else if(aiWin[k]==2){aiScore[i][j]+=390;}  else if(aiWin[k]==3){aiScore[i][j]+=1800;}else if(aiWin[k]==4){aiScore[i][j]+=20000;}}                                  }if(myScore[i][j]>max){max=myScore[i][j];x=i;y=j; }else if(myScore[i][j]=max){//面对用户下的子所带来的威胁是同一级别时,计算机优先去在对自己更有利的地方进行拦截(顺便进攻)if(aiScore[i][j]>max){max=aiScore[i][j];x=i;y=j;}   }if(aiScore[i][j]>max){max=aiScore[i][j];x=i;y=j; }else if(aiScore[i][j]=max){if(myScore[i][j]>max){//道理同上,进攻的机会出现同一级别时,选择更有利防守的下棋方法max=myScore[i][j];x=i;y=j;}  }                           }   }
}

当然,最后不要忘记让AI完成页面上的落子,并判断是否满足胜利条件,以及修改me值让用户继续落子;
        整个AI落子函数如下所示:

function AI(){var myScore=[];var aiScore=[];for(var i=0;i<15;i++){myScore[i]=[];aiScore[i]=[];for(var j=0;j<15;j++){myScore[i][j]=0;aiScore[i][j]=0;   }}var max=0;var x=0,y=0;for(var i=0;i<15;i++){for(var j=0;j<15;j++){if(chessBoard[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(aiWin[k]==1){aiScore[i][j]+=190;  }else if(aiWin[k]==2){aiScore[i][j]+=390;}  else if(aiWin[k]==3){aiScore[i][j]+=1800;}else if(aiWin[k]==4){aiScore[i][j]+=20000;}}                                  }if(myScore[i][j]>max){max=myScore[i][j];x=i;y=j; }else if(myScore[i][j]=max){if(aiScore[i][j]>max){max=aiScore[i][j];x=i;y=j;}    }if(aiScore[i][j]>max){max=aiScore[i][j];x=i;y=j; }else if(aiScore[i][j]=max){if(myScore[i][j]>max){max=myScore[i][j];x=i;y=j;}    }}  }   }//计算机落子oneStep(x,y,me);chessBoard[x][y]=1;//判断计算机有没有赢for(var k=0;k<count;k++){if(wins[x][y][k]){aiWin[k]++;if(aiWin[k]==5){title.innerHTML="--很遗憾,是电脑获胜了!--";over=true;}} }//计算机下完子,把me改为true,意思是玩家可以继续下子if(!over){me=!me;}
}

源码刚刚上传,还在审核,等审核完毕了就能从笔者主页找到啦。
        顺便吐槽一句,改了AI算法以后的计算机属实狠人,赢似乎是不想赢的,就怼着我的棋一通拦截,两个也拦,没的商量的那种,最后莫名其妙的它就一不小心到5个了?excuse me…
        附上被虐截图:

——————————————————————————— END ————————————————————————————

小案例 JavaScript-简易五子棋相关推荐

  1. python计算银行余额_Python 小案例实战 —— 简易银行存取款查询系统

    Python 小案例实战 -- 简易银行存取款查询系统 涉及知识点 包的调用 字典.列表的混合运用 列表元素索引.追加 基本的循环与分支结构 源码 import sys import time ban ...

  2. 【Android笔记65】Android小案例之简易版的房贷计算器(附源代码)

    这篇文章,主要介绍如何使用Android实现一个简易版的房贷计算器小案例. 目录 一.房贷计算器 1.1.运行效果演示 1.2.前提准备 (1)等额本息和等额本金

  3. java ssm小案例_简易的SSM框架整合小案例

    简易的SSM框架整合小案例 一.创建一个web工程的maven项目 1.项目名随便起 2.选择好你的maven路径,然后finish 二.配置pom.xml文件 org.springframework ...

  4. Java网络爬虫小案例(详细版)

    有bug:修改了<scope>test</scope>后,在控制台还是不能显示日志信息,没找到解决办法 配置了log4j.properties,控制台没有显示日志信息_连胜是我 ...

  5. 基于控制台的五子棋小游戏(简易)

    基于控制台的五子棋小游戏(简易) 展示 源码: 使用: GoBang类: 展示 话不多说上代码 Don't talk much, say the code! 源码: 使用: new完直接运行 publ ...

  6. HTML+CSS+JavaScript小案例(注册页面表单验证轮播图跳转主页动态表格select联动)

    案例:(表单验证) <!DOCTYPE html> <html lang="en"> <head><meta charset=" ...

  7. Javascript小案例(一):仿淘宝搜索框用户输入事件的实现

    淘宝是我们经常用的一个网上购物平台,打开淘宝网首页,找到淘宝首页的搜索框,如下如所示: (截图日期:2017年6月18日) 大家可以看到,当页面一打开,搜索框中就可以看到灰色字体"少女高跟鞋 ...

  8. 微信小程序实验案例:简易成语小词典

    微信小程序实验案例:简易成语小词典 01.准备工作 1●申请数据接口 现在网络上第三方的免费数据资源越来越少了,这里推荐使用聚合数据的免费接口来实现本次实验案例. 首先访问聚合数据官网https:// ...

  9. JavaScript 国庆倒计时小案例

    本次实现js国庆倒计时小案例 开发工具与关键技术:Visual Studio 2015 JavaScript 当然 首先还是先写出大概样式如下图: 之后便是我的css样式了: 下图便是在页面加载完成的 ...

最新文章

  1. Elasticsearch压缩索引——lucene倒排索引本质是列存储+使用嵌套文档可以大幅度提高压缩率...
  2. #前端# 解决前端页面滑动不顺畅的问题
  3. Data Mining with R
  4. 扬州大学c语言上机作业答案,扬州大学C语言上机作业1-9整理
  5. 单例模式和内部类的初步认识
  6. Python计算器程序实现,支持括号与符号检测、小数、负数运算
  7. 深入浅出通信原理知识点9
  8. 教你js生成二维码-QrCodeJS
  9. IT项目管理的实例与总结
  10. 系统集成方案(一).NET集成方案
  11. 数据与城市正义:回龙观居民“身体被掏空”问题如何解决
  12. 微信小程序文本超出自动换行解决方案
  13. 开发指南专题六:JEECG微云快速开发平台代码生成
  14. Gnuplot特殊字符之Symbol字体
  15. 将EXCEL插入SOLIDWORKS工程图的方法简介
  16. WS2812原理及实现
  17. 尚品汇_第3章_平台属性管理
  18. Spring 的控制反转/依赖注入
  19. 嘉楠科技第一代人工智能芯片勘智Kendryte惊艳亮相
  20. 【数据压缩】实验五——JPEG原理分析及JPEG解码器的调试

热门文章

  1. net start mongodb 发生系统错误 1058。
  2. 实时计算与SparkSteaming的对比
  3. win10 无法识别x64dbg 插件
  4. 随笔-杂记-将对您的电脑造成伤害。 您应该将它移到废纸篓
  5. 2022-2028全球与中国紫外线点固化系统市场现状及未来发展趋势
  6. python 客户端同构_同构python算法
  7. 微信小程序css篇----字体(Font)
  8. c语言乘法运算出负数,C语言用数组表示大数 乘法得出的结果怎么是负数之类的奇怪数字?...
  9. 如何获取微信小店页面路径
  10. 音诺恒RK3568高性能智能商显安卓广告机主板解决方案