最近工做之餘看了一個俄羅斯方塊小遊戲實現的視頻教程,以爲這個小遊戲的算法挺有意思,打算整理出來學習參考一下。html
雖然標題標着canvas,其實這個小遊戲用到的canvas知識點甚少,其核心仍是js算法問題。教程會比較枯燥,可能對實際工做中也沒太大用處(不過能夠用俄羅斯方塊作一個營銷小活動),寫出來目的主要是爲了學習一下這款小遊戲的算法思想,以及鍛鍊解決問題的能力。前端
本篇文章將分爲幾個模塊逐步講解:node
遊戲雖小,功能還挺複雜,須要的就是思惟和耐心。下面直接進入正題。react
俄羅斯方塊想必你們都玩過,大致玩法簡單說一下,在一個格子地圖上隨機下落不一樣形狀的方塊,經過←→方向鍵去控制方塊的移動,以及↑鍵改變方塊的形狀,↓鍵加速其下落,當方塊落下後滿一行時消除該行。git
來看一下咱們最終實現的效果圖:github
咱們能夠把俄羅斯方塊一分爲二的看,地圖+方塊。咱們把地圖當作一個二維的數組:算法
[
[0,0,0,0,0,0...],
[0,0,0,0,0,0...],
[0,0,0,0,0,0...],
[0,0,0,0,0,0...],
...
]
複製代碼
方塊也看做一個二維數組canvas
如Z形狀的方塊可看做爲:小程序
[
[1,1,0],
[0,1,1]
]
複製代碼
倒T形狀的方塊可看做爲:微信小程序
[
[0,1,0],
[1,1,1]
]
複製代碼
等等 而後兩個數組結合起來:
[
[0,0,1,1,0,0,0,0],
[0,0,0,1,1,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
]
複製代碼
上面是一個Z字形方塊在第一行。
從上面數組能夠一目瞭然的看到,咱們渲染canvas的時候將數組值爲0的元素渲染成地圖的顏色,而元素值爲1的渲染成方塊的顏色便可。 那麼方塊的移動又是怎麼作呢,很簡單就是去改變方塊左右的元素從0改成1,再將原本的位置重置爲0便可,在視覺上形成方塊的移動。
以下圖一個Z字方塊的移動:
知道其核心思想以後,能夠碼代碼開發了。
首先咱們先準備一個canvas
畫布:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>canvas</title>
<style>
#myCanvas {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
margin: auto;
background: #000;
}
</style>
</head>
<body>
<canvas id="myCanvas" height="500" width="500"></canvas>
<button id="stop">中止</button>
<button id="score">得分: 0</button>
<script src="/js/tetris/index.js"></script>
</body>
</html>
複製代碼
首先咱們須要瞭解一下canvas
的簡單操做,在畫布上畫一個小方塊:
index.js
let Tetris = {
init(){
//初始化
const canvas = document.getElementById('myCanvas');
this.gc = canvas.getContext('2d');
this.render();
},
//渲染畫布
render(){
//在座標爲100,100的位置畫一個40*40的紅色方塊
this.gc.fillStyle = 'red';
this.gc.fillRect(100,100,40,40);
}
};
Tetris.init();
複製代碼
如圖:
作俄羅斯方塊所需的canvas
知識點你只須要知道fillStyle
,fillRect
便可,沒有那麼複雜。
接下來,咱們要畫出N*N的格子地圖了。
先生成一個N*N的二維數組,值都爲0:
function map(r,c){
let data = [];
for(let i = 0; i < c; i++){
data.push([]);
//每行長度爲 r
data[i].length = r;
//全部元素默認值 0
data[i].fill(0);
}
return data;
}
console.table(map(20,10));
複製代碼
根據上面的方塊的渲染方式和地圖數組的生成結合,來渲染出一個20*20的地圖:
完整代碼以下:
index.js
let Tetris = {
init(){
//初始化
const canvas = document.getElementById('myCanvas');
this.gc = canvas.getContext('2d');
//20*20的格子
let data = this.map(20,20);
this.render(data);
},
map(r,c){
let data = [];
for(let i = 0; i < c; i++){
data.push([]);
//每行長度爲 r
data[i].length = r;
//全部元素默認值 0
data[i].fill(0);
}
return data;
},
render(data){
//計算每一個方塊的寬高 方塊之間間隔爲4
let w = 500/20 - 4;
let h = 500/20 - 4;
//計算方塊的行列
let r = data.length;
let c = data[0].length;
for(let i = 0; i < r; i ++){
for (let j = 0; j < c; j++){
//判斷數組裏的值 若爲1 則渲染爲紅色 0 則渲染爲白色
this.gc.fillStyle = data[i][j] === 0 ? 'white' : 'red';
this.gc.fillRect(
(w+4)*j+2,
(h+4)*i+2,
w,
h
);
}
}
}
};
Tetris.init();
複製代碼
分析一下上面稍微難以理解的是每一個方塊x,y軸值的計算。
詳細分析以下:
畫布寬高500 * 500 要平均分配20*20個方塊,
那麼方塊的最大寬度爲 500/20 = 25
可是這樣的話方塊會擠在一塊 所以咱們再給每一個方塊之間增長4個單位的間距
因而 單個方塊的 w/h = 500/20 - 4 = 21;
座標的算法:
20個方塊之間有19個間隙 剩下一個4單位的距離 分配到小方塊距離左右兩側的間隙
因而能夠列出:
n列 x座標
0 w*0 + 2
1 w*1 + 2 + 4*1
2 w*2 + 2 + 4*2
3 w*3 + 2 + 4*3
...
n w*n + 2 + 4*n
因此第j列的x座標能夠概括爲 (w+4)*j + 2
y座標亦然
複製代碼
執行完代碼看效果以下:
長征已經成功走出第一步,接下來就是方塊的建立了。
方塊有多種類型,如一字型,L字型,Z字型,倒T字型,田字型等等,根據上面算法中提到的,每種類型能夠用一個二維數組描述出來:
blockType: [
[[1,1,1,1]],
[[1,1],[1,1]],
[[1,1,0],[0,1,1]],
[[0,1,1],[1,1,0]],
[[0,1,0],[1,1,1]],
[[1,0,0],[1,1,1]],
[[0,0,1],[1,1,1]]
]
複製代碼
那怎麼把一個方塊再地圖上顯示出來呢?
也很簡單,只須要把方塊的二維數組插入到地圖的數組中就能夠了。
簡單實現以下:
//更新data數組
draw(block){
/*
* 假如block爲Z字型的方塊 [[1,1,0],[0,1,1]]
*/
for (let i = 0; i < block.length; i++){
for (let j = 0; j < block[0].length; j++){
this.data[i][j + this.y] = block[i][j];
}
}
console.table(this.data);
//再次調用render方法更新畫布
this.render(this.data);
}
複製代碼
要隨機生成一個不一樣形狀的方塊,調用Math.random()
便可。
貼完整的代碼:
index.js
let Tetris = {
//初始化
init(){
const canvas = document.getElementById('myCanvas');
this.gc = canvas.getContext('2d');
//20*20的格子
this.data = this.map(20,20);
//X軸的偏移量 之因此保存爲變量 是之後咱們作左右移動的是須要經過改變這個值來實現
this.x = 7;
//隨機生成一個方塊
this._block = this.block();
this.draw(this._block);
},
//地圖數據
map(r,c){
let data = [];
for(let i = 0; i < c; i++){
data.push([]);
//每行長度爲 r
data[i].length = r;
//全部元素默認值 0
data[i].fill(0);
}
return data;
},
//隨機生成一個類型的方塊
block(){
let index = Math.floor(Math.random()*7);
return this.blockType[index];
},
//方塊的類型
blockType: [
[[1,1,1,1]],
[[1,1],[1,1]],
[[1,1,0],[0,1,1]],
[[0,1,1],[1,1,0]],
[[0,1,0],[1,1,1]],
[[1,0,0],[1,1,1]],
[[0,0,1],[1,1,1]]
],
//重繪畫布
draw(block){
for (let i = 0; i < block.length; i++){
for (let j = 0; j < block[0].length; j++){
//要向x軸偏移 須要爲j加一個偏移量便可
this.data[i][j + this.x] = block[i][j];
}
}
console.table(this.data);
this.render(this.data);
},
//渲染
render(data){
//計算每一個方塊的寬高 方塊之間間隔爲4
let w = 500/20 - 4;
let h = 500/20 - 4;
//計算方塊的行列
let r = data.length;
let c = data[0].length;
for(let i = 0; i < r; i ++){
for (let j = 0; j < c; j++){
//判斷數組裏的值 若爲1 則渲染爲紅色 0 則渲染爲白色
this.gc.fillStyle = data[i][j] === 0 ? 'white' : 'red';
/*
* 座標算法
* 畫布寬度500 小方格寬度21 個數20 則 留下的空隙寬度爲 500 - 21*20 = 80 其中 20個小方塊可分4單位的間隙
* 20個方塊之間有19個間隙 剩下一個4單位的距離 分配到小方塊距離左右兩側的間隙
* 總結一下規律
* n行 x座標
* 0 w*0 + 2
* 1 w*1 + 2 + 4
* 2 w*2 + 2 + 4*2
* 3 w*3 + 2 + 4*3
* ...
* n w*n + 2 + 4*n
* 因此第j列的x座標能夠概括爲 (w+4)*j + 2
* y座標亦然
*/
this.gc.fillRect(
(w+4)*j+2,
(h+4)*i+2,
w,
h
);
}
}
}
};
Tetris.init();
複製代碼
刷新頁面會出現隨機的方塊以下圖:
方塊下落,其實就是要改變方塊在畫布上的Y軸位置,怎麼持續下落呢,很顯然須要開一個定時器。
[未完待續。。。]
[未完待續。。。]
[未完待續。。。]
[未完待續。。。]
[未完待續。。。]
[未完待續。。。]
[未完待續。。。]
最近時間比較緊,剩下模塊慢慢更新。大致思想了解以後,其餘問題均可以迎刃而解,無非就是須要耐心和不斷的調試。
喜歡就贊一波,給點更新的動力😆
最後廣而告之。 歡迎訪問人人貸大前端技術博客中心
裏面有關nodejs
react
reactNative
小程序 前端工程化等相關的技術文章陸續更新中,歡迎訪問和吐槽~