欢迎来到程序员的快乐!
目录
一、游戏规则:
二、编程准备和分析:
三、游戏菜单界面打印
四、创建字符数组存储数据
五、初始化棋盘
六、棋盘打印
七、玩家1,玩家2轮流下棋
八、判断游戏胜出、平局、继续
九、、游戏测试
十、总体代码:
两位玩家进行博弈,棋盘为3*3,当一方玩家的3个棋子连成一条线的时候胜利。如果棋盘上没有落子的位置且没有玩家胜出的时候判定为平局。
1、准备
game.h:游戏相关的函数声明,头文件包含,符号声明。
text.c:测试游戏的逻辑。
game.c:游戏相关函数的实现。
2、分析:
可能出现的四种游戏状态:玩家1胜、出玩家2胜、平局、游戏继续。
可能不是很熟悉的语法:#define 定义的标识符常量。
1、自己动手来给三子棋写一个menu()函数实现菜单界面的打印。
void menu()
{
printf("----------- Tic-Tac-Toe -----------\n");
printf("*********** 1.开始游戏 ************\n");
printf("*********** 0.退出游戏 ************\n");
printf("-----------------------------------\n");
}
2、在主函数中使用do...while循环语句和switch多分支语句,选择不同选项出现不同效果,我们暂时输出一句话来展现不同的效果。input是我们定义的一个变量,并把它当做循环和分支的条件表达式,当input = 1时开始游戏,input = 0 时退出游戏并且退出循环,input= 其它值时打印出一句错误提醒!
int main()
{
int input = 0;
do
{
menu();
printf("请输入选项:>\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("游戏开始!\n");
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选项错误,请重新输入:");
}
printf("\n");
} while (input);
return 0;
}
3、效果展示:
1、首先,我们写一个game()函数来实现游戏的一些逻辑!游戏界面打印好后,就要在game()函数中(在上述case 1:调用game()这个函数!)搭建游戏场景。第一步我们创建一个字符数组来存储数据,因为三子棋的棋盘是3*3,所以我们可以写成:
char board[3][3];
这样写是没有问题的,但是对于程序员来说,我们要考虑到代码的耦合度,假设我们突然想要把棋盘改成5*5或者更大的棋盘,就要改动大量的代码,这时候我们的效率就大大降低,而且更容易出错!综上所述,我们可以对代码进行这样的优化:
2、在game.h中定义标识符常量行(ROW)和列(COL)
//符号的定义
#define ROW 3//表示行
#define COL 3//表示列
注意:在Text.c中写上头文件#include "game.h"
3、这时我们就可以写成这样:
char board[ROW][COL];
到这里我们就完成了数组的创建!
实际上就是对数组的初始化,在game()函数中调用一个用来初始化的函数initboard(board, ROW, COL);,传入的3个参数分别是二维数组、行、列。
把这个和游戏相关的函数写在game.c中:
//初始化棋盘
void initboard (char ch[ROW][COL],int row,int col)
{
//遍历二维数组
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
ch[i][j] = ' ';
}
}
}
在game()函数中调用一个用来打印棋盘的函数printfboard(board, ROW, COL);传入的3个参数分别是二维数组、行、列。
把这个和游戏相关的函数写在game.c中,小张在这里介绍两种写法:
1、写死(简单,容易理解,灵活度低,改动麻烦)
把棋盘它分成两个部分来分析,第一部分是 (" %c | %c | %c "),第二部分是(“---|---|---”)三个%c对应的结果我们直接写出来即可。为了控制最后一行不打印分割行,写了个if()。
void printfboard(char board[ROW][COL],int row,int col)
{
int i = 0;
for (i = 0; i|
2、复杂一点点(方便后续根据要求修改代码)
这段代码看起来比上面的代码复杂一些,实际上也是很容易理解的。我把棋盘的“ %c ”和‘|’看成一个部分;“---”和‘|’看成一个部分。
board[i][j],第i行,第j列
‘|’分隔符不是总打印的,和上面的逻辑是一样的,写了个if(row- 1)......来控制。
void printfboard(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]);
if (j < col - 1)
printf("|");
}
printf("\n");
if (i < row - 1)
for (int i = 0; i < col; i++)
{
printf("---");
if (i < row - 1)
printf("|");
}
printf("\n");
}
}
效果图(第一种写法的效果图是一样的):
假设我们想要把棋盘改成10*10,只需要更改 标识符常量行(ROW)和列(COL)即可:
#define ROW 10//表示行
#define COL 10//表示列
效果图:
在game()函数中调用一个用来玩家1落子的函数play1move(board, ROW, COL);传入的3个参数分别是二维数组、行、列。
调用一个用来玩家2落子的函数play2move(board, ROW, COL);传入的3个参数分别是二维数组、行、列。
把这个和游戏相关的函数写在game.c中:
//玩家1下棋
void play1move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家1走:>");
while (1)
{
printf("请输入棋子坐标:");
scanf("%d %d", &x, &y);
//判断棋子是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//落子
//判断要下的坐标是否被占用
//这里要注意,对下标进行访问是原坐标-1
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
printf("坐标已经被占用!");
}
else
printf("坐标错误,请重新输入!");
}
}
//玩家2下棋
void play2move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家2走:>");
while (1)
{
printf("请输入棋子坐标:");
scanf("%d %d", &x, &y);
//判断棋子是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//落子
//判断要下的坐标是否被占用
//这里要注意,对下标进行访问是原坐标-1
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '#';
break;
}
else
printf("坐标已经被占用!");
}
else
printf("坐标错误,请重新输入!");
}
}
玩家1和2落子的逻辑是一样的,通过落子的坐标判断落子是否符合游戏规则,是否在棋盘内,落子坐标是否已经有棋子。这里要注意:控制台输入的x y坐标是数学上的坐标,但是判断坐标是否被占用的时候我们访问的是数组的下标,所以我们写成了(board[x - 1][y - 1]这样的形式!
效果图:
可能出现的四种游戏状态:规定每种状态下的返回值
玩家1胜:>'*'
出玩家2胜:>'#'
平局:>'A'
游戏继续:>'B'
在game()函数中调用一个用来判断胜负落子的函数iswin(board, ROW, COL);传入的3个参数分别是二维数组、行、列。
游戏结果的判断分为几个方向:
1、三行有一样的棋子
因为胜出一方的返回值类型和棋子的字符是一样的,所以返回胜出行的任意一个字符即可!
2、三列有一样的棋子
因为胜出一方的返回值类型和棋子的字符是一样的,所以返回胜出列的任意一个字符即可!
3、对角线有一样的棋子
对角线的两种情况写死即可!
4、都不满足游戏继续 retun ‘B’;
5、返回1平局,返回0游戏继续,如果棋盘满了且没有连成一条线则平局
isswin()函数中调用isfull()函数来判断棋盘是否已满,满了返回1,没满返回0
int isfull(char ch[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for(i = 0; i|
/判断游戏输赢
char iswin(char ch[ROW][COL], int row, int col)
{
//当三个棋子连成一条线的时候,游戏胜利
//判断三行有一样的棋子
int i = 0;
for (i = 0; i|
1、玩家1胜利:
2、玩家2胜利:
3、平局:
1、game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
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;//棋盘满了
}
//初始化棋盘
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 printfboard(char board[ROW][COL],int row,int col)
//{
// int i = 0;
// for (i = 0; i");
while (1)
{
printf("请输入棋子坐标:");
scanf("%d %d", &x, &y);
//判断棋子是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//落子
//判断要下的坐标是否被占用
//这里要注意,对下标进行访问是原坐标-1
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
printf("坐标已经被占用!");
}
else
printf("坐标错误,请重新输入!");
}
}
//玩家2下棋
void play2move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家2走:>");
while (1)
{
printf("请输入棋子坐标:");
scanf("%d %d", &x, &y);
//判断棋子是否合法
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//落子
//判断要下的坐标是否被占用
//这里要注意,对下标进行访问是原坐标-1
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '#';
break;
}
else
printf("坐标已经被占用!");
}
else
printf("坐标错误,请重新输入!");
}
}
//判断游戏输赢
char iswin(char ch[ROW][COL], int row, int col)
{
//当三个棋子连成一条线的时候,游戏胜利
//判断三行有一样的棋子
int i = 0;
for (i = 0; i < row; i++)
{
if (ch[i][0] == ch[i][1] && ch[i][1] == ch[i][2] && ch[i][1] != ' ')
{
return ch[i][1];
}
}
//判断三列有一样的棋子
int j = 0;
for (j = 0; j < col; j++)
{
if (ch[0][j] == ch[1][j] && ch[1][j] == ch[2][j] && ch[1][j] != ' ')
{
return ch[1][j];
}
}
//判断对角线
if (ch[0][0] == ch[1][1] && ch[1][1] == ch[2][2] && ch[1][1] != ' ')
{
return ch[1][1];
}
if (ch[0][2] == ch[1][1] && ch[1][1] == ch[2][0] && ch[1][1] != ' ')
{
return ch[1][1];
}
//判断是否平局,棋盘是否已经满了
//返回1平局,返回0继续游戏
int ret = isfull(ch, row, col);
if (ret == 1)
{
return 'A';
}
//继续
return 'B';
}
2、game.h
#pragma once
//头文件
#include
//符号的定义
#define ROW 3//表示行
#define COL 3//表示列
//函数的声明
//初始化棋盘的函数
void initboard(char board[ROW][COL], int row, int col);
//打印棋盘的函数
void printfboard(char board[ROW][COL], int row, int col);
//玩家1落子
void play1move(char board[ROW][COL], int row, int col);
//玩家2落子
void play2move(char board[ROW][COL], int row, int col);
//判断输赢
char iswin(char board[ROW][COL], int row, int col);
3、 text.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("----------- Tic-Tac-Toe -----------\n");
printf("*********** 1.开始游戏 ************\n");
printf("*********** 0.退出游戏 ************\n");
printf("-----------------------------------\n");
}
void game()
{
//字符数组存储数据
char board[ROW][COL];
//初始化棋盘
initboard(board, ROW, COL);
//打印棋盘
printfboard(board,ROW,COL);
char ret = 0;
while (1)
{
//玩家1走
play1move(board, ROW, COL);
printfboard(board, ROW, COL);
ret = iswin(board, ROW, COL);
if (ret != 'B')
break;
//玩家2走
play2move(board, ROW, COL);
printfboard(board, ROW, COL);
ret = iswin(board, ROW, COL);
if (ret != 'B')
break;
}
if (ret == '*')
{
printf("玩家1胜利!\n");
}
else if (ret == '#')
{
printf("玩家2胜利!\n");
}
else
{
printf("平局!\n");
}
printfboard(board, ROW, COL);
}
int main()
{
int input = 0;
do
{
menu();
printf("请输入选项:>\n");
scanf("%d", &input);
printf("\n");
switch (input)
{
case 1:
game();
/*printf("游戏开始!\n");*/
break;
case 0:
printf("退出游戏!\n");
break;
default:
printf("选项错误,请重新输入:");
}
printf("\n");
} while (input);
return 0;
}