一、前言:
所谓”三子棋“,就是在棋盘上下棋,在棋盘的任意一条直线上达成三颗己方棋子即可获胜的游戏,该游戏结果分三种:获胜、失败和平局。
若我方使用⭕符号,则:
获胜示例:
失败示例:
平局示例:
二、思想:
写程序,要先考虑好该程序的总流程,接着去实现需要实现的各个板块部分,从大的方面入手去写,然后再去细细填充每一部分的内容,最后串接起来。
那么要实现”三子棋“小游戏,首先抛开程序本身,我们在该项目之下创建两个源文件test.c和game.c,分别用来测试,存放所用到的函数,创建一个头文件game.h,用来实现函数声明以及头文件的引入,这是三个大的板块。
其次,要实现”三子棋“,从我们玩游戏的过程入手。测试文件中应包括:
1.菜单
2.玩游戏的函数。其中包含:
1)首先需要初始化棋盘;
2)其次在每次走完棋子后要展示出当前棋盘,所以需要展示棋盘的函数;
3)既然要下棋,那还要有落子的函数,包括用户的落子和电脑的落子两部分。
4)当然,落子之后紧接着就要判断胜负了,所以需要判断胜负的函数。
3.主函数
三、代码解析和实现:
首先,我们在test.c文件下,为了保证一定的规范性,我们可以写一个菜单界面,在主函数中配合switch语句使用。0表示退出,1表示玩游戏。
void menu()
{
printf("**********************\n");
printf("******0.exit**********\n");
printf("******1.paly**********\n");
printf("**********************\n");
}
我们先跳过玩游戏函数,在最后来分析,那么主函数如下:
用一个简单的流程图来解释就是:
这里一定要用随机种子,不然在后面使用随机数函数时,每次执行程序产生的随机数相等。
int main()
{
srand((unsigned)time(NULL));//随机种子
int input;
do
{
menu();
printf("请输入你的操作:\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
break;
}
} while (input != 0);
return 0;
}
//
在最重要的玩游戏函数部分中,包含了我们所要实现的所有关键函数,并把它们都写在game.c下,所以在这里我先对这些函数一一解释,然后再串接起来形成game()。
1)InitBoard();初始化棋盘
我们能够想到,初始化棋盘的说白了也就是初始化一个3行3列的二维数组,所以代码如下
void InitBoard(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
2)ShowBoard:展示棋盘
我们从最后的程序执行窗口来看,我们最终得到的是这样一个棋盘。
其中暗藏玄机,其实他是这样子的,如下图,棋盘每一格的横线选用三个_,是因为为了美观,我们每次落子时落在中间位置,相邻两字符为空,所以用三个_。
其实,从上图中可以看出,我们要展示的棋盘,其实是五行五列字符,其中包括空字符、棋子、竖线|、横线_。
总的来说是通过一个双重嵌套循环:
在内层函数中,我们要实现的是第一行字符,首先打印棋子,注意%c左右为空字符,保证美观,接着打印竖线|,但是从上面示意图可以看到竖线一共只需要两列即可,所以在打印每一行竖线只需要循环两次,在打印前通过if语句判断即可。三次循环结束后,换到第二行。
从内层函数出来后,打印第二行字符,第二行字符首先就是横线_,从上面示意图可以看出,横线一共只需要两行即可,所以每次在打印前要先判断外层循环是否到达第三次,也就是此时是否打印的是最后一行,若满足条件不是外层最后一次循环,则开始打印。通过一个内层循环打印第二行,方法同第一行。打印结束换行。
以此类推,再从最外层函数进入,打印后面的棋盘。
代码如下:
void ShowBoard(char board[ROW][COL], int row, int col)
{
printf("=====================\n");
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
//三个空格S
printf(" %c ", board[i][j]);
//两列竖线
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
//两行横线
if (i < row - 1)
{
for (int j = 0; j < col; j++)
{
printf("---");
//
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
}
}
printf("=====================\n");
}
3)PlayerMove:玩家落子
该函数比较简单,整体通过一个循环,由用户输入坐标,先判断输入的合法性,这里注意用户输入的行列为1、2、3而不是0、1、2。
若合法,就判断此位置是否为空,如果没有棋子就给二维数组赋值X即可,用"X"作为用户的棋子,结束循环。当然当此位置已经有棋子时,则输出该位置已经有棋子,再次循环;
若不合法,输出此坐标不合法,再次进入循环。
代码如下:
void PlayerMove(char board[ROW][COL], int row, int col)
{
while (1)
{
printf("请输入你的坐标:\n");
int x = 0;
int y = 0;
scanf("%d%d", &x, &y);
if (x >= 1 && x <= 3 && y >= 1 && y <= 3)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = 'X';
break;
}
else
{
printf("该位置已有棋\n");
}
}
else
{
printf("坐标不合法\n");
}
}
}
4)ComputerMove:电脑落子
原理和玩家落子相同,只不过坐标由随机函数分别对行数列数取余得到。用"O"作为电脑的棋子。
void ComputerMove(char board[ROW][COL], int row, int col)
{
while (1)
{
int x = rand() % row;//[0,1,2]
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = 'O';
break;
}
}
5)IsFull:判断棋盘是否被占满
我们在分析判断输赢函数之前,先来看这个函数,因为如果无法判断胜负,那么剩下两种情况,要么平局,要么比赛没有结束。对这两种情况进行判断时,需要用到IsFull函数。
那么做法就是遍历二维数组,寻找是否有空位置。若没有满,返回-1;反之,返回1,棋盘占满,那么平局。
static int IsFull(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return -1;//没有满
}
}
}
return 1;//说明当前平局
}
6)IsWin:判断输赢
不论是用户还是电脑,在每次落子之后都要对棋局的胜负进行判断。
当有一行或有一列或有一斜线的字符相等时那么返回这一行随便哪一个的字符,若返回为”X“,则玩家赢,若返回为”O",则电脑赢。
但是要注意,当某一行或某一列或某一斜线均为空字符时,也会出结果的,那么出错返回一个空字符,所以在判断时要保证其不为空字符。
当然如果此时无法判断输赢,那么来判断是游戏未结束还是出现平局,也就是判断棋盘是否被占满了,上面已经提到过,不再赘述。
char IsWin(char board[ROW][COL], int row, int col)
{
//行
for (int 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 (int j = 0; j < col; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j]
&& board[0][j] != ' ')
{
return board[0][j];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2]
&& board[0][0] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0]
&& board[0][2] != ' ')
{
return board[0][2];
}
//是否是平局
if (IsFull(board, row, col) == 1)
{
return 'Q';//平局
}
return ' ';//没有平局
}
以上函数即为玩游戏函数所需要调用的函数!!!
那么我们需要把他们在game.h头文件中声明一下,如下:
这里使用宏定义定义了 ROW和COL行列值为3。
#pragma once
//函数的声明 头文件的引入
#define ROW 3
#define COL 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void InitBoard(char board[ROW][COL], int row, int col);
void ShowBoard(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);
char IsWin(char board[ROW][COL], int row, int col);
如此一来,当我们有了game.h头文件,我们在game.c和test.c中只需要使用game.h头文件即可。
/
那么我们来执行最关键的一步,那就是玩游戏函数,有了上面的函数便可以轻易的实现.
Game();
我们用流程图来解释该函数:
void game()
{
printf("开始游戏\n");
char board[ROW][COL] = { 0 };
InitBoard(board, ROW, COL);
ShowBoard(board, ROW, COL);
char ret;
while (1)
{
PlayerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != ' ')
{
break;
}
ShowBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != ' ')
{
break;
}
ShowBoard(board, ROW, COL);
}
if (ret == 'X')
{
printf("玩家赢\n");
}
else if (ret == 'O')
{
printf("电脑赢\n");
}
else if (ret == 'Q')
{
printf("平局\n");
}
}
做完这一步我们的代码就已经完善了,那么我们来看结果:
四、执行结果:
“三子棋”游戏结束!!!!!!