三子棋(N子棋)C语言编程实现,超详细讲解

你好,本期内容,纯干货,三子棋详细教程,现在开始!

目录

一.main函数所在的.c为后缀的源代码

1.main函数内容的框架

2.srand与rand

3.game()函数的框架

二.game.h游戏源代码所需要的头文件

1.为什么要把这些头文件放在game.h里面?

2.头文件与源文件的区别

三.game.c游戏源代码的实现

1. 棋盘打印的思路:

2.玩家下棋的思路:

3.电脑下棋的思路:

4.谁是赢家的思路:

5.棋盘是否满的思路:

四.game.c源代码


一.main函数所在的.c为后缀的源代码

#include "game.h"
void menu()
{
    printf("********************\n");
    printf("***1.play  0.exit***\n");
    printf("********************\n");
}
void game()
{
    char board[ROW][COL] = { 0 };
    char ret = 0;
    initboard(board, ROW, COL);     //初始化棋盘
    printboard(board, ROW, COL);       //打印棋盘
    while (1)
    {
        playerdown(board, ROW, COL);    //玩家下棋
        ret = is_winner(board, ROW, COL);   //判断赢家
        if (ret != 'C')
        {
            break;
        }
        printboard(board, ROW, COL);     //打印棋盘
        computerdown(board, ROW, COL);     //电脑下棋
        ret = is_winner(board, ROW, COL);    //判断赢家
        if (ret != 'C')
        {
            break;
        }
        printboard(board, ROW, COL);     //打印棋盘
    }
    if (ret == '*')           //判断赢家
        printf("玩家赢\n");
    else if (ret == '#')
        printf("电脑赢\n");
    else if (ret == 'Q')
        printf("平局\n");
    printboard(board, ROW, COL);    //打印胜利的战果
}


//主函数
int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));    //用于电脑下棋时所需要的随机值
    do
    {
        menu();    //菜单
        printf("请选择:>");     
        scanf_s("%d", &input);
        switch (input)     //选择
        {
        case 1:
            game(); break;
        case 0:
            printf("退出游戏\n"); break;
        default:                                     //输入1开始游戏,输入0退出游戏
            printf("选择错误,请从新选择\n"); break;  //while条件为真则继续,为假终止循环
        }                                            //输入非1非0  从新选择,非0继续循环
    } while (input);    //
    return 0;
}

1.main函数内容的框架

  1. 菜单
  2. 选择
  3. 根据选择调用所属函数

2.srand与rand

   1.大致内容

      srand意思为设置rand的生成器,初始化随机数生成器它是个库函数,头文件为#include            <stdlib.h>

      详细内容请见:srand参考

      rand()生成的是1-RANDMAX(32767)的随机值,头文件与srand一样

      详细内容请见:   rand参考

   2.srand( (unsigned int) time(NULL) )

      time设置时间戳(下面会给你讲解),time是一个库函数,头文件#include <time.h>

      传参NULL意思为空指针,将time强制转换为无符号整形(unsigned int)

      详细内容请见:time参考

      时间戳(单位为秒):系统当前时间-系统起始时间

3.game()函数的框架

1.大致内容:

棋盘的初始化需要一个字符类型的二维数组来实现,棋盘的初始内容应当是空格,玩家输入坐标,将某个字符存储到所选坐标x-1,坐标y-1为下标的二维数组内,判断是否赢了,其次电脑利用srand设置的生成器后的rand来设置随机值,利用这个随机值%上棋盘的行数或者列数,得到的值的范围是小于行数的,放心将其作为下标,引用棋盘的二维数组,将咱们自己所设置的字符存入这个二维数组。判断是否赢了,如果赢了,则打印赢家。

2.ROW行与COL列的初始化

由于行与列需要我们自己任意定义,所以我们应当选择更合适的#define来标识符常量,排除需要换行改列的隐患,#define ROW 3    #define  COL 3,由于实现整个程序不止test.c源文件要使用ROW行与COL列,我们要把ROW,COL设置在game.h头文件里,在test.c文件头部输入代码#include "game.h",注意:双引号所引用的头文件为咱们自己设置的头文件,尖括号所引用的头文件为库函数所需要的头文件

二.game.h游戏源代码所需要的头文件

#include <stdio.h>    //输入输出头文件
#include <time.h>     //time设置时间戳头文件
#include <stdlib.h>   //rand,srand设置随机值头文件
#define ROW 5         //行的设置
#define COL 5         //列的设置
void initboard(char board[ROW][COL], int row, int col);   //初始化棋盘
void printboard(char board[ROW][COL], int row, int col);  //打印棋盘
void playerdown(char board[ROW][COL], int row, int col);  //玩家下棋
void computerdown(char board[ROW][COL], int row, int col);//电脑下棋
char is_winner(char board[ROW][COL], int row, int col);   //谁是赢家

1.为什么要把这些头文件放在game.h里面?

因为咱的test.c源文件的代码里面包含了game.h的头文件,所以它能够实现game.h里面的功能。

2.头文件与源文件的区别

头文件来声明,源文件来定义。

例如:

#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n",add(a, b));
	return 0;
}
int add(int a, int b)
{
	return a + b;
}

上面的代码控制台会出现:

咱知道程序的扫描是自上而下的,在main函数之前定义add函数是非不可选的,那么如何将其“add未定义,假设外部返回int”的这段话不报告的话,这里就是需要“声明”上线了

#include <stdio.h>
int add(int a, int b);   //这就是声明,标注其函数的返回类型,函数参数的类型
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n",add(a, b));
	return 0;
}
int add(int a, int b)
{
	return a + b;
}

三.game.c游戏源代码的实现

棋盘的执行效果:

1. 棋盘打印的思路:

board[ROW][COL]中的元素与 | 打印在一行,但 | 有限制条件,就是当j < col - 1  注意:col是COL传参后的临时拷贝,意思为列

其次打印 --- 与 | ,--- 的限制条件是i < row-1,i为行的循环, | 的限制条件为j < col - 1, row为行

2.玩家下棋的思路:

1.输入坐标x,y

2.判断该坐标x-1,y-1引用下标后的二维数组那个元素是否为空格,或者是否为电脑所下过的棋子,或者坐标非法,如果条件满足,则执行board[x-1][y-1] = '*';break;

void playerdown(char board[ROW][COL], int row, int col)  //玩家下棋
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("玩家下棋\n");
		printf("请输入坐标:>");
		scanf_s("%d %d", &x, &y);
		if ((x <= row && x - 1 >= 0) || (y <= col && y - 1 >= 0))
		{
			if (board[x - 1][y - 1] == '#')
			{
				printf("坐标已经被占用,请从新输入\n");
			}
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}
		}
		else
			printf("坐标非法,请从新输入\n");
	}
}

3.电脑下棋的思路:

1.初始化x,y

初始化的值为rand%row和rand%col,即大于等于0,小于row或者col的值的范围

2.判断条件

如果满足,赋值'#'

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

}

4.谁是赢家的思路:

1.框架

①横向连线判断

②竖向连线判断

③X型连线判断

2.具体

​
	for (i = 0; i < row; i++)
	{
		count = 0;
		for (j = 0; j < col - 1; j++)
		{
			if (board[i][j] == board[i][j + 1] && board[i][j] != ' ' && board[i][j + 1] != ' ')
			{
				count++;
			}
		}
		if (count == ROW - 1)
		{
			return board[i][0];
		}
	}

​

①横向连线判断

横向的和行、列都有关系,所以嵌套两层循环,比如第一行,咱需要判断两个紧挨着的1列和2列的内容是否相同,那这需要需要一个变量来计数,我设置整形count,利用count++来计数,那咱这计数也需要判断条件吧,咱知道紧挨着的凑两对的数量极限是board[0][2]吧,那么if条件中的==操作符右值应当撑死是board[0][2],所以你思考一下,j+1中j的值是否是应该撑死是col-1-1,由于我们for循环中j的初值是0,所以判断条件应当是j<col-1。

注意:空格也是存在相同的内容的,所以这里也需要判断该坐标-1后为下标的元素是否是空格,count最终计算的值应当是判断是否相等我们的列-1,如果条件满足,返回这个返回值,设置board[i][0]意思为返回第i行下标为0的元素,这个元素不是#就是*,因为*是玩家,#是电脑.

for (i = 0; i < row; i++)
	{
		count = 0;
		for (j = 0; j < col - 1; j++)
		{
			if (board[j][i] == board[j + 1][i] && board[j][i] != ' ' && board[j + 1][i] != ' ')
			{
				count++;
			}
		}
		if (count == COL - 1)
		{
			return board[0][i];
		}
	}

②竖向连线判断

竖向就是先把列固定住,循环行来判断紧邻两个元素是否相等

具体实践就是board[j][i] == board[j+1][i]

并且这两个元素不为空格

①横向连线判断中的count的计算相同道理,返回值主要与列有关。

//左上-右下
	count = 0;
	for (i = 0; i < col - 1; i++)
	{
		if (board[i][i] == board[i + 1][i + 1] && board[i][i] != ' ' && board[i + 1][i + 1] != ' ')
		{
			count++;
		}
	}
	if (count == col - 1)
		return board[0][0];

③X型连线判断

1.左上---右下

无非是一层循环判断board[i][i] = board[i+1][i+1]并且两个元素不为空格,再count++;

这里对角线的x与y相等,就好比数学中的函数y=x的图像。

//右上---左下
	count = 0;
	for (i = 0; i < row - 1; i++)
	{
		if (board[i][row-1-i] == board[i+1][row-2-i] && board[i][row - 1 - i] != ' ' && board[i + 1][row - 2 - i] != ' ')
		{
			count++;
		}
	}
	if (count == col - 1)
	{
		return board[i][row-1-i];
	}

③X型连线判断

2.右上---左下

经过上面一些代码的磨练,这个难度相对较高一点

我们知道这个列是从row-1开始的,行是从0开始的,但是每次循环都是从row-1的列下标开始的吗?答案是:NO!我们总要把列下标与行建立关系吧,所以就有了row-1-i,一层循环就够了,跟1.左上---右下相似,最终返回的值为与咱们判断的需要的任意一个值一样就ok了。

5.棋盘是否满的思路:

看代码,你就懂了。

int if_full(char board[ROW][COL], int row, 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;
}

四.game.c源代码:

#include "game.h"

/*----------------------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------------------*/

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

/*----------------------------------------------------------------------------------*/

void playerdown(char board[ROW][COL], int row, int col)  //玩家下棋
{
	int x = 0;
	int y = 0;
	while (1)
	{
		printf("玩家下棋\n");
		printf("请输入坐标:>");
		scanf_s("%d %d", &x, &y);
		if ((x <= row && x - 1 >= 0) || (y <= col && y - 1 >= 0))
		{
			if (board[x - 1][y - 1] == '#')
			{
				printf("坐标已经被占用,请从新输入\n");
			}
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}
		}
		else
			printf("坐标非法,请从新输入\n");
	}
}

/*----------------------------------------------------------------------------------*/

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

}
int if_full(char board[ROW][COL], int row, 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_winner(char board[ROW][COL], int row, int col)   //谁是赢家
{
	int i = 0;
	int j = 0;
	int count = 0;
	//行
	for (i = 0; i < row; i++)
	{
		count = 0;
		for (j = 0; j < col - 1; j++)
		{
			if (board[i][j] == board[i][j + 1] && board[i][j] != ' ' && board[i][j + 1] != ' ')
			{
				count++;
			}
		}
		if (count == ROW - 1)
		{
			return board[i][0];
		}
	}
	//列
	for (i = 0; i < row; i++)
	{
		count = 0;
		for (j = 0; j < col - 1; j++)
		{
			if (board[j][i] == board[j + 1][i] && board[j][i] != ' ' && board[j + 1][i] != ' ')
			{
				count++;
			}
		}
		if (count == COL - 1)
		{
			return board[0][i];
		}
	}
	//X
	//左上-右下
	count = 0;
	for (i = 0; i < col - 1; i++)
	{
		if (board[i][i] == board[i + 1][i + 1] && board[i][i] != ' ' && board[i + 1][i + 1] != ' ')
		{
			count++;
		}
	}
	if (count == col - 1)
		return board[0][0];
	//左下-右上
	count = 0;
	for (i = 0; i < row - 1; i++)
	{
		if (board[i][row-1-i] == board[i+1][row-2-i] && board[i][row - 1 - i] != ' ' && board[i + 1][row - 2 - i] != ' ')
		{
			count++;
		}
	}
	if (count == col - 1)
	{
		return board[i][row-1-i];
	}
	if (if_full(board, ROW, COL))
	{
		return 'Q';  //Q为平局
	}
	return 'C';  //返回值C为继续
}

本期内容当中的ROW行与COL列可以设置任何无符号整形值,棋盘大小可不止3X3。

那么你应该收获了许多吧,代码哪里需要改正的话,评论区留言,多多指教,共同进步!

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值