手把手教你写出《三子棋》小游戏

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

首先介绍下三子的玩法,玩家下棋,电脑下棋,直到某一方能够三点连成一条直线即可
文章比较长,因为解释了许多我在学习时,思考的问题,文章中还简单了如如果想进阶一点,玩多子棋的设计思路

先上下自己完成的效果图:
在这里插入图片描述

一、设计思路

在这里插入图片描述

二、具体步骤

1.游戏的逻辑

首先说明本项目是结合头文件.h和源文件.c(新建项的时候改下后缀即可)
在这里插入图片描述
(1)在test,c中,先敲出游戏逻辑的框架
这里设想的是三种选择模式:
1、进入游戏
2、退出游戏
其他输入:选择错误
这里注意,main函数是要放在最底下的,test()函数放在上面
因为编译的时候是从上往下,只有编译器先读到你定义的函数才知道它的存在,否则会报错

void test()
{

	int input = 0;
	do{
		menu();//游戏菜单
		printf("选择游戏模式:");
		scanf("%d", &input);
			switch (input)
		{
			case 1:
				game();//选择1;进入游戏的实现
				break;
			case 0:
				printf("退出游戏\n");
				break;
			default:
				printf("输入错误\n");

				break;
		}
		
	} while (input);
}
int main()
{
	test();
	return 0;
}

(2)同时可以设计出游戏一开始展示的菜单,同理,下面的menu(),game()要放在test()上面。

void menu()
{
	printf("***********************\n");
	printf("******* 1.play ********\n");
	printf("******* 0.exit ********\n");
	printf("***********************\n");

}

这里完成了,可以先运行一下,看看能不能正常的选择。

(3)接着就是game()函数
1)可以先定义个3x3的数组来存储棋盘的数组,char board[3][3] = { 0 }
2)下一步肯定是要初始化这个棋盘,我们可以设计个函数InitBoard,里面需要的参数首先要有我们的数组board,以及行3,列3
也就是InitBoard(board, 3, 3);
3)接着是要展示这个棋盘,我们也可以设计个函数DisplayBoard,传过来的参数自然还是board,3,3
4)关键部分就是下棋,要分玩家下棋,和电脑下棋,同理,player_move(board, 3, 3);computer_move(board, 3, 3)

void game()
{
	//存储棋盘数据
	char board[3][3] = { 0 };
	//初始化棋盘
	InitBoard(board, 3, 3);
	//展示棋盘
	DisplayBoard(board, 3, 3);
	//玩家下棋
	player_move(board, 3, 3);
	DisplayBoard(board, 3, 3);//这里是考虑到下完棋后要把棋盘展示给玩家看
	//电脑下棋
	computer_move(board, 3, 3);
	DisplayBoard(board, 3, 3);
}

此时,我们要注意一个问题,这时我们是要玩三子棋,那如果,想玩五子棋,十字棋,如何修改?
我们现在的进度修改起来还算简单,把所有的3,都改成5即可,那如果我们进程不断往前推进,使用这个3,3的次数越来越多,修改起来就会更加麻烦,你要在所有3,3的地方修改,所以这时可以作出一点优化

在game.h这个头文件里,宏定义两个变量,就是行和列的英文大写(可以选择小写,大写只是为了和普通变量区分开)

#define ROW 3
#define COL 3

这里可能有人要疑惑,为什么不使用全局变量?
解释一下:1)比如说,我在test.c里,定义了全局变量int row=3,想在game.c里使用row,就必须要加个外部变量声明
就是extern int row=3,这样才能扩展全局变量的作用域,而对于宏定义而言,只需要在game.c里引用下头文件#include "game.h"就行,宏定义不使用extern
2)其次,宏定义定义的是#define 宏名 字符,编译的时候就会使用,并会把所有出现ROW的地方替换成3,它是不需要开辟内存的,而全局变量需要
3)我们这里只是个简单的变量,使用宏定义更方便我们修改,宏定义缺点也不少,有兴趣的朋友可以多去了解下,这里就不过多介绍了

总之我们使用宏定义后,game()就变成

#inlcude "game.h"
void game()
{
	
	//存储棋盘数据
	//char board[3][3] = { 0 };
	char board[ROW][COL] = { 0 };
	//初始化棋盘
	InitBoard(board, ROW, COL);
	//展示棋盘
	DisplayBoard(board, ROW, COL);
	//玩家下棋
	player_move(board, ROW, COL);
	DisplayBoard(board, ROW, COL);
	//电脑下棋
	computer_move(board, ROW, COL);
	DisplayBoard(board, ROW, COL);
}

当然代码还是有问题的,没考虑如何实时判断输赢,知道游戏结束,之后再介绍.

2.游戏的实现

1)头文件的使用

上面我们用了InitBoard,DisplayBoard等等,但都没有定义,这里我们就可以先去头文件里声明,再去game.c里定义

为什么要先声明呢?因为头文件里的内容是共享的,就像你.c文件开头都要#include <stdio.h>,和#define一样,都是预处理,相当于提前告诉编译器,有stdio这个头文件库,里面有printf,scanf等函数,同样的,你自己定义的函数

return_type function(parameter_type param1, ...)
 { 
 .....
 }

如果想要在多个文件使用,那你在文件开头肯定也要声明下,告诉这个编译器,有这个函数,已经定义好了

return_type function(parameter_type param1, ...);

那这样你每个要使用的文件里都要先声明再调用,未免太麻烦了,所以在头文件里声明,在源文件里定义以及调用,就会非常高效了

那么game.h就变成了

#define ROW 3
#define COL 3
//约定俗成用大写,以此与小写的普通变量区分开来
void InitBoard(char board[ROW][COL], int row, int col);
//这里对应着一开始调用的InitBoard(board,ROW,COL),这里你也可以换成其他的比如
//void InitBoard(char arr[ROW][COL], int r, int c);
void DisplayBoard(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);

2)InitBoard(初始化棋盘)

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

3)DisplayBoard(展示棋盘)

就像一开始展示的那样,我们可以给棋盘用符号分割开来,更美观一点
在这里插入图片描述
这里加粗下颜色,看的更清楚些,格子是由三行两个“|”,以及两行“—|---|—”,结合一起
在这里插入图片描述

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]);//这里是要给棋盘的字符腾出空间,“ 字符 | 字符 | 字符 ”

			 if (j < col - 1)//打印一个空白字符,划一条|,最后一个|不需要,那么就限制下j
				 printf("|");//如果col是3,那么j<2,就是0,1,只要划两个竖杠

		 }
		 printf("\n");//换行,给下一行“ 字符 | 字符 | 字符 ”
		
		//---|---|---就只是单纯的符号,,只是为了美观
		 //基础写法:有两行---|---|---,就限制两次
		 //if (i < row - 1)
		 //{
			// printf("---|---|---");
			// printf("\n");
		 //}
		//更通用的写法,如果变成五子棋,那么基础写法明显不适用了,这里就要酱---和|分开,就像上面那样,把---当作一个字符
		//先有一个---(字符),再划一个|
		 if (i < row - 1)
		 {
			 for (j = 0; j < col; j++)
			 {
				 printf("---");

				 if (j < col - 1)
					 printf("|");

			 }
			 printf("\n");//换行,给下一行---|---|---

		 }
	 }
 }

这里可以在test()去注释掉两个下棋函数,看一下,能不能正常展示。

4)player_move(玩家下棋)

玩家下棋,肯定要手动输入坐标(scanf),接着要判断这个坐标是否在格子内(if)以及这个坐标上是否已经有了字符(if)
这里用while,就是要玩家输入了正确的坐标才能break

补充一点,因为数组一开始是从0开始,board[3][3],竖着是x,对应row,实际上可选的数,是0,1,2,横着是y,可选的坐标也是0,1,2
但我们平常的习惯是从一开始,所以,我们在代码里可以简单运算下,1,1坐标,其实就是这个数组的0,0坐标,3,3坐标其实就是对应棋盘的2,2

在这里插入图片描述

 void player_move(char board[ROW][COL], int row, int col)
 {
	 int x, 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");
	 }

 }

这里可以把test()里和电脑下棋的相关函数注意,看一下能不能正常下棋

5)computer_move(电脑下棋)

这里实现与上面玩家下棋类似,注意这里用了个rand函数,着重说一下
因为是电脑下棋,所以就是要采用随机数的方法
首先要在一开始引用time库以及stdlib库(这里其实可以在game,h里引用,在game.c里只要引用game.h,那么也就引用了这两个库)

#include <time.h>
#include <stdlib.h>

初始化随机发生器,放在test(),main(),或者就在下面函数定义里初始化都可以,只要在用rand之前即可。

    srand((unsigned int)time(NULL));//初始化随机数发生器
	//time是时钟函数,是获取时间,这边是指的是随机数的仓库,我们这里是要整形,要强制转化下数据类型
	//本来应该是先定义一个time_t类型的t变量,然后time(&t)
	//但也可以直接传入一个空指针,因为程序不需要经过参数而去获得的数据

想要了解这个随机播种具体的解释,可以去蓝色链接,往下翻到最后有个前辈给了很详细的说明/

 void computer_move(char board[ROW][COL], int row, int col)
 {
	 int x, y = 0;
	 printf("电脑下棋:\n");
	 while (1)
	 {
		 x = rand() % row;//%row意思是从[0,row)随机选一个数字,也就是0,1,2
		 y = rand() % col;
		
		 if (board[x][y] == ' ')//这里同样是要等到这个随机坐标是合法的,才能跳出去
		 {
			 board[x][y] = '#';
			 break;
		 }
		 
	 }

 }

6)win(判断输赢)

游戏赢的条件是三点一条线,那么肯定实时判断(设计一个函数win()),并返回一个值(定义个字符char ret),是继续,是电脑赢,还是玩家赢,还是平局(if {} else if{} else{})
注意之前电脑下棋是‘#’,玩家下棋是‘*’。可以为我们少定义两个返回值,那么我们可以设定

返回‘c’(continue),就继续
返回‘#’,那就是电脑胜利
返回‘星号’(数字8那个符号,这里输入 星号会莫名和上面的星号形成注释,又取消不了,有解决的办法还希望评论区有大神能指点一下)就是玩家胜利
返回‘d’(dogfall),就是平局

这里的逻辑就要放在test.c里的test()里,至于实时判断的函数就要放在game.c里

while (1)
	{
		//玩家下棋
		player_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		ret = win(board, ROW, COL);

		if (ret != 'c')//如果返回的不是继续,那么就跳出这个while,去判断输赢还是平局
		{
			break;
		}

		//电脑下棋
		computer_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		ret = win(board, ROW, COL);

		if (ret != 'c')
		{
			break;
		}

	}
	if (ret == '*')
	{
		printf("玩家胜利\n");
	}
	else if (ret == '#')
	{
		printf("电脑胜利\n");

	}
	else
	{
		printf("平局\n");
	}

接下来就是实时判断输赢的函数win(),别忘了再头文件里声明

char win(char board[ROW][COL], int row, int col);

接着来到game.c里定义,最简单的方法就算判断三种类型,行(一个for循环,三行),列(一个for循环,三列),对角线(两条,直接验证)

那么平局呢?平局的话只有一种可能,玩家电脑把9个格子都填满了,而且没满足上面的三种类型,
这里可以再额外设计个函数full(),判断格子是否满了,满了返回1,游戏平局,没满返回0,游戏继续

static int full(char board[ROW][COL], int row, int col)//这里static是因为full函数只是为了下面的win函数服务,其他函数或者源文件并不需要他,static可加可不加

 {
	 int i = 0;
	 int j = 0;
	 for (i = 0; i < row; i++)
	 {
		 for (j = 0; j < col; j++)
		 {
			 if (board[i][j] == ' ')//如果有一个格子是空的
			 {
				 return 0;//没满,返回0
			 }
		 }
	 }
	 return 1;//没有格子是空的,满了,返回1
 }

 
char win(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][0] == board[i][2] && board[i][1] != ' ')
		 //i=0的时候,如果(0,0)=(0,1)且(0,0)=(0,2)且(0,1)不为空白,那么返回(0,1)的字符,可能是#,可能是*
		//下面行和对角线同理
			 return board[i][1];

	 }
	 //判断列是否城三个
	 for (i = 0; i < row; i++)
	 {
		 if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
			 return board[1][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 (full(board, row, col) == 1)
		 {
			 return 'd';
		 }
		 return 'c';
	
 }

我这里判断行列对角线的时候是采用比较直接的方式,但也限制了,这么比只能在三子棋中有效
如果想要更通用的方法,我这里提供一种思路,其他的可以自己去思考下

	int i = 0;
	int j = 0;
	int count = 0;//变量count是为了限制这里只是为了比较行,
	//判断行是否三个
	for (i = 0; i < row; i++)
	{
			//原来的写法
			// if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][1] != ' ')
			// return board[i][1];

		for (j = 0; j < col - 1; j++)
		{
	
			//通用写法

			if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')
			//i=0,j=0:比较(0,0)==(0,1)且(0,0)是否为空,满足count=1
			//i=0,j=1:比较(0,1)==(0,2)且(0,j)是否为空,满足count=2
			//i=0不满足那就比较i=1的情况,以此类推
			{
				count++;
			}
			if (count == col - 1)//一行三个,如果一个与另外两个都相等,那么这一行就相等,说明分出了胜负,游戏结束
			{
				return board[i][j];
			}

		}

	}

三、整个代码

game.h

#include <time.h>
#include <stdlib.h>
#define ROW 3
#define COL 3

void InitBoard(char board[ROW][COL], int row, int col);
void DisplayBoard(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);

game.c

#define _CRT_SECURE_NO_WARNINGS

#include "game.h"


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

 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]);

			 if (j < col - 1)
				 printf("|");

		 }
		 printf("\n");
		 //基础写法
		 //if (i < row - 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)
 {
	 int x, 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], int row, int col)
 {
	 srand((unsigned int)time(NULL));
	 int x, y = 0;
	 printf("电脑下棋:\n");
	 while (1)
	 {
		 x = rand() % row;
		 y = rand() % col;
		
		 if (board[x][y] == ' ')
		 {
			 board[x][y] = '#';
			 break;
		 }
		 
	 }

 }


static int 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_win(char board[ROW][COL], int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	//判断行是否三个
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col - 1; j++)
		{
			//原来的写法
			// if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][1] != ' ')
			// return board[i][1];

			//通用写法

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

		}

	}
	//判断列是否成三个
	for (i = 0; i < col; i++)
	{
		//if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
		// return board[1][i];

		int j = 0;
		int count = 0;
		for (j = 0; j < row - 1; j++)
		{
			//原来的写法
			// if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][1] != ' ')
			// return board[i][1];

			//通用写法
			int count = 0;
			if (board[j][i] == board[j + 1][i] && board[0][i] != ' ')
			{
				count++;
			}
			if (count == row - 1)
			{
				return board[j][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 (full(board, row, col) == 1)
		{
			return 'd';
		}
		return 'c';

}

test.c

#define _CRT_SECURE_NO_WARNINGS

#include "game.h"

void menu()//输入的时候不小心按到了fn+ins,导致输入模式变成了嵌入模式,无法修改当前字符
{
	printf("***********************\n");
	printf("******* 1.play ********\n");
	printf("******* 0.exit ********\n");
	printf("***********************\n");

}

void game()
{
	
	char ret = 0;
	//存储棋盘数据
	//char board[3][3] = { 0 };
	char board[ROW][COL] = { 0 };

	//初始化棋盘

	InitBoard(board, ROW, COL);

	//展示棋盘
	DisplayBoard(board, ROW, COL);

	while (1)
	{
		//玩家下棋
		player_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		ret = win(board, ROW, COL);

		if (ret != 'c')
		{
			break;
		}

		//电脑下棋
		computer_move(board, ROW, COL);
		DisplayBoard(board, ROW, COL);
		ret = win(board, ROW, COL);

		if (ret != 'c')
		{
			break;
		}

	}
	if (ret == '*')
	{
		printf("玩家胜利\n");
	}
	else if (ret == '#')
	{
		printf("电脑胜利\n");

	}
	else
	{
		printf("平局\n");
	}
}


//测试菜单逻辑
void test()
{
	/*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);
}
int main()
{

	test();
	return 0;
}

四、总结

一整个博客敲下来,其实还是花了不少时间的,但是发现很多一开始觉得自己理解不到位的或者没有注意的地方,文章有点啰嗦,因为掺杂了不少从我这个新手角度会去疑惑的问题,自己的能力还是有限,如果文章中有什么错误或者各位有什么建议,欢迎指出,不胜感激,下篇文章我会尽快写出来,希望能与大家共勉。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值