C 言語の基礎 - マインスイーピング (再帰を含む)

目次

序文: 完全なコードは記事の最後に記載されています。前のコードは参考用です。

1. マインスイーパのゲームルール

2. コードレイアウトを分析する

3. コードの表示と説明

1. ゲームメニュー

2.チェス盤を表示する

 3. ブレイ

4. 私のものを確認してください

5. ゲームの勝敗

4. トータルコード表示

1.ゲーム.h

2.ゲーム.c

3.テスト.c

5. まとめ


序文: 完全なコードは記事の最後に記載されています。前のコードは参考用です。

1. マインスイーパのゲームルール

 

       私の友達はマインスイーパ ゲームをプレイしたことがあるはずです。上の写真はウェブ版のマインスイーパです。ランダムにグリッドをクリックすると、大きな空白領域と数字がポップアップします。数字がある場合は、8 つあることを意味しますこの数の 1 週間分のグリッド。合計で地雷の数はいくつありますか私たちがしなければならないことは、Ray ではないすべてのグリッドを見つけることだけです。今日は、次の単純な9*9 グリッド掃海艇について説明します以下にマインスイーパ Web 版へのリンクを貼っておきますので、プレイしてみたい友達はすぐに体験することができます!http://www.マインスイーパー.cn/

2. コードレイアウトを分析する

私たちはクリーンで読みやすいコードの原則を遵守し、次の図に示すようにコードを 3 つの部分に分割します。

「game.h」ヘッダー ファイルは、必要な定数を定義し、使用する必要があるヘッダー ファイルを宣言し、作成する関数を宣言するために使用されます。

「game.c」ソース ファイルは、これから作成する関数の定義と記述に使用されます。

「test.c」ソース ファイルは、作成する関数の本体です。

3. コードの表示と説明

1. ゲームメニュー

どのゲームにもゲーム プロセスを制御するメニューが必要なので、次のコードはよく知られている必要があります。

​​#include"game.h"
void menu()
{
	printf("**************************************\n");
	printf("************    1.play    ************\n");
	printf("************    0.exit    ************\n");
	printf("**************************************\n");
}
void game()
{
	char arr1[ROWS][COLS];
	char arr2[ROWS][COLS];
	SetBoard(arr1, ROWS, COLS, '0');//创建棋盘
	SetBoard(arr2, ROWS, COLS, '*');//创建棋盘
	SetMine(arr1, ROW, COL);//布雷
	ShowBoard(arr2, ROW, COL);//展示棋盘
	FindMine(arr1, arr2, ROW, COL);//排查雷
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择->:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("开始游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input == 1);
	return 0;
}

ゲーム関数を作成することで、他のすべての関数への呼び出しを実装します。

       ここにちょっとしたトリックがあります。while が 0 の判定条件はfalse でゼロ以外は true であることがわかっているので、 case 0 を終了ゲームとして使用し、ループを終了して1または他の数値を入力することもできます。 、ループが続き、選択を再開します。

2.チェス盤を表示する

 理想的なチェス盤は 9*9 の 2 次元配列ですが、実際には11*11 の配列を 2 つ作成する必要があります。なぜ 2 つの配列を作成するのでしょうか? このゲームは推理ゲームなので、プレイヤーとしては見た目だけで仕組みは分かりませんし、プログラマとしては動作を理解する必要があります。なぜ11*11 の配列を作成するのかについては、まずここでパスを購入しましょう。それについては、以下の説明で説明します。

	char arr1[ROWS][COLS];
	char arr2[ROWS][COLS];
	SetBoard(arr1, ROWS, COLS, '0');//创建棋盘
	SetBoard(arr2, ROWS, COLS, '*');//创建棋盘
void SetBoard(char arr1[ROWS][COLS], int rows, int cols, char set)//创建棋盘
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			arr1[i][j] = set;
		}
	}
}

上記のコードでは、配列arr1 はボード上のさまざまな初期化操作に使用され、配列arr2 はプレーヤーの表示に使用されます

void ShowBoard(char arr1[ROWS][COLS], int row, int col)//展示棋盘
{
	for (int i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	printf("___________________\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d|", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", arr1[i][j]);
		}
		printf("\n");
	}
}

上記のコードではチェス盤の表示を実現していますが、プレイヤーがより正確に判定する位置座標を選択できるようにしたいので、行数と列数も出力します。結果は次のようになります。

 

 3. ブレイ

ボードを作成した後、rand() 関数を呼び出してRandom Mineを実装する必要があります。コードは次のとおりです。

void SetMine(char arr1[ROWS][COLS], int row, int col)//布雷
{
	int count = EASY_COUNT;
	while(count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (arr1[x][y] == '0')
		{
			arr1[x][y] = '1';
			count--;
		}
	}
}

結果は次のとおりです。

 

4. 私のものを確認してください

サンダーを設定したら、すぐにゲームを開始します。コードは次のとおりです。

void FindMine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col)//排查雷
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (1)
	{
		printf("请输入要排查的坐标:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (arr1[x][y] == '1')
			{
				printf("很遗憾,踩雷了,游戏结束。\n");
				ShowBoard(arr1, ROW, COL);//展示棋盘	
				break;
			}
			else
			{
				system("cls");
				RecUnfold(arr1, arr2, x, y, &win);
				ShowBoard(arr2, ROW, COL);//展示棋盘
			}
		}
		else
		{
			printf("输入错误,请重新输入:");
		}
	}
}

       地雷を確認するときは、座標をクリックします。地雷でない場合は、周囲の 8 つのグリッドに地雷の数が表示されます。これは、次のコードによって実現されます。

int  MineNum(char arr1[ROWS][COLS], int x, int y)//统计周围雷数
{
	int count = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			if (arr1[i][j] == '1')
				count++;
		}
	}
	return count;
}

地雷の数をカウントするためのcount 変数を定義します。ここでは、上記のコードを使用して、9*9 の配列ではなく 11*11 の配列を作成する理由を説明します。

 

       グリッドの周りの地雷の数を数えたい場合、それが 9*9 配列だけの場合、最も境界のグリッドを判定すると、その隣接するグリッドの一部が判定できなくなり、問題が発生しやすくなります。したがって、 11*11 の配列を作成しますが、コードエラーを避けるために中央の 9*9 のみを初期化します。

       このように座標を一つ一つ判断するだけだと少々面倒なので、下図のようにグリッドをクリックして円を展開する効果を実現するために、再帰的に実現する RecUnfold 関数を作成ます

void RecUnfold(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int x, int y, int* win)//递归炸开
{
	if (x >= 1 && x <= ROWS && y >= 1 && y <= COLS)
	{
		int count = MineNum(arr1, x, y);//获取雷数
		if (count == 0)//四周没有雷时向四周展开
		{
			arr2[x][y] = ' ';//四周都没雷时改为空格
			for (int i = x - 1; i <= x + 1; i++)
			{
				for (int j = y - 1; j <= y + 1; j++)
				{
					if (arr2[i][j] == '*')//只对'*'展开,防止死递归
					{
						RecUnfold(arr1, arr2, i, j, win);
					}
				}
			}
		}
		//四周有雷时显示雷数
		else
		{
			arr2[x][y] = count + '0';
		}
		(*win)++;
	}
}

グリッドをクリックすると、それが雷でない場合は「 」に置き換えられ、その周囲の他のグリッドが再帰的に判断されます。その結果は次のようになります。

 

5. ゲームの勝敗

       サンダーをクリックするとゲームに負けますが、自分のものではないすべてのグリッドをクリックするとゲームが勝つことはすでにわかっています。次のコードでこれを実現します。

	int win = 0;
	if (win == row * col - EASY_COUNT)
		{
			printf("恭喜你,游戏胜利!\n");
			ShowBoard(arr1, ROW, COL);
			break;
		}

win 定数       を定義しそれを 0 に初期化し、それをRecUnfold 関数呼び出しの実際のパラメータとして使用します。これにより、グリッドが開いている限り、win == 71、つまり合計が71になるまでwin は ++ になります。グリッドの数 - 雷の数、ゲームそれだけです。ここで注意すべき点の 1 つは、 win の値を変更するためにRecUnfold 関数を呼び出すため呼び出すときにアドレスを渡し、それを受け取るためにポインターを使用する必要があることです。

4. トータルコード表示

1.ゲーム.h

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9//初始化部分
#define COL 9//初始化部分
#define ROWS ROW+2//数组整体
#define COLS COL+2//数组整体
#define EASY_COUNT 10//雷数
void SetBoard(char arr1[ROWS][COLS], int rows, int cols,char set);//创建棋盘
void ShowBoard(char arr1[ROWS][COLS], int row, int col);//展示棋盘
void SetMine(char arr1[ROWS][COLS], int row, int col);//布雷
void FindMine(char arr1[ROWS][COLS],char arr2[ROWS][COLS], int row, int col);//布雷

2.ゲーム.c

#include"game.h"
void SetBoard(char arr1[ROWS][COLS], int rows, int cols, char set)//创建棋盘
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			arr1[i][j] = set;
		}
	}
}
void ShowBoard(char arr1[ROWS][COLS], int row, int col)//展示棋盘
{
	for (int i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	printf("___________________\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d|", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", arr1[i][j]);
		}
		printf("\n");
	}
}
void SetMine(char arr1[ROWS][COLS], int row, int col)//布雷
{
	int count = EASY_COUNT;
	while(count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (arr1[x][y] == '0')
		{
			arr1[x][y] = '1';
			count--;
		}
	}
}
int  MineNum(char arr1[ROWS][COLS], int x, int y)//统计周围雷数
{
	int count = 0;
	for (int i = x - 1; i <= x + 1; i++)
	{
		for (int j = y - 1; j <= y + 1; j++)
		{
			if (arr1[i][j] == '1')
				count++;
		}
	}
	return count;
}
void RecUnfold(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int x, int y, int* win)//递归炸开
{
	if (x >= 1 && x <= ROWS && y >= 1 && y <= COLS)
	{
		int count = MineNum(arr1, x, y);//获取雷数
		if (count == 0)//四周没有雷时向四周展开
		{
			arr2[x][y] = ' ';//四周都没雷时改为空格
			for (int i = x - 1; i <= x + 1; i++)
			{
				for (int j = y - 1; j <= y + 1; j++)
				{
					if (arr2[i][j] == '*')//只对'*'展开,防止死递归
					{
						RecUnfold(arr1, arr2, i, j, win);
					}
				}
			}
		}
		//四周有雷时显示雷数
		else
		{
			arr2[x][y] = count + '0';
		}
		(*win)++;
	}
}
void FindMine(char arr1[ROWS][COLS], char arr2[ROWS][COLS], int row, int col)//排查雷
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (1)
	{
		printf("请输入要排查的坐标:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (arr1[x][y] == '1')
			{
				printf("很遗憾,踩雷了,游戏结束。\n");
				ShowBoard(arr1, ROW, COL);//展示棋盘	
				break;
			}
			else
			{
				system("cls");
				RecUnfold(arr1, arr2, x, y, &win);
				ShowBoard(arr2, ROW, COL);//展示棋盘
			}
		}
		else
		{
			printf("输入错误,请重新输入:");
		}
		if (win == row * col - EASY_COUNT)
		{
			printf("恭喜你,游戏胜利!\n");
			ShowBoard(arr1, ROW, COL);
			break;
		}

	}
}

3.テスト.c

#include"game.h"
void menu()
{
	printf("**************************************\n");
	printf("************    1.play    ************\n");
	printf("************    0.exit    ************\n");
	printf("**************************************\n");
}
void game()
{
	char arr1[ROWS][COLS];//定义初始化数组
	char arr2[ROWS][COLS];//定义展示数组
	SetBoard(arr1, ROWS, COLS, '0');//创建棋盘
	SetBoard(arr2, ROWS, COLS, '*');//创建棋盘
	SetMine(arr1, ROW, COL);//布雷
	ShowBoard(arr2, ROW, COL);//展示棋盘
	FindMine(arr1, arr2, ROW, COL);//排查雷
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择->:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("开始游戏\n");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input == 1);
	return 0;
}

5. まとめ

       駆け出しの小さなブロガーである私にとって、このゲームを書くのは打ちのめされましたが、幸いなことに、最後まで耐えるために一生懸命働きました。順風満帆どころか、成功に近道はなく、常に困難を乗り越えてこそ、自分自身を打ち破り、プログラミングの道をさらに前進し続けることができます。

       さて、今回の内容はこちらです。お友達もプライベート メッセージにコメントしてください。ブロガーの記事が気に入ったら、忘れずに 3 回クリックしてください。次号でお会いしましょう!

おすすめ

転載: blog.csdn.net/2303_78442132/article/details/132008352
おすすめ