HTML5 实现黑白棋游戏|附代码
黑白棋,又叫反棋 (Reversi) 、奥赛罗棋 (Othello) ,苹果棋,翻转棋。黑白棋在西方和日本很流行。游戏通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。黑白棋的棋盘是一个有 8*8 方格的棋盘。下棋时将棋下在空格中间,而不是像围棋一样下在交叉点上。开始时在棋盘正中有两白两黑四个棋子交叉放置,黑棋总是先下子。
▍图11-1 黑白棋游戏运行界面
▍图11-2 i表示执棋方(黑方)可落子位置
01
黑白棋游戏设计的思路
1. 棋子和棋盘
游戏开发时,需要事先准备黑白两色棋子和棋盘图片(如图11-3所示)。游戏最初显示时,棋盘上画上4个棋子。这里为了便于处理,采用一个qizi二维数组用来存储棋盘上棋子。
▍图11-3 黑白两色棋子
2. 翻转对方的棋子
需要从自己落子(x1,y1)为中心的横、竖、斜八个方向上判断是否需要翻转对方的棋子,程序中由鼠标的"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)。
最后从左,左上,上,右上,右,右下,下,左下八个方向上调用过程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;
}
else {
qizi[x][y] = BLACK;
}
}
3. 显示执棋方可落子位置
Can_go(x1,y1)从左,左上,上,右上,右,右下,下,左下八个方向上调用函数CheckDirect(x1, y1, dx, dy)判断某方向上是否形成夹击之势,如果形成且中间无空子则返回True,表示(x1,y1)可以落子。(x1,y1)处可以落子则用图片 i 显示。
4. 判断胜负功能
qizi[][]二维数组保存棋盘上的棋子信息,其中元素保存1,表示此处为黑子;元素保存2,表示此处为白子;元素保存0,表示此处为无棋子。通过对qizi数组中各方棋子数统计,在棋盘无处可下时,根据各方棋子数判断出输赢。
02
关键技术
1.Canvas对象支持的JavaScript的鼠标事件
// mouse event
canvas.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){
}
2. 获取鼠标在Canvas对象上坐标
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
黑白棋游戏设计的步骤
1. 游戏页面
<html>
<head>
<title>黑白棋</title>
<meta http-equiv=content-type content="text/html; charset=utf-8">
<meta name="Generator" content="EditPlus">
<meta name="Author" content="">
<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.png" style="display:none;">
<img id="qi_pan1" src="img/qi_pan1.jpg" style="display:none;">
<img id="Info2" src="img/Info2.png" style="display:none;">
<div id="message_txt" style="text-align:center;border:1px solid red; width:720px;height:20px;font-size:20px;"></div>
<input type="button" value="走棋提示" onclick="DoHelp()">
<script type="text/javascript" src="Main.js"></script>
</body>
</html>
2. 设计脚本(Main.js)
1)常量定义
游戏中常量定义,其中BLACK黑棋为1,WHITE白棋为1,无棋为0。
//常量
var BLACK = 1;
var WHITE = 2;
var KONG = 0;
var w=80;
var h=80;
以下获取Canvas对象,及用到的棋子和棋盘图片、提示图片。
var qizi =new Array();//构造一个qizi[][]二维数组用来存储棋子
var curQizi = BLACK;// 当前走棋方
var mycanvas=document.getElementById('myCanvas');
var context = mycanvas.getContext('2d');
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(){
initLevel();// 棋盘上初始4个棋子
showMoveInfo();//当前走棋方信息
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] = WHITE;
qizi[3][4] = BLACK;
qizi[4][3] = BLACK;
DrawMap(); //画棋盘和所有棋子
message_txt.innerHTML = "该黑棋走子";
}
//画棋盘和所有棋子
function DrawMap( )
{
context.clearRect ( 0 , 0 ,720 , 720);
context.drawImage(qipan,0,0,qipan.width,qipan.height);
for(i=0;i<qizi.length;i++)//行号
{
for(j=0;j<qizi[i].length;j++)//列号
{
var pic;
switch (qizi[i][j])
{
case KONG: //0
break;
case BLACK: //1
pic = blackstone;
context.drawImage(pic, w * j, h * i, pic.width, pic.height);
break;
case WHITE: //2
pic = whitestone;
context.drawImage(pic, w * j, h * i, pic.width, pic.height);
break;
}
}
}
}
showMoveInfo()显示轮到那方走棋。
function showMoveInfo(){
if (curQizi== BLACK)// 当前走棋方是黑棋
message_txt.innerHTML="该黑棋走子";
else
message_txt.innerHTML="该白棋走子";
}
init()函数同时对canvas添加鼠标单击事件的侦听。如果canvas被单击则执行doMouseDown函数完成走棋功能。
3)走棋过程
如果是棋盘被单击,则此位置像素信息(event.pageX,event.pageY)可以转换成棋盘坐标 (x1,y1),然后判断当前位置(x1, y1)是否可以放棋子(符合夹角之势),如果可以则此位置显示自己棋子图形,调用FanALLQi(i, j)从左,左上,上,右上等八个方向翻转对方的棋。最后判断对方是否有棋可走,如果对方可以走棋则交换走棋方。如果对方不可以走棋,则自己可以继续走棋,直到双方都不能走棋,显示输赢信息。
function doMouseDown(event) {
var x = event.pageX;
var y = event.pageY;
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, y1;
x1 = Math.round((thisQi.y - 40) / 80);
y1 = Math.round((thisQi.x - 40) / 80); //Math.round()四舍五入
if (Can_go(x1, y1)) {// 判断当前位置是否可以放棋子
//trace("can");
qizi[x1][y1] = curQizi;
FanALLQi(x1, y1);//从左,左上,上,右上,右,右下,下,左下方向翻转对方的棋
DrawMap();
//判断对方是否有棋可走,如有交换走棋方
if (curQizi==WHITE &&checkNext(BLACK) ||curQizi==BLACK &&checkNext(WHITE)) {
if (curQizi==WHITE) {
curQizi=BLACK;
message_txt.innerHTML = "该黑棋走子";
} else {
curQizi=WHITE;
message_txt.innerHTML = "该白棋走子";
}
} else if (checkNext(curQizi)) {
//判断自己是否有棋可走,如有,给出提示
message_txt.innerHTML = "对方无棋可走,请继续";
} else {//双方都无棋可走,游戏结束,显示输赢信息
isLoseWin();
}//统计双方的棋子数量,显示输赢信息。
}
else {
message_txt.innerHTML = "不能落子!";
}
}
4)可否落子判断
Can_go(x1,y1)从左,左上,上,右上,右,右下,下,左下八个方向判断(x1,y1)处可否落子。
function Can_go( x1, y1){
//从左,左上,上,右上,右,右下,下,左下八个方向判断
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;
}
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, y1, dx, dy){
var x,y;
var flag= false;
x = x1 + dx;
y = y1 + 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;
curQizi=i;
if ( Can_Num()>0) {
curQizi=old;
return true;
}
else {
curQizi=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) 从左,左上,上,右上,右,右下,下,左下八个方向翻转对方的棋子。
function FanALLQi(x1, y1) {
//从左,左上,上,右上,右,右下,下,左下八个方向翻转
if (CheckDirect(x1, y1, -1, 0) == true) {
DirectReverse(x1, y1, -1, 0);
}
if (CheckDirect(x1, y1, -1, -1) == true) {
DirectReverse(x1, y1, -1, -1);
}
if (CheckDirect(x1, y1, 0, -1) == true) {
DirectReverse(x1, y1, 0, -1);
}
if (CheckDirect(x1, y1, 1, -1) == true) {
DirectReverse(x1, y1, 1, -1);
}
if (CheckDirect(x1, y1, 1, 0) == true) {
DirectReverse(x1, y1, 1, 0);
}
if (CheckDirect(x1, y1, 1, 1) == true) {
DirectReverse(x1, y1, 1, 1);
}
if (CheckDirect(x1, y1, 0, 1) == true) {
DirectReverse(x1, y1, 0, 1);
}
if (CheckDirect(x1, y1, -1, 1) == true) {
DirectReverse(x1, y1, -1, 1);
}
}
DirectReverse()针对已形成夹击之势某方向上的对方棋子进行翻转。
function DirectReverse(x1, y1, dx, dy) {
var x, y;
var flag= false;
x = x1 + dx;
y = y1 + 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 != x1 || y != y1)) {
FanQi(x, y);
}
} while ((x != x1 || 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)是否在棋盘界内,如果在界内则返回真,否则返回假。
//InBoard()判断(x,y)是否在棋盘界内,如果在界内则返回真,否则返回假。
function InBoard(x,y ){
if (x >= 0 && x <= 7 && y >= 0 && y <= 7) {
return true;
} else {
return false;
}
}
6)显示执棋方可落子位置
“走棋提示”按钮单击事件函数是DoHelp(),它显示可以落子的位置提示。Show_Can_Position()用图片i显示可以落子的位置。
function DoHelp() {
showCanPosition();//显示可以落子的位置
}
function showCanPosition() {
//显示可以落子的位置
var i,j;
var n = 0;//可以落子的位置统计
for (i = 0; i <= 7; i++) {
for (j = 0; j <= 7; j++) {
if (qizi[i][j] == 0 && Can_go(i, j)) {
n = n + 1;
pic=info;
context.drawImage(pic,w*j+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) {
message_txt.innerHTML = "游戏结束黑方胜利,黑方:" + String(blacknum) + "白方:" + String(whitenum);
} else {
message_txt.innerHTML = "游戏结束白方胜利, 黑方:" + String(blacknum) + "白方:" + String(whitenum);
}
}
04
源代码下载
” 即可获得完整源代码。
05
参考书籍
《HTML5 网页游戏设计从基础到开发》
ISBN:978-7-302-49591-8
夏敏捷 编著
定价:69.8元
06
精彩文章回顾
检测
码
码