目录
在上一篇文章中,我们介绍了C语言中的数组。今天,我们将结合我们之前所学的所有内容,制作出我们的第二个项目——三子棋小游戏。
一、原理及问题分析
三子棋是一款经典的双人对战游戏,玩家通过轮流在3x3的棋盘上落子,先连成一条线(横、竖、对角线)的一方获胜。在这个游戏中,玩家与电脑对战,电脑采用随机落子策略。游戏的整体流程如下:
- 初始化棋盘:创建3x3的二维数组,初始化为空格。
- 打印棋盘:动态显示当前棋盘状态,方便玩家观察。
- 玩家下棋:输入坐标并校验合法性(范围、是否被占用)。
- 电脑下棋:随机生成合法坐标。
- 判断游戏状态:每次落子后检查是否达成胜利条件或平局。
- 循环游戏:单局结束后可选择继续游玩或退出。
关键问题:
- 模块化设计:将函数声明、定义、测试逻辑拆分到不同文件(test.c、game.h、game.c),提高代码可维护性。
- 通用性优化:使用符号常量(
ROW
和COL
)定义棋盘大小,便于后期扩展修改,不用一个一个改麻烦。 - 输赢判断:需遍历行、列、对角线,检查是否连成三子。
二、代码实现
2.1 test.c文件中
2.1.1 游戏框架搭建
游戏主流程使用do-while
循环,结合switch
语句处理玩家选择(这一点我们在之前的拆数字小游戏中已经实现过了):
int main()
{
int input=0;
srand((unsigned)time(NULL));
menu();
int count = 0;
do
{
if (count == 0)
{
printf("请输入(选择1开始游戏,选择0退出游戏):>");
}
else
{
printf("是否进行下一局(选1继续游玩,选0退出游戏):>");
}
scanf("%d", &input);
switch (input)//游戏整体流程
{
case 1:
printf("开始游戏\n");
game();
count++;
break;
case 0:
printf("已退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while(input);
}
2.2.2 游戏菜单函数实现
游戏菜单我们在猜数字小游戏中已经介绍过,就是有限次的printf打印:
void menu()//菜单函数
{
printf("*****************************\n");
printf("*****************************\n");
printf("*********三子棋游戏**********\n");
printf("*****************************\n");
printf("*********1.开始游戏**********\n");
printf("*********0.退出游戏**********\n");
printf("********版本:Beta1.0*********\n");
printf("********作者:Yang210*********\n");
printf("*****************************\n");
printf("*****************************\n");
}
2.2.3 game()函数部分
game()函数是游戏的主体逻辑部分,里面放着用来实现三子棋小游戏各种功能的函数,但是这些函数不在game()函数内声明和实现,我们只是在game()函数中调用这些函数,而具体的每一个函数实现在game.c文件中,而声明在game.h文件中,如此提高代码的可读性。
void game()//游戏主体逻辑
{
char arr[ROW][COL];
int choose = 0;
char flag = 0;
start(arr, ROW, COL);//棋盘初始化函数
print_board(arr, ROW, COL);//棋盘打印函数
while (1)
{
player(arr, ROW, COL);//玩家下棋函数
print_board(arr, ROW, COL);
flag=is_win(arr, ROW, COL);//判断输赢函数
if (flag != 'C')
{
break;
}
computer(arr, ROW, COL);//电脑下棋函数
Sleep(3000);
print_board(arr, ROW, COL);
flag = is_win(arr, ROW, COL);//判断输赢函数
if (flag != 'C')
{
break;
}
}
if (flag == '*')
{
printf("玩家获胜!\n");
}
else if (flag == '#')
{
printf("电脑获胜!\n");
}
else
{
printf("此局平局!\n");
}
print_board(arr, ROW, COL);
}
2.2.4 test.c文件内全部代码呈现
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()//菜单函数
{
printf("*****************************\n");
printf("*****************************\n");
printf("*********三子棋游戏**********\n");
printf("*****************************\n");
printf("*********1.开始游戏**********\n");
printf("*********0.退出游戏**********\n");
printf("********版本:Beta1.0*********\n");
printf("********作者:Yang210*********\n");
printf("*****************************\n");
printf("*****************************\n");
}
void game()//游戏主体逻辑
{
char arr[ROW][COL];
int choose = 0;
char flag = 0;
start(arr, ROW, COL);//棋盘初始化函数
print_board(arr, ROW, COL);//棋盘打印函数
while (1)
{
player(arr, ROW, COL);//玩家下棋函数
print_board(arr, ROW, COL);
flag=is_win(arr, ROW, COL);//判断输赢函数
if (flag != 'C')
{
break;
}
computer(arr, ROW, COL);//电脑下棋函数
Sleep(3000);
print_board(arr, ROW, COL);
flag = is_win(arr, ROW, COL);//判断输赢函数
if (flag != 'C')
{
break;
}
}
if (flag == '*')
{
printf("玩家获胜!\n");
}
else if (flag == '#')
{
printf("电脑获胜!\n");
}
else
{
printf("此局平局!\n");
}
print_board(arr, ROW, COL);
}
int main()
{
int input=0;
srand((unsigned)time(NULL));\\初始化随机数,在猜数字小游戏介绍过
menu();
int count = 0;
do
{
if (count == 0)
{
printf("请输入(选择1开始游戏,选择0退出游戏):>");
}
else
{
printf("是否进行下一局(选1继续游玩,选0退出游戏):>");
}
scanf("%d", &input);
switch (input)//游戏整体流程
{
case 1:
printf("开始游戏\n");
game();
count++;
break;
case 0:
printf("已退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while(input);
}
2.2 game.c文件中
2.2.1 棋盘初始化与打印
棋盘初始化:
// game.c
void start(char arr[ROW][COL], int row, int col)//1.棋盘初始化函数实现
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
arr[i][j] = ' ';
}
}
}
动态打印棋盘:
void print_board(char arr[ROW][COL], int row, int col)//2.棋盘打印函数实现
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)//打印数据行
{
for (j = 0;j < col;j++)
{
printf(" %c ", arr[i][j]);
if (j < col - 1)
{
printf("|");
}
}
putchar('\n');
if (i < row - 1) //打印分割行
{
for (j = 0;j < col;j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
}
putchar('\n');
}
}
2.2.2 玩家与电脑下棋逻辑
玩家下棋:
void player(char arr[ROW][COL], int row, int col)//3.玩家下棋函数实现
{
int x = 0;
int y = 0;
printf("当前回合玩家下棋(玩家代表*)\n");
while (1)
{
printf("请输入坐标(如:2 3表示第二行第三列):>");
scanf("%d %d", &x, &y);
if (x > row || y > col)
{
printf("坐标非法,请重新输入\n");
}
else if (x >= 1 && y >= 1 && x <= row && y <= col)
{
if (arr[x-1][y-1] == ' ')
{
arr[x-1][y-1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入\n");
}
}
}
}
电脑下棋:
void computer(char arr[ROW][COL], int row, int col)//4.电脑下棋函数实现
{
printf("当前回合电脑下棋\n");
while (1)
{
int x = rand() % 3;//若row为3,则余数为0-2,这是一个重要的技巧
int y = rand() % 3;
if (arr[x][y] == ' ')
{
arr[x][y] = '#';
break;
}
}
}
2.2.3 输赢判断与游戏状态
判断胜负(关于这部分只限于三子棋情况,还不够通用,后期可以参照打印棋盘的思路优化):
char is_win(char arr[ROW][COL], int row, int col)//5.判断输赢函数实现
{
int i = 0;
for (i = 0;i < row;i++)//判断行
{
if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ')
{
return arr[i][1];
}
}
for (i = 0;i < col;i++)//判断列
{
if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ')
{
return arr[1][i];
}
}
if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ')//判断主对角线
{
return arr[1][1];
}
else if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')//判断副对角线
{
return arr[1][1];
}
int ret= is_Full(arr, row, col);//判断是否平局的函数
if (ret == 1)
{
return 'C';
}
else
{
return 'S';
}
}
判断棋盘是否满了(是否平局):
int is_Full(char arr[ROW][COL], int row, int col)//6.判断棋盘是否满了函数实现
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (arr[i][j] == ' ')
{
return 1;
}
}
}
return 0;
}
2.2.4 gmae.c文件内全部代码呈现
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void start(char arr[ROW][COL], int row, int col)//1.棋盘初始化函数实现
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
arr[i][j] = ' ';
}
}
}
void print_board(char arr[ROW][COL], int row, int col)//2.棋盘打印函数实现
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
printf(" %c ", arr[i][j]);
if (j < col - 1)
{
printf("|");
}
}
putchar('\n');
if (i < row - 1)
{
for (j = 0;j < col;j++)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
}
putchar('\n');
}
}
void player(char arr[ROW][COL], int row, int col)//3.玩家下棋函数实现
{
int x = 0;
int y = 0;
printf("当前回合玩家下棋(玩家代表*)\n");
while (1)
{
printf("请输入坐标(如:2 3表示第二行第三列):>");
scanf("%d %d", &x, &y);
if (x > row || y > col)
{
printf("坐标非法,请重新输入\n");
}
else if (x >= 1 && y >= 1 && x <= row && y <= col)
{
if (arr[x-1][y-1] == ' ')
{
arr[x-1][y-1] = '*';
break;
}
else
{
printf("坐标被占用,请重新输入\n");
}
}
}
}
void computer(char arr[ROW][COL], int row, int col)//4.电脑下棋函数实现
{
printf("当前回合电脑下棋\n");
while (1)
{
int x = rand() % 3;
int y = rand() % 3;
if (arr[x][y] == ' ')
{
arr[x][y] = '#';
break;
}
}
}
char is_win(char arr[ROW][COL], int row, int col)//5.判断输赢函数实现
{
int i = 0;
for (i = 0;i < row;i++)
{
if (arr[i][0] == arr[i][1] && arr[i][1] == arr[i][2] && arr[i][1] != ' ')
{
return arr[i][1];
}
}
for (i = 0;i < col;i++)
{
if (arr[0][i] == arr[1][i] && arr[1][i] == arr[2][i] && arr[1][i] != ' ')
{
return arr[1][i];
}
}
if (arr[0][0] == arr[1][1] && arr[1][1] == arr[2][2] && arr[1][1] != ' ')
{
return arr[1][1];
}
else if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')
{
return arr[1][1];
}
int ret= is_Full(arr, row, col);
if (ret == 1)
{
return 'C';
}
else
{
return 'S';
}
}
int is_Full(char arr[ROW][COL], int row, int col)//6.判断棋盘是否满了函数实现
{
int i = 0;
int j = 0;
for (i = 0;i < row;i++)
{
for (j = 0;j < col;j++)
{
if (arr[i][j] == ' ')
{
return 1;
}
}
}
return 0;
}
2.3 game.h文件中
头文件用于声明函数和常量:
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 3
#define COL 3
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void start(char arr[ROW][COL], int row, int col);//1.棋盘初始化函数声明
void print_board(char arr[ROW][COL], int row, int col);//2.棋盘打印函数声明
void player(char arr[ROW][COL], int row, int col);//3.玩家下棋函数声明
void computer(char arr[ROW][COL], int row, int col);//4.电脑下棋函数声明
char is_win(char arr[ROW][COL], int row, int col);//5.判断输赢函数声明
int is_Full(char arr[ROW][COL], int row, int col);//6.判断棋盘是否满了函数声明
注意:虽然这部分文件我们放到了这篇文章中的最后一部分讲,但是game.h文件要先引用到test.c和game.c两个文件的开始部分中去。因为是自己写的头文件,所以引用方式为双引号 #include "game.h"。game.h文件并不是最后才写的,而是我们在实现game.c中的函数的功能时顺便,同时进行的。
总结
后期优化方向:
- 提升电脑AI:当前AI是随机下棋,比较笨,后期有能力了我们可以优化算法,使其能主动拦截玩家或寻找获胜机会。
- 扩展为N子棋(如五子棋),需优化输赢判断逻辑的通用性。
完整的代码已上传至三子棋小游戏 · 暮鹤筠/C language - 码云 。
最后,送给大家一句话“编程如对弈,步步为营,方能制胜。”下一篇文章,我们将继续运用我们现在所学的知识实现我们的第三个项目——扫雷小游戏,敬请期待!
作者其他文章链接:
[C语言初阶]猜数字小游戏实现_c语言猜数字游戏流程图-CSDN博客
Gitee详细使用教程_gitee使用教程-CSDN博客
[C语言初阶]函数-CSDN博客
[C语言初阶]递归-CSDN博客
[C语言初阶]数组-CSDN博客