准备工作
在写三子棋之前,首先考虑到写的函数块会比较多,为了避免代码出现杂糅现象,我决定将三子棋分为三个文件进行书写,分别是:
源文件index.c->存放主函数的地方,用于测试基本的游戏逻辑,使用游戏函数.
源文件game.c->里面放着封装好了可以实现特定功能的游戏代码.
头文件 game.h->用来存放游戏功能逻辑函数的头文件,在index.c头部引入
下棋基础逻辑index.c
程序运行起来后,看到的是游戏菜单,在游戏菜单里面可以选择1.开始游戏.和0.退出游戏两个选项
同时,我不想因为一局游戏结束而直接结束程序,而是希望程序能重新打印菜单,这样就能选择是否继续游戏.
打印菜单代码如下:
void menu()
{
printf("******************************\n");
printf("******* 1.play 0.exit *******\n");
printf("******************************\n");
}
因为在布尔值中,1为成立,0为不成立,我们可以通过用户输入的是1/0来作为循环条件,来判断是否进入下一次游戏,当玩输入1后,进入三子棋游戏,此时编写函数game();
主函数部分实现代码如下:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("三子棋\n");
game();
break;
case 2:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
game()函数的编写思路,以及game.c里面的函数编写:
1.创建数组board[ROW][COL],来存放棋盘中的数据.
2.编写初始化棋盘的函数
初始化棋盘的函数如下:
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] = ' ';
}
}
}
3.初始化棋盘后进行第一次打印棋盘,编写打印棋盘的函数
打印棋盘的函数如下:
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//打印分割信息
if (i < row - 1)
{
for (int j = 0; j < col; j++)
{
printf("---", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
4.开始下棋:
玩家先手,输入想要落子的坐标完成下棋,这里需要编写玩家移动棋子的函数.
玩家落子实现代码如下:
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");
}
}
}
玩家下棋完成后打印棋盘.
电脑下棋,输入想要落子的坐标完成下棋,这里需要编写电脑移动棋子的函数取余,来得到
电脑落子实现代码如下:
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
int x = 0;
int y = 0;
while (1)
{
x = rand() % row;//rand生成的随机数是0-32767
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
使用电脑下棋的时候,通过rand()函数求得随机值再取余,可得到 0-2的随机数,由这个随机数可以确定在哪里落子.
然而,调用rand()随机数前,需要提前给定一个srand()随机种子,如果srand里面的参数是不断变化的,那么每次调用rand()所得到的随机数每次也都不一样.
我这里提前在主函数中写入srand()并以时间作为参数,因为时间是不断变化的,那每次调用srand()的时候随机种子也就不一样,我这里选择在主函数里面写入srand
代码如下
srand((unsigned int)time(NULL));//设置随机数的生成起点
如上代码中使用了srand和time函数,需要调用头文件,我这里选择把头文件写入game.h中,后续可以再把game.h引入到index.c中.
game.h里的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
电脑下棋完成后打印棋盘.
5.结束下棋:判断输赢:
判断输赢的代码如下:
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][1];
}
}
//列
int j = 0;
for (j = 0; j < col;j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[0][j] != ' ')
{
return board[0][j];
}
}
//对角线
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[0][2];
}
//没有赢,判断平局
if (IsFull(board, row, col))
{
return 'Q';
}
//游戏继续
return 'C';
}
结束下棋的判断条件有三个1.玩家赢了 2.电脑赢了 3.棋盘中的棋子下满的情况下没有人赢,即平局.
因为需要判断棋盘是否已经放满了棋子,这里需要编写一个函数来判断:
int IsFull(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
那怎么判断怎样算是结束游戏,怎么算是继续游戏呢,我选择再额外设置一个游戏继续的选项.
把判断输赢的游戏逻辑编写为一个函数,在玩家落子的时候判断一下,在电脑落子的时候再判断一下,用ret来接受判断输赢函数的返回值.
返回值可以是如下几个字符来代表:
'*'->玩家赢了 '#'电脑赢了 'C'游戏继续 最后一个情况就是平局
只有当返回值为C的时候游戏才会继续,返回值不为C的时候就结束游戏.
以上为game()函数中需要用到的全部自定义函数,接下来需要在定义game.h头文件,并把头文件在index.c中引入.
综上所述game()函数的编写思路,可以得出:
game()函数的代码如下:
void game()
{
char ret = 0;
char board[ROW][COL] = {0};
InitBoard(board,ROW,COL);
DisplayBoard(board, ROW, COL);
//下棋
while (1)
{
PlayerMove(board, ROW, COL);
ret=IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
ret = IsWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
DisplayBoard(board, ROW, COL);
}
if (ret = '*')
{
printf("玩家赢了\n");
}
else if (ret = '#')
{
printf("电脑赢了\n");
}
else
{
printf("平局\n");
}
DisplayBoard(board, ROW, COL);
}
game.h内容如下,最好是写好注释,方便辨认代码的功能
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.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 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);
优化细节:
1.因为game.c和index.c中都需要重复写入#include<stdio.h>等相似的头文件,而且都要引入game.h所以我把这些需要用到的相似的头文件都放在game.h里面.
2.如题,我们这次选择制作的是三子棋小游戏,如果未来把这个小游戏改成五子棋,甚至十字棋的时候,还要更改行数和列数,比较麻烦,所以在game,h中自定义常量ROW和COL都为5.如果未来想要改为五子棋(举例)的话,直接更改自定义常量的值就行了