我们在完成三子棋的之前我们先看一下我们在写三子棋的时候会遇到哪些问题。首先我们肯定是得有一个玩家的选着界面,电脑根据这个玩家的选着情况来做出对应的步骤,比如继续游戏啊,退出游戏等等,好等选着结束后我们就要开始游玩游戏了,那么既然是三子棋,我们首先是不是得打印出棋盘长什么样,打印出棋盘之后我们是不是得让玩家下棋,那么玩家又是如何来下棋的又是一个问题,玩家下棋后就得电脑下棋,那么我们电脑是如何下棋的,是不是又是一个问题,那么在双方下棋的过程中是不是还得判断输赢,这又是一个一个问题,那么我们这里总结一下我们实现一个初步的三子棋会遇到哪些问题:
我们将一个大的问题细分成一个个小的问题,然后我们再来对这些小的问题进行一一的解答,这样我们完成这个小游戏的困难就会小很多,那么接下来我们就来看看我们就来解决第一个问题
在开始之前我们先得创建三个文件第一个就是头文件game.h用来存放一些函数的声明的,第二个就是game.c用来存放函数具体的实现,test.c里面就用来存放具体大致的结构。
首先我们这是一个游戏,那么既然是游戏的话就自然会有一个选择的界面,那么这里我们就先创建一个函数,这个函数的功能就是打印菜单来告诉玩家请做出选择,既然要做出选择,那么这里我们就创建一个变量input来记录玩家选择的结果,然后我们根据这个选择的结果来做出不同的判断,因为玩家在玩完这个游戏之后还得做出选着,是选择继续玩游戏还是选着退出游戏所以我们这里就得把这整个游戏的过程放到循环里面去,我们之前讲过这种情况放到do…while 循环里面是最好的选着,那么讲到这里我们第一步的具体思路就实现了,那么接下来我们就来一步一步的讲解:在test.c文件里面我们首先创建一个变量input,然后我们就进入了do…while循环:
#include
int main()
{
int input = 0;
do
{
}while ();
return 0;
}
在这个do…while 循环里面我们首先得将这个游戏的菜单打印出来,然后玩家就会根据菜单的内容来做出对应的选着,我们这里就用scanf函数来接收玩家的选择,再把玩家选着的情况放到input这个变量里面去,当然我们这里选着也是有要求得,因为选着退出游戏的话我们的do…while循环是得跳出循环的,所以我们就将结束循环对应的值设为0,再把input放到这个while后面的括号里面,因为一旦玩家选着结束游戏的话,就会将input的值赋值为0,这样while里面的值就会,所以判断的结果就会为假,为假的话就会直接的跳出循环,我们的游戏就会就会结束,然后我们就将开始游戏的值对应为1,那么这里我们就得用到switch这个选着语句来实行分支的结构,这样的话如果有玩家输入其他的值这样我们也可以用default语句来提醒玩家输入错误请重新输入:那么看到了这里我们第一阶段的问题就完成了我们可以看一下我们的代码应该就长这样:
//test.c文件
#include
void menu()
{
printf("************************\n");
printf("********* 1.play *******\n");
printf("********* 2.exit *******\n");
printf("************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("开始游戏\n");
break;
case 0 :
printf("游戏结束\n");
break;
default:
printf("选着错误\n");
break;
}
}while (input);
return 0;
}
首先我们来看看我们的棋盘长什么样
我们可以看到这是一个非常简易的棋盘,我们玩家和电脑下棋就是在这个方格子里面下棋,虽然我们这里看到的方格是空白的,但是我们要知道的一点就是其实这个方格的背后是数组并且还是一个二维的数组用来存储我们下棋的坐标,那么看到这里我们首先创建一个game函数用来实现我们游戏的运行,在这个game函数里面我们首先得创建一个二维的数组,并且将这个数组里面的每一个元素都初始化为空格,因为这样的话我们就能保证,我们棋盘打印的时候能够做到看起来是空格的感觉,那么这里我们就得创建一个能够初始化数组的函数,那么创建这个函数的时候我们就得进行传参,我们把二维数组和二维数组的行和列给传过去,那么这里我们就称这个函数的名字为InitBoard,既然你传了数据过来我是不是就得在函数的声明里面来进行接收,这里我们就用char board[3][3]
来接收这个数组,用int row
来接收行,用int col
来接收这个列:那么接下来我们就实现函数的初始化,这里就很简单了我们直接使用两个循环的嵌套就可以了我们的初始化数组的函数代码如下:
//game.c文件
void InitBoard(char board[3][3], int col, int row)
{
int i = 0;
int j = 0;
for (i = 0; i < col; i++)
{
for (j = 0; j < row; j++)
{
board[i][j] = ' ';
}
}
}
//test.c文件
void game()
{
char board[3][3] = { 0 };
InitBoard(board, 3, 3);
}
大家看到这里其实应该会发现一个小小的问题就是我们这里函数传的是3和3,并且这个3在后面的问题会出现很多次,那么这里就会出现一个问题就是如果我们不想打印3*3的表格呢?我们是不是就要将所有的3全部进行修改改成其他的数据那么这样的话我们工作量是不是你一下子就加大了很多啊,那么我们这里就可以在game.h文件里面创建两个宏一个用Row表示行,一个用Col表示列,这样以后我们修改数据的时候只用在这一个文件里面修改两个数据就可以了,但是所以我们这里的代码如下:
//game.h文件
void InitBoard(char board[3][3], int col, int row);
#define ROW 3
#define COL 3
那么初始化化数组的任务完成了,那么接下来的任务就是将这个表格的框架给打印出来我们可以看到我们这里用来分隔行的用的是—(三个减号)用来分隔列的是 | 那么我们这里就来创建一个函数用来打印这个棋盘,我们把这个就叫这个函数为DispalyBoard这个函数的参数也跟上面的初始化数组函数的参数一模一样这里就不多说了我们直接来看这个函数是如何实现的我们首先观察一下我们这里的表格:
我们们发现这个表格其实是很有点规律的,我们发现如果把每一行看成两个部分就是先打印一个(空格 %c 空格| 空格%c空格 |空格 %c空格 )再换行再打印一行(—|—|—)那么这样的话我们就可以将这个过程放到循环里面进行实现,我们这个3*3的表格就只用打印3遍(空格 %c 空格| 空格%c空格 |空格 %c空格 )打印两遍(—|—|—)那么这里我们就用for循环来实现这个功能,我们的代码如下:
//game.c文件
void DispalyBoard(char board[ROW][COL], int col, int row)
{
int i = 0;
for (i = 0; i < row; i++)
{
printf(" %c | %c | %c \n",board[i][0],board[i][1],board[i][2]);
if (i < row - 1)
{
printf("---|---|---\n");
}
}
}
其实这里大家应该能够发现一个问题就是虽然我们这里将这个棋盘打印出来了,但是我们这样的写法视乎只能打印列数为3的棋盘,如果列数不为3的话,我们就又得对代码进行修改,那我们就得想一种方法能能打印任意列数和行数,那么这里我们再来观察一下这个棋盘我们之前是因为将每一行看成了一个整体所以就会出现这样的问题,那么我们这里想想能不能将每一格看成一个整体呢?我们来看因为我们的分割行是非常的简单明的我们可以直接将3个(-)看成一个整体在每个方格下面进行打印,那么我们我们就来看看如何把一个小方块看成一个整体,我们可以看到我们的每一行都长成这样(空格 %c 空格 | 空格%c空格 |空格 %c空格 )那我们是不是可以将其拆分成(空格%c空格)再加上一个横杠( | )呢?然后我们又观察可得一行有n列的话就只有n-1个竖杠,那么我们打印每一行的时候少打印一个不就够了吗?因为我们这里这里是每一个空格的打印跟上面的每一行的打印就会有点区别我们这里就得用两个for循环的嵌套来打印我们的表格,我们的代码形式就如下:
for (i = 0; i < row; i++)
{
//打印数据
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//打印分割信息
//printf("---|---|---\n");
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
那么看到这里想必大家应该能够明白我们是如何打印这个这个棋盘的,那么接下来我们就来讲讲如何来下棋
我们首先让玩家下棋,那么我们这里就先自定义一个函数叫PlayerMove,那么玩家下完棋之后我们是不是得打印一下我们的棋盘让玩家知道现在的棋盘是什么样的了,然后再轮到我们的电脑进行下棋,那么我们这里再自定义一个函数叫做ComputerMove用来实现我们的电脑下棋的功能,等电脑下完棋之后我们是不是还得打印棋盘来告诉玩家现在的棋盘是什么样的了。那么打印棋盘这个好说因为我们已经实现了这个函数,那么接下来的问题就是如何实现玩家和电脑的下棋?好这里我们想一下我们这里是玩家下棋那么我们这里的下棋,其实说到底还是对数组里面的空格进行改变,那么既然是对数组里面的内容进行改变的话我们是不是就得将数组传过去,还得将数组中的行和列传过去,那么我们接收到了数组和数组的行和列那么我们这个函数具体又是怎么实现的呢?我们首先是不是得提示我们的玩家该下棋了,那么这个好办我们直接用printf函数打印出玩家下棋请输入坐标,然后我们是不是得让玩家输入对应的坐标进去,然后我们得到了坐标之后我们是不是就可以将这个坐标传到数组里面再将这个对应的位置的内容进行修改,但是这里大家要注意几个问题第一个就是我们玩家是不知道数组的下标是从0开始的,所以我们再把玩家输入的坐标传输到数组里面的时候得注意将其进行一定的修改,那么这里的修改就是将他的行和列的值全部都减去1,第二个就是我们对这个坐标进行修改之后我们还得判断玩家输入的坐标在这个位置能不能把棋下到这简单的说就是这个位置是不是空的,那么我们是不是得进行一次判断,如果玩家输入的位置有其他的值,那么我们是不是得告诉玩家位置已被占用请重新输入坐标,那么我们这里是不是还得将这个输入的过程放到一个循环里面,因为这样子玩家输入的坐标错误的话我们就可以让他重新输入,那么醉后一点就是我们玩家输入的坐标可能会错误,比如我们是一个3*3的棋盘,结果玩家输入的值是4,4这样是不是就超出了我们的预期,难么这里我们是不是得加上一个判断玩家坐标输入对错的功能,如果玩家输入的坐标没有错而且还没有被占用我们就可以判断玩家的输入为有效,那么这个时候我们就可以用break跳出这个循环那么看到了这里我们的代码就基本上可以实现了我们的代码如下:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
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] = '*';
break;
}
else
{
printf("坐标被占用,不能下棋,请选择其他位置\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
那么看到这里我们的玩家下棋就完成了,接下来我们就来看看如何实现ComputerMove这个函数,因为我们这个函数的实现和PlayerMove大致的原理是一样的,所以我们这两个函数的参数也是一样的,那么我们这里的难点就是我们的如何自动扽生成一个坐标,那么这里我们之前是讲过的,大家可以看看我的猜数字游戏里面详细的讲解了如何自动的生成随机数字,那么我们这里既然知道了如何生成随机数字,那么我们这里是不是就可以将这些随机的数字赋值给我们的横纵坐标x和y是不是就可以了啊,又因为这些生成的随机数肯定有些是不合理的,那么我们这里可以将其%上我们的行和列的值,再用同样的筛选条件对其进行赛选得得到合理的没有被占用的值,当然这里大家还要记住的就是不要忘了在主函数里面加入srand((unsigned int)time(NULL)来设置生成随机数的起点并且加入头文件time.h那么我们这里的ComputerMove这个函数就实现了,我们来看看代码的实现:
//game.c
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
int x = 0;
int y = 0;
while (1)
{
x = rand() % row;//0~2
y = rand() % col;//0~2
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
那么电脑下棋和玩家下棋的函数实现了,那么接下来我们就只用实现下棋的判断输赢就可以了
我们这里是三子棋,所以当有竖着连续的三个相同的子,或者横着连续的三个相同的子,或者斜着的连续的三个相同的子,我们就可以判断玩家的输赢,当然这里还有平局的情况就是我们的棋盘中所有的位置都下满了,无法再继续下棋了这就是平局的情况,那么我们这里就需要一个能够判断输赢的函数我们这里就叫IsWin,这个函数的参数跟我们前面打印棋盘的参数一模一样,我们再自定义一个函数用来判断是否平局,那么我们就叫这个函数为IsFull这个函数的参数也跟我们的前面的打印棋盘的参数一模一样,那么我们这里谁赢了我们就返回谁下的那个棋的样子的字符,如果是和局我们就返回字符Q,如果没有人赢,也没有和局,那么我们这里就返回字符C表示游戏继续,那么既然我们这个函数会有返回值,那么我们这里就可以通过这个返回值来判断我们下棋的那个while循环是否还需要继续那么我们这里的判断的过程的代码就如下:
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
//初始化棋盘的函数
InitBoard(board, ROW, COL);
DispalyBoard(board, ROW, COL);
//下棋
while (1)
{
PlayerMove(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
DispalyBoard(board, ROW, COL);
}
那么我们接下来就要具体的实现输赢的判断里我们就讨论最简单的情况呢就是当我们的这个棋盘为3*3的棋盘的情况:
首先在判断谁赢之前我们得首先判断这个棋盘满没满如果满了我们就返回1 ,如果没有满我们就返回0,那么我们这里就可以用两个for循环的嵌套来进行判断,将里面的内容与空格进行比较,如果有一个空格我们就可以返回0来结束循环结束这个函数,如果没有空格我们就可以返回一个1来结束这个函数被,那么我们的代码如下:
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 < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
这个很简单我们直接将同一行的三个空格的元素都比较一下不就够了吗?然后再用for循环将每一行的都进行一次比较,那么我们的代码如下:
//行
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];//
}
}
列的情况:
这个的想法其实跟我们上面的情况非常的相似,我们只用将上面的行改成列不就可以了吗?
我们的代码如下:
//列
int j = 0;
for (j = 0; j < col; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
{
return board[1][j];
}
}
对角线的情况:
因为我们这里是3*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];
}
既然我们的输赢都判断完了,那么我们这里就还差了一种情况就是判断游戏是否继续,因为经历判断玩家赢和电脑赢这两种情况,那么执行到这里肯定就只剩判断是否和棋了,那么我们这里就可以将判断和棋的函数放到一个if语句里面就可以了我们的代码如下:
//没有人赢,就要平局
if (IsFull(board, row, col))
{
return 'Q';
}
//游戏继续
return 'C';
那么看到这里我们的完整的代码就如下:
//game .h
#pragma once
#include
#include
#include
#define ROW 3
#define COL 3
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void DispalyBoard(char board[ROW][COL], int row, int col);
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//电脑下棋
//找没有下棋的随机下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'
//
char IsWin(char board[ROW][COL], int row, int col);
//game.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
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] = ' ';
}
}
}
//第一个版本
//void DispalyBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (i = 0; i < row; i++)
// {
// //打印数据
// printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
// //打印分割信息
// if(i
// printf("---|---|---\n");
// }
//}
void DispalyBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//打印数据
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//打印分割信息
//printf("---|---|---\n");
if (i < row - 1)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
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] = '*';
break;
}
else
{
printf("坐标被占用,不能下棋,请选择其他位置\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
int x = 0;
int y = 0;
while (1)
{
x = rand() % row;//0~2
y = rand() % col;//0~2
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//满了就返回1
//不满 返回0
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 < col; 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][1] != ' ')
{
return board[i][1];//
}
}
//列
int j = 0;
for (j = 0; j < col; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
{
return board[1][j];
}
}
//对角线
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];
}
//没有人赢,就要平局
if (IsFull(board, row, col))
{
return 'Q';
}
//游戏继续
return 'C';
}
\\test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("*****************************\n");
printf("***** 1. play 0. exit *****\n");
printf("*****************************\n");
}
void game()
{
char ret = 0;
char board[ROW][COL] = { 0 };
//初始化棋盘的函数
InitBoard(board, ROW, COL);
DispalyBoard(board, ROW, COL);
//下棋
while (1)
{
PlayerMove(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
DispalyBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
DispalyBoard(board, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));//设置随机数的生成起点的
int input = 0;
do
{
menu();//打印菜单
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误!\n");
break;
}
} while (input);
return 0;
}