你的位置:首页 > Java教程

[Java教程]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版本,完成俄罗斯方块游戏的所有基本功能,配了音效。后续我考虑网络对战,人机对战,机器自战。我主要是想做人工智能方面的试验,从让算法自己玩俄罗斯方块开始!