三子棋--游戏实现

Hello,大家好!!!这里是小周为您带来的呕心沥血之作------C语言秘籍!!

C语言秘籍分为初阶和高阶两部!!跟着小周学定会让你C语言功力大成,称霸武林,话不多说,我们接着上回继续开讲!!!!!

一、实现逻辑

1、游戏不退出,继续玩下一把(循环)

2、应用多文件的形式写代码

二、代码分布

test.c -- 测试游戏的(main)

game.c -- 游戏的实现 game.h -- 游戏函数的声明

三、代码实现

1、首先需要把这个游戏的工程创建好

2、写出main主函数,设置好本游戏的菜单,我设计的菜单有两个选项(1、play 0、exit)接着为了实现游戏不退出,可以接着玩下一把,我们就要采用循环的方法,那究竟用那种循环比较好呢?结合当今的游戏我们发现,进去肯定是要有界面的,所以综合考虑用do while循环较好

3、我们还应该考虑到,用户在输入1的时候要开始玩游戏,输入0的时候退出,输入其他选项输出“选择错误,重新选择”,同时我们发现输入1的时候,用户在玩游戏,输入0则退出,所以我们不妨将while后的判断条件直接设置为input

4、我们运行一下看看

发现存在了一些错误,将它们修改后再次运行

再次修改

这回把所有的问题全部解决,预期结果和运行后的结果完全一致

那问大家一个问题,我为什么要将input的定义放在do while循环外面呢?放里面可不可以呢

因为在这个程序中,将 input 的定义放在 do while 循环外部是为了确保在整个 main 函数的作用域内 input 都是可见的,并且在循环条件判断和 switch 语句中都可以使用它,如果放到循环里面,那么循环的判断条件就会出现问题

5、接下来要对游戏的功能设计实现

(1)我们需要一个3×3的棋盘,其次玩家在输入下棋坐标后,电脑也会对应下棋,二者用不同的图案对应,游戏在走的过程中要进行数据的存储,可以使用3×3的二维数组,比如

char board[3][3];

(2)没进行游戏之前数组中存放空格(可以封装在函数中实现哦)

(3)同时在前面我们提到要将游戏相关的代码放到game.c和game.h中

我们发现有好多3,如果未来游戏升级优化,变成五子棋,十字棋,棋盘越来越大,挨个改数字很麻烦,有没有什么更好的方法呢?

我们可以这么做,使用预处理指令,定义宏常量

将宏 ROW 定义为整数值 3在程序中,可以通过 ROW 来代替 3 使用,COL也是如此,因为我们将宏定义在了头文件,所以在别的文件如果使用的话,需要引用头文件,引用自己的文件则是

#include"game.h"

(4)接着实现初始化函数

void InitBoard(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] = ' ';
		}
	}
}

此段代码是游戏功能的实现,放在game.c中,同样需要在引用头文件game.h

补充:我们可以将每个文件都涉及到的相同库函数等的头文件都放在game.h中,这样会很方便

(5)我们再设计一个打印函数来看看初始化后的效果

//打印版本1
void DisplayBoard(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]);
		}
		printf("\n");
	}
}

我们运行一下主函数看一下

发现并不能看见棋盘在哪里,由此可见这个版本1的打印代码有点low,我们来优化一下

我们将每一个区域分的清楚一点,像这样

每个区域中间是对应游戏下棋时候的符号,符号两边用空格隔开,每列之间用竖线隔开,”---“这个是每行的分割线,可以将一行数据和分割线看成一组,最后一组的分割线不打印

//打印版本2
void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		//1、打印数据
		printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
		//2、打印分割线
		if (i < row - 1)
		{
			printf("---|---\---\n");
		}
	}
}

运行看一下,

好像是我们要的效果,如果换成10×10的棋盘呢?

我们发现代码还是存在问题的,并没有打印出10×10的棋盘,因为输出的3列定死了,再次优化

//打印版本3
void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		//1、打印数据
		int j = 0;
		for (j = 0; j < col; j++)
		{
			printf(" %c ",board[i][j]);
			if (j < col - 1)
			{
				printf("|");
			}
		}
		printf("\n");
		//2、打印分隔符
		if (i < row - 1)
		{
			int j = 0;
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)
				{
					printf("|");
				}
			}
			printf("\n");
		}
	}
}

这回运行后的棋盘才是完美符合要求的,不会再有问题

(6)玩家下棋功能的实现

在这里需要注意,玩家认为棋盘的行数和列数都是从1开始,而二维数组的行数和列数都是从0开始的,同时还要注意坐标输入是否合法,由于输入坐标的不合法还需要重新输入所以要考虑使用循环,还存在坐标被占有的情况

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, 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");
		}
	}
}

我们修改一下game函数代码,让玩家一直下棋,测试这个下棋的逻辑

void game()
{
	char board[ROW][COL] = { 0 };
	InitBoard(board,ROW,COL);//要有函数封装的思想,同时此函数需要将二维数组的行数和列数同样作为参数传递
	//打印棋盘
	DisplayBoard(board, ROW, COL);
	//下棋
	while (1)
	{
		//玩家下棋
		PlayerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//电脑下棋
	}
}

(7)电脑下棋模块的实现

注意一下是随机生成,rand的使用在之前提到过,所以这里就不再赘述

//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;//0~row-1
	int y = 0;//0~col-1

	printf("电脑下棋:>\n");
	while (1)
	{
		x = rand() % row;
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

接着测试代码

//test.c
void game()
{
	char board[ROW][COL] = { 0 };
	InitBoard(board,ROW,COL);//要有函数封装的思想,同时此函数需要将二维数组的行数和列数同样作为参数传递
	//打印棋盘
	DisplayBoard(board, ROW, COL);
	while (1)
	{
		//下棋
		//玩家下棋
		PlayerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		//电脑下棋
		ComputerMove(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
	}
}

运行后发现并没有什么问题(测试时候不看输赢,因为还有后续的代码)

(8)判断输赢

//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'
//同时主函数要在每次玩家下棋和电脑下棋后都追加判断一次,不是继续的情况就要break
//因为我规定了每种输赢的返回值
//所以game函数如下
void game()
{
    char board[ROW][COL] = { 0 };
    InitBoard(board,ROW,COL);//要有函数封装的思想,同时此函数需要将二维数组的行数和列数同样作为参数传递
    //打印棋盘
    DisplayBoard(board, ROW, COL);
    char ret = 0;
    while (1)
    {
        //下棋
        //玩家下棋
        PlayerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        //判断输赢
        ret = IsWin(board, ROW, COL);
        if (ret != 'C')
        {
            break;
        }
        //电脑下棋
        ComputerMove(board, ROW, COL);
        DisplayBoard(board, ROW, COL);
        //判断输赢
        ret = IsWin(board, ROW, COL);
        if (ret != 'C')
        {
            break;
        }
    }
    if (ret == '*')
    {
        printf("玩家赢\n");
    }
    else if (ret == '#')
    {
        printf("电脑赢\n");
    }
    else
    {
        printf("平局\n");
    }
}

还需注意赢的条件是三行的某一行或者三列的某一列或者对角线,有同样的符号,因为我们是对数组直接比对,是程序员的角度,所以二维数组的行和列都是从0开始的

//平局的情况---判断棋盘是否已满
int IsFull(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++)
        {
            if (board[i][j] == ' ')
            {
                return 0;//有空格
            }
        }
    }
    return 1;//没有空格,已满
}
​
//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'
​
char IsWin(char board[ROW][COL], int row, int col)
{
    //赢
    //行
    int i = 0;
    for (i = 0; i < row; i++)
    {
        if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
        {
            return board[i][0];//这就是为什么玩家赢和电脑赢对应符号是*和#的原因,简洁省去一些没有必要的判断
        }
    }
    //列
    for (i = 0; i < col; i++)
    {
        if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
        {
            return board[0][i];
        }
    }
    //对角线
    if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
    {
        return board[1][1];
    }
    if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
    {
        return board[1][1];
    }
    //平局---就是看棋盘满没满
    if (IsFull(board, ROW, COL) == 1)
    {
        return 'Q';
    }
    //继续
    return 'C';
}

到这里,这个游戏的代码就完全结束了,接下来我们来验证一下

非常棒!没有任何问题,可以实现的情况也全部展现出来了!给自己数个大拇指吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值