詳解前端俄羅斯方塊程式碼:HTML+JS+CANVAS實現原理
想寫個俄羅斯方塊的小遊戲,發現網上的各位大佬的程式碼,我看不明白。
好吧,其實我一直都看不懂別人的程式碼。
可是,flag已經立了,寫肯定是要寫的啦。
嗯……還是自力更生,自給自足。擼起袖子,說寫就寫。現在就說說我自己的經驗;
最終效果圖為:手指滑動螢幕即可。
俄羅斯方塊顯然比貪吃蛇難度大點。
但是
經過我的一步一步分析,我似乎看到了一條明朗的道路,現在我將我是如何一步一步的寫出這俄羅斯方塊的過程告訴你們吧。
看我正經臉
第一步:俄羅斯單方塊——shap【】 和 遊戲介面——all【】;
1.1:我們來分析一下,俄羅斯單個方塊有7種不同的樣子,以下我用Excel表格,畫出這7個型別。
1.2:其中,上色狀態為:0表示沒有顏色,1表示有顏色。這7個型別的俄羅斯單方塊,我暫且用shap陣列來表示他們
以下開始以shap來表示單方塊。例如:
所以,shap就有7種不同的值;
為什麼要是一個正方形呢?這樣就方便我們去旋轉改變shap的方向;
1.3:接著設定一下游戲的介面,是這一個寬400 * 高800 的canvas 畫布,其中小小方格的尺寸為20*20;
這個畫布是一張二維陣列的表,shap們會在這個表堆積起來。我將這個表叫all[ ]。
所以我們已經得出了,橫的有20個小方格,豎的有40個小方格;
預設每個小方格都是不上色的,例如all【1,1】=0表示 小小方格位置在【1,1】是沒有上色的;
//初始化堆積方塊
for (let i = 0; i < 40; i++) {
this.all[i] = new Array(0);
for (let j = 0; j < 20; j++) {
this.all[i][j] = 0;
}
}
根據上色的小方格,我們就可以知道all對應的值:
1.4:現在我們開始初始化,shap的各個形狀。
shap的【】每個值,也就是每個小方塊:都存3個數值,它們分別表示x座標,y座標,上色狀態(0或1);
- shap【i】【j】【0】---->x;
- shap【i】【j】【1】---->y;
- shap【i】【j】【2】---->上色狀態;
我們主要改變的是shap【i】【j】【2】的值,即上色狀態;
通過x和y我們就能得到小小方格在all的位置:(x/20,y/20 )
radomID:表示隨機的初始化第radomID種shap;因為shap肯定是要在整個all看不到的上面中間,所以200是預設的中間位置,y 必須設定成小於0;以下就是,shap【】的7種初始狀態:都在all畫布之上。
switch (this.radomID) {
case 0:
//長條
let defaultX = 200;
let defaultY = -60;
for (var i = 0; i < 5; i++) {
defaultX = 200;
if (i == 2) {
this.$set(this.shap, i, [
[defaultX, defaultY, 1],
[(defaultX += 20), defaultY, 1],
[(defaultX += 20), defaultY, 1],
[(defaultX += 20), defaultY, 1],
[(defaultX += 20), defaultY, 0]
]);
} else {
this.$set(this.shap, i, [
[defaultX, defaultY, 0],
[(defaultX += 20), defaultY, 0],
[(defaultX += 20), defaultY, 0],
[(defaultX += 20), defaultY, 0],
[(defaultX += 20), defaultY, 0]
]);
}
defaultY += 20;
}
console.log(this.shap)
break;
case 1:
//正方形
this.$set(this.shap, 0, [
[200, -40, 1],
[220, -40, 1]
]);
this.$set(this.shap, 1, [
[200, -20, 1],
[220, -20, 1]
]);
break;
case 2:
//正7
this.$set(this.shap, 0, [
[200, -60, 1],
[220, -60, 1],
[240, -60, 0]
]);
this.$set(this.shap, 1, [
[200, -40, 0],
[220, -40, 1],
[240, -40, 0]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 1],
[240, -20, 0]
]);
break;
case 3:
//反7
this.$set(this.shap, 0, [
[200, -60, 0],
[220, -60, 1],
[240, -60, 1]
]);
this.$set(this.shap, 1, [
[200, -40, 0],
[220, -40, 1],
[240, -40, 0]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 1],
[240, -20, 0]
]);
break;
case 4:
//正2
this.$set(this.shap, 0, [
[200, -60, 1],
[220, -60, 1],
[240, -60, 0]
]);
this.$set(this.shap, 1, [
[200, -40, 0],
[220, -40, 1],
[240, -40, 1]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 0],
[240, -20, 0]
]);
break;
case 5:
//反2
this.$set(this.shap, 0, [
[200, -60, 0],
[220, -60, 1],
[240, -60, 1]
]);
this.$set(this.shap, 1, [
[200, -40, 1],
[220, -40, 1],
[240, -40, 0]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 0],
[240, -20, 0]
]);
break;
case 6:
//土
this.$set(this.shap, 0, [
[200, -60, 0],
[220, -60, 1],
[240, -60, 0]
]);
this.$set(this.shap, 1, [
[200, -40, 1],
[220, -40, 1],
[240, -40, 1]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 0],
[240, -20, 0]
]);
break;
default:
break;
}
1.5:現在我們來寫一個每次出現隨機的shap的方法吧:radomShap()。
radomShap() {
this.scope += 1;
this.pain();
this.radomID = parseInt(Math.random() * 7);
// this.deleteShap();
this.shap.splice(0, this.shap.length); //清空存放單方塊的陣列
//shap陣列開始儲存不同單方塊
【這裡是隨機的shap初始狀態,上面已經展示過程式碼了,這裡就不在複製了】
this.rotate();
this.painShap();
},
每次隨機出現一個shap,就要執行這些操作;
- 分值就要加1分;
- 然後開始準備畫筆:pain()
- 隨機生成一個shap編號。
- 然後清空之前儲存過的shap【】資料,
- 開始儲存新的隨機shap形狀。
- 因為初始的shap形狀都是固定住的,這裡我們還要在出一個隨機的旋轉狀態的shap。這裡就使用rotate()方法來旋轉shap【】,這個時候shap【】的值也發生了變化;
- 變化後的shap【】,我們就可以開始繪製shap【】。這裡就使用painShap()方法;
pain():設定一下要畫的顏色黑色;
//繪製
pain() {
var c = document.getElementById("stage");
this.context = c.getContext("2d");
this.context.fillStyle = "#000000";
},
rotate():隨機0-3次shap;這裡我們可以先不看這個程式碼,下面我再解釋這個旋轉的問題;
painShap():繪製shap【】。當shap【i】【j】【2】==1的時候,我們就給它上色;為0我們就不管它;
painShap() {
for (var i = 0; i < this.shap.length; i++) {
for (var j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.fillRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
}
}
},
第二步:旋轉rotate();
現在我們就來講旋轉的這個問題;
2.1:如果要把一個shap,旋轉90度,有什麼變化呢?
沒錯,我們已經看出來,旋轉90度,就是把行變列,列變行。我們只要把上色狀態改變一下就可以了。
for (let i = 0; i < this.shap.length; i++) {
for (let j = this.shap[i].length - 1; j >= 0; j--) {
shap1[i][j][2] = this.shap[this.shap[i].length - 1 - j][i][2];
}
}
那現在我們就可以開始寫rotate()隨機旋轉的方法了:
//旋轉90度的方法
rotate() {
var changge = parseInt(Math.random() * 4);
console.log("變" + changge + "次")
for (i = 0; i <= changge; i++) {
let shap1 = [];
// 深拷貝
shap1 = JSON.parse(JSON.stringify(this.shap));
//行變列。列變行,把結果先存在shap1裡;
【行變列。列變行,把結果先存在shap1裡。這裡上面已經展示過程式碼了,就不復制了。】
}
this.deleteShap();
//那麼就把shap1的值賦給shap
this.shap = JSON.parse(JSON.stringify(shap1));
this.painShap();
}
},
deleteShap()方法:將最初shap【】的上色小方塊給清理掉。
因為每次變化shap【】我們就要清除原來最初的shap【】。
清理完之後,才用painShap方法繪製新的shap【】
//清除單方塊形狀
deleteShap() {
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
}
}
},
2.2:我們還得再寫一個單次旋轉不隨機的方法:change()。
因為玩遊戲的時候,肯定不能隨機旋轉吧,肯定是按照順時針一步一步來旋轉改變shap【】的,所以就有了change()。這個時候旋轉是不受控制的。
我們在旋轉過程中,就要考慮到這幾個問題:
- 旋轉的時候,shap上次的部分不能超出all畫布:x座標的範圍必須在0-380之間;y座標必須小於780;為啥y可以小於0?因為我初始的時候shap的y座標就已經小於0了哦~
- 旋轉的時候,shap不能碰到已經堆積好的方塊(all【i】【j】=1)。
先插入一個廣告:
這個小方塊的x,y表示的是左上角的座標。所以x在all的範圍是0-380;y是0-780。一下就是判斷的方法;
for (let i = 0; i < shap1.length; i++) {
for (let j = 0; j < shap1[i].length; j++) {
try {
if (
((shap1[i][j][0] < 0 || shap1[i][j][0] > 380||shap1[i][j][1] > 780) && shap1[i][j][2] == 1) || (this.all[this.shap[i][j][1] / 20][this.shap[i][j][0] / 20] == 1)) {
//兩個同時成立退出;
return;
}
} catch (error) {
}
}
}
這樣我們就可以開始寫change()了。
change() {
let shap1 = [];
// 深拷貝
shap1 = JSON.parse(JSON.stringify(this.shap));
//行變列。列變行,把結果先存在shap1裡;
【行變列。列變行,把結果先存在shap1裡。這裡上面已經展示過程式碼了,就不復制了。】
//判斷一下,改變方向以後,會不會超出牆||碰到堆積好的方塊;
【改變方向以後,會不會超出牆||碰到堆積好的方塊;這裡上面已經展示過程式碼了,就不復制了。】
this.deleteShap();
//那麼就把shap1的值賦給shap
this.shap = JSON.parse(JSON.stringify(shap1));
this.painShap();
}
}
第三步:移動:左移動,右移動,下移動;
3.1:左右移動 left()和right():每向左右移動一次,x座標都要+-20;
以左移動為例:每次移動都要清除原來的上色shap【】哦~
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
this.shap[i][j][0] -= 20;
}
}
但是我們要考慮的問題就有:
- x再怎麼加都不能超過380;再怎麼減都不能少於0;
- 每次左右移動的時候,如果碰到已經堆積好的方塊們,就不能再左右移動了。
現在我們就可以寫左移動的方法:left():
// 向左
left() {
//判斷左移動的過程中是否會與下一層堆積好的方塊重疊;是否會超過範圍;如果會的話,就開始把shap加入this.all然後退出;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
try {
//因為x可能會等於0;所以用try,catch過濾掉好了。不想管。。。。
if (
(this.shap[i][j][0] < 20 || this.all[y][x - 1] == 1) && this.shap[i][j][2] == 1
) {
//左邊有東西||或者靠牆了。不要向左了。
return;
}
} catch (error) {
// console.log("有bug")
}
}
}
//經過兩個判斷結束以後,沒有符合,繼續向左移動
【左移動,上面已經展示過程式碼,不在複製】
this.painShap();
},
左右程式碼同理;此處就不再展示了。
3.2:下降down():向下,y座標就是+20;
然而在下降的過程中,我們要考慮什麼問題呢?
- 我下降的時候,不能再下降了,還能不能繼續下降?
- 我下降的時候,會不會超出y座標會不會超過780?或者碰到了,已經堆積好的方塊們,要怎麼辦?
- 我下降的時候,滿格了要怎麼辦?
現在我們就來一一解決這些問題。
解決這些問題,那麼下降down()的方法也就出來來。
第一個問題:當紅框要繼續下降的話,這個時候,遊戲就應該結束了。因為接下去新的shap的第二層就放不到all裡。
所以我們我們可以看到all【0】,也就是畫布的第一行橙色框框所示,只要已經存在上色的小方塊。那麼遊戲就可以結束了。
然後我們就清空分值,清空all。清空整個400*800上色過的畫布;
for (let i = 0; i < this.all[0].length; i++) {
if (this.all[0][i] == 1) {
alert("遊戲結束");
this.scope = 0;
this.all.splice(0, this.all.length); //清空存放單方塊的陣列
//清空整個畫布
this.context.clearRect(0, 0, 400, 800);
return;
}
}
第二個問題:在下降的過程中,如果碰到已經上色過的all【】,或者y要超過780的時候,那麼shap【】就要停止下降了。
然後計算 不再下降的shap的位置 得到有顏色的小方塊的位置,給all裡對應的小方塊賦值為1;
//判斷下降過程中是否會與下一層堆積好的方塊重疊;是否會超過範圍;如果會的話,就開始把shap加入this.all然後退出;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
try {
if (
(this.shap[i][j][1] >= 780 || this.all[y + 1][x] == 1) && this.shap[i][j][2] == 1
) {
//會的話,那就就開始把shap加入this。all;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
this.all[y][x] = 1;
}
}
}
//放完以後退出
return;
}
} catch (error) {
}
}
}
第三個問題:如果下降的過程中滿行了。那麼我們就要清空這一整行的小方塊們;
- 把滿行的小方塊的清空以後,再清空現在螢幕上所有的堆積方塊。this.deletAll();
deletAll() {
for (let i = 0; i < this.all.length; i++) {
for (let j = 0; j < this.all[i].length; j++) {
if (this.all[i][j] == 1) {
this.context.clearRect(
j * 20,
i * 20,
20,
20
);
}
}
}
},
- 這個時候整個all的值就長成這個樣子了。被清空的我用黃色框框起來了。
- 被清空的部分,需要被清空行上面的集體往下移動,達到這個效果:這個方法就為allDown();程式碼如下:
我們從最底層往上遍歷,只要有一層有一個上色的小方塊,我就continue;
直到這一行都是為空,那麼這行的上面所有行集體往下移動;依次類推;
最後,要記得最頂層all【0】要預設值全為0;
allDown() {
for (let i = this.all.length - 1; i >= 0; i--) {
var num = 0; //一整行填滿的方塊數量
for (let j = 0; j < this.all[i].length; j++) {
//如果沒有
if (this.all[i][j] == 1) {
//只有這一行有存在1的那我就不管了。
continue; //跳出本次迴圈;
} else {
num++;
//假如這一行都是為空的話。那麼開始,這行以上的全部集體往下移動;
if (num == this.allLength) {
this.allLength = this.all[0].length;
var a0 = JSON.parse(JSON.stringify(this.all[0]));
if (i - 1 >= 0) {
for (let k = i - 1; k >= 0; k--) {
this.all[k + 1] = this.all[k];
}
this.all[0] = a0;
}
}
}
}
}
},
- 集體往下移動以後,那就在重繪堆積的方塊——painAll():
//重繪堆積好的方塊;
painAll() {
for (let i = 0; i < this.all.length; i++) {
for (let j = 0; j < this.all[i].length; j++) {
if (this.all[i][j] == 1) {
this.context.fillRect(
j * 20,
i * 20,
20,
20
);
} else {
this.context.clearRect(
j * 20,
i * 20,
20,
20
);
}
}
}
}
- 最後我們就可以完整的寫出滿格的方法了:滿格了那分數就加10分吧。恭喜恭喜~得了10分;
//先判斷是否滿格了,滿格就退出;
for (let i = 0; i < this.all.length; i++) {
var num = 0; //一整行填滿的方塊數量
for (let j = 0; j < this.all[i].length; j++) {
//如果沒有
if (this.all[i][j] != 1) {
continue;
} else {
num++;
if (num == this.allLength) {
for (let j = 0; j < this.all[i].length; j++) {
this.context.clearRect(
j * 20,
i * 20,
20,
20
);
this.all[i][j] = 0;
}
//把滿的清空以後。
//清空現在螢幕上所有的堆積方塊
this.deletAll();
//填滿清空的區域;
this.allDown();
//重繪
this.painAll();
this.radomShap();
this.scope += 10;
continue;
}
}
}
}
好了,親愛的朋友們,寫到這裡我已經心力交瘁了,很快,我們馬上就要迎來最終down方法了。
在下降之前,我們只要判斷一下是否存在剛才的那3個問題。如果不存在的話,ok。咱們可以往下移動了~
//向下
down() {
【判斷第一個問題是否存在的程式碼:存在,那咱們return吧】
【判斷第二個問題是否存在程式碼:存在,那咱們return吧】
【判斷第三個問題是否存在程式碼:存在,那咱們return吧】
//經過3個問題的判斷以後,沒有符合,那麼我們繼續向下移動
{
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
this.shap[i][j][1] += 20;
}
}
this.painShap();
}
},
開森,終於寫完了耶~~
最後還是說一下:
這個俄羅斯方塊,確實還是存在一些bug。(好吧,不是一些,是很多。)
比如:當下降的時候,我一直讓shap旋轉,於是……我的shap直接就超過all這個畫布了。然後就拜拜了。。。。這個問題我也不知道是什麼問題。沒解決好。如果你們發現了。可以跟我說下哦~
其他的我還沒發現|||
還有就是……我的程式碼,用了不知道多少個for迴圈。。這肯定不是一個好的方法。尷尬了。。。。。
然而我只會用最土的方法。
寫到這裡,咱們俄羅斯方塊的各種方法已經完全結束了。就可以看著去運用這些方法啦~
感謝您的來訪。謝謝。
第一次如此熱誠的寫下技術分析貼。語言稍有不通,如果你哪裡沒有看明白,請你留言,看到留言後,我會馬上回復。
本人小白菜,歡迎留言~
再次最後:
我的程式碼主要有一下這些方法。
以下是完整js程式碼:
var startx, starty;
//獲得角度
function getAngle(angx, angy) {
return (Math.atan2(angy, angx) * 180) / Math.PI;
}
//根據起點終點返回方向 1向上 2向下 3向左 4向右 0未滑動
function getDirection(startx, starty, endx, endy) {
var angx = endx - startx;
var angy = endy - starty;
var result = 0;
//如果滑動距離太短
if (Math.abs(angx) < 2 && Math.abs(angy) < 2) {
return result;
}
var angle = getAngle(angx, angy);
if (angle >= -135 && angle <= -45) {
result = 1;
} else if (angle > 45 && angle < 135) {
result = 2;
} else if (
(angle >= 135 && angle <= 180) ||
(angle >= -180 && angle < -135)
) {
result = 3;
} else if (angle >= -45 && angle <= 45) {
result = 4;
} else {
result = 0;
}
return result;
}
//手指接觸螢幕
document.addEventListener(
"touchstart",
function (e) {
startx = e.touches[0].pageX;
starty = e.touches[0].pageY;
},
false
);
//手指離開螢幕
let timer = null;
//速度控制
document.addEventListener(
"touchend",
function (e) {
if (app.gameState == 0) {
return;
}
var endx, endy;
endx = e.changedTouches[0].pageX;
endy = e.changedTouches[0].pageY;
var direction = getDirection(startx, starty, endx, endy);
clearInterval(timer);
if (direction == 0) {
clearInterval(timer);
}
if (direction == 1) {
app.change();
timer = setInterval(() => {
app.down();
}, app.v)
}
app.downV = 100;
if (direction == 2) {
timer = setInterval(() => {
app.down();
}, app.downV)
}
if (direction == 3) {
app.left();
timer = setInterval(() => {
app.down();
}, app.v)
}
if (direction == 4) {
app.right();
timer = setInterval(() => {
app.down();
}, app.v)
}
},
false
);
var app = new Vue({
el: "#teris",
data: {
scope: 0,
v: 300,
downV: 100,
radomID: null, //影象編號
shap: [], //存放圖形
all: new Array(), //存放所有已經內容
context: null, //畫布
rotateID: 0, //旋轉的狀態,
allLength: null,
gameState: 0
},
methods: {
begin() {
if (this.gameState != 0) {
//遊戲結束狀態
$("#title").html("");
$("#title").html("遊戲開始");
this.gameState = 0;
this.scope = 0;
this.all.splice(0, this.all.length); //清空存放單方塊的陣列
//清空整個畫布
this.context.clearRect(0, 0, 400, 800);
}
//遊戲開始狀態
else {
//初始化堆積方塊
for (let i = 0; i < 40; i++) {
this.all[i] = new Array(0);
for (let j = 0; j < 20; j++) {
this.all[i][j] = 0;
}
}
this.allLength = this.all[0].length;
//開始繪製單方塊;
//清空整個畫布
this.context.clearRect(0, 0, 400, 800);
this.radomShap();
this.gameState = 1;
//遊戲開始,預設自己往下移動;
timer = setInterval(() => {
this.down();
}, this.v)
$("#title").html("");
$("#title").html("遊戲結束");
}
},
//繪製
pain() {
var c = document.getElementById("stage");
this.context = c.getContext("2d");
this.context.fillStyle = "#000000";
},
//繪製圖像
painShap() {
for (var i = 0; i < this.shap.length; i++) {
for (var j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.fillRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
}
}
},
//隨機繪製單方塊
radomShap() {
this.scope += 1;
this.pain();
this.radomID = parseInt(Math.random() * 7);
// this.deleteShap();
this.shap.splice(0, this.shap.length); //清空存放單方塊的陣列
//shap陣列開始儲存不同單方塊
switch (this.radomID) {
case 0:
//長條
let defaultX = 200;
let defaultY = -60;
for (var i = 0; i < 5; i++) {
defaultX = 200;
if (i == 2) {
this.$set(this.shap, i, [
[defaultX, defaultY, 1],
[(defaultX += 20), defaultY, 1],
[(defaultX += 20), defaultY, 1],
[(defaultX += 20), defaultY, 1],
[(defaultX += 20), defaultY, 0]
]);
} else {
this.$set(this.shap, i, [
[defaultX, defaultY, 0],
[(defaultX += 20), defaultY, 0],
[(defaultX += 20), defaultY, 0],
[(defaultX += 20), defaultY, 0],
[(defaultX += 20), defaultY, 0]
]);
}
defaultY += 20;
}
console.log(this.shap)
break;
case 1:
//正方形
this.$set(this.shap, 0, [
[200, -40, 1],
[220, -40, 1]
]);
this.$set(this.shap, 1, [
[200, -20, 1],
[220, -20, 1]
]);
break;
case 2:
//正7
this.$set(this.shap, 0, [
[200, -60, 1],
[220, -60, 1],
[240, -60, 0]
]);
this.$set(this.shap, 1, [
[200, -40, 0],
[220, -40, 1],
[240, -40, 0]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 1],
[240, -20, 0]
]);
break;
case 3:
//反7
this.$set(this.shap, 0, [
[200, -60, 0],
[220, -60, 1],
[240, -60, 1]
]);
this.$set(this.shap, 1, [
[200, -40, 0],
[220, -40, 1],
[240, -40, 0]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 1],
[240, -20, 0]
]);
break;
case 4:
//正2
this.$set(this.shap, 0, [
[200, -60, 1],
[220, -60, 1],
[240, -60, 0]
]);
this.$set(this.shap, 1, [
[200, -40, 0],
[220, -40, 1],
[240, -40, 1]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 0],
[240, -20, 0]
]);
break;
case 5:
//反2
this.$set(this.shap, 0, [
[200, -60, 0],
[220, -60, 1],
[240, -60, 1]
]);
this.$set(this.shap, 1, [
[200, -40, 1],
[220, -40, 1],
[240, -40, 0]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 0],
[240, -20, 0]
]);
break;
case 6:
//土
this.$set(this.shap, 0, [
[200, -60, 0],
[220, -60, 1],
[240, -60, 0]
]);
this.$set(this.shap, 1, [
[200, -40, 1],
[220, -40, 1],
[240, -40, 1]
]);
this.$set(this.shap, 2, [
[200, -20, 0],
[220, -20, 0],
[240, -20, 0]
]);
break;
default:
break;
}
this.rotate();
this.painShap();
},
//清除單方塊形狀
deleteShap() {
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
}
}
},
//旋轉90度的方法
rotate() {
var changge = parseInt(Math.random() * 4);
console.log("變" + changge + "次")
for (i = 0; i <= changge; i++) {
let shap1 = [];
// 深拷貝
shap1 = JSON.parse(JSON.stringify(this.shap));
//行變列。列變行,把結果先存在shap1裡;
for (let i = 0; i < this.shap.length; i++) {
for (let j = this.shap[i].length - 1; j >= 0; j--) {
shap1[i][j][2] = this.shap[this.shap[i].length - 1 - j][i][2];
}
}
// //判斷一下,改變方向以後,會不會超出牆||碰到堆積好的方塊;
// for (let i = 0; i < shap1.length; i++) {
// for (let j = 0; j < shap1[i].length; j++) {
// try {
// if (
// ((shap1[i][j][0] < 0 || shap1[i][j][0] > 380||shap1[i][j][1] > 780) && shap1[i][j][2] == 1) || (this.all[this.shap[i][j][1] / 20][this.shap[i][j][0] / 20] == 1)) {
// //兩個同時成立退出;
// return;
// }
// } catch (error) {
// }
// }
// }
this.deleteShap();
//那麼就把shap1的值賦給shap
this.shap = JSON.parse(JSON.stringify(shap1));
this.painShap();
}
},
change() {
let shap1 = [];
// 深拷貝
shap1 = JSON.parse(JSON.stringify(this.shap));
//行變列。列變行,把結果先存在shap1裡;
for (let i = 0; i < this.shap.length; i++) {
for (let j = this.shap[i].length - 1; j >= 0; j--) {
shap1[i][j][2] = this.shap[this.shap[i].length - 1 - j][i][2];
}
}
//判斷一下,改變方向以後,會不會超出牆||碰到堆積好的方塊;
for (let i = 0; i < shap1.length; i++) {
for (let j = 0; j < shap1[i].length; j++) {
try {
if (
((shap1[i][j][0] < 0 || shap1[i][j][0] > 380||shap1[i][j][1] > 780) && shap1[i][j][2] == 1) || (this.all[this.shap[i][j][1] / 20][this.shap[i][j][0] / 20] == 1)) {
//兩個同時成立退出;
return;
}
} catch (error) {
}
}
}
this.deleteShap();
//那麼就把shap1的值賦給shap
this.shap = JSON.parse(JSON.stringify(shap1));
this.painShap();
},
//向下
down() {
//到頭了。就自殺了。
for (let i = 0; i < this.all[0].length; i++) {
if (this.all[0][i] == 1) {
alert("遊戲結束");
clearInterval(timer);
$("#title").html("");
$("#title").html("遊戲開始");
this.gameState = 0;
this.scope = 0;
this.all.splice(0, this.all.length); //清空存放單方塊的陣列
//清空整個畫布
this.context.clearRect(0, 0, 400, 800);
return;
}
}
//判斷下降過程中是否會與下一層堆積好的方塊重疊;是否會超過範圍;如果會的話,就開始把shap加入this.all然後退出;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
try {
if (
(this.shap[i][j][1] >= 780 || this.all[y + 1][x] == 1) && this.shap[i][j][2] == 1
) {
//會的話,那就就開始把shap加入this。all;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
this.all[y][x] = 1;
}
}
}
//放完以後退出
this.radomShap();
clearInterval(timer);
timer = setInterval(() => {
this.down();
}, this.v)
console.log(this.all)
return;
}
} catch (error) {
// console.log("y有bug")
}
}
}
//先判斷是否滿格了,滿格就退出;
for (let i = 0; i < this.all.length; i++) {
var num = 0; //一整行填滿的方塊數量
for (let j = 0; j < this.all[i].length; j++) {
//如果沒有
if (this.all[i][j] != 1) {
continue;
} else {
num++;
if (num == this.allLength) {
for (let j = 0; j < this.all[i].length; j++) {
this.context.clearRect(
j * 20,
i * 20,
20,
20
);
this.all[i][j] = 0;
}
//把滿的清空以後。
//清空現在螢幕上所有的堆積方塊
this.deletAll();
//填滿清空的區域;
this.allDown();
//重繪
this.painAll();
this.radomShap();
this.scope += 10;
continue;
}
}
}
}
//經過兩個判斷結束以後,沒有符合,繼續向下移動
{
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
this.shap[i][j][1] += 20;
}
}
this.painShap();
}
},
// 向左
left() {
//判斷左移動的過程中是否會與下一層堆積好的方塊重疊;是否會超過範圍;如果會的話,就開始把shap加入this.all然後退出;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
try {
//因為x可能會等於0;所以用try,catch過濾掉好了。不想管。。。。
if (
(this.shap[i][j][0] < 20 || this.all[y][x - 1] == 1) && this.shap[i][j][2] == 1
) {
//左邊有東西||或者靠牆了。不要向左了。
return;
}
} catch (error) {
// console.log("有bug")
}
}
}
//經過兩個判斷結束以後,沒有符合,繼續向左移動
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
this.shap[i][j][0] -= 20;
}
}
this.painShap();
},
// 向右
right() {
//判斷左移動的過程中是否會與右邊堆積好的方塊重疊;是否會超過範圍;如果會的話退出;
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
var y = this.shap[i][j][1] / 20;
var x = this.shap[i][j][0] / 20;
try {
//因為x可能會等於0;所以用try,catch過濾掉好了。不想管。。。。
if (
(this.shap[i][j][0] > 360 || this.all[y][x + 1] == 1) && this.shap[i][j][2] == 1
) {
return;
}
} catch (error) {
// console.log("x太大")
}
}
}
//經過兩個判斷結束以後,沒有符合,繼續向右移動
for (let i = 0; i < this.shap.length; i++) {
for (let j = 0; j < this.shap[i].length; j++) {
if (this.shap[i][j][2] == 1) {
this.context.clearRect(
this.shap[i][j][0],
this.shap[i][j][1],
20,
20
);
}
this.shap[i][j][0] += 20;
}
}
this.painShap();
},
deletAll() {
for (let i = 0; i < this.all.length; i++) {
for (let j = 0; j < this.all[i].length; j++) {
if (this.all[i][j] == 1) {
this.context.clearRect(
j * 20,
i * 20,
20,
20
);
}
}
}
},
//整體堆積好的方塊往下;
allDown() {
for (let i = this.all.length - 1; i >= 0; i--) {
var num = 0; //一整行填滿的方塊數量
for (let j = 0; j < this.all[i].length; j++) {
//如果沒有
if (this.all[i][j] == 1) {
//只有這一行有存在1的那我就不管了。
continue; //跳出本次迴圈;
} else {
num++;
//假如這一行都是為空的話。那麼開始,這行以上的全部集體往下移動;
if (num == this.allLength) {
this.allLength = this.all[0].length;
var a5 = JSON.parse(JSON.stringify(this.all[0]));
if (i - 1 >= 0) {
for (let k = i - 1; k >= 0; k--) {
this.all[k + 1] = this.all[k];
}
this.all[0] = a5;
}
}
}
}
}
},
//重繪堆積好的方塊;
painAll() {
for (let i = 0; i < this.all.length; i++) {
for (let j = 0; j < this.all[i].length; j++) {
if (this.all[i][j] == 1) {
this.context.fillRect(
j * 20,
i * 20,
20,
20
);
} else {
this.context.clearRect(
j * 20,
i * 20,
20,
20
);
}
}
}
}
},
mounted() {
var c = document.getElementById("stage");
this.context = c.getContext("2d");
}
});