マインスイーパ ゲームを作成する-3
前回は爆弾の配置と,爆弾数の表示,ゲームオーバーの定義を行いました
processing-p5.hateblo.jp
今回はセルに旗を立てられるようにします
下図が今回のアプリの様子です
コードの全体は以下のとおりです
boolean gameOver = false; int bomb_n = 10; // 爆弾の個数 int flag_n = 0; // 旗の個数 int col_num = 9; // 列数 int row_num = 9; // 行数 int field_w, field_h; // フィールドの幅と高さ int window_h = 50; // 上部windowの高さ Cell[][] field = new Cell[col_num][row_num]; float c_x, c_y; // セルの幅 float margin = 4; // セル間の余白 void setup(){ PFont font = createFont("4x4kanafont.ttf", 12, true); textFont(font); textAlign(CENTER, CENTER); frameRate(30); size(300, 350); field_w = width; field_h = height-window_h; c_x = (field_w-(col_num+1)*margin)/col_num; c_y = (field_h-(row_num+1)*margin)/row_num; initiateField(); } void draw(){ background(220); // display window noStroke(); fill(180); rect(0+margin, 0+margin, field_w-2*margin, window_h-margin); fill(0); int bomb_remain = bomb_n-flag_n; if(!gameOver){text("BOMB: "+ bomb_remain, 0.2*field_w, 0.5*window_h);} if(gameOver){ textSize(14);text("Game Over", 0.5*field_w, 0.25*window_h); textSize(10);text("Press Enter to Restart", 0.5*field_w, 0.75*window_h); textSize(12); } // display Field pushMatrix(); translate(0, window_h); for(int i=0; i<col_num; i++){ for(int j=0; j<col_num; j++){ field[i][j].display(); } } popMatrix(); } void mouseClicked(){ for(int i=0; i<col_num; i++){ for(int j=0; j<col_num; j++){ field[i][j].mouseClicked(); } } } void keyPressed(){ if(gameOver&&keyCode==ENTER){ // ゲームオーバー時にEnterクリックでゲーム再開 gameOver=false; initiateField(); } } void initiateField(){ // Cellインスタンス生成 for(int i=0; i<col_num; i++){ for(int j=0; j<col_num; j++){ field[i][j] = new Cell(i, j); } } // 爆弾を必要数生成 int n = bomb_n; while(n>0){ int xx = int(random(0,col_num-1)); int yy = int(random(0,row_num-1)); if(!field[xx][yy].isBomb){ // 爆弾がすでに配置されていないセルだけを選択 field[xx][yy].isBomb = true; n--; } } // 近傍の爆弾数をカウント for(int i=0; i<col_num; i++){ for(int j=0; j<col_num; j++){ field[i][j].checkNeighboringBomb(); } } } class Cell{ int x, y; // index int neighbor; // 近傍の爆弾数 boolean isBomb; // 爆弾があるか boolean isOpen; // セルは開いているか boolean flag; // フラッグは立っているか float cx_min, cx_max, cy_min, cy_max; // セルの4辺の座標 // constructor Cell(int _x, int _y){ x = _x; y = _y; isBomb = false; isOpen = false; flag = false; cx_min = x*(c_x+margin)+margin; // セルの左端 cx_max = (x+1)*(c_x+margin); // セルの右端 cy_min = y*(c_y+margin)+margin; // セルの上端 cy_max = (y+1)*(c_y+margin); // セルの下端 } // method void display(){ stroke(50); if(isOpen){noStroke(); fill(180);} else if(isMouseHover()){fill(200);} else {fill(240);} rect(x*(c_x+margin)+margin, y*(c_y+margin)+margin, c_x, c_y); if(isOpen){ // 爆弾や数値を表示 if(isBomb){fill(255,0,0);text("★", cx_min+0.5*c_x, cy_min+0.4*c_y);} else if(neighbor>0){fill(0);text(neighbor, cx_min+0.5*c_x, cy_min+0.4*c_y);} openNeighboringCells(); } else{ // フラッグを表示 if(flag){fill(0,0,255);text("♪", cx_min+0.5*c_x, cy_min+0.4*c_y);} } if(gameOver&&isBomb){ // ゲームオーバーの場合, 爆弾のセルを全て開く isOpen = true; } } void mouseClicked(){ // セル上でマウスクリックされたとき, isOpenにtrueを格納 if(isMouseHover()&&!gameOver){ // ゲームオーバ時はクリック不可 if(mouseButton==LEFT){ if(!flag){ // フラッグが立っていない場合のみクリック可能 isOpen = true; if(isBomb){gameOver=true;} // 爆弾をクリックした場合, gameOverにtrueを格納 } } else if(mouseButton==RIGHT){ if(flag){flag=false; flag_n--;} else{flag=true; flag_n++;} } } } boolean isMouseHover(){ // マウスがセル上にホバーしているときはtrue // 上部windowの高さ分だけ, mouseYから減算 if(cx_min<mouseX && mouseX<cx_max && cy_min<mouseY-window_h && mouseY-window_h<cy_max){ return true; } else{return false;} } void checkNeighboringBomb(){ // 近傍の爆弾数を数える int num = 0; if(x>0){ // 左列 if(y>0){if(field[x-1][y-1].isBomb){num++;}} if(y<row_num-1){if(field[x-1][y+1].isBomb){num++;}} if(field[x-1][y].isBomb){num++;} } if(x<col_num-1){ // 右列 if(y>0){if(field[x+1][y-1].isBomb){num++;}} if(y<row_num-1){if(field[x+1][y+1].isBomb){num++;}} if(field[x+1][y].isBomb){num++;} } if(y>0){if(field[x][y-1].isBomb){num++;}} if(y<row_num-1){if(field[x][y+1].isBomb){num++;}} if(isBomb){num=-1;} // 自分が爆弾の場合, -1を格納 neighbor = num; } void openNeighboringCells(){ // 近傍のセルが空欄の場合,セルを解放する if(x>0){ // 左列 if(y>0){if(field[x-1][y-1].neighbor==0){field[x-1][y-1].isOpen=true;}} if(y<row_num-1){if(field[x-1][y+1].neighbor==0){field[x-1][y+1].isOpen=true;}} if(field[x-1][y].neighbor==0){field[x-1][y].isOpen=true;} } if(x<col_num-1){ // 右列 if(y>0){if(field[x+1][y-1].neighbor==0){field[x+1][y-1].isOpen=true;}} if(y<row_num-1){if(field[x+1][y+1].neighbor==0){field[x+1][y+1].isOpen=true;}} if(field[x+1][y].neighbor==0){field[x+1][y].isOpen=true;} } if(y>0){if(field[x][y-1].neighbor==0){field[x][y-1].isOpen=true;}} if(y<row_num-1){if(field[x][y+1].neighbor==0){field[x][y+1].isOpen=true;}} } }
前回のコードから大きく変化した点をみていきましょう
まず,セル上に旗を立てるためにセルクラスのクラス変数としてflagを定義します
セル上で右クリックを一回押すとflag変数にtrueが格納されます
その状態でもう一度右クリックをするとflag変数にfalseが格納されます
なお,flag=trueのときは,セル上を左クリックしてもセルを開くことができません
Cell(int _x, int _y){ flag = false; } void mouseClicked(){ if(isMouseHover()&&!gameOver){ if(mouseButton==LEFT){ // 左クリック時の挙動 if(!flag){ // フラグが立っていない場合のみクリック可能 isOpen = true; if(isBomb){gameOver=true;} } } else if(mouseButton==RIGHT){ // 右クリック時の挙動 if(flag){flag=false; flag_n--;} // すでにフラグが立っているときは, フラグを取り消す else{flag=true; flag_n++;} // フラグが立っていないときは, 新たにフラグを立てる } } } }
下図は旗が立っているときの様子です
前回のコードでは爆弾は確率的に生成していましたが, 今回は爆弾の数を固定するため, 次のようなコードの書き方に変更しています
int bomb_n = 10; // 爆弾の個数 void initiateField(){ // 爆弾を必要数生成 int n = bomb_n; while(n>0){ int xx = int(random(0, col_num-1)); int yy = int(random(0, row_num-1)); if(!field[xx][yy].isBomb){ // 爆弾がすでに配置されていないセルだけを選択 field[xx][yy].isBomb = true; n--; } } }
マインスイーパでは, 画面上部に「のこり爆弾数」を表示することが一般的です
「のこり爆弾数」は「実際の爆弾数ー旗の数」で与えられます
旗の数を格納するグローバル変数flag_nと,爆弾の数を格納するグローバル変数bomb_nをそれぞれ定義し,その差分を画面上に表示します
int bomb_n = 10; // 爆弾の個数 int flag_n = 0; // 旗の個数 void draw(){ if(!gameOver){text("BOMB: "+ bomb_remain, 0.2*field_w, 0.5*window_h);} // のこり爆弾数表示 if(gameOver){ //ゲームオーバー画面表示 textSize(14);text("Game Over", 0.5*field_w, 0.25*window_h); textSize(10);text("Press Enter to Restart", 0.5*field_w, 0.75*window_h); textSize(12); } }
次回はゲームクリア時の画面表示の実装をしたいと思います