テトリスを作る-6 : 揃った段を消す
前回はテトリスブロックを積み重ねる機能を追加しました
processing-p5.hateblo.jp
今回はブロックが揃った段を消す機能を実装していきます
下図が今回のプログラムの様子です
コードは以下のようになりました
int col_num=10, row_num=20; // セルの数 float c_w, c_h; // セルの幅と高さ char[] t_type = {'i', 'o', 's', 'z', 'j', 'l', 't'}; // Tetriminoの種類 int time; // 遊び時間 boolean[][] field = new boolean[col_num+6][row_num+6]; // フィールド上のセルにブロックが存在しているか color[][] f_color = new color[col_num+6][row_num+6]; // フィールドの色 Tetrimino tetra1; // Tetriminoブロック void setup(){ frameRate(15); background(100); size(260, 460); c_w = width/(col_num+6.0); c_h = height/(row_num+6.0); // フィールドの初期化 stroke(200); fill(255); for(int i=0; i<col_num+6; i++){ for(int j=0; j<row_num+6; j++){ f_color[i][j] = color(255,255,255); if(i<=2||i>=col_num+3||j>=row_num+3){ field[i][j] = true; // 両端と底面の枠縁の部分をtrueにする } else { field[i][j] = false; } } } // テトリミノの初期化 char type = t_type[floor(random(7))]; tetra1 = new Tetrimino(type); } void draw(){ background(100); stroke(200); for(int i=3; i<col_num+3; i++){ for(int j=3; j<row_num+3; j++){ if(!field[i][j]){fill(255);} else{fill(f_color[i][j]);alpha(50);} rect(i*c_w, j*c_h, c_w, c_h); // フィールドの描画 } } tetra1.display(); if(frameCount%15==0){tetra1.fall();} // 15フレームごとにブロック落下 if(tetra1.isMoving){time=15;} else{time--;} if(time<0){ // ブロックが着地した場合,新しいブロックを生成 tetra1.stack(); char type = t_type[floor(random(7))]; tetra1 = new Tetrimino(type); time = 15; } lineClear(); // 揃った段を消去 } void lineClear(){ for(int j=row_num+2; j>=3; j--){ boolean gotLine = true; for(int i=2; i<=col_num+2; i++){ gotLine &= field[i][j]; } if(gotLine){ for(int jj=j; jj>=3; jj--){ for(int i=2; i<=col_num+2; i++){ field[i][jj] = field[i][jj-1]; f_color[i][jj] = f_color[i][jj-1]; } } gotLine = false; } } } void keyPressed(){ tetra1.keyPressed(); } class Tetrimino{ char type; // i, o, s, z, j, l, t color c; int x, y; // poition boolean isMoving; boolean[][] layout = new boolean[4][4]; // ブロックの形状 // constructor Tetrimino(char _type){ type = _type; x = 8; y = 3; isMoving = true; initiate(); } // methods void initiate(){ for(int i=0; i<4; i++){ for(int j=0; j<4; j++){ layout[i][j] = false; } } switch(type){ // 種類ごとにlayoutを決定 case 'i': for(int i=0; i<4;i++){layout[i][1]=true;} c = color(170, 202, 255); // 水色 break; case 'o': layout[1][1]=layout[1][2]=layout[2][1]=layout[2][2]=true; c = color(249, 234, 100); // 黄色 break; case 's': layout[1][2]=layout[2][1]=layout[2][2]=layout[3][1]=true; c = color(0, 255, 0); // 緑色 break; case 'z': layout[1][1]=layout[2][1]=layout[2][2]=layout[3][2]=true; c = color(255, 0, 0); // 赤色 break; case 'j': layout[1][1]=layout[2][1]=layout[3][1]=layout[3][2]=true; c = color(0, 0, 255); // 青色 break; case 'l': layout[1][1]=layout[2][1]=layout[3][1]=layout[1][2]=true; c = color(221, 90, 48); // オレンジ色 break; case 't': default: layout[1][1]=layout[2][1]=layout[3][1]=layout[2][2]=true; c = color(121, 78, 157); // 紫色 break; } } void display(){ // ブロックの描画 for(int i=0; i<4; i++){ for(int j=0; j<4; j++){ if(layout[i][j]){stroke(0);fill(c); rect((x+i-2)*c_w, (y+j-1)*c_h, c_w, c_h);} } } } void fall(){ // ブロックの落下 if(!isCollide(layout, x, y+1)) y++; // 衝突しない場合落下 else isMoving = false; // これ以上ブロックが落下できない場合,「ブロックが停止した」とみなす } void stack(){ // ブロックを積み重ねる for(int i=0; i<4; i++){ for(int j=0; j<4; j++){ if(layout[i][j]){ field[x+i-2][y+j-1] = true; f_color[x+i-2][y+j-1] = c; } } } } void keyPressed(){ switch(keyCode){ case SHIFT: if(!isCollide(rotate(), x, y)){layout=rotate(); isMoving = true;}// 回転しても衝突しない場合 else{ // 回転した場合衝突してしまうが,座標移動すると回転できる場合(kick) if(!isCollide(rotate(), x-1, y)){layout=rotate(); x--; isMoving = true;} else if(!isCollide(rotate(), x+1, y)){layout=rotate(); x++; isMoving = true;} else if(!isCollide(rotate(), x, y-1)){layout=rotate(); y--; isMoving = true;} } break; // ブロックの回転 case LEFT : if(!isCollide(layout, x-1, y)){x--; isMoving = true;} break; case RIGHT: if(!isCollide(layout, x+1, y)){x++; isMoving = true;} break; case DOWN : fall(); break; } } boolean[][] rotate(){ // Super Rotation System boolean[][] tmp = new boolean[4][4]; // layoutの情報を一時的に格納 switch(type){ // 右回転 case 'i': // I型 case 'o': // O型 for(int i=0; i<4; i++){ for(int j=0; j<4; j++){ tmp[j][3-i] = layout[i][j]; } } break; default: // その他 for(int i=1; i<4; i++){ for(int j=0; j<3; j++){ tmp[j+1][3-i] = layout[i][j]; } } break; } return tmp; } boolean isCollide(boolean[][] _tmp, int _x, int _y){ // ブロックが壁や他のブロックに衝突しているか boolean result = false; boolean[][] tmp = _tmp; int xx = _x; int yy = _y; for(int i=0; i<4; i++){ for(int j=0; j<4; j++){ if(tmp[i][j]&&field[xx+i-2][yy+j-1]){ result=true; } } } return result; } }
前回のコードから追加されたのは,関数lineClear()です
lineClear()では,field配列の各行について,要素がすべてtrue(=ブロックが揃っている)の行を調べています
もし要素がすべてtrueの場合,1つ前の行の要素をコピーしていくことで「段を消す」ことを実現します
void lineClear(){ for(int j=row_num+2; j>=3; j--){ // 行を下から精査 boolean gotLine = true; for(int i=2; i<=col_num+2; i++){ gotLine &= field[i][j]; // 行の各要素について論理積をとる } if(gotLine){ // 論理積がtrueのとき for(int jj=j; jj>=3; jj--){ for(int i=2; i<=col_num+2; i++){ field[i][jj] = field[i][jj-1]; // 上の行の内容をコピー f_color[i][jj] = f_color[i][jj-1]; } } gotLine = false; } } }
今回まででテトリスの基本的なゲーム要素を実装することができました
次に行うべきは,テトリスのゲームバランスの調整となります
これは例えば,ブロックの落下速度の変化であったり,次に現れるブロックの予告,ブロックの保留などです
また,ブロックの移動・回転や段消去のアニメーションにも改善の余地が多々ありますので,これらについても随時調整をしていきたいと思います