JavaScript簡單貪吃蛇,基本面向對象

 

沒有寫博客的習慣,這篇算心血來潮,算篇近幾天編寫的小程序紀實.

      以編寫此程序的方式結束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>   

<p id="headLocation"></p>

<p id="keyup"></p>

</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(){ // 查找隱藏的p結點       

        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("p");

            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 p = view.node;

                    p.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("p");

        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("p");

        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("p");

        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

    };

   

})();

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *