前言
想必大家都玩过三子棋吧,棋盘为3x3的九宫格,双方各自轮流下棋子,如果有一方的三个棋子先练成一横、一竖或者一条对角线,那么这一方就判为胜利;但如果棋盘下满之后没有出现一方棋子连成一条线的情况,那么就算和棋。这里我们用C语言来实现简易的三子棋。
三子棋的设计思路
既然是设计游戏,那么我们需要设计游戏菜单,游戏游玩,判断结果。
游戏菜单:提示玩家开始游戏还是退出游戏;
游戏游玩:输出棋盘以及双方棋子分布;
判断结果:判断哪一方获胜还是和棋。
三子棋的程序输出
根据模块化程序设计的思想,我们将主文件test.c和游戏程序game.c分开。
一、游戏菜单
void menu()
{
printf("*************************\n");
printf("******* 1.play ********\n");
printf("******* 0.exit ********\n");
printf("*************************\n");
}
首先我们以menu函数定义一个菜单出来,然后每次玩完游戏后我们再次返回到菜单页面。
int main()
{
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;
}
在这里,每次在选择之前先打印出菜单,然后我们输入1或者0。输入1,则开始游戏;输入0,则退出游戏,程序结束;如果输入其他字符,则会出现“选择错误,重新选择”的字样,需要重新输入对应的数字。
二、游戏游玩
1.棋盘创建
既然是三子棋,那么我们先创建一个3x3的二维数组
char board[3][3];
但是我们后面可能会把三子棋引申为多子棋,而程序中可能会多次使用数组的大小,因此我们在这里使用宏定义#define,用标识符代替行列数。我们创建一个头文件game.h,专门存放这些内容。
#define ROW 3
#define COL 3
那么我们之后只需要更改ROW和COL后面的数字就可以实现三子棋到多子棋的变化。
char board[ROW][COL];
2.棋盘初始化
刚开始的棋盘是全为空,一个棋子都没有,这里我们用空格代替空,我们在test.c的game函数中调用InitBoard(board, ROW, COL)函数,并在头文件game.h中进行声明。
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
我们在game.c文件中对该函数进行定义
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] = ' ';
}
}
}
初始完棋盘后,我们接着把棋盘打印出来,还是在头文件game.h中对函数进行声明,然后在game.c文件中进行定义
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
//上面是头文件game.h中的声明
//下面是源文件game.c中的函数详细代码
void DisplayBoard(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++)
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
}
但这种打印出来的效果没有很明显的九宫格分布,因此我们将其加上一些符号,让它看起来比较清晰
void DisplayBoard(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<row-1)
printf("---|---|---\n");
}
}
该程序打印出来虽说可以看出比较明确的九宫格,但由于输出的限制性,导致他只能打印出九宫格,如果后面数组的行列大小再进行改变,打印出来的棋盘也不会跟着变化,因此我们采用以下程序来打印棋盘
void DisplayBoard(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++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//在打印分割行
if (i < row - 1)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
printf("\n");
}
}
3.玩家电脑依次下棋
我们在此要实现的是玩家和电脑轮流下棋,因此我们写玩家下棋的代码,先在头文件game.h中声明,再在game.c文件中进行定义
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
//上面是头文件game.h中的声明
//下面是源文件game.c中的函数详细代码
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");
}
}
}
我们可以从这段代码中看出来,玩家需要输入x,y两个坐标,接着由程序判断该坐标所在位置是否是空,如果不是空,则“输出坐标被占用”,并让你重新输入坐标值;如果输入的值不在1到三子棋的最大行列数的范围内,则输出“坐标非法,需要重新输入”,直到输入一个有效并且为空的坐标处,用‘ * ’代替玩家在这里下的棋子。
接着我们要实现的是电脑进行下棋,我们要模仿玩家一样,进行有“思想”的下棋,这里是简单实现三子棋,因此我们要让电脑产生随机坐标,这里我们就要用上rand函数产生随机数
rand可以生成0~RAND_MAX(32767)之间的一个数,但只用rand函数的话,它只会生成一次随机数,后续不会再生成其他数,因此我们要用srand函数搭配使用,使它每次都进行一次随机。
srand((unsigned int)time(NULL));
在主函数中加入这句代码,可以让rand每次调用时,根据当地的系统时间time来产生一个随机值
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//上面是头文件game.h中的声明
//下面是源文件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;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
通过电脑产生随机数,得到合理的横竖坐标值,判断他是否是没有下棋子的空坐标,如果是,则用‘ # ’代替。
4.判断胜负
写好玩家和电脑下棋的代码后,我们要进行判断对局是否结束,以及结果是玩家赢、电脑赢还是平局。这里我们在game.h中声明IsWin(board, ROW, COL)函数,并在game.c中进行定义。
//判断输赢
char IsWin(char board[ROW][COL], int row, int col);
//上面是头文件game.h中的声明
//下面是源文件game.c中的函数详细代码
//玩家赢 - '*'
//电脑赢 - '#'
//平局 --- 'Q'
//游戏继续-'C'
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][0] != ' ')
{
return board[i][0];
}
}
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
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';
}
我们在这里先判断游戏结束有哪几种状态,分别是:玩家赢 - ' * ', 电脑赢 - ' # ',平局 --- ' Q ',游戏继续 - ' C '。
接着我们再分,玩家赢和电脑赢是因为有一方的三个棋子练成一横或一竖或一条对角线,因此我们进行判断,如果有这种情况,返回他所包含的字符即可直到是‘*’代表的玩家赢,还是‘#’代表的电脑赢。
在判断完上述两种情况之后,还有平局需要进行判断,如果棋盘上没有空的位置,则代表平局,返回Q,这里我们使用IsFull(board, row, col)函数进行判断。
//static - 修饰函数只在该文件中使用
static 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;
}
由于该函数只在game.c文件中使用,因此我们使用static进行修饰,限制它的作用域。
当程序判断出来不是平局后,则只剩下继续游戏这一选择,我们在这里用‘ C ’代替。
在game函数中建立一个字符型变量ret接受这四种返回值,在每次玩家下完棋以及电脑下完棋之后都要进行判断结果。
while (1)
{
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
当返回值为‘C’时,继续进行循环玩家和电脑轮流下棋,直到出现一方玩家获胜或者棋盘满子。
这时候通过if语句跳出while循环,进行判断结果。
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
至此,我们的游戏就结束了,我们也可以总体运行一遍,查找哪里需要修改的地方。
三、总体代码以及部分说明
1.game.h头文件
#pragma once//避免头文件被包含多次
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h> // - time函数
#include <stdlib.h> // - rand、srand函数
#define ROW 3
#define COL 3
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void DisplayBoard(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);
这里是程序用到的头文件、对程序的一些预处理以及别的文件共同使用到的函数声明。
2.game.c源文件
#include"geme.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 DisplayBoard(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++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//在打印分割行
if (i < row - 1)
{
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;
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//static - 修饰函数只在该文件中使用
static 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;
}
//玩家赢 - '*'
//电脑赢 - '#'
//平局 --- 'Q'
//游戏继续-'C'
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][0] != ' ')
{
return board[i][0];
}
}
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
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';
}
#include"geme.h"是对用户自己创建的头文件的引用
该文件是对游戏每一步的实现所写的具体代码。
3.test.c源文件
#include"geme.h"
void menu()
{
printf("*************************\n");
printf("******* 1.play ********\n");
printf("******* 0.exit ********\n");
printf("*************************\n");
}
void game()
{
char ret = 0;
char board[ROW][COL];
//开始的时候,数组的内容应该是全部空格
InitBoard(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//下棋
while (1)
{
PlayerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
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;
}
这里主要放main函数、菜单menu函数以及游戏大概流程的game函数。
总结
这算是我的第一篇正式博客,希望之后我能够勤快点,多多写博客,促进自己的学习!
三子棋只是个开始,不是个结束,还有四子棋,五子棋,N子棋!