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 相关文章推荐
关于ExtJS4.1:快捷键支持的问题
Apr 24 Javascript
JavaScript中获取鼠标位置相关属性总结
Oct 11 Javascript
JavaScript给url网址进行encode编码的方法
Mar 18 Javascript
vue.js+boostrap项目实践(案例详解)
Sep 21 Javascript
js中string和number类型互转换技巧(分享)
Nov 28 Javascript
Bootstrap基本组件学习笔记之分页(12)
Dec 08 Javascript
vue-cli开发环境实现跨域请求的方法
Apr 07 Javascript
前后端如何实现登录token拦截校验详解
Sep 03 Javascript
vue、react等单页面项目部署到服务器的方法及vue和react的区别
Sep 29 Javascript
js实现列表按字母排序
Aug 11 Javascript
Kettle中使用JavaScrip调用jar包对文件内容进行MD5加密的操作方法
Sep 04 Javascript
jQuery实现鼠标拖拽登录框移动效果
Sep 13 jQuery
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
php下过滤html代码的函数 提高程序安全性
2010/03/02 PHP
有关于PHP中常见数据类型的汇总分享
2014/01/06 PHP
ThinkPHP实现二级循环读取的方法
2014/11/03 PHP
PHP5.6读写excel表格文件操作示例
2019/02/26 PHP
深入分析原生JavaScript事件
2014/12/29 Javascript
总结JavaScript中布尔操作符||与&amp;&amp;的使用技巧
2015/11/17 Javascript
jQuery+CSS实现滑动的标签分栏切换效果
2015/12/17 Javascript
用canvas 实现个图片三角化(LOW POLY)效果
2016/02/18 Javascript
基于BootStrap Metronic开发框架经验小结【七】数据的导入、导出及附件的查看处理
2016/05/12 Javascript
Angularjs 创建可复用组件实例代码
2016/10/09 Javascript
JavaScript重定向URL参数的两种方法小结
2016/10/19 Javascript
JS正则表达式修饰符中multiline(/m)用法分析
2016/12/27 Javascript
兼容浏览器的js事件绑定函数(详解)
2017/05/09 Javascript
Bootstrap Table使用整理(五)之分页组合查询
2017/06/09 Javascript
关于echarts在节点显示动态数据及添加提示文本所遇到的问题
2018/04/20 Javascript
总结JavaScript在IE9之前版本中内存泄露问题
2018/04/28 Javascript
详解node Async/Await 更好的异步编程解决方案
2018/05/10 Javascript
vue-cli2与vue-cli3在一台电脑共存的实现方法
2019/09/25 Javascript
pycharm远程调试openstack代码
2017/11/21 Python
使用Python脚本zabbix自定义key监控oracle连接状态
2019/08/28 Python
Flask项目中实现短信验证码和邮箱验证码功能
2019/12/05 Python
Python实现初始化不同的变量类型为空值
2020/06/02 Python
在pycharm中文件取消用 pytest模式打开的操作
2020/09/01 Python
HTML5获取当前地理位置并在百度地图上展示的实例
2020/07/10 HTML / CSS
Discard Protocol抛弃协议的作用是什么
2015/10/10 面试题
工程预算与管理应届生求职信
2013/10/06 职场文书
小区门卫工作职责
2013/12/14 职场文书
如何编写优秀的食品项目创业计划书
2014/01/23 职场文书
简历的自我评价
2014/02/03 职场文书
创建学习型党组织实施方案
2014/03/29 职场文书
党的群众路线教育实践活动总结报告
2014/04/28 职场文书
多媒体编辑专业毕业生求职信
2014/06/13 职场文书
支部书记四风对照材料
2014/08/28 职场文书
城市规划应届生推荐信
2014/09/08 职场文书
行政介绍信范文
2015/05/04 职场文书
分享提高 Python 代码的可读性的技巧
2022/03/03 Python