共计 4504 个字符,预计需要花费 12 分钟才能阅读完成。
html5也出来也很久了,也是 html 标记语言的第五次重大修改。html5带来了很多原来没有的新特性,可以通过 https://www.w3.org/TR/html5/ 查看带的特性,目前大部分浏览器都支持 html5(IE9 以上)。这篇文章我们将使用canvas 来实现一个五子棋游戏。canvas
提供了绘制 2D 和 3D(WebGL)的接口, 游戏的图形显示我使用 canvas
来实现,逻辑功能还是使用 javaScript
来控制。
游戏分析
五子棋的棋盘和围棋一样只是格数多少的问题,本例中我们使用围棋 19 路这个格局 (其实就是一个 19*19 的矩阵)。通过格子的大小来绘制线条,棋子的直径就是格子的大小,使用棋手对象来代表棋子(本例我们新建两个棋手对象,通过更新下棋状态来通知棋手下棋)
,使用二维数组( 矩阵
) 来记录棋手下棋的位置。最终我们还要判断胜负关系, 本例的判断方法是 判断当前棋子的上、下、左、右、左上、右下、右上、左下
的八个方向中各自对应的组合方向是否能形成 5 子
。
创建棋盘
新建一个 Desk
类, 类的主要功能就是创建画布和渲染棋盘,以及一些开放功能获取配置、获取画布对象。
function Desk(standard, options) {
var defaults = _.merge({
size: 50,
standard: standard
}, options),
width = height = standard * parseInt(defaults.size, 10),
radius = parseInt(defaults.size, 10) / 2,
desk = document.createElement(\'div\'),
/**
*
* @type {HTMLCanvasElement}
*/
canvas = document.createElement(\'canvas\'),
ctx = null;
defaults["width"] = width;
defaults["height"]= height;
defaults["radius"]= radius;
canvas.setAttribute(\'id\', \'deskCanvas\');
canvas.setAttribute(\'width\', width "px");
canvas.setAttribute(\'height\',height "px");
canvas.style.cursor = "pointer";
desk.style.margin = "10px auto";
desk.style.width = width "px";
/**
*
* @param key
* @returns {*|null}
*/
this.getOption = function(key) {return defaults[key] || null;
};
/**
*
* @returns {HTMLCanvasElement}
*/
this.getCanvas = function() {return canvas;};
this.getContext= function(driver) {if (ctx === null) {return canvas.getContext(driver || "2d");
}
return ctx;
};
this.setContext = function(content) {ctx = content;};
desk.appendChild(canvas);
document.body.insertBefore(desk, document.body.firstChild);
}
Desk.prototype = {draw: function() {
/**
*
* @type {CanvasRenderingContext2D|WebGLRenderingContext}
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D
*/
var ctx = this.getContext(),
offset = 0,
size = this.getOption(\'size\'),
radius = this.getRadius(),
width = this.getOption(\'width\'),
height = this.getOption(\'height\'),
standard = this.getOption(\'standard\');
this.setContext(ctx);
// 设置填充颜色
ctx.fillStyle = "#f6f2e9";
// 使用 fillStyle 设置的颜色来填充矩形区域
ctx.fillRect(0, 0, this.getOption("width"), this.getOption("height"));
ctx.strokeStyle = "#999"; // 线条颜色
ctx.lineWidth = 1;
ctx.beginPath();
for (var i = 1; i <= standard; i) {
offset = i * size - size radius;
// 绘制竖线
ctx.moveTo(offset, radius);
ctx.lineTo(offset, height - radius);
// 绘制横线
ctx.moveTo(radius, offset);
ctx.lineTo(width - radius, offset);
}
ctx.stroke();
ctx.closePath();},
getRadius: function() {return this.getOption("radius");
},
getSize: function() {return this.getOption("size");
}
};
我们只需要 new Desk(19).draw()
就可以渲染出一个棋盘。
Note::
坐标点(x, y)的值不能从 0,0 开始, 要从棋子的半径位置开始增加, 写在边界棋子会绘制不全。
棋子绘制
新建一个 Chess
类,实现绘制棋子功能,初始化矩阵,获取坐标, 计算游结束。
function Chess(desk) {var position = [];
this.standard = desk.getOption(\'standard\') - 1;
this.size = desk.getSize();
this.desk = desk;
for (var y = 0; y <= this.standard; y) {position[y] = [];
for (var x = 0; x <= this.standard; x) {position[y][x] = null;
}
}
this.addPosition = function(object) {var x = Math.floor(object.point.x / this.size),
y = Math.floor(object.point.y / this.size);
if (position[y][x] !== null) {return false;}
position[y][x] = object;
return true;
};
this.getPosition = function() {return position;};
this.ctx = this.desk.getContext();
this.radius = this.desk.getRadius();}
Chess.prototype = {
/**
* 绘制棋子
* @param player
* @returns {*}
*/
draw: function(player) {var point = this.roundCirclePoint(player.getXY());
if (typeof point[0] === "object") {return this.noticeReact(point);
}
var object = {player: player, point: point};
if (!this.addPosition(object)) {return null;}
this.ctx.beginPath();
this.ctx.fillStyle = player.color;
this.ctx.arc(point.x, point.y, this.radius, 0, 2 * Math.PI);
this.ctx.fill();
this.ctx.closePath();
if (this.over(object)) {alert("玩家:" player.name "获得胜利");
}
return point;
},
/**
* 绘制提示点
* @param points
*/
noticeReact: function(points) {
this.ctx.strokeStyle = "#f4645f";
for (var i in points) {var m = points[i];
this.ctx.strokeRect(m.x - (this.radius / 2), m.y - (this.radius / 2), this.size - this.radius, this.size - this.radius
);
}
return null;
},
/**
* 转换坐标
* @param point
* @returns {*}
*/
roundCirclePoint: function(point) {var x = Math.floor((point.x - this.radius) / this.size),
y = Math.floor((point.y - this.radius) / this.size),
circle = [];
if (x < 0) x = 0;
if (y < 0) y = 0;
// 查找附近四个圆点中心
for(var i = x; i <= x 1; i) {for(var j = y; j <= y 1; j) {
circle.push({
x: i * 50 this.radius,
y: j * 50 this.radius
});
}
}
for (var index in circle) {var m = circle[index];
var distance = Math.pow(m.x - point.x, 2) Math.pow(m.y - point.y, 2);
// 计算点是否在圆内
if (distance < Math.pow(this.radius, 2)) {return m;}
}
return circle;
},
/**
* 判断游戏结束
* 通过判断当前棋子的八向连续相同的四子,或者正反方向的连续相同的棋子的和大于四
* @param object
* @returns {boolean}
*/
over: function(object) {
var player = object.player,
point = object.point,
x = Math.floor(point.x / this.size),
y = Math.floor(point.y / this.size),
position = this.getPosition(),
hx = vy = sh = sv = _hx = _vy = _sh = _sv = 0;
for (var i = 1; i <= 4; i) {if (x i < this.standard && position[y][x i] && position[y][x i].player === player && (i - hx) === 1) {hx ;}
if (y i < this.standard && position[y i][x] && position[y i][x].player === player && (i - vy) === 1) {vy ;}
if (y i < this.standard && x i < this.standard && position[y i][x i] && position[y i][x i].player === player && (i - sv) === 1) {sv ;}
if (y i < this.standard && x-i> -1 && position[y i][x-i] && position[y i][x-i].player === player && (i - sh) === 1) {sh ;}
}
for (var i = -1; i>= -4; i--) {if (x i> -1 && position[y][x i] && position[y][x i].player === player && (i _hx) === -1) {_hx ;}
if (y i> -1 && position[y i][x] && position[y i][x].player === player && (i _vy) === -1) {_vy ;}
if (y i> -1 && x i > -1 && position[y i][x i] && position[y i][x i].player === player && (i _sv) === -1) {_sv ;}
if (y i> -1 && x-i = 4 || vy _vy >= 4 || sv _sv >= 4 || sh _sh >= 4;
}
};
玩家
下棋肯定少不了玩家,这里我们通过玩家点击画布获取位置再调用棋子类绘制玩家的棋子,同时更新玩家的状态。默认玩家都是等待开始的状态,当玩点击的时候更新为下棋的状态,下完的时候更新其它玩家的状态同时更新自己的状态。
/**
*
* @param name
* @constructor
*/
function Player(name, color, chess) {
this.name = name;
this.color = color;
this.state = \'waitStart\';
var x = y = 0;
this.setXY = function(xv, yv) {x = xv; y = yv;};
this.getXY = function() {return {x: x, y: y};};
this.chess = chess;
}
Player.prototype = {
/**
*
*/
playChess: function() {if (this.state === \'waiting\' || this.state === \'waitStart\' || this.state === \'waitPlay\') {return ;}
var circlePoint = this.chess.draw(this);
if (circlePoint !== null) {Events.trigger("notify", ctrl, this);
} else {this.updateState("waitPlay");
}
},
updateState: function(state) {this.state = state;},
getState: function() {return this.state;}
};
Note:
this.updateState("waitPlay")
当前用户点击的区域无法匹配到正常的圆点,把玩家的状态更新为等待玩的状态。
控制
控制类主要是针对玩家的,获取当前要下棋的用户,通知回调。
function Control() {this.player = [];
};
Control.prototype = {addPlayer: function(player) {this.player.push(player);
},
getPlaying: function() {for (var i in this.player) {var state = this.player[i].getState();
if (state === \'waitPlay\') {return this.player[i];
}
}
var player = this.player[0];
player.updateState("waitPlay");
return player;
},
notify: function(player) {for (var i in this.player) {if (this.player[i] !== player) {this.player[i].updateState("waitPlay");
} else {this.player[i].updateState("waiting");
}
}
},
getPlayer: function() {return this.player;}
};
通过以上的几个类我们就完成了从棋盘到玩家再到下棋的整个流程。
事例
var desk = new Desk(19);
var chess = new Chess(desk);
var playerOne = new Player("Tom", "#FFF", chess);
var playerTwo = new Player("Seven", "#000", chess);
var ctrl = new Control();
ctrl.addPlayer(playerOne);
ctrl.addPlayer(playerTwo);
desk.draw();
Events.on(\'click\', function(e) {var player = ctrl.getPlaying();
player.updateState("playing");
player.setXY(e.offsetX, e.offsetY);
player.playChess();}, desk.getCanvas());
Events.on("notify", function(player) {ctrl.notify(player);
});
总结
在整个五子棋代码中实际是没有难点之处的,主要还是个人思路要清晰。大家也可以给我指出不足的地方。下期我们使用这个和 websocket
做一个在线的五子棋游戏。
源代码
https://github.com/TianLiangZhou/loocode-example/tree/master/gobang
推荐阅读
- https://www.w3.org/TR/html5/
- https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
- https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
- http://www.html5gamedevs.com/
- http://bucephalus.org/text/CanvasHandbook/CanvasHandbook.html
- https://joshondesign.com/p/books/canvasdeepdive/chapter01.html