22. 五子棋小游戏
文章目录
-
- 概要
- 整体架构流程
- 技术名词解释
- 技术细节
- 小结
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] 来简化不同方向上的搜索。
🌷 用户交互:程序包含输入输出功能,如读取用户输入坐标、打印当前棋盘状态,并处理非法输入和游戏结束条件(胜利或平局)。