游戏主函数
首先,我们设计三子棋时,要设计选择进入游戏,退出游戏的选项,以及输入错误内容时,重新选择的程序。
将游戏标题函数设置为
menu()
将游戏主题函数设置为
game()
若在选择时,输入内容不在程序设定范围内,则要求使用者重新输入。所以程序此处应有一个循环。
因此主函数程序设置如下:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏/n");
input -= 2;
break;
default:
printf("输入错误,请重新选择/n");
break;
}
} while (input);
return 0;
}
当输入1时,进入case 1:运行game()函数;
当输入2时,进入case 2:打印“退出游戏”并跳出switch循环,在dowhile结尾进行检测时,检测结果为0,跳出循环,退出程序;
当输入其他字符时,进入default,打印‘“输入错误,请重新选择”。因为input非零,所以继续进行dowhile循环。
代码实现的初步工作
首先我们创建游戏程序文件game.c,和头文件game.h
为了提高系统的可操作性,我们先将程序中的行与列定义为ROW和COL
所以头文件中预处理指令如下:
#define _CRT_SECURE_NO_WARNINGS 1 //scanf函数防止报错
#include <stdio.h> //printf函数
#include <time.h> //time函数,产生随机值(后面讲)
#define ROW 3 //行
#define COL 3 //列
然后在game.c和主函数所在文件test_tic_tac_toe.c中引用头文件:
#include “game.h”
接着设计主函数中的==menu()==函数。因为打印游戏菜单不需要返回值,所以函数类型void即可:
void menu()
{
printf("************************/n");
printf("******** 1.play ********/n");
printf("******** 2.exit ********/n");
printf("************************/n");
}
游戏实现原理与程序优化
根据主函数的设计,我们继续设计game()函数:
同理不需要返回值,所以函数类型为void:
void game()
{
char board[ROW][COL]; //存储数据,二维数组
//初始化棋盘
//打印棋盘
while (1)
{
//玩家落子
//判断游戏能否继续进行
//电脑落子
//判断游戏能否继续进行(胜、负和平局,若存在三者之一则跳出while循环)
}
//判断输赢,打印胜负结果
}
如上文所示,我们先整理出了一个大致的代码设计方向。
初始化棋盘
首先我们需要将数组(棋盘中9个落子点的位置)初始化为“ ”(空格)
void InitBoard(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] = ' ';
}
}
}
然后再在头文件game.h中声明,并做好注释:
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
打印棋盘
我们的棋盘由“|”(回车上面)和“-”(减号)组成
所以依次为
| |
---|---|--- //三个---和一个|
| |
---|---|---
| |
程序可以直接选择直接设置:
void DisplayBoard(char board) //打印棋盘
{
printf(" %c | %c | %c ", board[0][0],board[0][1],board[0][2]);
printf("---|---|---");
printf(" %c | %c | %c ", board[1][0],board[1][1],board[1][2]);
printf("---|---|---");
printf(" %c | %c | %c ", board[2][0],board[2][1],board[2][2]);
}
但是缺点很明显,如果棋盘大小不是3×3,那么这个程序就得重新编写了。
所以我们稍微修改亿下:
void DisplayBoard(char board[ROW][COL], int row, int col) //打印棋盘
{
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");
int k = 0;
if (i < ROW - 1)
{
for (k = 0; k < COL; k++)
{
printf("---");
if (k < COL - 1) //打印的|次数要少一次
{
printf("|");
}
}
printf("/n");
}
}
}
然后我们同样在头文件game.h中声明此函数,并做好注释:
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
这样我们可以用此函数做到初次打印空棋盘,以及之后每次玩家/电脑落子后的棋盘。
玩家下棋
玩家下棋时,我们可以让玩家输入坐标,以此确定落子位置。
这样具体到程序上,就是对上文初始化的二维数组进行赋值。
但是玩家的人类思维中,坐标是1,2,3这样顺序的,而程序是以0,1,2的次序排序。所以我们要将玩家输入的坐标-1,以此对标数组中的从0开始排序。
此外,我们还要玩家落子点有空位,且存在的问题。所以我们需要一个if的选择语句进行验证,若不满足条件则要求玩家重新输入。因此还要一个while循环
若玩家输入坐标存在,则对二维数组的此项赋值为“*”。
整理思路,我们可以敲出以下代码:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
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("坐标已被占用,请重新输入");
}
}
else
{
printf("坐标错误,请重新输入");
}
}
同样,在头文件game.h中声明此函数,并做好注释:
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
电脑下棋(这里挖个坑)
电脑落子,因为编者目前还不会制作更好的程序,做出一个智能的对战AI,所以电脑落子采用随机的方式。
在头文件game.h中引用
#include <time.h>
再在主函数的dowhile循环前加入
srand((unsigned int)time(NULL));
这样,我们就可以产生随机数了。
但是由于此随机数的范围巨大,所以我们需要将它的范围缩小到0~2,以此对应二维数组中的位置。此处最简单的就是用取模符号了(%)。
(挖坑:以后学到如何做这么一个智能对战的程序后,我再对这里的电脑落子程序进行优化)
其他的整体思路和玩家落子程序的思路类似,但!是!:
因为我们不需要机器报出位置已被占用的信息,所以不需要让它像玩家输入的程序一样打印“请重新输入”,只需要让机器重新选择坐标进行落子(重新选择二维数组的某一项赋值成“#”)
void ComputerMove(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] = '#';
break;
}
}
}
同样,在头文件game.h中声明此函数,并做好注释:
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
验证胜负机制与程序的继续进行
根据三子棋的规则,只要连续的三个子相同就可以判断胜负了。
所以对于程序是否需要,能否继续运行,总共存在以下四种情况:
判断输赢
1.玩家胜利
2.电脑胜利
3.棋盘满了,平局
4.仍有空位,游戏继续
所以此处我们设计一个返回类型为char的函数:
首先对每行,每列,两个对角线进行判断是否达成三点一线同一种棋子,且不为空格。
为了减少程序的运行次数,代码的总体量,我们将玩家/电脑的返回值分别设置为“*”和“#”,与玩家/电脑的落子一致(省去了一半的代码)
所以我们设计如下:
判断输赢
1.玩家胜利,返回*
2.电脑胜利,返回#
3.棋盘满了,平局,返回D
4.仍有空位,游戏继续,返回C
但是因为次函数体量较大,我们再设计一个新的函数来判断棋盘是否满了(是否平局)
方法很简单,只要每个位置都不为空格,则棋盘已满,代码如下:
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < ROW; j++)
{
if (board[i][j] != ' ')
{
return 0;
}
}
}
return 1;
}
然后我们就可以设计出判断程序如何继续执行的函数了:
char IsWin(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][0] != ' ')
{
return board[i][0];
}
}
//判断列
for (i = 0; i < COL; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
//判断对角线
if ((board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] != ' ') || (board[0][0] == board[1][1] && board[0][0] == board[2][2] && board[0][0] != ' '))
{
return board[1][1];
}
//判断平局(棋盘满没满) 平局返回1,还有空位返回0
int ret = IsFull(board, ROW, COL);
if (ret == 1)
{
return 'D'; //返回值为1,平局
}
else
{
return 'C'; //返回值为0,游戏仍在继续
}
}
同样,在头文件game.h中声明函数,并做好注释:
//判断输赢
//1.玩家胜利返回*
//2.电脑胜利返回#
//3.平局返回D
//4.游戏继续返回C
char IsWin(char board[ROW][COL], int row, int col);
//判断平局(棋盘满没满) 平局返回0,还有空位返回1
int IsFull(char board[ROW][COL], int row, int col);
这时候我们回到开始的game()函数:
```c
void game()
{
char board[ROW][COL]; //存储数据,二维数组
//初始化棋盘
//打印棋盘
while (1)
{
//玩家落子
//判断游戏能否继续进行
//电脑落子
//判断游戏能否继续进行(胜、负和平局,若存在三者之一则跳出while循环)
}
//判断输赢,打印胜负结果
}
依次将设计好的函数进行代入,完善整理程序,结果如下:
void game()
{
char board[ROW][COL]; //存储数据,二维数组
InitBoard(board, ROW, COL); //初始化棋盘
DisplayBoard(board, ROW, COL); // 打印棋盘
int ret = 0;
while (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;
}
}
//判断输赢,是否平局
if (ret == '*')
{
printf("玩家胜利/n");
}
if (ret == '#')
{
printf("玩家胜利/n");
}
if (ret == 'D')
{
printf("平局/n");
}
DisplayBoard(board, ROW, COL); // 游戏结束,再次打印棋盘
}
以上就是三子棋的整体思路了
BUG彩蛋(我也不知道为什么会这样)
本来程序是好好的,按照设计进行输入:
所以我自信满满地把它的exe文件发给了校友,让他玩玩看。
结果他输入了一个英文字符,然后敲下回车……
结果就这样了:
没错,无限打印。
这里是啥原理我就不明白了。
有大佬能解答一下咩?
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/145655.html