一.前言
三子棋也就是井字棋,井字棋满打满算也就9个格子,思考起来也不是那么麻烦,但是我们现在既然讲的是低配版三子棋,我们也不会去考虑电脑会不会去堵你,我们只让电脑下的随机一点就算完成了。好了,话不多说,直接开始。
二.文件的准备
我们先把前面的准备工作准备一下。我们先准备两个.c文件,一个是写有main函数的主文件,还有一个是自定义的文件主要目的是把一些写有游戏需要的函数的代码放里面,最后一个是该自定义文件的.h文件。当然这个自定义的文件你也可以不去加,主要我是为了这样使我们的代码更简洁容易懂。
像我这样就可以了。
三.分析
-
刚开始,为了使我们的代码更贴近游戏,我们可以用几个printf简单的打印游戏开始的界面。
简单一点像这样容易懂就行,来给你们先看看打印出来的效果,主要代码我接下来一起讲。 -
把这样子的菜单打印出来后,如果选择1,游戏就要开始了,想像一下,如果你要玩一个三子棋游戏,摁完开始后肯定会出现一个类似九宫格的图样吧,我们就根据每一行每一列的规律写到一个函数里然后打印出来。现在我先把走之前打印出来的九宫格给大家展示一下,稍后我再进行讲解。
-
现在我们开始设置结束的条件。游戏结束无非三种情况:1.玩家胜利 2.电脑胜利 3.平局。随后我们就通过代码计算来判断输赢最后输出结果
3.1主文件中代码的实现
好了我们大概的了解了一下主要步骤,现在我们可以开始写了
- 主函数:首先为了让游戏设置的合理些,就是让玩家再玩完一局之后不用退出去,直接输入数字来判断是下一把还是直接结束,这样我们可以在主函数里写一个do-while循环,循环里在写一个switch语句。这样就可以通过玩家选择来判断接下来的步骤。当然,我们不可能将所有有关的函数的名字都写在主函数里,所以我们把游戏用到的函数放到一个函数里,而函数如何实现则统一放到一个自己定义的.c文件里。
int main()
{
int input = 0;
do
{
menu();
printf("请输入->");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
game();
break;
case 0:
break;
default:
printf("输入错误请重新输入:\n");
break;
}
} while (input);
return 0;
}
这里的system(“cls”);是清屏函数
要引入头文件<stdlib.h>。
我的目的是希望每运行一次都会把之前的显示都清掉,要不然第二次玩的游戏界面上还有第一次玩的痕迹。
- 函数menu()和函数game():我们先看代码。
void menu()
{
printf("**************************\n");
printf("****** 1.play *******\n");
printf("****** 0.exit *******\n");
printf("**************************\n");
}
void game()
{
srand((unsigned int)time(NULL));
char map[ROW][COL];
//初始化地图
Init_Map(map);
//打印地图
Print_Map(map);
while(1)
{
//玩家下
Player(map);
//检测是否胜利
if (Is_Win(map) == '*')
{
printf("玩家胜利\n");
return;
}
else if(Is_Win(map) == '#')
{
printf("电脑胜利\n");
return;
}
else if (Is_Win(map) == 1)
{
printf("平局\n");
return;
}
//电脑下
computer(map);
//检测是否胜利
if (Is_Win(map) == '*')
{
printf("玩家胜利\n");
return;
}
else if (Is_Win(map) == '#')
{
printf("电脑胜利\n");
return;
}
else if (Is_Win(map) == 1)
{
printf("平局\n");
return;
}
}
}
menu() 这个函数比较容易懂就是打印之前提到的菜单,可以放在循环里面,因为每循环一次,就相当于重新开了把游戏,所以每次循环后都要让玩家看到菜单。
game() 这个函数就把我们在玩家选择1之后游戏该进行的步骤写出来。而里面你们不认识的函数就是游戏改如何实现的函数,接下来我会一个一个的去讲。现在你们就知道每个函数有什么功能就好。现在我从上到下给大家捋一捋。
3.2各个函数功能的实现
3.2.1初始化地图
map是我定义的一个二维数组。存放的是玩家,电脑下进去的符号。
初始化函数:
void Init_Map(char map[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
map[i][j] = ' ';
}
}
}
很简单,目的是为了让数组里面的内容全部初始化为空格。
所以直接用两层循环嵌套。
这里我还用到了两个宏定义ROW,COL。我把定义的内容放在了.h文件中。
这样做是以后能好修改,假如有一天3*3的已经满足不了你了,你就可以直接改define后面的数字。
3.2.2打印地图
我再来演示一遍打印出来的样子:
总共打印出来的样子是3*3总共9个格子,因为我现在还没接触过图形库那类的东西,想打印一个格子就用了些比较笨的方法:
我们在第一行打印—|—|—|
然后在第二行打印 * | * | * |
就这样一直重复往下走,这里面的*号就是我们之前定义的数组里面的内容了。
我们现在来看代码:
void Print_Map(char display_map[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
//这个for循环时打印第一行
{
printf("---|");
}
printf("\n");
//打印完第一行之后换行
for (j = 0; j < COL; j++)
//这个for循环是打印第二行
{
printf(" %c |", display_map[i][j]);
}
printf("\n");
}
}
我们在这里是每两行打印一次。
我们把这两行归为一组,换个角度想,我们需要两行一起才能打印出一个看上去合理的大方框。
3.2.3玩家下棋
void Player(char display_map[ROW][COL])
{
int x = 0;
int y = 0;
while(1)
{
printf("请玩家下棋,请输入坐标->\n");
scanf("%d,%d", &x, &y);
if (x > 0 && x <= ROW && y > 0 && y <= COL)
{
if (display_map[x - 1][y - 1] == ' ')
{
display_map[x - 1][y - 1] = '*';
Print_Map(display_map);
return;
}
else
printf("输入重复\n");
}
else
printf("输入不合法\n");
}
}
我们先分析玩家输入的几种情况:
1.输入正确
2.输入值不合法,就是超出了你所规定的范围
3.输入重复
所以在这里用两个if语句嵌套并且放在while(1)死循环里。你可以随便输,反正只有输入正确了你才能返回,也就是跳出函数。这里我们的函数是无返回值的,为了能让你跳出死循环直接写return就行。
为了考虑玩家的想法,如果有的玩家他没学过C语言,不知道数组首元素要从0开始,所以我们为了让玩家输入的内容和数组坐标所契合,所以用x-1和y-1。这里玩家下的位置用 * 代替。
如果你想在画圈的那个地方下棋,你输入坐标1,1就行。但其实那块地方对应的数组坐标是0,0。这样x-1,y-1就很好的解决了这个问题。
我们最好在下完之后在打印一下,这样你就能看到你下的地方在哪了。
3.2.4电脑下棋
void computer(char display_map[ROW][COL])
{
printf("电脑下棋\n");
while(1)
{
int x = rand() % ROW;//0-2
int y = rand() % COL;//0-2
if (display_map[x][y] == ' ')
{
display_map[x][y] = '#';
system("cls");
Print_Map(display_map);
return;
}
}
}
这里我们让电脑随机下棋,如果想让他变得比较智能一点,可以写个算法,当然这块我还没想好。
如果仔细看之前写的主函数,你们可能会看到这样一行代码:
srand((unsigned int)time(NULL));
这个srand函数是为了和rand()函数一起使用的。
原理就是:
srand()括号里面的内容里可以随便写一个值,可以把它当成种子,如果没有就默认为1,rand()每次用的时候都会检查是否调用过srand()函数,如果有就会产生一个随机值。但是这个种子如果不变的话,rand函数产生的随机值就固定了,换句话说,他就第一次随机,以后的值和第一次的一样。
这样的话我们就必须让srand函数里面的种子也是个随机值/不同值。这样就麻烦了,这不是套娃吗?所以为了满足我们的需求,我们通过time()函数来当作种子。
time():获取当前日历时间作为time_t类型的值.
我们直接把时间当成种子带进去,时间可不是固定的每分每秒都在运行。
因为time函数的返回值是time_t类型的,而srand需要的类型是unsigned int类型的,所以我门在使用时要强制类型转换。
time函数的参数填NULL空指针就行。
但是rand的随机值实在是太随机了,这里为了让这些随机值能被我们掌握在一定的范围内。所以这里用到了%。你们可以自己随便试,不管什么数M%N,得到的值得范围不可能超过N,范围只能是(0 ~ N-1)。rand%ROW,rand%COL的取值范围就是(0~ROW-1)(0 ~ COL-1).这些是数组的取值范围。
电脑下棋的位置判断就比较好判断了,只要是空格就可以下。下完打印出来然后在返回。
3.2.5检测是否胜利
玩家和电脑每下一次都要判断一下游戏当前的进展。如果此时
玩家赢了就返回‘*’
电脑赢了就返回‘#’
如果平局就返回1
其它情况就是返回0,表示游戏继续
//检查是否平局
static int is_fall(char display_map[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (display_map[i][j] == ' ')
return 0;
}
}
return 1;
}
int Is_Win(char display_map[ROW][COL])
{
int i = 0;
for (i = 0; i < ROW; i++)
{
if (
(display_map[i][0] == display_map[i][1]) &&
(display_map[i][1] == display_map[i][2]) &&
(display_map[i][0] != ' ')
)
{
return display_map[i][0];
}
}
for (i = 0; i < COL; i++)
{
if (
(display_map[0][i] == display_map[1][i]) &&
(display_map[1][i] == display_map[2][i]) &&
(display_map[0][i] != ' ')
)
{
return display_map[0][i];
}
}
if (
(display_map[0][0] == display_map[1][1]) &&
(display_map[1][1] == display_map[2][2]) &&
(display_map[0][0] != ' ')
)
{
return display_map[0][0];
}
else if (
(display_map[0][2] == display_map[1][1]) &&
(display_map[1][1] == display_map[2][0]) &&
(display_map[0][2] != ' ')
)
{
return display_map[0][2];
}
else if (is_fall(display_map) == 1)
return 1;
else
return 0;
}
我们先判断有没有哪方胜利:
在这里暴力求解:
先用一个for循环,一行一行的找,如果有一行相等都是’ * ‘或者’ # '说明玩家或者电脑胜利了,返回这一行的其中一个坐标的值就行(反正这三个值都一样)。
再用一个for循环,一列一列的找
再用两个if语句找两个对角线
如果上面这些代码都找完了,还没有返回值,说明可能是平局或者游戏还没结束。然后我们在判断是否平局。这里我们再写一个函数:static int is_fall(char display_map[ROW][COL])
判断方法也很容易,我们扫描整个数组如果还有空格,就说明还有地方可以落子,就返回0。如果没有空格了,说明是平局就返回1.
再到我们之前的函数来,如果is_fall返回了1,这个函数最后也返回1,说明平局。最后如果既没有赢,也没有平局,就是说明游戏还没结束在返回0.
四.代码
4.1threechess.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("**************************\n");
printf("****** 1.play *******\n");
printf("****** 0.exit *******\n");
printf("**************************\n");
}
void game()
{
srand((unsigned int)time(NULL));
char map[ROW][COL];
//初始化地图
Init_Map(map);
//打印地图
Print_Map(map);
while(1)
{
//玩家下
Player(map);
//检测是否胜利
if (Is_Win(map) == '*')
{
printf("玩家胜利\n");
return;
}
else if(Is_Win(map) == '#')
{
printf("电脑胜利\n");
return;
}
else if (Is_Win(map) == 1)
{
printf("平局\n");
return;
}
//电脑下
computer(map);
//检测是否胜利
if (Is_Win(map) == '*')
{
printf("玩家胜利\n");
return;
}
else if (Is_Win(map) == '#')
{
printf("电脑胜利\n");
return;
}
else if (Is_Win(map) == 1)
{
printf("平局\n");
return;
}
}
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入->");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
game();
break;
case 0:
break;
default:
printf("输入错误请重新输入:\n");
break;
}
} while (input);
return 0;
}
4.2game.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化地图
void Init_Map(char map[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
map[i][j] = ' ';
}
}
}
//打印地图
void Print_Map(char display_map[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
printf("---|");
}
printf("\n");
for (j = 0; j < COL; j++)
{
printf(" %c |", display_map[i][j]);
}
printf("\n");
}
}
//玩家下
void Player(char display_map[ROW][COL])
{
int x = 0;
int y = 0;
while(1)
{
printf("请玩家下棋,请输入坐标->\n");
scanf("%d,%d", &x, &y);
if (x > 0 && x <= ROW && y > 0 && y <= COL)
{
if (display_map[x - 1][y - 1] == ' ')
{
display_map[x - 1][y - 1] = '*';
Print_Map(display_map);
return;
}
else
printf("输入重复\n");
}
else
printf("输入不合法\n");
}
}
//电脑下
void computer(char display_map[ROW][COL])
{
printf("电脑下棋\n");
while(1)
{
int x = rand() % ROW;//0-2
int y = rand() % COL;//0-2
if (display_map[x][y] == ' ')
{
display_map[x][y] = '#';
system("cls");
Print_Map(display_map);
return;
}
}
}
//检查是否平局
static int is_fall(char display_map[ROW][COL])
{
int i = 0;
int j = 0;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (display_map[i][j] == ' ')
return 0;
}
}
return 1;
}
//检测是否胜利
int Is_Win(char display_map[ROW][COL])
{
int i = 0;
for (i = 0; i < ROW; i++)
{
if (
(display_map[i][0] == display_map[i][1]) &&
(display_map[i][1] == display_map[i][2]) &&
(display_map[i][0] != ' ')
)
{
return display_map[i][0];
}
}
for (i = 0; i < COL; i++)
{
if (
(display_map[0][i] == display_map[1][i]) &&
(display_map[1][i] == display_map[2][i]) &&
(display_map[0][i] != ' ')
)
{
return display_map[0][i];
}
}
if (
(display_map[0][0] == display_map[1][1]) &&
(display_map[1][1] == display_map[2][2]) &&
(display_map[0][0] != ' ')
)
{
return display_map[0][0];
}
else if (
(display_map[0][2] == display_map[1][1]) &&
(display_map[1][1] == display_map[2][0]) &&
(display_map[0][2] != ' ')
)
{
return display_map[0][2];
}
else if (is_fall(display_map) == 1)
return 1;
else
return 0;
}
4.3game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#define ROW 3
#define COL 3
#define ROWS 2 * ROW - 1
#define COLS 4 * COL + 1
//初始化地图
void Init_Map(char map[ROW][COL]);
//打印地图
void Print_Map(char display_map[ROW][COL]);
//玩家下
void Player(char display_map[ROW][COL]);
//电脑下
void computer(char display_map[ROW][COL]);
//检测是否胜利
int Is_Win(char display_map[ROW][COL]);