整个小游戏主要分成界面部分与赢法统计两方面。
源码地址:https://github.com/sunshineqt/test/tree/master/five-in-line
在线预览:https://sunshineqt.github.io/test/five-in-line/index.html
效果图:
先讲界面部分,主要知识点分为两点:
1 棋盘的画法
canvas绘制直线、设置画笔颜色
2-棋子的画法
canvas画圆、填充渐变色
以上两点可细分为五步:
(1) 获取canvas对象
var chess=document.getElementById("chess"); //1-获取canvas对象 var context=chess.getContext('2d');
(2) for循环绘制棋盘
context.strokeStyle="#b0b0b0";//设置划线的颜色 var drawChessBoard=function(){ //2-绘制棋盘 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(); } }
这里使用封装函数,将绘制棋子方法封装起来,方便调用,如水印图片设置之后再调用,防止水印图片遮挡住了棋盘。
(3) 棋盘上的水印图片
var logo=new Image(); //3-棋盘上的水印图片 logo.src="image/logo.png"; logo.onload=function(){ context.drawImage(logo,0,0,450,450); //水印图片设置 drawChessBoard();//后调用绘制棋盘函数,防止水印图片遮挡住棋盘 }
(4) 棋子的绘制
var oneStep=function(i,j,me){ //4-绘制棋子 context.beginPath(); context.arc(15+i*30,15+j*30,13,0,2*Math.PI); //通过arc弧度函数画圆 context.closePath(); var gradient=context.createRadialGradient(15+i*30,15+j*30,13,15+i*30,15+j*30,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(); }
ps:context.stroke()用来实现描边,而context.fill()用来实现填充。
(5) 鼠标点击,落子实现
var me=true;//控制轮流下棋,首先黑子先下 var chessBoard=[]; //定义一个二维数组,用来存储棋盘上位置的信息,初始化为0表示棋盘上无子 for(var i=0;i<15;i++){ chessBoard[i]=[]; for(var j=0;j<15;j++){ chessBoard[i][j]=0; } } chess.onclick=function(e){ //5--鼠标点击落子情况 var x= e.offsetX; var y= e.offsetY; var i=Math.floor(x / 30);//棋盘上的位置索引 var j=Math.floor(y / 30); if(chessBoard[i][j]==0){ //当存储为0,即棋盘上无子情况才能落子 oneStep(i,j,me);//黑子先下 if(me){ //如果下的是黑子,在二维数组中存储为1 chessBoard[i][j]=1; }else{ //如果下的是白子,在二维数组中存储为2 chessBoard[i][j]=2; } me=!me; } }
再讲我方及计算机方落子部分:
判断计算机落子,可先遍历棋盘上哪些交叉点可以落子,基于某种规则给交叉点计算得分,则得分最高的交叉点即为计算机要落子的地方
- 赢法数组:记录五子棋所有的赢法,用三维数组表示,前面两维表示棋盘
- 每一种赢法的统计数组,用一维数组表示
- 如何判断胜负,根据赢法的统计数组,如果说某一种赢法已经达到了五颗棋子,则这种赢法相当于被实现了,即某一方已经胜利了
- 计算机的落子规则,根据赢法的统计数组进行加分,若棋盘上一条线上已有同种颜色的棋子越多,则再落子的价值越大,则对其加一个更高的分数。根据分数最高的位置进行落子。
具体步骤:
(6) 定义赢法数组,表示为三维数组,保存了五子棋的所有赢法
var wins=[]; //6-定义赢法数组,为三维数组,保存了五子棋所有的赢法 for(var i=0;i<15;i++){ wins[i]=[]; for(var j=0;j<15;j++){ wins[i][j]=[]; } }
(7) 定义赢法种类,用变量count表示,初始化为0
var count=0;//7-定义赢法种类,初始化为0
(8) 填充赢法数组
//8-填充赢法数组 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++; } } console.log(count);//看有多少种赢法
(9) 赢法的统计数组
//9-赢法的统计数组 var myWin=[];//9-我方赢的数组 var computerWin=[];//9-计算机方赢的数组 for(var i=0;i<count;i++){ //9-赢法的初始化,这里需要注意,赢法的初始化用到//count变量,因此需要在count变量计算完毕之后进行赢法的初始化 myWin[i]=0; computerWin[i]=0; } if(over){ //9--判断游戏是否结束,置于onclick函数开头处 return; } for(var k=0;k<count;k++){ //9-我方赢法统计数组,置于onclick函数的黑子落子后 if(wins[i][j][k]){ myWin[k]++; computerWin[k]=6;//此时,计算机在第k种赢法上不可能赢了 if(myWin[k] == 5){ window.alert("你赢了"); over=true; } } }
ps:如果存在一个k使得myWin[k]==5,则第k种赢法已经实现了。
(10) 实现计算机落子
在onclick函数开始部分,设置为该onclick函数只对我方落子时有效
if(!me){ //10-设置onclick函数只对我方有效 return ; } 这种情况下,当我方落子时,可直接将棋盘位置存储为1: chessBoard[i][j]=1; //10-前面已经设置该click函数只对me即黑子有效,所以可以不用判断,直接存为1 在onclick函数结尾处,在前面赢法统计数组更新完毕后,判断over,若游戏没有结束的话,将下棋的权利交给计算机,同时调用计算机落子函数 if(!over){ //10-判断是否结束,否的话,调用计算机落子函数 me=!me;//10-如果没结束就把下棋的权利交给计算机 computer(); } var computer=function(){ //10-计算机落子实现 var myScore=[]; var computerScore=[]; 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++){ //10-统计我方和计算机可能赢的分数 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]+=100; }else if(myWin[k]==2){ myScore[i][j]+=200; }else if(myWin[k]==3){ myScore[i][j]+=500; }else if(myWin[k]==4){ myScore[i][j]+=800; } if(computerWin[k]==1){ computerScore[i][j]+=120; }else if(computerWin[k]==2){ computerScore[i][j]+=220; }else if(computerWin[k]==3){ computerScore[i][j]+=560; }else if(computerWin[k]==4){ computerScore[i][j]+=860; } } } } } }
ps: 遍历整个棋盘,若棋盘上点为空,则可落子,对其分数进行计算。假设第k种赢法在[i,j]处为true,则在此处落子是有价值的,对其进行加分。积分的计算则需要用到赢法的统计数组。若myWin[k]=1,则第k种赢法已经存在一个棋子,这时在该处落子是有价值的,这里通过加分多少来体现落子价值高低。若有一个棋子存在再落子,则加100,若有2个棋子存在再落子,则加200,若有3个棋子存在再落子,则加500,若有4个棋子存在再落子,则加800,
(11) 找出myScore、computerScore分数最高的点
定义max保存最高分数,u,v保存最高分数的点的坐标,在k循环完后做寻找最高分数及对应坐标这件事。U,v即为计算机要落子的点,然后调用oneStep(u,v,false)
在computer()函数部分实现
var max=0;//11-用来保存最高分数 var u= 0,v=0;//11-用来保存最高分数的点坐标 if(myScore[i][j] > max){ //11-记录最高分及坐标 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){ //11-记录最高分及坐标 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; } } oneStep(u,v,false); chessBoard[u][v]=2;//11-表示计算机在u,v处落子
(12) 计算机落子后,更新赢法的统计数组
其逻辑类似于我方的赢法统计数组,在computer()函数最后实现
for(var k=0;k<count;k++){ //12-计算机赢法统计数组 if(wins[u][v][k]){ computerWin[k]++; myWin[k]=6; if(computerWin[k] == 5){ window.alert("计算机赢了"); over=true; } } } if(!over){ //12-判断是否结束,否的话,调用计算机落子函数 me=!me;//12-如果没结束就把下棋的权利交给我方 }
效果图:
js插曲:
JavaScript数据类型大致可分为三种:基本数据类型、复合数据类型、特殊数据类型
基本数据类型:数值型(整型、实型)、布尔型、字符串型
复合数据类型:数组、对象
特殊数据类型:null、undefined
parseInt()、parseFloat()系统函数用于数据类型转换
typeof 用于判断表达式的数据类型
instance of判断一个变量是否是某个对象(类)的实例,返回值是布尔型的
prompt()函数用于显示提示用户进行输入的对话框
writeln()方法直接在浏览器中输出内容