黑白棋,又叫反棋(Reversi)、奥赛罗棋(Othello)、苹果棋、翻转棋。黑白棋在西方和日本很流行。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋的棋盘是一个有 8×8 方格的棋盘。开始时在棋盘正中有两白两黑四个棋子交叉放置,黑棋总是先下子。
黑白棋,又叫反棋(Reversi)、奥赛罗棋(Othello)、苹果棋、翻转棋。黑白棋在西方和日本很流行。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋的棋盘是一个有 8×8 方格的棋盘。开始时在棋盘正中有两白两黑四个棋子交叉放置,黑棋总是先下子。(1) 下子规则。把自己颜色的棋子放在棋盘的空格上,而当自己放下的棋子在横、竖、斜 8 个方向内有一个自己的棋子,则被夹在中间的全部翻转成为自己的棋子。并且只有在可以翻转棋子的地方才可以下子。如果玩家在棋盘上没有地方可以下子,则该玩家对手可以连下。(2) 胜负判定条件。双方都没有棋子可以下时棋局结束,以棋子数目来计算胜负,棋子多的一方获胜。在棋盘还没有下满时,如果一方的棋子已经被对方吃光,则棋局也结束。将对手棋子吃光的一方获胜。本文开发黑白棋游戏程序,黑白棋游戏的运行界面如图上图所示。该游戏具有显示执棋方可以落棋子的位置提示功能和判断胜负功能。在游戏过程中,单击“帮助”按钮则显示执棋方可落子位置(
图片表示可落子位置,如图 1 所示)。
■ 图 1 表示执棋方(黑方)可落子位置
01、黑白棋游戏设计的思路
棋子和棋盘游戏开发时,需要事先准备黑白两色棋子和棋盘图片(如图 2 所示)。游戏最初显示时,棋盘上画上 4 个棋子。这里为了便于处理,采用一个 qizi 二维数组用来存储棋盘上的棋子。
■ 图 2 黑白两色棋子和棋盘
翻转对方的棋子
需要从自己落子(x1,y1)为中心的横、竖、斜 8 个方向上判断是否需要翻转对方的棋子,程序中由鼠标的 mousedown 事件实现。在 mousedown 事件中参数 event 对象含有单击位置像素坐标(event.pageX,event.pageY),处理后变成 Canvas 对象内像素坐标(x,y)。再由如下公式换算:
x1 = Math.round((x- 40) / 80);//Math.round 四舍五入y1 = Math.round((x- 40) / 80);
复制代码
经过换算转换为棋盘坐标(x1,y1)。
最后从左、左上、上、右上、右、右下、下、左下 8 个方向上调用过程 DirectReverse(x1,y1,dx,dy)翻转对方的棋子。而具体棋子的翻转由 FanQi(x,y)实现。FanQi(x,y)修改数组 qizi 的(x,y)保存棋盘上的棋子信息。
function FanQi(x,y)if(qizi[x][y]== BLACK)qizi[x][y]= WHITE;elseqizi[x][y]= BLACK,
复制代码
显示执棋方可落子位置
Can_go(x1,y1)从左、左上、上、右上、右、右下、下、左下 8 个方向上调用函数 CheckDirect(x1,y1,dx,dy)判断某方向上是否形成夹击之势,如果形成且中间无空子则返回 True,表示(x1,y1)可以落子,(x1,y1)处可以落子则用图片图片显示。
判断胜负的功能
qizi[][]二维数组保存棋盘上的棋子信息,其中元素保存 1,表示此处为黑子;元素保存 2,表示此处为白子;元素保存 0,表示此处为无棋子。通过对 qizi 数组中各方棋子数的统计,在棋盘无处可下时,根据各方棋子数判断出输赢。
02、关键技术
Canvas 对象支持的 JavaScript 的鼠标事件
Canvas 对象支持所有的 JavaScript 的鼠标事件,包括鼠标单击(Mouse Click)、鼠标按下(Mouse Down)、鼠标抬起(Mouse Up)和鼠标移动(Mouse Move)。对 Canvas 添加鼠标事件方式有两种,一种方式如下:
//鼠标事件 mouse eventcanvas.addEventListener( "mousedown",doMouseDown,false);canvas.addEventListener( 'mousemove',doMouseMove,false);canvas.addEventListener('mouseup',doMouseUp,false);
复制代码
另外一种方式在 JavaScript 中被称为反模式。
canvas.onmousedown = function(e)canvas.onmouseup = function(e)canvas.onmousemove = function(e)
复制代码
获取鼠标在 Canvas 对象上的坐标
由于 Canvas 上鼠标事件中不能直接获取鼠标在 Canvas 的坐标,所获取的都是基于整个屏幕的坐标。所以通过鼠标事件 e.clientX 与 e.clientY 来获取鼠标位置,然后通过 Canvas.getBoundingClientRect()方法来获取 Canvas 对象相对浏览器窗口的位置(方法返回一个矩形对象,包含四个属性:left、top、right 和 bottom。分别表示 Canvas 对象各边与页面上边和左边的距离),通过计算得到鼠标在 Canvas 中的坐标。
事件坐标转换成 Canvas 坐标代码如下:
function getPointOnCanvas(canvas,x,y)var bbox = canvas.getBoundingClientRect()return(x: x - bbox.left,y:y - bbox.top);
复制代码
如果同时考虑 CSS 设置 width 和 height 的情况,则代码如下。
function getPointOnCanvas(canvas,x,y)var bbox = canvas.getBoundingClientRect();return(x: x- bbox.left *(canvas.width / bbox.width)y:y- bbox.top *(canvas.height/bbox.height)
复制代码
03、黑白棋游戏设计的步骤
游戏页面 index.html
<html >< head >< title>黑白棋</title><meta http - equiv= content - type content = "text/html; charset = utf - 8">< meta name="Generator" content ="EditPlus"><meta name ="Author" content = "n<meta name ="Keywords” content =<meta name ="Description" content = "</head ><body onload="init()”onkeydown ="DoKeyDown(event)"><canvas id="myCanvas”width="720”height ="720"你的浏览器还不支持</canvas>< img id= "whitestone"src = "img/whitestone.png"style= "display:none;">< img id= "blackstone"src = "img/blackstone.ng" style= "display:none;">< img id="gi pan1"src = "img/qi panl.jpg"style= "display:none;"><img id="Info2"src ="img/Info2.ng”style = "display:none;"><div id= "message txt" style= "text - align:center;border:lpx solid red; width:720px;height20px;font- size:20px;"></div>< input type ="button”value="走棋提示”onclick ="DoHelp()">< script type ="text/javascript"src ="Main.js"></script></body></html>
复制代码
设计脚本(Main.js)
1. 常量定义
游戏中常量定义,其中 BLACK 黑棋为 1,WHITE 白棋为 2,无棋为 0。
//常量var BLACK = 1;var WHITE = 2;var KONG = 0;var w= 80;var h= 80:
复制代码
以下获取 Canvas 对象,以及用到的棋子和棋盘图片、提示图片。
//构造一个 gizi[][]二维数组用来存储棋子//当前走棋方var mycanvas = document.getElementById('myCanvas');var context = mycanvas.getContext(2d'):var gizi=new Array();var curQizi= BLACK;var whitestone= document.getElementById("whitestone");var blackstone = document.getElementById("blackstone");var qipan = document.getElementById("qi pan1");var info = document.getElementById("Info2");var message txt = document.getElementById("message txt");//白棋图片//黑棋图片//棋盘//提示图形//提醒文字
复制代码
2. 初始化游戏界面
游戏开始时,调用 init()函数对保存棋盘上的棋子信息的 qizi 数组初始化,同时在棋盘上显示初始的 4 个棋子。
function init()finitLevel();showMoveInfo();//棋盘上初始 4个棋子//当前走棋方信息mycanvas.addEventListener( "mousedown,doMouseDown,false);function initLevel()var i,j;for(i=0;i<8;i++){ qizi[i]= new Array();for(j=0;j<8;j++)qizi[i][j]= KONG;//棋盘上初始 4 个棋子//1 为黑,2为白,0为无棋子qizi[3][3]= WHITE;qizi[4][ 4]= WHITEqizi[3][4] = BLACKqizi[4][3]= BLACK;DrawMap();//初始化界面//画棋盘和所有棋子message txt.innerHTML="该黑棋走子”//画棋盘和所有棋子function DrawMap()context.clearRect (0,0,720,720);context.drawImage(qipan,0,0,qipan.width,gipan.height);for(i= 0;i<gizi.length;i++)//行号//列号for(j= 0;j< gizi[i].length;j++)var pic;switch(qizi[i][j])case KONG:break;case BLACK://0//1pic =blackstone:context.drawImage(pic,w * j,h *,pic.width,pic.height);break;//2case WHITE:pic = whitestone;context.drawImage(pic,w * j,h *i,pic.width,pic.height);break;}}}}
复制代码
调用 showMoveInfo()函数显示轮到哪方走棋。
function showMoveInfo(// 当前走棋方是黑棋message_txt.innerHTML ="该黑棋走子”if(curOizi== BLACK)elsemessage_txt.innerHTML="该白棋走子”
复制代码
init()函数同时对 canvas 添加鼠标单击事件侦听,如果 canvas 被单击则执行 doMouseDown 函数完成走棋功能。
3. 走棋过程
如果是棋盘被单击,则此位置像素信息(event.clientX,event.clientY)可以转换成棋盘坐标(x1,y1),然后判断当前位置(x1,y1)是否可以放棋子(符合夹角之势),如果可以则此位置显示自己的棋子图形,调用 FanALLQi(i,j)函数从左、左上、上、右上等 8 个方向翻转对方的棋。最后判断对方是否有棋可走,如果对方可以走棋则交换走棋方。如果对方不可以走棋,则自己可以继续走棋,直到双方都不能走棋,显示输赢信息。
function doMouseDown(event)var x = event.clientXvar y = event.clientY:var canvas = event.target;var loc = getPointOnCanvas(canvas,x,y);console.log("mouse down at point( x:"+ loc.x+",y:"+ loc.y+")");clickQi(loc);function getPointOnCanvas(canvas,x,y)var bbox = canvas.getBoundingClientRect();return( x: x-bbox.left * (canvas.width/ bbox.width)y:y- bbox.top * (canvas.height / bbox.height)};function clickQi(thisQi){ var x1,yl;xl = parseInt((thisQi.y- 20)/ 80);yl= parseInt((thisQi.x- 20)/ 80);if(Can_go(x1,y1)){ //trace("can");qizi[x1][yl]= curQizi;//从左、左上、上、右上、右、右下、下、左下方向翻转对方的棋FanALLO(x1,yl);DrawMap();//parseInt()函数丢弃小数部分,保留整数// 判断当前位置是否可以放棋子//判断对方是否有棋可走,如有交换走棋方&&checkNext(WHITE))if(curQizi== WHITE &&checkNext(BLACK) curQizi== BLACKif(curQizi== WHITE) [curQizi= BLACK;message txt.innerHTML ="该黑棋走子”;elsecurQizi= WHITE;message txt.innerHTML=“该白棋走子“else if(checkNext(curQizi)) //判断自己是否有棋可走,如有,给出提示message txt.innerHTML ="对方无棋可走请继续//双方都无棋可走,游戏结束,显示输赢信息elseisLoseWin();//统计双方的棋子数量,显示输赢信息else { message txt.innerHTML="不能落子!";
复制代码
4. 可否落子判断
Can_go(x1,y1)从左、左上、上、右上、右、右下、下、左下 8 个方向判断(x1,y1)处可否落子。
function Can go( x1,yl)//从左、左上、上、右上、右、右下、下、左下 8 个方向判断if(CheckDirect(x1,y1,- 1,0)== true)[return true;if(CheckDirect(x1,y1,- 1,-1)== true){ return true;if(CheckDirect(x1,yl,0,- 1) == true) (return true;if(CheckDirect(x1,y1,1,- 1)== true) { return true;if(CheckDirect(x1,y1,1,0)== true) { return true;if(CheckDirect(x1,y1,1,1)== true) { return true;if(CheckDirect(x1,y1,0,1)== true) { return true;if(CheckDirect(x1,y1,- 1,1)== true) { return true;return false;
复制代码
调用 CheckDirect()函数判断某方向上是否形成夹击之势。如果形成且中间无空子则返回 True。
function CheckDirect(x1,yl,dx,dy)lvar x,Y;var flag = false;x=x1 + dx;y=yl + dy;while(InBoard(x,y) && !Ismychess(x,y) && qizi[x][y] != 0){ x+= dx;y += dy;flag = true;//构成夹击之势if(InBoard(x,y) && Ismychess(x,y) && flag == true) { //该方向落子有效return true;return false;
复制代码
调用 checkNext(i)函数验证参数代表的走棋方是否还有棋可走。
验证参数代表的走棋方是否还有棋可走¥@param i代表走棋方,1为黑方,2为白方@return true/false¥function checkNext(i)!old= curQizi;curOizi=i;if(Can Num()> 0){ curQizi=old;return true;elsecurQizi=old;return false;}}
复制代码
调用 Can_Num()函数统计可以落子的位置数。
function Can Num()var i,j,n=0;for(i=1;i<=8;i++) (for(j=1;j<=8;j++){ if(Can go(i,j)){ //统计可以落子的位置数n=n+1;return n;//可以落子的位置数
复制代码
5. 翻转对方的棋子
FanALLQi(int x1,int y1)从左、左上、上、右上、右、右下、下、左下 8 个方向翻转对方的棋子。
function FanALLQi(x1,yl)//从左、左上、上、右上、右、右下、下、左下 8 个方向翻转if(CheckDirect(x1,y1,- 1,0)== true){DirectReverse(xl,y1,- 1,0);if(CheckDirect(x1,yl,- 1,- 1) == true) { DirectReverse(x1,y1,- 1,-1);if(CheckDirect(x1,yl,0,- 1)== true) { DirectReverse(x1,y1,0,- 1);if(CheckDirect(x1,y1,1,- 1) == true) (DirectReverse(x1,yl,1,- 1);if(CheckDirect(x1,yl,1,0) == true) { DirectReverse(x1,yl,1,0);if(CheckDirect(x1,y1,1,1) == true) { DirectReverse(x1,yl,1,1);if(CheckDirect(x1,y1,0,1)== true) { DirectReverse(x1,y1,0,1);if(CheckDirect(x1,y1,- 1,1)== true) { DirectReverse(x1,yl,- 1,1);}
复制代码
调用 DirectReverse()函数针对某方向上已形成夹击之势的对方棋子进行翻转。
function DirectReverse(x1,yl,dx,dy)var x,Y;var flag = false;x= x1 + dx;y= yl + dy;while(InBoard(x,y) && !Ismychess(x,y) && qizi[x][y] != 0)x+= dx;y += dy;flag = true;//构成夹击之势if(InBoard(x,y) && Ismychess(x,y) && flag == true)do !x-= dx;y-=dy;if((x!= xl y! y1)){ FanQi(x,y);) while((x != xl y!= y1));
复制代码
调用 FanQi(int x,int y)函数将存储(x,y)处棋子信息 qizi[x][y]进行反色处理。
function FanQi(x,y)if(qizi[x][y]== BLACK)qizi[x][y]= WHITE;else(qizi[x][y]= BLACK;
复制代码
调用 InBoard()函数判断(x,y)是否在棋盘界内。如果在界内则返回 True,否则返回 False。
//InBoard()函数判断(x,y)是否在棋盘界内function InBoard(x,yif(x>=0&& x<=7 && y>=0 && y<=7){return true;else return false;
复制代码
6. 显示执棋方可落子位置
“走棋提示”按钮单击事件函数是 DoHelp()函数,它显示可以落子的位置提示。调用 Show_Can_Position()函数用图片
显示可以落子的位置。
function DoHelp)showCanPosition();//显示可以落子的位置function showCanPosition()//显示可以落子的位置var i,j;var n=0;for(i=0;i<=7;i++)[//可以落子的位置统计for(j=0;j<=7; j++)[if(gizi[i][j]==0 && Can_go(i,j)){ n=n+1;pic = info;//显示提示图形context.drawImage(pic,w*i+ 20,h*i+20,pic.width,pic.height);
复制代码
7. 判断胜负功能
调用 isLoseWin()函数统计双方的棋子数量,显示输赢信息。
//显示输赢信息function isLoseWin() var whitenum = 0;var blacknum = 0;var n=0,x,Y;for(x=0;x<= 8;x++)(for(y=0;y<=8;y++){ if(qizi[x][y] != 0)[{ n=n+1;if(qizi[x][y]== 2) { whitenum += 1;if(qizi[x][y]== 1){ blacknum += 1;if(blacknum > whitenum) fmessage txt.innerHTML ="游戏结束黑方胜利,黑方:"+ String(blacknum)"白方+String(whitenum);elsemessage txt.innerHTML="游戏结束白方胜利,黑方:"+ String(blacknum)+"白方:+String(whitenum);}}
复制代码
至此,就完成黑白棋游戏设计了。
评论