前言
“你跑的快 耳边都是风声 你跑的慢耳边自然就是闲言碎语”。
今天小编来教各位写一个简易的三子棋小游戏吧。
一、游戏整体框架
二、菜单选择
无论什么游戏都标明是否进入或退出游戏当然我们的三子棋也要有这个常规操作了。
那我们该如何制作菜单呢?
简而言之就是直接打印出来然后使用switch语句进行选择,我们来看一下代码吧
#include<stdio.h> void menu() { printf("**************************\n"); printf("******* 1.play *******\n"); printf("******* 0.exit *******\n"); printf("**************************\n"); } void game() { } int main() { int input = 0; do { menu(); printf("请玩家选择菜单>:\n"); scanf("%d", &input); switch (input) { case 1: printf("进入游戏\n"); game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择:>\n"); break; } } while (input); return 0; }
最上面的menu就是一个简易的菜单我相信这一定是没问题的所以我们不过多的解说了,那我们接着看主函数里面的do…while循环顾名思义就是循环,当我们一局游戏结束后还想玩怎么办呢所以就需要一个循环。当while(input)我们选择0时就会退出了,接着我们看到了switch语句进行菜单选择,选择1时,就进入游戏game(),选择0时就退出,选择其他数字时就会提醒用户重新选择。这样我们一个简易的菜单就制作完成了。
我们来看一下效果吧:
注意:我们在写代码的时候一定要写一部分测试一部分争取找到错误并调试改正,不要等项目完成后测试海里捞针找不到bug;
三、棋盘和棋盘的初始化、打印
(1).棋盘
所谓棋盘也就相当于一个二维数组,各位还知道二维数组怎么写吗?
arr[3][3]
同理我们就知道棋盘怎么写了我们看一下代码吧
接下来就是我们的初始化了将棋盘初始化为空格
(2).棋盘的初始化
说到初始化我们不得不说一下声明了;如果我们将三子棋的所有代码写在同一个模块里是不是太过繁杂、冗长。所以我们今天根据三子棋这个基础需要来回顾一下之前分模块写法。我们需要创建一个头文件命名为game.h(当然命名什么都可以我只是根据我的想法来的),我们将函数的声明写在这里面当我们需要的时候可以随时引用头文件名称(#include"game.h")当然说一下引用的时候不是< >而是" "
除此之外我们也要提一下之前的定义了,这是三子棋那如果是五子棋,十字棋,我们是不是需要重新改写一遍代码呢太过麻烦,所以这就要提到(#define)定义的好处了
这样将我们想要将三子棋改为五子棋或者十字棋的时候只需要改定义就可以使行列改变我们把行(ROW)列(COL)现在定义为3;
好了,定义和声明就说到现在我们回归正题开始对棋盘初始化,具体代码如下:
函数传参数的时候,我们接受的时候是直接用的数组的形式,row和col时行和列的形参
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] = ' '; } } //memset(&board[0][0],' ',row*col*sizeof(board[0][0])); }
我们将初始化函数放在了另一个模块里也就是game.c,不得不说一下,我们前面的菜单代码是放在test.c中写的我们是分了三个模块哦,小伙伴不要搞错所以我们在这个代码里也要引用头文件#include"game.h" #include<stdio.h>,既然我们test.c和game.c都引用了#include<stdio.h>我们何不把它放在game.h中直接引用game.h
(3).棋盘的打印
接下来就是我们棋盘的打印了:那棋盘怎么打印呢?
我们可以将棋盘的上面的%c(也就是要打印的字符)和|还有—看成一组,所以我们就是需要三组但是最后一行并不需要—所以我们在i<row-1的时候才打印。
我们可能写成:
void DisplayBoard(char board[ROW][COL],int row,int col) { int i = 0; for (i = 0; i < row; i++) { printf(" %c | %c | %c \n",board[i][0],board[i][1],board[i][2]); if (i < row - 1) { printf("---|---|---\n"); } } }
但是这就又回到了我们之前的问题那如果我打印五子棋 十字棋呢
printf(" %c | %c | %c \n",board[i][0],board[i][1],board[i][2]);这里的列是恒固定的,所以我们需要灵活变通一下,具体代码如下:
void DisplayBoard(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"); if (i < row - 1) { for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } printf("\n"); } } }
我们通过拆分使将%c 和 | 看成一组打印一个%c打印一个| 随着j++会导致最后一列多打印一个 | 所以我们要在j<col-1之前打印同理,将—和 | 看成一组,在i<row-1的前提下也要保证j<col-1不要打印 | 别忘了我们打印完这一行需要换行。
四、玩家落子
玩家落子我们可能会将x,y分别作为横坐标和纵坐标新手可不是我们程序员,知道数组就知道数组是从0开始的,他们认为第一行就是1开始的,所以
我们不应该写成board[x][y] = ’ * ';而应该写成board[x-1][y-1] = ‘*’;
void PlayerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; scanf("%d %d", &x, &y); board[x][y] = '*'; }
具体代码如下:
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)//对于用户来说坐标是从1开始的但是对于程序员来说下标是是从0开始的 { //落子时我们要考虑这个位置是不是被占用 if (board[x - 1][y - 1] == ' ')//所以这里对玩家里说的x,y我们应该写为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) { int x = 0; int y = 0; printf("电脑下棋:>\n"); while (1)//当电脑下棋位置被占用时不用else提醒它让它重新生成坐标就行了所以写成一个循环 { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }
#define _CRT_SECURE_NO_WARNINGS 1 #include"add.h" void menu() { printf("**************************\n"); printf("******* 1.play *******\n"); printf("******* 0.exit *******\n"); printf("**************************\n"); } void game() { //存放数据需要一个3*3二维数组 char board[ROW][COL] = { 0 };//因为我们三子棋游戏在里面放*和#所以是一个char类型的数组 //有棋盘后初始化为空格,当我们在里面放入*或者#时覆盖了空格 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);//打印电脑的落子 ret = IsWin(board, ROW, COL); //打印棋盘 //判断输赢 } } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请玩家选择菜单>:\n"); scanf("%d", &input); switch (input) { case 1: printf("进入游戏\n"); game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择:>\n"); break; } } while (input); return 0; }
六、判断输赢
对于输赢的判断我们有以下几种:
1.电脑赢(如果电脑赢我们返回#)
2.玩家赢(如果玩家赢我们判断*)
3.平局(如果平局我们返回’Q’)
4.游戏继续(如果游戏继续我们返回’C’)
首先我们先判断坐标中的哪种符号能连成线,注意这时候我们要返回字符,返回值就不能写void了应该写成char
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][2] && board[1][1] != ' ') { return board[1][1]; }
上面这种分别是电脑和玩家赢输的符号是否连成一条线我们还少平局的情况:
判断平局也就是判断坐标是否全部满如果都满了还是没有人赢就证明是平局了
int Is_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; } //平局 //满了返回1 不满返回0 if (Is_Full(board,row,col)) { return 'Q'; } //游戏继续 return 'C'; }
void game() { //存放数据需要一个3*3二维数组 char board[ROW][COL] = { 0 };//因为我们三子棋游戏在里面放*和#所以是一个char类型的数组 //有棋盘后初始化为空格,当我们在里面放入*或者#时覆盖了空格 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"); } if (ret == '#') { printf("电脑赢\n"); } if (ret == 'Q') { printf("平局\n"); } }
最后看IsWin的返回值,看看是返回什么,来判断到底是谁赢了。
七、全部代码
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.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);//返回的是字符所以是char
#define _CRT_SECURE_NO_WARNINGS 1 #include"add.h" void menu() { printf("**************************\n"); printf("******* 1.play *******\n"); printf("******* 0.exit *******\n"); printf("**************************\n"); } void game() { //存放数据需要一个3*3二维数组 char board[ROW][COL] = { 0 };//因为我们三子棋游戏在里面放*和#所以是一个char类型的数组 //有棋盘后初始化为空格,当我们在里面放入*或者#时覆盖了空格 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"); } if (ret == '#') { printf("电脑赢\n"); } if (ret == 'Q') { printf("平局\n"); } } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请玩家选择菜单>:\n"); scanf("%d", &input); switch (input) { case 1: printf("进入游戏\n"); game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择:>\n"); break; } } while (input); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include"add.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] = ' '; } } //memset(&board[0][0],' ',row*col*sizeof(board[0][0])); } void DisplayBoard(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"); 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 <= row && y >= 1 && y <= col)//对于用户来说坐标是从1开始的但是对于程序员来说下标是是从0开始的 { //落子时我们要考虑这个位置是不是被占用 if (board[x - 1][y - 1] == ' ')//所以这里对玩家里说的x,y我们应该写为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) { int x = 0; int y = 0; printf("电脑下棋:>\n"); while (1)//当电脑下棋位置被占用时不用else提醒它让它重新生成坐标就行了所以写成一个循环 { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } } int Is_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; } //至少判断四种结果:电脑赢(返回#),玩家赢(返回*),平局(返回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][2] && board[1][1] != ' ') { return board[1][1]; } //平局 //满了返回1 不满返回0 if (Is_Full(board,row,col)) { return 'Q'; } //游戏继续 return 'C'; }
总结
Ending,到此为止,我们的三子棋小游戏就告一段落了,如果后续想了解更多,就请关注我吧,在接下来的茫茫探索路上我们一起前进。