JavaScript贪吃蛇小组件实例代码


Posted in Javascript onAugust 20, 2017

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()方法,即可实现贪吃蛇的逻辑。默认的方向控制键为上下左右按键、暂停为空格,效果如下:

JavaScript贪吃蛇小组件实例代码

我们可以通过更改实例化时传入的配置对象来实现对游戏的更多控制。

 示例代码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>

通过参数的名字可以知道,配置蛇、食物、游戏背景的颜色,以及控制游戏的方向键。配置方向顺序为【左,下,右,上】。效果如下:

JavaScript贪吃蛇小组件实例代码

当然还有更加多的配置。还能够定义分数改变的回调的函数,以及游戏结束时的回调函数等。下面介绍一下配置参数,以及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 : 事件,游戏结束时,调用该方法。

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="#" rel="external nofollow" rel="external nofollow" >查看历史记录</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贪吃蛇小组件实例代码

在《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" rel="external nofollow" >
</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="#" rel="external nofollow" rel="external nofollow" >查看历史记录</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 作为方向键和暂停键。效果如下:

JavaScript贪吃蛇小组件实例代码

嗯哼,没错,完美实现了。使用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贪吃蛇小组件实例代码,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的!

Javascript 相关文章推荐
使用jQuery.fn自定义jQuery翻页插件
Jan 20 Javascript
javascript-简单的日历实现及Date对象语法介绍(附图)
May 30 Javascript
JQuery 实现在同一页面锚点链接之间的平滑滚动
Oct 29 Javascript
jQuery插件kinMaxShow扩展效果用法实例
May 04 Javascript
使用堆实现Top K算法(JS实现)
Dec 25 Javascript
js如何改变文章的字体大小
Jan 08 Javascript
jQuery中的on与bind绑定事件区别实例详解
Feb 28 Javascript
three.js实现3D模型展示的示例代码
Dec 31 Javascript
vue实现登录后页面跳转到之前页面
Jan 07 Javascript
详解微信小程序图片地扯转base64解决方案
Aug 18 Javascript
Vue基础配置讲解
Nov 29 Javascript
vue自定义插件封装,实现简易的elementUi的Message和MessageBox的示例
Nov 20 Vue.js
React Native 环境搭建的教程
Aug 19 #Javascript
ionic App问题总结系列之ionic点击系统返回键退出App
Aug 19 #Javascript
浅谈关于.vue文件中style的scoped属性
Aug 19 #Javascript
如何理解Vue的作用域插槽的实现原理
Aug 19 #Javascript
Vue Transition实现类原生组件跳转过渡动画的示例
Aug 19 #Javascript
JavaScript文件的同步和异步加载的实现代码
Aug 19 #Javascript
深入理解ES6学习笔记之块级作用域绑定
Aug 19 #Javascript
You might like
动画 《Pokemon Sword·Shield》系列WEB动画《薄明之翼》第2话声优阵容公开!
2020/03/06 日漫
VML绘图板②脚本--VMLgraph.js、XMLtool.js
2006/10/09 PHP
ecshop 批量上传(加入自定义属性)
2012/03/20 PHP
PHP7正式版测试,性能惊艳!
2015/12/08 PHP
thinkphp3.2.3 分页代码分享
2016/07/28 PHP
PHP7.1新功能之Nullable Type用法分析
2016/09/26 PHP
PHP自定义函数获取汉字首字母的方法
2016/12/01 PHP
asp函数split()对应php函数explode()
2019/02/27 PHP
PHP工厂模式的日常使用
2019/03/20 PHP
Mootools 1.2教程 事件处理
2009/09/15 Javascript
js 禁止选择功能实现代码(兼容IE/Firefox)
2010/04/23 Javascript
JavaScript中instanceof运算符的用法总结
2013/11/19 Javascript
javascript/jquery获取地址栏url参数的方法
2014/03/05 Javascript
JavaScript+html5 canvas制作色彩斑斓的正方形效果
2016/01/27 Javascript
JavaScript中闭包之浅析解读(必看篇)
2016/08/25 Javascript
使用vue中的v-for遍历二维数组的方法
2018/03/07 Javascript
React组件重构之嵌套+继承及高阶组件详解
2018/07/19 Javascript
Javascript查看大图功能代码实现
2020/05/07 Javascript
bootstrap实现tab选项卡切换
2020/08/09 Javascript
[53:03]Optic vs TNC 2018国际邀请赛小组赛BO2 第一场 8.17
2018/08/18 DOTA
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
仅用500行Python代码实现一个英文解析器的教程
2015/04/02 Python
matplotlib给子图添加图例的方法
2018/08/03 Python
使用OpCode绕过Python沙箱的方法详解
2019/09/03 Python
基于Python组装jmx并调用JMeter实现压力测试
2020/11/03 Python
解决pycharm 格式报错tabs和space不一致问题
2021/02/26 Python
利用CSS3的定位页面元素
2009/08/29 HTML / CSS
CSS3只让背景图片旋转180度的实现示例
2021/03/09 HTML / CSS
创先争优制度
2014/01/21 职场文书
大学生的创业计划书就该这么写
2014/01/30 职场文书
单位委托书
2014/10/15 职场文书
2016年小学中秋节活动总结
2016/04/05 职场文书
决心书格式及范文
2019/06/24 职场文书
导游词之澳门玫瑰圣母堂
2019/12/03 职场文书
Django cookie和session的应用场景及如何使用
2021/04/29 Python
高性能跳频抗干扰宽带自组网电台
2022/02/18 无线电