没有写博客的习惯,这篇算心血来潮,算篇近几天编写的小程序纪实.
以编写此程序的方式结束Javascript的本阶段的学习.编写的目的在于熟悉javascript的编程方式,包括代码风格,面向对象的运用等.
回到程序,说说Snake的移动的实现方法.其实很简单,向头部添加Unit,然后删除尾部.其他,参见注释.
程序包括一个html文件:snake.html和一个js文件:snake.js
snake.html:
$s(function(){
$s.SnakeContext.init();
});
snake.js:
/*
* JavaScript is simple snake. Basic object-oriented.
* Rules:
* 1. There is no wall, left and right are connected, top and bottom are connected.
* 2. The snake will die when its head collides with itself.
* Compatibility:
* Fully supports Firefox, Chrome
* Basic support for IE (Except for the debugging part)
*
* Author: pcenshao
* Please indicate the source for reprinting:
* http://blog.csdn.net/pywepe
* http://pcenshao.taobao.com
*/
(function(){
$s = function(){
using
return document.getElementById(arguments[0]); ; $s.UNIT_WIDTH = 10; // Unit width $s.UNIT_HEIGHT = 10; $s.PANEL_WIDTH = 30; // Logical width $s.PANEL_HEIGHT = 20; // Logical height $s.STEP = 250; Logical height $s.STEP = 250; * * The color of food */ $s.COLORS = ["blue","green","#494e8f","#905d1d","#845538","#77ac98","#8552a1 "]; /* * Debugging related * $s.DEBUG Debugging information display switch * $s.KEY_UP_DIR_ID Monitor the node id of the direction key. If it does not exist, it will not be displayed * $s.HEAD_LOCATION_ID Monitors the node ID of the snake head location. If it does not exist, it will not be displayed. */ $s.DEBUG = false; $s.KEY_UP_DIR_ID = "keyup"; $s.HEAD_LOCATION_ID = "headLocation"; $s.Dir = { // Represents direction, forced to call $s.Dir.UP method to avoid parameter errors UP: {}, DOWN: {}, LEFT : {}, RIGHT : {}, NONE : {} }; $s.State = { // Represents the state STOP : {}, RUNNGIN: {},PAUSE: {}}};
$ s.Unit = Function () {// A cell, look at the eyes of MVC, unit is a model, unitView is view
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(); // Notify 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.
if(i == (count - 1) )(
$s.Snake.prototype.crash = function(x,y) { // Pass in the position of the head, return true to indicate collision with itself
for(var i = this.units.length - 2; i >= 0; i --){ //Do not include the head itself
var u = this.units [i]; Return false;
};
$s .Snake.prototype.go = function(){
. length - 1];
,_y)){ / / Determine whether it collides with itself
SnakeContext.stop(); $s.
s.Dir.LEFT){ ir == $s.Dir. Up) { -_y -; } else if (dir == $ s.dir.down) { _y ++; } (_x > 1; } if(_y > ; = $ s.panel_height) { _y = 0; } if (_y & lt; 0) { _y = $ s.panel_height -1;}} var h = new $ s.Unit(); // New header. If ($ s.snakeContext.hasfood (_x, _y)) {// The next step encounter food
this.eat (_x, _y); head = this.units [this.units.Length- 1] ; // Because the eat method can change the head, re-acquire _x = head. _x --; _y --;I} else if (dir == $ s.dir.down) {
_y ++;
}
head.color = $ s.head_color;
head.repaint (); .units[this.units.length - 2];
(); (h); .units.length - 1; i >= 0; i --){ var u = this.units[i]; Head u.color = $s.HEAD_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); "); } } } /* * Random number generator, providing a simple method */ $s.Random = { randomNumber: function(lower,upper){ // Return the integer in the interval [lower, upper] var choices = upper - lower + 1;M Return math.floor (math.random () * choices + low); // value = math.floor (math.random () * Number of possible values + the first possible value)},
, RandomLocation: Function (maxx, maxy) { var x = $ s.random.randomnumber (0, maxx); var y y = $ s.randomnumber (0, maxy); Return {x: x ,y:y}; ); 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.loc ateOn = function(x . * Because snake movement is achieved by deleting tail nodes and adding nodes to the head, * There will be a large number of node creation operations in this process. For the sake of operational efficiency, the nodes are pooled and managed.结 The nodes at the tail are not deleted, but are hidden. You can reuse when you need nodes. _findHideNode = function(){ // Find hidden div nodes Y if (n.style.display == "None") { Return n; }} Return null; }; var pooledNode=this._findHideNode(); lement("div"); this.nodes.push (newNode); return newNode; instanceof $s.Unit){ var view = node.view; 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";
using use using ‐ ‐ ‐ ‐ ‐ ‐ use using using out through through using ‐ ‐ ‐ ‐ ‐‐ ‐‐‐ panel.style.position = "50px"; .style.marginBottom = "auto";
this.node = panel; Len.style. marginleft = "auto";
len.style.marginrid = "Auto";
len.style.marginbottom = "20px";
len.style.color = "Gray"; "12px";
len.innerHTML = "Length:";
document.body.appendChild(len);
$s.SnakeContext._len = len;
var startBn = document.createElement("button ");
startBn.innerHTML = "Start";
}; ; pauseBn.innerHTML = "pause";
pauseBn.style.marginLeft = "10px"; pauseBn = pauseBn;
document. body.appendChild(pauseBn); Bn.style.marginLeft = "10px";
StopBn.onclick = function(){
$s.SnakeContext.stop();
};
$s.SnakeContext._stopBn = stopBn;
document.body.appendChild(stopBn);
restartBn.onclick = function(){
window.location.href = window.location.href;
var line = document.createElement( "div" );
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;
}
_len : null
};
})();