前言
大家好,我是耶鲁,今天我先出一篇关于三子棋游戏的具体实现,希望能激发大家学习C的兴趣,使咱们不仅会玩游戏,还能知道玩的游戏是怎么实现的。知其然知其所以然!!废话不多说,下面我们就直接进入主题。
一、三子棋游戏的玩法及规则
三子棋是黑白棋的一种。三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。我相信大家小时候应该都玩过吧!
二、设计图
我们将实现三子棋游戏的程序分成下图所示的三个文件:
我们再将其细分,大概可设计为下图六大模块:
接下来我们将这几个模块一一实现,再将模块与模块之间相连接,即可实现三子棋游戏的实现。
小建议:我们写程序的时候,最好是写一部分测试一部分,特别是代码比较多的情况下,这样程序出现bug的话我们可以及时的发现,确保这一部分是正确的。如果等到最后再测试的话,出现问题,解决起来就比较麻烦。
1.游戏菜单
游戏菜单我们可以这样来设置:当玩家输入1时:开始游戏,当玩家输入0时:退出游戏,除此之外,如果玩家输入1 / 0之外的数字应该提醒玩家:输入错误,请重新输入。并且玩家可以多次进行游戏,如果玩一局不过瘾还可以再来一局。
经过上面的分析,我们大概可以知道程序要用到那些语句,我们可以使用switch语句来针对玩家所输入的数字进行相应的操作,使用循环语句使玩家可以按照自己的意愿选择是否多次进行游戏。
具体实现代码如下:
2.设计棋盘
下棋肯定是需要一个棋盘的(一般都是3×3形式的棋盘),这里的棋盘既然是三行三列的,这不跟我们学过的二维数组很像吗?所以我们可以尝试用字符型二维数组存储棋盘上的数据。
我们上面说到棋盘形式一般都是3×3,但是如果有人想改变棋盘的形式,我们还得在程序中一处处的修改数据,太过于复杂,所以我们在头文件中利用 #define 来定义 ROW3,COL 3 这两个符号,就可以将程序中使用到3的地方用ROW和COL进行替换,也可以对这两处进行修改,进而改变我们棋盘的样式。
如下图所示:
我们先将棋盘初始化为空格,再利用 ’ ‘,’|’,’—’ 这几个符号来刻画出棋盘,大概的效果图如下:
代码实现如下:
图代码运行结果图如下:
如果我们想将棋盘改为10 × 10的样式,但是我们打印出来的棋盘为什么是10 × 3的样式呢?这是因为我们程序中输出数据的那一行已经写“死”了,它的列数已经固定下来了。所以就会出现如下结果:
通过观察我们可以把 %c | 看成一组,最后一列我们只打印 %c 不打印 | ,既然我们最后一列不打印 | ,所以我们要将 %c 和 | 分开打印。
代码实现如下:
代码运行结果如下,经过我们的修改,版本2的代码的实现就非常的灵活了。
3.规定棋子
棋子可以根据大家个人的喜好来设置,我这里规定玩家的棋子是’*’, 电脑的棋子是’#’。
4.实现玩家下棋
玩家和电脑每走一步棋之后,棋盘都要进行记录。所以再设置一个打印棋盘的函数,不论玩家还是电脑下棋之后我们都会调用打印棋盘的函数,观察对局情况。
5.实现电脑下棋
在实现电脑下棋之前我们先简单介绍一下rand函数:它是C语言中用来产生一个随机数的函数,rand函数界限:<stdlib.h>头文件中有宏 #define。RAND_MAX 0x7fff,rand产生一个0-0x7fff之间的的随机数,也就是最大是32767的一个数字。rand函数确实会帮我们生成一个随机数,但是生成随机数的方法有问题,看下图:这是我从rand介绍文档里面截取的一段话。这句话的大概意思是:调用rand函数之前我得先调用srand函数,我们要用srand来设置随机数的生成器。
srand函数原型:
srand的参数应该是一个无符号整型并且是随时变化的,什么数字是随时在发生变化的?当然是时间啦!时间每分每秒都在发生变化。所以我们可以把时间传给srand这个函数。这里又得引入一个概念叫时间戳,大家如果感兴趣可以自行了解一下哈,我简单说明一下,时间戳就是目前的时间和计算机的起始时间的差值(秒)。我们想要获得时间戳就得利用C语言库函数time,time函数调用要给它传一个NULL,time_t是由typedef定义的整型值且函数头文件为<time.h>。
上述实现如下:
经过上面的一系列铺垫,电脑就可以得到一个随机数,再将随机数的数值通过取模运算控制在合法范围内即可。
6.对局状态
游戏的对局可分为四种状态
玩家赢 —’*'
电脑赢 — '#'
平均 — 'Q’
继续 ----'C’
根据返回相应的符号可知对局当前对应的状态。
分析:先设置一个判断输赢的函数,那一方三个棋子先连成一线的一方为胜方,横向、竖向、斜向均可,然后在判断是否平局,如果棋盘没有空格时就说明棋盘已经满了,此时还没有分出胜负的话,就说明此局是平局,否则就继续游戏。
代码实现如下:
三、游戏全部代码
test.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()
{
//数据存储到一个字符的二维数组中,玩家下棋是'*',电脑下棋是'#'
char board[ROW][COL] = { 0 }; //最开始的时候数组的内容应该是全部空格
//初始化棋牌
InitBoard(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//下棋
char ret = 0;
while (1)
{
player_move(board, ROW, COL); //玩家先下
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
computer_move(board, ROW, COL);//电脑后下
DisplayBoard(board, ROW, COL);
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
}
void test()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:>");
scanf("%d", &input); //玩家可能输入1 / 0 / 其它数
switch (input) //运用switch语句可以根据玩家输入的数字,执行相应的语句
{
case 1: //输入1,进入game函数
game();
break;
case 0: //输入1,进入退出游戏
printf("退出游戏\n");
break;
default: //输入其它数字
printf("输入错误,请重新输入\n");
break;
}
} while (input); //输入的input为0,条件为假,跳出循环。输入的input不为0,条件为真,继续进入do...while循环
}
int main()
{
test();
return 0;
}
game.h
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#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 player_move(char board[ROW][COL], int row, int col);
//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);
//
//判断输赢的代码
// 四种状态
//玩家赢 - '*'
//电脑赢 - '#'
//平均 --- 'Q'
//继续 ----'C'
char is_win(char board[ROW][COL], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#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循环设置出三行三列的形式
{
for (j = 0; j < col; j++)
{
board[i][j] = ' '; //棋盘先初始化为空格
}
}
}
//版本1
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// int j = 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");
// }
//}
//版本2
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 player_move(char board[ROW][COL], int row, int col)
{
printf("玩家下棋:>");
int x = 0;
int y = 0;
while (1)
{
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) //判断玩家输入坐标范围的合法性
{
if (board[x - 1][y - 1] == ' ') //判断输入坐标处是否有棋子,从玩家的角度去考虑数组的行和列和我们习惯上的行和列有所差别,数组是从0开始,而我们习惯从1开始
{
board[x - 1][y - 1] = '*';
break; //玩家下完之后,跳出循环,等待电脑下棋
}
else
{
printf("该坐标被占用,请重新输入!\n");
}
}
else
{
printf("坐标非法,请重输入!\n");
}
}
}
//电脑下棋
void computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋>\n");
while (1)
{
x = rand() % ROW;//0~2 //rand函数可以产生随机数且使随机数在合法坐标范围内
y = rand() % COL;//0~2
if (board[x][y] == ' ') //判断输入坐标处是否有棋子
{
board[x][y] = '#';
break;
}
}
}
//判断棋盘是否填满
int is_full(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 is_win(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]; //返回三个相等棋子的其中一个即可
}
}
//三列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ') //每一列的棋子相等且不为空格
{
return board[1][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 (1 == is_full(board, row, col))
{
return 'Q';
}
//棋盘还有空格,继续
return 'C';
}
总结
好了,三子棋游戏具体的实现过程就介绍至此了,我坚信慢工出细活,所以文章更新得到较为缓慢,希望大家多多支持!我也会尽我最大的努力为大家讲解清楚每一处知识,但精力有限,文章不免有瑕疵之处,如果大家有什么问题或者建议的话,可以在评论区讨论指出,谢谢! ψ(*`ー´)ψ