HTML5 俄羅斯方塊



沒想到我會突然寫一版HTML5+JS的俄羅斯方塊,只為了練習canvas,雖然最後發現…全部的程式碼三百多行,真正用到canvas才5行左右Orz

/**
 * Created by ercrta on 2015/9/10.
 * 2015-09-10 13:30~19:00
 * 2015-09-11 09:30~12:00
 * 2015-09-11 13:00~14:20
 * 共約9小時
 */

$(function(){
    var GAME_STATUS_INIT=0;
    var GAME_STATUS_RUN=1;
    var GAME_STATUS_STOP=2;
    var GAME_STATUS_END=3;
    var MOVE_DIRECTION_DOWN=0;
    var MOVE_DIRECTION_LEFT=1;
    var MOVE_DIRECTION_RIGHT=2;
    var BOX_STATUS_SPACE=0;         //空白位置
    var BOX_STATUS_MOVE_PUZZLE=1;   //移動中的方塊
    var BOX_STATUS_FIXED_PUZZLE=2;  //固定的方塊
    var MAP_WIDTH=8;
    var MAP_HEIGHT=19;
    var BOX_SIZE=10;
    var map;
    var speed=1000;
    var interval;
    var puzzle_position=[];
    var puzzle_center=[];
    var game_status;
    init();

    function init(){
        set_game_status(GAME_STATUS_INIT);
        $("#puzzle_canvas").attr("width",BOX_SIZE*MAP_WIDTH);
        $("#puzzle_canvas").attr("height",BOX_SIZE*MAP_HEIGHT);
        $("#btn_start").bind('click',function(){
            create_empty_map();
            do_puzzle_create(create_puzzle());
            set_game_status(GAME_STATUS_RUN);
            star_game();
        });
        $("#btn_stop").bind('click',function(){
            set_game_status(GAME_STATUS_STOP);
            clearInterval(interval);
        });
        $("#btn_restart").bind('click',function(){
            create_empty_map();
            do_puzzle_create(create_puzzle());
            set_game_status(GAME_STATUS_RUN);
            star_game();
        });
        $("#btn_continue").bind('click',function(){
            set_game_status(GAME_STATUS_RUN);
            star_game();
        });
        $(document.body).on('keydown', function(e) {
            doKeyDown(e);
        });
    }
    function star_game(){
        clearInterval(interval);
        interval=setInterval(function(){
            var move_success=do_arrow_direction(MOVE_DIRECTION_DOWN);
            if(move_success==false){
                do_set_fixed_puzzle();
                do_clean_fixed_puzzle();
                var create_success=do_puzzle_create(create_puzzle());
                if(create_success==false){
                    set_game_status(GAME_STATUS_END);
                    clearInterval(interval);
                    alert("End Game");
                }
            }
        },speed);
    }
    function doKeyDown(e) {
        if(game_status!=GAME_STATUS_RUN)return;
        var keyID = e.keyCode ? e.keyCode :e.which;
        if(keyID === 38 || keyID === 87)  { // up arrow and W
            do_puzzle_rotation();
        }
        if(keyID === 39 || keyID === 68)  { // right arrow and D
            do_arrow_direction(MOVE_DIRECTION_RIGHT);
        }
        if(keyID === 40 || keyID === 83)  { // down arrow and S
            do_arrow_direction(MOVE_DIRECTION_DOWN);
        }
        if(keyID === 37 || keyID === 65)  { // left arrow and A
            do_arrow_direction(MOVE_DIRECTION_LEFT);
        }
    }
    function set_game_status(_game_status){
        game_status=_game_status;
        view_buttons(_game_status);
    }
    function set_map(_map){
        map=_map;
        view_map();
    }
    function view_buttons(_game_status){
        $("#btn_start").hide();
        $("#btn_stop").hide();
        $("#btn_restart").hide();
        $("#btn_continue").hide();
        switch (_game_status){
            case GAME_STATUS_INIT:
                $("#btn_start").show();
                break;
            case GAME_STATUS_RUN:
                $("#btn_stop").show();
                $("#btn_restart").show();
                break;
            case GAME_STATUS_STOP:
                $("#btn_continue").show();
                break;
            case GAME_STATUS_END:
                $("#btn_restart").show();
                break;
            default :
                break;
        }
    }

    function view_map(){
        var c=document.getElementById("puzzle_canvas");
        var cxt=c.getContext("2d");

        for(var i=0;i<MAP_WIDTH;i++){
            for(var j=0;j<MAP_HEIGHT;j++){
                draw_box(cxt,i,j,map[i][j]);
            }
        }
    }
    function draw_box(cxt,x,y,box){
        switch (box){
            case BOX_STATUS_SPACE:cxt.fillStyle = "#FF0000";break;          //red
            case BOX_STATUS_MOVE_PUZZLE:cxt.fillStyle = "#00FF00";break;    //green
            case BOX_STATUS_FIXED_PUZZLE:cxt.fillStyle = "#0000FF";break;   //blue
            default :
                break;
        }
        cxt.fillRect(x*BOX_SIZE,y*BOX_SIZE,BOX_SIZE,BOX_SIZE);
  cxt.strokeRect(x*BOX_SIZE,y*BOX_SIZE,BOX_SIZE,BOX_SIZE);
    }
    function create_empty_map(){
        var tmp_map=new Array(MAP_WIDTH);
        for(var i=0;i<MAP_WIDTH;i++){
            tmp_map[i]=new Array(MAP_HEIGHT);
            for(var j=0;j<MAP_HEIGHT;j++){
                tmp_map[i][j]=0;
            }
        }
        set_map(tmp_map);
    }
    function create_puzzle(){
        var random_num=Math.floor((Math.random() * 19) + 1);
        switch(random_num){
            case 1:return [[0,0],[1,0],[2,0],[1,1]];break;  //T
            case 2:return [[1,0],[0,1],[1,1],[1,2]];break;  //T
            case 3:return [[1,0],[0,1],[1,1],[2,1]];break;  //T
            case 4:return [[0,0],[0,1],[1,1],[0,2]];break;  //T
            case 5:return [[1,0],[1,1],[1,2],[1,3]];break;  //I
            case 6:return [[0,0],[1,0],[2,0],[3,0]];break;  //I
            case 7:return [[0,0],[1,0],[1,1],[2,1]];break;  //Z
            case 8:return [[1,0],[0,1],[1,1],[0,2]];break;  //Z
            case 9:return [[1,0],[2,0],[0,1],[1,1]];break;  //倒Z
            case 10:return [[0,0],[0,1],[1,1],[1,2]];break;  //倒Z
            case 11:return [[0,0],[0,1],[1,0],[1,1]];break;  //田
            case 12:return [[0,0],[0,1],[0,2],[1,2]];break;  //L
            case 13:return [[0,0],[1,0],[2,0],[0,1]];break;  //L
            case 14:return [[0,0],[1,0],[1,1],[1,2]];break;  //L
            case 15:return [[2,0],[0,1],[1,1],[2,1]];break;  //L
            case 16:return [[1,0],[1,1],[0,2],[1,2]];break;  //倒L
            case 17:return [[0,0],[0,1],[1,1],[2,1]];break;  //倒L
            case 18:return [[0,0],[1,0],[0,1],[0,2]];break;  //倒L
            case 19:return [[0,0],[1,0],[2,0],[2,1]];break;  //倒L
            default :
                return [[1,0],[1,1],[1,2],[1,3]];break;     //I
        }
    }
    function do_arrow_direction(direction){
        switch (direction){
            case MOVE_DIRECTION_DOWN:return do_puzzle_move(0,1);break;
            case MOVE_DIRECTION_LEFT:return do_puzzle_move(-1,0);break;
            case MOVE_DIRECTION_RIGHT:return do_puzzle_move(1,0);break;
            default :
                break;
        }
    }
    function do_puzzle_create(puzzle){
        var new_puzzle_position=[];
        var len=puzzle.length;
        //計算新位置
        for(var i=0;i<len;i++){
            var point_x=puzzle[i][0];
            var point_y=puzzle[i][1];
            new_puzzle_position.push([2+point_x,point_y]);
        }
        return do_set_puzzle_in_map(new_puzzle_position,[3,1]);
    }
    function do_puzzle_move(move_x,move_y){
        var new_puzzle_position=[];
        var len=puzzle_position.length;
        //計算新位置
        for(var i=0;i<len;i++){
            var point_x=puzzle_position[i][0];
            var point_y=puzzle_position[i][1];
            new_puzzle_position.push([point_x+move_x,point_y+move_y]);
        }
        return do_set_puzzle_in_map(new_puzzle_position,[puzzle_center[0]+move_x,puzzle_center[1]+move_y]);
    }
    function do_puzzle_rotation(){
        var new_puzzle_position=[];
        var len=puzzle_position.length;
        //計算新位置
        var center_x=puzzle_center[0];
        var center_y=puzzle_center[1];
        for(var i=0;i<len;i++){
            var point_x=puzzle_position[i][0];
            var point_y=puzzle_position[i][1];

            var new_point_x=-( (point_y-center_y))+center_x;
            var new_point_y=-(-(point_x-center_x))+center_y;

            new_puzzle_position.push([new_point_x,new_point_y]);
        }
        return do_set_puzzle_in_map(new_puzzle_position,puzzle_center);
    }
    function do_set_puzzle_in_map(new_puzzle_position,new_puzzle_center){
        var len=new_puzzle_position.length;
        //檢查位置
        if(chk_puzzle_new_position(new_puzzle_position)==false){
            return false;
        }
        //清除舊位置
        do_clean_old_position();
        //填上新位置
        for(var i=0;i<len;i++){
            var point_x=new_puzzle_position[i][0];
            var point_y=new_puzzle_position[i][1];
            map[point_x][point_y]=BOX_STATUS_MOVE_PUZZLE;
        }
        //��錄puzzle的位置
        puzzle_position=new_puzzle_position;
        puzzle_center=new_puzzle_center;
        set_map(map);
        return true;
    }
    function chk_puzzle_new_position(new_puzzle_position){
        var len=new_puzzle_position.length;
        var check_position=true;
        for(var i=0;i<len;i++){
            var point_x=new_puzzle_position[i][0];
            var point_y=new_puzzle_position[i][1];
            if(point_x>=MAP_WIDTH || point_x<0){
                check_position=false;break;
            }
            if(point_y>=MAP_HEIGHT || point_y<0){
                check_position=false;break;
            }
            try{
                if(map[point_x][point_y]==BOX_STATUS_FIXED_PUZZLE){
                    check_position=false;break;
                }
            }catch (e){
                alert(point_x+','+point_y);
            }
        }
        return check_position;
    }
    function do_clean_old_position(){
        //清除舊的位置
        for(var i=0;i<MAP_WIDTH;i++){
            for(var j=0;j<MAP_HEIGHT;j++){
                if(map[i][j]==BOX_STATUS_MOVE_PUZZLE){
                    map[i][j]=BOX_STATUS_SPACE;
                }
            }
        }
    }
    function do_set_fixed_puzzle(){
        for(var i=0;i<MAP_WIDTH;i++){
            for(var j=0;j<MAP_HEIGHT;j++){
                if(map[i][j]==BOX_STATUS_MOVE_PUZZLE){
                    map[i][j]=BOX_STATUS_FIXED_PUZZLE;
                }
            }
        }
        set_map(map);
    }
    function do_clean_fixed_puzzle(){
        var i,j;
        var fixed_row_number=[];
        //檢查排滿的行數
        for(i=0;i<MAP_HEIGHT;i++){
            var flag=true;
            for(j=0;j<MAP_WIDTH;j++){
                if(map[j][i]!=BOX_STATUS_FIXED_PUZZLE){
                    flag=false;
                    break;
                }
            }
            if(flag){
                fixed_row_number.push(i);
            }
        }
        //清掉
        var len=fixed_row_number.length;
        for(i=0;i<len;i++){
            var row_num=fixed_row_number[i];
            for(j=row_num;j>0;j--){
                for(var m=0;m<MAP_WIDTH;m++){
                    if(j-1>=0) {
                        map[m][j] = map[m][j - 1];
                    }else{
                        map[m][j] = BOX_STATUS_SPACE;
                    }
                }
            }
        }
        set_map(map);
    }
});

2015-09-11建立
完整程式碼下載v0.0
2015-09-20更新,使用localStorage,視窗關閉後,重新打開檔案,會從上次的狀態續繼遊戲
完整程式碼下載v0.1

這次有碰到兩個稍微困擾我的地方:
1.canvas的大小設定,我是用jquery設的,所以本來是用css的方式設定,結果一設,就變形!!
舊寫法:
//$("#puzzle_canvas").css("width",BOX_SIZE*MAP_WIDTH);
//$("#puzzle_canvas").css("height",BOX_SIZE*MAP_HEIGHT);
更新成:
$("#puzzle_canvas").attr("width",BOX_SIZE*MAP_WIDTH);
$("#puzzle_canvas").attr("height",BOX_SIZE*MAP_HEIGHT);
2.旋轉的部份也困擾我了一下,一直鑽牛角尖,想到矩陣相乘去了,事實上只要稍微做一下加減就可以了(因為轉90度比較簡單)
var point_x=puzzle_position[i][0];
var point_y=puzzle_position[i][1];

var new_point_x=-( (point_y-center_y))+center_x;
var new_point_y=-(-(point_x-center_x))+center_y;
Category: 0 意見

nodejs-模組(Module) & 套件(Package)

模組(Module) & 套件(Package)
1.1一個Node.js檔案就是一個模組,這檔案可能是js程式、json或編譯過的c/c++擴充
1.2建立module.js
var name;
exports.setName=function(_name){
name=_name;
};

exports.sayHi=function(){
console.log('Hi! '+name);
}
1.3建立getmodule.js
var my_module=require('./module');
my_module.setName('By');
my_module.sayHi();

1.4執行getmodule.js,出現
Hi! By

1.5關於require & exports
require是用來取得模組的介面
exports是模組公開的介面

1.6require不會重複載入模組
修改getmodule.js如下:
var my_module1=require('./module');
my_module1.setName('my_module1');
my_module1.sayHi();

var my_module2=require('./module');
my_module2.setName('my_module2');
my_module1.sayHi();

執行結果:
Hi! my_module1
Hi! my_module2

我們兩次都是呼叫my_module1,卻發現第二次呼叫的my_module1的值被,my_module2給蓋過,表示my_module1跟my_module2指向同一個實例

1.6簡化exports
有時只想把物件封裝到模組中,建立obj.js內容如下:

function Hi(){
var name;
this.set_name=function(_name){
name=_name;
};

this.say=function(){
console.log("Hi! "+name);
}
}

exports.Hi=Hi;

想取用時,建立getobj.js,內容如下:

var HiClass=require('./obj.js').Hi;
var obj=new HiClass();
obj.set_name('my');
obj.say();

因為用require('./obj.js').Hi;來取用物件有點多餘,所以可以改成下面稍微簡化:

obj.js內容如下:

function Hi(){
var name;
this.set_name=function(_name){
name=_name;
};

this.say=function(){
console.log("Hi! "+name);
}
}

//exports.Hi=Hi;
module.exports=Hi;

getobj.js,內容如下:

//var HiClass=require('./obj.js').Hi;
var HiClass=require('./obj.js');
var obj=new HiClass();
obj.set_name('my');
obj.say();
Category: 0 意見

mysql脫逸字元

今天從DB查東西時,發生一些問題
Table的資料如下:
sn name
1 abc
2 a_b
3 a_c

我想查出a_開頭的name,像a_b跟a_c,所以sql下了:
Select * from name_table where name like LIKE 'a_%'
結果它連abc都出來了,原來_在sql表示的是萬用字元
mysql中的2個萬用字元,與 LIKE 關鍵字一起使用的
參考:http://www.1keydata.com/tw/sql/sql-wildcard.html
%(百分比符號) 代表零個、一個、或數個字母
_(底線) 代表剛好一個字母

解決方式便是使用脫逸字元 \ ,在\後的第一個字元會脫逸,修正語法如下:
Select * from name_table where name like LIKE 'a\_%'
另一個解決方法可使用 ESCAPE ,範例是使用\\,表示在\\後的第一個字元會脫逸:
SELECT * FROM name_table where name like 'a\\_%' ESCAPE '\\'
ESCAPE 範例2,使用z,表示在z後的第一個字元會脫逸
SELECT * FROM name_table where name like 'az_%' ESCAPE 'z'
Category: 0 意見