electron寫俄羅斯方塊遊戲(Tetris)

背景

在折騰ES6,忽然想起大學時用c語言寫過俄羅斯方塊,本項目中主要是利用ES6的Class特性進行面向對象編程。項目採用node.js v6.2.0 + electron v1.1.0 進行桌面開發,能跨全部平臺運行。javascript

思路

  • 全面應用面向對象的設計思想,讓功能內聚性強。php

  • 把七種方塊想成獨立的「生物」對象,讓它能「看」到周圍的世界。html

  • 沒有使用傳統的大的二維數組來表示遊戲場面狀態,而是讓tetris本身去「看」。html5

  • 使用html5的canvas來完成,比較象cgi編程。java

  • 使用最少的canvas特性,只用了fillRect,strokeRect,getImageData,clearRect等幾個函數。node

效果圖

我玩的最高紀錄^_^
nginx

運行方法

項目採用node.js v6.2.0 + electron v1.1.0 進行桌面開發,所以請先安裝相關係統:git

npm install electron-prebuilt -g

注:本項目採用方案能跨全部平臺運行,遇權限問題,請在命令行自行添加sudo 。github

源代碼:算法

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版本,完成俄羅斯方塊遊戲的全部基本功能,配了音效。後續我考慮網絡對戰,人機對戰,機器自戰。我主要是想作人工智能方面的試驗,從讓算法本身玩俄羅斯方塊開始!

相關文章
相關標籤/搜索