用C语言实现三子棋游戏:从思路到代码

  hello!这里是敲代码的小董,很荣幸您阅读此文,期待您的评论指点和关注,欢迎欢迎~~

✨✨个人主页:敲代码的小董

💗💗系列专栏:C语言

目录

用C语言实现三子棋游戏:从思路到代码

一、引言

1.三子棋游戏概述

2.用C语言开发的意义

二、游戏设计思路

1.棋盘表示

2.游戏流程

游戏开始前:

游戏进行中:

①玩家回合

②电脑回合

游戏结束后:

①结果显示

②菜单显示

三、代码实现前的准备

四、代码实现(部分)

1.初始化函数

2.打印棋盘函数

3.玩家下棋函数

4.电脑下棋函数

5.判断胜负函数 

五、完整代码

1.test.c

2.game.c

3.game.h


一、引言

1.三子棋游戏概述

        三子棋是一种简单而有趣的棋类游戏。游戏在3×3的棋盘上开展,双方分别执“X”和“O”棋子轮流落子。落子过程中,只要一方在横、竖或对角线上成功将三个自己的棋子连成一线,便赢得游戏。若棋盘被棋子填满后,双方均未达成三子连线的情况,就判定为平局。

2.用C语言开发的意义

         开发三子棋程序具有重要的学习意义。从C语言学习角度,它是巩固知识的有效途径。在编写过程中,像定义棋盘数组、运用循环控制下棋顺序、借助条件判断确定胜负等操作,能让学习者深入理解C语言的数组、循环、条件判断等语法知识,将零散的知识点整合成完整知识体系。

二、游戏设计思路

1.棋盘表示

        在C语言中,使用二维数组是一种常见且直观的表示棋盘的方式。

        例如:可以定义一个char类型的二维数组board[3][3]来表示三子棋的棋盘。数组的每一个元素代表棋盘上的一个格子。对于三子棋来说,棋盘是3×3的结构,所以二维数组的大小为3行3列。

char board[ROW][COL];

        其中ROW代表行,COL代表列,这里使用了宏定义的方法定义数组易于修改,在后期可以更改棋盘的大小,语义明确。

#define ROW 3
#define COL 3

2.游戏流程

游戏开始前:

        打印游戏菜单,调用棋盘初始化函数,将棋盘定义为初始状态,即所有格子都为空白。这一步确保游戏开始时棋盘是干净的,准备接受玩家和电脑的下棋操作。

游戏进行中:
①玩家回合
  • 显示当前棋盘状态,通过调用打印棋盘函数,让玩家清楚的看到棋盘上棋子的分布情况。
  • 调用玩家下棋函数,提示玩家输入下棋的位置(行和列)。
  • 玩家输入后,进行输入合法性检查,包括坐标是否在棋盘范围内,所选位置是否为空等。如果输入不合法,提示玩家重新输入,直到输入合法为止。
  • 一旦输入合法,将玩家的棋子放置在棋盘对应的位置上,然后检测是否有玩家获胜或平局的情况,通过调用判断输赢函数。如果游戏结束(玩家胜利或平局),则跳转到游戏结束的处理流程;如果游戏未结束,则进入电脑回合。
②电脑回合
  • 在电脑下棋之前,显示当前棋盘状态,以便玩家了解状况。
  • 调用电脑下棋函数,根据预定的算法确定电脑下棋的位置。
  • 将电脑的棋子放在选定的位置上,然后检测是否有电脑获胜或者平局的情况,通过调用判断输赢函数。如果游戏介绍(电脑获胜或者平局),则跳转到游戏介绍的处理流程;如果游戏未结束,则进入下一个玩家回合。
游戏结束后:
①结果显示

        根据判断输赢函数的结果,如果是玩家获胜,显示玩家获胜的消息;如果是电脑获胜,显示电脑获胜的消息;如果是平局,显示游戏平局的消息。

②菜单显示

        游戏结束后,不论是玩家或者电脑获胜,都在次显示菜单,让玩家选择“重新开始”或者“退出游戏”。如果玩家选择重新开始,回到游戏开始前的初始化步骤;如果玩家选择退出游戏,则结束游戏流程。

三、代码实现前的准备

        在本项目中使用了test.c(源文件)、game.c(源文件)和game.h(头文件)多个文件进行编程,将main()主函数、menu()打印游戏菜单函数、game()游戏函数、test()完成游戏相关逻辑等函数放入test.c源文件中,各种函数的声明、引用、宏等放入game.h头文件中,完成各个功能函数的定义放入game.c原文件中。

在C语言中使用多个.h(头文件)和.c(源文件)进行编程的好处:

  1. 模块化:将功能分开,便于理解和维护。
  2. 代码复用:容易在其他项目中使用已有的代码,减少代码冗余。
  3. 减少编译时间:修改部分代码是,只需要编译相关文件。
  4. 便于并行编译:提高编译速度。

四、代码实现(部分)

1.初始化函数

        这个函数初始化一个类似棋盘的二维字符数组。函数接收数组、行数和列数作参数,然后用两个嵌套的循环遍历数组的每一行和每一列,把每个元素都设成空格,这样就完成了棋盘的初始化,让棋盘开始是空白状态。  

//函数功能:初始化棋盘,将棋盘的每个位置设为空字符
//函数参数:
//char board[ROW][COL]:表示棋盘,是一个二维字符数组
// int row:棋盘的行数
// int col:棋盘的列数
void init_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)				// 外层循环,用于遍历每一行	
	{
		int j = 0;
		for (j = 0; j < col; j++)			// 内层循环,用于遍历当前行的每一列				
			board[i][j] = ' ';				// 将棋盘当前位置设置为空字符,表示未被占用
	}
}

2.打印棋盘函数

        print_board函数用于以一种特定的格式打印棋盘。它接受一个二维字符数组`board`以及表示行数`row`和列数`col`的整数作为参数。在函数内部,通过嵌套的`for`循环来实现棋盘的打印。外层循环遍历每一行,对于每一行,内层循环负责打印该行中的每个字符元素,并且在非最后一列时打印分隔符`|`。每一行的元素打印完成后,如果不是最后一行,会再通过一个循环打印行与行之间的分隔线`---`(同样非最后一列时添加`|`),这样就可以清晰地将棋盘布局以直观的形式显示出来。

//函数功能:打印棋盘
//函数参数:
//char board[ROW][COL]:表示棋盘,是一个二维字符数组
// int row:棋盘的行数
// int col:棋盘的列数
void print_board(char board[ROW][COL], int row, int col)
{
	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");
		}
	}
}

运行结果:

3.玩家下棋函数

        函数player_move是三子棋游戏里玩家下棋的操作函数。函数里有个棋盘,棋盘用`board`表示,棋盘有行有列,行数和列数分别由`row`和`col`确定。

        当玩家下棋的时候,函数会先告诉玩家轮到他下棋了。然后就进入一个循环,一直让玩家输入下棋的坐标,就像告诉程序把棋子下在棋盘的哪个格子里。如果玩家输入的坐标在棋盘范围内,并且这个格子还空着(没被下过棋),那就在这个格子里放上代表玩家的棋子(这里是`*`),这样玩家就下好棋了,循环也就结束了。要是输入的坐标超出棋盘范围,就会提示坐标不对;要是这个格子已经有棋子了,就会告诉玩家这个格子被占了,得重新输入下棋的坐标。

//玩家下棋
void player_move(char board[ROW][COL], int row, int col)
{
	printf("玩家下棋\n");								// 提示玩家开始下棋
	while (1)											// 进入循环,直到玩家成功下棋才会退出循环
	{
		printf("请输入玩家要下棋的坐标:>");				// 提示玩家输入下棋的坐标
		int x = 0, y = 0;
		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");						// 如果输入的坐标不在棋盘范围内,提示坐标非法
		}
	}
}

4.电脑下棋函数

        computer_move函数的功能是实现电脑下棋的操作。函数接收一个表示棋盘的二维字符数组`board`,以及棋盘的行数`row`和列数`col`作为参数。首先它会输出提示语告知正在进行电脑下棋操作,然后进入一个无限循环,在循环中随机生成棋盘范围内的坐标,一旦找到棋盘上为空(以空格表示空位置)的坐标,就在该坐标处下电脑的棋子(用`#`表示),之后便跳出循环,整体实现了电脑在棋盘中随机选择空位置下棋的功能。

// 电脑下棋:随机生成坐标,只要坐标没有被占用,就下棋。
void computer_move(char board[ROW][COL], int row, int col)
{
	printf("请等待,电脑下棋中:\n");	// 输出提示信息,表示电脑正在下棋
	while (1) {						// 进入无限循环,直到找到合适的下棋位置
		int x = rand() % row;		// 随机生成一个在棋盘行数范围内的整数作为横坐标x
		int y = rand() % col;		// 随机生成一个在棋盘列数范围内的整数作为纵坐标y
		
		if (board[x][y] == ' ')		// 判断当前坐标对应的棋盘位置是否为空(' '表示空)
		{
			board[x][y] = '#';		// 如果为空,则在该位置下电脑的棋子,这里用'#'表示电脑棋子
			break;					// 找到合适位置后,跳出循环
		}
	}
}

补充:C语言生成随机数

  1. 包含头文件:包含<stdlib.h>和<time.h>头文件,用于随机数相关操作。
  2. 设置种子:用srand((unsigned int)time(NULL)),根据当前时间设种子,确保每次运行程序生成不同随机数序列(只需要调用一次,根据具体情况,确定调用位置)。
  3. 生成随机数: rand函数生成0到RAND_MAX之间的随机数。
  4. 特定范围随机数:公式(rand()%(max - min + 1))+min`可生成min到max之间的随机数。

5.判断胜负函数 

        函数是用来判断一个棋盘游戏(如井字棋)是否有人获胜的。它检查每一行、每一列和两条对角线,看是否有连续的相同棋子。如果找到这样的情况,就返回那个棋子的类型,表示该玩家赢了。如果没有发现任何玩家获胜的情况,并且棋盘已经填满,那么函数会返回'Q',表示平局。如果既没有玩家获胜,棋盘也没有填满,函数则返回'C',意味着游戏还在进行中。

//判断输赢
//判断输赢的函数要告诉我:电脑赢了?玩家赢了?游戏继续?
//电脑赢:#
//玩家赢:*
//平  局: Q
//游戏继续: C
char is_win(char board[ROW][COL], int row, int col)
{
    int i = 0, j = 0, flag = 0;              // 初始化行、列和标志变量
    char temp;                               // 用于存储当前检查的棋子
    // 判断任意一行是否有相同棋子
    for (i = 0; i < row; i++)
    {
        temp = board[i][0];                  // 获取当前行的第一个棋子

        for (j = 0; j < col; j++)
        {
            if (board[i][j] == temp && temp != ' ')  // 如果当前行的所有棋子都相同且不为空
            {
                flag = 1;                            // 设置标志为1,表示该行有相同棋子
            }
            else
            {
                flag = 0;                            // 否则重置标志为0
                break;                               // 结束内层循环
            }
        }
        if (flag == 1)                               // 如果标志为1,表示该行有相同棋子
        {
            return temp;                             // 返回该行的棋子类型
        }
    }
    // 判断任意一列是否有相同棋子
    for (j = 0; j < col; j++)
    {
        temp = board[0][j];                           // 获取当前列的第一个棋子
        for (i = 0; i < row; i++)
        {
            if (board[i][j] == temp && temp != ' ')   // 如果当前列的所有棋子都相同且不为空
            {
                flag = 1;                             // 设置标志为1,表示该列有相同棋子
            }
            else
            {
                flag = 0;                             // 否则重置标志为0
                break;                                // 结束内层循环
            }
        }
        if (flag == 1)                                // 如果标志为1,表示该列有相同棋子
        {
            return temp;                              // 返回该列的棋子类型
        }
    }
    // 判断对角线 '\'
    temp = board[0][0];                               // 获取左上角的棋子
    for (i = 0; i < row; i++)
    {
        if (board[i][i] == temp && temp != ' ')       // 如果主对角线上的所有棋子都相同且不为空
        {
            flag = 1;                                 // 设置标志为1,表示主对角线有相同棋子
        }

        else
        {
            flag = 0;                                 // 否则重置标志为0
            break;                                    // 结束循环
        }
    }

    if (flag == 1)                                    // 如果标志为1,表示主对角线有相同棋子
    {
        return temp;                                  // 返回主对角线的棋子类型
    }
    // 判断对角线 '/'
    temp = board[row - 1][0];                         // 获取右上角的棋子
    for (i = 0; i < row; i++)
    {                                        // 如果副对角线上的所有棋子都相同且不为空
        if (board[row - 1 - i][i] == temp && temp != ' ') 
        {
            flag = 1;                                 // 设置标志为1,表示副对角线有相同棋子
        }
        else
        {
            flag = 0;                                   // 否则重置标志为0
            break;                                      // 结束循环
        }
    }
    if (flag == 1)                                      // 如果标志为1,表示副对角线有相同棋子
    {
        return temp;                                    // 返回副对角线的棋子类型
    }
    // 判断是否平局
    if (is_full(board, row, col) == 1)                  // 调用is_full函数判断棋盘是否已满
    {
        return 'Q';                                     // 如果棋盘已满,返回'Q'表示平局
    }
    // 继续游戏(没有玩家赢,电脑赢或平局)
    return 'C'; // 返回'C'表示游戏继续
}

五、完整代码

1.test.c
#include"game.h"
void menu()
{
	printf("***********************\n");
	printf("*****   1. play   *****\n");
	printf("*****   0. eixt   *****\n");
	printf("***********************\n");

}
void game()
{
	char board[ROW][COL];
	char ret = 0;
	//初始化棋盘
	init_board(board,ROW,COL);
	//打印棋盘
	print_board(board, ROW, COL);
	while (1)
	{
		//玩家下棋
		player_move(board, ROW, COL);
		Sleep(0);
		system("cls");
		print_board(board, ROW, COL);
		//判断输赢
		ret= is_win(board, ROW, COL);
		if (ret!='C')
		{
			break;
		}
		//电脑下棋
		computer_move(board, ROW, COL);
		Sleep(2000);
		system("cls");
		print_board(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");
	Sleep(2000);
	system("cls");
}


//完成游戏相关逻辑
void test()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Sleep(0);
			system("cls");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}
int main()
{
	test();
	return 0;
}
2.game.c
#include "game.h"
//函数功能:初始化棋盘,将棋盘的每个位置设为空字符
//函数参数:
//char board[ROW][COL]:表示棋盘,是一个二维字符数组
// int row:棋盘的行数
// int col:棋盘的列数
void init_board(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)						// 外层循环,用于遍历每一行	
	{
		int j = 0;
		for (j = 0; j < col; j++)					// 内层循环,用于遍历当前行的每一列				
			board[i][j] = ' ';						// 将棋盘当前位置设置为空字符,表示未被占用
	}
}

//函数功能:打印棋盘
//函数参数:
//char board[ROW][COL]:表示棋盘,是一个二维字符数组
// int row:棋盘的行数
// int col:棋盘的列数
void print_board(char board[ROW][COL], int row, int col)
{
	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], int row, int col)
{
	printf("玩家下棋\n");								// 提示玩家开始下棋
	while (1)											// 进入循环,直到玩家成功下棋才会退出循环
	{
		printf("请输入玩家要下棋的坐标:>");				// 提示玩家输入下棋的坐标
		int x = 0, y = 0;
		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], int row, int col)
{
	printf("请等待,电脑下棋中:\n");					// 输出提示信息,表示电脑正在下棋
	while (1) {										// 进入无限循环,直到找到合适的下棋位置
		int x = rand() % row;						// 随机生成一个在棋盘行数范围内的整数作为横坐标x
		int y = rand() % col;						// 随机生成一个在棋盘列数范围内的整数作为纵坐标y
		
		if (board[x][y] == ' ')						// 判断当前坐标对应的棋盘位置是否为空(' '表示空)
		{
			board[x][y] = '#';						// 如果为空,则在该位置下电脑的棋子,这里用'#'表示电脑棋子
			break;									// 找到合适位置后,跳出循环
		}
	}
}

//判断棋盘是否满了
//希望is_full这个函数只是为了支持is_win函数的,只在is_win函数内使用
//就没有必要在头文件中声明
static int is_full(char board[ROW][COL], int row, int col)
{
	int i = 0, j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			if (board[i][j]==' ')
				return 0;
		}
	}
	return 1;
}

//判断输赢
//判断输赢的函数要告诉我:电脑赢了?玩家赢了?游戏继续?
//电脑赢:#
//玩家赢:*
//平  局: Q
//游戏继续: C
char is_win(char board[ROW][COL], int row, int col)
{
	int i = 0, j = 0, flag = 0;
	char temp;
	//判断任意一行
	for (i = 0; i < row; i++)
	{
		temp = board[i][0];
		for (j = 0; j < col; j++)
		{
			if (board[i][j] == temp && temp != ' ')
			{
				flag = 1;
			}
			else
			{
				flag = 0;
				break;
			}
		}
		if (flag == 1)
			return temp;
	}
	//判断任意一列
	for (j = 0; j < col; j++)
	{
		temp = board[0][j];
		for (i = 0; i < row; i++)
		{
			if (board[i][j] == temp && temp != ' ')
				flag = 1;
			else
			{
				flag = 0;
				break;
			}
		}
		if (flag == 1)
			return temp;
	}
	//判断对角线 '\'
	temp = board[0][0];
	for (i = 0; i < row; i++)
	{
		if (board[i][i] == temp && temp != ' ')
		{
			flag = 1;
		}
		else
		{
			flag = 0;
			break;
		}
	}
	if (flag == 1)
	{
		return temp;
	}
	//判断对角线 '/'
	temp = board[row - 1][0];
	for (i = 0; i < row; i++)
	{
		if (board[row - 1 - i][i] == temp && temp != ' ')
		{
			flag = 1;
		}
		else
		{
			flag = 0;
			break;
		}
	}
	if (flag == 1)
	{
		return temp;
	}
	//平局?
	if (is_full(board, row, col) == 1)
	{
		return 'Q';
	}
	//继续(没有玩家赢,电脑赢 平局)
	return 'C';
}
3.game.h
#define ROW 3
#define COL 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
//头文件中声明函数
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_board(char board[ROW][COL], int row, int col);
//玩家下棋
void player_move(char board[ROW][COL], int row, int col);
//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);
//判断输赢
char is_win(char board[ROW][COL], int row, int col);

        

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值