hello!这里是敲代码的小董,很荣幸您阅读此文,期待您的评论指点和关注,欢迎欢迎~~
✨✨个人主页:敲代码的小董
💗💗系列专栏:C语言
目录
一、引言
1.三子棋游戏概述
三子棋是一种简单而有趣的棋类游戏。游戏在3×3的棋盘上开展,双方分别执“X”和“O”棋子轮流落子。落子过程中,只要一方在横、竖或对角线上成功将三个自己的棋子连成一线,便赢得游戏。若棋盘被棋子填满后,双方均未达成三子连线的情况,就判定为平局。
2.用C语言开发的意义
开发三子棋程序具有重要的学习意义。从C语言学习角度,它是巩固知识的有效途径。在编写过程中,像定义棋盘数组、运用循环控制下棋顺序、借助条件判断确定胜负等操作,能让学习者深入理解C语言的数组、循环、条件判断等语法知识,将零散的知识点整合成完整知识体系。
二、游戏设计思路
1.棋盘表示
在C语言中,使用二维数组是一种常见且直观的表示棋盘的方式。
例如:可以定义一个char类型的二维数组board[3][3]来表示三子棋的棋盘。数组的每一个元素代表棋盘上的一个格子。对于三子棋来说,棋盘是3×3的结构,所以二维数组的大小为3行3列。
char board[ROW][COL];
其中ROW代表行,COL代表列,这里使用了宏定义的方法定义数组易于修改,在后期可以更改棋盘的大小,语义明确。
#define ROW 3
#define COL 3
2.游戏流程
游戏开始前:
打印游戏菜单,调用棋盘初始化函数,将棋盘定义为初始状态,即所有格子都为空白。这一步确保游戏开始时棋盘是干净的,准备接受玩家和电脑的下棋操作。
游戏进行中:
①玩家回合
- 显示当前棋盘状态,通过调用打印棋盘函数,让玩家清楚的看到棋盘上棋子的分布情况。
- 调用玩家下棋函数,提示玩家输入下棋的位置(行和列)。
- 玩家输入后,进行输入合法性检查,包括坐标是否在棋盘范围内,所选位置是否为空等。如果输入不合法,提示玩家重新输入,直到输入合法为止。
- 一旦输入合法,将玩家的棋子放置在棋盘对应的位置上,然后检测是否有玩家获胜或平局的情况,通过调用判断输赢函数。如果游戏结束(玩家胜利或平局),则跳转到游戏结束的处理流程;如果游戏未结束,则进入电脑回合。
②电脑回合
- 在电脑下棋之前,显示当前棋盘状态,以便玩家了解状况。
- 调用电脑下棋函数,根据预定的算法确定电脑下棋的位置。
- 将电脑的棋子放在选定的位置上,然后检测是否有电脑获胜或者平局的情况,通过调用判断输赢函数。如果游戏介绍(电脑获胜或者平局),则跳转到游戏介绍的处理流程;如果游戏未结束,则进入下一个玩家回合。
游戏结束后:
①结果显示
根据判断输赢函数的结果,如果是玩家获胜,显示玩家获胜的消息;如果是电脑获胜,显示电脑获胜的消息;如果是平局,显示游戏平局的消息。
②菜单显示
游戏结束后,不论是玩家或者电脑获胜,都在次显示菜单,让玩家选择“重新开始”或者“退出游戏”。如果玩家选择重新开始,回到游戏开始前的初始化步骤;如果玩家选择退出游戏,则结束游戏流程。
三、代码实现前的准备
在本项目中使用了test.c(源文件)、game.c(源文件)和game.h(头文件)多个文件进行编程,将main()主函数、menu()打印游戏菜单函数、game()游戏函数、test()完成游戏相关逻辑等函数放入test.c源文件中,各种函数的声明、引用、宏等放入game.h头文件中,完成各个功能函数的定义放入game.c原文件中。
在C语言中使用多个.h(头文件)和.c(源文件)进行编程的好处:
- 模块化:将功能分开,便于理解和维护。
- 代码复用:容易在其他项目中使用已有的代码,减少代码冗余。
- 减少编译时间:修改部分代码是,只需要编译相关文件。
- 便于并行编译:提高编译速度。
四、代码实现(部分)
1.初始化函数
这个函数初始化一个类似棋盘的二维字符数组。函数接收数组、行数和列数作参数,然后用两个嵌套的循环遍历数组的每一行和每一列,把每个元素都设成空格,这样就完成了棋盘的初始化,让棋盘开始是空白状态。
//函数功能:初始化棋盘,将棋盘的每个位置设为空字符
//函数参数:
//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.打印棋盘函数
print_board函数用于以一种特定的格式打印棋盘。它接受一个二维字符数组`board`以及表示行数`row`和列数`col`的整数作为参数。在函数内部,通过嵌套的`for`循环来实现棋盘的打印。外层循环遍历每一行,对于每一行,内层循环负责打印该行中的每个字符元素,并且在非最后一列时打印分隔符`|`。每一行的元素打印完成后,如果不是最后一行,会再通过一个循环打印行与行之间的分隔线`---`(同样非最后一列时添加`|`),这样就可以清晰地将棋盘布局以直观的形式显示出来。
//函数功能:打印棋盘
//函数参数:
//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++) //外层循环控制行数
{
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.玩家下棋函数
函数player_move是三子棋游戏里玩家下棋的操作函数。函数里有个棋盘,棋盘用`board`表示,棋盘有行有列,行数和列数分别由`row`和`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.电脑下棋函数
computer_move函数的功能是实现电脑下棋的操作。函数接收一个表示棋盘的二维字符数组`board`,以及棋盘的行数`row`和列数`col`作为参数。首先它会输出提示语告知正在进行电脑下棋操作,然后进入一个无限循环,在循环中随机生成棋盘范围内的坐标,一旦找到棋盘上为空(以空格表示空位置)的坐标,就在该坐标处下电脑的棋子(用`#`表示),之后便跳出循环,整体实现了电脑在棋盘中随机选择空位置下棋的功能。
// 电脑下棋:随机生成坐标,只要坐标没有被占用,就下棋。
void computer_move(char board[ROW][COL], int row, int col)
{
printf("请等待,电脑下棋中:\n"); // 输出提示信息,表示电脑正在下棋
while (1) { // 进入无限循环,直到找到合适的下棋位置
int x = rand() % row; // 随机生成一个在棋盘行数范围内的整数作为横坐标x
int y = rand() % col; // 随机生成一个在棋盘列数范围内的整数作为纵坐标y
if (board[x][y] == ' ') // 判断当前坐标对应的棋盘位置是否为空(' '表示空)
{
board[x][y] = '#'; // 如果为空,则在该位置下电脑的棋子,这里用'#'表示电脑棋子
break; // 找到合适位置后,跳出循环
}
}
}
补充:C语言生成随机数
- 包含头文件:包含<stdlib.h>和<time.h>头文件,用于随机数相关操作。
- 设置种子:用srand((unsigned int)time(NULL)),根据当前时间设种子,确保每次运行程序生成不同随机数序列(只需要调用一次,根据具体情况,确定调用位置)。
- 生成随机数: rand函数生成0到RAND_MAX之间的随机数。
- 特定范围随机数:公式(rand()%(max - min + 1))+min`可生成min到max之间的随机数。
5.判断胜负函数
函数是用来判断一个棋盘游戏(如井字棋)是否有人获胜的。它检查每一行、每一列和两条对角线,看是否有连续的相同棋子。如果找到这样的情况,就返回那个棋子的类型,表示该玩家赢了。如果没有发现任何玩家获胜的情况,并且棋盘已经填满,那么函数会返回'Q',表示平局。如果既没有玩家获胜,棋盘也没有填满,函数则返回'C',意味着游戏还在进行中。
//判断输赢
//判断输赢的函数要告诉我:电脑赢了?玩家赢了?游戏继续?
//电脑赢:#
//玩家赢:*
//平 局: Q
//游戏继续: C
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0, j = 0, flag = 0; // 初始化行、列和标志变量
char temp; // 用于存储当前检查的棋子
// 判断任意一行是否有相同棋子
for (i = 0; i < row; i++)
{
temp = board[i][0]; // 获取当前行的第一个棋子
for (j = 0; j < col; j++)
{
if (board[i][j] == temp && temp != ' ') // 如果当前行的所有棋子都相同且不为空
{
flag = 1; // 设置标志为1,表示该行有相同棋子
}
else
{
flag = 0; // 否则重置标志为0
break; // 结束内层循环
}
}
if (flag == 1) // 如果标志为1,表示该行有相同棋子
{
return temp; // 返回该行的棋子类型
}
}
// 判断任意一列是否有相同棋子
for (j = 0; j < col; j++)
{
temp = board[0][j]; // 获取当前列的第一个棋子
for (i = 0; i < row; i++)
{
if (board[i][j] == temp && temp != ' ') // 如果当前列的所有棋子都相同且不为空
{
flag = 1; // 设置标志为1,表示该列有相同棋子
}
else
{
flag = 0; // 否则重置标志为0
break; // 结束内层循环
}
}
if (flag == 1) // 如果标志为1,表示该列有相同棋子
{
return temp; // 返回该列的棋子类型
}
}
// 判断对角线 '\'
temp = board[0][0]; // 获取左上角的棋子
for (i = 0; i < row; i++)
{
if (board[i][i] == temp && temp != ' ') // 如果主对角线上的所有棋子都相同且不为空
{
flag = 1; // 设置标志为1,表示主对角线有相同棋子
}
else
{
flag = 0; // 否则重置标志为0
break; // 结束循环
}
}
if (flag == 1) // 如果标志为1,表示主对角线有相同棋子
{
return temp; // 返回主对角线的棋子类型
}
// 判断对角线 '/'
temp = board[row - 1][0]; // 获取右上角的棋子
for (i = 0; i < row; i++)
{ // 如果副对角线上的所有棋子都相同且不为空
if (board[row - 1 - i][i] == temp && temp != ' ')
{
flag = 1; // 设置标志为1,表示副对角线有相同棋子
}
else
{
flag = 0; // 否则重置标志为0
break; // 结束循环
}
}
if (flag == 1) // 如果标志为1,表示副对角线有相同棋子
{
return temp; // 返回副对角线的棋子类型
}
// 判断是否平局
if (is_full(board, row, col) == 1) // 调用is_full函数判断棋盘是否已满
{
return 'Q'; // 如果棋盘已满,返回'Q'表示平局
}
// 继续游戏(没有玩家赢,电脑赢或平局)
return 'C'; // 返回'C'表示游戏继续
}
五、完整代码
1.test.c
#include"game.h"
void menu()
{
printf("***********************\n");
printf("***** 1. play *****\n");
printf("***** 0. eixt *****\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);
Sleep(0);
system("cls");
print_board(board, ROW, COL);
//判断输赢
ret= is_win(board, ROW, COL);
if (ret!='C')
{
break;
}
//电脑下棋
computer_move(board, ROW, COL);
Sleep(2000);
system("cls");
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");
Sleep(2000);
system("cls");
}
//完成游戏相关逻辑
void test()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
Sleep(0);
system("cls");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
2.game.c
#include "game.h"
//函数功能:初始化棋盘,将棋盘的每个位置设为空字符
//函数参数:
//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] = ' '; // 将棋盘当前位置设置为空字符,表示未被占用
}
}
//函数功能:打印棋盘
//函数参数:
//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++) //外层循环控制行数
{
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");
}
}
}
//玩家下棋
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"); // 如果输入的坐标不在棋盘范围内,提示坐标非法
}
}
}
// 电脑下棋:随机生成坐标,只要坐标没有被占用,就下棋。
void computer_move(char board[ROW][COL], int row, int col)
{
printf("请等待,电脑下棋中:\n"); // 输出提示信息,表示电脑正在下棋
while (1) { // 进入无限循环,直到找到合适的下棋位置
int x = rand() % row; // 随机生成一个在棋盘行数范围内的整数作为横坐标x
int y = rand() % col; // 随机生成一个在棋盘列数范围内的整数作为纵坐标y
if (board[x][y] == ' ') // 判断当前坐标对应的棋盘位置是否为空(' '表示空)
{
board[x][y] = '#'; // 如果为空,则在该位置下电脑的棋子,这里用'#'表示电脑棋子
break; // 找到合适位置后,跳出循环
}
}
}
//判断棋盘是否满了
//希望is_full这个函数只是为了支持is_win函数的,只在is_win函数内使用
//就没有必要在头文件中声明
static int is_full(char board[ROW][COL], int row, int col)
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j]==' ')
return 0;
}
}
return 1;
}
//判断输赢
//判断输赢的函数要告诉我:电脑赢了?玩家赢了?游戏继续?
//电脑赢:#
//玩家赢:*
//平 局: Q
//游戏继续: C
char is_win(char board[ROW][COL], int row, int col)
{
int i = 0, j = 0, flag = 0;
char temp;
//判断任意一行
for (i = 0; i < row; i++)
{
temp = board[i][0];
for (j = 0; j < col; j++)
{
if (board[i][j] == temp && temp != ' ')
{
flag = 1;
}
else
{
flag = 0;
break;
}
}
if (flag == 1)
return temp;
}
//判断任意一列
for (j = 0; j < col; j++)
{
temp = board[0][j];
for (i = 0; i < row; i++)
{
if (board[i][j] == temp && temp != ' ')
flag = 1;
else
{
flag = 0;
break;
}
}
if (flag == 1)
return temp;
}
//判断对角线 '\'
temp = board[0][0];
for (i = 0; i < row; i++)
{
if (board[i][i] == temp && temp != ' ')
{
flag = 1;
}
else
{
flag = 0;
break;
}
}
if (flag == 1)
{
return temp;
}
//判断对角线 '/'
temp = board[row - 1][0];
for (i = 0; i < row; i++)
{
if (board[row - 1 - i][i] == temp && temp != ' ')
{
flag = 1;
}
else
{
flag = 0;
break;
}
}
if (flag == 1)
{
return temp;
}
//平局?
if (is_full(board, row, col) == 1)
{
return 'Q';
}
//继续(没有玩家赢,电脑赢 平局)
return 'C';
}
3.game.h
#define ROW 3
#define COL 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<Windows.h>
//头文件中声明函数
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_board(char board[ROW][COL], int row, int col);
//玩家下棋
void player_move(char board[ROW][COL], int row, int col);
//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);
//判断输赢
char is_win(char board[ROW][COL], int row, int col);