前言
三子棋又名井字棋,是个老少皆宜的小游戏,相信大家都玩过吧,游戏规则就不多说了。今天我们就来用 C 语言来简单的实现它。
一、游戏预期画面
二、游戏的实现
1、菜单
void menu() { printf("*****************************\n"); printf("******* 1.play ********\n"); printf("******* 0.exit ********\n"); printf("*****************************\n"); }
2、主函数
首先,我们思考一下,本次游戏应该最少执行一次,在执行过程中进行选择,那么我们应该使用 do while 循环来实现。根据选择不同,来执行相应的程序,那么应该使用 switch 语句。
下面我们来看代码:
int main() { 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); //根据输入的值来确定是否进行循环,0则退出循环 return 0; //这也是为什么在菜单中把退出游戏设置成0的原因 }
3、game函数的实现
3.1、棋盘的打印
我们根据预期画面可以判断出,我们应该创建一个 3*3 的数组来存放我们下的棋,并且得把数组初始化为空格。我们可以根据坐标来选择落子位置。那么问题来了,我们应该如何打印棋盘呢?
首先,我们来仔细观察一下画面中的棋盘,我们可以发现每次下棋都是下在格子的中间也就是下图:
也就是说每个数的旁边有空格,同行的数之间用 | 隔开,第二行是由 - 和 | 组成。
我们把第一行和第二行看成一组,我们就可以得到三组(假设第三组第二行存在)
也就是像下图:
这样我们便能写出如下代码:
#define M 3 #define N 3 void game() { char arr[M][N] = { 0 }; chu_shi(arr, M, N); //初始化数组 da_yin(arr, M, N); //打印棋盘 } void chu_shi(char arr[M][N], int m, int n) { int i = 0; int j = 0; for(i=0;i<m;i++) for (j = 0; j < n; j++) { arr[i][j] = ' '; //将数组初始化为空格 } } void da_yin(char arr[M][N], int m, int n) { int i = 0; for (i; i < m; i++) { printf(" %c | %c | %c \n", arr[i][0], arr[i][1], arr[i][2]); } if (i < m - 1) //若没有 if 则打印了第三组的第二行,if 起限制作用 printf("---|---|---\n"); }
这样,我们就打印出来了整个棋盘。但有个小问题,就是如果我改动棋盘大小呢,现在是 3 * 3 ,我改动 M N 的值为 5 5 呢,我们可以看出,行是没错的,但是列只打印了三列,这明显不行,于是我们按照打印行的思路来打印列,同样的将列分成三组,如图:
我们可以将打印棋盘代码优化成如下:
void da_yin(char arr[M][N], int m, int n) { int i = 0; for (i; i < m; i++) { int j = 0; for (j = 0; j < n; j++) { printf(" %c ", arr[i][j]); if (j < n - 1) //限制 | 的打印 printf("|"); } printf("\n"); if (i < m - 1) { for (j = 0; j < n; j++) { printf("---"); if (j < n - 1) //限制 | 的打印 printf("|"); } } printf("\n"); } }
3.2、玩家下棋
接下来我们应该开始正式玩游戏了,也就是输入落子位置。
我们思考一下,我们坐标应该合理,不能超过数组规定,还有我们下的子应该保存下来,下次该点不能被选择。
我们可以写出如下代码:
void wan_jia(char arr[M][N], int m, int n) { int x = 0; int y = 0; //因为玩家可能不知道是从0行0列开始的 printf("玩家下棋:>\n"); //所以我们就设置成从第1行1列到第3行3列 while (1) { printf("请输入要下棋的坐标:>\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= m && y >= 1 && y <= n) { if (arr[x - 1][y - 1] == ' ') { arr[x - 1][y - 1] = '*'; break; } else { printf("该坐标被占用,请重新输入\n"); } } else { printf("坐标非法请重新输入"); } } }
3.3、电脑下棋
现在我们下了一步,接下来轮到电脑了,同样的逻辑,但电脑下棋是随机的,因此我们要生成随机数,随机数的生成代码如下:
#include<stdio.h> #include<stdlib.h> #include<time.h> int main() { srand((unsigned int)time(NULL)); //设置一个随机数的生成器 int m = rand()%3; //因为 rand 生成随机数范围0~32767 printf("%d",m); //所以 %3 使随机数生成范围为0~2 }
因此电脑下棋代码我们就可以写出了:
srand((unsigned int)time(NULL)); //我们需要放在 main 函数中 void dian_nao(char arr[M][N], int m, int n) { printf("电脑下棋:>\n"); while (1) { int x = rand() % m; int y = rand() % n; if (arr[x][y] == ' ') { arr[x][y] = '#'; break; } } }
这样我们就能完成玩家与电脑之间的对弈,但是美中不足的是,我们无法判断输赢。因此,下一步我们判断输赢。
3.4、判断输赢
我们知道游戏过程中会有几种状态:玩家赢、电脑赢、平局、游戏继续,这四种状态我们就分别用 ’ * ’ 、’ # ‘、’ Q ‘、’ C '来作为返回值。
我们知道三子棋的无非就是横或竖或斜三子成线,这样我们便能写出如下代码:
char is_win(char arr[M][N], int m, int n) { //我们要返回字符所以用 char int i = 0; for (i = 0; i < m; i++) //判断行 { if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][0] != ' ') { return arr[i][0]; } } for (i = 0; i < n; i++) //判断列 { if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[0][i] != ' ') { return arr[0][i]; } } //以下判断两条斜线 if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] != ' ') { return arr[0][0]; } if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ') { return arr[1][1]; } }
我们已经可以判断输赢了,但是我们似乎还漏了平局。
我们便再创建个函数来遍历 arr 数组,来判断是否还有空格,如果有也就是棋盘没满,返回0,反之返回1。
由此,我们可以写出如下代码:
//如果棋盘满了,返回1 //不满返回0 int is_full(char arr[M][N], int m, int n) { int i = 0; for (i = 0; i < m; i++) { int j = 0; for (j = 0; j < n; j++) { if (' ' == arr[i][j]) return 0; } } return 1; }
我们再将其插入判断属于代码中,可以得到:
char is_win(char arr[M][N], int m, int n) { int i = 0; for (i = 0; i < m; i++) { if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][0] != ' ') { return arr[i][0]; } } for (i = 0; i < n; i++) { if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[0][i] != ' ') { return arr[0][i]; } } if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] != ' ') { return arr[0][0]; } if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ') { return arr[1][1]; } //判断平局 if (is_full(arr, m, n) == 1) { return 'Q'; } //继续 return 'C'; }
3.5、game函数的真正实现
我们将 game 函数的实现过程,调用的函数了解了一遍,下面我们来看具体 game 函数如何实现:
void game() { char ret = 0; char arr[M][N] = { 0 }; chu_shi(arr, M, N); //首先,我们初始化数组 da_yin(arr, M, N); //打印一下棋盘 while (1) //进行游戏 { wan_jia(arr, M, N); //玩家先下 ret = is_win(arr, M, N); //每下一步进行判断 if (ret != 'C') //判断是否出现输赢或平局· break; //'C'代表继续,不等于 'C' 即出现了结果 //break 跳出循环 da_yin(arr, M, N); //每下完一步再打印一下棋盘 dian_nao(arr, M, N); //电脑下 ret = is_win(arr, M, N); if (ret != 'C') break; da_yin(arr, M, N); } if (ret == '*') printf("玩家赢\n"); else if (ret == '#') printf("电脑赢\n"); else printf("平局\n"); da_yin(arr, M, N); //最后游戏结束打印棋盘 }
注意,我们判断输赢都是在打印棋盘之前的,如果出了结果,我们是不知道棋盘状况的,所以游戏最后应该打印棋盘。
4、游戏具体代码
像这样一个游戏,我们应该分三个部分:game.h、test.c、game.c。
game.h : 包含头文件和对 test.c 中函数的声明。
test.c :将函数封装起来,使得 game.c 尽量简洁
game.c : 游戏主体,包含 main 函数。
4.1、game.h
#pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define M 3 #define N 3 //初始化数组 void chu_shi(char arr[M][N], int m, int n); //打印棋盘 void da_yin(char arr[M][N], int m, int n); //玩家下棋 void wan_jia(char arr[M][N], int m, int n); //电脑下棋 void dian_nao(char arr[M][N], int m, int n); //判断游戏状态 char is_win(char arr[M][N], int m, int n);
4.2、test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //初始化数组 void chu_shi(char arr[M][N], int m, int n) { int i = 0; int j = 0; for(i=0;i<m;i++) for (j = 0; j < n; j++) { arr[i][j] = ' '; } } //打印棋盘 void da_yin(char arr[M][N], int m, int n) { int i = 0; for (i; i < m; i++) { int j = 0; for (j = 0; j < n; j++) { printf(" %c ", arr[i][j]); if (j < n - 1) printf("|"); } printf("\n"); if (i < m - 1) { for (j = 0; j < n; j++) { printf("---"); if (j < n - 1) printf("|"); } } printf("\n"); } } //玩家下棋 void wan_jia(char arr[M][N], int m, int n) { int x = 0; int y = 0; printf("玩家下棋:>\n"); while (1) { printf("请输入要下棋的坐标:>\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= m && y >= 1 && y <= n) { if (arr[x - 1][y - 1] == ' ') { arr[x - 1][y - 1] = '*'; break; } else { printf("该坐标被占用,请重新输入\n"); } } else { printf("坐标非法请重新输入"); } } } //电脑下棋 void dian_nao(char arr[M][N], int m, int n) { printf("电脑下棋:>\n"); while (1) { int x = rand() % m; int y = rand() % n; if (arr[x][y] == ' ') { arr[x][y] = '#'; break; } } } //如果棋盘满了,返回1 //不满返回0 int is_full(char arr[M][N], int m, int n) { int i = 0; for (i = 0; i < m; i++) { int j = 0; for (j = 0; j < n; j++) { if (' ' == arr[i][j]) return 0; } } return 1; } //判断输赢 char is_win(char arr[M][N], int m, int n) { int i = 0; for (i = 0; i < m; i++) { if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][0] != ' ') { return arr[i][0]; } } for (i = 0; i < n; i++) { if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[0][i] != ' ') { return arr[0][i]; } } if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[0][0] != ' ') { return arr[0][0]; } if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ') { return arr[1][1]; } //判断平局 if (is_full(arr, m, n) == 1) { return 'Q'; } //继续 return 'C'; }
4.3、game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" //包含 game.h void menu() { printf("*****************************\n"); printf("******* 1.play ********\n"); printf("******* 0.exit ********\n"); printf("*****************************\n "); } void game() { char ret = 0; char arr[M][N] = { 0 }; chu_shi(arr, M, N); da_yin(arr, M, N); while (1) { wan_jia(arr, M, N); ret = is_win(arr, M, N); if (ret != 'C') break; da_yin(arr, M, N); dian_nao(arr, M, N); ret = is_win(arr, M, N); if (ret != 'C') break; da_yin(arr, M, N); } if (ret == '*') printf("玩家赢\n"); else if (ret == '#') printf("电脑赢\n"); else printf("平局\n"); da_yin(arr, M, N); } int main() { int input = 0; srand((unsigned int)time(NULL)); //设置一个随机数的生成器 do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,重新选择!\n"); break; } } while (input); return 0; }
这样我们的三子棋游戏就圆满完成了,咋一看似乎头晕晕的,但是如果我们把它拆分为一个个小步骤,我们便能轻易理解并写出来。
学习就是这样,碰到难题往往拆分步骤就变简单了。
以后会给大家带来惊险又刺激的扫雷游戏。
那我们下期见了~