视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
JavaScript简单贪吃蛇,基本面向对象
2020-11-27 20:31:11 责编:小采
文档
没有写博客的习惯,这篇算心血来潮,算篇近几天编写的小程序纪实.

以编写此程序的方式结束Javascript的本阶段的学习.编写的目的在于熟悉javascript的编程方式,包括代码风格,面向对象的运用等.

回到程序,说说Snake的移动的实现方法.其实很简单,向头部添加Unit,然后删除尾部.其他,参见注释.

程序包括一个html文件:snake.html和一个js文件:snake.js

snake.html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>JavaScript简单贪吃蛇</title>

<script type="text/javascript" src="snake.js"></script>

<script type="text/javascript" >

$s(function(){

$s.SnakeContext.init();

});

</script>

</head>

<body>

<div id="headLocation"></div>

<div id="keyup"></div>

</body>

</html>

snake.js:

/*

* JavaScript简单贪吃蛇.基本面向对象.

* 规则:

* 1.没有墙,左与右连接,上与下连接.

* 2.当蛇头碰撞到自身时死亡.

* 兼容性:

* 完全支持Firefox,Chrome

* 基本支持IE(除了调试部分)

*

* 作者:pcenshao

* 转载请注明来自:

* http://blog.csdn.net/pywepe

* http://pcenshao.taobao.com

*/

(function(){

$s = function(){

if(arguments.length == 1){

if(typeof arguments[0] == "string"){

return document.getElementById(arguments[0]);

}else if(typeof arguments[0] == "function"){

window.onload = arguments[0];

}

}

};

$s.UNIT_WIDTH = 10; // 单元的宽度

$s.UNIT_HEIGHT = 10;

$s.PANEL_WIDTH = 30; // 逻辑宽度

$s.PANEL_HEIGHT = 20; // 逻辑高度

$s.STEP = 250 ; // 每一步的时间

$s.HEAD_COLOR = "red"; // 蛇头颜色

$s.BODY_COLOR = "black"; // 蛇体颜色

/*

* 食物的颜色

*/

$s.COLORS = ["blue","green","#494e8f","#905d1d","#845538","#77ac98","#8552a1"];

/*

* 调试相关

* $s.DEBUG 调试信息显示开关

* $s.KEY_UP_DIR_ID 监视方向键的结点id,若不存在,则不显示

* $s.HEAD_LOCATION_ID 监视蛇头位置的结点id,若不存在,则不显示

*/

$s.DEBUG = false;

$s.KEY_UP_DIR_ID = "keyup";

$s.HEAD_LOCATION_ID = "headLocation";

$s.Dir = { // 代表方向,强制以$s.Dir.UP方法调用,避免参数错误

UP : {},

DOWN : {},

LEFT : {},

RIGHT : {},

NONE : {}

};

$s.State = { // 代表状态

STOP : {},

RUNNGIN : {},

PAUSE : {}

};

$s.Unit = function(){ // 一个单元格,用MVC的眼光看,Unit是模型,UnitView是视图

this.x = 0;

this.y = 0;

this.view = new $s.UnitView();

this.view.unit = this;

this.color = $s.BODY_COLOR;

};

$s.Unit.prototype.repaint = function(){

if(this.view != null){

this.view.repaint(); // 通知重绘

}

};

$s.Snake = function(){

this.units = [];

};

$s.Snake.prototype.init = function(dir,count){

var x = 5;

var y = 5;

for(var i = 0 ; i < count ; i ++){

var u = new $s.Unit();

u.x = x ;

u.y = y ++;

this.units.push(u);

if(i == (count - 1 )){

u.color = $s.HEAD_COLOR;

}

u.repaint();

}

};

$s.Snake.prototype.crash = function(x,y){ // 传入头部的位置,返回true表示碰撞自身

for(var i = this.units.length - 2 ; i >= 0 ; i --){ // 不包括头自身

var u = this.units[i];

if(u.x == x && u.y == y){

return true;

}

}

return false;

};

$s.Snake.prototype.go = function(){

// 判断前方是否有食物

// 是否撞墙

var _x = 0 , _y = 0;

var head = this.units[this.units.length - 1];

_x = head.x;

_y = head.y;

var dir = $s.SnakeContext.dir;

if(this.crash(_x,_y)){ // 判断是否碰撞到自身

$s.SnakeContext.stop();

$s.SnakeContext.ondead(); // 触发dead事件

return;

}

if(dir == $s.Dir.LEFT){

_x --;

}else if(dir == $s.Dir.RIGHT){

_x ++;

}else if(dir == $s.Dir.UP){

_y --;

}else if(dir == $s.Dir.DOWN){

_y ++;

}

// 实现左右连接,上下连接

if(_x >= $s.PANEL_WIDTH){

_x = 0;

}

if(_x < 0){

_x = $s.PANEL_WIDTH - 1;

}

if(_y >= $s.PANEL_HEIGHT){

_y = 0;

}

if(_y < 0){

_y = $s.PANEL_HEIGHT - 1;

}

var h = new $s.Unit(); // 新头

if($s.SnakeContext.hasFood(_x,_y)){ // 下一步碰到食物

this.eat(_x,_y);

head = this.units[this.units.length - 1]; // 因为eat方法可以改变头部,所以重新获取

_x = head.x;

_y = head.y;

if(dir == $s.Dir.LEFT){

_x --;

}else if(dir == $s.Dir.RIGHT){

_x ++;

}else if(dir == $s.Dir.UP){

_y --;

}else if(dir == $s.Dir.DOWN){

_y ++;

}

head.color = $s.HEAD_COLOR;

head.repaint();

var oldHead = this.units[this.units.length - 2];

oldHead.color = $s.BODY_COLOR;

oldHead.repaint();

return;

}

var tail = this.units.shift();

$s.NodePool.releaseNode(tail);

h.x = _x;

h.y = _y;

this.units.push(h);

for(var i = this.units.length - 1; i >= 0; i --){

var u = this.units[i];

if(i == (this.units.length - 1)){ // 头

u.color = $s.HEAD_COLOR;

}else{

u.color = $s.BODY_COLOR;

}

u.repaint();

}

};

$s.Snake.prototype.eat = function(x,y){

var food = $s.SnakeContext.food;

if(food != null){

food.alive = false;

this.units.push(food.unit);

$s.SnakeContext.oneat();

}else{

alert("error:no food on (" + x + "," + y + ")");

}

}

/*

* 随机数产生器,提供简便的方法

*/

$s.Random = {

randomNumber : function(lower,upper){ // 返回区间[lower,upper]的整数

var choices = upper - lower + 1;

return Math.floor(Math.random() * choices + lower); // value = Math.floor(Math.random() * 可能值的个数+ 第一个可能的值)

},

randomLocation : function(maxX,maxY){

var x = $s.Random.randomNumber(0,maxX);

var y = $s.Random.randomNumber(0,maxY);

return {x:x,y:y};

}

};

$s.Food = function(x,y){ // 代表食物,由一个Unit表示

this.unit = new $s.Unit();

this.unit.x = x;

this.unit.y = y;

var color = $s.COLORS[$s.Random.randomNumber(0,$s.COLORS.length - 1)];

this.unit.color = color;

this.alive = true;

this.unit.repaint();

};

$s.Food.prototype.locateOn = function(x,y){

return this.unit.x == x && this.unit.y == y;

};

/*

* HTML结点池,主要目的是提高效率

* 因为snake的移动是通过删除尾部结点并向头部添加结点实现的,

* 在这个过程中会有大量的结点创建操作,为了操作效率,所以对结点进行池化管理.

* 尾部的结点不删除,而是隐藏,需要结点时可以重用之

*/

$s.NodePool = {

nodes : []

};

$s.NodePool._findHideNode = function(){ // 查找隐藏的div结点

for(var i = 0 ; i < this.nodes.length ; i ++){

var n = this.nodes[i];

if(n.style.display == "none"){

return n;

}

}

return null;

};

$s.NodePool.createNode = function(){

var pooledNode = this._findHideNode();

if(pooledNode != null){

return pooledNode;

}else{

var newNode = document.createElement("div");

this.nodes.push(newNode);

return newNode;

}

};

$s.NodePool.releaseNode = function(node){

if(node != undefined && node != null){

if(node instanceof $s.Unit){

var view = node.view;

if(view != null){

var div = view.node;

div.style.display = "none";

}

}

}

}

$s.UnitView = function(){ // Unit的视图

this.unit = null;

this.node = null;

};

$s.UnitView.prototype.repaint = function(){

if(this.node == null){ // 初始化

var tag = $s.NodePool.createNode();

tag.style.width = $s.UNIT_WIDTH + "px";

tag.style.height = $s.UNIT_HEIGHT + "px";

tag.style.borderStyle = "dotted";

tag.style.borderWidth = "1px";

tag.style.borderColor = "white";

tag.style.margintLeft = "1px";

tag.style.marginRight = "1px";

tag.style.marginTop = "1px";

tag.style.marginBottom = "1px";

tag.style.backgroundColor = this.unit.color; // 颜色由模型Unit指定

tag.style.position = "absolute"; //容器的position指定为relative,孩子的position指定为absolute时,表示孩子相对容器绝对定位.

tag.style.display = "block"; // 重要,因为从NodePool取现的结点是隐藏状态的

var x = this.unit.x * $s.UNIT_WIDTH;

var y = this.unit.y * $s.UNIT_HEIGHT;

tag.style.top = y + "px";

tag.style.left = x + "px";

this.node = tag;

$s.SnakeContext.panelView.append(this);

}else{

var tag = this.node;

var x = this.unit.x * $s.UNIT_WIDTH;

var y = this.unit.y * $s.UNIT_HEIGHT;

tag.style.top = y + "px";

tag.style.left = x + "px";

tag.style.backgroundColor = this.unit.color;

}

};

$s.PanelView = function(){ // 整个游戏区域,包括按钮区

var panel = document.createElement("div");

panel.style.width = ($s.PANEL_WIDTH * $s.UNIT_WIDTH ) + "px";

panel.style.height = ($s.PANEL_HEIGHT * $s.UNIT_HEIGHT ) + "px";

panel.style.borderStyle = "dotted";

panel.style.borderColor = "red";

panel.style.borderWidth = "1px";

panel.style.marginLeft = "auto";

panel.style.marginRight = "auto";

panel.style.marginTop = "50px";

panel.style.position = "relative"; // 容器的position指定为relative,孩子的position指定为absolute时,表示孩子相对容器绝对定位.

panel.style.marginBottom = "auto";

this.node = panel;

document.body.appendChild(panel);

var len = document.createElement("div");

len.style.marginLeft = "auto";

len.style.marginRight = "auto";

len.style.marginBottom = "20px";

len.style.color = "gray";

len.style.fontSize = "12px";

len.innerHTML = "长度:";

document.body.appendChild(len);

$s.SnakeContext._len = len;

var startBn = document.createElement("button");

startBn.innerHTML = "开始";

startBn.style.marginLeft = "10px";

startBn.onclick = function(){

$s.SnakeContext.run();

};

$s.SnakeContext._startBn = startBn;

document.body.appendChild(startBn);

var pauseBn = document.createElement("button");

pauseBn.innerHTML = "暂停";

pauseBn.style.marginLeft = "10px";

pauseBn.onclick = function(){

$s.SnakeContext.pause();

};

$s.SnakeContext._pauseBn = pauseBn;

document.body.appendChild(pauseBn);

/*

var stopBn = document.createElement("button");

stopBn.innerHTML = "停止";

stopBn.style.marginLeft = "10px";

stopBn.onclick = function(){

$s.SnakeContext.stop();

};

$s.SnakeContext._stopBn = stopBn;

document.body.appendChild(stopBn);

*/

var restartBn = document.createElement("button");

restartBn.innerHTML = "重新开始";

restartBn.style.marginLeft = "10px";

restartBn.onclick = function(){

window.location.href = window.location.href;

};

$s.SnakeContext._restartBn = restartBn;

document.body.appendChild(restartBn);

var line = document.createElement("div");

line.style.height = "10px";

document.body.appendChild(line);

var span = document.createElement("span");

span.style.color = "gray";

span.style.fontSize = "12px";

span.innerHTML = "调试";

document.body.appendChild(span);

var debug = document.createElement("input");

debug.type = "checkbox";

debug.checked = $s.DEBUG;

debug.onchange = function(){

$s.SnakeContext.setDebug(debug.checked);

};

document.body.appendChild(debug);

};

$s.PanelView.prototype.append = function(unitView){

try{

this.node.appendChild(unitView.node);

}catch(e){

alert(e);

}

};

/*

* 全局环境类,代表应用

* 约定以_开头的成员为私有成员

* 启动程序的方法:

* window.onload = function(){

* $s.SnakeContext.init();

* }

*/

$s.SnakeContext = {

dir : $s.Dir.NONE,

state : $s.State.STOP,

goTimer : null,

run : function(){

if(this.state != $s.State.RUNNGIN){

this.state = $s.State.RUNNGIN;

this.goTimer = window.setInterval(function(){

$s.SnakeContext.updateFood();

$s.SnakeContext.snake.go();

},$s.STEP);

}

},

stop : function(){

this._setState($s.State.STOP);

},

pause : function(){

this._setState($s.State.PAUSE);

},

_setState : function(s){

if(this.state != s && this.goTimer != null){

window.clearInterval(this.goTimer);

this.goTimer = null;

this.state = s;

}

},

getFood : function(x,y){

for(var f in this.foods){

if(f.x == x && f.y == y){

return f;

}

}

return null;

},

init : function(){

this.panelView = new $s.PanelView();

this.snake = new $s.Snake();

this.dir = $s.Dir.DOWN;

this.snake.init($s.Dir.UP,3);

this._len.innerHTML = "长度:" + 3;

document.body.onkeyup = function(e){

var code = null;

if(window.event){ // fuck的IE

code = window.event.keyCode;

}else{

code = e.keyCode;

}

var str = "";

var oldDir = $s.SnakeContext.dir;

switch(code){

case 37: // left

if($s.SnakeContext.dir != $s.Dir.RIGHT){

$s.SnakeContext.dir = $s.Dir.LEFT;

}

str = "left";

break;

case 38 : // up

if($s.SnakeContext.dir != $s.Dir.DOWN){

$s.SnakeContext.dir = $s.Dir.UP;

}

str = "up";

break;

case 39: // right

if($s.SnakeContext.dir != $s.Dir.LEFT){

$s.SnakeContext.dir = $s.Dir.RIGHT;

}

str = "right";

break;

case 40: // down

if($s.SnakeContext.dir != $s.Dir.UP){

$s.SnakeContext.dir = $s.Dir.DOWN;

}

str = "down";

break;

}

if($s.SnakeContext.dir != oldDir){

if($s.DEBUG){

var v = $s($s.KEY_UP_DIR_ID);

if(v){

v.innerHTML = "方向键:" + str;

}

}

if($s.SnakeContext.goTimer != null){

window.clearInterval($s.SnakeContext.goTimer);

$s.SnakeContext.goTimer = null;

}

$s.SnakeContext.snake.go();

$s.SnakeContext.goTimer = window.setInterval(function(){

$s.SnakeContext.updateFood();

$s.SnakeContext.snake.go();

},$s.STEP);

}

};

var loc = $s.Random.randomLocation($s.PANEL_WIDTH - 1, $s.PANEL_HEIGHT - 1);

this.food = new $s.Food(loc.x,loc.y);

},

snake : null,

foods : [],

panelView : null,

food : null,

updateFood : function(){

if(this.food.alive){ // 当前Food还存活

return;

}

var loc = null;

do{

// 随机产生一个点,直到不Snake重叠

loc = $s.Random.randomLocation($s.PANEL_WIDTH - 1,$s.PANEL_HEIGHT - 1);

}while(this.overlap(loc));

this.food = new $s.Food(loc.x,loc.y);

},

overlap : function(loc){ // 检查是否与Snake重叠,当重叠时返回true

var x = loc.x;

var y = loc.y;

for(var i = 0 ; i < this.snake.units.length ; i ++ ){

var u = this.snake.units[i];

if(u.x == x && u.y == y){

return true;

}

}

return false;

},

hasFood : function(x,y){

if($s.DEBUG){

var xt = $s($s.HEAD_LOCATION_ID);

if(xt){

xt.innerHTML = "头部位置:(" + x + "," + y + ")";

}

}

return this.food.locateOn(x,y);

},

setDebug : function(enable){

if(enable != $s.DEBUG){

$s.DEBUG = enable;

if($s.DEBUG){ // 显示

var i = $s($s.KEY_UP_DIR_ID);

$s.SnakeContext._show(i);

i = $s($s.HEAD_LOCATION_ID);

$s.SnakeContext._show(i);

}else{ // 隐藏

var i = $s($s.KEY_UP_DIR_ID);

$s.SnakeContext._hide(i);

i = $s($s.HEAD_LOCATION_ID);

$s.SnakeContext._hide(i);

}

}

},

_show : function(tag){

if(tag){

tag.style.display = "block";

}

},

_hide : function(tag){

if(tag){

tag.style.display = "none";

}

},

ondead : function(){ // Snake死亡时回调

if(this._startBn){

this._startBn.disabled = true;

}

if(this._pauseBn){

this._pauseBn.disabled = true;

}

if(this._stopBn){

this._stopBn.disabled = true;

}

alert("挂了");

},

oneat : function(){ // Snake长度增加时回调

this._len.innerHTML = "长度:" + this.snake.units.length;

},

_startBn : null,

_pauseBn : null,

_stopBn : null,

_restartBn : null,

_len : null

};

})();

下载本文
显示全文
专题