【C语言小游戏】实现N子棋游戏

测了玩家和电脑连成行、列、两个对角线、平局的结果都没问题。
修改行列可做到N字棋,根据行列动态判断行、列、两边对角线的输赢条件。

项目设计

虽然只是一个小游戏,但还是有几百行代码的,而且为了 看起来有条理,这里分成了三个文件。
1、test.c:main函数、其它主要的函数调用。
2、game.h:所有头文件的引入;常量定义、函数声明都统一在这里,test.c文件包含该头文件即可。
3、game.c:game.h中声明的函数在这里实现。

游戏设计

1、*是玩家的棋子;
2、#是电脑的棋子;
3、棋盘由二维字符数组实现,初始时全部元素为空字符;
4、为了看起来轻松,使用了一些符号 - | 对行、列进行了分隔。

菜单选择

这个比较简单,就是上来就先打印一个简易的菜单,这个比较直观就不放效果图了。

static void menu()
{
	printf("-------------------------------------\n");
	printf("-------------1.开始游戏--------------\n");
	printf("-------------------------------------\n");
	printf("-------------0.退出游戏-------------\n");
	printf("-------------------------------------\n");
	printf("-------------9.清理屏幕-------------\n");
	printf("-------------------------------------\n");
}

int main()
{
	int in;
	do
	{
		menu();
		scanf("%d", &in);
		switch (in)
		{
		case 0:
			printf("Notification:退出游戏.\n");
			break;
		case 1:
			printf("Notification:开始游戏.\n");
			game(); // 最主要的函数
			break;
		case 9:
			printf("Notification:清理屏幕.\n");
			system("cls");
			break;
		default:
			printf("Notification:×没有该选项.\n");
			break;
		}
	} while (in); // 0才退出,其它选项继续
	return 0;
}

玩家输入选择,switch处理对应逻辑,输入值顺便还可以作为循环结束的条件。

开始游戏

作为棋盘类游戏,那么首先想到的肯定是使用二维数组作为棋盘,棋盘大小使用常量控制。

#define ROW 3
#define COL 3
static void game()
{
	char board[ROW][COL];
	initBoard(board);
	displayBoard(board);
}

有了棋盘,那么再就是理所应当地对其初始化,前面说过初始化为空字符。

void initBoard(char board[ROW][COL])
{
	int i, j;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			board[i][j] = ' ';
		}
	}
}

初始化完之后再把棋盘打印,不过由于都是空字符,所以同时为了看起来轻松
或美观,用了一些字符进行了行、列的分隔,这样看得出来棋盘内坐标。

void displayBoard(char board[ROW][COL])
{
	int i, j;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < COL - 1) // 最后一个棋子后面没棋子了,不需要再分隔了
			{
				printf("|"); // 每打印一个棋子就用 | 分隔
			}
		}
		printf("\n");
		if (i < ROW - 1) // 最后一行棋子后面没有行了,不需要再分隔
		{
			for (j = 0; j < COL; j++)
			{
				printf("---"); // 一行棋子打印完后用 --- 分隔
				if (j < COL - 1) // 最后一列不需要分隔
				{
					printf("|"); // 分隔每一列
				}
			}
		}
		printf("\n");
	}
}

棋盘分隔代码,还是有一些名堂在里面。
如最后每行最后一列不需要 | ,后面已经没有东西分隔了;
再就是最后一行不需要 — 进行分隔了,因为已经是最后一行。
不过怎么分割棋盘,分隔的方式对代码逻辑影响很大,到底该怎么打印还是要看怎么分隔。

棋盘分隔后效果如图:
【C语言小游戏】实现N子棋游戏_第1张图片
如果不进行分隔,那么打印出来的就是空白嘛,看也看不见棋盘,
玩的时候还要记住之前落子的下标,以及对方落子的下标,这个难度就很大了。

玩家落子

下子不是下一个子游戏就完了,所以要用循环,至于什么时候结束循环后面再去写相应逻辑判断。

static void game()
{
	char board[ROW][COL];
	initBoard(board);
	displayBoard(board);
	while (1)
	{
		playerMove(board);
	}
}

首先玩家需要输入x,y坐标。要注意的是坐标的合法性,
还有玩家不是程序员,所以这里的坐标都是从1开始,后续使用时再-1即可。

void playerMove(char board[ROW][COL])
{
	int x, y;
	while (1)
	{
		printf("Notification:玩家输入x、y坐标下棋子 > ");
		scanf("%d %d", &x, &y);
		// 玩家不知道从0开始
		if (x >= 1 && x <= ROW
			&& y >= 1 && y <= COL
			&& board[--x][--y] == ' ') // 同时是空的才能落子,-1符合我们的逻辑
		{
			// 所以要-1再放入棋子(判断部分已经--)
			board[x][y] = '*';
			displayBoard(board); // 落子后显示棋盘
			break;
		}
		else // 提示继续输入正确的坐标
		{
			printf("Notification:×坐标错误,请重新输入合适的坐标。\n");
			printf("错误原因:可能是该坐标不在范围内,或已经有棋子落下。\n");
			displayBoard(board);
		}
	}
}

电脑落子

static void game()
{
	char board[ROW][COL];
	initBoard(board);
	displayBoard(board);
	while (1)
	{
		playerMove(board);
		computerMove(board);
	}
}

大家很容易想到的是生成随机数作为下标,同时这样也是最容易实现的。

不过在C语言中使用随机数函数,还是有一些名堂在里面的。

void computerMove(char board[ROW][COL])
{
	printf("Notification:电脑下棋子 > ");
	int x, y;
	while (1)
	{
		x = rand() % ROW; // 取余生成0到ROW-1的x下标,和0到COL-1的y坐标
		y = rand() % COL; 
		if (board[x][y] == ' ') // 同样需要改坐标为空才能落子,否则继续生成新坐标
		{
			// +1正常显示给玩家看,玩家是不知道从0开始的。
			printf("%d %d\n", x + 1, y + 1);
			board[x][y] = '#';
			displayBoard(board);
			break;
		}
	}
}

rand()即生成随机数的函数,不过使用它有前提。

1、引入 stdlib.h 头文件;

2、在rand()使用前调用srand()函数初始化随机数,要注意的是该函数只需要执行一次,不要放在循环中,不然每次函数执行后,随机数会被固定产生固定的值。

3、往srand()函数传入一个动态的值,这里使用的是time()函数产生时间戳,时间戳无时不刻在变化,非常适合。time()函数中传空指针即可,同时将返回值强转成无符号整型以适合srand()形参类型。

int main()
{
	// 只需要执行一次,不要放在循环中,不然每次函数执行后,随机数会被固定产生固定的值。
	srand((unsigned int)time(NULL)); 
	int in;
	do
	{
		menu();
		printf("Notification:请选择 > ");
		scanf("%d", &in);
		switch (in)
		{
			... 
		}

	} while (in);
	return 0;
}

判断输赢

每次落子后都要进行判断。

static void game()
{
	char board[ROW][COL];
	initBoard(board);
	displayBoard(board);
	while (1)
	{
		playerMove(board);
		if (getResult(doesWin(board)) == 0)
		{
			break;
		}
		computerMove(board);
		if (getResult(doesWin(board)) == 0)
		{
			break;
		}
	}
}

doesWin()函数才是整个游戏的核心,根据棋盘大小进行了动态判断,不仅仅局限于三子棋。

/*
返回字符:
	'*' - 玩家赢
	'#' - 电脑赢
	'0' - 平局
	'1' - 继续游戏
*/
char doesWin(char board[ROW][COL])
{
	int i, j;

	// 玩家或电脑的棋子分别在棋盘上行、列、对角线的棋子个数
	int playerCount = 0,
		computerCount = 0;

	// 1.行判断
	for (i = 0; i < ROW; i++)
	{	// 一行结束,重新置0
		playerCount = 0;
		computerCount = 0;
		for (j = 0; j < COL; j++)
		{
			if (board[i][j] == '*')
			{
				playerCount++;
			}
			else if (board[i][j] == '#')
			{
				computerCount++;
			}
		}
		// 虽然是行判断,但由该行上的列数棋子决定有没有赢
		if (playerCount == COL || computerCount == COL)
		{   // 上面最后一次循环完了后,j还进行了++。
			return board[i][j - 1]; // 要么*要么#,无所谓。
		}
	}

	// 2.列判断(循环条件、数组下标和以往不同思维)
	for (i = 0; i < COL; i++)
	{
		// 既是对上面行判断留下的结果置0,同时也将列判断无效的结果置0
		playerCount = 0;
		computerCount = 0;
		for (j = 0; j < ROW; j++)
		{   // j当做行,i当做列(一趟循环固定不变)
			if (board[j][i] == '*')
			{
				playerCount++;
			}
			else if (board[j][i] == '#')
			{
				computerCount++;
			}
		}
		// 虽然是列判断,但由每行上的固定列数的棋子决定输赢
		if (playerCount == ROW || computerCount == ROW)
		{	// 上面循环最后i++了一下,要-1。
			return board[j - 1][i];
		}
	}

	// 3.从左向右下角的对角线判断 - [0][0] [1][1] [2][2]
	playerCount = 0;
	computerCount = 0;
	for (i = 0; i < ROW; i++)
	{
		if (board[i][i] == '*')
		{
			playerCount++;
		}
		else if (board[i][i] == '#')
		{
			computerCount++;
		}
	}
	if (playerCount == ROW || computerCount == ROW)
	{
		return board[0][0];
	}

	// 4.从右向左下角的对角线判断 - [0][2] [1][1] [2][0]
	playerCount = 0;
	computerCount = 0;
	for (i = 0; i < ROW; i++)
	{	// 这个对角线,只需要考虑列下标的变化
		if (board[i][COL - i - 1] == '*')
		{
			playerCount++;
		}
		else if (board[i][COL - i - 1] == '#')
		{
			computerCount++;
		}
	}
	if (playerCount == ROW || computerCount == ROW)
	{
		return board[0][COL - 1];
	}

	// 5.平局判断(前面任何一方没赢,后面棋盘上判断是否还有空)
	int spaceCount = 0;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (board[i][j] == ' ')
			{
				spaceCount++;
			}
		}
	}
	if (!spaceCount) // 棋盘内1个空都没有,平局
	{
		return '0';
	}

	// 6.都没赢,也没平局,那么游戏继续
	return '1';
}

getResult()函数只是对doesWin()函数的返回结果进行了处理。

int getResult(char sign)
{
	if (sign == '1')
	{
		return 1;
	}
	else if (sign == '*')
	{
		printf("Notification:玩家赢。\n");
	} 
	else if (sign == '#')
	{
		printf("Notification:电脑赢。\n");
	}
	else if (sign == '0') {
		printf("Notification:平局。\n");
	}
	return 0;
}

很明显,无论是电脑玩家赢,还是平局,游戏都不再继续,返回0。

所有代码

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
/*
	test.c - 函数调用
*/

static void menu()
{
	printf("-------------------------------------\n");
	printf("-------------1.开始游戏--------------\n");
	printf("-------------------------------------\n");
	printf("-------------0.退出游戏-------------\n");
	printf("-------------------------------------\n");
	printf("-------------9.清理屏幕-------------\n");
	printf("-------------------------------------\n");
}

int getResult(char sign)
{
	if (sign == '1')
	{
		return 1;
	}
	else if (sign == '*')
	{
		printf("Notification:玩家赢。\n");
	} 
	else if (sign == '#')
	{
		printf("Notification:电脑赢。\n");
	}
	else if (sign == '0') {
		printf("Notification:平局。\n");
	}
	return 0;
}

static void game()
{
	char board[ROW][COL];
	initBoard(board);
	displayBoard(board);
	while (1)
	{
		playerMove(board);
		if (getResult(doesWin(board)) == 0)
		{
			break;
		}
		computerMove(board);
		if (getResult(doesWin(board)) == 0)
		{
			break;
		}
	}
}

int main()
{
	srand((unsigned int)time(NULL));
	printf("Notification:请选择 > ");
	int in;
	do
	{
		menu();
		scanf("%d", &in);
		switch (in)
		{
		case 0:
			printf("Notification:退出游戏.\n");
			break;
		case 1:
			printf("Notification:开始游戏.\n");
			game();
			break;
		case 9:
			printf("Notification:清理屏幕.\n");
			system("cls");
			break;
		default:
			printf("Notification:×没有该选项.\n");
			break;
		}
	} while (in);
	return 0;
}
#pragma once
/*
	game.h - 头文件引入、常量、函数声明
*/

// 棋盘大小 - 行和列
#define ROW 3
#define COL 3

#include 
#include 
#include 
#include 

// 显示菜单
static void menu();

// 初始化棋盘
void initBoard(char board[ROW][COL]);

// 显示棋盘
void displayBoard(char board[ROW][COL]);

// 玩家落子
void playerMove(char board[ROW][COL]);

// 电脑落子
void computerMove(char board[ROW][COL]);

// 判断输赢
char doesWin(char board[ROW][COL]);
#define _CRT_SECURE_NO_WARNINGS 1
/*
	game.c - 函数实现
*/
#include "game.h"

// 初始化棋盘
void initBoard(char board[ROW][COL])
{
	int i, j;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			board[i][j] = ' ';
		}
	}
}

// 显示棋盘
void displayBoard(char board[ROW][COL])
{
	int i, j;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			printf(" %c ", board[i][j]);
			if (j < COL - 1)
			{
				printf("|");
			}
		}
		printf("\n");
		if (i < ROW - 1)
		{
			for (j = 0; j < COL; j++)
			{
				printf("---");
				if (j < COL - 1)
				{
					printf("|");
				}
			}
		}
		printf("\n");
	}
}

// 玩家落子
void playerMove(char board[ROW][COL])
{
	int x, y;
	while (1)
	{
		printf("Notification:玩家输入x、y坐标下棋子 > ");
		scanf("%d %d", &x, &y);
		// 玩家不知道从0开始
		if (x >= 1 && x <= ROW
			&& y >= 1 && y <= COL
			&& board[--x][--y] == ' ')
		{
			// 所以要-1再放入棋子(判断部分已经--)
			board[x][y] = '*';
			displayBoard(board);
			break;
		}
		else
		{
			printf("Notification:×坐标错误,请重新输入合适的坐标。\n");
			printf("错误原因:可能是该坐标不在范围内,或已经有棋子落下。\n");
			displayBoard(board);
		}
	}
}

// 电脑落子
void computerMove(char board[ROW][COL])
{
	printf("Notification:电脑下棋子 > ");
	int x, y;
	while (1)
	{
		x = rand() % ROW;
		y = rand() % COL;
		if (board[x][y] == ' ')
		{
			// +1正常显示,玩家是不知道从0开始的。
			printf("%d %d\n", x + 1, y + 1);
			board[x][y] = '#';
			displayBoard(board);
			break;
		}
	}
}

/*
返回字符:
	'*' - 玩家赢
	'#' - 电脑赢
	'0' - 平局
	'1' - 继续游戏
*/
// 判断输赢
char doesWin(char board[ROW][COL])
{
	int i, j;

	// 玩家或电脑的棋子分别在棋盘上行、列、对角线的棋子个数
	int playerCount = 0,
		computerCount = 0;

	// 行判断
	for (i = 0; i < ROW; i++)
	{	// 一行结束,重新置0
		playerCount = 0;
		computerCount = 0;
		for (j = 0; j < COL; j++)
		{
			if (board[i][j] == '*')
			{
				playerCount++;
			}
			else if (board[i][j] == '#')
			{
				computerCount++;
			}
		}
		// 虽然是行判断,但由该行上的列数棋子决定有没有赢
		if (playerCount == COL || computerCount == COL)
		{   // 上面最后一次循环完了后,j还进行了++。
			return board[i][j - 1]; // 要么*要么#,无所谓。
		}
	}

	// 列判断(循环条件、数组下标和以往不同思维)
	for (i = 0; i < COL; i++)
	{
		// 既是对上面行判断留下的结果置0,同时也将列判断无效的结果置0
		playerCount = 0;
		computerCount = 0;
		for (j = 0; j < ROW; j++)
		{   // j当做行,i当做列(一趟循环固定不变)
			if (board[j][i] == '*')
			{
				playerCount++;
			}
			else if (board[j][i] == '#')
			{
				computerCount++;
			}
		}
		// 虽然是列判断,但由每行上的固定列数的棋子决定输赢
		if (playerCount == ROW || computerCount == ROW)
		{	// 上面循环最后i++了一下,要-1。
			return board[j - 1][i];
		}
	}

	// 从左向右下角的对角线判断 - [0][0] [1][1] [2][2]
	playerCount = 0;
	computerCount = 0;
	for (i = 0; i < ROW; i++)
	{
		if (board[i][i] == '*')
		{
			playerCount++;
		}
		else if (board[i][i] == '#')
		{
			computerCount++;
		}
	}
	if (playerCount == ROW || computerCount == ROW)
	{
		return board[0][0];
	}

	// 从右向左下角的对角线判断 - [0][2] [1][1] [2][0]
	playerCount = 0;
	computerCount = 0;
	for (i = 0; i < ROW; i++)
	{
		if (board[i][COL - i - 1] == '*')
		{
			playerCount++;
		}
		else if (board[i][COL - i - 1] == '#')
		{
			computerCount++;
		}
	}
	if (playerCount == ROW || computerCount == ROW)
	{
		return board[0][COL - 1];
	}

	// 平局判断(前面任何一方没赢,后面棋盘上判断是否还有空)
	int spaceCount = 0;
	for (i = 0; i < ROW; i++)
	{
		for (j = 0; j < COL; j++)
		{
			if (board[i][j] == ' ')
			{
				spaceCount++;
			}
		}
	}
	if (!spaceCount) // 棋盘内1个空都没有
	{
		return '0';
	}

	// 游戏继续
	return '1';
}

你可能感兴趣的:(C语言,c语言,游戏)