当前位置:网站首页>从零到一快速学会三子棋
从零到一快速学会三子棋
2022-08-05 02:11:00 【随风的浪】
前言
三子棋又名井字棋,是个老少皆宜的小游戏,相信大家都玩过吧,游戏规则就不多说了。今天我们就来用 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;
}
这样我们的三子棋游戏就圆满完成了,咋一看似乎头晕晕的,但是如果我们把它拆分为一个个小步骤,我们便能轻易理解并写出来。
学习就是这样,碰到难题往往拆分步骤就变简单了。
下期预告
下期肯定要给大家带来惊险又刺激的扫雷了。
那我们下期见了~
边栏推荐
- EBS uses virtual columns and hint hints to optimize sql case
- “嘀哩哩,等灯等灯”,工厂安全生产的提示音
- 汇编语言之源程序
- Jincang database KingbaseES V8 GIS data migration solution (3. Data migration based on ArcGIS platform to KES)
- Transfer Learning - Joint Geometrical and Statistical Alignment for Visual Domain Adaptation
- 编译预处理等细节
- [Endnote] Word inserts a custom form of Endnote document format
- Flink 1.15.1 集群搭建(StandaloneSession)
- 网络安全与元宇宙:找出薄弱环节
- 如何发现一个有价值的 GameFi?
猜你喜欢
Leetcode刷题——22. 括号生成
Exploding the circle of friends, Alibaba produced billion-level concurrent design quick notes are too fragrant
"Dilili, wait for the lights, wait for the lights", the prompt sound for safe production in the factory
source program in assembly language
基于OpenVINO工具套件简单实现YOLOv7预训练模型的部署
Live playback including PPT download | Build Online Deep Learning based on Flink & DeepRec
(17) 51 MCU - AD/DA conversion
MySQL learning
.Net C# 控制台 使用 Win32 API 创建一个窗口
MySQL学习
随机推荐
Pisanix v0.2.0 发布|新增动态读写分离支持
如何逐步执行数据风险评估
C学生管理系统 据学号查找学生节点
PHP Skills Assessment
Live preview | 30 minutes started quickly!Look at credible distributed AI chain oar architectural design
.Net C# 控制台 使用 Win32 API 创建一个窗口
力扣-二叉树的前序遍历、中序遍历、后序遍历
MySQL学习
Why is this problem reported when installing oracle11
dotnet 6 为什么网络请求不跟随系统网络代理变化而动态切换代理
[Redis] Redis installation under Linux
CPDA|运营人如何从负基础学会数据分析(SQL)
海量服务实例动态化管理
C language basics -- pointers
“嘀哩哩,等灯等灯”,工厂安全生产的提示音
C语言基础知识 -- 指针
Exploding the circle of friends, Alibaba produced billion-level concurrent design quick notes are too fragrant
Hypervisor related knowledge points
DAY22:sqli-labs 靶场通关wp(Less01~~Less20)
Domain Driven Design - MDD