JavaScript版—贪吃蛇小组件
最近在学习JavaScript,利用2周的时间看完了《JavaScript高级编程》,了解了Js是一门面向原型编程的语言,没有像CSharp语言中的class,也没有私有、公有、保护等访问限制的级别。因此,想用 js 去封装一个小模块,还有诸多的不适应的地方。下面介绍一下,使用js如何模块化的编写贪吃蛇。
1 写在前面
看来《JavsScript高级编程》,想做一个小demo练练自己的手,选择了贪吃蛇游戏。由于以前都是用c#写的,将贪吃蛇写到一个类里面,然后一个一个小方法的拆分,只向外提供需要提供的方法。这样就可以将贪吃蛇作为一个模块,任何地方都可以复用的。然而,用js进行编写的时候,由于不能很好的利用js语言的特性进行模块化编程,所以第一版的实现完全采用面向过程的方式,将函数中所需要的变量全部声明为全局变量。虽然这样也能够实现功能,但是做不到复用,而且定义非常多的最顶层变量,污染了全局变量。写完之后,总想将自己写的重新封装一次,达到只向外提供必须要提供的变量、或者功能函数接口。查了一些许多资料,对于js的封装可以采用闭包的方式来进行实现。通过在函数内部声明局部变量和闭包函数来当做类型的私有变量和函数,然后通过this给对象向外提供需要开发的接口。
2 贪吃蛇组件的使用
2.1 初级示例
示例代码1如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>贪吃蛇组件</title>
</head>
<body>
<canvas width="600" height="600" id="gameScense"></canvas>
</body>
<script src="SnakeGame.js"></script>
<script>
var snakeGame = new SnakeGame("gameScense",{
});
snakeGame.startGame();
</script>
</html>
首先引入SnakeGame.js组件,然后通过实例化 SnakeGame对象,并向SnakeGame构造函数传入两个参数。第一参数是canvas的id,第二个参数游戏配置的对象,如果为空的话,那么采用默认的配置。最后,调用对象的startGame()方法,即可实现贪吃蛇的逻辑。默认的方向控制键为上下左右按键、暂停为空格,效果如下:

我们可以通过更改实例化时传入的配置对象来实现对游戏的更多控制。
示例代码2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>贪吃蛇组件</title>
</head>
<body>
<canvas width="600" height="600" id="gameScense"></canvas>
</body>
<script src="SnakeGame.js"></script>
<script>
var snakeGame = new SnakeGame("gameScense",{
snakeColor:"red",
foodColor:"green",
scenseColor:"blue",
directionKey:[68,83,65,87],
});
snakeGame.startGame();
</script>
</html>
通过参数的名字可以知道,配置蛇、食物、游戏背景的颜色,以及控制游戏的方向键。配置方向顺序为【左,下,右,上】。效果如下:

当然还有更加多的配置。还能够定义分数改变的回调的函数,以及游戏结束时的回调函数等。下面介绍一下配置参数,以及SnakeGame对象共有的方法。
2.2 公有方法
- startGame() : 开始游戏。在该方法内,会初始化各种设置。如,重置分数,蛇身,速度等。
- changeGameStatus():改变游戏状态,即暂停和开始,SnakeGame对象里面有一个私有变量,作为游戏的状态变量。
2.3 配置游戏参数的对象gameConfigObj属性、
gameConfigObj 对象一共该有10个属性,3个回调函数
属性
- size : 蛇块和食物的大小,默认20
- rowCount : 行,默认30行
- colCount : 列,默认30列
- snakeColor : 蛇身颜色,默认green
- foodColor : 食物颜色,默认yellow
- scenseColor : 游戏场景背景色, 默black
- directionKey : 方向键, 默认[39, 40, 37, 38] 上下左右
- pasueKey : 暂停键, 默认32,空格键
- levelCount : 速度等级控制,默认10.
- curSpeed : 初始速度,默认200毫秒
回调函数
- onCountChange : 事件,每一个食物,分数改变,并调用该方法,带有一个参数(count)
- onGamePause : 事件,游戏状态改变时,调用该方法,带有一个参数 1,代表暂停,0 ,代表游戏在进行。
- onGameOver : 事件,游戏结束时,调用该方法。带有一个参数(count),游戏结束时的分数。
2.4使用进阶
通过上面的属性我们可以设计一个交互性更加强的程序。代码如下。
示例3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>贪吃蛇组件</title>
<style type="text/css">
*{
margin:0px;
padding:0px;
}
#gamebd{
width:850px;
margin:50px auto;
}
#gameScense{
background-color:green;
float:left;
}
#gameSet{
margin-left:10px;
float:left;
}
.gameBoxStyle{
margin-bottom:7px;
padding:5px 10px;
}
.gameBoxStyle h3{
margin-bottom:7px;
}
.gameBoxStyle p{
line-height: 1.7em;
}
.gameBoxStyle input{
margin-top:7px;
background-color: white;
border:1px gray solid;
padding:3px 9px;
margin-right:9px;
}
.gameBoxStyle input[type=text]{
width:90px;
}
.gameBoxStyle input:hover{
background-color: #e2fff2;
}
.gameBoxStyle #txtValue{
color:red;
}
</style>
</head>
<body>
<div id="gamebd">
<canvas id="gameScense" width="600" height="600">
</canvas>
<div id="gameSet">
<div id="gameControl" class="gameBoxStyle">
<h3>游戏控制</h3>
<p>方向键:上,下,左,右</p>
<p>开始/暂停:空格</p>
</div>
<div id="gameStatus" class="gameBoxStyle">
<h3>游戏状态</h3>
<p>用户名:<input type="text" placeholder="输入用户名:" id="txtUserName" value="游客123"/> </p>
<p>当前用户1得分:<span id="txtValue">0</span></p>
<input type="button" value="开始游戏" id="btnStart"/>
<input type="button" value="暂停" id="btnPause"/>
</div>
<div id="game" class="gameBoxStyle">
<h3>游戏记录</h3>
<a href="#">查看历史记录</a>
</div>
</div>
</div>
<script src="js/SnakeGame.js"></script>
</body>
<script src="SnakeGame.js"></script>
<script>
var btnStart=document.getElementById("btnStart");
var btnPasue=document.getElementById("btnPause");
var gameSnake = new SnakeGame("gameScense",{
snakeColor:"red",
onCountChange:function(count){
var txtScore=document.getElementById("txtValue");
txtScore.innerText=count.toString( );
txtScore=null;
},
onGamePause:function(status){
if(status){
btnPasue.value = "开始";
}else {
btnPasue.value = "暂停"
}
},
onGameOver:function (status) {
alert("游戏结束");
}
});
btnStart.onclick=function(event){
if(checkUserName()){
gameSnake.startGame();
btnStart.blur();
}
}
btnPasue.onclick=function(event) {
gameSnake.changeGameStatus();
btnStart.blur();
}
function checkUserName(){
var txtUserName = document.getElementById("txtUserName");
if(txtUserName.value.length==0){
alert("用户名不能为空");
return false;
}else {
return true;
}
}
</script>
</html>
上面的代码通过设置OnChangeCount、onGamePause、onGameOver,三个回调函数,实现界面与组件的交互。效果如下:

在《JavaScript高级编程》这本书中说道一个模块模式,但是这种模式是单例模式,也就是闭包最后返回一个字面量的对象。但是我需要在一个页面中能够同时开启两个贪吃蛇的窗口,两个游戏通过设置配置不同的方向键和按钮操作,实现两个人同时一起玩。所以,在实现SnakeGame组件时,没有采用道格拉斯所说的模块模式。下面演示一下,如何在一个页面中,让两个人同时一起玩游戏。代码如下:
示例4
首先建立一个html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Jaume's贪吃蛇</title>
<link rel="stylesheet" href="css/gameStyle.css">
</head>
<body>
<div id="gamebd">
<canvas id="gameScense" width="600" height="600">
</canvas>
<canvas id="gameScense1" width="600" height="600" style="background-color: black">
</canvas>
<div id="gameSet">
<div id="gameControl" class="gameBoxStyle">
<h3>游戏控制</h3>
<p>方向键:上,下,左,右</p>
<p>开始/暂停:空格</p>
</div>
<div id="gameStatus" class="gameBoxStyle">
<h3>游戏状态</h3>
<p>当前用户1得分:<span id="txtValue">0</span></p>
<p>当前用户2得分:<span id="txtValue1">0</span></p>
<input type="button" value="开始游戏" id="btnStart"/>
</div>
<div id="game" class="gameBoxStyle">
<h3>游戏记录</h3>
<a href="#">查看历史记录</a>
</div>
</div>
</div>
<script src="js/SnakeGame.js"></script>
<script src="js/UIScript.js"></script>
</body>
</html>
样式文件如下:
*{
margin:0px;
padding:0px;
}
#gamebd{
/*width:850px;*/
/*margin:50px auto;*/
width:100%;
}
#gameScense{
background-color:green;
float:left;
}
#gameSet{
margin-left:10px;
float:left;
}
.gameBoxStyle{
margin-bottom:7px;
padding:5px 10px;
}
.gameBoxStyle h3{
margin-bottom:7px;
}
.gameBoxStyle p{
line-height: 1.7em;
}
.gameBoxStyle input{
margin-top:7px;
background-color: white;
border:1px gray solid;
padding:3px 9px;
margin-right:9px;
}
.gameBoxStyle input[type=text]{
width:90px;
}
.gameBoxStyle input:hover{
background-color: #e2fff2;
}
.gameBoxStyle #txtValue{
color:red;
}
在html中拖入了两个文件,一个是贪吃蛇组件,另一个是UIScript.js,其中的代码如下:
/**
* Created by tjm on 8/16/2017.
*/
var btnStart=document.getElementById("btnStart");
var gameSnake = new SnakeGame("gameScense",{
snakeColor:"red",
directionKey:[68,83,65,87],
pauseKey:81,
onCountChange:function(count){
var txtScore=document.getElementById("txtValue");
txtScore.innerText=count.toString( );
txtScore=null;
},
onGameOver:function (status) {
alert("游戏结束");
}
});
var gameSnake1 = new SnakeGame("gameScense1",{
snakeColor:"green",
size:20,
onCountChange:function(count){
var txtScore=document.getElementById("txtValue1");
txtScore.innerText=count.toString();
txtScore=null;
},
onGameOver:function (status) {
alert("游戏结束");
}
});
btnStart.onclick=function(event){
gameSnake.startGame();
gameSnake1.startGame();
btnStart.blur();
}
实例化两个SnakeGame对象,一个对象使用默认的上下左右键和空格键作为方向键和暂停键,而另一个使用了,W、A、S、D 以及 Q 作为方向键和暂停键。效果如下:

嗯哼,没错,完美实现了。使用SnakeGame这个组件,创建贪吃蛇游戏就是如此的简单。下面简单介绍一下,组件的实现方式。
3贪吃蛇组件实现方式
在上一节中就提到过,没有采用过道哥拉斯的设计模式,下面给出贪吃蛇设计结构。具体的源代码,可以在后面的链接中进行下载。代码如下:
/**
* Created by tjm on 8/18/2017.
*/
var SnakeGame = function () {
/*蛇块和食物组件类*/
function SnakeBlock(row,col){
this.row=row;
this.col=col;
}
SnakeBlock.prototype.draw = function(graphic,color,size){
graphic.fillStyle=color;
graphic.fillRect(size*this.col,size*this.row,size-2,size-2);
}
SnakeBlock.prototype.clearDraw = function(graphic,color,size){
graphic.fillStyle=color;
graphic.fillRect(size*this.col,size*this.row,size,size);
}
SnakeBlock.prototype.equal = function(snakeBlock){
if(snakeBlock.row==this.row && snakeBlock.col==this.col){
return true;
}else{
return false;
}
}
/*贪吃蛇组件类*/
function SnakeGame(gameScenseId, gameConfigObj) {
// 私有属性
var gameScense = document.getElementById(gameScenseId);
var graphic = gameScense.getContext("2d");
var count = 0;
var snake;
var curFood;
var runId;
var isMoved = false;//方向改变后,如果没有移动则方向键暂时失效。
var gameStatus = false;
var curDirection = 1;
var size = gameConfigObj.size || 20;
var rowCount = gameConfigObj.rowCount || 30;
var colCount = gameConfigObj.colCount || 30;
var snakeColor = gameConfigObj.snakeColor || "green";
var foodColor = gameConfigObj.foodColor || "yellow";
var scenseColor = gameConfigObj.scenseColor || "black";
var directionKey = gameConfigObj.directionKey || [39, 40, 37, 38];
var pauseKey = gameConfigObj.pauseKey || 32;
var levelCount = gameConfigObj.levelCount || 10;
var curSpeed = gameConfigObj.curSpeed || 200;
//公开事件
var onCountChange = gameConfigObj.onCountChange || null; //带有一个参数
var onGamePause = gameConfigObj.onGamePause || null; //带有一个参数
var onGameOver = gameConfigObj.onGameOver || null;
//判断
if(gameScense.width != size*rowCount || gameScense.height != size*colCount){
throw "场景大小不等于行列大小*蛇块大小";
}
//特权方法
this.startGame = startGame;
this.changeGameStatus = changeGameStatus;
//注册 dom 键盘事件
var preFunc = document.onkeydown;
document.onkeydown = function (e) {
var key = (e || event).keyCode;
handleKeyInput(key);
if (typeof preFunc == "function") {
preFunc(e);
}
}
//私有方法
/*初始化蛇身*/
function initSnake(){
···
}
/*绘制场景背景色*/
function initScense(){
···
}
/*产生食物*/
function genFood(){
···
}
/*吃食物*/
function eatFood(snakeHead){
···
}
/*判断游戏是否结束*/
function gameOver(){
···
}
/*蛇移动*/
function snakeMove(){
···
}
function changeSpeed(){
···
}
function handleKeyInput(key){
···
}
function initGame(){
···
}
function triggerEvent(callback,argument){
···
}
function runGame(){
···
}
function pauseGame() {
···
}
function changeGameStatus(){
···
}
function startGame(){
···
}
}
return SnakeGame; //最后返回一个组件构造函数
}();
上面有一个很重要的地方,就是键盘注册的代码,单独列出来分析一下。
var preFunc = document.onkeydown;
document.onkeydown = function (e) {
var key = (e || event).keyCode;
handleKeyInput(key);
if (typeof preFunc == "function") {
preFunc(e);
}
}
该段代码的逻辑是,首先判断在 document 上是否注册了onkeydown 事件,如果注册了该事件,则保存所引用的事件处理程序,然后重置onkeydown事件程序,然后在新的事件处理程序中,调用先前的事件处理程序,这样就实现了事件触发后,调用所有监听该事件处理程序,而不是直接覆盖。
另外关于贪吃蛇的设计逻辑,可以参看我另外一篇文章,个人觉得讲的非常详细了,文章:基于控制台实现贪吃蛇游戏
3 小结
通过这次贪吃蛇组件的设计,对 js 的模块化设计稍微了解了一下,但是,我也不知道上文所实现的贪吃蛇模块有哪些缺陷,希望有大神看到这篇文章,能给一些指导。当然了,该组件还可以进行进一步的扩展,比如将游戏的方块,替换成图片。有兴趣的可以从下面的链接进行下载,更改后别忘了分享哦。
源码下载链接:https://github.com/StartAction/SnakeGame
JavaScript版—贪吃蛇小组件的更多相关文章
- 用GUI实现java版贪吃蛇小游戏
项目结构 新建一个JFrame窗口,作为程序入口 public class GameStart{ public static void main(String[] args) { JFrame jFr ...
- c语言版贪吃蛇小游戏
编译环境:windows 7 64位 编译工具:codeblocks 13.12 备注:未使用graphics.h 声明:个人原创,未经允许,禁止转载!!! 数据结构:双向链表 1.程序未使用grap ...
- TOJ 3973 Maze Again && TOJ 3128 简单版贪吃蛇
TOJ3973传送门:http://acm.tzc.edu.cn/acmhome/problemdetail.do?&method=showdetail&id=3973 时间限制(普通 ...
- OC版贪吃蛇
昨天写了一个js版贪吃蛇,今天突然想写一个OC版的,来对比一下两种语言的区别 oc版功能,适配所有尺寸iphone,可暂停,可设置地图和蛇的比例,可加速 对比一下会发现js版的相对OC版的会简单一些, ...
- C++ 简单的控制台贪吃蛇小游戏
由于比较懒,所以不怎么写,觉得这样不应该.我应该对自己学的做出整理,不管是高端低端,写出来是自己的. // 贪吃蛇.cpp : 定义控制台应用程序的入口点. // #include "std ...
- 贪吃蛇小游戏-----C语言实现
1.分析 众所周知,贪吃蛇游戏是一款经典的益智游戏,有PC和手机等多平台版本,既简单又耐玩.该游戏通过控制蛇头方向吃食物,从而使得蛇变得越来越长,蛇不能撞墙,也不能装到自己,否则游戏结束.玩过贪吃蛇的 ...
- Winfrom 极简版贪吃蛇源码
该源码是我在百度知识库借助前辈的的经验,加上自己的一点小改动写的一个非常简陋的贪吃蛇小程序.如果你们有更好的改动方案,欢迎评论. 进入主题吧! 1.创建一个桌面应运程序,拖一个定时器控件.这样,程序界 ...
- JS高级---案例:贪吃蛇小游戏
案例:贪吃蛇小游戏 可以玩的小游戏,略复杂,过了2遍,先pass吧 先创建构造函数,再给原型添加方法.分别创建食物,小蛇和游戏对象. 食物,小蛇的横纵坐标,设置最大最小值,运动起来的函数,按上下左右键 ...
- 如何用python制作贪吃蛇以及AI版贪吃蛇
用python制作普通贪吃蛇 哈喽,大家不知道是上午好还是中午好还是下午好还是晚上好! 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很 ...
随机推荐
- Bootstrap警告框
前面的话 在网站中,网页总是需要和用户一起做沟通与交流.特别是当用户操作上下文为用户提供一些有效的警示框,比如说告诉用户操作成功.操作错误.提示或者警告等.在Bootstrap框架有一个独立的组件,实 ...
- 一个非常简单的例子告诉你attachEvent和addEventListener的区别
<input id="test" type="button" value="测试"> <script> var te ...
- CSS学习(页外引用还不懂)
CSS的语法结构为 选择符 {属性:值:} Selector {Property : Value:} 选择符:通配 *{....} , 元素 body{....} .h1{....}.p ...
- MySQL二进制日志备份和恢复详解
原文链接:http://www.showerlee.com/archives/681 ****经实践,该教程ok,特在此分享**** 基本概念 定义: 二进制日志包含了所有更新了数据或者已经潜在更新了 ...
- TCP常见的定时器三次握手与四次挥手
1.TCP常见的定时器 在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么这个时候,TCP协议是使用定时器进行实现的.在TCP中,会有七种定时器: 建立连接定时器(connecti ...
- docker~save与load的使用
回到目录 对于没有私有仓库来说,将本地镜像放到其它服务器上执行时,我们可以使用save和load方法,前者用来把镜像保存一个tar文件,后台从一个tar文件恢复成一个镜像,这个功能对于我们开发者来说还 ...
- WIN10下设置惠普HP1050等打印机打印颜色,只打黑白或彩色
今天同事问了一个问题,如何在WIN10下,设置惠普打印机只打印黑白, 上网搜了下,没有找到任何信息,只有在WIN8前系统设置的内容,经过几番折腾,得出此文. WIN10下设置惠普HP1050等打印机打 ...
- Android - Fragment(二)加载Fragment
Fragment加载方法 加载方法有两种,在xml文件中注册,或者是在Java代码中加载. xml中注册 例如在fragment_demo.xml中定义 <?xml version=" ...
- 玩玩微信公众号Java版之三:access_token及存储access_token
微信官方参考文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183 基本说明: access_token是 ...
- Jenkins 的svn插件下载的代码不是最新代码的问题
项目组使用Jenkins做自动化的每日编译和单元测试.经常发现,当提交完代码后,在Jenkins的每日编译代码还是旧代码,刚提交的代码并没有check out出来. 后来发现Jenkins服务器的时间 ...