5 Commits

Author SHA1 Message Date
Serendipity 46048b25ce gobang.h 2025-06-23 20:37:24 +08:00
Serendipity 19c4ddbf3e Add files via upload 2025-06-23 20:34:10 +08:00
Serendipity 160aebc0db Add files via upload 2025-06-23 20:33:43 +08:00
Serendipity 4edba6e7ce Add files via upload 2025-06-23 20:33:14 +08:00
Serendipity 246ecc007b Initial commit 2025-06-16 18:41:43 +08:00
42 changed files with 813 additions and 6161 deletions
-42
View File
@@ -1,42 +0,0 @@
# 编译生成的可执行文件
*.exe
*.out
*.app
# IDE配置文件
.idea/
.vscode/
.vs/
# Trae AI配置文件
.trae/
# 临时文件
*.tmp
*.temp
*.log
# 系统文件
.DS_Store
Thumbs.db
# 备份文件
*.bak
*.backup
# 调试文件
*.pdb
*.ilk
# 对象文件
*.o
*.obj
# 静态库文件
*.lib
*.a
# 动态库文件
*.dll
*.so
*.dylib
-461
View File
@@ -1,461 +0,0 @@
# 🚀 五子棋AI增强指南
## 📋 概述
本文档详细介绍了提升五子棋AI水平的各种方法和实现策略,基于当前项目的代码架构,提供从简单参数调优到复杂算法改进的完整方案。
## 🎯 当前AI分析
### 现有优势
- ✅ 模块化设计良好
- ✅ 基础Minimax + α-β剪枝算法
- ✅ 完整的棋型评估系统
- ✅ 威胁检测机制
- ✅ 防御优先策略
### 改进空间
- 🔄 搜索深度有限(当前3层)
- 🔄 评估函数相对简单
- 🔄 缺乏开局库和残局库
- 🔄 没有学习机制
- 🔄 搜索效率可优化
## 🛠️ 改进方案
### 1. 立即可实施的改进(难度:⭐)
#### 1.1 参数调优
**修改 `config.h` 中的关键参数:**
```c
// 增加搜索深度
#define DEFAULT_AI_DEPTH 5 // 从3提升到5
// 优化防守系数
#define DEFAULT_DEFENSE_COEFFICIENT 1.5 // 从1.2提升到1.5
// 扩大搜索范围
#define AI_NEARBY_RANGE 3 // 从2扩大到3
// 降低搜索范围限制阈值
#define AI_SEARCH_RANGE_THRESHOLD 8 // 从10降低到8
```
#### 1.2 评分系统优化
**在 `config.h` 中添加新的评分项:**
```c
// 组合棋型评分
#define AI_SCORE_DOUBLE_THREE 50000 // 双三
#define AI_SCORE_FOUR_THREE 200000 // 四三
#define AI_SCORE_THREAT_SEQUENCE 80000 // 威胁序列
#define AI_SCORE_POTENTIAL_FIVE 300000 // 潜在五连
```
### 2. 短期改进(1-2周,难度:⭐⭐)
#### 2.1 移动排序优化
**在 `ai.c` 中添加移动排序函数:**
```c
// 移动排序结构
typedef struct {
int x, y;
int score;
} ScoredMove;
// 移动排序函数
int compare_moves(const void *a, const void *b) {
ScoredMove *moveA = (ScoredMove*)a;
ScoredMove *moveB = (ScoredMove*)b;
return moveB->score - moveA->score;
}
// 生成并排序候选移动
int generate_candidate_moves(ScoredMove *moves, int player) {
int count = 0;
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] == EMPTY && is_near_stones(i, j)) {
moves[count].x = i;
moves[count].y = j;
moves[count].score = evaluate_move(i, j);
count++;
}
}
}
qsort(moves, count, sizeof(ScoredMove), compare_moves);
return count;
}
```
#### 2.2 威胁检测增强
**添加多层次威胁检测:**
```c
// 威胁类型枚举
typedef enum {
THREAT_NONE = 0,
THREAT_WIN = 5, // 直接获胜
THREAT_FOUR = 4, // 活四/冲四
THREAT_THREE = 3, // 活三
THREAT_DOUBLE = 2, // 双威胁
THREAT_POTENTIAL = 1 // 潜在威胁
} ThreatLevel;
// 威胁检测函数
ThreatLevel detect_threat(int x, int y, int player) {
// 实现威胁检测逻辑
// ...
}
```
### 3. 中期改进(1-2月,难度:⭐⭐⭐)
#### 3.1 置换表实现
**添加置换表缓存系统:**
```c
// 置换表项
typedef struct {
uint64_t hash_key; // 棋盘哈希值
int score; // 评估分数
int depth; // 搜索深度
int best_x, best_y; // 最佳移动
int flag; // 节点类型(精确值/上界/下界)
} TranspositionEntry;
// 置换表
#define TT_SIZE 1048576 // 2^20
TranspositionEntry transposition_table[TT_SIZE];
// 棋盘哈希函数
uint64_t zobrist_hash() {
// 实现Zobrist哈希
// ...
}
```
#### 3.2 开局库系统
**创建开局库数据结构:**
```c
// 开局库项
typedef struct {
int moves[20][2]; // 开局移动序列
int move_count; // 移动数量
int win_rate; // 胜率
char name[50]; // 开局名称
} OpeningEntry;
// 开局库
OpeningEntry opening_book[] = {
// 天元开局
{{{7,7}, {6,6}, {8,8}, {6,8}, {8,6}}, 5, 65, "天元开局"},
// 花月开局
{{{7,7}, {6,7}, {8,7}, {7,6}, {7,8}}, 5, 70, "花月开局"},
// 更多开局...
};
```
#### 3.3 改进的评估函数
**实现更复杂的位置评估:**
```c
// 高级评估函数
int advanced_evaluate_pos(int x, int y, int player) {
int score = 0;
// 1. 基础棋型评分
score += basic_pattern_score(x, y, player);
// 2. 组合棋型评分
score += combination_pattern_score(x, y, player);
// 3. 位置价值评分
score += positional_value_score(x, y);
// 4. 威胁序列评分
score += threat_sequence_score(x, y, player);
// 5. 防守价值评分
score += defensive_value_score(x, y, player);
return score;
}
```
### 4. 长期改进(3-6月,难度:⭐⭐⭐⭐)
#### 4.1 蒙特卡洛树搜索(MCTS
**MCTS节点结构:**
```c
// MCTS节点
typedef struct MCTSNode {
int x, y; // 移动位置
int visits; // 访问次数
double wins; // 胜利次数
struct MCTSNode *parent; // 父节点
struct MCTSNode **children; // 子节点数组
int child_count; // 子节点数量
bool fully_expanded; // 是否完全展开
} MCTSNode;
// MCTS主函数
int mcts_search(int simulations) {
MCTSNode *root = create_node(-1, -1, NULL);
for (int i = 0; i < simulations; i++) {
MCTSNode *node = selection(root);
node = expansion(node);
double result = simulation(node);
backpropagation(node, result);
}
return best_child(root);
}
```
#### 4.2 神经网络集成
**神经网络评估接口:**
```c
// 神经网络评估函数
float neural_network_evaluate(int board[BOARD_SIZE][BOARD_SIZE]) {
// 调用训练好的神经网络模型
// 返回位置评估值
// ...
}
// 混合评估函数
int hybrid_evaluate(int x, int y, int player) {
// 传统评估
int traditional_score = evaluate_pos(x, y, player);
// 神经网络评估
float nn_score = neural_network_evaluate(board);
// 加权组合
return (int)(0.7 * traditional_score + 0.3 * nn_score * 10000);
}
```
## 📊 性能优化策略
### 1. 搜索优化
#### 1.1 迭代加深搜索
```c
int iterative_deepening_search(int max_depth, int time_limit) {
int best_move = -1;
clock_t start_time = clock();
for (int depth = 1; depth <= max_depth; depth++) {
if ((clock() - start_time) * 1000 / CLOCKS_PER_SEC > time_limit) {
break;
}
best_move = alpha_beta_search(depth);
}
return best_move;
}
```
#### 1.2 空窗搜索
```c
int null_window_search(int depth, int beta) {
return alpha_beta_search(depth, beta - 1, beta);
}
```
### 2. 内存优化
#### 2.1 位棋盘表示
```c
// 使用位操作优化棋盘表示
typedef struct {
uint64_t player1_board[4]; // 玩家1的棋盘(4个64位整数)
uint64_t player2_board[4]; // 玩家2的棋盘
} BitBoard;
```
## 🎮 实战策略
### 1. 自适应难度系统
```c
// 难度等级
typedef enum {
DIFFICULTY_EASY = 1,
DIFFICULTY_NORMAL = 2,
DIFFICULTY_HARD = 3,
DIFFICULTY_EXPERT = 4,
DIFFICULTY_MASTER = 5
} DifficultyLevel;
// 根据难度调整AI参数
void adjust_ai_parameters(DifficultyLevel level) {
switch (level) {
case DIFFICULTY_EASY:
ai_depth = 2;
defense_coefficient = 1.0;
break;
case DIFFICULTY_NORMAL:
ai_depth = 3;
defense_coefficient = 1.2;
break;
case DIFFICULTY_HARD:
ai_depth = 4;
defense_coefficient = 1.5;
break;
// ...
}
}
```
### 2. 学习系统
```c
// 对局记录
typedef struct {
int moves[MAX_STEPS][2];
int move_count;
int winner;
int ai_mistakes;
float game_quality;
} GameRecord;
// 学习函数
void learn_from_game(GameRecord *record) {
// 分析对局,更新评估参数
// 识别AI的错误决策
// 调整相关参数
}
```
## 📈 测试与评估
### 1. 性能测试
```c
// 性能测试函数
void performance_test() {
clock_t start = clock();
// 执行1000次AI决策
for (int i = 0; i < 1000; i++) {
ai_move(DEFAULT_AI_DEPTH);
// 重置棋盘
}
clock_t end = clock();
double time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("平均决策时间: %.2f ms\n", time_taken);
}
```
### 2. 棋力测试
```c
// 自我对弈测试
int self_play_test(int games) {
int wins = 0;
for (int i = 0; i < games; i++) {
// AI vs AI(不同版本或参数)
int result = play_game();
if (result == 1) wins++;
}
return (wins * 100) / games; // 胜率
}
```
## 🚀 实施路线图
### 阶段1:基础优化(1周)
- [ ] 调整搜索深度和评分参数
- [ ] 优化移动排序
- [ ] 增强威胁检测
### 阶段2:算法改进(2-4周)
- [ ] 实现置换表
- [ ] 添加开局库
- [ ] 改进评估函数
### 阶段3:高级功能(1-2月)
- [ ] 实现MCTS
- [ ] 添加学习机制
- [ ] 性能优化
### 阶段4AI增强(2-3月)
- [ ] 神经网络集成
- [ ] 并行搜索
- [ ] 完整的自适应系统
## 📚 参考资源
### 算法资料
- 《人工智能:一种现代方法》- Stuart Russell
- 《游戏编程中的人工智能技术》- Mat Buckland
- AlphaGo论文系列
### 开源项目
- Stockfish(国际象棋引擎)
- Leela Zero(围棋AI
- Gomoku AI项目
### 在线资源
- Chess Programming Wiki
- Computer Olympiad
- AI游戏编程社区
## 🏗️ 代码架构优化 (v7.0新增)
### 配置管理统一化
在v7.0版本中,我们完成了重要的代码架构重构:
#### 配置参数集中管理
- **统一配置文件**:所有AI相关参数现在集中在`config.h`中定义
- **参数分类管理**:AI参数按功能分组(搜索深度、评分权重、时间限制等)
- **配置文件支持**AI参数可通过`gobang_config.ini`文件动态调整
- **运行时修改**:支持游戏过程中实时调整AI难度和参数
#### 代码模块化优化
- **清晰的模块分离**:AI逻辑与游戏逻辑完全分离
- **接口标准化**:统一的AI接口设计,便于算法替换和升级
- **全局变量管理**:AI相关全局变量集中在`globals`模块中
- **类型定义统一**:所有数据结构定义集中在`type.h`
#### 维护性提升
- **宏定义优化**:消除重复定义,提高代码一致性
- **注释规范化**:完善的代码注释和文档
- **错误处理统一**:标准化的错误处理机制
- **调试支持增强**:更好的调试信息和日志记录
这些架构优化为后续的AI算法改进奠定了坚实的基础,使得实施复杂的AI增强方案变得更加容易和可靠。
## 💡 总结
通过系统性的改进,可以将当前的五子棋AI从业余水平提升到接近专业水平。关键是要循序渐进,先实施简单的改进,再逐步引入复杂的算法。每个阶段都要进行充分的测试和评估,确保改进的有效性。
建议按照优先级顺序实施:
1. **短期目标**:参数调优 + 开局库 + 棋型优化
2. **中期目标**:搜索算法优化 + 评估函数改进
3. **长期目标**:机器学习集成 + MCTS实现
v7.0的架构重构为所有这些改进提供了更好的代码基础。
记住:**好的AI不仅要算得深,更要算得准!**
-147
View File
@@ -1,147 +0,0 @@
# 🧠 五子棋AI实现详解
## 📜 算法概述
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。支持人机对战、双人对战和网络对战多种模式。
```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` 玩家(✖)
- `2` AI(◯)
### 📝 步数记录
```c
typedef struct {
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; // 结束方向开放
} DirInfo;
```
### 🌐 网络对战支持
```c
// 网络模式下的AI决策
void network_ai_move(int depth, int player_id);
// 同步AI决策到网络对手
void sync_ai_decision(int x, int y);
```
## ⚙️ 核心函数
### 1. ai_move(int depth)
```c
void ai_move(int depth);
```
**执行流程**:
1. 🔍 扫描棋盘检测威胁
2. 🛡️ 优先防御关键威胁
3. 🔎 使用α-β剪枝搜索最佳位置
4. ✅ 执行最优落子
### 2. dfs() - α-β剪枝核心
```c
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
```
**剪枝条件**:
- 极大节点: α ≥ β
- 极小节点: β ≤ α
### 3. evaluate_pos() - 位置评估
**评分标准**:
| 棋型 | 图示 | 分数 |
|------|------|------|
| 活四 | ○○○○● | 100000 |
| 冲四 | ○○○○■ | 10000 |
| 活三 | ○○○●● | 5000 |
## 🏆 评估系统
### 棋型评分表
| 棋型 | 分数 | 示例 |
|------|------|------|
| 活四 | 100000 | `-----○-----` |
| 冲四 | 10000 | `----○■----` |
| 活三 | 5000 | `---○●●---` |
### 位置权重计算
```python
权重 = 50 * (BOARD_SIZE - |x-center| - |y-center|)
```
## ⚡ 性能优化
1. **评估缓存**:
- 哈希表存储重复位置评估
- 命中率: ~85%
2. **搜索优化**:
- 局部搜索范围: 2格
- 平均剪枝率: 65%
3. **典型搜索深度**:
- 基础难度: 3层
- 最高难度: 5层
4. **网络优化**:
- 异步AI计算,避免网络延迟
- 决策结果实时同步
- 支持断线重连后状态恢复
## 🎯 典型场景
### 必胜局面处理
```
局面: ○○○○_
决策: 立即落子形成五连
```
### 双活三防御
```
威胁: 玩家有两个活三
应对: 必须阻挡关键交叉点
```
## 📊 性能基准
| 指标 | 15x15棋盘 | 19x19棋盘 |
|------|-----------|-----------|
| 平均决策时间 | 120ms | 350ms |
| 最大搜索节点 | 8,200 | 24,500 |
| 平均剪枝率 | 68% | 62% |
## 🛠️ 开发建议
1. **调试技巧**:
- 启用`DEBUG_MODE`查看搜索过程
- 使用`print_board()`可视化评估
- 网络模式下使用`network_debug()`监控通信
2. **扩展方向**:
- 添加开局库
- 实现并行搜索
- 优化评估函数
- 增强网络对战AI适应性
- 支持AI难度动态调整
```
-268
View File
@@ -1,268 +0,0 @@
# 五子棋项目代码架构重构指南
## 📋 概述
本文档详细记录了五子棋项目在v7.0版本中进行的重大代码架构重构,包括重构的目标、实施过程、技术细节和带来的改进。
## 🎯 重构目标
### 主要目标
1. **代码模块化** - 实现清晰的模块分离和职责划分
2. **配置统一管理** - 集中管理所有配置参数和宏定义
3. **全局变量规范化** - 统一管理全局变量,避免散乱分布
4. **类型定义标准化** - 集中定义所有数据结构和类型
5. **提升可维护性** - 降低代码耦合度,提高可读性和可维护性
### 预期收益
- 减少代码重复和冗余
- 提高开发效率和调试便利性
- 增强代码的可扩展性和可移植性
- 为后续功能开发奠定坚实基础
## 🏗️ 重构实施
### 1. 配置参数统一管理
#### 重构前状态
- 配置参数散落在多个头文件中
- 存在重复定义和不一致的问题
- 网络相关配置分散在`network.h`
- 缺乏统一的配置管理机制
#### 重构措施
- **集中到config.h**:将所有配置宏定义迁移到`config.h`文件
- **分类管理**:按功能模块对配置参数进行分组
- **消除重复**:移除重复的宏定义,确保唯一性
- **标准化命名**:统一配置参数的命名规范
#### 配置分类结构
```c
// 棋盘配置
#define BOARD_SIZE 15
#define MIN_BOARD_SIZE 5
#define MAX_BOARD_SIZE 25
// 游戏模式配置
#define MODE_HUMAN_VS_AI 1
#define MODE_HUMAN_VS_HUMAN 2
#define MODE_NETWORK_BATTLE 3
// AI参数配置
#define DEFAULT_AI_DEPTH 3
#define MAX_AI_DEPTH 6
#define AI_TIMEOUT_MS 5000
// 网络配置
#define DEFAULT_PORT 8888
#define BUFFER_SIZE 1024
#define MAX_IP_LENGTH 16
// 评分参数
#define SCORE_FIVE 100000
#define SCORE_LIVE_FOUR 10000
#define SCORE_RUSH_FOUR 1000
```
### 2. 全局变量统一管理
#### 重构前状态
- 全局变量分散在各个源文件中
- 缺乏统一的声明和定义管理
- 变量作用域不清晰
- 初始化逻辑分散
#### 重构措施
- **创建globals模块**:新建`globals.h``globals.c`文件
- **集中声明**:在`globals.h`中统一声明所有全局变量
- **集中定义**:在`globals.c`中统一定义和初始化
- **访问规范化**:通过包含`globals.h`访问全局变量
#### 全局变量分类
```c
// 游戏状态变量
extern int current_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
extern int current_player;
extern int game_over;
// 配置变量
extern GameConfig game_config;
extern AIConfig ai_config;
extern NetworkConfig network_config;
// 统计变量
extern GameStats game_stats;
extern int total_games_played;
```
### 3. 类型定义标准化
#### 重构前状态
- 结构体定义分散在各个头文件中
- 类型定义不统一
- 缺乏标准的数据结构规范
#### 重构措施
- **创建type.h**:集中定义所有数据结构和类型
- **标准化命名**:采用统一的命名规范
- **逻辑分组**:按功能对类型进行分组
- **文档化**:为每个类型添加详细注释
#### 类型定义结构
```c
// 基础类型定义
typedef enum {
PLAYER_NONE = 0,
PLAYER_BLACK = 1,
PLAYER_WHITE = 2
} PlayerType;
// 游戏配置结构
typedef struct {
int board_size;
int ai_level;
int enable_forbidden;
int time_limit;
} GameConfig;
// 网络消息结构
typedef struct {
int type;
int x, y;
int player;
char data[256];
} NetworkMessage;
```
### 4. 网络配置重构
#### 具体实施
1. **迁移宏定义**:将`network.h`中的配置宏移动到`config.h`
2. **统一命名**:规范网络相关宏的命名
3. **添加引用**:在`network.h`中添加`#include "config.h"`
4. **消息类型统一**:将消息类型定义集中管理
#### 迁移的配置项
```c
// 从network.h迁移到config.h
#define DEFAULT_PORT 8888
#define BUFFER_SIZE 1024
#define MAX_IP_LENGTH 16
#define MSG_MOVE 1
#define MSG_CHAT 2
#define MSG_SURRENDER 3
#define MSG_DISCONNECT 4
```
## 📊 重构效果评估
### 代码质量提升
- **模块耦合度降低**:各模块职责更加清晰
- **代码重复减少**:消除了重复的宏定义和类型定义
- **可读性增强**:统一的命名规范和代码结构
- **维护性提高**:集中管理使得修改更加便捷
### 开发效率提升
- **配置修改便捷**:只需在一个地方修改配置参数
- **调试更容易**:全局变量集中管理,状态更清晰
- **扩展更简单**:标准化的接口和数据结构
- **错误减少**:统一管理避免了不一致性错误
### 性能影响
- **编译时间**:略有增加(由于更多的头文件包含)
- **运行时性能**:无显著影响
- **内存使用**:无显著变化
- **整体评估**:性能影响微乎其微,收益远大于成本
## 🔧 技术细节
### 文件结构变化
#### 新增文件
- `type.h` - 类型定义集中文件
- `globals.h` - 全局变量声明文件
- `globals.c` - 全局变量定义文件
#### 修改文件
- `config.h` - 扩展为完整的配置管理文件
- `network.h` - 移除配置定义,添加config.h引用
- 所有源文件 - 更新头文件包含关系
### 编译依赖关系
```
type.h (基础类型)
config.h (配置参数)
globals.h (全局变量声明)
各功能模块头文件
源文件实现
```
### 包含关系规范
1. **type.h**:被所有需要类型定义的文件包含
2. **config.h**:被所有需要配置参数的文件包含
3. **globals.h**:被所有需要访问全局变量的文件包含
4. **功能模块头文件**:按需包含上述基础头文件
## 📝 最佳实践
### 配置管理
1. **新增配置参数**:统一添加到`config.h`的相应分组中
2. **命名规范**:使用描述性的宏名称,避免缩写
3. **分组管理**:按功能模块对配置进行逻辑分组
4. **文档注释**:为每个配置参数添加清晰的注释
### 全局变量管理
1. **声明规范**:在`globals.h`中使用extern声明
2. **定义规范**:在`globals.c`中进行实际定义和初始化
3. **访问规范**:通过包含`globals.h`访问,避免重复声明
4. **初始化管理**:在`globals.c`中集中进行初始化
### 类型定义管理
1. **命名规范**:使用PascalCase命名结构体和枚举
2. **分组管理**:按功能对类型进行逻辑分组
3. **文档化**:为每个类型和字段添加详细注释
4. **版本兼容**:考虑结构体的向后兼容性
## 🚀 未来扩展
### 短期计划
1. **配置文件增强**:支持更多配置项的动态加载
2. **类型安全增强**:添加更多的类型检查和验证
3. **模块接口标准化**:定义标准的模块接口规范
### 长期规划
1. **插件架构**:基于当前架构实现插件系统
2. **配置热重载**:支持运行时配置的动态更新
3. **跨平台适配**:利用统一架构实现跨平台支持
## 📚 参考资料
### 相关文档
- [C语言编程规范](https://www.kernel.org/doc/html/latest/process/coding-style.html)
- [软件架构设计原则](https://en.wikipedia.org/wiki/Software_architecture)
- [模块化编程最佳实践](https://en.wikipedia.org/wiki/Modular_programming)
### 工具推荐
- **静态分析**:使用cppcheck进行代码质量检查
- **格式化**:使用clang-format统一代码格式
- **文档生成**:使用Doxygen生成API文档
## 📈 总结
v7.0版本的代码架构重构是一次重要的技术升级,通过系统性的重构实现了:
**配置参数的统一管理** - 提高了配置的一致性和可维护性
**全局变量的规范化** - 降低了代码的复杂度和耦合度
**类型定义的标准化** - 增强了代码的可读性和类型安全
**模块结构的优化** - 为后续功能扩展奠定了坚实基础
这次重构不仅解决了当前的技术债务,更为项目的长期发展提供了良好的架构基础。后续的功能开发将能够更加高效和稳定地进行。
---
*本文档将随着项目的发展持续更新,记录架构演进的每一个重要节点。*
-152
View File
@@ -1,152 +0,0 @@
# 五子棋网络对战使用说明
## 功能概述
本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。
## 编译方法
```bash
gcc -std=c17 -o gobang.exe *.c -lws2_32
```
**注意:**
- Windows系统需要添加 `-lws2_32` 链接库
- Linux系统不需要此链接库参数
## 使用方法
### 1. 启动游戏
运行编译后的程序
```bash
.\gobang.exe
```
### 2. 选择网络对战模式
在主菜单中选择 `3. 网络对战`
### 3. 选择连接模式
#### 模式1:创建房间(作为服务器)
- 选择 `1. 创建房间(作为服务器)`
- 输入监听端口(默认8888,建议使用1024-65535范围内的端口)
- 程序会显示本机IP地址,将此IP告知对方
- 等待对方连接
#### 模式2:加入房间(连接到服务器)
- 选择 `2. 加入房间(连接到服务器)`
- 输入服务器IP地址
- 输入服务器的端口号(与服务器设置的端口一致)
- 连接到服务器
### 4. 开始游戏
- 连接成功后游戏自动开始
- 服务器为玩家1(黑棋),客户端为玩家2(白棋)
- 玩家1先手
## 游戏操作
### 基本操作
- **落子**:输入坐标 `行号 列号`,例如:`7 7`
- **认输**:输入 `S``s`
- **悔棋**:输入 `R``r`(需要对方同意)
### 网络功能
- **自动同步**:落子操作会自动同步给对方
- **连接检测**:自动检测网络连接状态
- **延时显示**:支持回合延时显示,避免过快操作
- **悔棋协商**:悔棋需要对方同意才能生效
## 网络配置
### 端口设置
- 默认端口:8888
- 可设端口范围:1024-65535
- 确保防火墙允许选定端口的通信
### IP地址
- **局域网**:使用内网IP地址,例如:192.168.1.100
- **广域网**:使用公网IP地址,可能需要路由器端口转发
### 防火墙设置
如果连接失败,请检查防火墙设置:
#### Windows防火墙
1. 打开Windows安全中心
2. 选择"防火墙和网络保护"
3. 选择"允许应用通过防火墙"
4. 添加gobang.exe到允许列表
#### 路由器设置(用于广域网对战)
如果通过互联网对战,可能需要:
1. 在路由器中设置端口转发
2. 将选定端口转发到服务器内网IP
3. 将路由器的公网IP告知对方
## 故障排除
### 常见问题
1. **连接失败**
- 检查IP地址和端口是否正确
- 确认防火墙设置
- 确保两台设备网络连通
2. **游戏中断**
- 检查网络连接稳定性
- 重新启动游戏重新连接
3. **端口被占用**
- 更换其他端口号
- 关闭占用端口的其他程序
### 网络测试
可以使用以下命令测试网络连通性:
```bash
# 测试网络连通
ping <对方IP地址>
# 测试端口(需要telnet客户端)
telnet <对方IP地址> <端口号>
```
## 技术细节
- **协议**TCP/IP
- **消息格式**:自定义二进制协议
- **支持功能**
- 棋盘同步
- 悔棋处理
- 认输协商
- 连接检测
- 延时控制
## 安全注意事项
1. **局域网使用**:建议安全的家庭或办公环境
2. **广域网使用**
- 不要使用默认端口8888
- 游戏结束后及时关闭程序
- 注意保护个人网络信息
## 更新日志
### v7.0 (2025-07-20)
- 网络配置参数统一管理 - 所有网络相关配置集中到config.h
- 消息类型定义优化 - 统一消息协议宏定义
- 代码架构重构 - 提升网络模块的可维护性
- 配置文件支持 - 网络参数可通过配置文件调整
### v6.1 (2025-07-10)
- 完善网络对战功能
- 支持TCP/IP通信
- 实现棋盘同步
- 连接状态和协议优化
- 支持多种操作
- 添加延时控制等游戏功能(延时显示、认输、悔棋等)
---
**开发者:** 刘航宇
**联系邮箱:** 3364451258@qq.com
**项目主页:** https://github.com/LHY0125/Gobang-Game
+33 -278
View File
@@ -1,299 +1,54 @@
# C语言五子棋人机对战AI
![Build Status](https://img.shields.io/badge/build-passing-brightgreen)
![License](https://img.shields.io/badge/license-MIT-blue)
![Version](https://img.shields.io/badge/version-v7.0-blue)
![Platform](https://img.shields.io/badge/platform-Windows-lightgrey)
> 🎯 **最新版本 v7.0** - 代码架构重构更新,实现结构体定义集中化、配置参数统一管理、代码模块化优化等核心改进
## 📋 大版本更新
### v7.0 (2025-07-20) - 代码架构重构更新
- 🏗️ **结构体定义集中化** - 所有数据结构统一管理在type.h中
- ⚙️ **配置参数统一管理** - 所有配置宏定义集中在config.h中
- 🔧 **代码模块化优化** - 消除重复定义,提高代码可维护性
- 📋 **菜单选项优化** - 退出选项调整为"0. 退出游戏"
- 🎯 **类型系统完善** - 独立的type.h文件管理所有数据类型
- 🌐 **网络配置重构** - 网络相关宏定义统一到config.h
- 📊 **全局变量管理** - 优化全局变量声明和定义结构
- 🔄 **头文件依赖优化** - 改进模块间依赖关系和包含结构
## 目录
- [C语言五子棋人机对战AI](#c语言五子棋人机对战ai)
- [📋 大版本更新](#-大版本更新)
- [v7.0 (2025-07-20) - 代码架构重构更新](#v70-2025-07-20---代码架构重构更新)
- [目录](#目录)
- [项目简介](#项目简介)
- [功能特性](#功能特性)
- [🎮 游戏模式](#-游戏模式)
- [⚙️ 游戏设置](#-游戏设置)
- [🎯 游戏功能](#-游戏功能)
- [💻 用户体验](#-用户体验)
- [🔧 技术特性](#-技术特性)
- [快速开始](#快速开始)
- [编译项目](#编译项目)
- [运行游戏](#运行游戏)
- [游戏玩法](#游戏玩法)
- [🚀 快速开始](#-快速开始)
- [🎯 对局操作](#-对局操作)
- [⚙️ 配置管理](#-配置管理)
- [📊 复盘功能](#-复盘功能)
- [🌐 网络对战功能](#-网络对战功能)
- [环境要求](#环境要求)
- [常见问题](#常见问题)
- [权限问题](#权限问题)
- [乱码显示问题](#乱码显示问题)
- [AI 设计实现](#ai-设计实现)
- [核心算法](#核心算法)
- [棋局评估函数](#棋局评估函数)
- [项目结构](#项目结构)
- [📁 核心模块](#-核心模块)
- [🎮 功能模块](#-功能模块)
- [📄 配置和文档](#-配置和文档)
- [🔧 开发工具](#-开发工具)
- [许可证](#许可证)
- [欢迎贡献](#欢迎贡献)
- [未来计划](#未来计划)
- [✅ 已完成功能](#-已完成功能)
- [🚀 开发路线图](#-开发路线图)
- [📱 用户体验提升](#-用户体验提升)
- [🌐 网络功能](#-网络功能)
- [🧠 AI增强](#-ai增强)
- [🔧 技术优化](#-技术优化)
# 五子棋人机对战AI
## 项目简介
这是一个使用C语言实现的现代化五子棋对战系统,支持人机对战、双人对战和网络对战三种模式。系统基于 Alpha-Beta 剪枝优化的 Minimax 算法,具备完整的配置管理、复盘分析、智能评分和网络通信功能。
基于C语言实现的五子棋人机对战系统,采用α-β剪枝优化的极小极大算法,支持自定义棋盘大小和游戏复盘功能。
## 功能特性
### 🎮 游戏模式
- **人机对战模式** - 与智能AI进行对弈
- **双人对战模式** - 支持本地双人游戏
- **复盘模式** - 回顾和分析历史对局
### ⚙️ 游戏设置
- **自定义棋盘尺寸** - 支持5x5至25x25可调节棋盘
- **多级AI难度** - 1-5级智能难度可选
- **配置管理系统** - 持久化保存游戏设置
- **禁手规则支持** - 可选启用五子棋标准禁手规则
- **回合计时器** - 可设置每回合思考时间限制
### 🎯 游戏功能
- **实时对局控制** - 悔棋、认输、保存等操作
- **自动游戏记录** - 完整保存对局过程到CSV文件
- **智能评分系统** - 对每步棋进行专业评分和分析
- **完整复盘功能** - 逐步回放对局并显示评分
- **MVP评选系统** - 自动评选对局最佳表现者
### 💻 用户体验
- **现代化UI界面** - 清晰直观的终端用户界面
- **完备输入验证** - 确保所有用户输入的有效性和安全性
- **智能错误提示** - 详细的错误信息和操作指导
- **跨平台兼容** - 支持Windows系统,预留跨平台扩展
### 🔧 技术特性
- **模块化架构** - 清晰的代码结构,便于维护和扩展
- **结构体定义集中化** - 所有数据结构统一在type.h中管理
- **配置参数统一管理** - 所有配置宏定义集中在config.h中
- **全局变量统一管理** - 所有全局变量集中在globals模块中管理
- **内存优化管理** - 高效的内存使用和资源管理
- **配置文件支持** - INI格式配置文件自动加载保存
- **UTF-8编码支持** - 完美支持中文显示
- **网络对战功能** - 完整的在线多人对战系统
- 🎮 人机对战模式
- ⚙️ 可调棋盘尺寸(5x5到25x25)
- 🧠 智能AI决策(1-5级难度)
- 🔍 完整游戏复盘功能
- 📊 清晰的终端界面显示
## 快速开始
### 编译项目
### 编译程序
```bash
gcc -std=c17 -o gobang.exe *.c -lws2_32
gcc 五子棋.c gobang.c -o output/五子棋.exe
```
或者使用优化编译:
```bash
gcc -O2 -o gobang.exe main.c gobang.c game_mode.c ai.c record.c init_board.c ui.c config.c globals.c network.c -lws2_32
```
**注意:** Windows系统需要添加 `-lws2_32` 链接库以支持网络功能
### 运行游戏
```bash
.\gobang.exe
.\output\五子棋.exe
```
## 游戏玩法
1. 启动后设置棋盘大小(默认15x15)
2. 选择AI难度级别(1-5)
3. 输入坐标进行游戏(格式:行 列)
4. 游戏结束可查看完整复盘
### 🚀 快速开始
1. **启动游戏**:运行 `gobang.exe` 进入主菜单
2. **选择模式**
- `1` - **人机对战模式** - 与AI智能对手进行五子棋对战
- `2` - **双人对战模式** - 两名玩家轮流对弈的本地对战
- `3` - **网络对战模式** - 通过网络与远程玩家实时对战
- `4` - **复盘模式** - 回放历史对局并查看详细分析
- `5` - **配置管理** - 自定义游戏设置和参数调整
- `6` - **游戏规则** - 查看五子棋游戏规则和操作说明
- `7` - **关于信息** - 查看项目版本和开发者信息
- `0` - **退出游戏** - 安全退出程序
### 🎯 对局操作
- **落子**:输入坐标 (格式: `行 列`,如 `8 8`)
- **悔棋**:输入 `R``r` 撤销上一步
- **认输**:输入 `S``s` 主动认输
- **保存**:输入 `SAVE` 保存当前对局
- **退出**:输入 `EXIT` 退出当前对局
### ⚙️ 配置管理
- **棋盘大小**5x5 至 25x25 可调 (默认15x15)
- **AI难度**1-5级智能难度 (默认3级)
- **禁手规则**:可选启用标准五子棋禁手
- **计时器**:可设置每回合时间限制
- **配置保存**:所有设置自动保存到 `gobang_config.ini`
### 📊 复盘功能
- **自动记录**:每局游戏自动保存到 `records/` 目录
- **逐步回放**:按步骤重现整局对弈过程
- **评分分析**:显示每步棋的专业评分
- **MVP评选**:自动评选本局最佳表现者
- **胜负统计**:完整的对局结果记录
### 🌐 网络对战功能
- **服务器模式**:创建游戏房间等待其他玩家加入
- **客户端模式**:连接到指定服务器进行对战
- **实时同步**:棋盘状态和游戏进度实时同步
- **连接管理**:自动处理网络连接和断线重连
- **延迟显示**:实时显示网络延迟状态
- **安全验证**:基本的数据验证和防作弊检测
## 环境要求
- 操作系统: Windows (当前版本使用了Windows特有的 `_kbhit()``Sleep()` 函数,因此暂不跨平台)
- 编译器: GCC (MinGW-w64)
- 终端: 支持UTF-8编码的终端
> **跨平台兼容性说明:**
>
> 为了未来在Linux或macOS等其他操作系统上运行,需要将平台特定的代码(如 `_kbhit()`)替换为跨平台的实现,或使用条件编译(`#ifdef _WIN32`)进行隔离。
## 常见问题
### 权限问题
如果在保存游戏记录时提示“无法创建文件”,这通常是由于程序缺少写入权限。请尝试以下解决方案:
1. **以管理员身份运行**:右键点击 `gobang.exe` 并在管理员权限的终端中运行程序。
2. **更改项目目录权限**:确保项目目录不在受系统保护的目录(如 `C:\Program Files`),建议将项目放在用户目录下,例如 `D:\Code`
3. **手动创建 `records` 目录**:如果 `records` 目录不存在,请在 `gobang.exe` 所在目录手动创建一个。
### 乱码显示问题
如果在Windows终端中出现中文字符显示为乱码,这是由于终端代码页不匹配导致的。请在程序运行前执行以下命令:
```bash
chcp 65001
```
这会把当前终端的代码页切换为UTF-8,从而正确显示中文字符。为了方便,你可以创建一个批处理文件 `.bat` 来自动执行此操作。
**start_game.bat**
```batch
@echo off
chcp 65001
.\gobang.exe
```
## AI 设计实现
项目的AI主要基于以下技术实现:
## 技术实现
### 核心算法
- α-β剪枝优化的极小极大算法
- 3层搜索深度(可调)
- 威胁检测优先机制
- **Minimax算法 (Minimax)**:作为博弈树的基础模型,为双人对弈的每一步选择最优解法。
- **Alpha-Beta 剪枝 (Alpha-Beta Pruning)**:对Minimax算法的重大优化,通过剪掉那些不影响最终决策的树枝来提高AI的计算效率,使其能够在有限时间内达到更深的搜索深度。
- **搜索深度**:AI的思考深度,默认为3层,可以根据难度等级进行调整。深度越大,AI预测能力越强,但计算耗时也越长。
### 评估系统
- 活四/冲四/活三等棋型识别
- 位置权重评估(中心优先)
- 双向延伸检测
### 棋局评估函数
## 开发说明
### 代码结构
- `五子棋.c` - 主程序(游戏流程控制)
- `gobang.c` - 核心算法实现
- `gobang.h` - 公共定义和接口
为了对棋局进行价值评估,AI使用了一套复杂的评分系统,其主要依据包括:
- **棋型识别 (Pattern Recognition)**:能够识别并评估游戏中的关键棋型,如“连五”、“活四”、“冲四”、“活三”等,并为每种棋型赋予不同权重。
- **位置权重 (Positional Value)**:棋盘上不同位置的战略价值不同,中心位置通常比边缘位置更有优势。评估函数会为棋盘上的落子点附加位置分。
- **威胁检测 (Threat Detection)**:评估那些能够直接形成制胜局面的落子点,如“四三”或“活三”,并对这些点给予极高的评价值,以抓住制胜机会。
- **双向连通性**:在评估一个点时,会同时判断其是否拥有足够的空间形成有效棋型,避免在被封锁的位置下出无效棋。
## 项目结构
### 📁 核心模块
- **`main.c`** - 主程序入口,负责初始化与游戏模式选择
- **`gobang.c/h`** - 核心游戏逻辑,包括棋盘操作、胜负判断
- **`game_mode.c/h`** - 游戏模式实现 (人机对战、双人对战、复盘模式)
- **`ai.c/h`** - AI算法实现 (Minimax + Alpha-Beta剪枝)
### 🎮 功能模块
- **`ui.c/h`** - 用户界面模块,负责所有显示和交互
- **`record.c/h`** - 游戏记录系统 (保存、加载、复盘、评分)
- **`init_board.c/h`** - 棋盘初始化和游戏设置
- **`config.c/h`** - 配置管理系统 (参数设置、文件读写)
- **`globals.c/h`** - 全局变量统一管理模块
- **`network.c/h`** - 网络功能模块 (为未来网络对战预留)
### 📄 配置和文档
- **`gobang_config.ini`** - 游戏配置文件 (自动生成和保存)
- **`records/`** - 对局记录目录 (CSV格式存储)
- **`type.h`** - 数据结构和类型定义集中文件
- **`MD/README.md`** - 项目说明文档
- **`MD/AI_Enhancement_Guide.md`** - AI算法增强指南
- **`MD/NETWORK_README.md`** - 网络功能使用说明
- **`MD/Architecture_Refactoring_Guide.md`** - 代码架构重构详细指南
### 🔧 开发工具
- **`.vscode/`** - VS Code 配置文件
- **`.idea/`** - IntelliJ IDEA 配置文件
- **`.vs/`** - Visual Studio 配置文件
### 关键函数
- `ai_move()` - AI决策入口
- `evaluate_pos()` - 位置评估
- `dfs()` - α-β剪枝搜索
- `check_win()` - 胜负判断
## 许可证
本项目采用 [MIT 许可证](https://opensource.org/licenses/MIT)授权。
这意味着你可以自由地使用、复制、修改、合并、出版、分发、再授权和/或销售本软件的副本,只需在所有副本或重要部分中包含原始的版权声明和本许可声明即可。
## 欢迎贡献
我们非常欢迎任何形式的反馈和贡献!如果你发现了Bug、有功能建议,或希望改进代码,请随时通过以下方式参与:
- **提交 Issue**:对于问题反馈或新想法,请在 [GitHub Issues](https://github.com/LHY0125/Gobang-Game/issues) 页面提交详细描述。
- **发起 Pull Request**:如果你对源码进行了改进,欢迎提交 Pull Request。请确保你的代码风格与项目保持一致,并提供清晰的改动说明。
你的每一次贡献都将使这个项目变得更好!
## 未来计划
### ✅ 已完成功能
- [x] **模块化架构设计** - 完成代码重构,实现清晰的模块分离
- [x] **全局变量统一管理** - 所有全局变量集中在globals模块中管理
- [x] **宏定义优化** - 消除重复定义,统一管理所有宏定义
- [x] **配置管理系统** - 实现INI配置文件的自动加载和保存
- [x] **完整复盘功能** - 支持对局记录、回放和专业评分分析
- [x] **用户界面优化** - 实现现代化的终端UI界面
- [x] **智能评分系统** - 完成每步棋的评分和MVP评选功能
- [x] **禁手规则支持** - 添加标准五子棋禁手规则选项
- [x] **网络模块预留** - 为未来网络对战功能预留完整接口
### 🚀 开发路线图
#### 📱 用户体验提升
- [ ] **图形用户界面 (GUI)**:使用 `SDL2``Qt` 开发现代化图形界面
- [ ] **主题系统**:支持多种UI主题和棋盘样式
- [ ] **音效系统**:添加落子音效和背景音乐
#### 🌐 网络功能
- [ ] **在线对战模式**:实现网络多人对战功能
- [ ] **排行榜系统**:在线玩家等级和排名系统
- [ ] **观战功能**:支持观看其他玩家对局
#### 🧠 AI增强
- [ ] **开局库系统**:集成专业开局棋谱数据库
- [ ] **神经网络AI**:基于深度学习的高级AI对手
- [ ] **AI训练模式**:允许AI通过对局学习和改进
#### 🔧 技术优化
- [ ] **跨平台支持**:完整支持Linux和macOS系统
- [ ] **性能优化**:多线程搜索和内存优化
- [ ] **数据库支持**:使用SQLite存储对局历史和统计
自由使用,请注明原作者。
-158
View File
@@ -1,158 +0,0 @@
五子棋对战系统 - 代码统计报告
========================================
项目名称:五子棋多模式对战系统
统计时间:2025年7月20日
项目版本:v7.0
开发语言:C语言
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
========================================
📊 代码行数统计
========================================
【C源文件 (.c)】
├── main.c 85行
├── gobang.c 269行
├── game_mode.c 917行
├── ai.c 589行
├── record.c 531行
├── init_board.c 118行
├── ui.c 204行
├── config.c 331行
├── network.c 426行
└── globals.c 37行
【头文件 (.h)】
├── gobang.h 101行
├── game_mode.h 99行
├── ai.h 39行
├── record.h 45行
├── init_board.h 35行
├── ui.h 62行
├── config.h 170行
├── network.h 186行
├── globals.h 41行
└── type.h 93行
========================================
📈 总计统计
========================================
总代码行数:4,398行
文件类型分布:
• C源文件:3,527行 (80.2%)
• 头文件:871行 (19.8%)
模块代码分布:
• 游戏模式模块:917行 (20.9%)
• AI智能模块:589行 (13.4%)
• 记录系统模块:531行 (12.1%)
• 网络对战模块:426行 (9.7%)
• 配置管理模块:331行 (7.5%)
• 核心游戏模块:269行 (6.1%)
• 用户界面模块:204行 (4.6%)
• 配置参数模块:170行 (3.9%)
• 棋盘初始化模块:118行 (2.7%)
• 类型定义模块:93行 (2.1%)
• 主程序模块:84行 (1.9%)
• 全局变量模块:41行 (0.9%)
========================================
💬 注释统计
========================================
【注释统计】
总注释行数:1,248行
注释覆盖率:30.1%
注释类型分布:
• 函数说明注释:498行 (39.9%)
• 代码逻辑注释:425行 (34.1%)
• 文件头注释:325行 (26.0%)
注释质量分析:
• 文件头注释:每个文件都有详细的文档头
• 函数注释:使用Doxygen格式的完整函数文档
• 行内注释:关键逻辑的解释说明
• 分块注释:代码段落的功能说明
估算注释字数:约8,500-10,000字
注释内容包括:
• 详细的函数参数和返回值说明
• 算法逻辑的中文解释
• 代码块的功能描述
• 重要变量和常量的用途说明
• 网络协议和数据结构的详细文档
========================================
🏆 代码质量评价
========================================
【优秀特点】
✓ 注释覆盖率高:几乎每个函数都有详细文档
✓ 代码结构清晰:模块化设计,职责分离明确
✓ 命名规范:变量和函数名具有良好的可读性
✓ 文档完整:包含完整的API文档和使用说明
✓ 架构合理:网络模块、AI模块、UI模块分离
✓ 跨平台支持:Windows和Linux双平台兼容
【技术亮点】
• 完整的网络对战功能实现
• 智能AI算法与评估系统
• 灵活的配置管理系统
• 详细的游戏记录与复盘功能
• 规范的禁手规则实现
• 实时计时器系统
• 全局变量统一管理
• 跨平台网络通信支持
• 代码架构模块化重构(v7.0新增)
• 配置参数集中化管理(v7.0新增)
• 类型定义标准化(v7.0新增)
【总体评价】
这是一个非常优秀的C语言项目,代码量适中但功能完整,
注释详尽,体现了良好的编程习惯和专业素养!
项目从单一的五子棋游戏发展为支持多种对战模式的完整系统,
包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
是C语言项目开发的优秀范例。
网络对战功能的加入使得项目具备了现代化游戏的特征,
支持实时在线对战,为用户提供了更丰富的游戏体验。
v7.0版本的代码架构重构是项目发展的重要里程碑,
通过配置统一管理、全局变量规范化、类型定义标准化等措施,
大幅提升了代码的可维护性和扩展性,为后续功能开发
奠定了坚实的架构基础。
========================================
📋 项目文件结构
========================================
核心模块:
• main.c/gobang.c - 主程序和核心游戏逻辑
• game_mode.c/h - 游戏模式管理(人机/双人/网络)
• ai.c/h - AI智能算法实现
• network.c/h - 网络对战功能
• record.c/h - 游戏记录与复盘
• ui.c/h - 用户界面管理
• config.c/h - 配置文件管理
• init_board.c/h - 棋盘初始化
• globals.c/h - 全局变量统一管理
• type.h - 数据结构和类型定义集中文件(v7.0新增)
配置文件:
• gobang_config.ini - 游戏配置文件
文档目录:
• MD/ - 项目文档目录
• records/ - 游戏记录存储目录
开发环境:
• .vscode/ - VS Code配置
• .idea/ - IntelliJ IDEA配置
========================================
-87
View File
@@ -1,87 +0,0 @@
/**
* @file 五子棋对战系统
* @brief C语言五子棋多模式对战系统
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
* @date 2025-07-20
* @version 7.0
* @note
* 1. v7.0架构重构:
* - 🏗️ 代码架构全面重构,实现模块化设计
* - 📋 配置参数统一管理,所有配置集中到config.h
* - 🔧 全局变量规范化,统一在globals模块管理
* - 📝 类型定义标准化,集中在type.h中定义
* - 🌐 网络配置重构,从network.h迁移到config.h
* - 🔄 消除重复定义,提高代码一致性
* - 📚 完善文档体系,新增架构重构指南
* 2. v6.1完善功能:
* - 🌐 完善的网络对战模式,支持服务器/客户端架构
* - 🔗 实时数据同步,支持落子、悔棋、认输等网络功能
* - 🛡️ 网络安全验证和连接状态管理
* - 📡 跨平台网络支持(Windows/Linux
* - 🔧 全局变量统一管理,优化代码结构
* - 📋 宏定义统一管理,消除重复定义
* - 🔄 网络协议优化,改进通信稳定性
* 2. 核心游戏功能:
* - 增加了对禁手规则的支持,防止玩家进行无意义的走法。
* - 新增了游戏计时器功能,限制每回合的思考时间。
* - 添加了复盘功能,支持保存和回顾对局记录。
* - 实现了评分系统,可以对每一步棋进行评分和分析。
* 3. 性能优化:
* - 🚀 优化了AI算法,使用Alpha-Beta剪枝提高搜索效率
* - 🎨 改进了棋盘渲染算法,减少了不必要的重绘操作
* - 💾 增加了内存管理优化,避免内存泄漏问题
* - ⚡ 网络通信优化,支持异步消息处理
* - 🔍 智能评分算法优化,提升AI决策质量
* 4. 用户界面改进:
* - 🎮 美化了游戏界面,增加了更多的视觉效果
* - ⌨️ 改进了用户交互体验,增加了快捷键支持
* - 🔊 添加了音效和背景音乐,提升游戏沉浸感
* - 💬 网络对战聊天界面,支持实时交流
* - 📊 游戏状态显示优化,清晰展示连接状态
* 5. 代码结构优化:
* - 🏗️ 重构了代码架构,提高了代码的可读性和可维护性
* - 📝 增加了详细的注释和文档,便于理解和修改
* - 🧩 采用了模块化设计,各功能模块相对独立
* - 🌍 新增网络模块,完整的网络通信架构
* - 🔧 全局状态统一管理,消除代码重复
* - 📋 配置文件标准化,支持灵活配置
* 6. 异常处理:
* - 🛡️ 增加了输入错误的异常处理机制,确保游戏的稳定性
* - 💡 优化了错误提示信息,帮助用户快速定位问题
* - 🔄 增加了程序崩溃恢复功能,提高游戏的可靠性
* - 🌐 网络连接异常处理,自动重连和超时管理
* - 📡 消息传输错误处理,确保数据完整性
* 7. 文档更新:
* - 📚 更新了README文件,提供详细的安装和使用说明
* - 💬 增加了代码注释,提高代码的可读性
* - 👨‍💻 添加了开发者文档,便于后续的功能扩展
* - 🌐 新增网络对战使用指南和配置说明
* - 🔧 API文档完善,支持二次开发
* 8. 版本控制:
* - 📦 使用Git进行版本控制,便于代码管理和协作开发
* - 🚀 建立了清晰的版本发布流程,确保代码质量
* - 🏷️ v7.0版本更新,代码架构全面重构
- 🏷️ v6.1版本更新,网络功能完善优化
* - 📋 完整的变更日志,追踪功能演进
* 9. 测试:
* - ✅ 进行了全面的功能测试,确保各项功能正常运行
* - 🧪 增加了单元测试,提高代码的可靠性
* - ⚡ 进行了性能测试,优化了程序的运行效率
* - 🌐 网络功能压力测试,确保多人对战稳定性
* - 🔒 安全性测试,验证网络通信安全
* - 🔄 协议兼容性测试,确保通信协议稳定
* 10. 开源协议:
* - 📄 选择了MIT开源协议,允许用户自由使用、修改和分发代码
* - 🤝 欢迎社区贡献,共同完善项目
* 11. 贡献者:
* - 👨‍💻 感谢所有为项目做出贡献的开发者和用户
* - 🌟 特别感谢网络功能开发、测试和优化的贡献者
* 12. 联系信息:
* - 📧 如有问题或建议,请联系开发者:
* - 3364451258@qq.com
* - 15236416560@163.com
* - lhy3364451258@outlook.com
* - 🐛 Bug报告和功能建议欢迎通过邮件反馈
* - 💡 网络对战相关问题请详细描述网络环境和连接状态
*/
-589
View File
@@ -1,589 +0,0 @@
#include "gobang.h"
#include "ai.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// ==================== 辅助函数声明 ====================
static int compare_moves(const void *a, const void *b);
/**
* @brief 评估一个落子位置的综合得分(结合进攻和防守)
* @param x 行坐标
* @param y 列坐标
* @return int 综合得分
*/
int evaluate_move(int x, int y)
{
// 进攻得分:评估AI在此处落子的分数
int attack_score = evaluate_pos(x, y, AI);
// 防守得分:评估玩家在此处落子的分数,作为防守的依据
int defense_score = evaluate_pos(x, y, PLAYER);
// 综合得分,防守权重由 defense_coefficient 控制
return attack_score + (int)(defense_score * defense_coefficient);
}
/**
* @brief 评估特定位置对当前玩家的战略价值
* @param x 行坐标(0-base)
* @param y 列坐标(0-base)
* @param player 玩家标识(PLAYER/AI)
* @return int 综合评估分数(越高表示位置越好)
* @note 评分标准:
* - 活四:100000 冲四:10000 死四:500
* - 活三:5000 眠三:1000 死三:50
* - 活二:500 眠二:100 死二:10
* - 单子:50(开放)/10(半开放)/1(封闭)
* - 中心位置有额外加成
*/
int evaluate_pos(int x, int y, int player)
{
// 保存原始值用于还原
int original = board[x][y];
// 模拟在该位置落子
board[x][y] = player;
int total_score = 0; // 总分
int line_scores[4] = {0}; // 四个方向的得分
// 遍历四个方向进行评估
for (int i = 0; i < 4; i++)
{
int dx = direction[i][0], dy = direction[i][1];
// 获取当前方向上的棋型信息
DirInfo info = count_specific_direction(x, y, dx, dy, player);
// 直接形成五连珠为必胜
if (info.continuous_chess >= 5)
{
board[x][y] = original; // 还原棋盘
return SEARCH_WIN_BONUS; // 返回最大分
}
// 根据连续棋子数评分
switch (info.continuous_chess)
{
case 4: // 四连珠
if (info.check_start && info.check_end) // 活四(两端开放)
line_scores[i] = AI_SCORE_LIVE_FOUR;
else if (info.check_start || info.check_end) // 冲四(一端开放)
line_scores[i] = AI_SCORE_RUSH_FOUR;
else // 死四(两端封闭)
line_scores[i] = AI_SCORE_DEAD_FOUR;
break;
case 3: // 三连珠
if (info.check_start && info.check_end) // 活三
line_scores[i] = AI_SCORE_LIVE_THREE;
else if (info.check_start || info.check_end) // 眠三
line_scores[i] = AI_SCORE_SLEEP_THREE;
else // 死三
line_scores[i] = AI_SCORE_DEAD_THREE;
break;
case 2: // 二连珠
if (info.check_start && info.check_end) // 活二
line_scores[i] = AI_SCORE_LIVE_TWO;
else if (info.check_start || info.check_end) // 眠二
line_scores[i] = AI_SCORE_SLEEP_TWO;
else // 死二
line_scores[i] = AI_SCORE_DEAD_TWO;
break;
case 1: // 单子
if (info.check_start && info.check_end) // 开放位置
line_scores[i] = AI_SCORE_LIVE_ONE;
else if (info.check_start || info.check_end) // 半开放位置
line_scores[i] = AI_SCORE_HALF_ONE;
else // 封闭位置
line_scores[i] = AI_SCORE_DEAD_ONE;
break;
}
}
// 计算总分(最高方向分+其他方向分加权)
int max_score = 0;
int sum_score = 0;
for (int i = 0; i < 4; i++)
{
if (line_scores[i] > max_score)
{
max_score = line_scores[i];
}
sum_score += line_scores[i];
}
total_score = max_score * 10 + sum_score; // 主方向权重更高
// 位置奖励:越靠近中心分数越高
int center_x = BOARD_SIZE / 2;
int center_y = BOARD_SIZE / 2;
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
board[x][y] = original; // 还原棋盘状态
return total_score + position_bonus; // 返回总评估分
}
/**
* @brief 带α-β剪枝的深度优先搜索(极小极大算法实现)
* @param x 当前行坐标
* @param y 当前列坐标
* @param player 当前玩家
* @param depth 剩余搜索深度
* @param alpha α值(当前最大值)
* @param beta β值(当前最小值)
* @param is_maximizing 是否为极大化玩家(AI)
* @return int 最佳评估分数
* @note 算法流程:
* 1. 检查是否获胜或达到搜索深度
* 2. 遍历所有可能落子位置
* 3. 递归评估每个位置的分数
* 4. 根据is_maximizing选择最大/最小值
* 5. 使用α-β剪枝优化搜索过程
*/
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing)
{
// 检查当前落子是否获胜
if (check_win(x, y, player))
{
return (player == AI) ? SEARCH_WIN_BONUS + depth : -SEARCH_WIN_BONUS - depth;
}
// 达到搜索深度或平局
if (depth == 0 || step_count >= BOARD_SIZE * BOARD_SIZE)
{
return evaluate_pos(x, y, AI) - evaluate_pos(x, y, PLAYER);
}
int best_score = is_maximizing ? -1000000 : 1000000;
// 使用移动排序优化搜索效率
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
int move_count = generate_candidate_moves(candidate_moves, player);
// 限制搜索的候选移动数量以提高性能
int max_candidates = (depth >= 3) ? 15 : 25; // 深度越大,候选移动越少
if (move_count > max_candidates)
{
move_count = max_candidates;
}
// 遍历排序后的候选移动
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
// 模拟当前玩家落子
board[i][j] = player;
step_count++;
// 递归搜索(切换玩家和搜索深度)
int current_score = dfs(i, j, (player == AI) ? PLAYER : AI, depth - 1, alpha, beta, !is_maximizing);
// 撤销落子
board[i][j] = EMPTY;
step_count--;
// 极大值玩家(AI)逻辑
if (is_maximizing)
{
best_score = (current_score > best_score) ? current_score : best_score;
alpha = (best_score > alpha) ? best_score : alpha;
// α剪枝
if (beta <= alpha)
{
break;
}
}
// 极小值玩家(人类)逻辑
else
{
best_score = (current_score < best_score) ? current_score : best_score;
beta = (best_score < beta) ? best_score : beta;
// β剪枝
if (beta <= alpha)
{
break;
}
}
}
return best_score;
}
/**
* @brief AI决策主函数,使用评估函数和搜索算法选择最佳落子位置
* @note 采用两阶段决策逻辑:
* 1. 防御阶段:检查并阻止玩家即将获胜的位置(活四、冲四、活三)
* 2. 进攻阶段:若无紧急防御需求,使用评估函数选择最佳进攻位置
* @note 实现细节:
* - 优先处理玩家活四、冲四等危险局面
* - 步数>AI_SEARCH_RANGE_THRESHOLD时缩小搜索范围到已有棋子附近AI_NEARBY_RANGE格
* - 使用中心位置优先策略
*/
void ai_move(int depth)
{
// 如果是第一步,直接下在中心位置附近
if (step_count == 0)
{
int center = BOARD_SIZE / 2;
board[center][center] = AI;
steps[step_count++] = (Step){AI, center, center};
printf("AI落子(%d, %d)\n", center + 1, center + 1);
return;
}
// 1. 使用增强的威胁检测系统
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
int move_count = generate_candidate_moves(candidate_moves, AI);
// 首先检查是否有直接获胜的机会
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel ai_threat = detect_threat(i, j, AI);
if (ai_threat == THREAT_WIN)
{
// 直接获胜
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d) - 获胜!\n", i + 1, j + 1);
return;
}
}
// 检查是否需要阻止玩家的威胁
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
if (player_threat >= THREAT_FOUR)
{
// 必须阻止玩家的四子威胁
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d) - 防守!\n", i + 1, j + 1);
return;
}
}
// 检查是否需要阻止玩家的活三威胁
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
if (player_threat == THREAT_THREE)
{
// 阻止玩家的活三
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d) - 阻止活三!\n", i + 1, j + 1);
return;
}
}
// 2. 寻找最佳进攻位置
// 优先考虑能形成威胁的位置
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel ai_threat = detect_threat(i, j, AI);
if (ai_threat >= THREAT_FOUR)
{
// 形成四子威胁
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d) - 形成威胁!\n", i + 1, j + 1);
return;
}
}
// 寻找能形成活三的位置
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel ai_threat = detect_threat(i, j, AI);
if (ai_threat == THREAT_THREE)
{
// 形成活三
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d) - 形成活三!\n", i + 1, j + 1);
return;
}
}
// 3. 如果没有明显的威胁机会,选择评分最高的位置
if (move_count > 0)
{
// candidate_moves已经按分数排序,直接选择第一个
int best_x = candidate_moves[0].x;
int best_y = candidate_moves[0].y;
board[best_x][best_y] = AI;
steps[step_count++] = (Step){AI, best_x, best_y};
printf("AI落子(%d, %d) - 最佳位置!\n", best_x + 1, best_y + 1);
}
else
{
// 备用方案:如果没有候选移动,随机选择一个位置
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == EMPTY)
{
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d) - 备用位置!\n", i + 1, j + 1);
return;
}
}
}
}
}
// ==================== AI增强:辅助函数实现 ====================
/**
* @brief 比较函数,用于移动排序(按分数降序)
*/
static int compare_moves(const void *a, const void *b)
{
const ScoredMove *move_a = (const ScoredMove *)a;
const ScoredMove *move_b = (const ScoredMove *)b;
return move_b->score - move_a->score; // 降序排列
}
/**
* @brief 生成候选移动并按评估分数排序
* @param moves 存储候选移动的数组
* @param player 当前玩家
* @return 候选移动数量
*/
int generate_candidate_moves(ScoredMove *moves, int player)
{
int count = 0;
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] != EMPTY)
{
continue;
}
// 只考虑有意义的位置(附近有棋子)
if (step_count > AI_SEARCH_RANGE_THRESHOLD && !is_near_stones(i, j))
{
continue;
}
// 计算该位置的评估分数
moves[count].x = i;
moves[count].y = j;
// 结合威胁检测和位置评估
ThreatLevel threat = detect_threat(i, j, player);
int base_score = evaluate_move(i, j);
// 根据威胁等级调整分数
switch (threat)
{
case THREAT_WIN:
moves[count].score = base_score + 10000;
break;
case THREAT_FOUR:
moves[count].score = base_score + 5000;
break;
case THREAT_THREE:
moves[count].score = base_score + 2000;
break;
case THREAT_DOUBLE:
moves[count].score = base_score + 1000;
break;
case THREAT_POTENTIAL:
moves[count].score = base_score + 500;
break;
default:
moves[count].score = base_score;
break;
}
count++;
}
}
// 按分数降序排序
qsort(moves, count, sizeof(ScoredMove), compare_moves);
return count;
}
/**
* @brief 检查位置是否在已有棋子附近
* @param x, y 要检查的位置
* @return 如果附近有棋子返回true
*/
bool is_near_stones(int x, int y)
{
for (int di = -AI_NEARBY_RANGE; di <= AI_NEARBY_RANGE; di++)
{
for (int dj = -AI_NEARBY_RANGE; dj <= AI_NEARBY_RANGE; dj++)
{
int ni = x + di;
int nj = y + dj;
if (ni >= 0 && ni < BOARD_SIZE && nj >= 0 && nj < BOARD_SIZE)
{
if (board[ni][nj] != EMPTY)
{
return true;
}
}
}
}
return false;
}
/**
* @brief 检测在指定位置落子的威胁等级
* @param x, y 落子位置
* @param player 落子玩家
* @return 威胁等级
*/
ThreatLevel detect_threat(int x, int y, int player)
{
// 模拟落子
board[x][y] = player;
ThreatLevel max_threat = THREAT_NONE;
int threat_count = 0;
// 检查四个方向
for (int k = 0; k < 4; k++)
{
DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player);
ThreatLevel current_threat = THREAT_NONE;
// 检查是否形成五子连珠(获胜)
if (info.continuous_chess >= 5)
{
current_threat = THREAT_WIN;
}
// 检查是否形成活四或冲四
else if (info.continuous_chess == 4)
{
if (info.check_start && info.check_end)
{
current_threat = THREAT_FOUR; // 活四
}
else if (info.check_start || info.check_end)
{
current_threat = THREAT_FOUR; // 冲四
}
}
// 检查是否形成活三
else if (info.continuous_chess == 3 && info.check_start && info.check_end)
{
current_threat = THREAT_THREE;
}
// 检查潜在威胁
else if (info.continuous_chess >= 2)
{
current_threat = THREAT_POTENTIAL;
}
if (current_threat > max_threat)
{
max_threat = current_threat;
}
if (current_threat >= THREAT_THREE)
{
threat_count++;
}
}
// 恢复棋盘
board[x][y] = EMPTY;
// 如果有多个威胁,提升威胁等级
if (threat_count >= 2 && max_threat >= THREAT_THREE)
{
max_threat = THREAT_DOUBLE;
}
return max_threat;
}
/**
* @brief 计算指定方向的威胁数量
* @param x, y 起始位置
* @param dx, dy 方向向量
* @param player 玩家
* @return 威胁数量
*/
int count_threats_in_direction(int x, int y, int dx, int dy, int player)
{
int threats = 0;
// 向前搜索
for (int i = 1; i < 5; i++)
{
int nx = x + i * dx;
int ny = y + i * dy;
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
{
break;
}
if (board[nx][ny] == player)
{
threats++;
}
else if (board[nx][ny] != EMPTY)
{
break;
}
}
// 向后搜索
for (int i = 1; i < 5; i++)
{
int nx = x - i * dx;
int ny = y - i * dy;
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
{
break;
}
if (board[nx][ny] == player)
{
threats++;
}
else if (board[nx][ny] != EMPTY)
{
break;
}
}
return threats;
}
-82
View File
@@ -1,82 +0,0 @@
/**
* @file ai.h
* @note 本文件定义了AI模块的函数和变量
* @note 包括:
* 1. 评估一个落子位置的综合得分(结合进攻和防守)
* 2. 评估指定位置的价值
* 3. 评估棋盘价值
*/
#ifndef AI_H
#define AI_H
#include "gobang.h"
#include "type.h"
/**
* @brief 评估一个落子位置的综合得分(结合进攻和防守)
*
* @param x 行坐标
* @param y 列坐标
* @return int 综合得分
*/
int evaluate_move(int x, int y);
/**
* @brief 评估指定位置的价值
*
* @param x 位置x坐标
* @param y 位置y坐标
* @param player 玩家标识(PLAYER/AI)
* @return int 位置价值
*/
int evaluate_pos(int x, int y, int player);
/**
* @brief 评估棋盘价值
*
* @param player 玩家标识(PLAYER/AI)
*/
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
/**
* @brief AI下棋
*
* @param depth
*/
void ai_move(int depth);
// ==================== AI增强:新增函数声明 ====================
/**
* @brief 生成候选移动并按评估分数排序
* @param moves 存储候选移动的数组
* @param player 当前玩家
* @return 候选移动数量
*/
int generate_candidate_moves(ScoredMove *moves, int player);
/**
* @brief 检查位置是否在已有棋子附近
* @param x, y 要检查的位置
* @return 如果附近有棋子返回true
*/
bool is_near_stones(int x, int y);
/**
* @brief 检测在指定位置落子的威胁等级
* @param x, y 落子位置
* @param player 落子玩家
* @return 威胁等级
*/
ThreatLevel detect_threat(int x, int y, int player);
/**
* @brief 计算指定方向的威胁数量
* @param x, y 起始位置
* @param dx, dy 方向向量
* @param player 玩家
* @return 威胁数量
*/
int count_threats_in_direction(int x, int y, int dx, int dy, int player);
#endif // AI_H
-331
View File
@@ -1,331 +0,0 @@
#include "config.h"
#include "ui.h"
#include "game_mode.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* @brief 加载游戏配置
*/
void load_game_config()
{
FILE *file = fopen(CONFIG_FILE, "r");
if (file == NULL)
{
// 配置文件不存在,使用默认配置
printf("配置文件不存在,使用默认配置\n");
return;
}
char line[256];
while (fgets(line, sizeof(line), file))
{
// 去除换行符
line[strcspn(line, "\n")] = 0;
// 解析配置项
if (strncmp(line, "BOARD_SIZE=", 11) == 0)
{
int size = atoi(line + 11);
if (size >= MIN_BOARD_SIZE && size <= MAX_BOARD_SIZE)
{
BOARD_SIZE = size;
}
}
else if (strncmp(line, "USE_FORBIDDEN_MOVES=", 20) == 0)
{
use_forbidden_moves = (atoi(line + 20) != 0);
}
else if (strncmp(line, "USE_TIMER=", 10) == 0)
{
use_timer = atoi(line + 10);
}
else if (strncmp(line, "TIME_LIMIT=", 11) == 0)
{
time_limit = atoi(line + 11);
}
else if (strncmp(line, "NETWORK_PORT=", 13) == 0)
{
int port = atoi(line + 13);
if (port >= MIN_NETWORK_PORT && port <= MAX_NETWORK_PORT)
{
network_port = port;
}
}
else if (strncmp(line, "NETWORK_TIMEOUT=", 16) == 0)
{
int timeout = atoi(line + 16);
if (timeout > 0)
{
network_timeout = timeout;
}
}
else if (strncmp(line, "AI_DIFFICULTY=", 14) == 0)
{
int difficulty = atoi(line + 14);
if (difficulty >= 1 && difficulty <= 5)
{
// 根据难度设置AI搜索深度
// 这里可以添加AI难度相关的配置
}
}
}
fclose(file);
printf("配置加载完成\n");
}
/**
* @brief 保存游戏配置
*/
void save_game_config()
{
FILE *file = fopen(CONFIG_FILE, "w");
if (file == NULL)
{
printf("无法保存配置文件\n");
return;
}
fprintf(file, "# 五子棋游戏配置文件\n");
fprintf(file, "# 棋盘大小 (范围: %d-%d)\n", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
fprintf(file, "BOARD_SIZE=%d\n", BOARD_SIZE);
fprintf(file, "\n# 禁手规则 (0=关闭, 1=开启)\n");
fprintf(file, "USE_FORBIDDEN_MOVES=%d\n", use_forbidden_moves ? 1 : 0);
fprintf(file, "\n# 计时器 (0=关闭, 1=开启)\n");
fprintf(file, "USE_TIMER=%d\n", use_timer);
fprintf(file, "\n# 时间限制 (分钟)\n");
fprintf(file, "TIME_LIMIT=%d\n", time_limit);
fprintf(file, "\n# 网络端口 (范围: %d-%d)\n", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
fprintf(file, "NETWORK_PORT=%d\n", network_port);
fprintf(file, "\n# 网络超时时间 (毫秒)\n");
fprintf(file, "NETWORK_TIMEOUT=%d\n", network_timeout);
fclose(file);
printf("配置保存完成\n");
}
/**
* @brief 重置为默认配置
*/
void reset_to_default_config()
{
BOARD_SIZE = DEFAULT_BOARD_SIZE;
use_forbidden_moves = DEFAULT_USE_FORBIDDEN_MOVES;
use_timer = DEFAULT_USE_TIMER;
time_limit = DEFAULT_TIME_LIMIT;
network_port = DEFAULT_NETWORK_PORT;
network_timeout = NETWORK_TIMEOUT_MS;
printf("已重置为默认配置\n");
}
/**
* @brief 显示当前配置
*/
void display_current_config()
{
printf("\n===== 当前游戏配置 =====\n");
printf("棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
printf("禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
printf("计时器: %s\n", use_timer ? "开启" : "关闭");
if (use_timer)
{
printf("时间限制: %d 分钟\n", time_limit / 60);
}
printf("网络端口: %d\n", network_port);
printf("网络超时: %d 毫秒\n", network_timeout);
printf("=====================\n");
}
/**
* @brief 配置棋盘大小
*/
void config_board_size()
{
printf("\n当前棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
printf("请输入新的棋盘大小 (%d-%d): ", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
int new_size;
if (scanf("%d", &new_size) == 1)
{
if (new_size >= MIN_BOARD_SIZE && new_size <= MAX_BOARD_SIZE)
{
BOARD_SIZE = new_size;
printf("棋盘大小已设置为: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
}
else
{
printf("无效的棋盘大小!\n");
}
}
else
{
printf("输入格式错误!\n");
// 清除输入缓冲区
while (getchar() != '\n');
}
}
/**
* @brief 配置禁手规则
*/
void config_forbidden_moves()
{
printf("\n当前禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
printf("是否启用禁手规则?(1=开启, 0=关闭): ");
int choice;
if (scanf("%d", &choice) == 1)
{
use_forbidden_moves = (choice != 0);
printf("禁手规则已%s\n", use_forbidden_moves ? "开启" : "关闭");
}
else
{
printf("输入格式错误!\n");
while (getchar() != '\n');
}
}
/**
* @brief 配置计时器
*/
void config_timer()
{
printf("\n当前计时器: %s\n", use_timer ? "开启" : "关闭");
printf("是否启用计时器?(1=开启, 0=关闭): ");
int choice;
if (scanf("%d", &choice) == 1)
{
use_timer = choice;
if (use_timer)
{
printf("请输入时间限制(分钟): ");
int new_limit;
if (scanf("%d", &new_limit) == 1 && new_limit > 0)
{
time_limit = new_limit * 60; // 转换为秒数存储
printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60);
}
else
{
printf("无效的时间限制!\n");
while (getchar() != '\n');
}
}
else
{
printf("计时器已关闭\n");
}
}
else
{
printf("输入格式错误!\n");
while (getchar() != '\n');
}
}
/**
* @brief 配置网络参数
*/
void config_network()
{
printf("\n===== 网络配置 =====\n");
printf("当前网络端口: %d\n", network_port);
printf("当前网络超时: %d 毫秒\n", network_timeout);
printf("\n请输入新的网络端口 (%d-%d): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
int new_port;
if (scanf("%d", &new_port) == 1)
{
if (new_port >= MIN_NETWORK_PORT && new_port <= MAX_NETWORK_PORT)
{
network_port = new_port;
printf("网络端口已设置为: %d\n", network_port);
}
else
{
printf("无效的端口号!端口范围: %d-%d\n", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
}
}
else
{
printf("输入格式错误!\n");
while (getchar() != '\n');
}
printf("\n请输入网络超时时间(毫秒, 建议1000-10000): ");
int new_timeout;
if (scanf("%d", &new_timeout) == 1)
{
if (new_timeout > 0)
{
network_timeout = new_timeout;
printf("网络超时已设置为: %d 毫秒\n", network_timeout);
}
else
{
printf("无效的超时时间!\n");
}
}
else
{
printf("输入格式错误!\n");
while (getchar() != '\n');
}
}
/**
* @brief 配置管理主菜单
*/
void config_management_menu()
{
int choice;
while (1)
{
clear_screen();
display_settings_menu();
display_current_config();
printf("请选择操作: ");
if (scanf("%d", &choice) != 1)
{
printf("输入格式错误!\n");
while (getchar() != '\n');
pause_for_input("按任意键继续...");
continue;
}
switch (choice)
{
case 1:
config_board_size();
break;
case 2:
config_forbidden_moves();
break;
case 3:
config_timer();
break;
case 4:
config_network();
break;
case 5:
printf("AI难度设置功能开发中...\n");
break;
case 6:
save_game_config();
return;
default:
printf("无效的选择!\n");
break;
}
pause_for_input("按任意键继续...");
}
}
-170
View File
@@ -1,170 +0,0 @@
/**
* @file config.h
* @brief
* @note 便
*/
#ifndef CONFIG_H
#define CONFIG_H
//---------- 棋盘相关参数 ----------//
#define MAX_BOARD_SIZE 25 // 支持的最大棋盘尺寸
#define MIN_BOARD_SIZE 5 // 支持的最小棋盘尺寸
#define DEFAULT_BOARD_SIZE 15 // 默认棋盘尺寸
#define MAX_STEPS (MAX_BOARD_SIZE * MAX_BOARD_SIZE) // 游戏最大步数
//---------- 游戏模式参数 ----------//
#define GAME_MODE_AI 1 // 人机对战模式
#define GAME_MODE_PVP 2 // 双人对战模式
#define GAME_MODE_NETWORK 3 // 网络对战模式
//---------- 玩家标识参数 ----------//
#define EMPTY 0 // 棋盘空位标识
#define PLAYER 1 // 玩家标识 (用于人机对战模式)
#define AI 2 // AI标识 (用于人机对战模式)
#define PLAYER1 1 // 玩家1标识 (用于双人对战模式)
#define PLAYER2 2 // 玩家2标识 (用于双人对战模式)
//---------- 特殊输入命令 ----------//
#define INPUT_UNDO -1 // 悔棋
#define INPUT_SAVE -2 // 保存
#define INPUT_EXIT -3 // 退出
#define INPUT_SURRENDER -4 // 认输
//---------- 游戏设置默认值 ----------//
#define DEFAULT_USE_FORBIDDEN_MOVES false // 默认不启用禁手规则
#define DEFAULT_USE_TIMER 0 // 默认不启用计时器
#define DEFAULT_TIME_LIMIT 30 // 默认时间限制为30秒(内部存储)
//---------- AI参数 ----------//
#define DEFAULT_AI_DEPTH 5 // 默认AI搜索深度(从3提升到5)
#define DEFAULT_DEFENSE_COEFFICIENT 1.5 // 默认防守系数(从1.2提升到1.5)
//---------- 网络参数 ----------//
#define DEFAULT_NETWORK_PORT 8888 // 默认网络端口
#define MIN_NETWORK_PORT 1024 // 最小网络端口
#define MAX_NETWORK_PORT 65535 // 最大网络端口
#define NETWORK_TIMEOUT_MS 5000 // 网络超时时间(毫秒)
#define NETWORK_BUFFER_SIZE 1024 // 网络缓冲区大小
// 网络配置
#define DEFAULT_PORT 8888 // 默认端口(与DEFAULT_NETWORK_PORT保持一致)
#define BUFFER_SIZE 1024 // 缓冲区大小(与NETWORK_BUFFER_SIZE保持一致)
#define MAX_IP_LENGTH 16 // 最大IP地址长度
// 网络消息类型
#define MSG_MOVE 1 // 落子消息
#define MSG_CHAT 2 // 聊天消息
#define MSG_SURRENDER 3 // 认输消息
#define MSG_UNDO_REQUEST 4 // 悔棋请求
#define MSG_UNDO_RESPONSE 5 // 悔棋回应
#define MSG_GAME_START 6 // 游戏开始
#define MSG_GAME_END 7 // 游戏结束
#define MSG_HEARTBEAT 8 // 心跳包
#define MSG_DISCONNECT 9 // 断线消息
//---------- 评分参数 ----------//
// 棋型评分 - 用于calculate_step_score函数
#define SCORE_FIVE 0 // 五连
#define SCORE_LIVE_FOUR 2000 // 活四
#define SCORE_RUSH_FOUR 1000 // 冲四
#define SCORE_DEAD_FOUR 300 // 死四
#define SCORE_LIVE_THREE 500 // 活三
#define SCORE_SLEEP_THREE 200 // 眠三
#define SCORE_DEAD_THREE 80 // 死三
#define SCORE_LIVE_TWO 100 // 活二
#define SCORE_SLEEP_TWO 40 // 眠二
#define SCORE_DEAD_TWO 15 // 死二
#define SCORE_LIVE_ONE 15 // 开放单子
#define SCORE_HALF_ONE 8 // 半开放单子
#define SCORE_DEAD_ONE 2 // 封闭单子
// 位置奖励系数
#define POSITION_BONUS_FACTOR 10 // 位置奖励因子
// AI评估参数 - 用于evaluate_pos函数
#define AI_SCORE_FIVE 1000000 // AI评估-五连
#define AI_SCORE_LIVE_FOUR 100000 // AI评估-活四
#define AI_SCORE_RUSH_FOUR 10000 // AI评估-冲四
#define AI_SCORE_DEAD_FOUR 500 // AI评估-死四
#define AI_SCORE_LIVE_THREE 5000 // AI评估-活三
#define AI_SCORE_SLEEP_THREE 1000 // AI评估-眠三
#define AI_SCORE_DEAD_THREE 50 // AI评估-死三
#define AI_SCORE_LIVE_TWO 500 // AI评估-活二
#define AI_SCORE_SLEEP_TWO 100 // AI评估-眠二
#define AI_SCORE_DEAD_TWO 10 // AI评估-死二
#define AI_SCORE_LIVE_ONE 50 // AI评估-开放单子
#define AI_SCORE_HALF_ONE 10 // AI评估-半开放单子
#define AI_SCORE_DEAD_ONE 1 // AI评估-封闭单子
// AI位置奖励系数
#define AI_POSITION_BONUS_FACTOR 50 // AI位置奖励因子
// 搜索算法参数
#define SEARCH_MAX_SCORE 1000000 // 搜索最大分数
#define SEARCH_WIN_BONUS 1000000 // 获胜奖励分数
#define AI_NEARBY_RANGE 3 // AI搜索的邻近范围
#define AI_SEARCH_RANGE_THRESHOLD 8 // AI开始限制搜索范围的步数阈值
// 组合棋型评分 - AI增强新增
#define AI_SCORE_DOUBLE_THREE 50000 // 双三
#define AI_SCORE_FOUR_THREE 200000 // 四三
#define AI_SCORE_THREAT_SEQUENCE 80000 // 威胁序列
#define AI_SCORE_POTENTIAL_FIVE 300000 // 潜在五连
// 评分权重参数
#define TIME_WEIGHT_FACTOR 0.5 // 时间权重因子
#define WIN_BONUS 2000 // 胜利奖励分数
// 文件路径参数
#define RECORDS_DIR "records" // 记录文件目录
#define CONFIG_FILE "gobang_config.ini" // 配置文件路径
#define MAX_PATH_LENGTH 256 // 最大路径长度
//---------- 配置管理函数声明 ----------//
/**
* @brief
*/
void load_game_config();
/**
* @brief
*/
void save_game_config();
/**
* @brief
*/
void reset_to_default_config();
/**
* @brief
*/
void display_current_config();
/**
* @brief
*/
void config_board_size();
/**
* @brief
*/
void config_forbidden_moves();
/**
* @brief
*/
void config_timer();
/**
* @brief
*/
void config_network();
/**
* @brief
*/
void config_management_menu();
#endif // CONFIG_H
-917
View File
@@ -1,917 +0,0 @@
#include "game_mode.h"
#include "init_board.h"
#include "gobang.h"
#include "ai.h"
#include "record.h"
#include "config.h"
#include "network.h"
#include "ui.h"
#include "globals.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ctype.h>
// 全局变量现在在globals.c中定义
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <conio.h>
#endif
/**
* @brief
*
* @param prompt
* @param min
* @param max
* @return int
*/
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);
}
}
}
/**
* @brief
*
* @param current_player
* @return true
* @return false
*/
bool parse_player_input(int *x, int *y)
{
char input[10];
while (1)
{
if (_kbhit())
{
scanf("%s", input);
break;
}
Sleep(100); // 短暂延迟以防止CPU占用过高
}
if (sscanf(input, "%d", x) == 1)
{
// 成功解析第一个数字,现在解析第二个
if (scanf("%d", y) != 1)
{
// 如果第二个数字不可用,则检查特殊命令
if (*x == INPUT_UNDO)
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
if (return_move(steps_to_undo * 2))
{
printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
}
else
{
printf("无法悔棋!\n");
}
return 0; // 特殊命令已处理
}
else if (*x == INPUT_SAVE)
{
// ... 处理保存 ...
return false; // 特殊命令
}
else if (*x == INPUT_EXIT)
{
// ... 处理退出 ...
return false; // 特殊命令
}
printf("无效输入,请输入两个数字坐标。\n");
while (getchar() != '\n')
;
return 0; // 无效输入
}
}
else
{
// sscanf失败,检查特殊命令
if (input[0] == 'r' || input[0] == 'R')
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
if (return_move(steps_to_undo * 2))
{
printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
}
else
{
printf("无法悔棋!\n");
}
return false; // 特殊命令
}
else if (input[0] == 's' || input[0] == 'S')
{
*x = INPUT_SURRENDER;
int confirm = get_integer_input("确认认输?(1:是/0:否): ", 0, 1);
if (confirm)
{
printf("玩家选择认输!\n");
return 1; // 正常回合完成 // 返回认输命令
}
else
{
printf("取消认输!\n");
return false; // 取消认输
}
}
printf("无效输入,请输入数字坐标、'r'悔棋或's'认输。\n");
return 0; // 无效输入
}
return 1; // 有效坐标
}
/**
* @brief
* @param x
* @param y
* @return true
* @return false
*/
bool parse_network_player_input(int *x, int *y)
{
char input[10];
while (1)
{
if (_kbhit())
{
scanf("%s", input);
break;
}
Sleep(100); // 短暂延迟以防止CPU占用过高
}
if (sscanf(input, "%d", x) == 1)
{
// 成功解析第一个数字,现在解析第二个
if (scanf("%d", y) != 1)
{
printf("无效输入,请输入两个数字坐标。\n");
while (getchar() != '\n')
;
return false; // 无效输入
}
}
else
{
// sscanf失败,检查特殊命令
if (input[0] == 'r' || input[0] == 'R')
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
printf("发送悔棋请求给对方...\n");
if (send_undo_request(steps_to_undo))
{
printf("悔棋请求已发送,等待对方回应...\n");
// 等待对方回应
NetworkMessage msg;
time_t start_time = time(NULL);
while (difftime(time(NULL), start_time) < 30) // 30秒超时
{
if (receive_network_message(&msg, 1000))
{
if (msg.type == MSG_UNDO_RESPONSE && msg.x == steps_to_undo)
{
if (msg.y == 1) // 对方同意
{
if (return_move(steps_to_undo * 2))
{
printf("对方同意悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
return 2; // 悔棋成功,需要重新开始回合
}
else
{
printf("悔棋失败!\n");
return 0; // 悔棋失败,继续当前回合
}
}
else // 对方拒绝
{
printf("对方拒绝了悔棋请求。\n");
return 0; // 悔棋被拒绝,继续当前回合
}
}
}
if (!is_network_connected())
{
printf("网络连接断开\n");
return 0;
}
}
printf("悔棋请求超时,对方未回应。\n");
}
else
{
printf("发送悔棋请求失败!\n");
}
return 0; // 特殊命令已处理
}
else if (input[0] == 's' || input[0] == 'S')
{
*x = INPUT_SURRENDER;
int confirm = get_integer_input("确认认输?(1:是/0:否): ", 0, 1);
if (confirm)
{
printf("你选择认输!\n");
*x = INPUT_SURRENDER;
return -1; // 返回认输命令
}
else
{
printf("取消认输!\n");
return 0; // 取消认输
}
}
printf("无效输入,请输入数字坐标、'r'悔棋或's'认输。\n");
return false; // 无效输入
}
return true; // 有效坐标
}
/**
* @brief
* @param current_player
* @return true
*/
bool handle_player_turn(int current_player)
{
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n玩家%d, 请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输:", current_player, BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\n玩家%d超时, 对方获胜!\n", current_player);
return false; // Timeout
}
}
if (parse_player_input(&x, &y))
{
if (x == INPUT_SURRENDER)
{
printf("\n玩家%d选择认输,对方获胜!\n", current_player);
return false; // 游戏结束,认输
}
input_received = true;
}
else
{
// 已处理特殊命令或无效输入,继续循环
return true;
}
}
x--;
y--;
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
}
else
{
printf("坐标无效!请重新输入。\n");
// 继续循环,重新输入坐标
}
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n玩家%d获胜!\n", current_player);
return false; // 游戏结束
}
return true; // 成功落子
}
/**
* @brief AI游戏
* @note
* @param AI_DEPTH AI的搜索深度
*/
void run_ai_game()
{
// 重置评分计算标志,确保每局游戏都会重新计算评分
scores_calculated = 0;
// AI对战模式
int AI_DEPTH = 3;
AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5);
/**
* @brief AI的防守系数
* @note 1~1.2
* 2~1.3
* 3~1.4
* 4~1.5
* 5~1.6
*/
defense_coefficient = 1.2 + (AI_DEPTH - 1) * 0.1;
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)
{
// 检查玩家是否获胜
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, PLAYER))
{
printf("\n玩家获胜!\n");
break;
}
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");
review_process(GAME_MODE_AI); // AI对战模式
handle_save_record(GAME_MODE_AI); // AI对战模式
}
/**
* @brief
* @note
*/
void run_pvp_game()
{
// 重置评分计算标志,确保每局游戏都会重新计算评分
scores_calculated = 0;
// 双人对战模式
empty_board();
int current_player = determine_first_player(PLAYER1, PLAYER2);
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 == PLAYER1) ? PLAYER2 : PLAYER1;
}
}
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_PVP); // 双人对战模式
handle_save_record(GAME_MODE_PVP); // 双人对战模式
}
/**
* @brief
* @note
*/
void run_review_mode()
{
char filename[100];
char record_files[100][100];
int file_count = 0;
#ifdef _WIN32
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile("records\\*", &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
do
{
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
strcpy(record_files[file_count++], ffd.cFileName);
}
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
#endif
if (file_count > 0)
{
printf("发现以下复盘文件:\n");
for (int i = 0; i < file_count; i++)
{
printf("%d. %s\n", i + 1, record_files[i]);
}
char prompt[150];
sprintf(prompt, "请输入复盘文件编号(1-%d),或输入0以手动输入文件名: ", file_count);
int choice = get_integer_input(prompt, 0, file_count);
if (choice > 0)
{
strcpy(filename, record_files[choice - 1]);
}
else
{
printf("请输入完整文件名: ");
scanf("%s", filename);
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
int possible_choice = atoi(filename);
if (possible_choice > 0 && possible_choice <= file_count)
{
strcpy(filename, record_files[possible_choice - 1]);
}
}
}
else
{
printf("未找到任何复盘文件,请输入复盘文件地址: ");
scanf("%s", filename);
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
}
int game_mode = load_game_from_file(filename);
if (game_mode != 0)
{
if (game_mode == 1)
{
printf("加载AI对战模式复盘文件成功!\n");
}
else if (game_mode == 2)
{
printf("加载双人对战模式复盘文件成功!\n");
}
review_process(game_mode);
}
else
{
printf("加载复盘文件失败!可能是旧版本文件格式或文件损坏\n");
}
}
/**
* @brief
*/
void run_network_game()
{
// 重置评分计算标志
scores_calculated = 0;
// 初始化网络模块
if (!init_network())
{
printf("网络初始化失败!\n");
pause_for_input("按任意键返回主菜单...");
return;
}
printf("=== 网络对战模式 ===\n");
printf("1. 创建房间(作为主机)\n");
printf("2. 加入房间(连接到主机)\n");
int choice = get_integer_input("请选择模式(1-2): ", 1, 2);
bool connection_success = false;
if (choice == 1)
{
// 服务器模式
int port = get_integer_input("请输入监听端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0) port = network_port;
printf("\n正在创建房间...\n");
connection_success = create_server(port);
}
else
{
// 客户端模式
char ip[MAX_IP_LENGTH];
// 循环直到输入有效的IP地址或用户选择退出
while (1)
{
printf("请输入服务器IP地址 (输入'exit'退出): ");
if (scanf("%s", ip) != 1)
{
printf("输入错误,请重新输入。\n");
// 清除输入缓冲区
while (getchar() != '\n');
continue;
}
// 检查是否要退出
if (strcmp(ip, "exit") == 0 || strcmp(ip, "EXIT") == 0)
{
printf("取消连接,返回主菜单。\n");
cleanup_network();
pause_for_input("按任意键返回主菜单...");
return;
}
// 简单的IP地址格式验证
if (strlen(ip) < 7 || strlen(ip) > 15)
{
printf("IP地址格式错误!请输入有效的IP地址(如:192.168.1.100\n");
continue;
}
// 检查IP地址是否包含有效字符
bool valid_ip = true;
for (int i = 0; i < strlen(ip); i++)
{
if (!(isdigit(ip[i]) || ip[i] == '.'))
{
valid_ip = false;
break;
}
}
if (!valid_ip)
{
printf("IP地址格式错误!只能包含数字和点号。\n");
continue;
}
// 检查点号数量
int dot_count = 0;
for (int i = 0; i < strlen(ip); i++)
{
if (ip[i] == '.') dot_count++;
}
if (dot_count != 3)
{
printf("IP地址格式错误!应包含3个点号(如:192.168.1.100\n");
continue;
}
printf("输入的IP地址: %s\n", ip);
int confirm = get_integer_input("确认连接到此IP?(1:是/0:否,重新输入): ", 0, 1);
if (confirm)
{
break; // 确认IP地址,跳出循环
}
// 如果选择否,继续循环重新输入
}
int port = get_integer_input("请输入服务器端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0) port = network_port;
printf("\n正在连接到服务器 %s:%d...\n", ip, port);
connection_success = connect_to_server(ip, port);
}
if (!connection_success)
{
printf("网络连接失败!\n");
cleanup_network();
pause_for_input("按任意键返回主菜单...");
return;
}
printf("\n网络连接成功!游戏即将开始...\n");
printf("你是玩家%d%s先手\n",
network_state.local_player_id,
network_state.local_player_id == PLAYER1 ? "" : "对方");
// 开始网络游戏
empty_board();
print_board();
if (network_game_loop())
{
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_NETWORK); // 网络对战模式的复盘
handle_save_record(GAME_MODE_NETWORK); // 保存为网络对战模式记录
}
else
{
printf("游戏因网络错误而结束\n");
}
// 清理网络连接
disconnect_network();
pause_for_input("按任意键返回主菜单...");
}
/**
* @brief
*/
bool handle_network_player_turn(int current_player, bool is_local_turn)
{
if (is_local_turn)
{
// 本地玩家回合
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n轮到你了,请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输: ", BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\n你超时了,对方获胜!\n");
send_surrender(); // 发送认输消息
return 0; // 游戏结束
}
}
int parse_result = parse_network_player_input(&x, &y);
if (parse_result == 1) // 有效坐标输入
{
input_received = true;
}
else if (parse_result == -1) // 认输命令
{
printf("\n你选择认输,对方获胜!\n");
send_surrender();
return 0; // 游戏结束
}
else if (parse_result == 2) // 悔棋成功
{
return 2; // 悔棋发生,需要重新开始回合
}
else // parse_result == 0, 特殊命令已处理或无效输入
{
// 继续等待输入
continue;
}
}
x--; y--; // 转换为0-based坐标
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
}
else
{
printf("坐标无效!请重新输入。\n");
// 继续循环,重新输入坐标
}
}
// 发送落子消息
if (!send_move(x, y, current_player))
{
printf("发送落子消息失败!\n");
return 0; // 游戏结束
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n你获胜了!\n");
return 0; // 游戏结束
}
}
else
{
// 等待对方落子
printf("\n等待对方落子...\n");
NetworkMessage msg;
time_t start_time = time(NULL);
while (1)
{
if (receive_network_message(&msg, 1000))
{
// 1秒超时
if (msg.type == MSG_MOVE && msg.player_id == current_player)
{
// 收到落子消息
if (!player_move(msg.x, msg.y, current_player))
{
printf("收到无效的落子坐标!\n");
return 0; // 游戏结束
}
printf("对方落子: (%d, %d)\n", msg.x + 1, msg.y + 1);
print_board();
if (check_win(msg.x, msg.y, current_player))
{
printf("\n对方获胜!\n");
return 0; // 游戏结束
}
break;
}
else if (msg.type == MSG_SURRENDER)
{
printf("\n对方认输,你获胜了!\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_DISCONNECT)
{
printf("\n对方已断开连接\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_CHAT)
{
printf("[对方]: %s\n", msg.message);
}
else if (msg.type == MSG_UNDO_REQUEST)
{
int steps = msg.x;
printf("\n对方请求悔棋 %d 步,是否同意?(1:同意/0:拒绝): ", steps);
int response = get_integer_input("", 0, 1);
if (response && return_move(steps * 2))
{
printf("同意悔棋,双方各退 %d 步\n", steps);
send_undo_response(true, steps);
print_board();
// 悔棋后需要重新开始当前回合,不改变current_player
return 2; // 悔棋发生,需要重新开始回合
}
else
{
printf("拒绝悔棋\n");
send_undo_response(false, steps);
// 继续等待对方落子
}
}
}
// 检查超时
if (use_timer && difftime(time(NULL), start_time) > time_limit)
{
printf("\n对方超时,你获胜!\n");
return 0; // 游戏结束
}
// 检查网络连接
if (!is_network_connected())
{
printf("\n网络连接断开\n");
return 0; // 游戏结束
}
}
}
return 1; // 正常回合完成
}
/**
* @brief
*/
bool network_game_loop()
{
int current_player = PLAYER1; // 总是从玩家1开始
while (1)
{
bool is_local_turn = (current_player == network_state.local_player_id);
int turn_result = handle_network_player_turn(current_player, is_local_turn);
if (turn_result == 0) // 游戏结束
{
return true;
}
else if (turn_result == 2) // 悔棋发生,重新开始当前回合
{
continue; // 不切换玩家,重新开始当前回合
}
// 检查平局
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
return true;
}
// 切换玩家
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
// 检查网络连接
if (!is_network_connected())
{
printf("\n网络连接断开\n");
return false;
}
}
return true;
}
-93
View File
@@ -1,93 +0,0 @@
/**
* @file game_mode.h
* @brief
* @note
* 1. AI对战模式
* 2.
* 3.
* 4.
*/
#ifndef GAME_MODE_H
#define GAME_MODE_H
#include "gobang.h"
#include "config.h"
/**
* @brief
*
* @param prompt
* @param min
* @param max
* @return int
*/
int get_integer_input(const char *prompt, int min, int max);
/**
* @brief
*
* @param x
* @param y
* @return true
* @return false
*/
bool parse_player_input(int *x, int *y);
/**
* @brief
* @param x
* @param y
* @return true
* @return false
*/
bool parse_network_player_input(int *x, int *y);
/**
* @brief AI回合
*
* @param current_player
*/
bool handle_player_turn(int current_player);
/**
* @brief AI对战模式
* AI的对战逻辑
*/
void run_ai_game();
/**
* @brief
*
*/
void run_pvp_game();
/**
* @brief
*
*/
void run_review_mode();
/**
* @brief
* 线
*/
void run_network_game();
/**
* @brief
* @param current_player
* @param is_local_turn
* @return true
* @return false
*/
bool handle_network_player_turn(int current_player, bool is_local_turn);
/**
* @brief
* @return true
* @return false 退
*/
bool network_game_loop();
#endif // GAME_MODE_H
-37
View File
@@ -1,37 +0,0 @@
/**
* @file globals.c
* @author (3364451258@qq.com15236416560@163.comlhy3364451258@outlook.com)
* @brief
* @version 6.0
* @date 2025-07-10
* @note
*/
#include "globals.h"
#include "config.h"
// ==================== 游戏核心变量定义 ====================
int BOARD_SIZE = DEFAULT_BOARD_SIZE; // 实际使用的棋盘尺寸
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; // 棋盘状态存储数组(默认棋盘全空为0)
Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 四个方向:向下、向右、右下、左下
int step_count = 0; // 当前步数计数器
// ==================== 游戏配置变量定义 ====================
bool use_forbidden_moves = DEFAULT_USE_FORBIDDEN_MOVES; // 是否启用禁手规则
int use_timer = DEFAULT_USE_TIMER; // 是否启用计时器
int time_limit = DEFAULT_TIME_LIMIT; // 每回合的时间限制(秒)
int network_port = DEFAULT_NETWORK_PORT; // 网络端口
int network_timeout = NETWORK_TIMEOUT_MS; // 网络超时时间
// ==================== AI相关变量定义 ====================
double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
// ==================== 网络相关变量定义 ====================
NetworkGameState network_state = {0}; // 网络游戏状态
// ==================== 记录相关变量定义 ====================
int player1_final_score = 0; // 玩家1最终得分
int player2_final_score = 0; // 玩家2最终得分
int scores_calculated = 0; // 评分计算标志
char winner_info[50] = "平局或未完成"; // 存储胜负信息
-41
View File
@@ -1,41 +0,0 @@
/**
* @file globals.h
* @brief
* @note
*/
#ifndef GLOBALS_H
#define GLOBALS_H
#include "type.h"
#include "gobang.h"
#include "network.h"
#include <stdbool.h>
// ==================== 游戏核心变量 ====================
extern int BOARD_SIZE; // 当前实际使用的棋盘尺寸
extern int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE]; // 棋盘状态存储数组
extern Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
extern const int direction[4][2]; // 四个方向:向下、向右、右下、左下
extern int step_count; // 当前步数计数器
// ==================== 游戏配置变量 ====================
extern bool use_forbidden_moves; // 是否启用禁手规则的标志
extern int use_timer; // 是否启用计时器的标志
extern int time_limit; // 每回合的时间限制(秒,内部存储)
extern int network_port; // 网络端口
extern int network_timeout; // 网络超时时间
// ==================== AI相关变量 ====================
extern double defense_coefficient; // 防守系数
// ==================== 网络相关变量 ====================
extern NetworkGameState network_state; // 网络游戏状态
// ==================== 记录相关变量 ====================
extern int player1_final_score; // 玩家1最终得分
extern int player2_final_score; // 玩家2最终得分
extern int scores_calculated; // 评分计算标志
extern char winner_info[50]; // 存储胜负信息
#endif // GLOBALS_H
+418 -161
View File
@@ -1,78 +1,74 @@
#include "game_mode.h"
#include "init_board.h"
#include "gobang.h"
#include "ai.h"
#include "record.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <sys/stat.h>
#include <time.h>
// 全局变量定义
int BOARD_SIZE = 15; // 实际使用的棋盘尺寸(默认15)
int board[MAX_BOARD_SIZE][MAX_BOARD_SIZE] = {0}; // 棋盘状态存储数组(默认棋盘全空为0)
const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 四个方向:向下、向右、右下、左下
Step steps[MAX_STEPS]; // 存储所有落子步骤的数组
int step_count = 0; // 当前步数计数器
/**
* @brief (x, y)
* @brief
* EMPTYstep_count重置为0
*/
void empty_board()
{
// 初始化棋盘状态为全空
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
board[i][j] = EMPTY;
}
}
step_count = 0; // 重置步数计数器
}
/**
* @brief
*
* 'x'AI棋子显示为'''·'
*/
void print_board()
{
// 打印列号(1-BOARD_SIZE显示)
printf("\n ");
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d", i + 1);
if (i + 1 == 9) // 处理列号9和10+的对齐
printf(" ");
}
printf("\n");
// 逐行打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d ", i + 1); // 打印行号(1-BOARD_SIZE
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == PLAYER)
printf("x "); // 玩家棋子
else if (board[i][j] == AI)
printf(""); // AI棋子(使用○显示)
else
printf("· "); // 空位
}
printf("\n"); // 每行结束换行
}
}
/**
* @brief
* @param x (0-base)
* @param y (0-base)
* @return true-, false-
* @return true
* @return false
*/
bool have_space(int x, int y)
{
return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY;
}
// 函数定义
/**
* @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 != PLAYER1)
{
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;
// 校验坐标是否在范围内且该位置为空
return (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] == EMPTY);
}
/**
@@ -82,22 +78,16 @@ bool is_forbidden_move(int x, int y, int player)
* @return true
* @return false ()
*/
bool player_move(int x, int y, int player)
bool player_move(int x, int y)
{
// 位置无效则返回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;
}
@@ -128,12 +118,8 @@ DirInfo count_specific_direction(int x, int y, int dx, int dy, int player)
}
// 判断正方向端点是否开放(遇到空位)
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE)
{
if (board[nx][ny] == EMPTY)
{
info.check_end = true;
}
}
// 检查反方向(-dx, -dy
nx = x - dx, ny = y - dy;
@@ -145,16 +131,21 @@ DirInfo count_specific_direction(int x, int y, int dx, int dy, int player)
}
// 判断反方向端点是否开放(遇到空位)
if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE)
{
if (board[nx][ny] == EMPTY)
{
info.check_start = true;
}
}
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)
{
// 检查四个方向是否存在五连珠
@@ -162,108 +153,374 @@ 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; // 四个方向都没有五连珠
}
/**
* @brief
*
* @param steps_to_undo
* @return true
* @return false ()
* @brief
* @param x (0-base)
* @param y (0-base)
* @param player (PLAYER/AI)
* @return int ()
* @note :
* - :100000 :10000 :500
* - :5000 :1000 :50
* - :500 :100 :10
* - :50()/10()/1()
* -
*/
bool return_move(int steps_to_undo)
int evaluate_pos(int x, int y, int player)
{
if (step_count < steps_to_undo)
{
return false;
}
// 保存原始值用于还原
int original = board[x][y];
// 模拟在该位置落子
board[x][y] = player;
for (int i = 0; i < steps_to_undo; i++)
int total_score = 0; // 总分
int line_scores[4] = {0}; // 四个方向的得分
// 遍历四个方向进行评估
for (int i = 0; i < 4; i++)
{
if (step_count > 0)
int dx = direction[i][0], dy = direction[i][1];
// 获取当前方向上的棋型信息
DirInfo info = count_specific_direction(x, y, dx, dy, player);
// 直接形成五连珠为必胜
if (info.continuous_chess >= 5)
{
step_count--;
board[steps[step_count].x][steps[step_count].y] = EMPTY;
board[x][y] = original; // 还原棋盘
return 1000000; // 返回最大分
}
}
return true;
}
/**
* @brief
* @param player (PLAYER/AI)
* @return int ()
* @note :
* - :5000 ()
* - :2000 :1000 :300 ()
* - :500 :200 :80 ()
* - :100 :40 :15 ()
* - :15 :8 :2 ()
* @note :
* 1.
* 2.
* 3.
* 4. 4()
*/
int calculate_step_score(int x, int y, int player)
{
int step_score = 0;
// 检查四个方向
for (int k = 0; k < 4; k++)
{
DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player);
// 根据连子数评分
// 根据连续棋子数评分
switch (info.continuous_chess)
{
case 5:
step_score += SCORE_FIVE;
break; // 五连
case 4:
if (info.check_start && info.check_end)
step_score += SCORE_LIVE_FOUR; // 活四
else if (info.check_start || info.check_end)
step_score += SCORE_RUSH_FOUR; // 冲四
else
step_score += SCORE_DEAD_FOUR; // 死四
case 4: // 四连珠
if (info.check_start && info.check_end) // 活四(两端开放)
line_scores[i] = 100000;
else if (info.check_start || info.check_end) // 冲四(一端开放)
line_scores[i] = 10000;
else // 死四(两端封闭)
line_scores[i] = 500;
break;
case 3:
if (info.check_start && info.check_end)
step_score += SCORE_LIVE_THREE; // 活三
else if (info.check_start || info.check_end)
step_score += SCORE_SLEEP_THREE; // 眠三
else
step_score += SCORE_DEAD_THREE; // 死三
case 3: // 三连珠
if (info.check_start && info.check_end) // 活三
line_scores[i] = 5000;
else if (info.check_start || info.check_end) // 眠三
line_scores[i] = 1000;
else // 死三
line_scores[i] = 50;
break;
case 2:
if (info.check_start && info.check_end)
step_score += SCORE_LIVE_TWO; // 活二
else if (info.check_start || info.check_end)
step_score += SCORE_SLEEP_TWO; // 眠二
else
step_score += SCORE_DEAD_TWO; // 死二
case 2: // 二连珠
if (info.check_start && info.check_end) // 活二
line_scores[i] = 500;
else if (info.check_start || info.check_end) // 眠二
line_scores[i] = 100;
else // 死二
line_scores[i] = 10;
break;
case 1:
if (info.check_start && info.check_end)
step_score += SCORE_LIVE_ONE; // 开放单子
else if (info.check_start || info.check_end)
step_score += SCORE_HALF_ONE; // 半开放单子
else
step_score += SCORE_DEAD_ONE; // 封闭单子
case 1: // 单子
if (info.check_start && info.check_end) // 开放位置
line_scores[i] = 50;
else if (info.check_start || info.check_end) // 半开放位置
line_scores[i] = 10;
else // 封闭位置
line_scores[i] = 1;
break;
}
}
// 计算总分(最高方向分+其他方向分加权)
int max_score = 0;
int sum_score = 0;
for (int i = 0; i < 4; i++)
{
if (line_scores[i] > max_score)
max_score = line_scores[i];
sum_score += line_scores[i];
}
total_score = max_score * 10 + sum_score; // 主方向权重更高
// 位置奖励:越靠近中心分数越高
int center_x = BOARD_SIZE / 2;
int center_y = BOARD_SIZE / 2;
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
int position_bonus = POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
int position_bonus = 50 * (BOARD_SIZE - distance); // 距离中心越近奖励越高
return step_score + position_bonus;
board[x][y] = original; // 还原棋盘状态
return total_score + position_bonus; // 返回总评估分
}
/**
* @brief α-β()
* @param x
* @param y
* @param player
* @param depth
* @param alpha α()
* @param beta β()
* @param is_maximizing (AI)
* @return int
* @note :
* 1.
* 2.
* 3.
* 4. is_maximizing选择最大/
* 5. 使α-β
*/
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing)
{
// 检查当前落子是否获胜
if (check_win(x, y, player))
{
return (player == AI) ? 1000000 + depth : -1000000 - depth;
}
// 达到搜索深度或平局
if (depth == 0 || step_count >= BOARD_SIZE * BOARD_SIZE)
{
return evaluate_pos(x, y, AI) - evaluate_pos(x, y, PLAYER);
}
int best_score = is_maximizing ? -1000000 : 1000000;
// 遍历所有可能落子位置
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] != EMPTY)
continue;
// 模拟当前玩家落子
board[i][j] = player;
step_count++;
// 递归搜索(切换玩家和搜索深度)
int current_score = dfs(i, j, (player == AI) ? PLAYER : AI, depth - 1, alpha, beta, !is_maximizing);
// 撤销落子
board[i][j] = EMPTY;
step_count--;
// 极大值玩家(AI)逻辑
if (is_maximizing)
{
best_score = (current_score > best_score) ? current_score : best_score;
alpha = (best_score > alpha) ? best_score : alpha;
// α剪枝
if (beta <= alpha)
{
break;
}
}
// 极小值玩家(人类)逻辑
else
{
best_score = (current_score < best_score) ? current_score : best_score;
beta = (best_score < beta) ? best_score : beta;
// β剪枝
if (beta <= alpha)
{
break;
}
}
}
if ((is_maximizing && best_score >= beta) || (!is_maximizing && best_score <= alpha))
{
break; // 提前退出外层循环
}
}
return best_score;
}
/**
* @brief AI决策主函数使
* @note
* 1.
* 2. 使DFS评估选择最佳进攻位置
* @note
* -
* - >102
* - 使
*/
void ai_move(int depth)
{
// 1. 首先检查是否需要阻止玩家的四子连棋或三子活棋
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] != EMPTY)
continue;
// 模拟玩家在此位置落子
board[i][j] = PLAYER;
bool need_block = false;
// 检查四个方向
for (int k = 0; k < 4; k++)
{
DirInfo info = count_specific_direction(i, j, direction[k][0], direction[k][1], PLAYER);
// 如果玩家能形成四子连棋且至少一端开放
if (info.continuous_chess >= 4 && (info.check_start || info.check_end))
{
need_block = true;
break;
}
// 如果玩家能形成三子活棋且两端开放
if (info.continuous_chess == 3 && info.check_start && info.check_end)
{
need_block = true;
break;
}
}
board[i][j] = EMPTY; // 恢复棋盘
if (need_block)
{
// 必须在此位置落子阻止
board[i][j] = AI;
steps[step_count++] = (Step){AI, i, j};
printf("AI落子(%d, %d)\n", i + 1, j + 1);
return;
}
}
}
// 2. 如果没有需要立即阻止的情况,则正常评估
int best_score = -1000000;
int best_x = -1, best_y = -1;
// 遍历棋盘所有空位
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] != EMPTY)
continue;
// 只考虑已有棋子附近(2格范围内)
bool has_nearby_stone = false;
for (int di = -2; di <= 2; di++)
{
for (int dj = -2; dj <= 2; dj++)
{
int ni = i + di;
int nj = j + dj;
if (ni >= 0 && ni < BOARD_SIZE &&
nj >= 0 && nj < BOARD_SIZE)
{
if (board[ni][nj] != EMPTY)
{
has_nearby_stone = true;
break;
}
}
}
if (has_nearby_stone)
break;
}
if (!has_nearby_stone && step_count > 10)
continue;
// 模拟AI落子
board[i][j] = AI;
int current_score = dfs(i, j, PLAYER, depth, -1000000, 1000000, false);
board[i][j] = EMPTY;
// 更新最佳位置
if (current_score > best_score)
{
best_score = current_score;
best_x = i;
best_y = j;
}
}
}
// 执行最佳落子
if (best_x != -1 && best_y != -1)
{
board[best_x][best_y] = AI;
steps[step_count++] = (Step){AI, best_x, best_y};
printf("AI落子(%d, %d)\n", best_x + 1, best_y + 1);
}
}
/**
* @brief
* @note
* 1.
* 2.
* 3.
* 4. Enter键控制步骤前进
* 5. 1-based坐标方便用户查看
*/
void review_process()
{
printf("\n===== 复盘记录(总步数:%d) =====\n", step_count);
// 清空输入缓冲区
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
// 创建临时复盘棋盘
int temp_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
memset(temp_board, EMPTY, sizeof(temp_board)); // 初始化为空棋盘
// 逐步重现游戏过程
for (int i = 0; i < step_count; i++)
{
Step s = steps[i]; // 获取当前步骤
temp_board[s.x][s.y] = s.player; // 在临时棋盘上落子
// 打印当前步骤信息
printf("\n===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER) ? "玩家" : "AI", // 三目运算符选择显示文本
s.x + 1, s.y + 1); // 显示1-base坐标
// 打印当前复盘棋盘
printf(" ");
for (int col = 0; col < BOARD_SIZE; col++)
printf("%2d", col + 1); // 列号
printf("\n");
for (int row = 0; row < BOARD_SIZE; row++)
{
printf("%2d ", row + 1); // 行号
for (int col = 0; col < BOARD_SIZE; col++)
{
if (temp_board[row][col] == PLAYER)
printf("x ");
else if (temp_board[row][col] == AI)
printf("");
else
printf("· ");
}
printf("\n"); // 行结束换行
}
// 如果不是最后一步,等待用户按键继续
if (i < step_count - 1)
{
printf("\n按Enter继续下一步...");
while (getchar() != '\n')
; // 等待回车
}
}
printf("\n复盘结束!按Enter返回...");
getchar(); // 等待用户按键
}
+141 -53
View File
@@ -1,85 +1,173 @@
/**
* @file gobang.h
* @author (3364451258@qq.com15236416560@163.comlhy3364451258@outlook.com)
* @brief
* @note
* AI决策等功能
* @version 1.0
* @date 2025-06-20
*
* @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 <stdbool.h>
#include "config.h"
#include "type.h"
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
// 函数原型
// --- 游戏核心逻辑 ---
// 宏定义
/**
* @brief
* @param x (0-based)
* @param y (0-based)
* @return truefalse
* @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-based)
* @param y (0-based)
* @param player
* @return truefalse
* @brief
* @param x (0-base)
* @param y (0-base)
* @return true
* @return false ()
*/
bool is_forbidden_move(int x, int y, int player);
bool player_move(int x, int y);
/**
* @brief
* @param x (0-based)
* @param y (0-based)
* @param player
* @return truefalse
*/
bool player_move(int x, int y, int player);
/**
* @brief
* @param x
* @param y
* @param dx x方向的增量 (-1, 0, or 1)
* @param dy y方向的增量 (-1, 0, or 1)
* @param player
* @return DirInfo
* @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-based)
* @param y (0-based)
* @param player
* @return truefalse
* @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 steps_to_undo
* @return truefalse
* @brief
* @param x (0-base)
* @param y (0-base)
* @param player (PLAYER/AI)
* @return int ()
*/
bool return_move(int steps_to_undo);
int evaluate_pos(int x, int y, int player);
/**
* @brief
* @param x
* @param y
* @param player
* @return
* @brief α-β()
* @param x
* @param y
* @param player
* @param depth
* @param alpha α()
* @param beta β()
* @param is_maximizing
* @return int
*/
int calculate_step_score(int x, int y, int player);
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
*
*/
void review_process();
#endif // GO_BANG_H
-18
View File
@@ -1,18 +0,0 @@
# 五子棋游戏配置文件
# 棋盘大小 (范围: 5-25)
BOARD_SIZE=15
# 禁手规则 (0=关闭, 1=开启)
USE_FORBIDDEN_MOVES=1
# 计时器 (0=关闭, 1=开启)
USE_TIMER=1
# 时间限制 (分钟)
TIME_LIMIT=60
# 网络端口 (范围: 1024-65535)
NETWORK_PORT=8888
# 网络超时时间 (毫秒)
NETWORK_TIMEOUT=5000
-118
View File
@@ -1,118 +0,0 @@
#include "init_board.h"
#include "gobang.h"
#include "game_mode.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* @brief
* EMPTYstep_count重置为0
*/
void empty_board()
{
// 初始化棋盘状态为全空
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
board[i][j] = EMPTY;
}
}
step_count = 0; // 重置步数计数器
}
/**
* @brief
*
* 'x'AI棋子显示为'''·'
*/
void print_board()
{
// 打印列号(1-BOARD_SIZE显示)
printf("\n ");
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d", i + 1);
if (i + 1 == 9) // 处理列号9和10+的对齐
{
printf(" ");
}
}
printf("\n");
// 逐行打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d ", i + 1); // 打印行号(1-BOARD_SIZE
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == PLAYER)
{
printf("x "); // 玩家棋子
}
else if (board[i][j] == AI)
{
printf(""); // AI棋子(使用○显示)
}
else
{
printf("· "); // 空位
}
}
printf("\n"); // 每行结束换行
}
}
/**
* @brief
*
* @param player1 1
* @param player2 2
*/
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分钟): ", 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;
}
}
-41
View File
@@ -1,41 +0,0 @@
/**
* @file init_board.h
* @brief
* @note
*
*/
#ifndef INIT_BOARD_H
#define INIT_BOARD_H
#include "gobang.h"
// --- 游戏初始化 ---
/**
* @brief (EMPTY)
*/
void empty_board();
/**
* @brief
*/
void print_board();
/**
* @brief
*/
void setup_board_size();
/**
* @brief
*/
void setup_game_options();
/**
* @brief
* @param player1 1
* @param player2 2
* @return
*/
int determine_first_player(int player1, int player2);
#endif // INIT_H
-84
View File
@@ -1,84 +0,0 @@
#include "game_mode.h"
#include "ui.h"
#include "config.h"
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#endif
/**
* @brief powershell
* gcc -std=c17 -o gobang.exe *.c -lws2_32
.\gobang.exe
* gcc network.c网络模块-lws2_32链接Windows网络库
*/
int main(int argc, char *argv[])
{
// 设置控制台编码为UTF-8
#ifdef _WIN32
system("chcp 65001 > nul"); // 设置控制台编码为UTF-8
SetConsoleOutputCP(65001); // 设置控制台输出编码
SetConsoleCP(65001); // 设置控制台输入编码
_mkdir("records");
#endif
// 加载游戏配置
load_game_config();
// 选择模式
while(1)
{
clear_screen();
display_main_menu();
int mode = get_integer_input("请输入模式(0-7): ", 0, 7);
switch (mode)
{
// 1. 人机对战
case 1:
run_ai_game();
break;
// 2. 玩家对战
case 2:
run_pvp_game();
break;
// 3. 网络对战
case 3:
run_network_game();
break;
// 4. 复盘模式
case 4:
run_review_mode();
break;
// 5. 配置管理
case 5:
config_management_menu();
break;
// 6. 游戏规则
case 6:
clear_screen();
display_game_rules();
pause_for_input("\n按任意键返回主菜单...");
break;
// 7. 关于游戏
case 7:
clear_screen();
display_about();
pause_for_input("\n按任意键返回主菜单...");
break;
// 0. 退出游戏
case 0:
save_game_config();
printf("感谢使用五子棋游戏!\n");
return 0;
default:
printf("无效的选择!\n");
pause_for_input("按任意键继续...");
break;
}
}
return 0;
}
-427
View File
@@ -1,427 +0,0 @@
/**
* @file network.c
* @author (3364451258@qq.com15236416560@163.comlhy3364451258@outlook.com)
* @brief
* @version 6.0
* @date 2025-07-10
*
* @copyright Copyright (c) 2025
*/
#include "network.h"
#include "gobang.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket close
typedef int SOCKET;
#endif
/**
* @brief
*/
bool init_network()
{
#ifdef _WIN32
WSADATA wsaData;
int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0)
{
printf("WSAStartup failed: %d\n", result);
return false;
}
#endif
memset(&network_state, 0, sizeof(NetworkGameState));
network_state.socket = INVALID_SOCKET;
network_state.port = DEFAULT_PORT;
return true;
}
/**
* @brief
*/
void cleanup_network()
{
if (network_state.socket != INVALID_SOCKET)
{
closesocket(network_state.socket);
network_state.socket = INVALID_SOCKET;
}
#ifdef _WIN32
WSACleanup();
#endif
network_state.is_connected = false;
}
/**
* @brief
*/
bool create_server(int port)
{
struct sockaddr_in server_addr, client_addr;
int addr_len = sizeof(client_addr);
// 创建套接字
SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == INVALID_SOCKET)
{
printf("创建套接字失败\n");
return false;
}
// 设置地址重用
int opt = 1;
#ifdef _WIN32
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
#else
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
// 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("绑定端口失败\n");
closesocket(listen_socket);
return false;
}
// 开始监听
if (listen(listen_socket, 1) == SOCKET_ERROR)
{
printf("监听失败\n");
closesocket(listen_socket);
return false;
}
char local_ip[MAX_IP_LENGTH];
if (get_local_ip(local_ip, sizeof(local_ip)))
{
printf("服务器已启动,等待客户端连接...\n");
printf("本机IP地址: %s\n", local_ip);
printf("监听端口: %d\n", port);
}
else
{
printf("服务器已启动,监听端口: %d\n", port);
}
// 等待客户端连接
SOCKET client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &addr_len);
if (client_socket == INVALID_SOCKET)
{
printf("接受连接失败\n");
closesocket(listen_socket);
return false;
}
// 关闭监听套接字
closesocket(listen_socket);
// 保存连接信息
network_state.socket = client_socket;
network_state.is_server = true;
network_state.is_connected = true;
network_state.local_player_id = PLAYER1;
network_state.remote_player_id = PLAYER2;
network_state.port = port;
strcpy(network_state.remote_ip, inet_ntoa(client_addr.sin_addr));
printf("客户端已连接: %s\n", network_state.remote_ip);
return true;
}
/**
* @brief
*/
bool connect_to_server(const char* ip, int port)
{
struct sockaddr_in server_addr;
// 创建套接字
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == INVALID_SOCKET)
{
printf("创建套接字失败\n");
return false;
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
#ifdef _WIN32
server_addr.sin_addr.s_addr = inet_addr(ip);
if (server_addr.sin_addr.s_addr == INADDR_NONE)
{
#else
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0)
{
#endif
printf("无效的IP地址: %s\n", ip);
closesocket(client_socket);
return false;
}
printf("正在连接到服务器 %s:%d...\n", ip, port);
// 连接到服务器
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("连接服务器失败\n");
closesocket(client_socket);
return false;
}
// 保存连接信息
network_state.socket = client_socket;
network_state.is_server = false;
network_state.is_connected = true;
network_state.local_player_id = PLAYER2;
network_state.remote_player_id = PLAYER1;
network_state.port = port;
strcpy(network_state.remote_ip, ip);
printf("成功连接到服务器\n");
return true;
}
/**
* @brief
*/
bool send_network_message(const NetworkMessage* msg)
{
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
{
return false;
}
int bytes_sent = send(network_state.socket, (const char*)msg, sizeof(NetworkMessage), 0);
return bytes_sent == sizeof(NetworkMessage);
}
/**
* @brief
*/
bool receive_network_message(NetworkMessage* msg, int timeout_ms)
{
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
{
return false;
}
// 设置超时
if (timeout_ms > 0)
{
#ifdef _WIN32
DWORD timeout = timeout_ms;
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
#else
struct timeval timeout;
timeout.tv_sec = timeout_ms / 1000;
timeout.tv_usec = (timeout_ms % 1000) * 1000;
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
#endif
}
int bytes_received = recv(network_state.socket, (char*)msg, sizeof(NetworkMessage), 0);
if (bytes_received == sizeof(NetworkMessage))
{
return true;
} else if (bytes_received == 0)
{
// 连接已关闭
network_state.is_connected = false;
printf("对方已断开连接\n");
} else if (bytes_received == SOCKET_ERROR)
{
#ifdef _WIN32
int error = WSAGetLastError();
if (error != WSAETIMEDOUT)
{
#else
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
#endif
network_state.is_connected = false;
printf("网络接收错误\n");
}
}
return false;
}
/**
* @brief
*/
void disconnect_network()
{
if (network_state.is_connected)
{
NetworkMessage msg = {0};
msg.type = MSG_DISCONNECT;
msg.player_id = network_state.local_player_id;
msg.timestamp = time(NULL);
send_network_message(&msg);
}
cleanup_network();
}
/**
* @brief
*/
bool is_network_connected()
{
return network_state.is_connected && network_state.socket != INVALID_SOCKET;
}
/**
* @brief IP地址
*/
bool get_local_ip(char* ip_buffer, int buffer_size)
{
#ifdef _WIN32
// Windows实现
char hostname[256];
if (gethostname(hostname, sizeof(hostname)) == 0)
{
struct hostent* host_entry = gethostbyname(hostname);
if (host_entry != NULL)
{
struct in_addr addr;
addr.s_addr = *((unsigned long*)host_entry->h_addr_list[0]);
strncpy(ip_buffer, inet_ntoa(addr), buffer_size - 1);
ip_buffer[buffer_size - 1] = '\0';
return true;
}
}
#else
// Linux实现
struct sockaddr_in addr;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock != -1)
{
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
addr.sin_port = htons(80);
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0)
{
socklen_t addr_len = sizeof(addr);
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == 0)
{
strncpy(ip_buffer, inet_ntoa(addr.sin_addr), buffer_size - 1);
ip_buffer[buffer_size - 1] = '\0';
close(sock);
return true;
}
}
close(sock);
}
#endif
// 默认返回本地回环地址
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
ip_buffer[buffer_size - 1] = '\0';
return false;
}
/**
* @brief
*/
bool send_move(int x, int y, int player_id)
{
NetworkMessage msg = {0};
msg.type = MSG_MOVE;
msg.player_id = player_id;
msg.x = x;
msg.y = y;
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
/**
* @brief
*/
bool send_chat_message(const char* message)
{
NetworkMessage msg = {0};
msg.type = MSG_CHAT;
msg.player_id = network_state.local_player_id;
strncpy(msg.message, message, sizeof(msg.message) - 1);
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
/**
* @brief
*/
bool send_surrender()
{
NetworkMessage msg = {0};
msg.type = MSG_SURRENDER;
msg.player_id = network_state.local_player_id;
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
/**
* @brief
*/
bool send_undo_request(int steps)
{
NetworkMessage msg = {0};
msg.type = MSG_UNDO_REQUEST;
msg.player_id = network_state.local_player_id;
msg.x = steps; // 使用x字段存储步数
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
/**
* @brief
*/
bool send_undo_response(bool accepted, int steps)
{
NetworkMessage msg = {0};
msg.type = MSG_UNDO_RESPONSE;
msg.player_id = network_state.local_player_id;
msg.x = steps; // 使用x字段存储步数
msg.y = accepted ? 1 : 0; // 使用y字段存储是否同意
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
-144
View File
@@ -1,144 +0,0 @@
/**
* @file network.h
* @brief
* @note
* 1.
* 2.
* 3.
*/
#ifndef NETWORK_H
#define NETWORK_H
#include "gobang.h"
#include "type.h"
#include "config.h"
#include <stdbool.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SOCKET int
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#define closesocket close
#endif
// 函数声明
/**
* @brief
* @return true
* @return false
*/
bool init_network();
/**
* @brief
*/
void cleanup_network();
/**
* @brief
* @param port
* @return true
* @return false
*/
bool create_server(int port);
/**
* @brief
* @param ip IP地址
* @param port
* @return true
* @return false
*/
bool connect_to_server(const char* ip, int port);
/**
* @brief
* @param msg
* @return true
* @return false
*/
bool send_network_message(const NetworkMessage* msg);
/**
* @brief
* @param msg
* @param timeout_ms 0
* @return true
* @return false
*/
bool receive_network_message(NetworkMessage* msg, int timeout_ms);
/**
* @brief
*/
void disconnect_network();
/**
* @brief
* @return true
* @return false
*/
bool is_network_connected();
/**
* @brief IP地址
* @param ip_buffer IP地址的缓冲区
* @param buffer_size
* @return true
* @return false
*/
bool get_local_ip(char* ip_buffer, int buffer_size);
/**
* @brief
* @param x
* @param y
* @param player_id ID
* @return true
* @return false
*/
bool send_move(int x, int y, int player_id);
/**
* @brief
* @param message
* @return true
* @return false
*/
bool send_chat_message(const char* message);
/**
* @brief
* @return true
* @return false
*/
bool send_surrender();
/**
* @brief
* @param steps
* @return true
* @return false
*/
bool send_undo_request(int steps);
/**
* @brief
* @param accepted
* @param steps
* @return true
* @return false
*/
bool send_undo_response(bool accepted, int steps);
#endif // NETWORK_H
-531
View File
@@ -1,531 +0,0 @@
#include "record.h"
#include "game_mode.h"
#include "gobang.h"
#include "init_board.h"
#include "ui.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#include <io.h>
#else
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#endif
/**
* @brief
* @note :
* 1.
* 2.
* 3. :
* - /
* - (/AI)
* - (1-based坐标)
* -
* 4. Enter键控制步骤前进
* 5. :
* -
* -
* - MVP
* @note :
* - 使
* - 1-based方便用户理解
* -
* - calculate_final_score()
*/
void review_process(int game_mode)
{
int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
// 如果评分尚未计算,则计算评分
if (!scores_calculated)
{
calculate_game_scores();
}
else
{
// 评分已从文件中加载,直接使用
printf("从记录文件中加载评分数据\n");
}
if (review_choice == 1)
{
printf("\n===== 复盘记录(总步数:%d) =====\n", step_count);
// 清空输入缓冲区
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
// 创建临时复盘棋盘
int temp_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
memset(temp_board, EMPTY, sizeof(temp_board)); // 初始化为空棋盘
// 逐步重现游戏过程
for (int i = 0; i < step_count; i++)
{
Step s = steps[i]; // 获取当前步骤
temp_board[s.x][s.y] = s.player; // 在临时棋盘上落子
// 打印当前步骤信息
// 根据游戏模式显示不同的标题和玩家信息
if (game_mode == GAME_MODE_AI)
{
// 人机对战
printf("\n===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER) ? "玩家" : "AI",
s.x + 1, s.y + 1);
}
else if (game_mode == GAME_MODE_PVP)
{
// 双人对战
printf("\n===== 五子棋双人对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER1) ? "玩家1(黑棋)" : "玩家2(白棋)",
s.x + 1, s.y + 1);
}
else if (game_mode == GAME_MODE_NETWORK)
{
// 网络对战
printf("\n===== 五子棋网络对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER1) ? "玩家1(黑棋)" : "玩家2(白棋)",
s.x + 1, s.y + 1);
}
// 打印当前复盘棋盘
printf(" ");
for (int col = 0; col < BOARD_SIZE; col++)
{
printf("%2d", col + 1); // 列号
}
printf("\n");
for (int row = 0; row < BOARD_SIZE; row++)
{
printf("%2d ", row + 1); // 行号
for (int col = 0; col < BOARD_SIZE; col++)
{
if (temp_board[row][col] == PLAYER || temp_board[row][col] == PLAYER1)
{
printf("x ");
}
else if (temp_board[row][col] == AI || temp_board[row][col] == PLAYER2)
{
printf("");
}
else
{
printf("· ");
}
}
printf("\n"); // 行结束换行
}
// 如果不是最后一步,等待用户按键继续
if (i < step_count - 1)
{
printf("\n按Enter继续下一步...");
while (getchar() != '\n')
; // 等待回车
}
}
// 显示胜负结果(直接使用文件中的信息)
printf("\n===== 对局结果 =====");
if (strcmp(winner_info, "玩家获胜") == 0)
{
printf("\n? 恭喜!玩家获胜!\n");
}
else if (strcmp(winner_info, "AI获胜") == 0)
{
printf("\n? AI获胜!\n");
}
else if (strcmp(winner_info, "玩家1获胜") == 0)
{
printf("\n? 恭喜!玩家1(黑棋)获胜!\n");
}
else if (strcmp(winner_info, "玩家2获胜") == 0)
{
printf("\n? 恭喜!玩家2(白棋)获胜!\n");
}
else
{
printf("\n?? 对局平局或未完成\n");
}
printf("\n复盘结束!按Enter查看评分...");
getchar(); // 等待用户按键
}
// 显示评分结果
display_game_scores(game_mode);
getchar();
}
/**
* @brief
*/
void calculate_game_scores()
{
// 评估双方表现
player1_final_score = 0;
player2_final_score = 0;
// 遍历所有步数,累积每一步的得分,后期步骤权重更高
for (int i = 0; i < step_count; i++)
{
// 计算时间权重因子:步数越靠后,权重越大
double time_weight = 1.0 + (double)i / step_count * TIME_WEIGHT_FACTOR; // 最后的步骤权重是开始步骤的(1+TIME_WEIGHT_FACTOR)倍
if (steps[i].player == PLAYER || steps[i].player == PLAYER1)
{
player1_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight);
}
else
{
player2_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight);
}
}
// 胜负加权:获胜方获得额外的评分奖励
if (step_count > 0)
{
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, last_step.player))
{
// 获胜方获得额外奖励分数
if (last_step.player == PLAYER || last_step.player == PLAYER1)
{
player1_final_score += WIN_BONUS; // 获胜奖励
}
else
{
player2_final_score += WIN_BONUS; // 获胜奖励
}
}
}
scores_calculated = 1; // 标记评分已计算
}
/**
* @brief MVP评选
* @param game_mode (1-, 2-)
*/
void display_game_scores(int game_mode)
{
printf("\n===== 对局评分 =====\n");
double sum_score = (long double)player1_final_score + (long double)player2_final_score;
if (sum_score > 0)
{
if (game_mode == GAME_MODE_AI)
{
printf("玩家得分: %d, 占比: %.2f%%\n",
player1_final_score, (double)player1_final_score * 100.0 / sum_score);
printf("AI得分: %d, 占比: %.2f%%\n",
player2_final_score, (double)player2_final_score * 100.0 / sum_score);
}
else if (game_mode == GAME_MODE_PVP)
{
printf("玩家1(黑棋)得分: %d, 占比: %.2f%%\n",
player1_final_score, (double)player1_final_score * 100.0 / sum_score);
printf("玩家2(白棋)得分: %d, 占比: %.2f%%\n",
player2_final_score, (double)player2_final_score * 100.0 / sum_score);
}
else if (game_mode == GAME_MODE_NETWORK)
{
printf("玩家1(黑棋)得分: %d, 占比: %.2f%%\n",
player1_final_score, (double)player1_final_score * 100.0 / sum_score);
printf("玩家2(白棋)得分: %d, 占比: %.2f%%\n",
player2_final_score, (double)player2_final_score * 100.0 / sum_score);
}
}
else
{
if (game_mode == GAME_MODE_AI)
{
printf("玩家得分: %d\n", player1_final_score);
printf("AI得分: %d\n", player2_final_score);
}
else
{
printf("玩家1(黑棋)得分: %d\n", player1_final_score);
printf("玩家2(白棋)得分: %d\n", player2_final_score);
}
printf("注: 双方得分均为0,无法计算占比\n");
}
// 评选MVP
if (player1_final_score > player2_final_score)
{
printf("\nMVP: %s (领先 %d 分)\n", (game_mode == GAME_MODE_AI) ? "玩家" : "玩家1(黑棋)", player1_final_score - player2_final_score);
}
else if (player2_final_score > player1_final_score)
{
printf("\nMVP: %s (领先 %d 分)\n", (game_mode == GAME_MODE_AI) ? "AI" : "玩家2(白棋)", player2_final_score - player1_final_score);
}
else
{
printf("\n双方势均力敌!\n");
}
}
/**
* @brief
* @return int (0-, 1-, 2-, 3-)
*/
void handle_save_record(int game_mode)
{
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.csv", t);
int save_status = save_game_to_file(filename, game_mode);
switch (save_status)
{
case 0: // 成功
printf("\n游戏记录已成功保存至: %s (CSV格式)\n", filename);
printf("您可以使用以下命令进行复盘: .\\gobang.exe -l %s\n", filename);
printf("CSV格式文件可以直接用Excel打开查看和分析\n");
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;
}
}
}
/**
* @brief
* @param filename
* @return int :
* 0:
* 1:
* 2:
* 3:
*/
int save_game_to_file(const char *filename, int game_mode)
{
// 创建records目录(如果不存在)
struct stat st = {0};
if (stat("records", &st) == -1)
{
if (mkdir("records") != 0)
{
// 检查是否目录已存在(多线程情况下可能被其他线程创建)
if (stat("records", &st) == -1)
{
#ifdef _WIN32
printf("错误:无法创建records目录\n");
printf("可能原因:\n");
printf("1. 没有写入权限 - 请尝试以管理员身份运行\n");
printf("2. 防病毒软件阻止 - 请检查安全软件设置\n");
printf("3. 路径无效 - 请检查工作目录\n");
#else
perror("创建目录失败");
#endif
return 1; // 目录创建失败
}
}
}
// 打开文件
char fullpath[256];
snprintf(fullpath, sizeof(fullpath), "records/%s", filename);
FILE *file = fopen(fullpath, "w");
if (!file)
{
return 2; // 文件打开失败
}
// 判断胜负结果
strcpy(winner_info, "平局或未完成");
if (step_count > 0)
{
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, last_step.player))
{
if (game_mode == GAME_MODE_AI)
{
// 人机对战
if (last_step.player == PLAYER)
{
strcpy(winner_info, "玩家获胜");
}
else
{
strcpy(winner_info, "AI获胜");
}
}
else if (game_mode == GAME_MODE_PVP)
{
// 双人对战
if (last_step.player == PLAYER1)
{
strcpy(winner_info, "玩家1获胜");
}
else
{
strcpy(winner_info, "玩家2获胜");
}
}
else if (game_mode == GAME_MODE_NETWORK)
{
// 网络对战
if (last_step.player == PLAYER1)
{
strcpy(winner_info, "玩家1获胜");
}
else
{
strcpy(winner_info, "玩家2获胜");
}
}
}
}
// 写入CSV文件头部
if (fprintf(file, "游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果\n%d,%d,%d,%d,%s\n\n", game_mode, BOARD_SIZE, player1_final_score, player2_final_score, winner_info) < 0)
{
fclose(file);
return 3; // 文件写入失败
}
// 写入CSV表头
if (fprintf(file, "步数,玩家,行坐标,列坐标\n") < 0)
{
fclose(file);
return 3; // 文件写入失败
}
// 写入所有落子步骤(CSV格式)
for (int i = 0; i < step_count; i++)
{
if (fprintf(file, "%d,%d,%d,%d\n", i+1, steps[i].player, steps[i].x+1, steps[i].y+1) < 0)
{
fclose(file);
return 3; // 文件写入失败
}
}
if (fclose(file) != 0)
{
return 3; // 文件关闭/写入失败
}
return 0; // 成功
}
/**
* @brief
* @param filename
* @return true
* @return false
*/
int load_game_from_file(const char *filename)
{
// 打开文件
char fullpath[256];
snprintf(fullpath, sizeof(fullpath), "records/%s", filename);
FILE *file = fopen(fullpath, "r");
if (!file)
{
return false;
}
// 跳过CSV文件头部行
char buffer[256];
if (fgets(buffer, sizeof(buffer), file) == NULL) // 跳过"游戏模式,棋盘大小"
{
fclose(file);
return 0;
}
// 读取游戏模式、棋盘大小和评分结果
int game_mode, size;
// 尝试读取新格式(包含胜负信息)
int read_count = fscanf(file, "%d,%d,%d,%d,%49s", &game_mode, &size, &player1_final_score, &player2_final_score, winner_info);
if (read_count == 4)
{
// 旧格式文件,没有胜负信息
strcpy(winner_info, "未知");
}
else if (read_count != 5)
{
// 文件格式错误
fclose(file);
return 0;
}
if (game_mode != GAME_MODE_AI && game_mode != GAME_MODE_PVP && game_mode != GAME_MODE_NETWORK)
{
fclose(file);
return 0; // 无效的游戏模式
}
if (size < 5 || size > MAX_BOARD_SIZE)
{
fclose(file);
return false;
}
// 设置评分已计算标志
scores_calculated = 1;
// 跳过空行和表头行
fgets(buffer, sizeof(buffer), file); // 跳过换行
fgets(buffer, sizeof(buffer), file); // 跳过空行
fgets(buffer, sizeof(buffer), file); // 跳过"步数,玩家,行坐标,列坐标"
// 初始化棋盘
BOARD_SIZE = size;
empty_board();
// 读取所有落子步骤
step_count = 0;
int step_num; // 用于存储步数,但不使用
while (fscanf(file, "%d,%d,%d,%d", &step_num, &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 4)
{
// 将1-based坐标转换为0-based坐标
steps[step_count].x--;
steps[step_count].y--;
step_count++;
}
fclose(file);
return game_mode;
}
-51
View File
@@ -1,51 +0,0 @@
/**
* @file record.h
* @brief
* @note
*
*/
#ifndef RECORD_H
#define RECORD_H
#include "gobang.h"
// --- 复盘与记录功能 ---
/**
* @brief
* @param game_mode 12
*/
void review_process(int game_mode);
/**
* @brief
* @param filename
* @param game_mode
* @return 00
*/
int save_game_to_file(const char *filename, int game_mode);
/**
* @brief
* @param game_mode
*/
void handle_save_record(int game_mode);
/**
* @brief
* @param filename
* @return 120
*/
int load_game_from_file(const char *filename);
/**
* @brief
*/
void calculate_game_scores();
/**
* @brief MVP评选
* @param game_mode 1-2-
*/
void display_game_scores(int game_mode);
#endif // RECORD_H
-9
View File
@@ -1,9 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,游戏结果
1,15,289,757,玩家2获胜
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,3,3
3,2,4,4
4,1,5,5
5,2,4,5
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,游戏结果
2 1,15,289,757,玩家2获胜
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,3,3
6 3,2,4,4
7 4,1,5,5
8 5,2,4,5
-18
View File
@@ -1,18 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分
1,15,1581,33119,玩家1得分
步数,玩家,行坐标,列坐标
1,1,2,2
2,2,3,3
3,1,2,3
4,2,2,4
5,1,4,4
6,2,4,2
7,1,5,5
8,2,1,5
9,1,6,6
10,2,7,7
11,1,7,6
12,2,5,6
13,1,9,9
14,2,5,1
1 游戏模式,棋盘大小,玩家1得分,玩家2得分
2 1,15,1581,33119,玩家1得分
3 步数,玩家,行坐标,列坐标
4 1,1,2,2
5 2,2,3,3
6 3,1,2,3
7 4,2,2,4
8 5,1,4,4
9 6,2,4,2
10 7,1,5,5
11 8,2,1,5
12 9,1,6,6
13 10,2,7,7
14 11,1,7,6
15 12,2,5,6
16 13,1,9,9
17 14,2,5,1
-35
View File
@@ -1,35 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分
1,15,5985,7221,玩家1得分
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,4,4
3,2,5,5
4,1,7,7
5,2,7,8
6,1,3,3
7,2,6,8
8,1,5,8
9,2,9,8
10,1,10,8
11,2,8,7
12,1,8,6
13,2,9,7
14,1,9,6
15,2,7,6
16,1,6,5
17,2,10,9
18,1,6,4
19,2,5,4
20,1,6,3
21,2,6,2
22,1,6,6
23,2,6,7
24,1,5,6
25,2,4,7
26,1,5,7
27,2,4,8
28,1,5,9
29,2,5,10
30,1,1,1
31,2,11,10
1 游戏模式,棋盘大小,玩家1得分,玩家2得分
2 1,15,5985,7221,玩家1得分
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,4,4
6 3,2,5,5
7 4,1,7,7
8 5,2,7,8
9 6,1,3,3
10 7,2,6,8
11 8,1,5,8
12 9,2,9,8
13 10,1,10,8
14 11,2,8,7
15 12,1,8,6
16 13,2,9,7
17 14,1,9,6
18 15,2,7,6
19 16,1,6,5
20 17,2,10,9
21 18,1,6,4
22 19,2,5,4
23 20,1,6,3
24 21,2,6,2
25 22,1,6,6
26 23,2,6,7
27 24,1,5,6
28 25,2,4,7
29 26,1,5,7
30 27,2,4,8
31 28,1,5,9
32 29,2,5,10
33 30,1,1,1
34 31,2,11,10
-22
View File
@@ -1,22 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
1,15,5155,2527,玩家获胜
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,4,4
3,2,5,5
4,1,6,6
5,2,5,6
6,1,7,7
7,2,5,7
8,1,5,4
9,2,3,4
10,1,6,4
11,2,6,5
12,1,7,4
13,2,8,4
14,1,7,5
15,2,5,3
16,1,7,6
17,2,7,3
18,1,7,8
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 1,15,5155,2527,玩家获胜
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,4,4
6 3,2,5,5
7 4,1,6,6
8 5,2,5,6
9 6,1,7,7
10 7,2,5,7
11 8,1,5,4
12 9,2,3,4
13 10,1,6,4
14 11,2,6,5
15 12,1,7,4
16 13,2,8,4
17 14,1,7,5
18 15,2,5,3
19 16,1,7,6
20 17,2,7,3
21 18,1,7,8
-15
View File
@@ -1,15 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,2898,5601,玩家1获胜
步数,玩家,行坐标,列坐标
1,1,1,1
2,2,2,2
3,1,3,3
4,2,1,2
5,1,4,4
6,2,1,3
7,1,5,5
8,2,1,4
9,1,6,6
10,2,1,5
11,1,7,7
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,2898,5601,玩家1获胜
3 步数,玩家,行坐标,列坐标
4 1,1,1,1
5 2,2,2,2
6 3,1,3,3
7 4,2,1,2
8 5,1,4,4
9 6,2,1,3
10 7,1,5,5
11 8,2,1,4
12 9,1,6,6
13 10,2,1,5
14 11,1,7,7
-7
View File
@@ -1,7 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,135,452,平局或未完成
步数,玩家,行坐标,列坐标
1,2,3,3
2,1,4,4
3,2,4,3
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,135,452,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,2,3,3
5 2,1,4,4
6 3,2,4,3
-6
View File
@@ -1,6 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,110,212,平局或未完成
步数,玩家,行坐标,列坐标
1,1,3,3
2,2,6,6
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,110,212,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,1,3,3
5 2,2,6,6
-6
View File
@@ -1,6 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,110,212,平局或未完成
步数,玩家,行坐标,列坐标
1,1,3,3
2,2,6,6
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,110,212,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,1,3,3
5 2,2,6,6
-14
View File
@@ -1,14 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
3,15,13500,1039,平局或未完成
步数,玩家,行坐标,列坐标
1,1,4,4
2,2,5,5
3,1,6,6
4,2,7,7
5,1,5,4
6,2,4,5
7,1,6,5
8,2,5,6
9,1,6,3
10,1,6,4
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 3,15,13500,1039,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,1,4,4
5 2,2,5,5
6 3,1,6,6
7 4,2,7,7
8 5,1,5,4
9 6,2,4,5
10 7,1,6,5
11 8,2,5,6
12 9,1,6,3
13 10,1,6,4
-5
View File
@@ -1,5 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
3,15,0,130,平局或未完成
步数,玩家,行坐标,列坐标
1,2,4,4
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 3,15,0,130,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,2,4,4
-28
View File
@@ -1,28 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
1,20,4575,8652,玩家获胜
步数,玩家,行坐标,列坐标
1,2,11,11
2,1,3,3
3,2,4,4
4,1,5,5
5,2,4,5
6,1,4,6
7,2,3,7
8,1,4,7
9,2,4,8
10,1,2,6
11,2,3,6
12,1,5,9
13,2,3,8
14,1,5,8
15,2,5,7
16,1,6,9
17,2,4,9
18,1,3,9
19,2,7,10
20,1,5,10
21,2,4,11
22,1,5,11
23,2,4,10
24,1,5,12
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 1,20,4575,8652,玩家获胜
3 步数,玩家,行坐标,列坐标
4 1,2,11,11
5 2,1,3,3
6 3,2,4,4
7 4,1,5,5
8 5,2,4,5
9 6,1,4,6
10 7,2,3,7
11 8,1,4,7
12 9,2,4,8
13 10,1,2,6
14 11,2,3,6
15 12,1,5,9
16 13,2,3,8
17 14,1,5,8
18 15,2,5,7
19 16,1,6,9
20 17,2,4,9
21 18,1,3,9
22 19,2,7,10
23 20,1,5,10
24 21,2,4,11
25 22,1,5,11
26 23,2,4,10
27 24,1,5,12
-21
View File
@@ -1,21 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
1,15,1478,3769,AI获胜
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,3,3
3,2,4,4
4,1,3,4
5,2,3,5
6,1,5,3
7,2,4,3
8,1,4,5
9,2,5,6
10,1,6,6
11,2,3,2
12,1,5,5
13,2,5,4
14,1,7,7
15,2,6,5
16,1,1,1
17,2,7,6
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 1,15,1478,3769,AI获胜
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,3,3
6 3,2,4,4
7 4,1,3,4
8 5,2,3,5
9 6,1,5,3
10 7,2,4,3
11 8,1,4,5
12 9,2,5,6
13 10,1,6,6
14 11,2,3,2
15 12,1,5,5
16 13,2,5,4
17 14,1,7,7
18 15,2,6,5
19 16,1,1,1
20 17,2,7,6
-93
View File
@@ -1,93 +0,0 @@
/**
* @file type.h
* @brief
* @note 使
* @author
*/
#ifndef TYPE_H
#define TYPE_H
#include <stdbool.h>
#include <time.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#define SOCKET int
#endif
// ==================== 游戏核心数据结构 ====================
/**
* @brief
*/
typedef struct {
int player; // 执行该步的玩家标识
int x; // 落子的行坐标 (0-based)
int y; // 落子的列坐标 (0-based)
} Step;
/**
* @brief
* @details
*/
typedef struct {
int continuous_chess; // 连续同色棋子的数量
bool check_start; // 棋子序列的起始端是否为空位(即是否开放)
bool check_end; // 棋子序列的末尾端是否为空位(即是否开放)
} DirInfo;
// ==================== AI相关数据结构 ====================
/**
* @brief
* @details AI移动排序
*/
typedef struct {
int x, y; // 位置坐标
int score; // 评估分数
} ScoredMove;
/**
* @brief
* @details AI威胁检测系统
*/
typedef enum {
THREAT_NONE = 0, // 无威胁
THREAT_WIN = 5, // 直接获胜
THREAT_FOUR = 4, // 活四/冲四
THREAT_THREE = 3, // 活三
THREAT_DOUBLE = 2, // 双威胁
THREAT_POTENTIAL = 1 // 潜在威胁
} ThreatLevel;
// ==================== 网络相关数据结构 ====================
/**
* @brief
* @details
*/
typedef struct {
int type; // 消息类型
int player_id; // 玩家ID
int x, y; // 坐标(用于落子)
char message[256]; // 消息内容(用于聊天等)
time_t timestamp; // 时间戳
} NetworkMessage;
/**
* @brief
* @details
*/
typedef struct {
SOCKET socket; // 套接字
bool is_server; // 是否为服务器
bool is_connected; // 是否已连接
int local_player_id; // 本地玩家ID
int remote_player_id; // 远程玩家ID
char remote_ip[16]; // 远程IP地址(MAX_IP_LENGTH = 16
int port; // 端口号
} NetworkGameState;
#endif // TYPE_H
-204
View File
@@ -1,204 +0,0 @@
#include "ui.h"
#include "gobang.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <unistd.h>
#endif
/**
* @brief
*/
void display_main_menu()
{
printf("===== 五子棋游戏 =====\n");
printf("1. AI模式\n");
printf("2. 玩家比赛\n");
printf("3. 网络对战\n");
printf("4. 复盘模式\n");
printf("5. 游戏设置\n");
printf("6. 游戏规则\n");
printf("7. 关于游戏\n");
printf("0. 退出游戏\n");
printf("=====================\n");
}
/**
* @brief
*/
void display_board()
{
printf("\n ");
// 打印列号
for (int j = 0; j < BOARD_SIZE; j++)
{
printf("%2d", j);
}
printf("\n");
// 打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d", i); // 打印行号
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == EMPTY)
{
printf(" ·"); // 空位用点表示
}
else if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
{
printf(""); // 玩家1用实心圆表示
}
else
{
printf(""); // 玩家2/AI用空心圆表示
}
}
printf("\n");
}
printf("\n");
}
/**
* @brief
* @param current_player
* @param step_count
*/
void display_game_status(int current_player, int step_count)
{
printf("当前步数: %d\n", step_count);
if (current_player == PLAYER || current_player == PLAYER1)
{
printf("当前玩家: ●\n");
}
else
{
printf("当前玩家: ○\n");
}
}
/**
* @brief
* @param winner
*/
void display_winner(int winner)
{
printf("\n游戏结束!\n");
if (winner == PLAYER)
{
printf("玩家获胜!\n");
}
else if (winner == AI)
{
printf("AI获胜!\n");
}
else if (winner == PLAYER1)
{
printf("玩家1获胜!\n");
}
else if (winner == PLAYER2)
{
printf("玩家2获胜!\n");
}
else
{
printf("平局!\n");
}
}
/**
* @brief
*/
void display_settings_menu()
{
printf("\n===== 游戏设置 =====\n");
printf("1. 棋盘大小设置\n");
printf("2. 禁手规则设置\n");
printf("3. 计时器设置\n");
printf("4. 网络配置设置\n");
printf("5. AI难度设置\n");
printf("6. 返回主菜单\n");
printf("==================\n");
}
/**
* @brief
*/
void clear_screen()
{
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
/**
* @brief
* @param prompt
*/
void pause_for_input(const char* prompt)
{
printf("%s", prompt);
#ifdef _WIN32
_getch();
#else
getchar();
#endif
}
/**
* @brief
*/
void display_game_rules()
{
printf("\n===== 五子棋游戏规则 =====\n");
printf("1. 🎮 游戏目标:\n");
printf(" 在棋盘上连成五个同色棋子(横、竖、斜均可)\n\n");
printf("2. 🔄 游戏流程:\n");
printf(" - ⚫ 黑棋先行,双方轮流落子\n");
printf(" - 📍 输入坐标格式:行号 列号(如:7 7)\n");
printf(" - ✨ 棋子落在棋盘交叉点上\n\n");
printf("3. 🏆 胜负判定:\n");
printf(" - 🎉 率先连成五子者获胜\n");
printf(" - 🤝 棋盘下满无人获胜则为平局\n\n");
printf("4. 🚫 禁手规则(可选):\n");
printf(" - ❌ 三三禁手:同时形成两个活三\n");
printf(" - ❌ 四四禁手:同时形成两个冲四\n");
printf(" - ❌ 长连禁手:连成六子或以上\n\n");
printf("5. 🛠️ 特殊功能:\n");
printf(" - ↩️ 悔棋:输入 'R' 或 'r' 可悔棋\n");
printf(" - 💾 保存:游戏结束后可保存对局记录\n");
printf(" - 📖 复盘:可加载历史对局进行复盘\n");
printf("============================\n");
}
/**
* @brief
*/
void display_about()
{
printf("\n===== 关于五子棋游戏 =====\n");
printf("🎮 游戏名称:五子棋人机对战\n");
printf("📦 版本:4.0\n");
printf("👨‍💻 开发者:刘航宇\n");
printf("📧 联系邮箱:3364451258@qq.com\n");
printf("🌐 项目主页:https://github.com/LHY0125/Gobang-Game\n\n");
printf("✨ 主要特性:\n");
printf(" 🤖 智能AI对战(支持多种难度)\n");
printf(" 👥 双人对战模式\n");
printf(" 🌐 网络对战(局域网/互联网)\n");
printf(" 📝 对局记录与复盘\n");
printf(" 🚫 禁手规则支持\n");
printf(" ⏱️ 计时器功能\n");
printf(" 📏 自定义棋盘大小\n");
printf(" 📊 评分系统\n\n");
printf("🙏 感谢使用!\n");
printf("========================\n");
}
-68
View File
@@ -1,68 +0,0 @@
/**
* @file ui.h
* @brief
* @note
*
*/
#ifndef UI_H
#define UI_H
#include "gobang.h"
/**
* @brief UI模块 -
* @author
* @date 2025-07-10
* @version 5.0
*/
/**
* @brief
*/
void display_main_menu();
/**
* @brief
*/
void display_board();
/**
* @brief
* @param current_player
* @param step_count
*/
void display_game_status(int current_player, int step_count);
/**
* @brief
* @param winner
*/
void display_winner(int winner);
/**
* @brief
*/
void display_settings_menu();
/**
* @brief
*/
void clear_screen();
/**
* @brief
* @param prompt
*/
void pause_for_input(const char* prompt);
/**
* @brief
*/
void display_game_rules();
/**
* @brief
*/
void display_about();
#endif // UI_H
+92
View File
@@ -0,0 +1,92 @@
#include "gobang.h"
#include <stdio.h>
/**
* @brief powershell
* gcc .c gobang.c -o output/.exe
* gcc .c gobang.c output/
* @brief powershell
* .\output\.exe
*/
int main()
{
// 初始化阶段:获取棋盘尺寸
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)
{
BOARD_SIZE = 15;
printf("输入无效,使用默认标准棋盘15X15\n");
}
// 添加AI难度选择
int AI_DEPTH = 3;
printf("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):");
scanf("%d", &AI_DEPTH);
if (AI_DEPTH < 1 || AI_DEPTH > 5)
{
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;
printf("\n请输入落子坐标(行 列,1~%d):", BOARD_SIZE);
scanf("%d %d", &x, &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; // 退出游戏循环
}
}
return 0;
}