目录
事先准备:
三子棋的玩法如下:
我们在写一些重要的代码时,最好把自定义函数的声明、定义以及整个程序的实现,分别放在不同的文件里面。这样写不仅会让你的逻辑更清晰、更容易调试代码还便于后续对整个代码的保密操作。
游戏的实现逻辑:
- test.c —— 测试游戏
- game.h —— 游戏函数的声明
- game.c —— 游戏函数的实现
头文件的作用:
#include<stdlib.h>
#include<time.h>
#include<stdio.h>
使用宏定义:
#define ROW 3 #define COL 3
使用宏定义的原因:
1.推高代码可读性,后续代码中直接用变量代替,参数修改量大大减少。
2.提高扩展性,如果将来要修改棋盘尺寸,代码修改会很方便。
基本思路:
1.菜单界面选择/退出游戏
2.创建棋盘并初始化(棋盘为全空格)
char board[ROW][COL];//存放玩家或者电脑的落子情况
char ret = 0;3.打印棋盘
循环{
玩家下棋
打印棋盘
判断玩家输赢:*
电脑下棋
打印棋盘
判断电脑输赢:#
}
【平局:Q(特殊情况)游戏继续:C】
以此循环第1条功能
菜单界面
void menu()
{
printf("********************\n");
printf("***** 1.play *******\n");
printf("***** 0.exit *******\n");
printf("********************\n");
}
测试的逻辑部分:
void game()
{
//存放玩家或者电脑的落子情况
char board[ROW][COL];
char ret = 0;
// 初始化一下棋盘为全空格
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
while (1) {
//玩家下棋
player_move(board, ROW, COL);
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
computer_move(board,ROW,COL);
print_board(board, ROW, COL);
//判断输赢
ret = is_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '#')
printf("电脑赢了\n");
else if (ret == '*')
printf("玩家赢了\n");
else if (ret == 'Q')
printf("平局\n");
print_board(board, ROW, COL);
}
void test()
{
srand((unsigned int)time(NULL));
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()
{
test();
return 0;
}
这部分代码之所以放外面是为了避免代码冗余
注意:
srand((unsigned int)time());srand()函数需要一个time函数的返回值作为参数,但time()函数的返回值是time_t,srand又需要unsigned int类型。
1.初始化棋盘为全空格
游戏开始之前要进行初始化,先建立一个空的棋盘雏形,这里用二维数组来替代棋盘上的位置,空格符号来代表此时棋盘上没有棋子落子。此时就是一个空棋盘,每次开始之前都要置空。
使用“InitBoard()”函数来进行棋盘的初始化操作。
char board[ROW][COL];
init_board(board, ROW, COL);
void init_board(char board[ROW][COL], int row, int col);
函数的实现如下:
void init_board(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.打印棋盘
打印效果:
可以看出上面打印的棋盘是有归律的
_ _ _ | _ _ _ | _ _ _
这是它的规律,而第3列是没有|,所以当j<col-1时才打印|
可看到最后一行没有_ _ _可说明当i<row-1时才打印_ _ _
测试和头文件部分:
print_board(board, ROW, COL);
void print_board(char board[ROW][COL],int row, int col);
代码实现:
void print_board(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]);
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 (j = 0; j < col; j++)
{
printf("---");
if(j<col-1)
printf("|");
}
printf("\n");
}
}
}
3.玩家下棋
三点基本要求
- 接收玩家所输入的地址位置(我们所判断的数组元素应该是玩家输入值-1)
- 判断玩家所输入的地址所在是否已有落子
- 将玩家的棋子落入对应位置
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
这样写会把棋盘写死
还有就是不方便扩展棋盘,假设变成5x5的棋盘修改量巨大
测试和头文件部分:
player_move(board, ROW, COL);
void player_move(char board[ROW][COL], int row,int col);
代码实现:
void player_move(char board[ROW][COL], int row, int col)
{
printf("玩家下棋\n");
while (1) {
printf("请输入要下棋的坐标:>");
int x = 0, y = 0;
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");
}
}
}
运行结果:
4.电脑下棋
随机生存坐标,只要坐标没被占用,就下棋
srand()函数只用调用一次即可
int x = rand() % row;
int y = rand() % col;rand()函数生成的自然数是0-32767之间的数,若rand()%10那么就是0-9之间的数
也需要判断电脑所下的地址是否被占用,以及将棋子落下,这点和玩家所需的要求一致。
测试和头文件部分:
computer_move(board,ROW,COL);
void computer_move(char board[ROW][COL], int row, int col);
代码实现:
void computer_move(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
while (1)//反复下
{
int x = rand() % row;//0-2范围
int y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
运行结果:
\
5.判断棋盘满的条件
使用is_full()函数来实现这一功能。
在这一函数中,需要实现以下几个逻辑:
判断游戏结束的四种状态:
玩家赢 -- 返回'*'
电脑赢 -- 返回'#'
平局 -- 返回'Q'
游戏继续 -- 返回'C'
前面的ret是用于返回的一个值,若ret != 'C' ,则游戏继续 ,用char ret = 0定义,
static 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;
}
判断平局条件
is_full这个函数只是为了支持is_win函数的,只是在is_win函数内部使用,没必要在头文件中声明
运行结果:
游戏继续
6.判断胜利条件
实现判断胜利的函数是is_win(), 前面玩家和电脑赢的为啥会返回一个'*'和'#', 以下举例解释:
其实就是3种情况,就是判断行、列和对角线
测试和头文件部分:
char board[ROW][COL];
char ret = 0;
ret = is_win(board, ROW, COL);
代码实现:
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][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[0][0] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ')
{
return board[2][0];
}
//平局
if (is_full(board, row, col) == 1)
{
return 'Q';
}
//继续
//没有玩家或者电脑赢,也没有平局,游戏继续
return 'C';
}