はじめに
こんにちは 高専一年生、メカテック部部員です
C言語でマインスイーパーを作ったので共有します
再帰を用いております ぜひご覧ください
入れたい機能
- マスを開ける
- 開いたマスごとに数字を入れる
- 旗を立てる
- 最初の一手で爆弾を踏まないようにする
- 周りに爆弾のないマスがあると一斉に自動で連鎖して開く
コード紹介
includeと関数宣言
define
でマップの広さ(1辺の長さ)と爆弾の個数を設定します
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ONE_SIDE 5
#define BOMBS 5
int squared(int num);
void map_shuffle(int *map);
void map_making(int *map,int *flag,int mode);
int input_act(int *map,int *flag,int score,int *open_count);
int input_cell(int *map,int *flag,int act,int score,int *open_count);
int branching(int *map,int *flag,int act,int cell,int score,int *open_count);
int receiving_digging_location(int *map,int cell,int score,int *open_count);
int dig(int *map,int cell,int *open_count);
void step_sort(int *step,int cell);
void raise_flag(int cell,int *flag);
void clear_check(int score);
main関数
ここではプレイの繰り返しおよびゲームの進行を行います
int main(void)
{
int map[ONE_SIDE * ONE_SIDE] = {0};
int flag[ONE_SIDE * ONE_SIDE] = {0};
int score = 0;
int result;
int open_count;
srand((unsigned int)time(NULL));
map_shuffle(map);
while(score < squared(ONE_SIDE) - BOMBS){
map_making(map,flag,0);
open_count = 0;
result = input_act(map,flag,score,&open_count);
if(result == 0){
break;
}else if(result != -1){
score += open_count;
}
}
clear_check(score);
map_making(map,flag,1);
return 0;
}
squared関数
これは引数の二乗された数を返す関数です
マクロである、ONE_SIDE
の二乗を求めるときに使います
int squared(int num){ /*ONE_SIDEを二乗してマップの大きさにするためだけのもの*/
return num * num;
}
map_shuffle関数
ここではマクロからmap配列
に爆弾を散りばめます
爆弾は、map配列
内で -1 と扱われます
void map_shuffle(int *map){ /*マップのシャッフル*/
int i;
int random;
for(i=0; i<squared(ONE_SIDE); i++){
map[i] = 0;
}
for(i=0; i<BOMBS; i++){
do{
random = rand()%squared(ONE_SIDE);
}while(map[random] == -1); /*random位置にバクダンがあるとランダム作り直し*/
map[random] = -1;
}
}
map_making関数
ここではmap配列
、flag配列
からマップをプリントします
ゲーム終了後の表示も条件分けで担っているため少々複雑になってしまいました
ここからわかる通り、map配列
において
- 0 はまだ開いてないマス
- -1 はまだ開いていないが爆弾の潜むマス
- 9 は開いた爆弾と隣接しないマス
- 1~8 は開いた爆弾と隣接するマス
flag配列
において
- 1 は旗が立てられたマス
- 0 は旗が立てられていないマス
を表します
void map_making(int *map,int *flag,int mode){
int i;
for(i=0; i<squared(ONE_SIDE); i++){
if(flag[i] == 1){
if(mode == 1 && map[i] == -1){
printf("\033[35m[\033[93mP\033[35m]");
}else{
printf("\033[0m[\033[93mP\033[0m]");
}
}else{
if(map[i] == 0){
printf("\033[0m[ \033[0m]");
}else if(map[i] == -1){
if(mode == 0){
printf("\033[0m[ \033[0m]");
}else{
printf("\033[35m[*]");
}
}else if(map[i] == 9){
printf("\033[0m[\033[90mX\033[0m]");
}else{
printf("\033[0m[\033[31m%d\033[0m]",map[i]);
}
}
if(i%ONE_SIDE == ONE_SIDE-1){
printf("\n");
}
}
}
input_act関数
マップを掘るか旗を立てるかの入力をここで行ってもらいます
ここで入力ミスを検知し、聞き直しています
input_cell関数
の戻り値を返します
※この戻り値は、dig関数
またはbranching関数
で初めて明らかになります
int input_act(int *map,int *flag,int score,int *open_count){
int act = 3;
while(act<1 || 2<act){
printf("\033[0m何をしますか?\n\n 1 → ほる\n 2 → 旗を立てる\n\n答えてください:");
scanf("%d",&act);
}
return input_cell(map,flag,act,score,open_count);
}
input_cell関数
どこを掘るか またはどこを旗を立てるかの入力をここで行ってもらいます
ここで入力ミスを検知し、聞き直しています
branching関数
の戻り値を返します
int input_cell(int *map,int *flag,int act,int score,int *open_count){
int cell;
int x = -12;
int y = -12;
do{
do{
do{
printf("\033[0mx座標は?(1~%d):",ONE_SIDE);
scanf("%d",&x);
}while(x<1 || ONE_SIDE<x);
do{
printf("\033[0my座標は?(1~%d):",ONE_SIDE);
scanf("%d",&y);
}while(y<1 || ONE_SIDE<y);
cell = (y-1)*ONE_SIDE + (x-1);
}while(act == 1 && flag[cell] == 1);
}while(map[cell] != 0 && map[cell] != -1);
return branching(map,flag,act,cell,score,open_count);
}
branching関数
変数:act
の値によって処理の枝分かれを行います
act
が2、つまり旗を立てる処理の時は -1 を返します
何が行われたか、または何が起きたかによりmain関数
になんの数値が返されるかが決まるのです
int branching(int *map,int *flag,int act,int cell,int score,int *open_count){
if(act == 1){
return receiving_digging_location(map,cell,score,open_count);
}
if(act == 2){
raise_flag(cell,flag);
return -1;
}
}
receiving_digging_location関数
入力されたcell
の位置に爆弾があるか無いかを確認します
- あった場合
- もしそれが一度目の採掘なのならば
map_shuffle関数
を呼び出し、同じcell
でもう一度この関数を呼び出します - 一度目でない場合、 0 を返します
- もしそれが一度目の採掘なのならば
- なかった場合
-dig関数
を呼び出しその戻り値を返します
int receiving_digging_location(int *map,int cell,int score,int *open_count){
if(map[cell] == -1){
if(score == 0){
map_shuffle(map);
return receiving_digging_location(map,cell,score,open_count);
}else{
return 0;
}
}else{
return dig(map,cell,open_count);
}
}
dig関数
さあ来ました正念場です
ここでも目的は、
「周りに爆弾がないマス、つまり 9 のマスが開かれた場合はそれに隣接するマスを一気に開く」
ことです
ここでは再帰を使って解決します
int dig(int *map,int cell,int *open_count){
int step[8] = {-1*ONE_SIDE,-1*ONE_SIDE+1,1,ONE_SIDE+1,ONE_SIDE,ONE_SIDE-1,-1,-1*ONE_SIDE-1};
int i;
int open = 0;
int bombs_count = 0;
step_sort(step,cell);
for(i=0; i<8; i++){
if(step[i] != 0){
if(map[cell+step[i]] == -1){
bombs_count++;
}
}
}
if(bombs_count == 0){
map[cell] = 9;
(*open_count)++;
for(i=0; i<8; i++){
if(step[i] != 0){
if(map[cell+step[i]] == 0){
dig(map,cell+step[i],open_count);
open++;
}
}
}
return 1;
}else{
(*open_count)++;
map[cell] = bombs_count;
return 1;
}
}
- 開くマスを受け取る
-
周りを探索し、
-
隣接爆弾の数が 0 なら
map配列
の同じマスに 9 をいれて、変数:open_count
を1増やす
周りのマスでdig関数を呼び出す
最後に 1 を返す -
隣接爆弾の数が 0以外 なら
map配列
の同じマスに隣接爆弾数をいれて、変数:open_count
を1増やす
最後に 1 を返す
-
というのが基本的な流れとなります
※open_count変数
はmain関数
のscore変数
を加算するためにあります
step_sort関数
dig関数
で使うためstep配列
を
map配列
がバッファオーバーフロ―を起こさないために
調整するためのものです
void step_sort(int *step,int cell){
if(cell/ONE_SIDE == 0){
step[7] = 0;
step[0] = 0;
step[1] = 0;
}else if(cell/ONE_SIDE == ONE_SIDE-1){
step[3] = 0;
step[4] = 0;
step[5] = 0;
}
if(cell%ONE_SIDE == 0){
step[5] = 0;
step[6] = 0;
step[7] = 0;
}else if(cell%ONE_SIDE == ONE_SIDE-1){
step[1] = 0;
step[2] = 0;
step[3] = 0;
}
}
raise_flag関数
flag配列
を変化させます
void raise_flag(int cell,int *flag){
flag[cell] = !flag[cell];
}
clear_check関数
クリアしたかどうかを判断・表示する関数です
void clear_check(int score){
if(score >= squared(ONE_SIDE) - BOMBS){
printf("\033[0mクリア!\n");
}else{
printf("\033[0mドッカン!バクダンを踏んでしまった!\n");
}
}
最後に
ここまで読んでいただいてありがとうございました
まだまだ未熟なコードですが、
C言語のゲームの製作の参考にしていただけたらなと感じています