1. 程式人生 > >詳解前端俄羅斯方塊程式碼:HTML+JS+CANVAS實現原理

詳解前端俄羅斯方塊程式碼: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. 分值就要加1分;
  2. 然後開始準備畫筆:pain()
  3. 隨機生成一個shap編號。
  4. 然後清空之前儲存過的shap【】資料,
  5. 開始儲存新的隨機shap形狀。
  6. 因為初始的shap形狀都是固定住的,這裡我們還要在出一個隨機的旋轉狀態的shap。這裡就使用rotate()方法來旋轉shap【】,這個時候shap【】的值也發生了變化;
  7. 變化後的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()。這個時候旋轉是不受控制的。

我們在旋轉過程中,就要考慮到這幾個問題:

  1. 旋轉的時候,shap上次的部分不能超出all畫布:x座標的範圍必須在0-380之間;y座標必須小於780;為啥y可以小於0?因為我初始的時候shap的y座標就已經小於0了哦~
  2. 旋轉的時候,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;
        }
      }

但是我們要考慮的問題就有:

  1. x再怎麼加都不能超過380;再怎麼減都不能少於0;
  2. 每次左右移動的時候,如果碰到已經堆積好的方塊們,就不能再左右移動了。

現在我們就可以寫左移動的方法: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;

然而在下降的過程中,我們要考慮什麼問題呢?

  1. 我下降的時候,不能再下降了,還能不能繼續下降?
  2. 我下降的時候,會不會超出y座標會不會超過780?或者碰到了,已經堆積好的方塊們,要怎麼辦?
  3. 我下降的時候,滿格了要怎麼辦?

現在我們就來一一解決這些問題。

解決這些問題,那麼下降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");
  }
});