五子棋项目总结 JavaScript+jQuery(插件写法)+bootstrap(模态框)
Html部分(界面):
1.五子棋棋盘由canvas完成;
2.两个按钮,样式由bootstrap完成;
3.菜单按钮对应的模态框,可以选择游戏模式:玩家自由对战,和电脑对战,还可以指定谁先执子和哪个颜色先执子;
4.游戏结束显示的模态框;
模态框由modal.js插件实现
Js部分
模式1玩家对战电脑
模式2玩家自由对战
1游戏初始化,绘制棋盘
1.1创建canvas,将canvas和span(小黄点)添加到html的div中,初始化一个二维数组defaultOptions.chessArray[i][j]使15*15的棋盘上每个点的默认值都是没有棋子。
1.2接下来绘制棋盘,棋盘的三个参数(棋盘的外边距、内边距、每格的宽度)共同决定了棋盘的总宽度和高度,通过getContext('2d')确定2d上下文对象,设定ctx.fillStyle、ctx.font和ctx.textAlign(用于调整A-O及1-15的位置),开始画竖线,总共有15根横线,使用循环,ctx.beginPath(),画竖线通过ctx.moveTo和ctx.lineTo调整每根线的起点和终点(x坐标以间隔作为步进不断递增,y不变),然后利用ctx.stroke()??,然后利用ctx.fillText填充棋盘边缘的字母(A-O),横线的画法基本相同,不同之处是x保持不变,y递增。
利用ctx.rect(起点x,起点y,宽度,高度)画大矩形框包围这些直线。
为了美观,为棋盘添加4个小黑点(它们的位置类似于黄金分割点),同样需要提前为其制定样式(ctx.fillStyle),ctx.arc(圆心x坐标, 圆心y坐标, 半径, 起始角度, 最终角度)。
1.3游戏初始化时就应该监听鼠标事件,chess_canvas.on('mousedown', function(event) {})
1.3.1如果不是玩家下棋阶段,点鼠标没有任何效果;如果游戏已经结束,点鼠标也没有任何效果 ,这时候只有点重新开始才能清空棋盘;所有监听事件时首先判断是否处于这两种情形之一,若是直接返回;
1.3.1.1玩家下棋阶段的判断:两种情况,玩家自由对战模式直接返回true,因为两个都是玩家;玩家对战电脑,只有当前要下的棋子颜色和设置的玩家执子的颜色相同时才返回true;
1.3.1.2游戏是否结束的判断:若游戏结束则隐藏小黄点;
1.3.2 如果不是上两种情况,则获取鼠标点击的位置(event.offsetX减去棋盘外边距、内边距),再将位置转换成左边(除以间隔,注意取整),准备绘制棋子,如果那个点没有棋子,则获取当前玩家或电脑要下棋的颜色getCurrentColor(),结合坐标和颜色下棋playChess(pointX, pointY, chessColor);每下一枚棋子,我们都要记录下来(白棋和黑棋分别是两个参数defaultOptions.blackLastChess = [pointX, pointY]、defaultOptions.whiteLastChess = [pointX, pointY]记录的用途??标记获胜棋子时会用);每下完一枚棋,就应该判断下棋方是否胜利;
获取要下棋的颜色:开始的第一个棋子不是黑色就是白色,分成这两种情况,分别取模交替变换颜色defaultOptions.chessCount % 2 == 0、defaultOptions.chessCount % 2 == 1;
下棋,其实就是绘制棋子(类似于前面的四个小点,不过效果不同),首先将棋子的小黄点显示,纪录棋子的数量和不同位置棋子的颜色(二维数组),棋子的半径为间隔的2/5,根据传入的坐标定位棋子,并ctx.arc画出棋子,为了更加逼真,为棋子添加径向渐变,ctx.createRadialGradient(1, 2, r,1, 2, 0),通过传入的颜色,添加颜色的渐变gradient.addColorStop(0, "#0A0A0A"); gradient.addColorStop(1, "#636766");最后为刚下的棋子添加小黄点作为标识,chessboardOptions.chessboard_container.find('span.indicator').css(..);
CSS中添加top和left值定位该span元素,span是块元素,为了使其显示更美观需要倒圆角;
判断下棋方是否胜利(该判定方法是针对玩家的判定是否胜利的方法)
连续棋子的个数作为是否胜利的评判标准,当连续棋子的个数大于或等于5个时,取得胜利,而连续棋子可能会在以自己为中心的4条线上出现(x方向,y方向,左斜,右斜),这4条线在具体分析时要以自己为中心拆成8个方向(上,下,左,右,左上,左下,右上,右下),依次判断自己相邻的棋子(判断颜色是否相同),要满足连续,所以一旦有别的颜色或者空位出现,立即break,去反向(比如刚刚是上,现在应该接着判断下)在进行判断 ,连续棋子的个数仍然累加;如果个数大于等于5个,调用hasWin(chessColor),首先结束游戏(改变变量defaultOptions.isGameStart = false;defaultOptions.isGameOver = true;隐藏小黄点),再标记获胜的棋子
要标记获胜的棋子,需要最后几个相连的棋子都找到,从最后一个棋子入手,每下一个棋子,都会在defaultOptions.blackLastChess或defaultOptions.whiteLastChess中记录下最后一个棋子的位置,这样下完棋如果游戏结束,取得胜利,我们从它入手就能找到其余几个棋子,同样,以这个棋子为中心,在4条线8个方向上去寻找相邻同色的棋子,并记录它们的位置,每个棋子的横坐标(lineChess[0][nums] = i;)和纵坐标(lineChess[1][nums] = m;)分别由二维数组记录,最后找完以后,通过循环遍历所有连续的棋子,给它们进行标记;
markChess(根据坐标和颜色画圆)
1.4五子棋AI
由于是计算机自动下棋,所以要考虑权重问题,通过算法确定棋下在哪里最好。
双重循环遍历15*15棋盘上的每个点,只要是没有放置棋子的点,都有可能作为下棋点,计算每个空闲点的权重,采用冒泡法,找到最大权重,并记录位置,然后在该位置下棋,下完棋后同样需要记录defaultOptions.blackLastChess,以便最后标识获胜棋子。下棋之后,可能出现两种情形,一是电脑获胜,根据不同的情况设定不同的权重,超过一定的权重值时电脑获胜;二是电脑没有获胜,则轮到下一个玩家;
计算下棋至(i,j)的权重,首先基于该位置给一定的权重,棋盘的中心处,权重最高,向四周扩散的位置,权重逐渐减小。同样,权重的计算也要考虑4条线,每条线上要考虑两种情况(黑棋、白棋),即要把对手下在该点处的权重也作为总权重的一部分,最终将4条线上,每条线两种情况的权重都相加得到总权重,用于冒泡阶段比较;电脑的权重和玩家的权重可以这样理解,电脑的权重可以理解为电脑进攻,计算玩家的权重可以理解为电脑的防守,进攻和防守选一个为主,这里设置进攻为主,在设置权重时进攻的权重比防守多一位数(即大10倍左右);
每个方向上权重的判定,要根据该方向连子的情况及连子两端的情况具体分析,每条线分为两个方向,每个方向判断是否有颜色相同且连续(不连续则判断两端或break)的棋子,如果有则num++,没有则判断该位置是否没有棋子,没有棋子side1 = true;反向写法相同;最后返回num,side1,side2存到一个对象中;
根据棋子的分布具体计算某点的权重,从对象中取出对应方向的num,side1,side2作为函数参数,并增加isAI作为判定电脑和玩家的bool变量。在该函数中使用switch判断num(连子的个数1,2,3,4,5),针对不同的num又有不同的计算权重, 比如num=1和num=4相比,num=4设定的权重应该更高,这里设定权重,num=1为一级(两位数),num=2和num=3为一级(三位数),num=4为一级(4位数),num=5为一级(5位数);连子两端的情况也会影响权重,分为连子两端都没有棋子(side1 && side2)和连子一端有棋子一端没有棋子(side1 || side2),两端都没有棋子的情况权重肯定更高,这种情况设定的权重比第二种的权重高一级,由于考虑了玩家在该点处的权重,还要设置玩家对应的不同情况的权重,weight = isAI ? 15 : 10;由于以进攻为主,所以玩家的权重比电脑的权重稍小,但是都处于同一级。
以上部分按照jQuery插件形式编写为闭包的形式
以下部分根据菜单栏选中的不同结果,获取到这些结果并传进插件中,利用defaultOptions = $.extend(defaultOptions, options);可以将这些变化的参数传递进入jQuery插件,可以满足一个插件供两种模式的共用
游戏结束时内容的动态显示
通过callback_victory回调函数,在游戏取得胜利后调用,根据chesscolor分为两种显示情况,即白棋赢了和黑棋赢了,每种情况还应该把玩家执子的颜色提示出来,就是判断玩家颜色
$('#showResult').find('.modal-title').text('提示【你执' + (playerColor == 1 ? "白子" : "黑子") + '】');
菜单栏的动态显示
分为几个部分的工作:
1玩家自由对战什么时候禁用,什么时候不禁用;
先行执子:对其设定监听事件$("[name='firstStart']").on("change", function(e) {})
这里默认有两种模式可选:一是玩家自由对战,二是玩家对战电脑,当选择玩家先执子时,才有可能有玩家自由对战模式,此时玩家自由对战模式可选,而先行执子是电脑,则要使玩家自由对战模式处于禁用状态;
2更新文本提示信息
要及时更新显示的文本提示,首先通过start = $("[name='firstStart']").filter(":checked").val();获取到底选中的是哪个;
3通过选中的先行执子和先行颜色,我们就能判断玩家是什么颜色,并最后动态设置传进闭包中
4判断处于哪种模式,直接判读玩家自由对战模式是最方便的,看是否勾选了即可,剩下的就是另一种情况
5监听新的游戏按钮,点击后就要重新设定模式,设定先行执子,先行颜色,将这些传进插件,开始新游戏;
分为两种模式:玩家自由对战模式、玩家对战电脑,不管是哪种模式,都要设定对应的GameMode、playerColor、blackStart值和callback_victory回调函数
6.还应该有默认的GameMode、playerColor、blackStart值和callback_victory回调函数,因为玩家可能进入游戏界面后直接开始游戏,没有通过菜单里设置这些参数,默认设置为玩家对战电脑模式,玩家先行,黑棋先行,对应的回调函数也得设定,以便胜利后显示。