关于三子棋游戏的简易实现与N子棋胜利判断方法
要实现三子棋游戏,主要需要实现以下几个要求:>
- 需要一个棋盘,既然需要一个棋盘,棋盘是在一个平面上的,所以我们需要创建一个二维数组
- 棋盘的打印
- 玩家下棋与电脑下棋
- 判断胜利
因为在写三子棋游戏中,我分了两个文件,一个是game.c,一个是test.c。game.c主要是游戏的实现部分,而test.c是主函数测试部分,
并且相对应的给了一个game.h文件,用来实现函数各个声明与标识符常量的定义。
这里先给出game.h以便更好了解各个标识符所代表的意义.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
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);
以下是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 ret = 0;
char board[ROW][COL];//? NO,we can't do it, because we are so hard to change relveant value;
//首先初始化棋盘
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");
}
}
void test()
{
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);
}
int main()
{
srand((unsigned int)time(NULL));
test();
return 0;
}
接着为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 (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;
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");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
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 1;
}
}
return 0;
}
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];
}
}
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[1][3] && board[1][1] != ' ')
{
return board[1][1];
}
int ret = IsFull(board, row, col);
if (1 == ret)
{
return 'C';
}
else
{
return 'E';
}
}
首先是test.c中的内容:
这里的test函数是为了存放我们想要实现的让玩家输入数字来判断是否进入游戏的一个函数,这里的srand函数是为了后面中电脑下棋的一个前提条件:
于是现在来到了我们的test函数中
首先还是常例的打印菜单函数,提示以下玩家应该选什么:
运行结果是这样
既然已经提示了,接下来总得输入点什么吧?因此上图的scanf与do…while循环便是为了来适配我们的选择,因为游戏至少执行一次,当你输入1的时候,进入游戏,结束游戏后,while判断input为真,可以重新输入数字。当输入为0时,do…while循环判定为假,直接退出循环,退出程序,当input输入其他值时,重新输入.现在我们输入几个数字之后来看一下效果:(下图的game函数暂时使用printf(“三子棋游戏”)来代替。
接下来轮到game函数部分,也是今天最重要的一部分,这其内部,我们将通过各类函数来实现三子棋游戏功能,先看代码:
刚开始想法是,先创建一个二维数组对吧,类似于这样:
但是行和列里面直接放置数字三是否不太合适呢,万一某天我们要玩五子棋,甚至是十字棋,我们要一个一个去改动与其相关的变量,因此,这里是不太合适的,因此,我们在game.h中定义两个标识符常量ROW,与COL,分别为行和列,这样每次只用更改ROW与COL的值就可以了(别忘记包含头文件噢)
数组定义好之后,接下来我们需要将其初始化,我们写一个Initboard函数来实现,先给大家看一看构建好的三子棋棋盘是什么样的。
旁边的横线与竖线是我们所起的装饰,可以很明显的看到,我们为数组每一个值都初始化为空格,这样也是以便后文的下棋。
写了一个Initboard函数过后,我们在game.h中进行声明并在game.c中进行定义
初始化过后,我们就可以来打印棋盘了,打印棋盘前,我们将棋盘分为两部分,分别打印,如下所示
将 %c |分为一组,—|分为另一组来进行打印,并且—|只用打印两组,也即为row-1组
因此,我们这样编写Displayboard函数
里层第一个for循环中为什么要加if(j<col-1)的条件?,因为我们在打印|时,每一行只要col-1条,在这里也每一行即为2条|,不信你可以去看下方上方三子棋的图喔,下方的if(j<col-1)同理,里层第二个for循环也一样,只需要在row-1行前打印—|就够了,请看下图
(之前分析时候的图)**
**这样处理之后,程序运行的结果为:
我们便成功的将其打印出来了。
棋盘有了,接下来总得下棋吧?我们编写一个PlayerMove函数来实现,既然是下棋,也不能只下一次,因此我们也要编写一个循环
如下图所示
接下来时PlayerMove函数的具体实现
此处我们将玩家下棋的字符弄为,若玩家成功下棋,直接跳出循环,不用再继续了,若玩家输错,则可以重新输入。这里为什么我们会将board[x-1]y-1] 赋值为 呢,正是因为数组的下标从0开始,但玩家一般会觉得第一行就是第一行,因此,实际的坐标是比输入的坐标-1得到的。现在我们看一下效果**
此然玩家下完了,也应该轮到电脑下了吧?这里我们编写一个ComputerMove函数来实现电脑下棋,如下所示
此处的x与y我们让其生成0~2随机值,这样可以对应到数组每个元素的下标,并且当数组该下表所对应的值为空格时,赋值为’#;直接跳出循环。
当玩家下完与电脑下完之后,我们需要判断其是否胜利,因此我们编写IsWin函数来判断是否胜利,若胜利,我们返回此时下棋的那个字符,若平局,我们返回字符‘E’,若未平局也未胜利,则返回 ‘C’,代表游戏继续。下面是IsWin函数的具体实现方法。
此处在判断每一行与每一列,对角线元素是否相同后,若都不相同,则有可能是平局,因此我们写了一个IsFull函数,来判断是否还有棋盘中是否还有空格,如果有空格,则证明还未平局。那我们就return ‘C’,否则,return‘E’,代表平局。IsFull函数的实现方法:
运行出来的结果为:
下面是程序的改进方法:
在判断胜利方面,我们其实把程序写死了,也即为只能判断三子棋的胜利,现在我要改动数值玩五子棋,那么其实这个程序已经无法再使用了,因此,我对胜利的判断有以下思考:即计算所下的那个位置的字符所在的行其总个数,列的总个数,对角线的总个数
下面我将画一张图来表示
下面对代码进行改动
因为我们每次下棋后都要进行判断,因此干脆在下棋中直接进行判断即可,对程序改动如下:
对PlayerMove与ComputerMove改为有返回值类型的函数
在ComputerMove与PlayerMove后面增加了IsWin函数
IsWin函数便实现我们刚才的通过加和来判断输赢,我们重点来看一下其怎么实现:
char IsWin(char board[ROW][COL], int x, int y)
{
int total = 1;
int i = 0;
int j = 0;
//判断一行中元素个数是否为3
for (i = y+1; i <= COL - 1; i++)
{
if (board[x][i] == board[x][y])
{
total++;
}
}
for (i = y - 1; i >= 0; i--)
{
if (board[x][i] == board[x][y])
{
total++;
}
}
if (total == ROW)
{
return board[x][y];
}
//判断一列
total = 1;
for (i = x + 1; i <= ROW - 1; i++)
{
if (board[i][y] == board[x][y])
total++;
}
for (i = x - 1; i >= 0; i--)
{
if (board[i][y] == board[x][y])
total++;
}
if (total == COL)
{
return board[x][y];
}
total = 1;
//判断对角线
for (i = x - 1, j = y + 1; i >= 0 && j <= COL - 1; i--, j++)
{
if (board[i][j] == board[x][y])
total++;
}
for (i = x +1, j = y -1; i<=ROW-1&&j>=0; i++,j--)
{
if (board[i][j] == board[x][y])
total++;
}
if (total == COL)
{
return board[x][y];
}
total = 1;
for (i = x - 1, j = y - 1; i >= 0 && j >= 0; i--, j--)
{
if (board[i][j] == board[x][y])
total++;
}
for (i = x + 1, j = x + 1; i <= ROW - 1 && j <= COL - 1; i++, j++)
{
if (board[i][j] == board[x][y])
total++;
}
if (total == COL)
{
return board[x][y];
}
int rec = IsFull(board,ROW,COL);
if (rec == 1)
{
return 'E';
}
else
{
return 'C';
}
}
此处便是判断一行,我们将我们下棋的那个点的坐标作为参数传值进去,同时判断其左边与右边与其相同的元素个数,定义一个变量total,为了计算相同元素个数,我们已经下的那一次棋也算为相同元素个数之1,因此其赋值为1,
判断列数则与其上面相同,如下所示
判断对角线时,对角线有两种情况,我们需要分别来考虑,第一种情况是这样的
还有一种情况:
因此,我们也就可以写出对角线的相同元素和的代码,但别忘了还有判断平局与继续的IsFull函数,把他直接跟着后面就可以了
这样的话就顺利写出来了,现在来看一下五子棋的效果
本文完。