C语言经典项目实战——三子棋
Posted Zheng"Rui
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言经典项目实战——三子棋相关的知识,希望对你有一定的参考价值。
三子棋,又称井字棋,是我们经常爱玩的游戏,今天来学习,如何用C语言来实现一个简单的三子棋小游戏。
目录
一、分析三子棋项目需求
首先对我们要实现的三子棋项目,分析它的需求。为了了解它需要哪些函数,我们先模拟一遍下棋的过程、
/*
1.初始化棋盘
2、玩家落子
2.1 展示棋盘
2.2 判断胜负
3、电脑落子
3.1 展示棋盘
3.2 判断胜负
*/
在这个步骤中,我们看到,要实现三子棋,起码要实现,棋盘创建,期盼展示,玩家落子,电脑落子,胜负判断等功能的函数。由此就可以开展,三子棋的实现。
二、各个功能实现
首先在play.h,这个头文件中,写下棋盘的初始化信息,如棋盘为三行三列
#ifndef _PLAY_H
#define _PLAY_H
#define ROW 3
#define COL 3
#endif
注意:这里的#ifdef 后面加的名字可以是任意的,但一般都是自己的大写文件名,这一句代码的作用是,防止在头文件中重复申明了多个函数,或者是结构体。
然后进行函数申明,(这里我们还没有写主函数,也没有进行任何一个变量定义,但是没有关系,我们先进行函数的申明,然后再play.c文件中,进行函数定义)
void menu();//菜单
void InitBoard(char board[ROW][COL]);//初始化棋盘
void ShowBoard(char board[ROW][COL]);//展示棋盘
void Player(char board[ROW][COL]);//玩家下棋
void Computer(char board[ROW][COL]);//电脑下棋
char IsWin(char board[ROW][COL]);//判断胜负
根据上一步的需求分析,我们知道主要实现的就是,初始化棋盘,棋盘展示,玩家下棋,电脑下棋,判断胜负,这几个函数,所以我们先将其定义出来,其中的参数我们都向其中传入棋盘board。
接下来,进行函数实现,注意,接下来的函数定义,都在play.c中实现。
首先看菜单函数的实现。
下棋的时候,我要知道按什么按键开始游戏,按什么按键退出游戏,所以需要一个菜单函数
1.menu()函数
void menu()
{
printf("请开始游戏吧!\\n");
printf("---------------------------------\\n");
printf("-----1.开始--------0.退出--------\\n");
printf("---------------------------------\\n");
}
这个函数向玩家提示,按1开始游戏,按0退出。
来看它的显示效果
2.InitBoard()函数
接下来实现棋盘的初始化功能
void InitBoard(char board[ROW][COL])
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
我们遍历整个二维数组,将其中的每个单元都赋值为‘ ’(空格),先对其进行初始化。
然后实现棋盘展示功能
实现这个功能的时候,并不是把这个二维数组简单的按顺序打印(当然也可以,只要你不嫌简陋) 这里,我们想个我们的棋盘加上一些边界,让棋盘看上去美观一点、就像这样:
在这些空格中放入棋子。
3.ShowBoard()函数
接下来看,如何打印这个棋盘。
void ShowBoard(char board[ROW][COL])
{
for (int i = 0; i < ROW * 2 + 1; i++)
{
if (i % 2 == 0)
{
for (int j = 0; j < COL; j++)
{
printf("----");
}
printf("\\n");
}
else
{
for (int j = 0; j < COL * 2 + 1; j++)
{
if (j % 2 == 0)
{
printf("|");
}
else
{
printf(" %c ", board[i / 2][j / 2]);
}
}
printf("\\n");
}
}
}
注意:我上面给的只是一开始没下棋时候的棋盘,而我们用这个函数的时候,不仅要展示棋盘的边界,还要展示其中的棋子。所以,我们将整个棋盘打印的时候看成是7行7列的一个数组,然后去打印。
观察棋盘,我们发现横线只在0,2,4,6行出现,数据在1,3,5行出现,竖线在0,2,4,6列出现,数据在1,3,5列出现。由此就可以打印出我们期待的棋盘的样子。
4.Player()函数
然后实现玩家下棋
void Player(char board[ROW][COL])
{
while (1)
{
printf("请输入你的座标:");
int x, y;
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] = 'X';
break;
}
else
{
printf("该位置已经有棋子!\\n");
}
}
else
{
printf("输入错误。\\n");
}
}
}
在这个函数中,要注意使用了while函数,而且还是给了判断条件1,也就是说,如果在这个函数中不使用break的话,就陷入了死循环。为什么要这么设计呢,因为,我们要预防用户输入的棋子坐标不正确 或者 此位置上有其他棋子的情况,需要重新输入,只有当输入正确时,才结束循环,否则一直输入。
看一下函数效果
5.Computer()函数
下面来看电脑下棋功能的实现
和玩家下棋类似,只不过这一次,不需要我们在手动输入,而是采用随机数,进行随机位置下棋。
void Computer(char board[ROW][COL])
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = 'O';
break;
}
}
}
我们让随机数对行数和列数取模,这样就不会有越界的情况,同时使用while循环,防止电脑下在了已经有棋子的地方,来保证电脑棋子的正确性。
下面来看函数效果、
不管是电脑下棋,还是玩家下棋,下过一步之后都要进行胜负判断,判断是否应该结束游戏
7.IsWin()函数
所以下面来实现,判断胜负函数:
char IsWin(char board[ROW][COL])
{
for (int 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 (int j = 0; j < COL; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
{
return board[0][j];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
{
return board[0][2];
}
if (IsFull == 0)
{
return 'Q';
}
return 'n';
}
在这里我们给函数定义一个返回值,目的是通过返回值来判断是否出现赢家,还是应该继续游戏。三子棋判断胜利的条件十分简单,只要判断每一行是否全为相同字母,或者每一列,或者每一个对角线,是否有三个相同的棋子,如果有,则返回这个棋子,这样我们就能在调用它的时候知道,哪一方获得胜利,这里有个小技巧,就是我们不用设置一个字母来放置判断条件然后在最后统一返回,而是在每一次判断后,如果成功就直接返回,这样可以减少代码量。
在判断完是否有赢家之后,如果这个函数还没有结束,就说明没有赢家,那我们就要判断棋盘是否已经满了,如果满了,但是没有出现赢家,说明应该按照平局处理,如果没有赢家,棋盘也没满,则说明结果正常,游戏继续。
观察代码,如果玩家胜利的话,就返回’X’,电脑胜利就返回’O’,平局的话就返回’Q’,如果游戏继续的话就返回’n’,这里我们把返回’n’看作正常情况,那么只要这个函数返回的不是字符n,就说明要么玩家胜利,要么电脑胜利,要么平局,不论如何,游戏应该结束了。利用这个特征,对我们之后调用这个函数,非常有帮助。
同时,判断棋盘有没有满的函数也很简单,只要遍历一遍棋盘,如果发现有空格,说明没满,返回1,如果遍历完成之后,也没有找到空格,说明棋盘满了,返回0;
下面来看具体实现:
int IsFull(char board[ROW][COL])
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
return 1;
}
}
}
return 0;
}
到此处为止,三子棋项目所需要的所有函数已经完成,之后要进行的就是对于这些函数的调用问题。我们仍然设置一个叫RunGame的函数来进行游戏的调用,目的就是为了保证主函数的简短和可读性。
三、RunGame函数和main函数的实现
为了对主函数进行化简,我们设置了RunGame函数,在三子棋这个项目中,因为本身游戏所用到的函数并不多,也不需要复杂的调用,所以这个RunGame函数是可以省略的,直接在主函数中实现,但是需要知道,在其他大一点的项目中,最好添加上这一项,保证可读性。
同时,添加上这个函数,还有一个好处,就是这个函数代表了一次游戏的执行过程,这样我们就能实现,让玩家结束一局游戏之后,选择继续游戏,还是退出游戏。
下面来看具体实现:
void RunGame()
{
char board[ROW][COL];
InitBoard(board);
ShowBoard(board);
char temp;
while (1)
{
Player(board);
ShowBoard(board);
temp = IsWin(board);
if (temp != 'n')
{
break;
}
Computer(board);
ShowBoard(board);
temp = IsWin(board);
if (temp != 'n')
{
break;
}
}
if (temp == 'X'){
printf("玩家胜利!\\n");
}
else if (temp == 'O')
{
printf("电脑胜利!\\n");
}
else if (temp == 'Q')
{
printf("平局!\\n");
}
}
既然是游戏的一次执行过程,那么在游戏的最开始,我们需要先定义一个二维数组当作我们的棋盘,然后调用InitBoard函数来初始化,之后向玩家展示这个初始化好的空棋盘。对于一个游戏来说,只要游戏没有结束,那么我们就应该一直执行它,所以,需要在一个死循环中来执行游戏,当if判断应该结束游戏时,才跳出循环。
这里巧妙地运用了IsWin函数的返回值,用temp来接收函数返回值,每次判断不需要,逐个字符去判断是否相等,而是判断是否为’n’,如果为’n’,说明游戏继续,如果不为’n’,不管它是什么,都说明游戏应该结束,这时跳出循环,在循环体外,判断temp,来说明到底是什么情况。
由于我们之前已经做了很多的铺垫,所以主函数就会十分简短,下面来看主函数的调用
srand((unsigned)time(NULL));
int input;
do{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
RunGame();
break;
case 0:
break;
default:
break;
}
} while (input);
return 0;
}
第一句的sand语句,是为了种下一个随机种子,我们在Computer函数中,要求电脑用随机数生成数据,但是,其实生成的是伪随机数,我们重复运行函数就会发现,电脑下的棋都会出现在固定位置,为了解决这个问题,我们引入随机种子,这个种子的位置可以随意,但一定要在调用Computer函数之前。
之后是一个do~while循环,通过我们输入的数据,如果输入1,则开始游戏,输入0则退出游戏。
四、项目的整体分析
到这里,整个三子棋游戏也就完成了,下面来对我们实现的三子棋游戏进行总结。
首先我们完成了真正的三子棋的大部分必备功能,包括棋盘初始化,玩家和电脑相会对弈,游戏判断胜负等等,它搭建起来一个三子棋游戏的框架,就算是真正的的三子棋游戏,它的内核实现,也是这样的。当然,这个项目也存在问题,就是,我们虽然实现了电脑下棋,但是真正运行就会发现,我们设置的这个电脑玩家总是“笨笨的”,它不知道往哪里下棋可以赢得比赛,这是因为,我们给电脑的只是一个随机数,我们可以保证这个随机数一定是符合要求的,但是不能保证这个随机数会帮助电脑取得胜利,所以和这样的电脑下棋是无聊的。
但是,前面提到,这个项目已经明确了三子棋的框架结构,我们还可以设置两个真人玩家互相对弈,只需要把玩家下棋的代码复制一份,给玩家2,然后调用的时候,让玩家2取代电脑的位置就可以实现,甚至,在掌握网络有关知识后,还可以实现,两个人联网对弈,这都是基于我们目前实现的这个简易的三子棋框架的。
五、完整代码
play.h
#ifndef _PLAY_H
#define _PLAY_H
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 3
#define COL 3
void menu();//菜单
void RunGame();
void InitBoard(char board[ROW][COL]);//初始化棋盘
void ShowBoard(char board[ROW][COL]);//展示棋盘
void Player(char board[ROW][COL]);//玩家下棋
void Computer(char board[ROW][COL]);//电脑下棋
char IsWin(char board[ROW][COL]);//判断胜负
int IsFull(char board[ROW][COL]);
#endif
play.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"play.h"
void menu()
{
printf("请开始游戏吧!\\n");
printf("---------------------------------\\n");
printf("-----1.开始--------0.退出--------\\n");
printf("---------------------------------\\n");
}
void InitBoard(char board[ROW][COL])
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
void ShowBoard(char board[ROW][COL])
{
for (int i = 0; i < ROW * 2 + 1; i++)
{
if (i % 2 == 0)
{
for (int j = 0; j < COL; j++)
{
printf("----");
}
printf("\\n");
}
else
{
for (int j = 0; j < COL * 2 + 1; j++)
{
if (j % 2 == 0)
{
printf("|");
}
else
{
printf(" %c ", board[i / 2][j / 2]);
}
}
printf("\\n");
}
}
}
void Player(char board[ROW][COL])
{
while (1)
{
printf("请输入你的座标:");
int x, y;
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] = 'X';
break;
}
else
{
printf("该位置已经有棋子!\\n");
}
}
else
{
printf("输入错误。\\n");
}
}
}
void Computer(char board[ROW][COL])
{
while (1)
{
int x = rand() % ROW;
int y = rand() % COL;
if (board[x][y] == ' ')
{
board[x][y] = 'O';
break;
}
}
}
char IsWin(char board[ROW][COL])
{
for (int 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 (int j = 0; j < COL; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
{
return board[0][j];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
{
return board[0][2];
}
if (IsFull == 0)
{
return 'Q';
}
return 'n';
}
int IsFull(char board[ROW][COL])
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
return 1;
}
}
}
return 0;
}
void RunGame()
{
char board[ROW][COL];
InitBoard(board);
ShowBoard(board);
char temp;
while (1)
{
Player(board);
ShowBoard(board);
temp = IsWin(board);
if (temp != 'n')
{
break;
}
Computer(board);
ShowBoard(board);
temp = IsWin(board);
if (temp != 'n')
{
break;
}
}
if (temp == 'X'){
printf("玩家胜利!\\n");
}
else if (temp == 'O')
{
printf("电脑胜利!\\n");
}
else if (temp == 'Q')
{
printf("平局!\\n");
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"play.h"
int main()
{
/*
1.初始化棋盘
2、玩家落子
2.1 展示棋盘
2.2 判断胜负
3、电脑落子
3.1 展示棋盘
3.2 判断胜负
*/
srand((unsigned)time(NULL));
int input;
do{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
RunGame();
break;
case 0:
break;
default:
break;
}
} while (input);
return 0;
}
以上是关于C语言经典项目实战——三子棋的主要内容,如果未能解决你的问题,请参考以下文章