当前位置:网站首页>保姆级手把手教你用C语言写三子棋
保姆级手把手教你用C语言写三子棋
2022-07-06 09:11:00 【East-sunrise】
三子棋、扫雷......这些游戏大家并不陌生吧?但是如果能够自己通过编程实现这些游戏,那该是多酷的一件事啊
目录
前言
C语言在学习了函数还有数组后,我们可以自己写一些经典的小游戏了。这能让我们更加熟练地掌握知识。今天我们将通过C语言,实现简单版三子棋小游戏!下面就一起来一步一步地实现吧!!
一、三子棋游戏展示及介绍
在开始今天的分享前,我们先总体的了解一下游戏的最终呈现并且可以先准备好编程思路
- 首先在游戏开始前,会呈现一个供玩家选择的开始菜单,可以对是否进行游戏而做出选择
- 当玩家选择开始游戏后便立即呈现出一个棋盘可以让玩家下棋
- 然后便是游戏环节的进行,由玩家和电脑分别下棋进行人机博弈
- 随着游戏的进行会进行游戏的输赢判定并给出结果
- 最后当一局游戏结束后会再次出现选择菜单供玩家选择是否继续游戏
️当我们对我们将要实现的游戏有了大概的设计及思路后便可开始我们的编程之旅啦~~
二、三子棋游戏编程实现
1.游戏开始界面及代码雏形模板
在开始编程之前我们应该已经掌握函数知识,因为游戏的代码并没有很简单并且会分成好多个部分。这时就需要利用封装函数使得我们的程序更加简洁易读。
️在此我想先分享一个我自己的小心得:
当我们已经熟练掌握函数时,在要开始编程的时候可以先顺着思路给出一个程序雏形,当要实现什么功能时可以先定义一个函数,等到思路捋清后再进行函数的实现
代码展示:
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);
return 0;
}
- 以上的代码就是三子棋游戏的雏形模板,也可以理解为我们实现这款游戏的编程思路。而根据我们要实现的功能我也先按照思路封装了函数。如Menu( )、Gmae( )
- 而通过我们开头的游戏展示及其思路,我们要让玩家能够不止一次地玩游戏(三子棋对于小学生可能有点幼稚,但是大学生就刚刚好)所以我们应该创建一个循环结构
- 其次,当程序一运行时就会出现游戏的开始选择菜单,也就是说在进行判断前就已经先打印出了菜单,已经先执行了一次 ------> 那么我们就应该采用do......while结构
- 而当你做出选择后,应该能根据你做出的选择而判断后执行不同功能 ------>那么我们就可以采用switch结构。而在此有个可以使得代码更加简洁的小设计,我们可以发现,当我们菜单选择0的时候便退出游戏也即是退出循环,而当输入非0的选择便会继续循环。那么我们正好可以将我们的输入变量input放在do......while结构的while判断位置。
当程序雏形和思路已经初步形成后,便开始对游戏的具体实现了。
首先是Menu()函数的实现,便是打印出游戏开始菜单。此函数比较简单,在此不过多赘述。
void menu() {
printf("*******************************\n");
printf("***** 1.play 0.exit *****\n");
printf("*******************************\n");
}
接着便是对Game( )函数的实现。这才是今天的重头戏️......
2.构建棋盘
- 当我们选择开始游戏后会先呈现出一个初始棋盘,这个棋盘看似简单实则不然哦
- 我们应该想到,这是要供我们接下来下棋的一个棋盘,而不是简简单单用printf函数打印出几行几列就可以的。而我们和电脑下的每一个棋子都会保存在棋盘上待到本轮游戏结束。也就意味着这个棋盘必须具备存储元素的功能 ------->数组
- 没错,就是数组。所以构建棋盘就是等同于初始化一个数组,并且数组元素初始化为空格。因此我们可以在Game()函数中再封装一个初始化棋盘的函数InitBoard()。
- 而初始化棋盘后要将棋盘呈现打印出来,我们在此可以封装一个呈现棋盘的函数DisplayBoard()。而初始化棋盘和打印棋盘也即是对数组进行操作,所以对于这两个函数我们应该传入数组以及棋盘的行数列数
- 并且在打印棋盘的时候我们还要打印出每个棋格的分割线,这也是我们要考虑的问题
代码展示:
void InitBoard(char board[3][3], int 3, int 3)
{
for(int i = 0;i<3;i++)
for (int j = 0; j < 3; j++)
{
board[i][j] = ' ';
}
}
void DispalyBoard(char board[3][3], int 3, int 3)
{
for (int i = 0; i < 3; i++)
{
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//打印分割行
if (i < row - 1)//构建这个判断能够使得棋盘的最后一行没有分割行
printf("---|---|---\n");
}
}
看完以上的代码后你是否有一些发现或者想法?
如果有一天,想要追求更高难度,不局限与三子棋。想要变成五子棋?十字棋?那岂不是要将代码里所有的“3”都改掉?并且打印棋盘的棋格分割也会出现错误需要重写。
所以上面的程序被我们写“死”了,也就是代码的耦合度太高,拓展力太差。这时我们就需要做到“解耦合”从而提高我们程序的拓展力
我们可以在程序的开头进行宏定义#define,定义棋盘的行数ROW和列数COL,并且对打印棋盘的函数进行优化。
优化版本代码展示:
#define ROW 3
#define COL 3
void InitBoard(char board[ROW][COL], int row, int col)
{
for(int i = 0;i<row;i++)
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
void DisplayBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
//打印数据
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//打印棋盘的每一行可以想着能把什么看成相同的一组
//然后进行有条件的循环
//这时候你会发现,空格+数据+空格+ | 是一组
//最后一个的时候没有分割符,然后有几列就打印几组
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//打印分割行---别忘了加一个判断使得最后一行没有分割行
//printf("---|---|---\n");
if (i < row - 1)
{
for (int j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
注:以上是对初始化棋盘函数IntialBoard() 和 呈现棋盘函数DisplayBoard()的定义。而在打印棋盘的分割符时由于想要提高程序的拓展力,因而有些难度。有关的思考我通过在代码中的注释呈现。
3.过渡
根据第一部分的游戏展示及其思路,我们以及完成了游戏开始菜单以及游戏函数Game()的部分功能(初始化棋盘、呈现棋盘)而接下来将要实现的便是游戏的重点 ------人机博弈功能。同样的我们可以先通过定义函数将我们的大概思路框架呈现出来。
我们准备设计玩家先于电脑并依次下棋,并且每方下完一次棋后呈现新的棋盘。同时也应该具备判断输赢的功能。于是我大概构建了以下框架
void game() { char board[ROW][COL] = { ' ' }; InitialBoard(board, ROW, COL); DisplayBoard(board, ROW, COL); while (1) { PlayerMove();//玩家落子 DisplayBoard(board, ROW, COL); IsWin() //判断输赢机制 ComputerMove();//电脑落子 DisplayBoard(board, ROW, COL); IsWin() //判断输赢机制 } }
在此框架中我定义了玩家下棋函数PlayerMove( ),电脑下棋函数ComputerMove( ),还有输赢判断函数IsWin( )。接下来我们将一一实现。
4. 玩家与电脑落棋设计
- 从游戏的展示我们可以知道,我们想要玩家下棋时只需输入棋格坐标即可下棋。而数组的行列坐标都是从0开始的,但是可能玩家并不知道这一点对吧?所以这是我们接下来应该解决的问题。
- 其次,我们还能再考虑到什么问题呢?下棋下棋,是不是有可能会下错位置,没在棋盘范围里?也可能是不是下的位置已经有棋子了?所以我们应该对下棋坐标的合法性进行判断
代码展示:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x, y;
printf("玩家请下棋:\n");
while (1)
{
printf("请输入坐标:\n");
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");
}
}
}
当玩家下棋后便是电脑下棋。而我们这款简易版三子棋游戏是设计电脑下棋时,没有策略,随机找空格下棋。所以这个随机,就需要调用我们生成随机数的函数了。
要引用头文件#include<stdlib.h> 、 #include<time.h>
然后在主函数设置随机数srand((unsigned)time(NULL));
具体原理这里就不过多赘述了
代码展示:
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
int x, y;
//判断坐标是否被占用
while (1)
{
x = rand() % row;//表示范围是0~row
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
5.游戏输赢判定设计
- 三子棋想必大家肯定知道规则吧,所以我们设计输赢判断的功能其实也就是一个遍历数组的过程,看是否有三个相同元素相连
- 而输赢判断应该在每下一次棋就执行一次,若有结果了就结束游戏,返回结果
- 然后此时我们应该思考一个问题:判定后有多少种结果?-------1.玩家赢 2.电脑赢 3.平局(棋盘已经下满) 4.还没分出胜负,游戏继续
- 说到底,输赢判断应该返回不同结果,并根据不同结果做出不同的反应。那么我们可以设计当玩家赢时,返回‘ * ’,当电脑赢时返回‘ # ’,当平局时返回‘ Q ’,当还未分出胜负时返回‘ C ’️
代码展示:
char IsWin(char board[ROW][COL], int row, int col)
{
//先判断行
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
//列
for (int 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];
}
}
//对角线
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[0][2] != ' ')
{
return board[1][1];
}
//能走到这里说明没人赢,那就可能是平局
if (IsFull(board,row,col))
{
return 'Q';
}
//如果没人赢又不是平局,那游戏继续
return 'c';
}
在设计输赢判断函数的过程中,由于棋盘下满才会返回平局结果,所以我们再封装一个IsFull()函数对棋盘是否已满做出判断 ️
int IsFull(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
完成了下棋函数和输赢判断函数的实现后,我们的代码也将接近尾声了。剩下的就是设计Game( )函数根据输赢判断函数的不同结果做出不同反应了。
代码展示:
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//开始下棋
while (1)
{
PlayerMove(board, ROW, COL);
//判断输赢
ret = IsWin(board,ROW,COL);
if (ret != 'c')
{
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
//判断输赢
ret = IsWin(board,ROW,COL);
if (ret != 'c')
{
break;
}
DisplayBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家赢了!\n");
}
else if (ret == '#')
{
printf("电脑赢了!\n");
}
else
{
printf("平局!\n");
}
DisplayBoard(board, ROW, COL);
}
三、最终代码呈现
由于这次代码的函数较多,我们可以另起一个以.h为后缀的头文件专门放置函数声明,另起一个以.c为后缀的源文件去定义函数,这样子我们游戏测试的源文件中就较为简洁易读
1.test.c文件
#include"game.h"
void menu() {
printf("*******************************\n");
printf("***** 1.play 0.exit *****\n");
printf("*******************************\n");
}
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//开始下棋
while (1)
{
PlayerMove(board, ROW, COL);
ret = IsWin(board,ROW,COL); //判断输赢
if (ret != 'c')
{
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
ret = IsWin(board,ROW,COL); //判断输赢
if (ret != 'c')
{
break;
}
DisplayBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家赢了!\n");
}
else if (ret == '#')
{
printf("电脑赢了!\n");
}
else
{
printf("平局!\n");
}
DisplayBoard(board, ROW, COL);
}
int main() {
srand((unsigned)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);
return 0;
}
2.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);
3.game.c文件
#include"game.h"
void InitBoard(char board[ROW][COL], int row, int col)
{
for(int i = 0;i<row;i++)
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
void DisplayBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
{
for (int 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, y;
printf("玩家请下棋:\n");
while (1)
{
printf("请输入坐标:\n");
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 ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
int x, y;
//判断坐标是否被占用
while (1)
{
x = rand() % row;//表示范围是0~row
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
int IsFull(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
//判断输赢
char IsWin(char board[ROW][COL], int row, int col)
{
//先判断行
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{
return board[i][1];
}
}
//列
for (int 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];
}
}
//对角线
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[0][2] != ' ')
{
return board[1][1];
}
if (IsFull(board,row,col))
{
return 'Q';
}
//如果没人赢又不是平局,那游戏继续
return 'c';
}
四、心得体会
这个代码可以算是我学到目前为止写过最难的一个,学习后也是受益匪浅。其实它运用的知识没有很多,主要运用了数组、函数的知识。而更多收获的是一个顺着思路一步一步前进一步一步优化拓展的编程感受,虽然在前进的时候有时候会感觉到举步维艰,但是当解决了一关又一关后那个豁然开朗和成就感又是令人沉醉且享受的。
今天的分享就到此为止啦,欢迎大家探讨及建议!️️点赞关注走一波~~~
边栏推荐
- Not registered via @enableconfigurationproperties, marked (@configurationproperties use)
- MySQL底层的逻辑架构
- Chrome浏览器端跨域不能访问问题处理办法
- UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xd0 in position 0成功解决
- 再有人问你数据库缓存一致性的问题,直接把这篇文章发给他
- MySQL learning diary (II)
- Not registered via @EnableConfigurationProperties, marked(@ConfigurationProperties的使用)
- PyTorch RNN 实战案例_MNIST手写字体识别
- Installation of pagoda and deployment of flask project
- cmooc互联网+教育
猜你喜欢
[Julia] exit notes - Serial
MySQL combat optimization expert 04 uses the execution process of update statements in the InnoDB storage engine to talk about what binlog is?
C杂讲 文件 续讲
用于实时端到端文本识别的自适应Bezier曲线网络
Routes and resources of AI
The underlying logical architecture of MySQL
AI的路线和资源
Use xtrabackup for MySQL database physical backup
软件测试工程师必备之软技能:结构化思维
基于Pytorch的LSTM实战160万条评论情感分类
随机推荐
cmooc互联网+教育
The governor of New Jersey signed seven bills to improve gun safety
Security design verification of API interface: ticket, signature, timestamp
Const decorated member function problem
MySQL ERROR 1040: Too many connections
Simple solution to phpjm encryption problem free phpjm decryption tool
MySQL combat optimization expert 05 production experience: how to plan the database machine configuration in the real production environment?
UEditor国际化配置,支持中英文切换
NLP路线和资源
MySQL real battle optimization expert 11 starts with the addition, deletion and modification of data. Review the status of buffer pool in the database
17 medical registration system_ [wechat Payment]
MySQL storage engine
Constants and pointers
Anaconda3 安装cv2
MySQL learning diary (II)
MySQL实战优化高手02 为了执行SQL语句,你知道MySQL用了什么样的架构设计吗?
MySQL實戰優化高手08 生產經驗:在數據庫的壓測過程中,如何360度無死角觀察機器性能?
16 medical registration system_ [order by appointment]
A necessary soft skill for Software Test Engineers: structured thinking
UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xd0 in position 0成功解决