三子棋的实现
1.环境搭建
.此处我们采用多文件联合实现,这对我们以后与别人合作开发的有着一定的帮助。首先我们创建三个文件分别为头文件(gane.h),源文件(game.c和test.c)。
2.游戏实现
game.h
其中game.h用于包含一些在源文件中所需要的系统头文件以及一些函数的定义,利用这种方式避免了在后续源文件中重复引用系统头文件,只需要引用我们创建的gam.h即可全部满足。
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 20
#define COL 20
#define num 5
//初始化棋盘
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);
//判断输赢
//玩家赢-'*'
//电脑赢-'#'
//平局,棋盘已满-'Q'
//继续下棋-'C'
char IsWin(char board[ROW][COL], int row, int col);
test.c,game.c
一般我们写代码的顺序就是先写主函数,再写我们需要的其他函数。
这里我们把主函数写在 test.c 中,其他函数写在 game.c 中,这样方便我们排错。
首先我们明确我们要写的游戏的内容:(开始菜单,游戏过程,结果显示)
1.开始菜单:
我们写一个普通的打印函数作为开始菜单,menu()
void menu() {
printf("**********************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**********************\n");
选择1开始游戏,选择0退出游戏
2.进行游戏:
这部分是游戏的主要部分,也是该游戏较难的部分。
我们首先得写一个函数将棋盘打印出来,利用空格填充我们下子的地方;
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
//版本1
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%c", board[i][j]);
}
printf("\n");
}
}
版本1打印出来的棋盘我们是无法看见的,所以影响玩家的体验感,所以我们要让棋盘可视化
例如下面这个棋盘:
上图这个棋盘我们简单分析一下,不能发现棋盘是由多个 ---| 排序得到的但是最排是没有 | 的,另外每个组合都用空格填充,试着写一下:
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
//版本2
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//1. 打印数据
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//2. 打印分割线
if(i < row-1)
printf("---|---|---\n");
}
}
版本2相较于版本1确实做到了棋盘可视化,但是任然有一些不足——棋盘的大小是固定死的不能根据我们的需求随时变化,因此我们进行优化:
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
//版本3
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//1. 打印数据
int j = 0;
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//2. 打印分割线
if (i < row - 1)
{
//printf("---|---|---\n");
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
版本3完美地解决了版本2的不足。
如此我们就把棋盘打印好了,接下来就是下子环节。我们规定玩家先手,电脑后手。
PlayerMove:玩家将棋子下输入自己下子的位置,位置被占用则重新输入,若输入超过棋盘大小同样重新输入。另外大部分玩家不理解数组的读取,他们在输入棋子位置时是直观输入的,考虑到这一点,我们也要进行适当的调整来保证玩家落子在想要的位置。
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x=0;
int y=0;
printf("玩家下棋:> \n");
//玩家下棋
while (1) {
printf("请输入落子位置\n");
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:电脑随机落子,因此我们采用 srand 和 time 函数来提供随机数,但是想要取到我们所需范围内的随机数,我们还需用初识随机数模上(%)棋盘的长宽,如此便可得到(0~row,0~col)内的随机数。与玩家下棋一样电脑下棋同样需要进行判断落子位置。
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;//0~row-1
int y = 0;//0~col-1
printf("电脑下棋:> \n");
while (1) {
x = rand() % row;
y = rand() % col;
if (board[x][y] == ' ') {
board[x][y] = '#';
break;
}
}
}
3.结果判断
判断是否三子成功连线,只需判断 行&列&两条对角线 ;
游戏进行过程中无非以下几种情况:
1.玩家赢
2.电脑赢
3.平局(棋盘已满且无人胜出)
4.继续
此时我们可以写一个 IsWin 函数来对多种情况进行判断:
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[2][i]&&board[0][i]!=' ') {
return board[0][i];
}
}
//对角线
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[1][1] != ' ')
return board[1][1];
//平局
if (IsFull(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
通过在主函数中调用这些代码即可实现简单的三子棋小游戏。
#include"game.h"
void menu() {
printf("**********************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**********************\n");
}
void game() {
char board[ROW][COL] = { 0 };
char ret = 0;
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;
}
3.三子棋进价五子棋及多子棋的实现
但我们想玩多子棋时,我们是否要根据棋子数来调整棋盘尺寸,答案是肯定的,棋盘大小总是随着我们的需求变化,通过上述代码我们很容易就能实现棋盘大小的变化,但是它在判断输赢时已经定死了赢得游戏的要求,所以我们随意地通过改变棋子数来达到五子棋及多子棋的效果,若要达到此种效果其实我们只需要改动 IsWin 函数的判断条件即可。
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
int count = 0;
//行判断
for (j = 0; j < col; j++)
{
for (i = 0; i < row; i++)
{
if (board[i][j] != ' ')
{
count++;
if (count == num)
return board[i][j];
}
else
count = 0;
}
}
//列判断
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] != ' ')
{
count++;
if (count == num)
return board[i][j];
}
else
count = 0;
}
}
//对角线
for (i = 0; i < row; i++)
{
if (board[i][i] != ' ')
{
count++;
if (count == num)
return board[i][i];
}
else
count = 0;
}
for (i = row-1; i>=0; i--)
{
if (board[i][i] != ' ')
{
count++;
if (count == num)
return board[i][i];
}
else
count = 0;
}
//平局
if (IsFull(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
这个方法是我刚接触c的时候写的,后来又一次想起来又思考了一番发现其实是错误的,这种判断输赢的条件仅仅是判断棋盘是否为空,所以假如水平方向有玩家的棋子和电脑的棋子一共五个的话他就会随意的判定为某一方胜利,其次就是斜方向我只对主对角线和次对角线进行了判断并没有考虑其他斜方向的情况。发现问题并解决以下是我重新完善的代码:
char IsWin(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
int count1 = 0;
//玩家棋子计数
int count2 = 0;
//电脑棋子计数
//行判断
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] != ' ')
{
if (board[i][j] == '*') {
count1++;
count2 = 0;
}
//每次一个标志++的时候另一个归零避免对后面的计数产生影响
else {
count2++;
count1 = 0;
}
if (count1 >= num || count2 >= num)
return board[i][j];
}
else {
count1 = 0;
count2 = 0;
}
}
}
//列判断
for (j = 0; j < col; j++)
{
for (i = 0; i < row; i++)
{
if (board[i][j] != ' ')
{
if (board[i][j] == '*') {
count1++;
count2 = 0;
}
else {
count2++;
count1 = 0;
}
if (count1 >= num || count2 >= num)
return board[i][j];
}
else {
count1 = 0;
count2 = 0;
}
}
}
//对角线
for (i = 0; i < row-num; i++)
{
for (j = 0; j < col-num; j++) {
for (int k = 0; k < num; k++) {
if (board[i+k][j+k] != ' ')
{
if (board[i+k][j+k] == '*') {
count1++;
count2 = 0;
}
else {
count2++;
count1 = 0;
}
if (count1 >= num || count2 >= num)
return board[i][j];
}
else {
count1 = 0;
count2 = 0;
}
}
}
}
for (i = 0; i<row-num; i++)
{
for (j = col - 1; j >= num; j--) {
for (int k = 0; k < num; k++) {
if (board[i+k][j-k] != ' ')
{
if (board[i+k][j-k] == '*') {
count1++;
count2 = 0;
}
else {
count2++;
count1 = 0;
}
if (count1 >= num || count2 >= num)
return board[i][j];
}
else {
count1 = 0;
count2 = 0;
}
}
}
}
//平局
if (IsFull(board, row, col) == 1)
{
return 'Q';
}
return 'C';
}
相较于之前的方法,这次我将玩家棋子和电脑棋子分别进行了计数 另外一点值得注意的是当我们对其中一方的棋子计数时要将另一方的计数值归零避免出现和之前一样的错误(即五个不一样棋子出现就会判定胜负),还有我改进了斜方向的判定条件 不仅仅针对角线处 对其他斜向也进行判断,如此这般我们便可以得到一个完整的多子棋玩法。
#define ROW 20
#define COL 20
#define num 5
这是我第一次实现C语言编写小游戏,有些地方花了些许时间去理解,当然我也不敢保证我完全弄懂了,所以我把我的学习过程分享给大家,希望对一些和我一样的新手有帮助,也希望有大佬能有所指教。