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没有测试到的欢迎批评指正~

 

posted @ 2022-02-10 17:33  ttweixiao9999  阅读(744)  评论(0编辑  收藏  举报