0
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?

【C言語】再帰で再現!マインスイーパー制作紀行

Last updated at Posted at 2025-09-19

はじめに

こんにちは 高専一年生、メカテック部部員です
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の値によって処理の枝分かれを行います
act2、つまり旗を立てる処理の時は -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言語のゲームの製作の参考にしていただけたらなと感じています

0
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
0
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?