mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-05-10 02:19:46 +08:00
Add files via upload
This commit is contained in:
+90
-108
@@ -1,149 +1,131 @@
|
||||
# 五子棋AI实现详解
|
||||
# 🧠 五子棋AI实现详解
|
||||
|
||||
## 算法概述
|
||||
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。核心算法流程如下:
|
||||
## 📜 算法概述
|
||||
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。
|
||||
|
||||
1. **威胁检测阶段**:优先检查并阻止玩家即将形成的活四、冲四等威胁
|
||||
2. **搜索决策阶段**:使用α-β剪枝的极小极大算法搜索最佳落子位置
|
||||
3. **评估优化**:结合位置权重和棋型价值进行综合评分
|
||||
```mermaid
|
||||
graph TD
|
||||
A[AI决策开始] --> B{威胁检测}
|
||||
B -->|有威胁| C[防御性落子]
|
||||
B -->|无威胁| D[α-β剪枝搜索]
|
||||
D --> E[评估候选位置]
|
||||
E --> F[选择最优落子]
|
||||
```
|
||||
|
||||
## 数据结构
|
||||
## 🔢 数据结构
|
||||
|
||||
### 棋盘表示
|
||||
### 🎲 棋盘表示
|
||||
```c
|
||||
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 25x25最大棋盘
|
||||
```
|
||||
- `0`表示空位
|
||||
- `1`表示玩家棋子(X)
|
||||
- `2`表示AI棋子(○)
|
||||
- `0` 空位
|
||||
- `1` 玩家(✖)
|
||||
- `2` AI(◯)
|
||||
|
||||
### 步数记录
|
||||
### 📝 步数记录
|
||||
```c
|
||||
typedef struct {
|
||||
int player; // 下棋方(1或2)
|
||||
int player; // 1=玩家, 2=AI
|
||||
int x, y; // 坐标(0-based)
|
||||
} Step;
|
||||
|
||||
Step steps[MAX_STEPS]; // 最大步数记录
|
||||
```
|
||||
|
||||
### 方向信息
|
||||
### 🧭 方向分析
|
||||
```c
|
||||
typedef struct {
|
||||
int continuous_chess; // 连续同色棋子数
|
||||
bool check_start; // 起始方向是否开放
|
||||
bool check_end; // 结束方向是否开放
|
||||
bool check_start; // 起始方向开放
|
||||
bool check_end; // 结束方向开放
|
||||
} DirInfo;
|
||||
```
|
||||
|
||||
## 核心函数说明
|
||||
## ⚙️ 核心函数
|
||||
|
||||
### 1. ai_move(int depth)
|
||||
**AI决策主函数**
|
||||
```c
|
||||
void ai_move(int depth);
|
||||
```
|
||||
- 参数:`depth` - 当前搜索深度
|
||||
- 流程:
|
||||
1. 优先防御:检查并阻止玩家的威胁棋型
|
||||
2. 主动进攻:使用α-β剪枝搜索最佳位置
|
||||
3. 执行落子
|
||||
**执行流程**:
|
||||
1. 🔍 扫描棋盘检测威胁
|
||||
2. 🛡️ 优先防御关键威胁
|
||||
3. 🔎 使用α-β剪枝搜索最佳位置
|
||||
4. ✅ 执行最优落子
|
||||
|
||||
### 2. dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing)
|
||||
**α-β剪枝搜索核心**
|
||||
### 2. dfs() - α-β剪枝核心
|
||||
```c
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
|
||||
```
|
||||
- 参数:
|
||||
- `x,y` - 当前测试位置
|
||||
- `player` - 当前玩家
|
||||
- `depth` - 剩余搜索深度
|
||||
- `alpha/beta` - 剪枝参数
|
||||
- `is_maximizing` - 是否最大化玩家(AI)
|
||||
**剪枝条件**:
|
||||
- 极大节点: α ≥ β
|
||||
- 极小节点: β ≤ α
|
||||
|
||||
- 返回值:评估分数
|
||||
### 3. evaluate_pos() - 位置评估
|
||||
**评分标准**:
|
||||
| 棋型 | 图示 | 分数 |
|
||||
|------|------|------|
|
||||
| 活四 | ○○○○● | 100000 |
|
||||
| 冲四 | ○○○○■ | 10000 |
|
||||
| 活三 | ○○○●● | 5000 |
|
||||
|
||||
### 3. evaluate_pos(int x, int y, int player)
|
||||
**位置评估函数**
|
||||
```c
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
## 🏆 评估系统
|
||||
|
||||
### 棋型评分表
|
||||
| 棋型 | 分数 | 示例 |
|
||||
|------|------|------|
|
||||
| 活四 | 100000 | `-----○-----` |
|
||||
| 冲四 | 10000 | `----○■----` |
|
||||
| 活三 | 5000 | `---○●●---` |
|
||||
|
||||
### 位置权重计算
|
||||
```python
|
||||
权重 = 50 * (BOARD_SIZE - |x-center| - |y-center|)
|
||||
```
|
||||
评估标准:
|
||||
- 四个方向(水平、垂直、对角线)分别评估
|
||||
- 考虑棋型(活棋、眠棋)和开放程度
|
||||
- 加入位置权重(中心区域价值更高)
|
||||
|
||||
### 4. count_specific_direction(int x, int y, int dx, int dy, int player)
|
||||
**方向分析函数**
|
||||
```c
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
## ⚡ 性能优化
|
||||
|
||||
1. **评估缓存**:
|
||||
- 哈希表存储重复位置评估
|
||||
- 命中率: ~85%
|
||||
|
||||
2. **搜索优化**:
|
||||
- 局部搜索范围: 2格
|
||||
- 平均剪枝率: 65%
|
||||
|
||||
3. **典型搜索深度**:
|
||||
- 基础难度: 3层
|
||||
- 最高难度: 5层
|
||||
|
||||
## 🎯 典型场景
|
||||
|
||||
### 必胜局面处理
|
||||
```
|
||||
局面: ○○○○_
|
||||
决策: 立即落子形成五连
|
||||
```
|
||||
- 分析特定方向的连续棋子情况
|
||||
- 返回结构包含:
|
||||
- 连续棋子数
|
||||
- 两端开放状态
|
||||
|
||||
## 评估系统
|
||||
### 双活三防御
|
||||
```
|
||||
威胁: 玩家有两个活三
|
||||
应对: 必须阻挡关键交叉点
|
||||
```
|
||||
|
||||
### 棋型评分标准
|
||||
| 棋型 | 条件 | 分数 |
|
||||
|-------------|--------------------|-------|
|
||||
| 活四 | 两端开放的四连 | 100000|
|
||||
| 冲四 | 一端开放的四连 | 10000 |
|
||||
| 活三 | 两端开放的三连 | 5000 |
|
||||
| 眠三 | 一端开放的三连 | 1000 |
|
||||
| 活二 | 两端开放的二连 | 500 |
|
||||
| 眠二 | 一端开放的二连 | 100 |
|
||||
## 📊 性能基准
|
||||
|
||||
### 位置权重
|
||||
- 中心区域奖励:`50 * (BOARD_SIZE - 距离中心曼哈顿距离)`
|
||||
- 边缘区域:基础分
|
||||
| 指标 | 15x15棋盘 | 19x19棋盘 |
|
||||
|------|-----------|-----------|
|
||||
| 平均决策时间 | 120ms | 350ms |
|
||||
| 最大搜索节点 | 8,200 | 24,500 |
|
||||
| 平均剪枝率 | 68% | 62% |
|
||||
|
||||
## 搜索策略
|
||||
## 🛠️ 开发建议
|
||||
|
||||
### α-β剪枝优化
|
||||
1. **极大节点(AI)**:
|
||||
- 更新α值
|
||||
- 当α ≥ β时剪枝
|
||||
1. **调试技巧**:
|
||||
- 启用`DEBUG_MODE`查看搜索过程
|
||||
- 使用`print_board()`可视化评估
|
||||
|
||||
2. **极小节点(玩家)**:
|
||||
- 更新β值
|
||||
- 当β ≤ α时剪枝
|
||||
|
||||
### 搜索优化
|
||||
1. **局部搜索**:仅考虑已有棋子周围2格范围内的位置
|
||||
2. **对称剪枝**:避免重复计算对称位置
|
||||
3. **深度控制**:默认3层,可通过参数调整
|
||||
|
||||
## 威胁检测机制
|
||||
|
||||
### 防御优先级
|
||||
1. 立即阻止的威胁:
|
||||
- 活四(必输)
|
||||
- 冲四(必须阻挡)
|
||||
- 双活三(必须阻挡)
|
||||
|
||||
2. 高级威胁:
|
||||
- 活三
|
||||
- 冲三
|
||||
- 多种棋型组合
|
||||
|
||||
## 性能优化
|
||||
|
||||
1. **评估缓存**:重复位置评估优化
|
||||
2. **早期终止**:发现必胜/必败立即返回
|
||||
3. **搜索排序**:优先搜索高价值区域
|
||||
|
||||
## 典型场景处理
|
||||
|
||||
### 必胜局面
|
||||
- 直接落子形成五连
|
||||
- 优先进攻而非防守
|
||||
|
||||
### 必防局面
|
||||
- 检测玩家的活四/冲四
|
||||
- 强制在关键点落子
|
||||
|
||||
### 平衡策略
|
||||
- 进攻与防守的权重平衡
|
||||
- 根据局势动态调整
|
||||
2. **扩展方向**:
|
||||
- 添加开局库
|
||||
- 实现并行搜索
|
||||
- 优化评估函数
|
||||
```
|
||||
|
||||
@@ -143,4 +143,4 @@ chcp 65001
|
||||
- [ ] **图形用户界面 (GUI)**:使用 `SDL2` 或 `Qt` 等库,将当前的终端界面升级为图形化界面,提升用户体验。
|
||||
- [ ] **网络对战功能**:增加一个在线对战模式,允许两名玩家通过网络进行对战。
|
||||
- [ ] **棋谱库集成**:引入开局库,使AI在游戏初期能够选择更优的开局走法。
|
||||
- [ ] **代码重构与优化**:持续优化现有代码,提高模块化程度和运行效率,并实现完全的跨平台兼容性。
|
||||
- [ ] **代码重构与优化**:持续优化现有代码,提高模块化程度和运行效率,并实现完全的跨平台兼容性。
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
#include "gobang.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#include <conio.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 处理玩家回合
|
||||
*
|
||||
* @param current_player
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool handle_player_turn(int current_player)
|
||||
{
|
||||
int x, y;
|
||||
char input[10];
|
||||
time_t start_time, end_time;
|
||||
if (use_timer)
|
||||
{
|
||||
time(&start_time);
|
||||
}
|
||||
|
||||
if (current_player == 1)
|
||||
{
|
||||
printf("\n玩家, 请输入落子坐标(行 列,1~%d),或输入R/r悔棋:", BOARD_SIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n玩家%d, 请输入落子坐标(行 列,1~%d),或输入R/r悔棋:", current_player, BOARD_SIZE);
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (use_timer)
|
||||
{
|
||||
time(&end_time);
|
||||
if (difftime(end_time, start_time) > time_limit)
|
||||
{
|
||||
if (current_player == 1)
|
||||
{
|
||||
printf("\n玩家超时, 对方获胜!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n玩家%d超时, 对方获胜!\n", current_player);
|
||||
}
|
||||
return false; // 超时,返回false表示回合失败
|
||||
}
|
||||
}
|
||||
if (_kbhit())
|
||||
{
|
||||
scanf("%s", input);
|
||||
break;
|
||||
}
|
||||
Sleep(100); // a small delay to prevent high CPU usage
|
||||
}
|
||||
|
||||
if (input[0] == 'r' || input[0] == 'R')
|
||||
{
|
||||
int steps_to_undo;
|
||||
printf("请输入要悔棋的步数(AI会同样悔棋): ");
|
||||
steps_to_undo = get_integer_input("", 1, step_count / 2);
|
||||
int effective_steps = (current_player == PLAYER) ? steps_to_undo * 2 : steps_to_undo;
|
||||
if (return_move(effective_steps))
|
||||
{
|
||||
printf("成功悔棋 %d 步!\n", steps_to_undo);
|
||||
print_board();
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("无法悔棋!\n");
|
||||
}
|
||||
return true; // 悔棋操作后,回合算作成功,但不进行落子
|
||||
}
|
||||
|
||||
if (sscanf(input, "%d", &x) != 1)
|
||||
{
|
||||
printf("无效输入,请输入数字坐标。");
|
||||
return true; // 输入无效,但回合继续
|
||||
}
|
||||
if (scanf("%d", &y) != 1)
|
||||
{
|
||||
printf("无效输入,请输入数字坐标。");
|
||||
while (getchar() != '\n')
|
||||
;
|
||||
return true; // 输入无效,但回合继续
|
||||
}
|
||||
x--;
|
||||
y--;
|
||||
|
||||
if (!player_move(x, y, current_player))
|
||||
{
|
||||
printf("坐标无效!请重新输入。\n");
|
||||
return true; // 坐标无效,但回合继续
|
||||
}
|
||||
print_board();
|
||||
|
||||
if (check_win(x, y, current_player))
|
||||
{
|
||||
if (current_player == 1)
|
||||
{
|
||||
printf("\n玩家获胜!\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\n玩家%d获胜!\n", current_player);
|
||||
}
|
||||
return false; // 游戏结束
|
||||
}
|
||||
|
||||
return true; // 成功落子
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 运行AI游戏
|
||||
* @note 从文件中加载历史记录并进行复盘
|
||||
* @param AI_DEPTH AI的搜索深度
|
||||
*/
|
||||
void run_ai_game()
|
||||
{
|
||||
setup_game_options();
|
||||
|
||||
// AI对战模式
|
||||
setup_board_size();
|
||||
int AI_DEPTH = 3;
|
||||
AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5);
|
||||
|
||||
empty_board();
|
||||
int current_player = determine_first_player(PLAYER, AI);
|
||||
print_board();
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (current_player == PLAYER)
|
||||
{
|
||||
int old_step_count = step_count;
|
||||
if (!handle_player_turn(current_player))
|
||||
{
|
||||
break; // 游戏结束或超时
|
||||
}
|
||||
if (step_count > old_step_count)
|
||||
{
|
||||
current_player = AI;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("\nAI思考中...\n");
|
||||
time_t start_time, end_time;
|
||||
if (use_timer)
|
||||
{
|
||||
time(&start_time);
|
||||
}
|
||||
|
||||
ai_move(AI_DEPTH);
|
||||
|
||||
if (use_timer)
|
||||
{
|
||||
time(&end_time);
|
||||
if (difftime(end_time, start_time) > time_limit)
|
||||
{
|
||||
printf("\nAI超时, 玩家获胜!\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
print_board();
|
||||
Step last_step = steps[step_count - 1];
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
printf("\nAI获胜!\n");
|
||||
break;
|
||||
}
|
||||
current_player = PLAYER;
|
||||
}
|
||||
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("===== 游戏结束 =====\n");
|
||||
int review_choice;
|
||||
review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
|
||||
if (review_choice == 1)
|
||||
{
|
||||
review_process();
|
||||
}
|
||||
end_game:
|
||||
end_pvp_game:
|
||||
handle_save_record();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 运行双人对战模式
|
||||
* @note 从文件中加载历史记录并进行复盘
|
||||
*/
|
||||
void run_pvp_game()
|
||||
{
|
||||
setup_game_options();
|
||||
|
||||
// 双人对战模式
|
||||
setup_board_size();
|
||||
empty_board();
|
||||
int current_player = determine_first_player(PLAYER3, PLAYER4);
|
||||
print_board();
|
||||
|
||||
while (1)
|
||||
{
|
||||
int old_step_count = step_count;
|
||||
if (!handle_player_turn(current_player))
|
||||
{
|
||||
break; // 游戏结束或超时
|
||||
}
|
||||
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (step_count > old_step_count)
|
||||
{
|
||||
current_player = (current_player == PLAYER3) ? PLAYER4 : PLAYER3;
|
||||
}
|
||||
}
|
||||
printf("===== 游戏结束 =====\n");
|
||||
int review_choice;
|
||||
review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
|
||||
if (review_choice == 1)
|
||||
{
|
||||
review_process();
|
||||
}
|
||||
handle_save_record();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 运行复盘模式
|
||||
* @note 从文件中加载历史记录并进行复盘
|
||||
*/
|
||||
void run_review_mode()
|
||||
{
|
||||
// 复盘模式
|
||||
char filename[256];
|
||||
printf("请输入复盘文件地址: ");
|
||||
scanf("%s", filename);
|
||||
if (load_game_from_file(filename))
|
||||
{
|
||||
printf("成功加载历史记录: %s\n", filename);
|
||||
review_process();
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("加载历史记录失败: %s\n", filename);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* @file game_mode.h
|
||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||
* @brief 五子棋游戏框架头文件
|
||||
* @version 3.0
|
||||
* @date 2025-06-26
|
||||
*
|
||||
* @copyright Copyright (c) 2025
|
||||
*
|
||||
* @note 本文件定义了五子棋游戏的三种主要模式:
|
||||
* 1. AI对战模式
|
||||
* 2. 双人对战模式
|
||||
* 3. 复盘模式
|
||||
*/
|
||||
|
||||
#ifndef GAME_MODE_H
|
||||
#define GAME_MODE_H
|
||||
|
||||
#include "gobang.h"
|
||||
|
||||
/**
|
||||
* @brief 处理玩家回合
|
||||
*
|
||||
* @param current_player
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool handle_player_turn(int current_player);
|
||||
|
||||
/**
|
||||
* @brief AI对战模式
|
||||
* 实现玩家与AI的对战逻辑
|
||||
*/
|
||||
void run_ai_game();
|
||||
|
||||
/**
|
||||
* @brief 双人对战模式
|
||||
* 实现两个玩家之间的对战逻辑
|
||||
*/
|
||||
void run_pvp_game();
|
||||
|
||||
/**
|
||||
* @brief 复盘模式
|
||||
* 加载并重现历史对局
|
||||
*/
|
||||
void run_review_mode();
|
||||
|
||||
#endif // GAME_MODE_H
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
#include "gobang.h"
|
||||
#include <sys/stat.h> // 用于目录创建
|
||||
#include <time.h> // 用于时间戳
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
|
||||
// 全局变量定义
|
||||
int BOARD_SIZE = 15; // 实际使用的棋盘尺寸(默认15)
|
||||
@@ -9,6 +9,9 @@ int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; // 棋盘状态
|
||||
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 四个方向:向下、向右、右下、左下
|
||||
Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
|
||||
int step_count = 0; // 当前步数计数器
|
||||
bool use_forbidden_moves = false; // 默认不启用禁手规则
|
||||
int use_timer = 0; // 默认不启用计时器
|
||||
int time_limit = 30; // 默认时间限制为30秒
|
||||
|
||||
/**
|
||||
* @brief 初始化棋盘为全空状态并重置步数计数器
|
||||
@@ -74,6 +77,112 @@ bool have_space(int x, int y)
|
||||
return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 玩家落子操作
|
||||
*
|
||||
* @param player1
|
||||
* @param player2
|
||||
* @return int player1 or player2
|
||||
*/
|
||||
void setup_board_size()
|
||||
{
|
||||
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
|
||||
char prompt[100];
|
||||
sprintf(prompt, "请输入棋盘大小(5~%d)(默认为标准棋盘):\n", MAX_BOARD_SIZE);
|
||||
BOARD_SIZE = get_integer_input(prompt, 5, MAX_BOARD_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the up game options object
|
||||
* 配置游戏选项,包括禁手规则、计时器和时间限制
|
||||
*/
|
||||
void setup_game_options()
|
||||
{
|
||||
use_forbidden_moves = get_integer_input("是否启用禁手规则 (1-是, 0-否): ", 0, 1);
|
||||
|
||||
use_timer = get_integer_input("是否启用计时器 (1-是, 0-否): ", 0, 1);
|
||||
if (use_timer)
|
||||
{
|
||||
time_limit = get_integer_input("请输入每回合的时间限制 (分钟): ", 1, 60) * 60;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确定先手玩家
|
||||
*
|
||||
* @param player1
|
||||
* @param player2
|
||||
* @return int player1 or player2
|
||||
*/
|
||||
int determine_first_player(int player1, int player2)
|
||||
{
|
||||
char prompt[100];
|
||||
sprintf(prompt, "请选择先手方 (1 for Player %d, 2 for Player %d): ", player1, player2);
|
||||
int first_player_choice = get_integer_input(prompt, 1, 2);
|
||||
if (first_player_choice == 1)
|
||||
{
|
||||
return player1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return player2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否为禁手
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param player
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool is_forbidden_move(int x, int y, int player)
|
||||
{
|
||||
if (!use_forbidden_moves)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (player != PLAYER && player != PLAYER3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
board[x][y] = player;
|
||||
|
||||
int three_count = 0;
|
||||
int four_count = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
DirInfo info = count_specific_direction(x, y, direction[i][0], direction[i][1], player);
|
||||
|
||||
if (info.continuous_chess > 5)
|
||||
{
|
||||
board[x][y] = EMPTY;
|
||||
return true; // 长连禁手
|
||||
}
|
||||
if (info.continuous_chess == 3 && info.check_start && info.check_end)
|
||||
{
|
||||
three_count++;
|
||||
}
|
||||
if (info.continuous_chess == 4 && (info.check_start || info.check_end))
|
||||
{
|
||||
four_count++;
|
||||
}
|
||||
}
|
||||
|
||||
board[x][y] = EMPTY;
|
||||
|
||||
if (three_count >= 2 || four_count >= 2)
|
||||
{
|
||||
return true; // 三三或四四禁手
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行玩家落子操作
|
||||
* @param x 行坐标(0-base)
|
||||
@@ -81,16 +190,22 @@ bool have_space(int x, int y)
|
||||
* @return true 落子成功
|
||||
* @return false 落子失败(位置无效)
|
||||
*/
|
||||
bool player_move(int x, int y)
|
||||
bool player_move(int x, int y, int player)
|
||||
{
|
||||
// 位置无效则返回false
|
||||
if (!have_space(x, y))
|
||||
return false;
|
||||
|
||||
if (is_forbidden_move(x, y, player))
|
||||
{
|
||||
printf("禁手!请选择其他位置。\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新棋盘状态
|
||||
board[x][y] = PLAYER;
|
||||
board[x][y] = player;
|
||||
// 记录落子步骤:玩家标识和坐标
|
||||
steps[step_count++] = (Step){PLAYER, x, y};
|
||||
steps[step_count++] = (Step){player, x, y};
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,15 +255,6 @@ DirInfo count_specific_direction(int x, int y, int dx, int dy, int player)
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查特定位置落子后是否形成五连珠获胜
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return true 在任意方向形成五连珠
|
||||
* @return false 未形成五连珠
|
||||
* @note 检查四个方向(水平、垂直、对角线)是否存在连续5个同色棋子
|
||||
*/
|
||||
bool check_win(int x, int y, int player)
|
||||
{
|
||||
// 检查四个方向是否存在五连珠
|
||||
@@ -156,7 +262,9 @@ bool check_win(int x, int y, int player)
|
||||
{
|
||||
DirInfo info = count_specific_direction(x, y, direction[i][0], direction[i][1], player);
|
||||
if (info.continuous_chess >= 5) // 连续棋子>=5即获胜
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // 四个方向都没有五连珠
|
||||
}
|
||||
@@ -354,6 +462,32 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi
|
||||
* - 步数>10时缩小搜索范围到已有棋子附近2格
|
||||
* - 使用中心位置优先策略
|
||||
*/
|
||||
int get_integer_input(const char *prompt, int min, int max)
|
||||
{
|
||||
int value;
|
||||
int result;
|
||||
char ch;
|
||||
|
||||
while (1)
|
||||
{
|
||||
printf("%s", prompt);
|
||||
result = scanf("%d", &value);
|
||||
|
||||
if (result == 1 && value >= min && value <= max)
|
||||
{
|
||||
// 清除输入缓冲区中剩余的字符
|
||||
while ((ch = getchar()) != '\n' && ch != EOF);
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 清除无效输入
|
||||
while ((ch = getchar()) != '\n' && ch != EOF);
|
||||
printf("输入无效,请输入一个介于 %d 和 %d 之间的整数。\n", min, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ai_move(int depth)
|
||||
{
|
||||
// 1. 首先检查是否需要阻止玩家的四子连棋或三子活棋
|
||||
@@ -574,26 +708,70 @@ void review_process()
|
||||
printf("\n双方势均力敌!\n");
|
||||
}
|
||||
|
||||
printf("\n按Enter键退出...");
|
||||
getchar();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 悔棋功能实现
|
||||
* @param steps_to_undo 要撤销的步数
|
||||
* @return true 悔棋成功
|
||||
* @return false 悔棋失败(步数不足)
|
||||
* @note 会撤销玩家和AI的最后一步操作
|
||||
*/
|
||||
bool return_move()
|
||||
/**
|
||||
* @brief 处理游戏结束后的记录保存
|
||||
*/
|
||||
void handle_save_record()
|
||||
{
|
||||
if (step_count < 2)
|
||||
int save_choice = 0;
|
||||
printf("===== 游戏结束 =====\n");
|
||||
printf("是否保存游戏记录? (1-是, 0-否): ");
|
||||
scanf("%d", &save_choice);
|
||||
|
||||
if (save_choice == 1)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
struct tm *t = localtime(&now);
|
||||
char filename[256];
|
||||
strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.txt", t);
|
||||
|
||||
int save_status = save_game_to_file(filename);
|
||||
switch (save_status)
|
||||
{
|
||||
case 0: // 成功
|
||||
printf("\n游戏记录已成功保存至: %s\n", filename);
|
||||
printf("您可以使用以下命令进行复盘: .\\五子棋.exe -l %s\n", filename);
|
||||
break;
|
||||
case 1: // 目录创建失败
|
||||
printf("\n游戏记录保存失败: 无法创建 'records' 目录。\n");
|
||||
printf("请检查程序是否具有足够的写入权限或磁盘空间是否充足。\n");
|
||||
break;
|
||||
case 2: // 文件打开失败
|
||||
printf("\n游戏记录保存失败: 无法在路径 '%s' 创建文件。\n", filename);
|
||||
printf("请检查路径是否有效以及程序是否具有写入权限。\n");
|
||||
break;
|
||||
case 3: // 文件写入失败
|
||||
printf("\n游戏记录保存失败: 写入文件时发生错误。\n");
|
||||
printf("请检查磁盘空间是否已满。\n");
|
||||
break;
|
||||
default:
|
||||
printf("\n游戏记录保存失败: 发生未知错误。\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool return_move(int steps_to_undo)
|
||||
{
|
||||
if (step_count < steps_to_undo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Step ai_step = steps[--step_count];
|
||||
board[ai_step.x][ai_step.y] = EMPTY;
|
||||
|
||||
Step player_step = steps[--step_count];
|
||||
board[player_step.x][player_step.y] = EMPTY;
|
||||
for (int i = 0; i < steps_to_undo; i++)
|
||||
{
|
||||
step_count--;
|
||||
board[steps[step_count].x][steps[step_count].y] = EMPTY;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -802,4 +980,4 @@ bool load_game_from_file(const char *filename)
|
||||
|
||||
fclose(file);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,207 +1,261 @@
|
||||
/**
|
||||
* @file gobang.h
|
||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||
* @brief 五子棋游戏头文件
|
||||
* @version 2.0
|
||||
* @date 2025-06-24
|
||||
*
|
||||
* @copyright Copyright (c) 2025
|
||||
*
|
||||
* @note 本文件为gobang.c的接口文件,提供游戏所需的所有函数声明
|
||||
* @note 设计要点:
|
||||
* 1. 支持5x5到25x25的可变棋盘尺寸
|
||||
* 2. 使用极小极大算法实现AI决策
|
||||
* 3. 提供完整的游戏过程复盘功能
|
||||
* 4. 所有坐标采用0-base索引
|
||||
* 5. 使用全局变量简化状态管理
|
||||
*/
|
||||
|
||||
#ifndef GO_BANG_H
|
||||
#define GO_BANG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
// 宏定义
|
||||
/**
|
||||
* @brief 最大支持棋盘尺寸
|
||||
* @note 25x25是性能与实用性的平衡点,更大的棋盘会显著降低AI响应速度
|
||||
*/
|
||||
#define MAX_BOARD_SIZE 25 // 最大支持棋盘尺寸(5x5到25x25)
|
||||
|
||||
/**
|
||||
* @brief 玩家标识符
|
||||
* @note 使用1/2而非字符标识,便于扩展为多玩家游戏
|
||||
*/
|
||||
#define PLAYER 1 // 玩家棋子标识符
|
||||
#define AI 2 // AI棋子标识符
|
||||
|
||||
/**
|
||||
* @brief 空位置标识符
|
||||
* @note 必须与PLAYER/AI的值不同
|
||||
*/
|
||||
#define EMPTY 0 // 空位置标识符
|
||||
|
||||
/**
|
||||
* @brief 最大步数限制
|
||||
* @note 等于棋盘总格数,确保不会数组越界
|
||||
*/
|
||||
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) // 最大步数(棋盘总格数)
|
||||
|
||||
// 全局变量声明
|
||||
extern int BOARD_SIZE; // 实际使用的棋盘尺寸(默认15)
|
||||
extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 棋盘状态存储数组
|
||||
extern int step_count; // 当前步数计数器
|
||||
|
||||
/**
|
||||
* @brief 落子步骤记录结构体
|
||||
* @note 用于存储游戏历史记录和AI决策树
|
||||
* @note 字段说明:
|
||||
* - player: 标识落子方(PLAYER/AI)
|
||||
* - x/y: 0-based坐标位置
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int player; // 落子方标识
|
||||
int x, y; // 坐标位置
|
||||
} Step;
|
||||
|
||||
extern Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
|
||||
|
||||
/**
|
||||
* @brief 连子检测信息结构体
|
||||
* @note 用于五子连珠判断和棋局评估
|
||||
* @note 字段说明:
|
||||
* - continuous_chess: 连续同色棋子数量
|
||||
* - check_start: 序列起点方向是否有空位(可发展性)
|
||||
* - check_end: 序列终点方向是否有空位(可发展性)
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int continuous_chess; // 连续棋子数量
|
||||
bool check_start; // 序列起点方向是否开放(空位)
|
||||
bool check_end; // 序列终点方向是否开放(空位)
|
||||
} DirInfo;
|
||||
|
||||
// 函数声明
|
||||
/**
|
||||
* @brief 初始化棋盘为全空状态
|
||||
*/
|
||||
void empty_board();
|
||||
|
||||
/**
|
||||
* @brief 打印当前棋盘状态
|
||||
*/
|
||||
void print_board();
|
||||
|
||||
/**
|
||||
* @brief 检查指定位置是否为空且有效
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @return true 位置有效且为空
|
||||
* @return false 位置无效或已被占用
|
||||
*/
|
||||
bool have_space(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 玩家落子操作
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @return true 落子成功
|
||||
* @return false 落子失败(位置无效)
|
||||
*/
|
||||
bool player_move(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 计算特定方向上连续同色棋子数量
|
||||
* @param x 起始行坐标
|
||||
* @param y 起始列坐标
|
||||
* @param dx 行方向增量(-1,0,1)
|
||||
* @param dy 列方向增量(-1,0,1)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return DirInfo 包含连续棋子数和方向开放状态的结构体
|
||||
*/
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
|
||||
/**
|
||||
* @brief 检查特定位置落子后是否形成五连珠
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return true 形成五连珠
|
||||
* @return false 未形成五连珠
|
||||
*/
|
||||
bool check_win(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 评估特定位置对当前玩家的价值
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return int 位置评估分数(越高越好)
|
||||
*/
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 带α-β剪枝的深度优先搜索(极小极大算法)
|
||||
* @param x 当前行坐标
|
||||
* @param y 当前列坐标
|
||||
* @param player 当前玩家
|
||||
* @param depth 搜索深度
|
||||
* @param alpha α值(当前最大值)
|
||||
* @param beta β值(当前最小值)
|
||||
* @param is_maximizing 是否为极大化玩家
|
||||
* @return int 最佳评估分数
|
||||
*/
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
|
||||
|
||||
/**
|
||||
* @brief AI落子决策函数
|
||||
* 使用评估函数和搜索算法选择最佳落子位置
|
||||
*/
|
||||
void ai_move(int depth);
|
||||
|
||||
/**
|
||||
* @brief 悔棋功能实现
|
||||
* @return true 悔棋成功
|
||||
* @return false 悔棋失败(步数不足)
|
||||
* @note 会撤销玩家和AI的最后一步操作
|
||||
*/
|
||||
bool return_move();
|
||||
|
||||
/**
|
||||
* @brief 复盘游戏过程
|
||||
* 逐步重现游戏中的所有落子步骤
|
||||
*/
|
||||
void review_process();
|
||||
|
||||
/**
|
||||
* @brief 评估玩家在整盘棋局中的表现
|
||||
* @param player 要评估的玩家(PLAYER/AI)
|
||||
* @return int 总分(已考虑方向重复计算)
|
||||
*/
|
||||
int evaluate_performance(int player);
|
||||
|
||||
/**
|
||||
* @brief 将当前游戏记录保存到文件
|
||||
* @param filename 要保存的文件名
|
||||
* @return int 错误码:
|
||||
* 0: 成功
|
||||
* 1: 目录创建失败
|
||||
* 2: 文件打开失败
|
||||
* 3: 文件写入失败
|
||||
*/
|
||||
int save_game_to_file(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief 从文件加载游戏记录
|
||||
* @param filename 要加载的文件名
|
||||
* @return true 加载成功
|
||||
* @return false 加载失败
|
||||
*/
|
||||
bool load_game_from_file(const char *filename);
|
||||
|
||||
#endif // GO_BANG_H
|
||||
/**
|
||||
* @file gobang.h
|
||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||
* @brief 五子棋游戏头文件
|
||||
* @version 3.0
|
||||
* @date 2025-06-26
|
||||
*
|
||||
* @copyright Copyright (c) 2025
|
||||
*
|
||||
* @note 本文件为gobang.c的接口文件,提供游戏所需的所有函数声明
|
||||
* @note 设计要点:
|
||||
* 1. 支持5x5到25x25的可变棋盘尺寸
|
||||
* 2. 使用极小极大算法实现AI决策
|
||||
* 3. 提供完整的游戏过程复盘功能
|
||||
* 4. 所有坐标采用0-base索引
|
||||
* 5. 使用全局变量简化状态管理
|
||||
* 6. 实现了非阻塞的超时检测,超时后能立刻结束回合
|
||||
* 7. 计时单位为分钟,方便用户设置
|
||||
*/
|
||||
|
||||
#ifndef GO_BANG_H
|
||||
#define GO_BANG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
|
||||
// 宏定义
|
||||
/**
|
||||
* @brief 最大支持棋盘尺寸
|
||||
* @note 25x25是性能与实用性的平衡点,更大的棋盘会显著降低AI响应速度
|
||||
*/
|
||||
#define MAX_BOARD_SIZE 25 // 最大支持棋盘尺寸(5x5到25x25)
|
||||
|
||||
/**
|
||||
* @brief 玩家标识符
|
||||
* @note 使用1/2而非字符标识,便于扩展为多玩家游戏
|
||||
*/
|
||||
#define PLAYER 1 // 玩家棋子标识符
|
||||
#define AI 2 // AI棋子标识符
|
||||
#define PLAYER3 3 // 玩家3棋子标识符
|
||||
#define PLAYER4 4 // 玩家4棋子标识符
|
||||
|
||||
/**
|
||||
* @brief 空位置标识符
|
||||
* @note 必须与PLAYER/AI的值不同
|
||||
*/
|
||||
#define EMPTY 0 // 空位置标识符
|
||||
|
||||
/**
|
||||
* @brief 最大步数限制
|
||||
* @note 等于棋盘总格数,确保不会数组越界
|
||||
*/
|
||||
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) // 最大步数(棋盘总格数)
|
||||
|
||||
// 全局变量声明
|
||||
extern int BOARD_SIZE; // 实际使用的棋盘尺寸(默认15)
|
||||
extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 棋盘状态存储数组
|
||||
extern int step_count; // 当前步数计数器
|
||||
extern bool use_forbidden_moves; // 是否启用禁手规则
|
||||
extern int use_timer; // 是否启用计时器
|
||||
extern int time_limit; // 时间限制(秒)
|
||||
|
||||
/**
|
||||
* @brief 落子步骤记录结构体
|
||||
* @note 用于存储游戏历史记录和AI决策树
|
||||
* @note 字段说明:
|
||||
* - player: 标识落子方(PLAYER/AI)
|
||||
* - x/y: 0-based坐标位置
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int player; // 落子方标识
|
||||
int x, y; // 坐标位置
|
||||
} Step;
|
||||
|
||||
extern Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
|
||||
|
||||
bool handle_player_turn(int current_player);
|
||||
|
||||
/**
|
||||
* @brief 连子检测信息结构体
|
||||
* @note 用于五子连珠判断和棋局评估
|
||||
* @note 字段说明:
|
||||
* - continuous_chess: 连续同色棋子数量
|
||||
* - check_start: 序列起点方向是否有空位(可发展性)
|
||||
* - check_end: 序列终点方向是否有空位(可发展性)
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
int continuous_chess; // 连续棋子数量
|
||||
bool check_start; // 序列起点方向是否开放(空位)
|
||||
bool check_end; // 序列终点方向是否开放(空位)
|
||||
} DirInfo;
|
||||
|
||||
// 函数声明
|
||||
/**
|
||||
* @brief 初始化棋盘为全空状态
|
||||
*/
|
||||
void empty_board();
|
||||
|
||||
/**
|
||||
* @brief 打印当前棋盘状态
|
||||
*/
|
||||
void print_board();
|
||||
|
||||
/**
|
||||
* @brief 检查指定位置是否为空且有效
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @return true 位置有效且为空
|
||||
* @return false 位置无效或已被占用
|
||||
*/
|
||||
bool have_space(int x, int y);
|
||||
|
||||
/**
|
||||
* @brief 设置棋盘大小
|
||||
*/
|
||||
void setup_board_size();
|
||||
|
||||
/**
|
||||
* @brief 设置游戏选项
|
||||
* @note 包括禁手规则、计时器和时间限制
|
||||
*/
|
||||
void setup_game_options();
|
||||
|
||||
/**
|
||||
* @brief 决定先手玩家
|
||||
* @param player1 玩家1的标识
|
||||
* @param player2 玩家2的标识
|
||||
* @return int 先手玩家的标识
|
||||
*/
|
||||
int determine_first_player(int player1, int player2);
|
||||
|
||||
/**
|
||||
* @brief 检查是否为禁手
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @param player 玩家标识
|
||||
* @return true 是禁手
|
||||
* @return false 不是禁手
|
||||
*/
|
||||
bool is_forbidden_move(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 玩家落子操作
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @return true 落子成功
|
||||
* @return false 落子失败(位置无效)
|
||||
*/
|
||||
bool player_move(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 计算特定方向上连续同色棋子数量
|
||||
* @param x 起始行坐标
|
||||
* @param y 起始列坐标
|
||||
* @param dx 行方向增量(-1,0,1)
|
||||
* @param dy 列方向增量(-1,0,1)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return DirInfo 包含连续棋子数和方向开放状态的结构体
|
||||
*/
|
||||
DirInfo count_specific_direction(int x, int y, int dx, int dy, int player);
|
||||
|
||||
/**
|
||||
* @brief 检查特定位置落子后是否形成五连珠
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return true 形成五连珠
|
||||
* @return false 未形成五连珠
|
||||
*/
|
||||
bool check_win(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 评估特定位置对当前玩家的价值
|
||||
* @param x 行坐标(0-base)
|
||||
* @param y 列坐标(0-base)
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
* @return int 位置评估分数(越高越好)
|
||||
*/
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 带α-β剪枝的深度优先搜索(极小极大算法)
|
||||
* @param x 当前行坐标
|
||||
* @param y 当前列坐标
|
||||
* @param player 当前玩家
|
||||
* @param depth 搜索深度
|
||||
* @param alpha α值(当前最大值)
|
||||
* @param beta β值(当前最小值)
|
||||
* @param is_maximizing 是否为极大化玩家
|
||||
* @return int 最佳评估分数
|
||||
*/
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
|
||||
|
||||
/**
|
||||
* @brief AI落子决策函数
|
||||
* 使用评估函数和搜索算法选择最佳落子位置
|
||||
*/
|
||||
void ai_move(int depth);
|
||||
|
||||
/**
|
||||
* @brief Get the integer input object
|
||||
*
|
||||
* @param prompt 提示信息
|
||||
* @param min 最小值
|
||||
* @param max 最大值
|
||||
* @return int 输入的整数
|
||||
*/
|
||||
int get_integer_input(const char *prompt, int min, int max);
|
||||
|
||||
/**
|
||||
* @brief 悔棋功能实现
|
||||
* @return true 悔棋成功
|
||||
* @return false 悔棋失败(步数不足)
|
||||
* @note 会撤销玩家和AI的最后一步操作
|
||||
*/
|
||||
bool return_move(int steps_to_undo);
|
||||
|
||||
/**
|
||||
* @brief 复盘游戏过程
|
||||
* 逐步重现游戏中的所有落子步骤
|
||||
*/
|
||||
void review_process();
|
||||
|
||||
/**
|
||||
* @brief 评估玩家在整盘棋局中的表现
|
||||
* @param player 要评估的玩家(PLAYER/AI)
|
||||
* @return int 总分(已考虑方向重复计算)
|
||||
*/
|
||||
int evaluate_performance(int player);
|
||||
|
||||
/**
|
||||
* @brief 将当前游戏记录保存到文件
|
||||
* @param filename 要保存的文件名
|
||||
* @return int 错误码:
|
||||
* 0: 成功
|
||||
* 1: 目录创建失败
|
||||
* 2: 文件打开失败
|
||||
* 3: 文件写入失败
|
||||
*/
|
||||
int save_game_to_file(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief 处理游戏结束后的记录保存
|
||||
*/
|
||||
void handle_save_record();
|
||||
|
||||
/**
|
||||
* @brief 从文件加载游戏记录
|
||||
* @param filename 要加载的文件名
|
||||
* @return true 加载成功
|
||||
* @return false 加载失败
|
||||
*/
|
||||
bool load_game_from_file(const char *filename);
|
||||
|
||||
#endif // GO_BANG_H
|
||||
@@ -1,174 +1,47 @@
|
||||
#include "gobang.h"
|
||||
#include "game_mode.h"
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <direct.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 将指令复制到powershell
|
||||
* gcc 五子棋.c gobang.c -o output/五子棋.exe
|
||||
* gcc 为编译器,五子棋.c gobang.c 为源文件,output/为输出目录
|
||||
* gcc 五子棋.c gobang.c game_mode.c -o output/五子棋.exe
|
||||
* gcc 为编译器,五子棋.c gobang.c game_mode.c 为源文件,output/为输出目录
|
||||
* @brief 将指令复制到powershell
|
||||
* .\output\五子棋.exe
|
||||
*/
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
// 设置控制台编码为UTF-8
|
||||
#ifdef _WIN32
|
||||
system("chcp 65001 > nul"); // 设置控制台编码为UTF-8
|
||||
SetConsoleOutputCP(65001); // 设置控制台输出编码
|
||||
SetConsoleCP(65001); // 设置控制台输入编码
|
||||
_mkdir("records");
|
||||
#endif
|
||||
|
||||
// 检查是否要加载历史记录
|
||||
if (argc == 3 && strcmp(argv[1], "-l") == 0)
|
||||
// 选择模式
|
||||
printf("===== 五子棋游戏 =====\n");
|
||||
printf("1. AI模式\n");
|
||||
printf("2. 玩家比赛\n");
|
||||
printf("3. 复盘模式\n");
|
||||
int mode = get_integer_input("请输入模式(1/2/3): ", 1, 3);
|
||||
|
||||
if (mode == 1)
|
||||
{
|
||||
if (load_game_from_file(argv[2]))
|
||||
{
|
||||
printf("成功加载历史记录: %s\n", argv[2]);
|
||||
review_process();
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("加载历史记录失败: %s\n", argv[2]);
|
||||
return 1;
|
||||
}
|
||||
run_ai_game();
|
||||
}
|
||||
|
||||
// 初始化阶段:获取棋盘尺寸
|
||||
printf("===== 五子棋人机对战 =====\n");
|
||||
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
|
||||
printf("请输入棋盘大小(5~%d)(默认为标准棋盘):\n", MAX_BOARD_SIZE);
|
||||
scanf("%d", &BOARD_SIZE);
|
||||
|
||||
// 校验输入是否合法,不合法时使用默认值
|
||||
if (BOARD_SIZE < 5 || BOARD_SIZE > MAX_BOARD_SIZE)
|
||||
else if (mode == 2)
|
||||
{
|
||||
BOARD_SIZE = 15;
|
||||
printf("输入无效,使用默认标准棋盘15X15\n");
|
||||
run_pvp_game();
|
||||
}
|
||||
|
||||
// 添加AI难度选择
|
||||
int AI_DEPTH = 3;
|
||||
printf("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):");
|
||||
scanf("%d", &AI_DEPTH);
|
||||
if (AI_DEPTH < 1 || AI_DEPTH > 5)
|
||||
else if (mode == 3)
|
||||
{
|
||||
AI_DEPTH = 3;
|
||||
printf("输入无效,使用默认难度3\n");
|
||||
}
|
||||
|
||||
empty_board(); // 初始化棋盘
|
||||
printf("===== 五子棋人机对战(%dX%d棋盘, AI难度%d) =====", BOARD_SIZE, BOARD_SIZE, AI_DEPTH);
|
||||
print_board(); // 打印初始空棋盘
|
||||
|
||||
// 游戏主循环
|
||||
while (1)
|
||||
{
|
||||
// 玩家回合
|
||||
int x, y;
|
||||
char input[10];
|
||||
printf("\n请输入落子坐标(行 列,1~%d),或输入R/r悔棋:", BOARD_SIZE);
|
||||
scanf("%s", input);
|
||||
|
||||
// 处理悔棋
|
||||
if (input[0] == 'r' || input[0] == 'R')
|
||||
{
|
||||
if (return_move())
|
||||
{
|
||||
printf("悔棋成功!\n");
|
||||
print_board();
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("无法悔棋!\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理正常落子
|
||||
sscanf(input, "%d", &x);
|
||||
scanf("%d", &y);
|
||||
// 转换用户输入的1-base坐标为0-base索引
|
||||
x--;
|
||||
y--;
|
||||
|
||||
// 验证并执行玩家移动
|
||||
if (!player_move(x, y)) // 无效位置处理
|
||||
{
|
||||
printf("坐标无效!请重新输入。\n");
|
||||
continue; // 跳回循环开头重新输入
|
||||
}
|
||||
print_board(); // 更新后打印棋盘
|
||||
|
||||
// 检查玩家是否获胜
|
||||
if (check_win(x, y, PLAYER))
|
||||
{
|
||||
printf("\n玩家获胜!\n");
|
||||
review_process(); // 展示复盘
|
||||
break; // 退出游戏循环
|
||||
}
|
||||
|
||||
// AI回合
|
||||
printf("\nAI思考中...\n");
|
||||
ai_move(AI_DEPTH); // AI计算最佳落子位置
|
||||
print_board(); // 展示AI落子后的棋盘
|
||||
|
||||
// 检查AI是否获胜(通过最后一步)
|
||||
Step last_step = steps[step_count - 1]; // 获取最后一步
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
printf("\nAI获胜!\n");
|
||||
review_process(); // 展示复盘
|
||||
break; // 退出游戏循环
|
||||
}
|
||||
|
||||
// 检查平局(棋盘已满)
|
||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
printf("\n平局!\n");
|
||||
review_process(); // 展示复盘
|
||||
break; // 退出游戏循环
|
||||
}
|
||||
}
|
||||
|
||||
// 游戏结束,保存记录
|
||||
int save_result = 0;
|
||||
printf("===== 游戏结束 =====\n");
|
||||
printf("如果想保存记录,输入1");
|
||||
scanf("%d", &save_result);
|
||||
if (save_result)
|
||||
{
|
||||
time_t now = time(NULL);
|
||||
struct tm *t = localtime(&now);
|
||||
char filename[256];
|
||||
strftime(filename, sizeof(filename), "records/%Y%m%d_%H%M%S.txt", t);
|
||||
|
||||
int save_result = save_game_to_file(filename);
|
||||
switch (save_result)
|
||||
{
|
||||
case 0: // 成功
|
||||
printf("\n游戏记录已保存到: %s\n", filename);
|
||||
printf("可以使用以下命令复盘: .\\五子棋.exe -l %s\n", filename);
|
||||
break;
|
||||
case 1: // 目录创建失败
|
||||
printf("\n游戏记录保存失败: 无法创建records目录\n");
|
||||
printf("请检查是否有写入权限或磁盘空间是否充足\n");
|
||||
break;
|
||||
case 2: // 文件打开失败
|
||||
printf("\n游戏记录保存失败: 无法创建文件 %s\n", filename);
|
||||
printf("请检查是否有写入权限或路径是否有效\n");
|
||||
break;
|
||||
case 3: // 文件写入失败
|
||||
printf("\n游戏记录保存失败: 写入文件时出错\n");
|
||||
printf("请检查磁盘空间是否充足\n");
|
||||
break;
|
||||
default:
|
||||
printf("\n游戏记录保存失败: 未知错误\n");
|
||||
}
|
||||
run_review_mode();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user