手把手教你如何实现简易版三子棋

发布于:2022-10-17 ⋅ 阅读:(429) ⋅ 点赞:(0)

 

 

目录

写在前面

实现三子棋小游戏需要具备哪些知识呢?

游戏设计

1.main函数设计

2.menu函数的设计

3.game函数的设计

3.1InitBoard函数

3.2DisplayBoard函数

3.3player_move函数

3.4computer_move函数

3.4.1随机坐标的生成

3.5is_win函数

3.5.1is_is_full

完整源码


写在前面


大家是否曾经和同桌玩过三子棋小游戏呢? 

也许大家都玩过这个游戏,但是对它的称呼却并不相同,那首先带大家回忆一下玩法和规则

看到这些九宫格,有没有感觉到死去的记忆突然攻击你呢?

今天我们就试着用c语言来实现这个小游戏。

实现三子棋小游戏需要具备哪些知识呢?


1.分支与循环语句

2.函数

3.数组

没错,当我们能简单的使用上述知识,就可以来试着写一个三子棋小游戏了!

如果觉得上述知识没有简单掌握的话,请参照我之前的博客,里面有庖丁解牛式讲解。

游戏设计


游戏设计我们采用多文件的形式:

1.test.c          //只包含main()、menu()、game()

2.game.c      //用于定义游戏需要的各个函数

3.game.h     //用于包含各种头文件和函数声明

1.main函数设计

首先,我们需要向玩家展示一份游戏菜单,如下图:

 玩家此时可以选择

1.开始游戏

0.退出游戏

而且每结束一局游戏游戏后,都会再次打印菜单,所以此处需要一个循环。

那么三种循环语句我们该如何选择呢?

为了确保程序一运行之后,就能打印菜单,我们选择do......while循环

do

{
         menu();//为了代码的整洁,我们设计一个单独的menu函数来打印菜单

}while(...)

 之后我们还需要定义一个变量input来接收玩家的选择

int input = 0;

printf("请选择:>");
scanf("%d", &input);

那么还有个问题,我们do......while该何时停止呢?

此时有个巧妙的设计,当玩家输入“0”来选择退出游戏是,循环就应该停止,那不妨就用input来控制循环,如下:

int main()
{
    int input = 0;
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);

        .........
    } while (input);
    return 0;
}

接下来我们根据玩家输入的选择来实现菜单中的功能

switch (input)
        {
        case 1:
            game();//进入游戏,我们用另外一个函数game()单独实现
            break;
        case 0:
            printf("退出游戏\n");
            break;
        default:
            printf("选择错误,请重新输入\n");//对于选项以外的输入,给出提示
            break;
        }

 但这里我们main函数的主体已经实现完毕。

来看看完整代码:

int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新输入\n");
			break;
		}
	} while (input);
    return 0;
}

2.menu函数的设计

menu()函数,我们只需要用它将菜单的内容打印到屏幕上即可,所以它的实现很简单:

void menu()
{
	printf("******三子棋*******\n");
	printf("*******************\n");
	printf("******1.start******\n");
	printf("******0.exit ******\n");
	printf("*******************\n");
	printf("*******************\n");
}

3.game函数的设计

 如图,玩家和电脑每走一步,都会将棋盘打印出来,因此,我们需要用一个3*3的二维数组来存放棋子。

char board[ROW][COL];//ROW为行数,COL为列数

由于在后面的设计中,我们会多次用到行数和列数,我们干脆在game.h中用define定义ROWCOL。今后如果想设计四子棋,五子棋,十子棋等等,我们只需改变ROWCOL即可。

#define ROW  3
#define COL 3

仔细观察棋盘,当某个位置没有落子时,它是空白的。所以我们首先得初始化一下二维数组board,使其内容都为空格。这里我们用一个函数InitBoard来实现数组的初始化。

3.1InitBoard函数

 数组的初始化也很简单,通过for循环的嵌套遍历整个数组即可实现

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

InitBoard是设计游戏内容的函数,所以我们将它在game.h中声明,并在game.c中定义。

接着,玩家游戏开始前,我们最好先将棋盘打印一下,方便玩家选择坐标进行落子,

所以我们需要设计一个打印棋盘的函数。

3.2DisplayBoard函数

计算机里没有提供棋盘的图案,所以这需要我们手动来设计。

为了简便我们用' | '' - '来“拼凑”一个棋盘大概长这个样子:

 那么我们简单分析一下这个棋盘的组成

 注意红色圈圈里的其实是空格,而最中间的红色圈圈就是将来要落子的位置。

因此棋盘就是数组元素与' | '' -  '组成的,其中2、4为分割线。那我们直接打印就好:

void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
    for (int i = 0; i < row; i++)
	{
		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
		if(i<row-1)
			printf("---|---|---\n");
	}
}

这样我们就可以成功的打印3*3的棋盘了。但有一点小小的瑕疵就是,如果将来我们修改ROW

COL的值,想要打印10*10的棋盘,显然此刻的 DisplayBoard 不能完成。我们可以试着改造一个

动态的、大小可变的棋盘打印函数,如下:

void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
	/*for (int i = 0; i < row; i++)
	{
		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
		if(i<row-1)
			printf("---|---|---\n");
	}*/
	int i = 0;
	for (i = 0; i < row; i++)
	{
        //打印棋子和' | '
		int j = 0;
		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");
	}
}

DisplayBoard是设计游戏内容的函数,所以我们将它在game.h中声明,并在game.c中定义。 

到目前为止,我们的game()函数已经完善到这步:

void game()
{
    char board[ROW][COL];
    InitBoard(board, ROW, COL);//初始化棋盘
    DisplayBoard(board, ROW, COL);//打印棋盘
    

//下棋
    ......
}

 接下来就是最重要的环节,下棋的部分了。

我们默认让玩家先手,这是我们设计一个函数来实现玩家下棋。

3.3player_move函数

玩家下棋时,首先我们给出提示玩家该下棋了:

printf("玩家下棋:>\n");

 下棋其实本质就是,将棋子的坐标保存到数组中,在通过DisplayBoard打印。

当然,在落子之前,我们必须得判断棋子的坐标是否合理。比如:

1.该坐标是不是已经被其他棋子占用

2.该坐标是否超出了棋盘的范围

当玩家不小心下到了不合理的位置,我们要给出提示,并让他重新选择下棋的坐标,直到下在正确

的位置,所以,此处应该运用到循环。

注意:大部分玩家并非程序员,所以他们可能并不知道数组的下标是从0开始的。所以我们需要将

玩家输入的坐标减一之后再使用。

void player_move(char board[ROW][COL], const int row, const int col)
{
	int x = 0;
	int y = 0;
	printf("玩家下棋:>\n");
	while (1)
	{
		printf("请输入坐标:>");
		scanf("%d %d", &x,&y);
        //判断下棋坐标是否超出棋盘范围
        //如果合理,break结束循环
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
            //判断该坐标是否已有棋子
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}
			else
				printf("该坐标已被占用,请重新输入\n");
		}
		else
		{
			printf("超出棋盘范围,请重新输入\n");
		}
	}
}

3.4computer_move函数

玩家落一子后轮到电脑下棋了,电脑是不会自主思考的,这里我们就不要求AI的方式下棋了。

我们让电脑在一个随机的坐标下棋即可。

电脑下棋时,我们照样得判断它随机生成的坐标是否合法。但是却不用提醒它“你下错了”,我们就

让它不停的尝试,直到找到正确的位置。此处依旧需要一个while循环。

void computer_move(char board[ROW][COL], const int row, const int col)
{
    printf("电脑下棋:>\n");
    while (1)
    {
        int x = ...//该怎么赋值?
        int y = ...
        if (board[x][y] == ' ')
        {
            board[x][y] = '#';
            break;
        }
    }
}

但这里我们怎么让电脑生成随机坐标的?

3.4.1随机坐标的生成

这里我们将认识一个函数 rand ,它的作用就是生成一个随机数

我们这里不详细介绍它的原理,只是简单介绍一下使用方法:

使用rand函数之前,我们得包含stdlib.h的头文件。

最重要的一步,我们要在main()函数内部加上这句代码:

srand((unsigned int)time(NULL));

//别忘记包含time()函数所在的头文件  time.h

 这样做之后,我们就可以使用rand函数了。 

既然是随机数,我们就通过对随机数模3的操作,让它的值落在[0,3)的区间内。

int x = rand() % row;
int y = rand() % col;

 然后仿照玩家下棋的逻辑,我们就顺利完成电脑下棋的函数。

void computer_move(char board[ROW][COL], const int row, const int col)
{
	printf("电脑下棋:>\n");
	while (1)
	{
		int x = rand() % row;
		int y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

至此,玩家和电脑都会下棋了,我们只需要通过while循环让他俩一人一子即可,每落一子后将棋盘再展示一遍。

void game()
{
    char ret = 0;
    char board[ROW][COL];
    InitBoard(board, ROW, COL);
    DisplayBoard(board, ROW, COL);
    //下棋
    while (1)
    {
        player_move(board, ROW, COL);//玩家下棋
        DisplayBoard(board, ROW, COL);
        

        computer_move(board, ROW, COL);//电脑下棋
        DisplayBoard(board, ROW, COL);
    }

}

但到这里还没有结束,因为我们会发现一会儿程序就会进入死循环,因为棋盘下满后,电脑一直在

生成随机数妄图找到正确的位置,却找不到。

按照正常的逻辑,当一方获胜后,游戏将不在进行,并宣布某一方获胜。

当棋盘下满后,无任何一方获胜,就宣布平局。

那我们该怎么实现这样的逻辑呢?

3.5is_win函数

当玩家或电脑每落下一子后,我们都应该判断一下,是否一方获胜,或者平局,若无任一情况,则

游戏继续。

 如图所示,游戏获胜有四种情况:

1.任意一行三个棋子相同

2.任意一列三个棋子相同

3.主对角线三个棋子相同

4.次对角线三个棋子相同

若玩家获胜则返回' * ',否则返回' # '

若平局则返回'Q',

若都未发生则返回'c'

 那么我们分别来进行判断:

//每一行各棋子是否相同
    for (i = 0; i < row; i++)
    {
        int flag = 1;
        for (j = 0; j < col - 1; j++)
        {
            if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
                flag = 0;
        }
        if (flag == 1)
            return board[i][0];
    }

 //每一列各棋子是否相同
    for (i = 0; i < col; i++)
    {
        int flag = 1;
        for (j = 0; j < row - 1; j++)
        {
            if (board[j][i] != board[j+1][i] || board[j][i] == ' ')
                flag = 0;
        }
        if (flag == 1)
            return board[0][i];
    }

 //主对角线各棋子是否相同
    while (board[i][j]!=' '&&board[i][j] == board[i+1][j+1])
    {
        i++;
        j++;
        if (i == row-1 || j == col-1)
            return board[0][0];
    }

 //次对角线个棋子是否相同
    i = 0;
    j = 0;
    while (board[i][col-1-j] != ' ' && board[i][col-1-j] == board[i + 1][col-2-j])
    {
        i++;
        j++;
        if (i == row - 1 || j == col - 1)
            return board[0][col-1];
    }

 四种获胜情况判断完之后,就要考虑是否平局了

那么我们通过函数is_full来判断棋盘是否下满

3.5.1is_is_full

又是一次简单的数组遍历

//判断棋盘是否下满
//如果下满就返回1,否则返回0
static int is_full(char board[ROW][COL], const int row, const int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            if (board[i][j] == ' ')
                return 0;
        }
    }
    return 1;
}
//是否平局
if (is_full(board, ROW, COL)==1)
{
	return 'Q';
}

当然上述内容都在is_win函数中现在来看一下完整的is_win函数:

char is_win(char board[ROW][COL], const int row, const int col)
{
	int i = 0;
	int j = 0;
	//每一行各棋子是否相同
	for (i = 0; i < row; i++)
	{
		int flag = 1;
		for (j = 0; j < col - 1; j++)
		{
			if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
				flag = 0;
		}
		if (flag == 1)
			return board[i][0];//直接返回棋子的内容就知道是谁赢
	}
	//每一列各棋子是否相同
	for (i = 0; i < col; i++)
	{
		int flag = 1;
		for (j = 0; j < row - 1; j++)
		{
			if (board[j][i] != board[j+1][i] || board[j][i] == ' ')
				flag = 0;
		}
		if (flag == 1)
			return board[0][i];//直接返回棋子的内容就知道是谁赢
	}
	//主对角线各棋子是否相同
	while (board[i][j]!=' '&&board[i][j] == board[i+1][j+1])
	{
		i++;
		j++;
		if (i == row-1 || j == col-1)
			return board[0][0];//直接返回棋子的内容就知道是谁赢
	}
	//次对角线个棋子是否相同
	i = 0;
	j = 0;
	while (board[i][col-1-j] != ' ' && board[i][col-1-j] == board[i + 1][col-2-j])
	{
		i++;
		j++;
		if (i == row - 1 || j == col - 1)
			return board[0][col-1];//直接返回棋子的内容就知道是谁赢
	}
	//是否平局
	if (is_full(board, ROW, COL)==1)
	{
		return 'Q';
	}
	return 'c';
}

 到这里,我们在回去继续完善game()

每落一子后,进行判断,若is_win返回'c'则游戏继续,否则意味着游戏结束,退出循环,之后就可

以判断是否任何一方获胜或者平局,我们这样实现这个逻辑:

void game()
{
	char ret = 0;
	char board[ROW][COL];
	InitBoard(board, ROW, COL);
	DisplayBoard(board, ROW, COL);
	//下棋
	while (1)
	{
		player_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		
		ret = is_win(board, ROW, COL);
		if (ret != 'c')
			break;
		
		computer_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		
		ret = is_win(board, ROW, COL);
		if (ret != 'c')
			break;
		
	}
	if (ret == '*')
		printf("玩家赢\n");
	else if (ret == '#')
		printf("电脑赢\n");
	else if (ret == 'Q')
		printf("平局\n");
	DisplayBoard(board, ROW, COL);
}

至此,整个游戏的逻辑已经全部实现完毕!赶紧玩一下自己做的小游戏吧,并试着找找bug。

完整源码

//test.c
#define _CRT_SECURE_NO_DEPRECATE  1
#include"game.h"

void menu()
{
	printf("******三子棋*******\n");
	printf("*******************\n");
	printf("******1.start******\n");
	printf("******0.exit ******\n");
	printf("*******************\n");
	printf("*******************\n");
}
void game()
{
	char ret = 0;
	char board[ROW][COL];
	InitBoard(board, ROW, COL);
	DisplayBoard(board, ROW, COL);
	//下棋
	while (1)
	{
		player_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		
		ret = is_win(board, ROW, COL);
		if (ret != 'c')
			break;
		
		computer_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		
		ret = is_win(board, ROW, COL);
		if (ret != 'c')
			break;
		
	}
	if (ret == '*')
		printf("玩家赢\n");
	else if (ret == '#')
		printf("电脑赢\n");
	else if (ret == 'Q')
		printf("平局\n");
	DisplayBoard(board, ROW, COL);
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新输入\n");
			break;
		}
	} while (input);
    return 0;
}
//game.c
#define _CRT_SECURE_NO_DEPRECATE  1
#include"game.h"

//初始化棋盘
void InitBoard(char board[ROW][COL], const int row, const int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ' ;
		}
	}
}
//打印棋盘
void DisplayBoard(char board[ROW][COL], const int row, const int col)
{
	/*for (int i = 0; i < row; i++)
	{
		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
		if(i<row-1)
			printf("---|---|---\n");
	}*/
	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		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 player_move(char board[ROW][COL], const int row, const int col)
{
	int x = 0;
	int y = 0;
	printf("玩家下棋:>\n");
	while (1)
	{
		printf("请输入坐标:>");
		scanf("%d %d", &x,&y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}
			else
				printf("该坐标已被占用,请重新输入\n");
		}
		else
		{
			printf("超出棋盘范围,请重新输入\n");
		}
	}
}
//电脑下棋
void computer_move(char board[ROW][COL], const int row, const int col)
{
	printf("电脑下棋:>\n");
	while (1)
	{
		int x = rand() % row;
		int y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}
//判断棋盘是否下满
static int is_full(char board[ROW][COL], const int row, const int col)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == ' ')
				return 0;
		}
	}
	return 1;
}
//判断棋盘状态
char is_win(char board[ROW][COL], const int row, const int col)
{
	int i = 0;
	int j = 0;
	//每一行各棋子是否相同
	for (i = 0; i < row; i++)
	{
		int flag = 1;
		for (j = 0; j < col - 1; j++)
		{
			if (board[i][j] != board[i][j + 1] || board[i][j] == ' ')
				flag = 0;
		}
		if (flag == 1)
			return board[i][0];
	}
	//每一列各棋子是否相同
	for (i = 0; i < col; i++)
	{
		int flag = 1;
		for (j = 0; j < row - 1; j++)
		{
			if (board[j][i] != board[j+1][i] || board[j][i] == ' ')
				flag = 0;
		}
		if (flag == 1)
			return board[0][i];
	}
	//主对角线各棋子是否相同
	while (board[i][j]!=' '&&board[i][j] == board[i+1][j+1])
	{
		i++;
		j++;
		if (i == row-1 || j == col-1)
			return board[0][0];
	}
	//次对角线个棋子是否相同
	i = 0;
	j = 0;
	while (board[i][col-1-j] != ' ' && board[i][col-1-j] == board[i + 1][col-2-j])
	{
		i++;
		j++;
		if (i == row - 1 || j == col - 1)
			return board[0][col-1];
	}
	//是否平局
	if (is_full(board, ROW, COL)==1)
	{
		return 'Q';
	}
	return 'c';
}
//game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.h>

#define ROW  3
#define COL 3

//初始化棋盘
void InitBoard(char board[ROW][COL], const int row, const int col);
//打印棋盘
void DisplayBoard(char board[ROW][COL], const int row, const int col);
//玩家下棋
void player_move(char board[ROW][COL], const int row, const int col);
//电脑下棋
void computer_move(char board[ROW][COL], const int row, const int col);
//判断棋盘状态
char is_win(char board[ROW][COL], const int row, const int col);

 感谢观看!

 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到