前言
人生如棋,落子无悔!你还记得我们的童年游戏“三子棋”吗?你想过有一天我们能用C语言写出“三子棋吗?相信你已经迫不及待了,下面就跟小编一起过关斩将“破棋局”!
文章目录
一、游戏规则:
三点一线决胜负!
二、效果展示
三、项目拆分
这里建议大家在写项目时,将项目拆分为多个模块来实现:
将项目拆分有以下优点:
1、大变小,使部分功能可重用。
2、分离关注点,将来每个团队、每个人只需要关注、维护系统的一部分模块或一个子系统,提升开发效率。
3、代码结构更加清晰,便于项目调试。
四、游戏设计思路
一、 游戏交互界面
- 创建游戏菜单
- 游戏模式选择
- 创建游戏交互主体
二、三子棋游戏
- 创建三子棋盘
- 初始化棋盘
- 打印棋盘
1.简单模式
- 玩家下棋
- 判断输赢
- 电脑下棋
- 判断输赢
2.困难模式
- 电脑模拟智能下棋
- 判断输赢
- 玩家下棋
- 判断输赢
五、游戏交互界面
1.创建游戏菜单
为了能给用户更好的游戏交互体验,我们首先需要设计一个可视化菜单。主要功能是对游戏进程进行选择,其它装饰效果大家可以花点心思,脑洞大开随性设计。
这里就简单介绍一下我设计的游戏菜单:
先看效果:
这里我简单添加了一个文字向中间汇聚打印的效果,主要用到
Sleep()
函数,
system("cls")
系统清屏命令。
代码展示:
voidmenu(){//创建两个数组char arr1[]="------------Welcom to game!!!------------";char arr2[]="*****************************************";int left =0;int right =strlen(arr1)-1;while(left <= right){
arr2[left]= arr1[left];
arr2[right]= arr1[right];printf("%s\n", arr2);//休息1秒Sleep(50);system("cls");//执行系统命令//清空屏幕
left++;
right--;}printf("%s\n", arr2);Sleep(300);printf("************ 1.开始游戏 ************\n");printf("************ 0.退出游戏 ************\n");}
2.游戏模式选择
由于在游戏中添加了AI元素,所以将游戏分为了简单模式和困难模式。所以我们还需要一个游戏模式选择界面。
代码展示:
intmodel(){int choice =0;while(1){printf("----------------------------------------\n");printf("--------------1.简单模式---------------\n");printf("--------------2.困难模式---------------\n");printf("----------------------------------------\n");printf("请选择:>");scanf("%d",&choice);if(choice ==1){printf("\n只有菜鸡才会选择简单模式!!!\n");printf("\n你准备好了吗?游戏要开始喽!!!");Sleep(2000);break;}if(choice ==2){printf("\n你能赢得了电脑吗?\n");printf("你可能不会输,但也不一定会赢!!!");printf("\n你准备好了吗?游戏要开始喽!!!");Sleep(3000);break;}else{printf("输入错误,请重新选!\n");}}return choice;}
好奇的小伙伴可能会问了,为什么此函数需要返回值?我们顺藤摸瓜,带着疑问继续往下看👇
3.搭建游戏交互主体
还记得上次的猜数字游戏吗?三子棋的游戏交互主体也是如出一辙。
代码展示:
intmain(){int input =0;srand((unsignedint)time(NULL));do{
count =1;//保证每一把开始count=1,//使AI模式下,电脑第一次在中间落子(后面详细介绍)menu();printf("请选择:>");scanf("%d",&input);switch(input){case1://三子棋游戏game(model());//这里就用到了函数的链式访问break;case0:printf("退出游戏\n");break;default:printf("选择错误,重新选择\n");Sleep(1000);break;}}while(input);return0;}
效果展示:
还记得model()函数的返回值吗?
在这里
game(model())
表示将玩家模式选择的结果传递给
game()
函数。因为游戏的执行过程被封装在
game()
当中,我们需要在游戏中决定游戏执行方向,所以需要一个返回值记录玩家的选择结果,最后将选择结果交给游戏函数。
六、游戏实现
1.游戏主体逻辑框架-game()
在实现游戏之前,先给出封装后的游戏逻辑框架——
void game(int choice)
当然这个框架不是一下就能搭建好的,我们一般是先搭建一部分,然后随着对游戏功能的不断拓展将框架逐渐完善。这里给出是为了大家更好的理解游戏设计。
我们游戏的设计就是按照下面的思路逻辑,先捋清游戏设计思路以及游戏的相关功能,再对各个功能进行实现。
//游戏主体逻辑框架voidgame(int choice){//创建棋盘char board[ROW][COL];//初始化棋盘InitBoard(board, ROW, COL);char ret=0;//用于接受游戏状态while(1){简单模式if(choice ==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;}困难模式else{//电脑下棋AI(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断电脑是否赢得游戏
ret =IsWin(board, ROW, COL);if(ret !='C')break;//玩家下棋PlayerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断玩家是否赢得游戏
ret =IsWin(board, ROW, COL);if(ret !='C')break;}困难模式}if(ret =='o'){printf("\n------------- 玩家赢了 ------------ \n");}elseif(ret =='x'){printf("\n------------- 电脑赢了 -------------\n");}else{printf("\n--------------- 平局 ------------------\n");}}
1.创建三子棋盘
我们已知三子棋盘是3行3列,因此很容易想到用二维数组来表示棋盘:
//棋盘创建#defineROW3#defineCOL3char board[ROW][COL];
注意:这里的行——
ROW
、列——
COL
采用了宏定义
采用宏定义的优点:
1、方便程序的修改,增加了程序的可塑性(比如我想搭建五子棋盘,只需修改
ROW
,
COL
)
2、提高程序的运行效率:可以省去函数中传参的过程
2.初始化棋盘
当双方均未下棋时,我们需要一个空棋盘。由于初始时棋盘内容不显示,我们通过变量棋盘数组,将棋盘内容初始化为空格。
//棋盘初始化voidInitBoard(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]=' ';}}}
3.打印棋盘
棋盘样式:
通过效果图,我们通过
---
和
|
的有机组合打印出三子棋盘,同时还可以打印一些提示信息,方便玩家辨别棋子。
代码展示:
voidDisplayBoard(char board[ROW][COL],int row,int col){system("cls");printf("********** 三子棋游戏 **********\n");printf("玩家棋子:o\n");printf("电脑棋子:x\n");int i =0;for(i =0; i < row; i++){int j =0;for(j =0; j < col; j++){printf(" %c ", board[i][j]);if(j < col -1)printf("|");}printf("\n");if(i < row -1){
j =0;for(j =0; j < col; j++){printf("---");if(j < col -1)printf("|");}printf("\n");}}}
4.模式规定
为了增加游戏的趣味性,给游戏设置了两种模式,分别是:
- 简单模式规则: 玩家先下棋,电脑后下棋。注: 电脑随机下棋胜率: 玩家>电脑
- 困难模式规则: 电脑先下棋并且第一步棋落子中间,玩家后下棋注: ”模拟电脑智能“下棋(增加下棋算法)胜率: 电脑>=玩家
5.简单模式
(1)玩家下棋
代码展示:
voidPlayerMove(char board[][COL],int row,int col)//玩家下棋{int x =0;int y =0;while(1){printf("请玩家输入下棋的坐标:>");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]='o';break;}else{printf("坐标被占用, 请重新输入\n");}}else{printf("坐标非法, 请重新输入\n");}}}
注意:
1、board[x-1][y-1]
——
x-1,y-1
是因为:玩家不是程序员,输入坐标比二维数组中字符坐标大
1
比如玩家在中间下棋,则会输入坐标
(2 ,2)
,所以我们采用
x-1,y-1
使数组坐标合法化。
2、玩家棋子用o
表示(可自定义)
(2)简单模式电脑下棋
voidComputerMove(char board[ROW][COL],int row,int col)//电脑下棋{printf("电脑走:>\n");while(1){int x =rand()% row;int y =rand()% col;//判断占用if(board[x][y]==' '){
board[x][y]='x';break;}}}
注意:
1、这里使用了随机生成函数rand()
,不清楚怎样使用的小伙伴可以看一下随机生成三剑客
2、不同于玩家下棋,这里省去了判断坐标合法性,因为
x=rand()%row
、
y=rand()%col
必定合法
3、电脑棋子用
x
表示(可自定义)
(3)判断游戏状态
三子棋游戏一共用四种游戏状态:
1、玩家赢
三行、三列、左右对角线存在三个棋子相同且均为o
2、电脑赢
三行、三列、左右对角线存在三个棋子相同且均为x
3、平局
棋盘下满且不存在上述两种情况
4、游戏继续
上述情况均不满足规定:
1、玩家赢--return 'o'
2、电脑赢
--return 'x'
3、平局
--return 'Q'
4、游戏继续
--return 'C'
//判断棋盘是否已满(返回值用于判断是否平局)intIsFull(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]==' '){return0;//棋盘没满}}}return1;//棋盘满了}判断输赢///1、判断三行是否一样charIsWin(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][1]!=' '){return board[i][1];//}}//2、判断三列是否一样for(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];}}//3、判断对角线是否一样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[1][1]!=' '){return board[1][1];}//4、判断平局//如果棋盘满了并且上述情况均不满足,返回Q、否则返回Cint ret =IsFull(board, row, col);if(ret ==1){return'Q';}//继续return'C';}
简单模式下的三子棋到这里就结束了,我们玩一把:
(4)问题与改进
通过观察发现,玩家和电脑每次下棋都会打印一次棋盘,这样的话游戏界面就会非常混乱,不利于玩家观察。为了能够让游戏界面更简洁,可以在打印棋盘函数
DisplayBoard()
中增加一个系统清屏命令
system("cls")
,使界面中只保留一个棋盘。
改进后效果:
是不是变得干净整洁多了!
5.困难模式
困难模式下,玩家下棋与判断游戏状态和简单模式相同,困难模式的核心是电脑下棋规则设计。
通过简单模式的演示效果,我们发现简单模式下电脑下棋总是呆呆的,电脑胜率几乎为零,玩家就算是赢了也没有什么成就感。所以可以给电脑植入一些算法,使电脑”智能“,提高电脑胜率,从而增加游戏的趣味性和挑战性。
(1)困难模式-模拟智能下棋
设计思路:
在设计电脑“智能下棋”之前,我们可以分析一下玩家是怎样取胜的。玩家下棋的时候,总有这样一种思路:
1、先看棋盘上任意连续的三个位置是否有己方的两枚棋子,如果有并且剩下一个位置为空,那么在此处落子就会取胜。
2、如果1、
不满足,再看棋盘任意三个位置是否有对方的两枚棋子,如果有并且剩下一个位置为空,那么在此处落子保证不输
3、如果1、2、
均不满足,那么就分析大局观,在有较大可能赢并且为空的位置落子
补充: 开局先落子,并且落子中间胜率大
显然
3、
对于目前来说实现起来比较困难,但是我们可以对
1、 2、
及
**补充**
进行实现,从而模拟出两种“智能”情况
下面展示“模拟电脑智能下棋”的代码的详细介绍,思路很简单,代码较长,大家慢慢食用!
代码展示:(这里由于代码过长,以缩进后图片形式呈现。详细代码放到了最后!)
七、完整代码:
上面我们已经提到,通过项目拆分,将三子棋项目分为了三个模块(1、逻辑部分 2、声明部分 3、函数定义部分),分别放在文件(test.c
/game.h /game.c)中。
如下图:
1.游戏交互主体-test.c
#include"game.h"int flag=1;//为了限制,每次都以动画方式进场/游戏菜单/voidmenu(){if(flag ==1){char arr1[]="------------Welcom to game!!!------------";char arr2[]="*****************************************";int left =0;int right =strlen(arr1)-1;while(left <= right){
arr2[left]= arr1[left];
arr2[right]= arr1[right];printf("%s\n", arr2);//休息1秒Sleep(50);system("cls");//执行系统命令//清空屏幕
left++;
right--;}printf("%s\n", arr2);Sleep(300);printf("************ 1.开始游戏 ************\n");printf("************ 0.退出游戏 ************\n");}else{printf("------------Welcom to game!!!------------\n");printf("************ 1.开始游戏 ************\n");printf("************ 0.退出游戏 ************\n");}}/游戏模式/intmodel(){int choice =0;while(1){printf("----------------------------------------\n");printf("--------------1.简单模式---------------\n");printf("--------------2.困难模式---------------\n");printf("----------------------------------------\n");printf("请选择:>");scanf("%d",&choice);if(choice ==1){printf("\n只有菜鸡才会选择简单模式!!!\n");printf("\n你准备好了吗?游戏要开始喽!!!");Sleep(2000);break;}if(choice ==2){printf("\n你能赢得了电脑吗?\n");printf("你可能不会输,但也不一定会赢!!!");printf("\n你准备好了吗?游戏要开始喽!!!");Sleep(3000);break;}else{printf("输入错误,请重新选!\n");}}return choice;}/游戏主题/voidgame(int choice){//存储数据 - 二维数组char board[ROW][COL];//初始化棋盘 - 初始化空格InitBoard(board, ROW, COL);//打印一下棋盘 - 本质是打印数组的内容char ret =0;//接受游戏状态/简单模式/while(1){if(choice ==1){//玩家下棋DisplayBoard(board, ROW, COL);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;}/困难模式/else{//电脑下棋AI(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断电脑是否赢得游戏
ret =IsWin(board, ROW, COL);if(ret !='C')break;//玩家下棋PlayerMove(board, ROW, COL);DisplayBoard(board, ROW, COL);//判断玩家是否赢得游戏
ret =IsWin(board, ROW, COL);if(ret !='C')break;}}if(ret =='o'){printf("\n------------- 玩家赢了 ------------ \n");}elseif(ret =='x'){printf("\n------------- 电脑赢了 -------------\n");}else{printf("\n--------------- 平局 ------------------\n");}}/游戏交互逻辑结构/int count=0;//声明外部符号intmain(){int input =0;srand((unsignedint)time(NULL));do{
count =1;//保证每一把开始count=1,使电脑在中间落子menu();printf("请选择:>");scanf("%d",&input);switch(input){case1:game(model());
flag++;break;case0:printf("退出游戏\n");break;default:printf("选择错误,重新选择\n");Sleep(1000);break;}}while(input);return0;}
2.声明部分-game.h
#pragmaonce//头文件的包含/#include<stdio.h>#include<time.h>#include<string.h>#include<windows.h>#include<stdlib.h>符号的定义/#defineROW3#defineCOL3函数的声明////初始化棋盘的voidInitBoard(char board[ROW][COL],int row,int col);//打印棋盘的函数voidDisplayBoard(char board[ROW][COL],int row,int col);//玩家下棋voidPlayerMove(char board[ROW][COL],int row,int col);//电脑下棋(简单模式)voidComputerMove(char board[ROW][COL],int row,int col);//电脑下棋(AI模式)voidAI(char board[ROW][COL],int row,int col);//判断游戏是否有输赢charIsWin(char board[ROW][COL],int row,int col);
3.函数定义部分-game.c
#include"game.h"初始化棋盘/voidInitBoard(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]=' ';}}}打印棋盘/voidDisplayBoard(char board[ROW][COL],int row,int col){system("cls");printf("********** 三子棋游戏 **********\n");printf("玩家棋子:o\n");printf("电脑棋子:x\n");int i =0;for(i =0; i < row; i++){int j =0;for(j =0; j < col; j++){printf(" %c ", board[i][j]);if(j < col -1)printf("|");}printf("\n");if(i < row -1){
j =0;for(j =0; j < col; j++){printf("---");if(j < col -1)printf("|");}printf("\n");}}}玩家下棋/voidPlayerMove(char board[][COL],int row,int col)//玩家下棋{int x =0;int y =0;while(1){printf("请玩家输入下棋的坐标:>");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]='o';break;}else{printf("坐标被占用, 请重新输入\n");}}else{printf("坐标非法, 请重新输入\n");}}}电脑下棋(简单模式)/voidComputerMove(char board[ROW][COL],int row,int col)//电脑下棋{printf("电脑走:>\n");while(1){int x =rand()% row;int y =rand()% col;//判断占用if(board[x][y]==' '){
board[x][y]='x';break;}}}判断棋盘是否已满/intIsFull(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]==' '){return0;//棋盘没满}}}return1;//棋盘满了}判断输赢/charIsWin(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][1]!=' '){return board[i][1];//}}///判断三列///for(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[1][1]!=' '){return board[1][1];}///判断平局/////如果棋盘满了返回1, 不满返回0int ret =IsFull(board, row, col);if(ret ==1){return'Q';}//继续return'C';}
4.模拟智能下棋(完整版)
extern count;//AI模式voidAI(char board[ROW][COL],int row,int col){int i =0;int flag =2;if(count ==1)//定义全局变量,保证电脑先走,第一步一定下中间{
board[1][1]='x';//赋值是一个等号
count++;}else{if(flag ==2){//判断行有没有两个xfor(i =0; i < row; i++){if((board[i][0]== board[i][1])&&(board[i][1]=='x')&&(board[i][2]==' ')){
board[i][2]='x'; flag =1;break;}if((board[i][0]== board[i][2])&&(board[i][2]=='x')&&(board[i][1]==' ')){
board[i][1]='x'; flag =1;break;}if((board[i][1]== board[i][2])&&(board[i][1]=='x')&&(board[i][0]==' ')){
board[i][0]='x'; flag =1;break;}}//判断列有没有两个xfor(i =0; i < col; i++){if((board[0][i]== board[1][i])&&(board[0][i]=='x')&&(board[2][i]==' ')){
board[2][i]='x'; flag =1;break;}if((board[0][i]== board[2][i])&&(board[0][i]=='x')&&(board[1][i]==' ')){
board[1][i]='x'; flag =1;break;}if((board[1][i]== board[2][i])&&(board[1][i]=='x')&&(board[0][i]==' ')){
board[0][i]='x'; flag =1;break;}}}while(flag ==2){//判断右对角线有没有两个xif((board[0][0]== board[1][1])&&(board[0][0]=='x')&&(board[2][2]==' ')){
board[2][2]='x';
flag =1;break;}if((board[0][0]== board[2][2])&&(board[0][0]=='x')&&(board[1][1]==' ')){
board[1][1]='x';
flag =1;break;}if((board[2][2]== board[1][1])&&(board[1][1]=='x')&&(board[0][0]==' ')){
board[0][0]='x';
flag =1;break;}//判断左对角线有没有两个xif((board[2][0]== board[1][1])&&(board[1][1]=='x')&&(board[0][2]==' ')){
board[0][2]='x';
flag =1;break;}if((board[2][0]== board[0][2])&&(board[2][0]=='x')&&(board[1][1]==' ')){
board[1][1]='x';
flag =1;break;}if((board[1][1]== board[0][2])&&(board[1][1]=='x')&&(board[2][0]==' ')){
board[2][0]='x';
flag =1;break;}break;}if(flag ==2){//判断行有没有两个ofor(i =0; i < row; i++){//再判断if((board[i][0]== board[i][1])&&(board[i][1]=='o')&&(board[i][2]==' ')){
board[i][2]='x'; flag =1;break;}if((board[i][0]== board[i][2])&&(board[i][2]=='o')&&(board[i][1]==' ')){
board[i][1]='x'; flag =1;break;}if((board[i][1]== board[i][2])&&(board[i][1]=='o')&&(board[i][0]==' ')){
board[i][0]='x'; flag =1;break;}}//判断列有没有两个ofor(i =0; i < col; i++){//再判断if((board[0][i]== board[1][i])&&(board[0][i]=='o')&&(board[2][i]==' ')){
board[2][i]='x'; flag =1;break;}if((board[0][i]== board[2][i])&&(board[0][i]=='o')&&(board[1][i]==' ')){
board[1][i]='x'; flag =1;break;}if((board[1][i]== board[2][i])&&(board[1][i]=='o')&&(board[0][i]==' ')){
board[0][i]='x'; flag =1;break;}}}while(flag ==2){//判断右对角线有没有两个oif((board[0][0]== board[1][1])&&(board[0][0]=='o')&&(board[2][2]==' ')){
board[2][2]='x';
flag =1;break;}if((board[0][0]== board[2][2])&&(board[0][0]=='o')&&(board[1][1]==' ')){
board[1][1]='x';
flag =1;break;}if((board[2][2]== board[1][1])&&(board[1][1]=='o')&&(board[0][0]==' ')){
board[0][0]='x';
flag =1;break;}//判断左对角线有没有两个oif((board[2][0]== board[1][1])&&(board[1][1]=='o')&&(board[0][2]==' ')){
board[0][2]='x';
flag =1;break;}if((board[2][0]== board[0][2])&&(board[2][0]=='o')&&(board[1][1]==' ')){
board[1][1]='x';
flag =1;break;}if((board[1][1]== board[0][2])&&(board[1][1]=='o')&&(board[2][0]==' ')){
board[2][0]='x';
flag =1;break;}}//如果以上都不满足,则随机放置xwhile(flag ==2){int x =rand()% row;int y =rand()% col;if(board[x][y]==' '){
board[x][y]='x';break;}}}}
总结
由于作者水平有限,如笔下有误,敬请留言。
如果本文对您有所帮助,请给博主点赞👍关注🙏哦,笔者会持续更新干货教程,期待与君共勉!
版权归原作者 小牛要翻身 所有, 如有侵权,请联系我们删除。