2019年9月10日火曜日

ソースコード公開] C言語で実装したCUIマインスイーパゲーム(広がる0機能を含む)

製作したコードはGitHubに上げておきました:
https://github.com/ka373/SimpleCUIMinesweeperWithSpreadingZeros


__________________________________________
週末に趣味プログラミングみたいな感じで、CUIマインスイーパゲームを実装しました。

多くの方々は、データ構造やアルゴリズムの詳細を学ぶ前にマインスイーパゲームを実装しようとします。このプログラムはその方にも理解できるように実装しました。

シンプルなゲームですが、プログラミングをする観点でみると、いくつかの考える部分があります。

そして、マインスイーパゲームを実装した人には、「広がる0機能」を実装しなかった人も多かったですね。ここではその部分まで実装しました。
 *広がる0機能:踏まれたブロックの周りの8ブロックに地雷がない場合、その8ブロックも踏まれたこととして処理していく機能

今から実装したプログラムについて簡単に紹介します。

I. ゲームの目的
地雷以外の全てのブロック(地)を踏むと、ゲームをクリアすることができます。

II. ゲームの進行
ボードがあり、そのボードを踏んだり、地雷や「?」を表示することでゲームを進行します。


__________________________________________
[いくつかの製作過程]


0. ゲームの構造
ゲームが終了するまで1)〜4)を繰り返します。
 1)ブロック(地)の位置とタイプを入力します。
 2)ゲーム進行関数に位置とタイプを入力します。
 3)ゲームを続けるかゲームオーバーかを判断します。
 4)進行状況(ボード)をプリントします。


1. ボード作り
ボードで行われるゲームなので、ボードが必要です。
2つのボード、
 1) ユーザーが探索していく、見えるボード(board_visible)と、
 2) 解答ボード(board_kotae)
を作ります。

int board_visible[NUM_OF_ROWS][NUM_OF_COLUMNS];    // Similar to the board seen by the user
int board_kotae[NUM_OF_ROWS][NUM_OF_COLUMNS];    //Board with numbers and mines (correct answer board)
cs



2. 解答ボード(board_kotae)の構成

2.1. 地雷作りと配置
ボードブロックの数だけの乱数を作り、作られた乱数に基づいてボードに地雷を配置します。


    //mine planting
    if (process_NUM_MINE_randoms() == 1) {
        for (i = 0; i < NUM_MINE; i++) {
            int mine_row = obtained_randoms[i] / NUM_OF_ROWS;
            int mine_column = obtained_randoms[i] % NUM_OF_ROWS;
            board_kotae[mine_row][mine_column] = MINE;
            //printf("\nLocation of mines: row: %d column: %d\n", mine_row, mine_column);
        }
    }
cs


2.2. 数字の設定
配列を探索しつつ、そのブロックの周辺ブロックの地雷の数を数えて数字を入力します。


3. ゲームの進行関数の作成(process_game)

3.1. 入力された場所が既に訪れた場所なら無効とします。

3.2. 新しいブロックを踏んだ場合
解答ボードを参考にして見えるボードに値を入力します。
見えるボードに入力する値は、ボードを出力するときに使います。

3.2.1. 踏んだところが1以上8以下の数字である場合
見えるボードに、対応する数字を入力します。

3.2.2踏んだところが地雷である場合
見えるボードに対応する数字を入力します。
ゲームオーバーを示す値を返します。 (私の場合はis_live = 0;のようにしました)

3.2.3. 踏んだところが0の場合(周囲に地雷がない場合)
選択された部分が0の場合、0以外の数字が出てくるまで0が広がっていきます。
このゲームの醍醐味であり、実装難度が他の部分に比べて高い部分だと思います。
しかし、方法をみてから実装すると、ただ簡単なことに変わるかもしれませんね。
従って、自らの力でマインスイーパゲームを実装したい方は、この部分を参考する前に、自分で解決してみてください。
.
.
.
.
.
.
.
.
.
.
私の場合、再帰関数を使って、次のように実装しました。
踏んだところが0の場合、左上、上、右上、左、右、左下、下、右下方向に再帰的に検査していきます。
短い期間で趣味プログラミングのように作ったもので、これが最適化されたアルゴリズムと断定することはできません。ただし、必要な性能を出すことには悪くないと思います。
この記事を読んでいる方は、これより良いアルゴリズムも考えてみてください。

いくつかのパスを描いてみると次のとおりです:



        //spreading zeros(0の連鎖爆弾!0의 연쇄폭탄!)
        if (board_visible[row_entered][column_entered] == 0) {
            if (can_go(row_entered - 1, column_entered - 1)) {
                process_game(row_entered - 1, column_entered - 1, STEPPED);    //Spread to left upper diagonal ↖
            }
            if (can_go(row_entered - 1, column_entered + 1)) {
                process_game(row_entered - 1, column_entered + 1, STEPPED);    //Spread across the upper right diagonal ↗
            }
            if (can_go(row_entered + 1, column_entered - 1)) {
                process_game(row_entered + 1, column_entered - 1, STEPPED);    //Spread to left lower diagonal ↙
            }
            if (can_go(row_entered + 1, column_entered + 1)) {
                process_game(row_entered + 1, column_entered + 1, STEPPED);    //Spread across the lower right diagonal ↘
            }
            if (can_go(row_entered - 1, column_entered)) { process_game(row_entered - 1, column_entered, STEPPED); }//Spread up ↑
            if (can_go(row_entered + 1, column_entered)) { process_game(row_entered + 1, column_entered, STEPPED); }//Spread down ↓
            if (can_go(row_entered, column_entered - 1)) { process_game(row_entered, column_entered - 1, STEPPED); }//Spread left ←
            if (can_go(row_entered, column_entered + 1)) { process_game(row_entered, column_entered + 1, STEPPED); }//Spread to the right →
        }// end of if연쇄폭탄
cs



__________________________________________
マインスイーパゲームを作成するために必要なものを少しだけ整理してみました。

そして、再帰処理により広がる0機能も実装してみました。

このように、マインスイーパゲーム等のゲームや多様なプログラムを実装しみることで、少しでも人の幸せに貢献できるし、考える練習もできるし、プログラミング技術や実装能力の向上も図れると思います。

0 件のコメント:

コメントを投稿