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 意見: