1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C++でマインスイーパを作る!①マップを描画しよう(1)目標設定、配列、define(マクロ形式オブジェクト)

Last updated at Posted at 2020-12-03

##流れを考える
 ということで、マインスイーパを実際に作っていきましょう。
 最初のコードはこんな感じです。

tokuniimihanai
#include<iostream>

using namespace std;

int main(){

 return 0;
}

 当たり前ですが、まだ何もできていません!
 ここからいきなり書き始めるのは無理無理カタツムリなので、まずは「そもそもマインスイーパって何だ?」と考えることで、最初の目標を設定します。
 ↓↓そこで、以下の画像を見てみましょう。デカかったらゴメン。↓↓
記事用_001.png

 最後の方ではムズいことや考えることが多すぎて混乱してしまいましたが、まずやるべきことは、

①フィールドがある
②フィールド上に壊せる壁がある

これらを実装することです。

##マップを作るには?——配列の利用
 たとえば縦15マス*横30マスの場合、合計で450個のマスがあります。
 450個の変数を手作業で準備・管理するのは大変です。
 そこで、配列を利用して一度に管理してしまいましょう。

hairetsuttesugoi
 int map[15][30];

 この記述は、map[0][0]からmap[14][29]までのint型変数を用意したことを1行で表せます。便利‼

int map[15][30]; //配列の宣言
for(int i = 0; i < 15; i++){      //0-14行目まで繰り返す処理の中に、
   for(int j = 0; j < 30; j++){   //0-29列目まで繰り返す処理を埋め込みます。この表記は頻出なので注意‼
      map[i][j] = 0;              //配列の全てに0を代入
      cout << map[i][j];          //配列の全てを出力
   }
   cout << "\n";                  //ここ忘れがちですが、各行29列目のあとで改行しましょう。
}

このように記述すれば、実行した時には縦15*横30個の0がずらっと並ぶはずです。↓↓

000000000000000000000000000000 //map[0][0]からmap[0][29]まで出力し\nで改行。
000000000000000000000000000000  //map[1][0]からmap[1][29]まで出力し\nで改行。以下同じ。
000000000000000000000000000000
     (……省略……)
000000000000000000000000000000
000000000000000000000000000000
000000000000000000000000000000 //map[14][0]からmap[14][29]まで出力し\nで改行。

↑↑こんなイメージ。

##マインスイーパらしい画面にしよう!
しかし
しかしですよ、皆さん。
マインスイーパの画面ってこんなんじゃ無いですよね?
 一度実際のマインスイーパの画面を見てみましょう。(拾い画像です、すみません!)↓↓
play.png

 0じゃダメですよね!
 マインスイーパのマップ上には、壊すべき壁がミッチリ並んでいる状態です。まあ表現するなら「□」とかでしょうか。しかし、今の時点ではマップ上にひたすら0が表示されて終わりです。
 そこで、このような仕組みを導入してみましょう。

①int[15][30]型配列mapに値を代入する(さっきの例だと0)
②代入された値に従ってマップに描画する(0の場合□を描画する)

 こうすると、□以外の描画も可能であるうえ、「種々の文字や図形の描画」を「数字の計算」で管理できるようになります。便利‼
 先ほどのコードを直してみましょう。
 今回は、上述の①と②を別々の処理で行ってみるので、同じような記述が連続してしまいますが、仕方ないと思ってあきらめてください。

int map[15][30];
for(int i = 0; i < 15; i++){
   for(int j = 0; j < 30; j++){
      map[i][j] = 0;
   }
}
//ここまでで配列の全てに0を代入できました。

//以下の記述で画面に出力してみます。
for(int i = 0; i < 15; i++){
   for(int j = 0; j < 30; j++){
      switch(map[i][j]){             //switch文で配列の値の場合分けをします。今回は0しかありませんが、今後増えます。
         case 0: cout << "□"; break; //:の後には必ず半角スペースを入れる事と、各caseの最後にbreak;を忘れずに‼
         default: break;
      }
   }
   cout << "\n";
   }

こんな感じです。
 2つ目の記述は、全ての配列に対して

①値が0ならば□を出力
②値がそれ以外ならば何もしない

 という場合分けをしています。
 各配列の値ですが、

①ふつうの壁…0
②爆弾入りの壁…1
③ふつうの壁(チェック入り)…2
④爆弾入りの壁(チェック入り)…3
⑤爆弾(ゲームオーバー)…-1
⑥壊した後の壁…-2

 と、おそらく6パターンにまで増えそうです。したがって今回はif文でなくswitch文の場合分けを採用しました。
 では、実際に出力してみましょう。ドン!!
minesweeper.png

 良さそう‼
 なんとかマップを描画できた、という感じです。

##もっと良くしよう!
 さて、うまくできたところでちょっと現在のコードを「全文」確認してみましょう。

imano-code
#include<iostream>

using namespace std;

int main() {
    int map[15][30];
    for (int i = 0; i < 15; i++) {
        for (int j = 0; j < 30; j++) {
            map[i][j] = 0;
        }
    }   
 
    for (int i = 0; i < 15; i++) {
        for (int j = 0; j < 30; j++) {
            switch (map[i][j]) {            
            case 0: cout << "□"; break; 
            default: break;
            }
        }
        cout << "\n";
    }

    return 0;
}

このコードは処理として間違っているわけではないのですが、「読みにくい」です。具体的には

①15とか30とか、マスに関する数字が出てきすぎ。それぞれ何を表してるの? 変更する時すべてに手をつけなきゃいけないので面倒
②main()関数の中で処理がたくさん行われてるけど、それぞれどんな処理をしているの?

 などなど……。
 実際にはもっとたくさんあるかもですが、とりあえずこの2点に絞って解決しましょう!
##①マス数を分かりやすくする――defineを使おう!
 ところで皆さん、これを見てください。

#define MICHO 20  //文末に;は不要なので注意!

「何これ?」という感じかもしれませんが、この記述を使って、

#define MICHO 20
#include<iostream>
using namespace std;

int main(){
   cout << MICHO;

   return 0;
}

MICHOの値を出力するだけのコードを作ってみると、

20

 と出力される筈です。
 このような命令を#define指令といい、#define A Bのかたちで用います。そうすると、Aはそれ以降Bを表すものとして置き換えられるわけです。
(また、Aは「これ、defineされた値だよ」と分かりやすくするために全て大文字で表記します)

 このように表記するメリットとしては、

①値の変更を一括化するので取りこぼしを防ぐことができる。
②数値に名付けができるので読みやすくなる。

等が挙げられます。他にもconstやenumを使う方法があり、というかそちらの方が推奨されているらしい(ゴメン)のですが、とりあえず今回は#defineを使ってマスの数を管理してみましょう!

 まず、コードの先頭に、

#define MAP_HEIGHT 15
#define MAP_WIDTH 30

と書いてあげましょう。こうすることによってこれ以降、

MAP_HEIGHTが15という値を表す
MAP_WIDTHが30という値を表す

と決められたことになります。
 したがって、全ての値を書き換えてあげると、

#define MAP_HEIGHT 15
#define MAP_WIDTH 30

#include<iostream>

using namespace std;

int main() {
    int map[MAP_HEIGHT][MAP_WIDTH];
    for (int i = 0; i < MAP_HEIGHT; i++) {
        for (int j = 0; j < MAP_WIDTH; j++) {
            map[i][j] = 0;
        }
    }   
 
    for (int i = 0; i < MAP_HEIGHT; i++) {
        for (int j = 0; j < MAP_WIDTH; j++) {
            switch (map[i][j]) {            
            case 0: cout << "□"; break; 
            default: break;
            }
        }
        cout << "\n";
    }

    return 0;
}

分かりやすい!!!!!!!!!!

 ……いや、どうだろう。まだあまり効果が実感できないかもしれません。コードってそもそも読みにくいですもんね。とりあえず今は騙されたと思ってこの書き方でいってみましょう。

##いったん切る!
 ここまでで記事としてはかなり長くなってしまいました。
 本当は②の「main()関数の中でどんな処理してるのか分かりにくいよ~」を何とかしたかったのですが、今回はここでいったん打ち切って次回にまわします。ゴメン!!

 とはいえ、今回だけでもマインスイーパの画面を表示することには成功したはずです。「何すればいいのん?」と迷っていた方々にとって、大きな進歩だと思います! 次回からも一緒に頑張っていきましょう!!

 それでは次回、「マップを描画しよう(2)」をお楽しみにお待ちください!!

====================================
 読んでいただきありがとうございました。
 未熟者ゆえ、いたらぬ点が多くあると思います。分からなかった箇所に関してはご質問いただければ返せる範囲でお返事させていただきます。また、間違っている点や改善点などありましたら存分にご指摘ください。真摯に学ばせていただきます。
     MICHO

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?