#井字棋程序的可实现性以及实现思路#
目录
1、程序的可实现性
每个人在学生阶段肯定都有和同桌玩过井字棋,或者说,三子棋。井字棋较易上手,这个游戏的规则简单、操作单一,只要一方完成三点一线即可获胜。那么灵感来了,我是不是也可以通过我前段时间学习的C语言知识来帮助我实现这个小程序?
2、实现程序的思路
一款游戏的结构:
- 菜单界面
- 游戏主体
菜单界面
可以想象,井字棋这种小游戏的菜单界面所展示的,无非就是选择决定与否的两个选项。
选项代表着什么?
————可显示的、可操作选择的
显示效果由 printf() 函数实现
void menu()
{
printf("+-------------------------+\n");
printf("+---------1.play----------+\n");
printf("+---------2.exit----------+\n");
printf("+-------------------------+\n");
}
选择这个操作,若是想用鼠标点击选项实现,现阶段的我是没有头绪,故不考虑。从初学者的角度思考,通过键盘输入,经由程序判断即可实现“选择”。
键盘键入由 scanf() 函数实现
程序判断由 switch 条件语句实现(其中 "game()" 为后续的游戏主体函数)
printf("开始游戏吗?\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 2:
printf("退出游戏\n");
break;
default:
printf("输入有误,重新输入:\n");
break;
}
整个菜单实现的代码如下
void menu()
{
printf("+-------------------------+\n");
printf("+---------1.play----------+\n");
printf("+---------2.exit----------+\n");
printf("+-------------------------+\n");
}
void choose()
{
int input = 0;
do
{
menu();
printf("开始游戏吗?\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game();
break;
case 2:
printf("退出游戏\n");
break;
default:
printf("输入有误,重新输入:\n");
break;
}
} while (1);
}
运行效果
游戏主体
游戏主体主要有4个功能
- 显示棋盘
- 玩家所下棋子
- 电脑随机下棋子
- 判断输赢
棋盘的显示
显示棋盘比较简易,但是这个程序并不是只需求一个棋盘,而是 “承载” 信息的棋盘,这就需要对棋盘进行初始化操作。
空棋盘中“放置” 空格字符 下棋子时“放置” 'X' 'O'
为什么放置两字要加引号呢?因为这里实现的方式并不是将某个字符确确实实地 “放进去”,而是通过二维数组实现——将对应行列的元素 赋值 为特定的字符。
我在这里定义了一个 Init_board 来实现初始化棋盘的工作。
顺便插一嘴,在这次程序的编写中,我添加了三个文件,分别是
declare.h ——头文件,包含功能函数,对各个被调用函数给出一个描述,防止定义的冲突。头文件不需要包含代码,只起 描述性作用。
game.c、test.c——源文件,负责游戏的具体 实现 和 测试。
分几个不同目的的文件可以让工作进行地更有条理,可能井字棋这个任务不是很复杂,但道阻且长,我们还是要养成条理工作的习惯。
回到主题,我定义了 Init_board 来初始化我的数组,简单的 for 循环就可以实现:
void Init_board(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
然后就是打印数组,直接打印出一个封闭的井字格是不现实的,可以用 '+' '-' 的组合来形成井字
不难看出,打印出棋盘是有规律可循的,三组一模一样的可以用 for 循环打印,单独一行的打印也比较方便操作
void Print_board(char board[ROW][COL])
{
int i, j;
printf("+---+---+---+\n");
for (i = 0; i < ROW; i++)
{
printf("| %c | %c | %c |\n",board[i][0], board[i][1], board[i][2]);
printf("+---+---+---+\n");
}
}
棋盘打印效果如下
玩家下棋子
为了方便理解,默认玩家先手。
第一棋没什么问题,玩家从第二棋开始就有了一种可能性———指定的坐标和电脑所下的重合,那么这种情况下我们应该重新回到输入的环节,存在这种操作吗?
在以往的学习中,我们接触过 continue 语句,其作用是跳过本次循环体中尚未执行的语句,开始下一次循环条件的判定,通俗理解:本次循环结束。
但有一点需要注意,continue 语句并没有使整个循环终止。
说明一下 continue 和 break 的区别:
continue 跳出本次循环,还会进行下次本层循环的条件判定,break 跳出本层循环,不会再进行本层循环的条件判定。
除了会下到已经下过的棋子上,玩家输入坐标时也可能输入超出数组范围的坐标,这个问题通过 if 语句也可以轻松解决。
下面展示具体代码实现
void Player(char board[ROW][COL])
{
while(1)
{
int i, j;
printf("请输入下子坐标:\n");
scanf("%d %d", &i, &j);
if (i < 1 || i > ROW || j < 1 || j > COL)
{
printf("输入错误,请重新输入坐标:\n");
continue;
}
if (board[i-1][j-1] != ' ')
{
printf("该坐标已下过子,请重新输入坐标\n");
continue;
}
board[i-1][j-1] = 'X';
break;
}
}
效果展示
电脑随机下棋子
技术有限,无法实现人工智能下棋子。退而求其次,我们只要求电脑可以自主下棋子,哪怕是随机生成的也行。
说到随机生成,思考一下计算机中有什么是一直在变化的。没错,是时间。
我们可以利用 时间戳(可以通过自行搜索深入了解)来生成随机的数字,从而实现生成坐标的目的。
为了调用函数,我们需要调用对应的头文件——
<stdlib.h>
<time.h>
为了调用 rand() 函数,我们还需要进行初始化:
srand((unsigned int)time(NULL));
随机坐标的生成:
int i = rand() % ROW;
int j = rand() % COL;
也许你会奇怪,随机数难道不会越界吗?为什么要对 ROW、COL 取余呢?
越界是肯定的,那么我们就要想办法将越界的数字转变成 0、1、2。动动聪明的脑袋,这些数字是不是对3 取余 后都变成 0、1、2 了?没错,恭喜你数学达到了小学二年级水平👍👍
( ROW 和 COL 都是我在 declare.h 里定义为 3 的常量)
那么到这里电脑随机生成坐标的代码就已经实现了,具体代码如下:
void Computer(char board[ROW][COL])
{
while (1)
{
int i = rand() % ROW;
int j = rand() % COL;
if (board[i][j] != ' ')
{
continue;
}
board[i][j] = 'O';
break;
}
}
实现效果见 “玩家下棋子” 部分的图。
判断输赢
下过井字棋的都知道,先达到三点一线的一方获胜,如果棋盘满了但是不符合一方三点一线的条件,则属于平局,也称和棋。
从相对简单的功能开始实现——判断棋盘是否满。这操作和初始化棋盘的逻辑几乎一致,故直接展示实现代码:
int FULL(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
我将这里的 FULL 函数定义为了 int 类型,这是因为在接下来判断输赢的函数中,需要用到 FULL 函数的返回值进行条件判定。
也许你会觉得判断三点一线很简单:“啊,不就是判断三点一线吗,小孩都会做。”但我给你看看实现的代码:
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][0] != ' ' && board[i][0] == board[i][1] && board[i][0] == board[i][2])
return board[i][0];
}
}
for (j = 0; j < COL; j++)
{
for (i = 0; i < COL; i++)
{
if (board[0][j] != ' ' && board[0][j] == board[1][j] && board[0][j] == board[2][j])
return board[0][j];
}
}
if (board[0][0] != ' ' && board[0][0] == board[1][1] && board[0][0] == board[2][2])
{
return board[0][0];
}
if (board[2][0] != ' ' && board[2][0] == board[1][1] && board[2][0] == board[0][2])
{
return board[2][0];
}
是不是有点长?是不是很恐惧?
但是我告诉你哦,如果逻辑清晰,写一次条件判断,剩下的 CTRL C + CTRL V 然后随便改改就行了,你信吗?
你可能还有疑惑:“啊,为什么要 return 这种奇奇怪怪的东西啊,我都不知道是什么就随便 return 啊,还有王法吗,还有法律吗?”
这就是我们接下来判定一方获胜的判定条件,我告诉你, return 的不是什么奇奇怪怪的东西,是形成三点一线的元素,是获胜一方的棋子。
很清晰了吧,接下来展示 Judge 判断输赢函数的具体实现:
char Judge(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][0] != ' ' && board[i][0] == board[i][1] && board[i][0] == board[i][2])
return board[i][0];
}
}
for (j = 0; j < COL; j++)
{
for (i = 0; i < COL; i++)
{
if (board[0][j] != ' ' && board[0][j] == board[1][j] && board[0][j] == board[2][j])
return board[0][j];
}
}
if (board[0][0] != ' ' && board[0][0] == board[1][1] && board[0][0] == board[2][2])
{
return board[0][0];
}
if (board[2][0] != ' ' && board[2][0] == board[1][1] && board[2][0] == board[0][2])
{
return board[2][0];
}
if (FULL(board))
{
return 'e';
}
return ' ';
}
为什么是 char 类型的?请注意看看 return 值的类型哦。
至此游戏主体分析完毕。
合体!!!
可能你会觉得上边讲的支离破碎,但是想想艾尔登法环里的大升降机,那不也是需要你奔波打怪拿到的符文碎片结合起来才能供你使用的吗,通过自己的能力实现的才是对自己最有收获的!可能你的水平和我一样有限,但是只要有进步的心和付诸的行动,相信我,你也可以变成光!
下边是所有代码展示(不包括头文件,头文件的截图上文有)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include"declare.h"
#include<stdlib.h>
#include<time.h>
void menu()
{
printf("+-------------------------+\n");
printf("+---------1.play----------+\n");
printf("+---------2.exit----------+\n");
printf("+-------------------------+\n");
}
void choose(char board[ROW][COL])
{
int input = 0;
do
{
menu();
printf("开始游戏吗?\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
game(board);
break;
case 2:
printf("退出游戏\n");
break;
default:
printf("输入有误,重新输入:\n");
break;
}
} while (1);
}
void Init_board(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
board[i][j] = ' ';
}
}
}
void Print_board(char board[ROW][COL])
{
int i, j;
printf("+---+---+---+\n");
for (i = 0; i < ROW; i++)
{
printf("| %c | %c | %c |\n",board[i][0], board[i][1], board[i][2]);
printf("+---+---+---+\n");
}
}
void Player(char board[ROW][COL])
{
while(1)
{
int i, j;
printf("请输入下子坐标:\n");
scanf("%d %d", &i, &j);
if (i < 1 || i > ROW || j < 1 || j > COL)
{
printf("输入错误,请重新输入坐标:\n");
continue;
}
if (board[i-1][j-1] != ' ')
{
printf("该坐标已下过子,请重新输入坐标\n");
continue;
}
board[i-1][j-1] = 'X';
break;
}
}
void Computer(char board[ROW][COL])
{
while (1)
{
int i = rand() % ROW;
int j = rand() % COL;
if (board[i][j] != ' ')
{
continue;
}
board[i][j] = 'O';
break;
}
}
int FULL(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
return 0;
}
}
return 1;
}
char Judge(char board[ROW][COL])
{
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (board[i][0] != ' ' && board[i][0] == board[i][1] && board[i][0] == board[i][2])
return board[i][0];
}
}
for (j = 0; j < COL; j++)
{
for (i = 0; i < COL; i++)
{
if (board[0][j] != ' ' && board[0][j] == board[1][j] && board[0][j] == board[2][j])
return board[0][j];
}
}
if (board[0][0] != ' ' && board[0][0] == board[1][1] && board[0][0] == board[2][2])
{
return board[0][0];
}
if (board[2][0] != ' ' && board[2][0] == board[1][1] && board[2][0] == board[0][2])
{
return board[2][0];
}
if (FULL(board))
{
return 'e';
}
return ' ';
}
void game(char board[ROW][COL])
{
Init_board(board);
char WINNER = ' ';
while (1)
{
Print_board(board);
Player(board);
WINNER = Judge(board);
if (WINNER != ' ')
{
break;
}
Computer(board);
WINNER = Judge(board);
if (WINNER != ' ')
{
break;
}
}
Print_board(board);
if (WINNER == 'X')
{
printf("您获胜了\n");
}
else if (WINNER == 'O')
{
printf("您太弱了\n");
}
else
{
printf("您算不上强的\n");
}
}
int main()
{
char board[ROW][COL] = { 0 };
srand((unsigned int)time(NULL));
while (1)
{
choose(board);
game(board);
}
system("pause");
return 0;
}
一起加油!