• electron写俄罗斯方块游戏(Tetris)


    背景

    在折腾ES6,突然想起大学时用c语言写过俄罗斯方块,本项目中主要是利用ES6的Class特性进行面向对象编程。项目采用node.js v6.2.0 + electron v1.1.0 进行桌面开发,能跨所有平台运行。

    思路

    • 全面应用面向对象的设计思想,让功能内聚性强。

    • 把七种方块想成独立的“生物”对象,让它能“看”到周围的世界。

    • 没有使用传统的大的二维数组来表示游戏场面状态,而是让tetris自己去“看”。

    • 使用html5的canvas来完成,比较象cgi编程。

    • 使用最少的canvas特性,只用了fillRect,strokeRect,getImageData,clearRect等几个函数。

    效果图

    我玩的最高纪录^_^

    运行方法

    项目采用node.js v6.2.0 + electron v1.1.0 进行桌面开发,因此请先安装相关系统:

    npm install electron-prebuilt -g

    注:本项目采用方案能跨所有平台运行,遇权限问题,请在命令行自行添加sudo 。

    源代码:

    git clone https://git.oschina.net/zhoutk/Tetris.git
    或者:
    git clone https://github.com/zhoutk/Tetris

    进入项目目录:

    cd Tetris

    运行程序:

    electron .

    关键代码分析

    功能尽量内聚,类Block封装所有小方块的操作,canvas 接口函数基本上在这个类中封装着;Tetris类组合了Block,封装了俄罗斯方块的绝大部分操作。

    Block类(小方块类)

    class Block{
        constructor(ctx,fillColor,strokeColor){
            this.ctx = ctx;                              //canvas对象
            this.width = BLOCKWIDTH;                     //小方块边长
            this.fillColor = fillColor || 'blue';        //直充颜色
            this.strokeColor = strokeColor || 'white';   //描边颜色
        }
        draw(x,y){                                       //绘制不方块
            this.ctx.save();
            this.ctx.fillStyle = this.fillColor;
            this.ctx.fillRect(x*this.width + 1,y*this.width + 1,this.width-2,this.width-2)
            this.ctx.strokeStyle = this.strokeColor;
            this.ctx.strokeRect(x*this.width + 1,y*this.width + 1,this.width-2,this.width-2);
            this.ctx.restore();
        }
        erase(x,y){                                        //擦除小方块
            this.ctx.clearRect(x*this.width , y*this.width , 30, 30)
        }
        canSee(x,y){                                        //看某个位置是否为空
            let c = this.ctx.getImageData(x*this.width+9,y*this.width+9,1,1)
            return c.data[0] | c.data[1] | c.data[2] | c.data[3];
        }
        getColor(x,y){                                      //取某个位置上的颜色
            let c = this.ctx.getImageData(x*this.width+9,y*this.width+9,1,1)
            return 'rgba('+c.data[0]+','+c.data[1]+','+c.data[2]+','+c.data[3]+')';
        }
    }

    Tetris类(俄罗斯方块类)

    class Tetris {
        constructor(shape,ctx,x,y){
            this.data = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]];    //方块形状数据
            this.shape = shape || 0;                                  //方块形状代码
            this.ctx = ctx;                                            //canvas对象
            this.x =  x || 0;                                        //方块位置数据
            this.y =  y || 0;
            this.block = new Block(ctx, COLORS[shape]);              //组合block对象
    
            for(let i = 0; i < SHAPES[this.shape].length; i++){      //方块形状初始化 
                if(SHAPES[this.shape][i]){
                    this.data[i % 4][1 + Math.floor(i/4)] = 1;
                }
            }
        }
        cleanup(){ ... }                                            //消层,计分
        moveNext(){ ... }                                           //方块下移一格
        moveLeft(){ ... }                                           //方块左移一格
        moveRight(){ ... }                                          //方块右移一格
        moveDown(){ ... }                                           //方块移动到底
        rotate(){ ... }                                             //方块旋转
        canDrawNext(){ ... }                                        //检测新方块是否能放置,游戏结束检测
        draw(){ ... }                                               //调用block对象,进行俄罗斯方块绘制
        erase(){ ... }                                               //调用block对象,进行俄罗斯方块擦除
        canSee{ ... }                                               //调用block对象,进行俄罗斯方块放置检测
        

    Block.canSee

    取指定位置象素颜色属性,模拟方块“视觉”。

    canSee(x,y){
            let c = this.ctx.getImageData(x*this.width+9,y*this.width+9,1,1)
            return c.data[0] | c.data[1] | c.data[2] | c.data[3];        //黑色为全零,异或为0时,位置为空,其它表示位置已经被占用。
        }

    Tetris.cleanup

    消层比较复杂,配上注释。

    cleanup(){
            let h = 19, levelCount = 0;               //一次消除层数统计变量
            while(h >= 0){                            //从最底层一直找到顶
                let count = 0;                        //记录同一层上空位置的数量
                for(let i = 0; i< 10; i++){           //遍历一层
                    if(this.canSee(i,h)){             //位置为空,变量加一
                        count++;
                    }
                } 
                if(count == 0){                       //层满,需要消除
                    let level = h;                    //待消层
                    levelCount++;                     //消层数量加一
                    SOUNDS['score'].play();
                    while(level >= 0){                //将待消层上面的所有层整体下移一层
                        let ct = 0;                    //记录同一层上空位置的数量
                        for(let j = 0; j < 10; j++){
                            this.block.erase(j,level);     //清除待消层方格
                            if(this.canSee(j,level-1)){    //空位置统计
                                ct++;
                            }else{
                                let bk = new               //取垂直上方方格颜色          Block(this.ctx,this.block.getColor(j,level-1)) 
                                bk.draw(j,level)           //下移
                            }
                        }
                        if(ct == 10){                       //一层都是空位置,整体下移提前完成。
                            break;
                        }else{
                            level--;                        //楼层上移
                        }
                    }
                }else if(count == 10){                   //一层都是空位置,消层工作提前完成。
                    break;
                }else{
                    h--;
                }
            }
            return levelCount;
        }

    Tetris.moveNext

    方块下移一层比较复杂,配上注释。

    moveNext(){
            let flag = true;                                    //为跳出双重循环设置的变量
            for(let i = 0; i < 4; i++){                         //检测是否能下移
                for(let j = 0; j < 4; j++){
                    if(this.data[i][j] && (j ==3 || this.data[i][j+1] == 0)){
                        if(!this.canSee(this.x + i, this.y + 1 + j)){
                            flag = false;                      //已经到底
                            break;
                        }
                    }
                }
                if(!flag){
                    break;
                }
            }
            if(flag){                                        //下移一层
                this.erase();
                this.y++;
                this.draw();
                return true;
            }else{                                            //到底处理
                let level = this.cleanup();                   //消层处理
                if(level > 0){                                //消层数量大于零
                    levels += level;                          //计分
                    scores += LVSCS[level]
                    document.getElementById('levelShow').value = levels;
                    document.getElementById('scoreShow').value = scores;
                    if(Math.floor(scores / STEPVAL) != STEP){  //调速度级别
                        clearInterval(interval)
                        interval = setInterval( tick, TICKVAL - ++STEP * STEPVAL );
                        document.getElementById('speedShow').value = STEP + 1;
                    }
                }else{
                    SOUNDS['down'].play()
                }
                return false;
            }
        }

    操作及规则

    • 方向上键:旋转

    • 方向左键:左移

    • 方向右键:右移

    • 方向下键:下移

    • 空格键:下移到底

    • 计分:同时消队一层计1分;二层3分;三层3分;四层十分

    • 速度分十个级别,每个级别相差50ms

    小结

    项目现在处于v1.0.0版本,完成俄罗斯方块游戏的所有基本功能,配了音效。后续我考虑网络对战,人机对战,机器自战。我主要是想做人工智能方面的试验,从让算法自己玩俄罗斯方块开始!

  • 相关阅读:
    pandas 修改指定列中所有内容
    Python 实现获取【昨天】【今天】【明天】日期
    Selenium定位不到指定元素原因之iframe(unable to locate element)
    Pandas 通过追加方式合并多个csv
    python setup.py install 报错:error: [WinError 3] 系统找不到指定的路径。: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\PlatformSDK\lib
    pandas 如何判断指定列是否(全部)为NaN(空值)
    报错:PermissionError: [WinError 5] Access is denied: 'C:\Program Files\Anaconda3\Lib\site-packages\pywebhdfs'
    Node.js的函数返回值
    在Eclipse中使用JSHint检查JavaScript
    Node.js前端自动化工具:gulp
  • 原文地址:https://www.cnblogs.com/zhoutk/p/5534146.html
Copyright © 2020-2023  润新知