文章目录
-
- 概要
- 整体架构流程
- 技术名词解释
- 技术细节
- 小结
1. 概要
🔊 JackQiao 对 米粒 说:“今天咱们玩个五子棋小游戏,电脑与你轮流在一个 n×n 的网格上放置棋子(X 或 O),网格由你输入的正整数n决定,谁先连成五个相同的棋子(横、竖或斜)即获胜。如果棋盘被填满且无人获胜,则游戏以平局结束”。
😇 米粒想到:
✅ 让用户输入棋盘的大小。
✅ 创建并初始化棋盘。
✅ 开始游戏循环,交替让玩家和电脑下棋。
✅ 每次下完棋后检查是否有人赢了或者棋盘是否满了。
✅ 如果有玩家赢了或棋盘满了,则结束游戏。
2. 整体架构流程
2.1. 包含的库
#include <stdio.h> #include <stdlib.h> #include <time.h>
📶 stdio.h:用于输入输出操作,比如打印信息和读取用户输入。
📶 stdlib.h:包含了一些有用的函数,比如随机数生成。
📶 time.h:用于获取当前时间,这里是为了设置随机数种子。
2.2. 定义符号常量
#define EMPTY ' ' #define PLAYER1 'X' // 黑棋 #define PLAYER2 'O' // 白棋
📶 EMPTY:表示空格,即没有棋子的地方。PLAYER1 和 PLAYER2:分别代表两个玩家的棋子,一个是 'X',另一个是 'O'。
2.3. 初始化棋盘
void init_board(char board[], int n) {for (int i = 0; i < n * n; i++){board[i] = EMPTY;} }
📶 这个函数用来初始化棋盘。它会把棋盘上的所有位置都设为空格(即没有棋子)。n 是棋盘的大小,比如如果 n=5,那么就是一个 5x5 的棋盘。
2.4. 打印棋盘
void print_board(char board[], int n) {// 打印列号printf(" ");for (int i = 0; i < n; i++) {printf("%2d ", i);}printf("\n");// 打印上边框printf(" +");for (int i = 0; i < n; i++) {printf("---+");}printf("\n");// 打印棋盘内容及行号for (int i = 0; i < n; i++) {printf("%2d|", i);for (int j = 0; j < n; j++) {printf(" %c |", board[i * n + j]);}printf("\n");// 打印行间的分隔线printf(" +");for (int j = 0; j < n; j++) {printf("---+");}printf("\n");} }
✅ 效果如下 :
2.4.1. 打印列号
printf(" "); for (int i = 0; i < n; i++) {printf("%2d ", i); } printf("\n");
📶 printf(" ");:这行代码打印了三个空格,用于对齐后续的行号。
📶 for (int i = 0; i < n; i++) { ... }:这个循环遍历棋盘的每一列,并为每一列打印一个编号。📶 printf("%2d ", i);:这里使用了格式化字符串 %2d 来确保每个数字占用至少两个字符的空间(如果数字是一位数,则前面会补一个空格),并在其后加上两个空格以增加可读性。📶 printf("\n");:打印完所有列号后,换行以便开始打印棋盘的上边框。
2.4.2. 打印上边框
printf(" +"); for (int i = 0; i < n; i++) {printf("---+"); } printf("\n");
📶 printf(" +");:打印两个空格和一个加号(+),作为左边界的起点。
📶 for (int i = 0; i < n; i++) { ... }:这个循环遍历棋盘的每一列,并为每一列打印一个由三个连字符(---)组成的分隔线,最后用一个加号结束。
📶 printf("\n");:打印完上边框后换行,准备打印棋盘内容。
2.4.3. 打印棋盘内容及行号
for (int i = 0; i < n; i++) {printf("%2d|", i);for (int j = 0; j < n; j++) {printf(" %c |", board[i * n + j]);}printf("\n");// 打印行间的分隔线printf(" +");for (int j = 0; j < n; j++) {printf("---+");}printf("\n"); }
📶 行号与棋盘内容
- for (int i = 0; i < n; i++) { ... }:这个外层循环遍历棋盘的每一行。
- printf("%2d|", i);:为当前行打印行号(同样使用 %2d 确保两位宽),然后打印竖线(|)表示该行的开始。
- 内层循环 for (int j = 0; j < n; j++) { ... } 遍历每一行中的每一个单元格:
- printf(" %c |", board[i * n + j]);:打印当前单元格的内容(即棋子或空格),并用竖线将其与其他单元格分隔开。
📶 行间分隔线
- 每一行内容打印完毕后,紧接着打印该行下方的分隔线。
- 这个部分与打印上边框的部分几乎相同,只是它位于每两行之间,而不是顶部。
2.5. 检查位置是否为空
int is_valid_move(char board[], int n, int x, int y) {if (x < 0 || x >= n || y < 0 || y >= n) return 0;return board[x * n + y] == EMPTY; }
📶 这个函数检查给定的位置 (x, y) 是否在棋盘范围内并且是否为空。如果是的话,返回 1 表示可以下棋;否则返回 0。
2.6. 放置棋子
void make_move(char board[], int n, int x, int y, char player) {board[x * n + y] = player; }
📶 这个函数会在指定的位置 (x, y) 上放置玩家的棋子。
2.7. 检查是否有玩家获胜
int check_win(char board[], int n, int x, int y, char player) {int directions[8][2] = { {0, 1}, {1, 0}, {1, 1}, {1, -1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1} };for (int d = 0; d < 8; d++) {int count = 1;for (int i = 1; i < 5; i++) {int nx = x + directions[d][0] * i;int ny = y + directions[d][1] * i;if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) {count++;}else {break;}}for (int i = 1; i < 5; i++) {int nx = x - directions[d][0] * i;int ny = y - directions[d][1] * i;if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) {count++;}else {break;}}if (count >= 5) return 1;}return 0; }
2.7.1. 定义搜索方向
int directions[8][2] = { {0, 1}, {1, 0}, {1, 1}, {1, -1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1} };
- 📶 directions 是一个二维数组,存储了八个方向的增量值。
- 📶 {0, 1} 表示向右移动(横向)。
- 📶 {1, 0} 表示向下移动(纵向)。
- 📶 {1, 1} 表示右下对角线。
- 📶 {1, -1} 表示左下对角线。
- 📶 {0, -1} 表示向左移动(横向)。
- 📶 {-1, 0} 表示向上移动(纵向)。
- 📶 {-1, -1} 表示左上对角线。
- 📶 {-1, 1} 表示右上对角线。
2.7.2. 检查方向
for (int i = 1; i < 5; i++){int nx = x + directions[d][0] * i;int ny = y + directions[d][1] * i;if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) {count++;} else {break;} }
- 内层第一个循环沿着当前方向的正方向(即增加方向)检查最多4个位置。
- nx 和 ny 计算了在当前位置 (x, y) 沿着当前方向移动 i 步后的新坐标。
- 如果新坐标在棋盘范围内,并且该位置的棋子与当前玩家相同,则增加计数器 count。
- 如果遇到边界或不同棋子,则停止检查该方向
2.8. 电脑玩家随机移动
void make_computer_move(char board[], int n, char player) {int x, y;do {x = rand() % n;y = rand() % n;} while (!is_valid_move(board, n, x, y));printf("电脑玩家 %c 下在 (%d, %d)\n", player, x, y);make_move(board, n, x, y, player); }
2.9. 主函数
int main() {int n;printf("请输入棋盘大小 n: ");scanf("%d", &n);char* board = (char*)malloc(n * n * sizeof(char));if (!board) {printf("内存分配失败\n");return 1;}srand(time(NULL)); // 初始化随机数种子init_board(board, n);char current_player = PLAYER1;int x, y;while (1) {print_board(board, n);if (current_player == PLAYER1) {printf("玩家 %c,请输入坐标 (x y): ", current_player);scanf("%d %d", &x, &y);if (!is_valid_move(board, n, x, y)) {printf("无效的移动,请重新输入。\n");continue;}make_move(board, n, x, y, current_player);}else {make_computer_move(board, n, current_player);}if (check_win(board, n, x, y, current_player)) {print_board(board, n);printf("玩家 %c 获胜!\n", current_player);free(board);return 0;}if (is_board_full(board, n)) {print_board(board, n);printf("平局!\n");free(board);return 0;}current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;}free(board);return 0; }
2.10. 程序运行如下:
3. 技术名词解释
🔔 directions 是一个二维数组,存储了八个方向的增量值。
- 📶 directions 是一个二维数组,存储了八个方向的增量值。
- 📶 {0, 1} 表示向右移动(横向)。
- 📶 {1, 0} 表示向下移动(纵向)。
- 📶 {1, 1} 表示右下对角线。
- 📶 {1, -1} 表示左下对角线。
- 📶 {0, -1} 表示向左移动(横向)。
- 📶 {-1, 0} 表示向上移动(纵向)。
- 📶 {-1, -1} 表示左上对角线。
- 📶 {-1, 1} 表示右上对角线。
4. 技术细节
-
检查相应方
for (int i = 1; i < 5; i++){int nx = x - directions[d][0] * i;int ny = y - directions[d][1] * i;if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player){count++;} else
{break;}
}
✅ 整体代码
#include <stdio.h> #include <stdlib.h> #include <time.h>#define EMPTY ' ' #define PLAYER1 'X' // 黑棋 #define PLAYER2 'O' // 白棋// 初始化棋盘 void init_board(char board[], int n) {for (int i = 0; i < n * n; i++){board[i] = EMPTY;} }// 打印棋盘 void print_board(char board[], int n) {// 打印列号printf(" ");for (int i = 0; i < n; i++) {printf("%2d ", i);}printf("\n");// 打印上边框printf(" +");for (int i = 0; i < n; i++) {printf("---+");}printf("\n");// 打印棋盘内容及行号for (int i = 0; i < n; i++) {printf("%2d|", i);for (int j = 0; j < n; j++) {printf(" %c |", board[i * n + j]);}printf("\n");// 打印行间的分隔线printf(" +");for (int j = 0; j < n; j++) {printf("---+");}printf("\n");} }// 检查位置是否为空 int is_valid_move(char board[], int n, int x, int y) {if (x < 0 || x >= n || y < 0 || y >= n) return 0;return board[x * n + y] == EMPTY; }// 放置棋子 void make_move(char board[], int n, int x, int y, char player) {board[x * n + y] = player; }// 检查是否有玩家获胜 int check_win(char board[], int n, int x, int y, char player) {int directions[8][2] = { {0, 1}, {1, 0}, {1, 1}, {1, -1}, {0, -1}, {-1, 0}, {-1, -1}, {-1, 1} };for (int d = 0; d < 8; d++) {int count = 1;for (int i = 1; i < 5; i++) {int nx = x + directions[d][0] * i;int ny = y + directions[d][1] * i;if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) {count++;}else {break;}}for (int i = 1; i < 5; i++) {int nx = x - directions[d][0] * i;int ny = y - directions[d][1] * i;if (nx >= 0 && nx < n && ny >= 0 && ny < n && board[nx * n + ny] == player) {count++;}else {break;}}if (count >= 5) return 1;}return 0; }// 检查棋盘是否已满 int is_board_full(char board[], int n) {for (int i = 0; i < n * n; i++) {if (board[i] == EMPTY) return 0;}return 1; }// 电脑玩家随机移动 void make_computer_move(char board[], int n, char player) {int x, y;do {x = rand() % n;y = rand() % n;} while (!is_valid_move(board, n, x, y));printf("电脑玩家 %c 下在 (%d, %d)\n", player, x, y);make_move(board, n, x, y, player); }int main() {int n;printf("请输入棋盘大小 n: ");scanf("%d", &n);char* board = (char*)malloc(n * n * sizeof(char));if (!board) {printf("内存分配失败\n");return 1;}srand(time(NULL)); // 初始化随机数种子init_board(board, n);char current_player = PLAYER1;int x, y;while (1) {print_board(board, n);if (current_player == PLAYER1) {printf("玩家 %c,请输入坐标 (x y): ", current_player);scanf("%d %d", &x, &y);if (!is_valid_move(board, n, x, y)) {printf("无效的移动,请重新输入。\n");continue;}make_move(board, n, x, y, current_player);}else {make_computer_move(board, n, current_player);}if (check_win(board, n, x, y, current_player)) {print_board(board, n);printf("玩家 %c 获胜!\n", current_player);free(board);return 0;}if (is_board_full(board, n)) {print_board(board, n);printf("平局!\n");free(board);return 0;}current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;}free(board);return 0; }
5. 小结
✅ 针对该小游戏,米粒做出以下知识点总结:
🌷 数据结构:使用一维数组 char board[] 来表示二维棋盘,通过索引计算 [x * n + y] 来访问特定位置。
🌷 算法逻辑:通过遍历八个方向来检查每次落子后是否形成五子连珠,使用方向增量数组 directions[8][2] 来简化不同方向上的搜索。
🌷 用户交互:程序包含输入输出功能,如读取用户输入坐标、打印当前棋盘状态,并处理非法输入和游戏结束条件(胜利或平局)。