C语言实现五子棋(完整灵活升级版)(gobang by c - a complete and flexible upgrade version)
看了 C 视频教程中的三子棋实现,修改代码升级为五子棋,或者说 n子棋(即可以自由设置成功的时候棋子的连接数),代码中也写了详细的注释,分享给大家~
运行环境:Visual Studio 2019
该代码总共包含三个文件,一个是 main.c 主文件,还有2个分别是 game.c 和 game.h 实现游戏功能的文件。
1. main.c 文件
main.c 文件定义了代码的整个架构,主要包含2个部分
1. 显示游戏菜单:玩家选择是否开始游戏或者结束游戏
2. 定义了 game() 这个函数来实现游戏的整个基本架构,即 初始化 —— 判断棋盘是否满 —— 玩家下棋 —— 判断玩家是否胜利 —— 电脑下棋 —— 判断电脑是否胜利 。其中 ret 存放游戏最终的结果,'o' 表示玩家赢,'x' 表示电脑赢,'Q' 表示平局
1 #define _CRT_SECURE_NO_WARNINGS 1
2 #include "game.h"
3
4 // 测试五子棋游戏
5 void menu() {
6 printf("***************************************\n");
7 printf("******* 1. play 0. exit ********\n");
8 printf("***************************************\n");
9 }
10
11 // 游戏的整个算法实现
12 void game() {
13 // 用于存储判断输赢最终
14 char ret = 0;
15 // 用于判断棋盘是否满的值
16 int fullValue = 0;
17 if (ROW * COL % 2 == 0) {
18 fullValue = ROW * COL;
19 }
20 else {
21 fullValue = ROW * COL - 1;
22 }
23 //printf("fullValue = %d \n", fullValue);
24 // 计算所走步数
25 int count = 0;
26 // 数组-存放走出的棋盘信息
27 char board[ROW][COL] = { 0 };
28 // 数组-存放判断电脑和玩家胜利的最终结果
29 // 这个是字符串(字符数组)的初始化,因为后面字符串比较要用到strcmp这个函数,所以这里需要定义的是字符数组
30 char PlayerWin[TARGET+1] = "";
31 char ComputerWin[TARGET+1] = "";
32 // 初始化棋盘,全部都是空格
33 InitBoard(board, ROW, COL);
34 // 初始化用于判断是玩家赢还是电脑有赢的两个变量
35 InitWin(PlayerWin, ComputerWin, TARGET);
36 // 打印棋盘
37 DisplayBoard(board, ROW, COL, count);
38 // 开始下棋
39 while (1)
40 {
41 // 判断棋盘是否满了,满了就平局
42 if (count == fullValue) {
43 break;
44 ret = "Q";
45 }
46
47 // 玩家下棋
48 PlayerMove(board, ROW, COL);
49 count++;
50 DisplayBoard(board, ROW, COL, count);
51 // 判断玩家是否赢
52 ret = IsWin(board, PlayerWin, ComputerWin, ROW, COL, TARGET);
53 if (ret != 'C') {
54 break;
55 }
56
57 // 电脑下棋
58 ComputerMove(board, ROW, COL);
59 count++;
60 DisplayBoard(board, ROW, COL, count);
61 // 判断电脑是否赢
62 ret = IsWin(board, PlayerWin, ComputerWin, ROW, COL, TARGET);
63 if (ret != 'C') {
64 break;
65 }
66 }
67 // 跳出循环,说明比较结果出来了,那么就对比赛结果进行判断
68 if (ret == 'o') {
69 printf("!!!!!! 游戏结束 —— 玩家赢 \n");
70 }
71 else if (ret == 'x') {
72 printf("!!!!!! 游戏结束 —— 电脑赢 \n");
73 }
74 else {
75 printf("!!!!!! 游戏结束 —— 平局\n");
76 }
77 }
78
79 void test() {
80 int input = 0;
81 // 放在这里是因为srand只能执行一次,而test也是只执行一次
82 srand((unsigned int)time(NULL));
83 // 用时间戳来设置随机种子,把time返回的time_t类型强制转换成无符号整型
84 if (ROW >= TARGET && COL >= TARGET) {
85 // 进入游戏环节
86 do
87 {
88 // 显示游戏菜单
89 menu();
90 printf("请选择:> ");
91 scanf("%d", &input);
92 switch (input)
93 {
94 case 1:
95 game();
96 break;
97 case 0:
98 printf("退出游戏\n");
99 break;
100 default:
101 printf("选择错误_,请重新选择!\n");
102 break;
103 }
104 } while (input);
105 }
106 else {
107 printf("棋盘的行和列值必须大于等于目标数 \n");
108 }
109 }
110
111 int main() {
112 test();
113 return 0;
114 }
2. game.h 文件
game.h 文件中包含了一些重要参数的设置,以及 game.c 中定义函数的声明。
1 #pragma once
2 #define ROW 7 // 棋盘的行数
3 #define COL 7 // 棋盘的列数
4 #define TARGET 5 // 几个相连算是赢
5
6 #include <stdio.h>
7 #include <stdlib.h> // rand 函数
8 #include <time.h> // time 函数
9 #include <string.h>
10
11 // --------- 声明 ----------
12
13 // 棋盘初始化
14 void InitBoard(char board[ROW][COL], int row, int col);
15 // 用于判断是否赢的数组初始化
16 void InitWin(char PlayerWin[], char ComputerWin[], int target);
17 // 显示棋盘
18 void DisplayBoard(char board[ROW][COL], int row, int col, int count);
19 // 玩家走
20 void PlayerMove(char board[ROW][COL], int row, int col);
21 // 电脑走
22 void ComputerMove(char board[ROW][COL], int row, int col);
23 // 判断是否游戏结束
24 // 返回四种游戏的状态: 玩家赢 - 'o' 电脑赢 - 'x' 平局 - 'Q' 继续 - 'C'
25 char IsWin(char board[ROW][COL], char PlayerWin[], char ComputerWin[], int row, int col, int target);
3. game.c 文件
game.c 文件中存放了实现五子棋游戏的主要函数的定义,其中判断游戏是否有一方获胜的 IsWin 函数相对不好理解,大家要注意以下几点:
1. 横向、竖向和2个对角线方向的代码中有许多重复的部分,其中要修改的就是 i, j 变量循环时的取值范围以及看清楚 i, j 到底是表示行还是表示列,除了竖向以外,i 都是表示行,j 都是表示列,竖向则相反。
2. i, j 的范围到底是怎么来的,为了更清楚了解 i, j 的取值范围,可以利用高中解数学题目时常用到的 “取特殊值法” 来求解,就是举个例子以此来然后判断 i, j 的取值范围,如下图所示,取了 ROW=7, COL=7, TARGET=5 来获取相应的取值范围。
1 #define _CRT_SECURE_NO_WARNINGS 1
2 #include "game.h"
3
4 // 棋盘初始化,初始化所有棋盘值为空
5 void InitBoard(char board[ROW][COL], int row, int col) {
6 int i = 0;
7 int j = 0;
8 for (i = 0;i < row;i++) {
9 for (j = 0;j < col;j++) {
10 board[i][j] = ' ';
11 }
12 }
13 }
14
15 // 初始化用于比对判断是玩家赢还是电脑赢的数组PlayerWin和ComputerWin
16 void InitWin(char PlayerWin[], char ComputerWin[], int target) {
17 int i = 0;
18 for (i = 0; i < target;i++) {
19 char str_temp1[2] = { 'o', '\0'};
20 char str_temp2[2] = { 'x', '\0'};
21 strcat(PlayerWin, str_temp1);
22 strcat(ComputerWin, str_temp2);
23 }
24 // 字符型数组求长度的方法,求长度要用strlen,不能用sizeof(arr)/sizeof(arr[0])
25 //printf("InitWin -len(PlayerWin)=%d \n", strlen(PlayerWin));
26 //printf("InitWin - len(ComputerWin)=%d \n", strlen(ComputerWin));
27 //printf("InitWin - %s \n", PlayerWin);
28 //printf("InitWin - %s \n", ComputerWin);
29 }
30
31 // 显示棋盘
32 void DisplayBoard(char board[ROW][COL], int row, int col, int count) {
33 int i = 0;
34 printf(" \n -- 第 %d 步 \n", count);
35 printf("\n");
36 // 显示列标签
37 printf(" ");
38 for (i = 0;i < col;i++) {
39 if (i + 1 > 9) {
40 printf(" %d ", i+1);
41 }
42 else {
43 printf(" %d ", i+1);
44 }
45 }
46 printf("\n");
47 for (i = 0;i < row;i++) {
48 // 显示行标签
49 if (i + 1 > 9) {
50 printf(" %d ", i+1);
51 }
52 else {
53 printf(" %d ", i+1);
54 }
55 int j = 0;
56 for (j = 0;j < col;j++) {
57 // 1. 打印一行数据
58 printf(" %c ", board[i][j]);
59 if (j < col - 1)
60 printf("|");
61 }
62 printf("\n");
63 printf(" ");
64 // 2. 打印分割行
65 if (i < row - 1) {
66 for (j = 0;j < col;j++) {
67 printf("---");
68 if (j < col - 1)
69 printf("|");
70 }
71 printf("\n");
72 }
73 }
74 printf("\n");
75 }
76
77 // 玩家走 o(玩家根据输入的坐标位置填写o)
78 void PlayerMove(char board[ROW][COL], int row, int col) {
79 int x = 0;
80 int y = 0;
81 printf("玩家走:> \n");
82 while (1) {
83 printf("请输入要下的坐标:> ");
84 scanf("%d%d", &x, &y);
85 // 判断x,y坐标的合法性
86 if (x >= 1 && x <= row && y >= 1 && y <= col) {
87 if (board[x - 1][y - 1] == ' ') {
88 board[x - 1][y - 1] = 'o';
89 break;
90 }
91 else {
92 printf("该坐标被占用\n");
93 }
94 }
95 else {
96 printf("坐标非法,请重新输入\n");
97 }
98 }
99 }
100
101 // 电脑走 x(电脑通过寻找空格的地方而随机走,打x)
102 void ComputerMove(char board[ROW][COL], int row, int col) {
103 int x = 0;
104 int y = 0;
105 printf("电脑走:> \n");
106 // 这里设置成随机走的方式,哪里空就走哪里
107 while (1) {
108 x = rand() % ROW;
109 y = rand() % COL;
110 if (board[x][y] == ' ') {
111 board[x][y] = 'x';
112 break;
113 }
114 }
115 }
116
117 // 判断游戏是否已经有一方获胜,如果是的话则返回获胜方,否则继续游戏
118 // 判断游戏是否获胜通过循环提取 横向,纵向,上斜,下斜 的target元素,然后比对这target元素是否和PlayerWin和ComputerWin相等来判断获胜方
119 char IsWin(char board[ROW][COL], char PlayerWin[], char ComputerWin[], int row, int col, int target) {
120 int i = 0;
121 int j = 0;
122 int k = 0;
123 int flag = 1;
124 char str[TARGET+1] = ""; // 不设置数组大小会出现Stack around the variable 'str' was corrupted.数组越界的bug
125 // 横向 判断是否满足获胜条件
126 for (i = 0;i < row;i++) {
127 for (j = 0;j <= col - target;j++) {
128 // 用于判断是否进行比对的旗帜
129 flag = 1;
130 // 重新给str赋值为""
131 // void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符
132 memset(str, '\0', 1);
133 for (k = 0;k < target;k++) {
134 // 为了提高代码效率,这里判断只要检测到空格就说明这target长度的数据不可能有一方获胜
135 if (board[i][j+k] == ' ') {
136 flag = 0;
137 break;
138 }
139 // 如果不是空格,那么就保存当前格子值用于比对
140 char str_temp[2] = { board[i][j+k], '\0' };
141 strcat(str, str_temp);
142 }
143 // 如果flag为1,则说明str中没有空格,那么接下来就 对str的结果进行比对判断是否有一方获胜
144 if (flag == 1) {
145 if (strcmp(str, PlayerWin) == 0) {
146 return 'o';
147 }
148 else if(strcmp(str, ComputerWin) == 0) { // 不能不加if,因为可能出现3*3格子,一行oox类型情况
149 return 'x';
150 }
151 }
152 //printf("len(str)=%d \n", strlen(str));
153 //printf("%s \n", str);
154 }
155 }
156 // 竖向 判断是否满足获胜条件
157 for (i = 0;i < col;i++) {
158 for (j = 0;j <= row - target;j++) {
159 flag = 1;
160 // 重新给str赋值为""
161 memset(str, '\0', 1);
162 for (k = 0;k < target;k++) {
163 if (board[j+k][i] == ' ') { // 注意,这里i是列,j是行
164 flag = 0;
165 break;
166 }
167 char str_temp[2] = { board[j+k][i], '\0' };
168 strcat(str, str_temp);
169 }
170 if (flag == 1) {
171 if (strcmp(str, PlayerWin) == 0) {
172 return 'o';
173 }
174 else if(strcmp(str, ComputerWin) == 0) {
175 return 'x';
176 }
177 }
178 }
179 }
180
181 // 两个对角线方向
182 // 左上右下 对角方向判断是否满足获胜条件
183 for (i = 0;i <= row - target;i++) {
184 for (j = 0;j <= col - target;j++) {
185 flag = 1;
186 memset(str, '\0', 1);
187 for (k = 0;k < target;k++) {
188 if (board[i + k][j + k] == ' ') {
189 flag = 0;
190 break;
191 }
192 char str_temp[2] = { board[i + k][j + k], '\0' };
193 strcat(str, str_temp);
194 }
195 if (flag == 1) {
196 if (strcmp(str, PlayerWin) == 0) {
197 return 'o';
198 }
199 else if(strcmp(str, ComputerWin) == 0) {
200 return 'x';
201 }
202 }
203 }
204 }
205
206 // 左下右上 对角反向是否满足获胜条件
207 for (i = 0;i <= row - target;i++) {
208 for (j = target - 1;j < col;j++) {
209 flag = 1;
210 memset(str, '\0', 1);
211 for (k = 0;k < target;k++) {
212 if (board[i + k][j - k] == ' ') {
213 flag = 0;
214 break;
215 }
216 char str_temp[2] = { board[i + k][j - k], '\0' };
217 strcat(str, str_temp);
218 }
219 if (flag == 1) {
220 if (strcmp(str, PlayerWin) == 0) {
221 return 'o';
222 }
223 else if(strcmp(str, ComputerWin) == 0) {
224 return 'x';
225 }
226 }
227 }
228 }
229
230 return 'C';
231 }
运行结果:
本代码实现的五子棋可以随意定义棋盘的大小以及获胜的指标,界面也做了优化,大家可以尝试跑下,玩下,甚至继续优化代码,可能还有一些bug没有测试到的欢迎批评指正~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)