注:在最后面,完整源码会以两种形式展现。在讲解时,以三个源文件的形式。
前言:三子棋,顾名思义,就是三个子连在一起就可以胜出。在本节我们要介绍的三子棋模式是这样子的:在键盘输入坐标(表示落子),和电脑对下(当前电脑设计为随机下)。
我们先看游戏执行起来的样子:
全局思路:下棋肯定需要一个棋盘,不然会显得杂乱无章;当有了棋盘之后,就需要落子,落子需要双方,一个是我们自己(手动下),另一个就是电脑(设置自动下);当每一次落子,都需要判断是否输赢,否则重复落子步骤(循环);而判断输赢也作为一块内容。因为作为小游戏,肯定需要一个游戏菜单。
思路简图:设置菜单------>设置棋盘------->(玩家落子--->电脑落子)------>判断输赢
三子棋流程图:
上面是铺垫,接下来才是重头戏,该上强度了。
一、准备工作和游戏菜单
在第一个源文件(test.c)和头文件(game.h)中实现
1.准备工作
(1)建立两个源文件和一个头文件
目的:方便观察和日后的工作(不展开)
(2)建立三个文件的联系(大致雏形)
上面的信息大致可以让我们知道每个文件大致要存放的内容,关于引用头文件这些知识不是我们现阶段需要明白的,只需记住这样用就行。
2.游戏菜单
(1)菜单的模板
void menu()//菜单函数 { printf("####################\n"); printf("###### 1.play ######\n"); printf("###### 0.exit ######\n"); printf("####################\n"); } int main() { int input = 0; do//循环菜单 { menu(); printf("请选择>:"); scanf("%d",&input); } while (input); return 0; }
运行结果:
1.我们把菜单封装成一个函数,放在第一个源文件中。
2.当调用完菜单之后,会有两个选择,这个时候就需要输入数据,这就需要用到scanf函数。
3.因为当结束一局游戏后,会再次出现菜单让我们选择继续与否,所以要用到do…while循环,因为这样至少会执行一次菜单。
(2)输入数据后的选择与判断
当我们用scanf函数输入数据后,就需要根据输入的数据选择不同的路径。
void menu()//菜单函数 { printf("####################\n"); printf("###### 1.play ######\n"); printf("###### 0.exit ######\n"); printf("####################\n"); } int main() { int input = 0; do//循环菜单 { menu(); printf("请选择>:"); scanf("%d",&input); switch (input)//用switch来判断选择 { case 1:printf("你已选择继续游戏\n"); break; case 0:printf("你已选择退出游戏\n"); break; default:printf("选择错误,重新选择\n");//防止乱选 } } while (input); return 0; }
运行结果:
选择1:
选择0:
选择其他:
根据输入数据的选择,我们就需要用到switch函数来判断。
(3)继续游戏后的选择
为了选择1之后就直接可以进入游戏,所以我们在后面直接跟上game函数,然后在game中函数调用各种函数接口。
void menu()//菜单函数 { printf("####################\n"); printf("###### 1.play ######\n"); printf("###### 0.exit ######\n"); printf("####################\n"); } void game() { char board[ROW][COL]; InitBoard(board,ROW,COL);//初始化棋盘 DisplayBoard(board,ROW,COL);//打印棋盘 //落子循环 } int main() { int input = 0; do//循环菜单 { menu(); printf("请选择>:"); scanf("%d",&input); switch (input) { case 1:printf("你已选择继续游戏\n"); game(); break; case 0:printf("你已选择退出游戏\n"); break; default:printf("选择错误,重新选择\n"); } } while (input); return 0; }
#include<stdio.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);
在我们的game函数内,放的是一些接口,功能暂未实现。
我们需要用到二维数组,然后它的行和列需要用到宏定义的常量。如果后续需要更改棋盘的大小也会很方便。
接下来我们再一一实现每个函数的功能吧
二、初始化和打印棋盘
在第二个源文件(game.c)中实现函数体内部的功能。
1.初始化棋盘
//初始化函数 InitBoard(board,ROW,COL);
初始化,因为刚开始的棋盘是空的,只需要全部赋值成空格就好。
//初始化棋盘 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] = ' ';//全部初始化成空格 } } }
我们可以通过调试窗口中的监视观察数组是否被成功赋值。
知识点:二维数组赋值
2.打印棋盘
//打印棋盘函数 DisplayBoard(board,ROW,COL);
如果直接将初始化好的字符数组打印出来,是看不见的,但是呢,我们可以先将空格换成其他可以看见的符号,从而可以检验我们的“打印棋盘函数”是否写对了。
要想打印出下面的这个棋盘该怎么做呢?
这是一个九宫格,其实这个棋盘有五行五列。第一行是空格和竖线组成,第二行是横线和竖线组成组成,后面同理。
我们可以有很多种方式打印,我们列举一种
1.我们可以一行一行的打印出来,打印完一行就换行。
2.竖线有两行,横线也只有两行,他们只能打印两次。
3.数组打印三行三列,横线打印两行,竖线打印两行。
打印棋盘函数:
//打印棋盘 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) { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } } printf("\n"); } }
运行结果:
第一层循环,控制行;那不是五行吗?为什么这里只循环三次,因为有第二层循环的控制。
总结:第一次循环(第一层):打印三次空格和两个竖线(第二层循环),第二层循环没有结束,继续换行打印横线和竖线;这里需要注意:每一行需要打印三次横线(第二次循环必须循环三次),但是只打印两横(第一层循环只能打印两次);而每一行只打印两次竖线,并且只打印两行。
三、玩家与电脑的落子
1.玩家落子(*)
因为我们所输入的坐标是从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 <= 3 && y >= 1 && y <= 3) { //判断落子位置是否被占用 if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else printf("该位置已被占用,请重新输入\n"); } else printf("落子位置非法\n"); } }
总结:落子时,考虑位置是否合法,坐标是否被占,否则重新循环,最后再落子,落子完成再退出循环。
知识点:选择语句的条件判断与数字下标的运用
2.电脑落子(#)
电脑落子,则是需要随机产生数字作为坐标再落子,所以我们需要用到产生随机数的知识点。
生成随机数:
#include<stdlib.h>//srand所需头文件 #include<time.h>//time所需头文件 srand((unsigned)time(NULL)); int x=rand();//此时,x中的值就是随机值
其中srand函数和rand函数是配合使用的。
srand函数只需要在主函数中提到一次,srand函数中的参数设为时间,返回一个NULL,并强制类型转化。我们当前只需要计熟这句话就行。
然后我们就可以使用rand函数来产生随机数了,只需要用变量来接收即可。
当前头文件:
#include<stdio.h> #include<stdlib.h> #include<time.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 PlayerMove(char board[ROW][COL],int row,int col); //电脑落子 void ComputerMove(char board[ROW][COL],int row,int col);
当前源文件(test.c):
#include"game.h" void menu()//菜单函数 { printf("####################\n"); printf("###### 1.play ######\n"); printf("###### 0.exit ######\n"); printf("####################\n"); } void game() { char ret = 0; char board[ROW][COL]; InitBoard(board,ROW,COL);//初始化棋盘 DisplayBoard(board,ROW,COL);//打印棋盘 PlayerMove(board, ROW, COL); DisplayBoard(board, ROW, COL);//打印棋盘 ComputerMove(board,ROW,COL); DisplayBoard(board, ROW, COL);//打印棋盘 } int main() { int input = 0; srand((unsigned)time(NULL)); do//循环菜单 { menu(); printf("请选择>:"); scanf("%d",&input); switch (input) { case 1:printf("你已选择继续游戏\n"); game(); break; case 0:printf("你已选择退出游戏\n"); break; default:printf("选择错误,重新选择\n"); } } while (input); return 0; }
电脑落子函数:
//电脑落子 void ComputerMove(char board[ROW][COL], int row, int col) { printf("电脑落子:\n"); int x = 0; int y = 0; while (1) { x = rand() % row; y = rand() % col;//范围0-2 if (board[x][y] == ' ') { board[x][y] = '#';//满足就落子,否则继续循环 break; } } }
上面的落子函数虽然已经写完,但是运行起来还是很不完整的,接下来的判赢才是重头戏。
四、判断输赢与循环落子
1.判赢
游戏的大致走向有四种:继续游戏、玩家赢、电脑赢和平局。其中,继续游戏就是循环落子的原因,知道出现一个结局。
判赢函数:
char Iswin(char board[ROW][COL], int row, int col)
我们这里规定一下,根据该函数的返回值来决定四种结果:
//*--玩家赢 #----电脑赢 P----平局 C-----游戏继续
判赢:
三子棋游戏胜利的结果就是三个子连成一条线。三子连成线的结果无非就是三种:横、竖和斜的。
//*--玩家赢 #----电脑赢 P----平局 C-----继续 char Iswin(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++)//三列相等的 { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') return board[0][i]; } for (i = 0; i < col; i++)//三列相等的 { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') return board[i][0]; } //判断\相等 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ') return board[1][1]; //判断/相等 if (board[2][2] == board[1][1] && board[1][1] == board[0][0] && board[1][1] != ' ') return board[1][1]; return 'C'; }
1.前面两个for循环判断横与列是否相等,如果相等就返回某一个坐标的值。
2.后面两个if语句同样的效果,满足条件就返回某个坐标。
3.如果上面的都不满足,则会返回'C',也就是游戏继续
上面的代码还差一种结局,那就是平局。平局的条件莫非就是棋盘满了,但是还没有分出胜负,所以我们另外封装一个判满的函数即可。(在上面的代码基础上改进)
int IsFull(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; }
//*--玩家赢 #----电脑赢 P----平局 C-----继续 char Iswin(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++)//三列相等的 { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') return board[0][i]; } for (i = 0; i < col; i++)//三列相等的 { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') return board[i][0]; } //判断\相等 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ') return board[1][1]; //判断/相等 if (board[2][2] == board[1][1] && board[1][1] == board[0][0] && board[1][1] != ' ') return board[1][1]; //判断平局 if (IsFull(board, row, col)) return 'P'; return 'C'; }
现在代码完整了,只要不满足输赢或者平局,游戏就会继续。
现在函数体的内容都已经完成,接下来需要在主函数实现接收其返回值并实现循环落子。
2.用循环实现游戏继续
其实就是每下一次,就打印一次棋盘,并且判断一次游戏是否继续
char ret=0; while (1) { //玩家下棋 PlayerMove(board, ROW, COL); DisplayBoard(board, ROW, COL);//打印棋盘 //每走一步棋就判断一次 ret = Iswin(board,ROW,COL); if (ret != 'C')//C!=C为假,不会跳出循环 break; //电脑下棋 ComputerMove(board, ROW, COL); DisplayBoard(board, ROW, COL);//打印棋盘 //判断电脑输赢 ret = Iswin(board,ROW,COL); if (ret != 'C') break; } //跳出循环,表示博弈结束,并判断结局 if (ret == '*') printf("恭喜玩家获胜,再来一局吧\n"); if (ret == '#') printf("电脑获胜,再来一局吧\n"); if (ret == 'P') printf("恭喜平局,谁也没有获胜\n");
到这里每个阶段的内容就完成了,接下来是总体的代码。
五、完整源码
1.分装成三个文件的源码
(1)头文件(game.h)
#include<stdio.h> #include<stdlib.h> #include<time.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 PlayerMove(char board[ROW][COL],int row,int col); //电脑落子 void ComputerMove(char board[ROW][COL],int row,int col); //判断输赢 char Iswin(char board[ROW][COL], int row, int col);
(2)源文件(game.c)
#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) { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } } printf("\n"); } } //玩家落子 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 <= 3 && y >= 1 && y <= 3) { //判断落子位置是否被占用 if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else printf("该位置已被占用,请重新输入\n"); } else printf("落子位置非法\n"); } } //电脑落子 void ComputerMove(char board[ROW][COL], int row, int col) { printf("电脑落子:\n"); int x = 0; int y = 0; while (1) { x = rand() % row; y = rand() % col;//范围0-2 if (board[x][y] == ' ') { board[x][y] = '#';//满足就落子,否则继续循环 break; } } } int IsFull(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; }//*--玩家赢 #----电脑赢 P----平局 C-----继续 char Iswin(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++)//三列相等的 { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') return board[0][i]; } for (i = 0; i < col; i++)//三列相等的 { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') return board[i][0]; } //判断\相等 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ') return board[1][1]; //判断/相等 if (board[2][2] == board[1][1] && board[1][1] == board[0][0] && board[1][1] != ' ') return board[1][1]; //判断平局 if (IsFull(board, row, col)) return 'P'; return 'C'; }
(3)源文件(test.c)
#include"game.h" void menu()//菜单函数 { printf("####################\n"); printf("###### 1.play ######\n"); printf("###### 0.exit ######\n"); printf("####################\n"); } void game() { char ret = 0; char board[ROW][COL]; InitBoard(board,ROW,COL);//初始化棋盘 DisplayBoard(board,ROW,COL);//打印棋盘 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"); if (ret == '#') printf("电脑获胜,再来一局吧\n"); if (ret == 'P') printf("恭喜平局,谁也没有获胜\n"); } int main() { int input = 0; srand((unsigned)time(NULL)); do//循环菜单 { menu(); printf("请选择>:"); scanf("%d",&input); switch (input) { case 1:printf("你已选择继续游戏\n"); game(); break; case 0:printf("你已选择退出游戏\n"); break; default:printf("选择错误,重新选择\n"); } } while (input); return 0; }
2.一个文件的源码
#include<stdio.h> #include<stdlib.h> #include<time.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 PlayerMove(char board[ROW][COL],int row,int col); //电脑落子 void ComputerMove(char board[ROW][COL],int row,int col); //判断输赢 char Iswin(char board[ROW][COL], int row, int col); //初始化棋盘 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) { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } } printf("\n"); } } //玩家落子 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 <= 3 && y >= 1 && y <= 3) { //判断落子位置是否被占用 if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else printf("该位置已被占用,请重新输入\n"); } else printf("落子位置非法\n"); } } //电脑落子 void ComputerMove(char board[ROW][COL], int row, int col) { printf("电脑落子:\n"); int x = 0; int y = 0; while (1) { x = rand() % row; y = rand() % col;//范围0-2 if (board[x][y] == ' ') { board[x][y] = '#';//满足就落子,否则继续循环 break; } } } int IsFull(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; }//*--玩家赢 #----电脑赢 P----平局 C-----继续 char Iswin(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++)//三列相等的 { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ') return board[0][i]; } for (i = 0; i < col; i++)//三列相等的 { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ') return board[i][0]; } //判断\相等 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ') return board[1][1]; //判断/相等 if (board[2][2] == board[1][1] && board[1][1] == board[0][0] && board[1][1] != ' ') return board[1][1]; //判断平局 if (IsFull(board, row, col)) return 'P'; return 'C'; } void menu()//菜单函数 { printf("####################\n"); printf("###### 1.play ######\n"); printf("###### 0.exit ######\n"); printf("####################\n"); } void game() { char ret = 0; char board[ROW][COL]; InitBoard(board,ROW,COL);//初始化棋盘 DisplayBoard(board,ROW,COL);//打印棋盘 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"); if (ret == '#') printf("电脑获胜,再来一局吧\n"); if (ret == 'P') printf("恭喜平局,谁也没有获胜\n"); } int main() { int input = 0; srand((unsigned)time(NULL)); do//循环菜单 { menu(); printf("请选择>:"); scanf("%d",&input); switch (input) { case 1:printf("你已选择继续游戏\n"); game(); break; case 0:printf("你已选择退出游戏\n"); break; default:printf("选择错误,重新选择\n"); } } while (input); return 0; }
六、总结
1.需要掌握产生随机数的方法。
2.游戏总的是采用二维数组实现,其中包括了二维数组的赋值和打印数据。
3.函数的返回值和各种循环结果、选择结构。