当前位置: 代码迷 >> JavaScript >> JavaScript容易贪吃蛇,基本面向对象
  详细解决方案

JavaScript容易贪吃蛇,基本面向对象

热度:550   发布时间:2012-09-04 14:19:30.0
JavaScript简单贪吃蛇,基本面向对象
没有写博客的习惯,这篇算心血来潮,算篇近几天编写的小程序纪实.

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

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

   作者:pcenshao
   转载请注明来自:
     http://blog.csdn.net/pywepe
   

     程序包括一个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
    };
   
})();



计算机 二手书 经典
http://pcenshao.taobao.com
1 楼 沙舟狼客 2011-08-01  
用html5中的canvas标签写更简单,参考http://ligson.iteye.com/blog/1136517
  相关解决方案