Processingでテトリスをつくってみる

#背景
Processingでテトリスを作ろうとして日本語で参考にできそうなサイトがあまりないので、つくることにしました。

以下の動画を参考
[Coder un Tetris en 30 minutes](https://www.youtube.com/watch?v=1TQvm8MmkGI&feature=share)

###環境
Processing 3.3.7
MacOS High Sierra バージョン 10.13.5

#コード

```java:processing.tetris
//２次元配列を用意する
//x:0~9, y:0~19
int Grid[][] = new int[10][20];

Tetromino current = new Tetromino();//クラスの宣言 currentという名前のTetrominoクラス
int initPosX = 4;
int initPosY = 0;

//buffer 記憶変数
int timeBuff = 0;
int fallSpeed = 40;

int score = 0;

int blockSize = 13;

void setup(){
  size(200, 375);
  
  //テキストのフォント設定
  textFont(createFont("Courier", 20));
  //テキストの表示位置設定
  textAlign(CENTER,CENTER);
  
  current = new Tetromino();//クラスの作成
  
  initPosX = 4;
  initPosY = 0;
}

void keyPressed(){
  if(keyCode == 37){//←のコード
    boolean stop = false;
    
    for(int i = 0; i < 4; i++){
      if((current.getShapeX(i) + initPosX) > 0){
        if((current.getShapeY(i) + initPosY) > 0 && current.getShapeY(i) + initPosY < 19){
          if(Grid[(current.getShapeX(i) + initPosX -1)][(current.getShapeY(i) + initPosY)] != 0){  //←方向のブロックに色がついてたら
            stop = true;
          }
        }
      }
      else stop = true;
    }
    //←方向に移動
    if(!stop) initPosX--;
  }
  
  //回転
  if(keyCode == 38){//↑のコード
    boolean rotFlag = false;
    boolean minFlag = false;
    boolean maxFlag = false;
    int count = 0;
    int countMin = 0;
    int countMax = 0;
    
      for(int i = 0; i < 4; i++){
        if(current.col != 4){
          if(((current.getShapeY(i) + initPosY)>0) && ((current.getShapeY(i) + initPosY) < 19)){  //上にはみ出していない時
            if(((current.getRotShapeX(i) + initPosX) >= 0) && ((current.getRotShapeX(i) + initPosX) < 10)){  //回転した時にはみ出していない時
              if(Grid[(current.getRotShapeX(i) + initPosX)][(current.getRotShapeY(i) + initPosY)] == 0 || Grid[(current.getRotShapeX(i) + initPosX)][(current.getRotShapeY(i) + initPosY)] == current.col){
                count++;
              }
            }
          }
          if((current.getRotShapeX(i) + initPosX) < 0) minFlag = true;
          if((current.getRotShapeX(i) + initPosX) > 9) maxFlag = true;
        }
      }
      
      if(minFlag == true){
        for(int i = 0; i < 4; i++){
        if(((current.getShapeY(i) + initPosY)>0) && ((current.getShapeY(i) + initPosY) < 19)){  //上にはみ出していない時
            if(Grid[(current.getRotShapeX(i) - current.getRotMinX())][(current.getRotShapeY(i) + initPosY)] == 0 || Grid[(current.getRotShapeX(i) - current.getRotMinX())][(current.getRotShapeY(i) + initPosY)] == current.col){
              countMin++;
            }
          }
        }
      }
      
      if(maxFlag == true){
        for(int i = 0; i < 4; i++){
        if(((current.getShapeY(i) + initPosY)>0) && ((current.getShapeY(i) + initPosY) < 19)){  //上にはみ出していない時
            if(Grid[(current.getRotShapeX(i) - current.getRotMaxX() + 9)][(current.getRotShapeY(i) + initPosY)] == 0 || Grid[(current.getRotShapeX(i) - current.getRotMaxX() + 9)][(current.getRotShapeY(i) + initPosY)] == current.col){
              countMax++;
            }
          }
        }
      }
      
      if(count >3) rotFlag = true;
      if(countMin >3) rotFlag = true;
      if(countMax >3) rotFlag = true;
      
    if(current.col != 4){//正方形のブロックは回転させない
      if(rotFlag == true){
        current.rot();
        //壁際で回転した時の処理
       if((current.getMinX() + initPosX) < 0) initPosX -= (current.getMinX() + initPosX);
       if((current.getMaxX() + initPosX) > 9) initPosX -= (current.getMaxX() + initPosX -9);
       if((current.getMinY() + initPosY) < 0) initPosY -= (current.getMinY() + initPosY);
       if((current.getMaxY() + initPosY) > 19) initPosY -= (current.getMaxY() + initPosY -19);
      }
    }
  }
  
  if(keyCode == 39){//→のコード
  boolean stop = false;
    
    for(int i = 0; i < 4; i++){
      if(current.getShapeX(i) + initPosX < 9){
        if((current.getShapeY(i) + initPosY) > 0 && current.getShapeY(i) + initPosY < 19){
          if(Grid[current.getShapeX(i) + initPosX +1][current.getShapeY(i) + initPosY] != 0){  //→方向のブロックに色がついてたら
            stop = true;
          }
        }
      }
      else stop = true;
    }  
      //→方向に移動
      if(!stop) initPosX++;
  }
  if(keyCode == 40){//↓のコード
    score++;
    initPosY++;
  }
}

void draw(){
  background(0);
  noStroke();
  for(int i = 0; i < 10; i++){
    for(int k = 0; k < 20; k++){
      if(Grid[i][k] == 0) fill(100);
      else if(Grid[i][k] == 1) fill(50, 150, 255);
      else if(Grid[i][k] == 2) fill(0, 0, 255);
      else if(Grid[i][k] == 3) fill(255, 150, 0);
      else if(Grid[i][k] == 4) fill(255, 255, 0);
      else if(Grid[i][k] == 5) fill(100, 255, 0);
      else if(Grid[i][k] == 6) fill(150, 0, 150);
      else fill(255, 0, 0);
      //15pxごとに13pxの正方形を描く
      rect(i * 15 + 25, k *15 + 25, blockSize,blockSize);
    }
  }
  
  //
  displayCurrent();
  
  timeBuff++;
  
  //何フレーム毎に落ちるか設定
  if(timeBuff > fallSpeed){
    timeBuff = 0;
    initPosY++;
  }
  
  verifyGrid();
  
  text("Score : " + score, width/2, 345);
}


//------------------------------------------------------------------------------
//ここから関数とクラス

void lost(){
  fill(0,150);
  rect(0, 0, width, height);
  fill(255);
  text("Score : " + score, 100, 345);
  noLoop();//これでプログラム終了にする
}

void verifyGrid(){
  boolean stop = false;
  
   //stopするかの判定
   for(int i = 0; i < 4; i++){
     if((current.getShapeY(i)+initPosY) <19){
       if(Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY + 1)] != 0){ 
         stop = true;
       }
     }
      if(current.getShapeY(i) + initPosY == 19){
        stop = true;
      }
   }
   
   
   if(stop){
     //グリッドの色を塗り替えるか終了
     for(int i = 0; i < 4; i++){
       if((current.getShapeY(i)+initPosY) < 0){
         lost();
       }
       else Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY)] = current.getColor();
     }
     
     int count = 0;
     
     //横一列に色がついていたら、ブロックを消す
     for(int y = 0; y < 20; y++){
       boolean destroy = true;
       
       for(int x = 0; x < 10; x++){
         if(Grid[x][y] == 0) destroy = false;
       }
       
       if(destroy){
         count++;
         for(int y2 = y-1; y2 > -1; y2--){
           for(int x = 0; x < 10; x++){
             Grid[x][y2+1] = Grid[x][y2];
           }
         }
       }
     }
     
     if(count > 0){
     if(count == 1) score += 40;
     else if(count == 2) score += 100;
     else if(count == 3) score += 300;
     else score += 1200;
   }
   
   //再設定
  current = new Tetromino();//クラスの作成
  //初期位置に戻す
  initPosX = 4;
  initPosY = 0;
  
    for(int i = 0; i < 4; i++){
      if(current.getShapeY(i) + initPosY < 19){
        if((current.getShapeY(i) + initPosY) > 0 && current.getShapeY(i) + initPosY < 19){
          if(Grid[current.getShapeX(i) + initPosX][current.getShapeY(i) + initPosY + 1] != 0){  //→方向のブロックに色がついてたら
            lost();
          }
        }
      }
   }
   
  }//ここまでif(stop)
}

void displayCurrent(){
  if(current.getColor() == 0) fill(100);
  else if(current.getColor() == 1) fill(50, 150, 255);
  else if(current.getColor() == 2) fill(0, 0, 255);
  else if(current.getColor() == 3) fill(255, 150, 0);
  else if(current.getColor() == 4) fill(255, 255, 0);
  else if(current.getColor() == 5) fill(100, 255, 0);
  else if(current.getColor() == 6) fill(150, 0, 150);
  else fill(255, 0, 0);
  
  for(int i = 0; i < 4; i++){
    rect((current.getShapeX(i) + initPosX)*15 + 25, (current.getShapeY(i) + initPosY)*15 + 25, blockSize, blockSize);
  }
}


class Tetromino{
  int shape[][] = new int[4][2];
  //colはcolorのこと
  //col = 0は背景色
  int col = 0;
  
  public Tetromino(){
    //ランダムに色を選ぶ
    col = int(random(1, 8));//1~7まで
      if(col == 1){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = -2;
      }
      else if(col == 2){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = -1;
        shape[3][1] = -1;
      }
      else if(col == 3){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = 1;
        shape[3][1] = -1;
      }
      else if(col == 4){
        shape[1][0] = 1;
        shape[2][1] = 1;
        shape[3][0] = 1;
        shape[3][1] = 1;
      }
      else if(col == 5){
        shape[1][0] = -1;
        shape[2][1] = -1;
        shape[3][0] = 1;
        shape[3][1] = -1;
      }
      else if(col == 6){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][1] = -1;
      }
      else{
        shape[1][0] = 1;
        shape[2][1] = -1;
        shape[3][0] = -1;
        shape[3][1] = -1;
      }
  }
  
  //左に90度回転
  public void rot(){
    for(int i = 0; i < 4; i++){
      int buff = shape[i][0];
      shape[i][0] = shape[i][1];
      shape[i][1] = -buff;
    }
  }
  public int getShapeX(int i){
    return shape[i][0];
  }
  public int getRotShapeX(int i){
    return shape[i][1];
  }
  public int getShapeY(int i){
    return shape[i][1];
  }
    public int getRotShapeY(int i){
    return -shape[i][0];
  }
  public int getMaxX(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][0] > shape[i][0]) i = k;
    }
    return shape[i][0];
  }
  public int getRotMaxX(){
    int i = 0;
    for(int k = 1; k < 4; k++){
      if(shape[k][1] > shape[i][1]) i = k;
    }
    return shape[i][1];
  }
  public int getMinX(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][0] < shape[i][0]) i = k;
      }
      return shape[i][0];
    }
    
  public int getRotMinX(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][1] < shape[i][1]) i = k;
      }
      return shape[i][1];
    }
    
  public int getMaxY(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][1] > shape[i][1]) i = k;
    }
    return shape[i][1];
  }
  public int getMinY(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][1] < shape[i][1]) i = k;
    }
    return shape[i][1];
  }
  
  public int getColor(){
    return col;
  }
  
}
```

#コードの解説

##グリッドの描写
```java:draw
for(int i = 0; i < 10; i++){
    for(int k = 0; k < 20; k++){
      if(Grid[i][k] == 0) fill(100);
      else if(Grid[i][k] == 1) fill(50, 150, 255);
      else if(Grid[i][k] == 2) fill(0, 0, 255);
      else if(Grid[i][k] == 3) fill(255, 150, 0);
      else if(Grid[i][k] == 4) fill(255, 255, 0);
      else if(Grid[i][k] == 5) fill(100, 255, 0);
      else if(Grid[i][k] == 6) fill(150, 0, 150);
      else fill(255, 0, 0);
      //15pxごとに13pxの正方形を描く
      rect(i * 15 + 25, k *15 + 25, blockSize,blockSize);
    }
  }
```
Grid[0~9][0~19]の値によってグリッドの色をかえる。
Gridの値を書き換えることで、テトリスのブロックが落ち終わった時に色をかえることができる。

```java:
void displayCurrent(){
  if(current.getColor() == 0) fill(100);
  else if(current.getColor() == 1) fill(50, 150, 255);
  else if(current.getColor() == 2) fill(0, 0, 255);
  else if(current.getColor() == 3) fill(255, 150, 0);
  else if(current.getColor() == 4) fill(255, 255, 0);
  else if(current.getColor() == 5) fill(100, 255, 0);
  else if(current.getColor() == 6) fill(150, 0, 150);
  else fill(255, 0, 0);
  
  for(int i = 0; i < 4; i++){
    rect((current.getShapeX(i) + initPosX)*15 + 25, (current.getShapeY(i) + initPosY)*15 + 25, blockSize, blockSize);
  }
}
```
この関数によって動くブロックを表示している。
表示する上で、

色：　current.getColor()
ブロックの形状:　current.getShapeX(i), current.getShapeY(i)

が重要な要素。

また、位置はinitPosX,initPosYの値を書き換えることで、下にいったり横にいったりする。

##テトリスのクラス
＜役割＞
・クラス生成時にランダムに色と形を決定
・回転によって形をかえる
・形情報の取得

```java:
class Tetromino{
  int shape[][] = new int[4][2];
  //colはcolorのこと
  //col = 0は背景色
  int col = 0;
  
  public Tetromino(){
    //ランダムに色を選ぶ
    col = int(random(1, 8));//1~7まで
      if(col == 1){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = -2;
      }
      else if(col == 2){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = -1;
        shape[3][1] = -1;
      }
      else if(col == 3){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = 1;
        shape[3][1] = -1;
      }
      else if(col == 4){
        shape[1][0] = 1;
        shape[2][1] = 1;
        shape[3][0] = 1;
        shape[3][1] = 1;
      }
      else if(col == 5){
        shape[1][0] = -1;
        shape[2][1] = -1;
        shape[3][0] = 1;
        shape[3][1] = -1;
      }
      else if(col == 6){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][1] = -1;
      }
      else{
        shape[1][0] = 1;
        shape[2][1] = -1;
        shape[3][0] = -1;
        shape[3][1] = -1;
      }
  }
  
  //左に90度回転
  public void rot(){
    for(int i = 0; i < 4; i++){
      int buff = shape[i][0];
      shape[i][0] = shape[i][1];
      shape[i][1] = -buff;
    }
  }
  public int getShapeX(int i){
    return shape[i][0];
  }
  public int getRotShapeX(int i){
    return shape[i][1];
  }
  public int getShapeY(int i){
    return shape[i][1];
  }
    public int getRotShapeY(int i){
    return -shape[i][0];
  }
  public int getMaxX(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][0] > shape[i][0]) i = k;
    }
    return shape[i][0];
  }
  public int getRotMaxX(){
    int i = 0;
    for(int k = 1; k < 4; k++){
      if(shape[k][1] > shape[i][1]) i = k;
    }
    return shape[i][1];
  }
  public int getMinX(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][0] < shape[i][0]) i = k;
      }
      return shape[i][0];
    }
    
  public int getRotMinX(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][1] < shape[i][1]) i = k;
      }
      return shape[i][1];
    }
    
  public int getMaxY(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][1] > shape[i][1]) i = k;
    }
    return shape[i][1];
  }
  public int getMinY(){
    int i = 0;
    for(int k = 0; k < 4; k++){
      if(shape[k][1] < shape[i][1]) i = k;
    }
    return shape[i][1];
  }
  
  public int getColor(){
    return col;
  }
  
}
```
###形情報の取得
このクラスでは、形情報の取得が一番理解しずらいと思う。
何をやってるか説明しづらいので図示してみる。

```java:
public Tetromino(){
    //ランダムに色を選ぶ
    col = int(random(1, 8));//1~7まで
      if(col == 1){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = -2;
      }
      else if(col == 2){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = -1;
        shape[3][1] = -1;
      }
      else if(col == 3){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][0] = 1;
        shape[3][1] = -1;
      }
      else if(col == 4){
        shape[1][0] = 1;
        shape[2][1] = 1;
        shape[3][0] = 1;
        shape[3][1] = 1;
      }
      else if(col == 5){
        shape[1][0] = -1;
        shape[2][1] = -1;
        shape[3][0] = 1;
        shape[3][1] = -1;
      }
      else if(col == 6){
        shape[1][0] = 1;
        shape[2][0] = -1;
        shape[3][1] = -1;
      }
      else{
        shape[1][0] = 1;
        shape[2][1] = -1;
        shape[3][0] = -1;
        shape[3][1] = -1;
      }
  }
```
![IMG_0807.jpg](https://qiita-image-store.s3.amazonaws.com/0/214664/3fdf3142-20b1-81d0-a482-fce91b21c5ec.jpeg)

4つのマスをどのような配置にするかで、ブロックの形状が決まる。
shape[マスの番号][xかy]というようになっていて、最初は全てのマスのx、yは0になっている。
縦が(0,0)になっているところが回転の中心になる。
![IMG_0808.jpg](https://qiita-image-store.s3.amazonaws.com/0/214664/8dff70e7-efcb-3c99-14df-9114bb5ebe84.jpeg)

shape[i][k]でi番目のマスのx,yの値を取得できる。
全てのマスにアクセスできることが重要。

###回転
xを-yに、yをxにすることで形を変えている。
shape[i][0]がi番目のマスのx
shape[i][1]がi番目のマスのy

```java:
  //左に90度回転
  public void rot(){
    for(int i = 0; i < 4; i++){
      int buff = shape[i][0];
      shape[i][0] = shape[i][1];
      shape[i][1] = -buff;
    }
  }
```
![IMG_0809.jpg](https://qiita-image-store.s3.amazonaws.com/0/214664/0d869333-ccef-4683-4192-3cd5da5152e5.jpeg)

##落ち終わる判定

```java:
void verifyGrid(){
  boolean stop = false;
  
   //stopするかの判定
   for(int i = 0; i < 4; i++){
     if((current.getShapeY(i)+initPosY) <19){
       if(Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY + 1)] != 0){ 
         stop = true;
       }
     }
      if(current.getShapeY(i) + initPosY == 19){
        stop = true;
      }
   }
   
   
   if(stop){
     //グリッドの色を塗り替えるか終了
     for(int i = 0; i < 4; i++){
       if((current.getShapeY(i)+initPosY) < 0){
         lost();
       }
       else Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY)] = current.getColor();
     }
     
     int count = 0;
     
     //横一列に色がついていたら、ブロックを消す
     for(int y = 0; y < 20; y++){
       boolean destroy = true;
       
       for(int x = 0; x < 10; x++){
         if(Grid[x][y] == 0) destroy = false;
       }
       
       if(destroy){
         count++;
         for(int y2 = y-1; y2 > -1; y2--){
           for(int x = 0; x < 10; x++){
             Grid[x][y2+1] = Grid[x][y2];
           }
         }
       }
     }
     
     if(count > 0){
     if(count == 1) score += 40;
     else if(count == 2) score += 100;
     else if(count == 3) score += 300;
     else score += 1200;
   }
   
   //再設定
  current = new Tetromino();//クラスの作成
  //初期位置に戻す
  initPosX = 4;
  initPosY = 0;
  
    for(int i = 0; i < 4; i++){
      if(current.getShapeY(i) + initPosY < 19){
        if((current.getShapeY(i) + initPosY) > 0 && current.getShapeY(i) + initPosY < 19){
          if(Grid[current.getShapeX(i) + initPosX][current.getShapeY(i) + initPosY + 1] != 0){  //→方向のブロックに色がついてたら
            lost();
          }
        }
      }
   }
   
  }//ここまでif(stop)
}
```

###落ちた判定
まず、落ちたってどうやって判断すればいいのか。
・下に他のブロックがある時
・下が地面の時
の２種類ある。

```java:
   //stopするかの判定
   for(int i = 0; i < 4; i++){
     if((current.getShapeY(i)+initPosY) <19){
       if(Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY + 1)] != 0){ 
         stop = true;
       }
     }
      if(current.getShapeY(i) + initPosY == 19){
        stop = true;
      }
   }
```
if(Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY + 1)] != 0)
つまり、あるマスの１つ下のマスが0じゃない時、つまり色がついている時。

if(current.getShapeY(i) + initPosY == 19)
あるマスのyが地面にある時。

###色の塗り替え

```java:
     for(int i = 0; i < 4; i++){
       if((current.getShapeY(i)+initPosY) < 0){
         lost();
       }
       else Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY)] = current.getColor();
     }
```
Grid[(current.getShapeX(i) + initPosX)][(current.getShapeY(i) + initPosY)] = current.getColor();
これで、グリッドの値をブロックの色で書き換えている。

###ブロックを消して、一段下げる

```java:
     //横一列に色がついていたら、ブロックを消す
     for(int y = 0; y < 20; y++){
       boolean destroy = true;
       
       for(int x = 0; x < 10; x++){
         if(Grid[x][y] == 0) destroy = false;
       }
       
       if(destroy){
         count++;
         for(int y2 = y-1; y2 > -1; y2--){
           for(int x = 0; x < 10; x++){
             Grid[x][y2+1] = Grid[x][y2];
           }
         }
       }
     }
```

for(int x = 0; x < 10; x++){
　if(Grid[x][y] == 0) destroy = false;
}
横一列のグリッドの１つでも0(色なし)の時にはブロックを消さない。
つまり、横一列全部のグリッドに色がついていたら削除するってこと。

if(destroy){
　count++;
　　for(int y2 = y-1; y2 > -1; y2--){
　　for(int x = 0; x < 10; x++){
　　Grid[x][y2+1] = Grid[x][y2];
　　}
　}
}
これで、削除している。
削除っていうか、色を書き換えてるだけなんだけど。
![IMG_0810.jpg](https://qiita-image-store.s3.amazonaws.com/0/214664/9de0d16f-44e5-60f5-fc16-ef059ded207f.jpeg)
下のグリッドが、上のグリッドの色に書き換えられていく。
こうして、横一列削除されて、一段下がったような感じに見える。