目录
一.前言:
今天我们来讲解我们学习C语言后所编写的第一个小游戏-------三子棋游戏。在这一篇博客中,我们很有可能会涉及一些我们没听过的库函数,头文件,也会了解到C语言一些不同的用法。同时这一篇博客对于新入门的小萌新来说会有点复杂和超前,没关系,我会尽自己最大的努力来为大家讲解。
二.三子棋的实现:
我们在前面有说过三子棋对于我们萌新来说太过于超前,所以我们写代码需要一步一步来,切记不能一步到位。
1.创建文件
因为三子棋的代码长度长,如果我们将全部三子棋的代码写到一个文件中就显得很杂乱无章,到时候查找好修改错误的时候就会十分的麻烦。因此,我们将三子棋的组成分为三个文件来书写。
game.h用来存放我们代码要用到的所有头文件,test.c用于书写目录等基础面板,而game.c则用来输入我们编写的游戏程序的内容。
这样做最根本的目的便是在哪个区域写入对应编程代码,最后game.c和game.h一起传输到test.c中实现代码的组装。
2.菜单
每个游戏都有自己的菜单,三子棋也不例外,游戏的菜单是游戏的重中之重,因此我们先打印菜单。
这就是我们三子棋的一个基础菜单,菜单没有要的返回值,所以我们创建菜单的实现用的是void mulu()的函数。
3.初始化棋盘
在打印棋盘之前我们应该优先初始化我们的棋盘,这样才能在打印棋盘的时候不会出错。
上图便是我们棋盘的模型,在棋盘当中有9个格子可以供我们下棋,但是这些格子并不是凭空出现的,我们需要将他们编写出来,而格子里的输入的正是我们的空格。而初始化棋盘正是要将这些空格给打印出来。
便可以将我们棋盘的格子初始化为空格。这是我们的游戏内容,所以我们将它剪辑在game.c文件里面。因为函数最后要在test.c中组装,所以我们应该在test.c中对其进行声明。
有人可能要问数组中的L和K是从哪里来的?不要着急,我们先继续往下说。
4.打印棋盘
在初始化棋盘之后,我们便应该将初始化后的棋盘打印出来。
我们可以看见在打印棋盘的时候又出现了L和K。erwei表示我们要打印二维数组用于储存我们后来输入玩家和电脑下棋的位置。这其中的L和K便是分别代表了行和列。
那么我们便在头文件中定义两个宏,让我们后面调用到行和列的时候输入K和L可以直接调用。
因为是三子棋,棋盘大小为3x3,所以我们也将函数的行和列用宏将其定位为3。
这个时候就有人要说了,既然我知道是打印3x3的表格,那么为什么不直接在打印棋盘和初始化棋盘处将L和K都换为3呢。
那么这样我只能说这个人的格局不够打开。
将L和K替换为3,在三子棋中也是正确的输入方法。但是如果我们要在这个编程的基础上将原本3x3的棋盘替换为5x5的棋盘时,那么原本是3的位置都应该被我们改为5,这是一个巨大的工程,稍有不慎就可能导致程序报错。
如果我们采用宏的方式,我们只要将原先的3改为5,那么整个程序中的L和K都将变成5,这样我们便可以轻而易举的打印出5x5的棋盘。
接下来我们在game.c中编写出我们的棋盘框架。
这样子便能成功的编写出我们棋盘的框架。这是一种实现方法,但是这种方法和上面将K和L都改为3是同一个道理,就是过于死板,如果我要将3x3的棋盘修改为5x5棋盘时候,在这里就要再增加两个%c和‘|’,同理,下面打印列的也要多输入2次---|。
那么如何将其简易化呢?
在这里我们就应该运用循环语句进行打印。有人发现在for里,我又新加入了一个判断条件,那么这个判断条件有什么用呢?我们去掉这个if语句,然后运行一下代码来试试看。
这里我们发现了,棋盘的打印变样了。
在最后面多出来了一排‘|’,而我们可以看出函数以空格加‘|’为一组,所以我们最后结束的时候也是以‘|’结束的,这就导致了我们多打印了一排'|'。因此我们需要在这之前加入一个判断条件,使它不打印出最后一组‘|’,使它打印3次%c,同时打印2次‘|’。同理,下文的列打印也是这个样子。
最后我们打印出来的图标便是这个样式。
这便是我们打印表格的方式。
5.玩家与电脑下棋
在打印完棋盘后我们就要编写玩家和电脑的下棋位置。
首先我们先编译玩家下棋的编码。
先在头文件处编写。
我们在test.c中输入编程,第一条编码用来表达玩家下棋的位置,第二条编码则用来打印玩家下棋的位置。 然后再game.c中编译我们的游戏内容。如下:
这个时候有人就会发现在这个函数里面多了一个if判断语句。
有人不禁要问,为什么在这里数组里面输入的值是x-1和y-1呢?这样的话我输入(2,2),这个符号在棋盘中不就显示在(1,1)位置了吗?这是因为在程序员和玩家眼中棋盘的位置有所不同。
同一个位置,在程序员眼中的坐标是(2,2),但是在玩家的眼中则是(3,3)。因此我们才要多一个if语句进行判断。
输入玩家下棋后我们就来输入电脑下棋的位置。同理在test.c中先进行声明然后编写头文件。
接下来我们在game.c中编译我们的内容,但是此时电脑下棋的代码就与我们玩家下棋的代码有所不同。
因为我们的电脑是随机下棋,所以我们在其中引入了rand的库函数,而只用该函数的前提则需要在test.c中编译srand库函数。
这样程序才可以正常执行。
6.判断输赢
在玩家和电脑下棋的时候,总会在某个时刻或者棋盘被填满的时候停止程序。因此我们要写一个函数来判断输赢。
然后再在game.c和game.h中再分别书写游戏的头文件和内容运行。
除了判断输赢以外我们也要判断平局的结果。所以在game.c中我们应该再加一条函数用于判断我们的平局的局面。
在这其中我们要注意的是ret!=‘C’。
如果不加入该条件的话,函数会一直运行:
而我们正常的应该是这个样子:
其余部分判断等只是稍微费时间而且,并不是不能做到,所以我们应该写得出来。
完整的各文件的代码:
这就是我们的三子棋游戏,现在我将完整的代码给放在底下。
test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void mulu()
{
printf("************************\n");
printf("****** 1.开始游戏 ******\n");
printf("****** 0.结束游戏 ******\n");
printf("************************\n");
}
void game()
{
char erwei[L][K];
char ret = 0;
//初始化棋盘
chushi_erwei(erwei, L, K);//chushi_erwei(erwei, 3, 3);
//打印棋盘
print_erwei(erwei, L, K);//print_erwei(erwei, 3, 3);
//玩家下棋
while (1)
{
player(erwei, L, K);
print_erwei(erwei, L, K);
ret = win(erwei, L, K);
if (ret != 'C')
{
break;
}
competer(erwei, L, K);
print_erwei(erwei, L, K);
ret = win(erwei, L, K);
if (ret != 'C')
{
break;
}
}
if (ret == '#')
{
printf("电脑赢.\n");
}
else if (ret == '*')
{
printf("玩家赢.\n");
}
else if (ret == 'Q')
{
printf("平局.\n");
}
/* 判断输赢
电脑赢:#
玩家赢:*
平局:Q;
游戏继续:C*/
}
void test()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
mulu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏.\n");
break;
default:
printf("输入错误,请重新输入.\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
game.c:
#include "game.h"
void chushi_erwei(char erwei[K][L],int l,int k)
{
int i = 0;
for (i = 0; i < l; i++)
{
int j = 0;
for (j = 0; j < k; j++)
{
erwei[i][j] = ' ';
}
}
}
//void print_erwei(char erwei[L][K], int l, int k)
//{
// int i = 0;
// for (i = 0; i < l; i++)
// {
// printf(" %c | %c | %c \n ", erwei[i][0], erwei[i][1], erwei[i][2]);
// if(i<l-1)
// printf("---|---|---\n");
// }
//}
//
void print_erwei(char erwei[L][K], int l, int k)
{
int i = 0;
for (i = 0; i < l; i++)
{
int j = 0;
for (j = 0; j < k; j++)
{
printf(" %c ", erwei[i][j]);
if(j<k-1)
printf("|");
}
printf("\n");
if(i<l-1)
for (j = 0; j < k; j++)
{
printf("---");
if(j<k-1)
printf("|");
}
printf("\n");
}
}
void player(char erwei[L][K], int l, int k)
{
printf("玩家下棋.\n");
while (1)
{
printf("输入玩家的位置");
int x = 0;
int y = 0;
scanf_s("%d %d", &x, &y);
if (x >= 1 && x <= l && y >= 1 && y <= k)
{
if (erwei[x - 1][y - 1] == ' ')
{
erwei[x - 1][y - 1] = '*';
break;
}
else
{
printf("重复输入,输入无效,请重新输入。\n");
}
}
else
printf("输入非法\n");
}
}
void competer(char erwei[L][K], int l, int k)
{
printf("电脑下棋.\n");
while (1)
{
int x = rand() % l;
int y = rand() % k;
if (erwei[x][y] == ' ')
{
erwei[x][y] = '#';
break;
}
}
}
int is_full(char erwei[L][K], int l, int k)
{
int i = 0;
int j = 0;
for (i = 0; i < l; i++)
{
for (j = 0; j < k; j++)
{
if (erwei[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char win(char erwei[L][K], int l, int k)
{
int i = 0;
for (i = 0; i < l; i++)
{
if (erwei[i][0] == erwei[i][1] && erwei[i][1] == erwei[i][2] && erwei[i][1] != ' ')
{
return erwei[i][0];
}
}
for (i = 0; i < k; i++)
{
if (erwei[0][i] == erwei[1][i] && erwei[1][i] == erwei[2][i] && erwei[0][i] != ' ')
{
return erwei[0][i];
}
}
if (erwei[0][0] == erwei[1][1] && erwei[1][1] == erwei[2][2] && erwei[0][0] != ' ')
{
return erwei[0][0];
}
if (erwei[0][2] == erwei[1][1] && erwei[1][1] == erwei[2][0] && erwei[0][2] != ' ')
{
return erwei[0][2];
}
if (is_full(erwei, l, k) == 1)
{
return 'Q';
}
return 'C';
}
game.h:
#define L 3
#define K 3
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//初始化棋盘
void chushi_erwei(char erwei[L][K],int l ,int k);
//打印棋盘
void print_erwei(char erwei[L][K],int l,int k );
//玩家下棋
void player(char erwei[L][K], int l, int k);
//电脑下棋
void competer(char erwei[L][K], int l, int k);
//判断输赢
char win(char erwei[L][K], int l, int k);
结尾:
这是我第一次写如此大规模的程序,同时也是我到现在写过的最长的一篇博客,所以博客里面的逻辑可能会有点混乱,但是我还是希望这篇博客可以给写三子棋程序的同学们带来一丝灵感。而我会进一步的优化我的逻辑能力,让接下来的每一篇博客都可以更加让人感到浅显易懂。