mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-05-10 02:19:46 +08:00
Compare commits
16 Commits
ai
...
NSIS_and_Inno
| Author | SHA1 | Date | |
|---|---|---|---|
| 17d7079c41 | |||
| 1abde99a68 | |||
| 205e943bcb | |||
| ae0629ef6e | |||
| 2121831478 | |||
| a11ddeb8d1 | |||
| 39d84f38cf | |||
| 198a8a2943 | |||
| 0e53b09d9b | |||
| 1d8e2e25cf | |||
| 6d4de737c4 | |||
| b413f7254a | |||
| cb81105809 | |||
| fbe4f5273c | |||
| 59c816767b | |||
| 504868c739 |
+12
-1
@@ -2,6 +2,7 @@
|
|||||||
*.exe
|
*.exe
|
||||||
*.out
|
*.out
|
||||||
*.app
|
*.app
|
||||||
|
*.o
|
||||||
|
|
||||||
# IDE配置文件
|
# IDE配置文件
|
||||||
.idea/
|
.idea/
|
||||||
@@ -39,4 +40,14 @@ Thumbs.db
|
|||||||
# 动态库文件
|
# 动态库文件
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
|
# 资源文件
|
||||||
|
*.res
|
||||||
|
*.ico
|
||||||
|
|
||||||
|
# 打包文件
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# 临时游戏存档
|
||||||
|
*.csv
|
||||||
+105
-461
@@ -1,461 +1,105 @@
|
|||||||
# 🚀 五子棋AI增强指南
|
## 🎯 项目优势
|
||||||
|
### ✅ 已经做得很好的方面
|
||||||
## 📋 概述
|
- 📚 代码质量优秀 :注释覆盖率30.1%,使用Doxygen格式文档
|
||||||
|
- 🏗️ 架构设计合理 :模块化设计,职责分离清晰
|
||||||
本文档详细介绍了提升五子棋AI水平的各种方法和实现策略,基于当前项目的代码架构,提供从简单参数调优到复杂算法改进的完整方案。
|
- 🎮 功能完整丰富 :支持人机对战、双人对战、网络对战、GUI界面
|
||||||
|
- 🔧 技术栈现代化 :SDL3图形库、双版本架构、专业安装包
|
||||||
## 🎯 当前AI分析
|
- 📋 文档完善 :详细的README、AI增强指南、架构重构指南
|
||||||
|
- 🌐 跨平台考虑 :已有条件编译支持Windows/Linux
|
||||||
### 现有优势
|
## 🚀 改进建议
|
||||||
- ✅ 模块化设计良好
|
### 1. 🧪 测试体系建设(高优先级)
|
||||||
- ✅ 基础Minimax + α-β剪枝算法
|
当前状况 :项目缺乏自动化测试
|
||||||
- ✅ 完整的棋型评估系统
|
|
||||||
- ✅ 威胁检测机制
|
建议改进 :
|
||||||
- ✅ 防御优先策略
|
|
||||||
|
- 添加单元测试框架(如Unity或自制简单测试框架)
|
||||||
### 改进空间
|
- 为核心模块编写测试用例:AI算法、棋盘逻辑、网络通信
|
||||||
- 🔄 搜索深度有限(当前3层)
|
- 添加集成测试验证各模块协作
|
||||||
- 🔄 评估函数相对简单
|
- 创建性能基准测试
|
||||||
- 🔄 缺乏开局库和残局库
|
### 2. 🛡️ 错误处理和安全性增强(高优先级)
|
||||||
- 🔄 没有学习机制
|
当前状况 :基础错误处理存在,但可以更完善
|
||||||
- 🔄 搜索效率可优化
|
|
||||||
|
建议改进 :
|
||||||
## 🛠️ 改进方案
|
|
||||||
|
- 增强输入验证,防止缓冲区溢出
|
||||||
### 1. 立即可实施的改进(难度:⭐)
|
- 添加网络通信的安全验证机制
|
||||||
|
- 完善内存管理,添加内存泄漏检测
|
||||||
#### 1.1 参数调优
|
- 增加异常恢复机制
|
||||||
|
### 3. 🎯 AI算法优化(中优先级)
|
||||||
**修改 `config.h` 中的关键参数:**
|
当前状况 :基础Minimax+α-β剪枝,搜索深度3层
|
||||||
|
|
||||||
```c
|
建议改进 :
|
||||||
// 增加搜索深度
|
|
||||||
#define DEFAULT_AI_DEPTH 5 // 从3提升到5
|
- 实现置换表缓存系统
|
||||||
|
- 添加开局库和残局库
|
||||||
// 优化防守系数
|
- 实现迭代加深搜索
|
||||||
#define DEFAULT_DEFENSE_COEFFICIENT 1.5 // 从1.2提升到1.5
|
- 增加威胁检测优化
|
||||||
|
- 支持多线程并行搜索
|
||||||
// 扩大搜索范围
|
### 4. 🔧 构建和部署优化(中优先级)
|
||||||
#define AI_NEARBY_RANGE 3 // 从2扩大到3
|
当前状况 :编译脚本功能完善,但可以更自动化
|
||||||
|
|
||||||
// 降低搜索范围限制阈值
|
建议改进 :
|
||||||
#define AI_SEARCH_RANGE_THRESHOLD 8 // 从10降低到8
|
|
||||||
```
|
- 添加CMake构建系统支持
|
||||||
|
- 实现持续集成/持续部署(CI/CD)
|
||||||
#### 1.2 评分系统优化
|
- 添加依赖管理和版本控制
|
||||||
|
- 完善跨平台构建脚本
|
||||||
**在 `config.h` 中添加新的评分项:**
|
### 5. 📊 性能监控和分析(中优先级)
|
||||||
|
建议添加 :
|
||||||
```c
|
|
||||||
// 组合棋型评分
|
- 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 // 潜在五连
|
### 6. 🎮 用户体验提升(低优先级)
|
||||||
```
|
建议改进 :
|
||||||
|
|
||||||
### 2. 短期改进(1-2周,难度:⭐⭐)
|
- 添加音效和动画效果
|
||||||
|
- 实现主题和皮肤系统
|
||||||
#### 2.1 移动排序优化
|
- 增加游戏统计和成就系统
|
||||||
|
- 支持自定义快捷键
|
||||||
**在 `ai.c` 中添加移动排序函数:**
|
### 7. 🌐 网络功能增强(低优先级)
|
||||||
|
建议改进 :
|
||||||
```c
|
|
||||||
// 移动排序结构
|
- 实现房间系统和匹配机制
|
||||||
typedef struct {
|
- 添加观战功能
|
||||||
int x, y;
|
- 支持断线重连
|
||||||
int score;
|
- 实现聊天系统
|
||||||
} ScoredMove;
|
## 📋 具体实施建议
|
||||||
|
### 立即可实施(1-2周)
|
||||||
// 移动排序函数
|
1. 1.
|
||||||
int compare_moves(const void *a, const void *b) {
|
创建基础测试框架
|
||||||
ScoredMove *moveA = (ScoredMove*)a;
|
2. 2.
|
||||||
ScoredMove *moveB = (ScoredMove*)b;
|
添加输入验证和边界检查
|
||||||
return moveB->score - moveA->score;
|
3. 3.
|
||||||
}
|
完善错误日志记录
|
||||||
|
4. 4.
|
||||||
// 生成并排序候选移动
|
优化编译警告处理
|
||||||
int generate_candidate_moves(ScoredMove *moves, int player) {
|
### 短期目标(1-2月)
|
||||||
int count = 0;
|
1. 1.
|
||||||
for (int i = 0; i < BOARD_SIZE; i++) {
|
完善单元测试覆盖
|
||||||
for (int j = 0; j < BOARD_SIZE; j++) {
|
2. 2.
|
||||||
if (board[i][j] == EMPTY && is_near_stones(i, j)) {
|
实现置换表和开局库
|
||||||
moves[count].x = i;
|
3. 3.
|
||||||
moves[count].y = j;
|
添加性能监控
|
||||||
moves[count].score = evaluate_move(i, j);
|
4. 4.
|
||||||
count++;
|
增强网络安全性
|
||||||
}
|
### 长期规划(3-6月)
|
||||||
}
|
1. 1.
|
||||||
}
|
实现神经网络AI
|
||||||
qsort(moves, count, sizeof(ScoredMove), compare_moves);
|
2. 2.
|
||||||
return count;
|
完整跨平台支持
|
||||||
}
|
3. 3.
|
||||||
```
|
添加数据库支持
|
||||||
|
4. 4.
|
||||||
#### 2.2 威胁检测增强
|
实现云端对战
|
||||||
|
## 🏆 总体评价
|
||||||
**添加多层次威胁检测:**
|
你的项目已经达到了 专业级别的开源项目标准 !代码架构清晰、功能完整、文档详尽。主要的改进空间在于测试覆盖和一些高级特性的添加。
|
||||||
|
|
||||||
```c
|
项目亮点 :
|
||||||
// 威胁类型枚举
|
|
||||||
typedef enum {
|
- 从v7.0的架构重构到v8.2的编译优化,版本迭代思路清晰
|
||||||
THREAT_NONE = 0,
|
- SDL3图形化界面实现现代化
|
||||||
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
|
|
||||||
- [ ] 添加学习机制
|
|
||||||
- [ ] 性能优化
|
|
||||||
|
|
||||||
### 阶段4:AI增强(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不仅要算得深,更要算得准!**
|
|
||||||
+11
-1
@@ -1,7 +1,7 @@
|
|||||||
# 🧠 五子棋AI实现详解
|
# 🧠 五子棋AI实现详解
|
||||||
|
|
||||||
## 📜 算法概述
|
## 📜 算法概述
|
||||||
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。支持人机对战、双人对战和网络对战多种模式。
|
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
graph TD
|
graph TD
|
||||||
@@ -48,6 +48,16 @@ void network_ai_move(int depth, int player_id);
|
|||||||
void sync_ai_decision(int x, int y);
|
void sync_ai_decision(int x, int y);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🎨 GUI界面AI支持 (v8.0新增)
|
||||||
|
```c
|
||||||
|
// GUI模式下的AI可视化决策
|
||||||
|
void gui_ai_move_with_animation(int x, int y);
|
||||||
|
// 显示AI思考过程
|
||||||
|
void show_ai_thinking_process(SDL_Renderer* renderer);
|
||||||
|
// AI决策结果的图形化展示
|
||||||
|
void render_ai_decision_info(SDL_Renderer* renderer, int score, int depth);
|
||||||
|
```
|
||||||
|
|
||||||
## ⚙️ 核心函数
|
## ⚙️ 核心函数
|
||||||
|
|
||||||
### 1. ai_move(int depth)
|
### 1. ai_move(int depth)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## 📋 概述
|
## 📋 概述
|
||||||
|
|
||||||
本文档详细记录了五子棋项目在v7.0版本中进行的重大代码架构重构,包括重构的目标、实施过程、技术细节和带来的改进。
|
本文档详细记录了五子棋项目在v7.0-v8.0版本中进行的重大代码架构重构,包括重构的目标、实施过程、技术细节和带来的改进。v8.0版本新增了SDL3图形化界面模块,实现了双版本架构设计。
|
||||||
|
|
||||||
## 🎯 重构目标
|
## 🎯 重构目标
|
||||||
|
|
||||||
@@ -12,6 +12,8 @@
|
|||||||
3. **全局变量规范化** - 统一管理全局变量,避免散乱分布
|
3. **全局变量规范化** - 统一管理全局变量,避免散乱分布
|
||||||
4. **类型定义标准化** - 集中定义所有数据结构和类型
|
4. **类型定义标准化** - 集中定义所有数据结构和类型
|
||||||
5. **提升可维护性** - 降低代码耦合度,提高可读性和可维护性
|
5. **提升可维护性** - 降低代码耦合度,提高可读性和可维护性
|
||||||
|
6. **双版本架构** - (v8.0新增) 实现控制台版本和GUI版本的并行支持
|
||||||
|
7. **图形化界面集成** - (v8.0新增) 无缝集成SDL3图形库,提供现代化用户界面
|
||||||
|
|
||||||
### 预期收益
|
### 预期收益
|
||||||
- 减少代码重复和冗余
|
- 减少代码重复和冗余
|
||||||
|
|||||||
+73
@@ -0,0 +1,73 @@
|
|||||||
|
# 五子棋游戏编译指南
|
||||||
|
|
||||||
|
本项目现在支持使用 `make` 命令进行编译,提供了更便捷的构建方式。
|
||||||
|
|
||||||
|
## 系统要求
|
||||||
|
|
||||||
|
- MinGW64 编译器
|
||||||
|
- SDL3 开发库(用于图形界面)
|
||||||
|
- Make 工具
|
||||||
|
|
||||||
|
## 编译命令
|
||||||
|
|
||||||
|
### 查看所有可用命令
|
||||||
|
```bash
|
||||||
|
make help
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编译所有版本
|
||||||
|
```bash
|
||||||
|
make all
|
||||||
|
```
|
||||||
|
这将同时编译控制台版本和GUI版本。
|
||||||
|
|
||||||
|
### 只编译控制台版本
|
||||||
|
```bash
|
||||||
|
make console
|
||||||
|
```
|
||||||
|
生成 `gobang_console.exe`
|
||||||
|
|
||||||
|
### 只编译GUI版本
|
||||||
|
```bash
|
||||||
|
make gui
|
||||||
|
```
|
||||||
|
生成 `gobang_gui.exe`
|
||||||
|
|
||||||
|
### 清理编译文件
|
||||||
|
```bash
|
||||||
|
make clean
|
||||||
|
```
|
||||||
|
删除所有目标文件(.o)和可执行文件(.exe)
|
||||||
|
|
||||||
|
### 编译并运行
|
||||||
|
```bash
|
||||||
|
# 编译并运行控制台版本
|
||||||
|
make run-console
|
||||||
|
|
||||||
|
# 编译并运行GUI版本
|
||||||
|
make run-gui
|
||||||
|
```
|
||||||
|
|
||||||
|
## 生成的文件
|
||||||
|
|
||||||
|
- `gobang_console.exe` - 控制台版本,支持所有功能包括图形界面模式
|
||||||
|
- `gobang_gui.exe` - GUI版本,与控制台版本功能相同
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 两个版本都包含完整功能,包括图形界面支持
|
||||||
|
2. 需要确保SDL3库路径正确配置在Makefile中
|
||||||
|
3. 编译时会显示一些警告,这是正常的
|
||||||
|
4. 如果遇到编译错误,请检查MinGW64和SDL3的安装路径
|
||||||
|
|
||||||
|
## 传统编译方式
|
||||||
|
|
||||||
|
如果不使用Makefile,仍可以使用传统的gcc命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 控制台版本
|
||||||
|
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
||||||
|
|
||||||
|
# GUI版本
|
||||||
|
gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
```
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# 为五子棋游戏添加图标指南
|
||||||
|
|
||||||
|
## 问题说明
|
||||||
|
|
||||||
|
在尝试为可执行文件添加图标时,发现icon文件夹中的图标文件格式不正确:
|
||||||
|
- `gobang_icon1.ico` 和 `gobang_icon2.ico` 实际上是HTML文件和PNG文件,而不是真正的ICO格式文件
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 方法一:获取正确的ICO文件
|
||||||
|
|
||||||
|
1. **下载或创建真正的ICO文件**
|
||||||
|
- 使用在线ICO转换工具将PNG/JPG转换为ICO格式
|
||||||
|
- 推荐网站:https://www.icoconverter.com/
|
||||||
|
- 或使用GIMP、Photoshop等图像编辑软件导出ICO格式
|
||||||
|
|
||||||
|
2. **替换现有文件**
|
||||||
|
- 将正确的ICO文件保存为 `icon/gobang_icon.ico`
|
||||||
|
- 确保文件是真正的ICO格式(文件头应为 00 00 01 00)
|
||||||
|
|
||||||
|
3. **修改资源文件**
|
||||||
|
- 编辑 `gobang.rc` 文件
|
||||||
|
- 取消注释图标行:`IDI_APPLICATION ICON "icon\\gobang_icon.ico"`
|
||||||
|
|
||||||
|
4. **重新编译**
|
||||||
|
```bash
|
||||||
|
windres gobang.rc -o gobang.res
|
||||||
|
gcc -std=c17 -o gobang.exe *.c gobang.res -lws2_32
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法二:使用现有PNG文件(需要转换)
|
||||||
|
|
||||||
|
如果你有PNG格式的图标文件,可以:
|
||||||
|
|
||||||
|
1. **在线转换**
|
||||||
|
- 访问 https://convertio.co/png-ico/
|
||||||
|
- 上传PNG文件并转换为ICO格式
|
||||||
|
- 下载转换后的ICO文件
|
||||||
|
|
||||||
|
2. **使用ImageMagick(如果已安装)**
|
||||||
|
```bash
|
||||||
|
magick convert icon/your_image.png icon/gobang_icon.ico
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法三:使用Windows资源编辑器
|
||||||
|
|
||||||
|
1. 编译不带图标的exe文件(当前状态)
|
||||||
|
2. 使用Resource Hacker等工具后期添加图标
|
||||||
|
3. 下载Resource Hacker:http://www.angusj.com/resourcehacker/
|
||||||
|
|
||||||
|
## 当前状态
|
||||||
|
|
||||||
|
- ✅ 程序可以正常编译和运行
|
||||||
|
- ✅ 包含版本信息资源
|
||||||
|
- ❌ 暂时没有应用程序图标
|
||||||
|
- ✅ 提供了完整的构建脚本
|
||||||
|
|
||||||
|
## 编译指令
|
||||||
|
|
||||||
|
### 不带图标版本(当前可用)
|
||||||
|
```bash
|
||||||
|
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
||||||
|
```
|
||||||
|
|
||||||
|
### 带图标版本(需要正确的ICO文件)
|
||||||
|
```bash
|
||||||
|
windres gobang.rc -o gobang.res
|
||||||
|
gcc -std=c17 -o gobang.exe *.c gobang.res -lws2_32
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证ICO文件格式
|
||||||
|
|
||||||
|
可以使用以下PowerShell命令检查文件是否为真正的ICO格式:
|
||||||
|
```powershell
|
||||||
|
Get-Content icon/gobang_icon.ico -AsByteStream -TotalCount 4 | ForEach-Object { '{0:X2}' -f $_ }
|
||||||
|
```
|
||||||
|
|
||||||
|
正确的ICO文件应该显示:`00 00 01 00`
|
||||||
+16
-18
@@ -2,17 +2,31 @@
|
|||||||
|
|
||||||
## 功能概述
|
## 功能概述
|
||||||
|
|
||||||
本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。
|
本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。v8.0版本新增了GUI界面的网络对战支持,提供更直观的可视化网络游戏体验。
|
||||||
|
|
||||||
|
### v8.2 (2025-10-08)
|
||||||
|
- 专业安装包制作 - 支持Inno Setup和NSIS双重安装方案
|
||||||
|
- 安装程序优化 - 完整的文件打包和快捷方式创建
|
||||||
|
- 分发体系完善 - 提供标准化的软件分发解决方案
|
||||||
|
- 用户体验提升 - 一键安装部署,支持完整卸载功能
|
||||||
|
|
||||||
## 编译方法
|
## 编译方法
|
||||||
|
|
||||||
|
### 控制台版本
|
||||||
```bash
|
```bash
|
||||||
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### GUI版本
|
||||||
|
```bash
|
||||||
|
gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
|
||||||
|
```
|
||||||
|
|
||||||
**注意:**
|
**注意:**
|
||||||
- Windows系统需要添加 `-lws2_32` 链接库
|
- Windows系统需要添加 `-lws2_32` 链接库
|
||||||
- Linux系统不需要此链接库参数
|
- GUI版本需要SDL3库支持
|
||||||
|
- Linux系统不需要ws2_32链接库参数
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
@@ -129,23 +143,7 @@ telnet <对方IP地址> <端口号>
|
|||||||
- 游戏结束后及时关闭程序
|
- 游戏结束后及时关闭程序
|
||||||
- 注意保护个人网络信息
|
- 注意保护个人网络信息
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v7.0 (2025-07-20)
|
|
||||||
- 网络配置参数统一管理 - 所有网络相关配置集中到config.h
|
|
||||||
- 消息类型定义优化 - 统一消息协议宏定义
|
|
||||||
- 代码架构重构 - 提升网络模块的可维护性
|
|
||||||
- 配置文件支持 - 网络参数可通过配置文件调整
|
|
||||||
|
|
||||||
### v6.1 (2025-07-10)
|
|
||||||
- 完善网络对战功能
|
|
||||||
- 支持TCP/IP通信
|
|
||||||
- 实现棋盘同步
|
|
||||||
- 连接状态和协议优化
|
|
||||||
- 支持多种操作
|
|
||||||
- 添加延时控制等游戏功能(延时显示、认输、悔棋等)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**开发者:** 刘航宇
|
**开发者:** 刘航宇
|
||||||
**联系邮箱:** 3364451258@qq.com
|
**联系邮箱:** 3364451258@qq.com
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# 五子棋游戏 - 图形化界面说明 (v8.0)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
v8.0版本实现了完整的双版本架构,支持两种界面模式:
|
||||||
|
- **控制台界面**:传统的文本界面,保持原有功能完整性
|
||||||
|
- **图形化界面**:基于SDL3的现代图形界面,提供可视化操作体验
|
||||||
|
|
||||||
|
## v8.0新增功能
|
||||||
|
- ✅ **SDL3图形化界面**:现代化的可视化棋盘
|
||||||
|
- ✅ **鼠标交互支持**:点击落子,直观操作
|
||||||
|
- ✅ **窗口管理优化**:自动居中,响应式设计
|
||||||
|
- ✅ **事件驱动架构**:流畅的用户交互体验
|
||||||
|
- ✅ **安装包支持**:提供专业的安装程序
|
||||||
|
- ✅ **双版本并行**:控制台和GUI版本独立运行
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
### SDL3库配置
|
||||||
|
1. 下载SDL3-3.2.22开发库
|
||||||
|
2. 解压到:`D:\settings\SDL\SDL3-3.2.22\`
|
||||||
|
3. 确保目录结构如下:
|
||||||
|
```
|
||||||
|
D:\settings\SDL\SDL3-3.2.22\
|
||||||
|
├── x86_64-w64-mingw32\
|
||||||
|
│ ├── include\ # 头文件
|
||||||
|
│ ├── lib\ # 库文件
|
||||||
|
│ └── bin\ # DLL文件
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 编译环境
|
||||||
|
- GCC编译器(MinGW-w64)位于:`D:\Program Files\mingw64`
|
||||||
|
- Windows 10/11操作系统
|
||||||
|
- 支持OpenGL的显卡驱动
|
||||||
|
|
||||||
|
## 编译方法
|
||||||
|
|
||||||
|
### 方法一:使用批处理脚本(推荐)
|
||||||
|
```bash
|
||||||
|
# 编译图形化版本
|
||||||
|
.\compile_gui.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法二:手动编译
|
||||||
|
```bash
|
||||||
|
# 控制台版本
|
||||||
|
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
||||||
|
|
||||||
|
# 图形化版本
|
||||||
|
gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
|
||||||
|
# 复制SDL3.dll到当前目录
|
||||||
|
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行方法
|
||||||
|
|
||||||
|
### 控制台版本
|
||||||
|
```bash
|
||||||
|
.\gobang.exe
|
||||||
|
```
|
||||||
|
在主菜单选择模式1-7进行游戏
|
||||||
|
|
||||||
|
### 图形化版本
|
||||||
|
```bash
|
||||||
|
.\gobang_gui.exe
|
||||||
|
```
|
||||||
|
在主菜单选择模式8启动图形化界面
|
||||||
|
|
||||||
|
## 图形化界面功能
|
||||||
|
|
||||||
|
- **窗口大小**:800x600像素
|
||||||
|
- **棋盘显示**:15x15标准五子棋棋盘,木质纹理背景
|
||||||
|
- **鼠标操作**:点击棋盘交叉点进行落子
|
||||||
|
- **键盘操作**:
|
||||||
|
- `ESC`:退出图形化界面返回主菜单
|
||||||
|
- `R`:重新开始游戏
|
||||||
|
- `U`:撤销上一步(如果支持)
|
||||||
|
- **游戏状态**:
|
||||||
|
- 实时显示当前玩家(黑子/白子)
|
||||||
|
- 显示游戏进度和状态
|
||||||
|
- 胜负结果提示
|
||||||
|
- **视觉效果**:
|
||||||
|
- 黑白棋子带阴影效果
|
||||||
|
- 当前玩家指示器
|
||||||
|
- 平滑的图形渲染
|
||||||
|
- **胜负判定**:自动检测五子连珠并显示获胜者
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 编译错误
|
||||||
|
1. **找不到SDL3头文件**
|
||||||
|
- 检查SDL3库路径是否正确
|
||||||
|
- 确保include目录存在
|
||||||
|
|
||||||
|
2. **链接错误**
|
||||||
|
- 检查lib目录是否存在
|
||||||
|
- 确保SDL3.lib文件存在
|
||||||
|
|
||||||
|
3. **运行时错误**
|
||||||
|
- 确保SDL3.dll在系统PATH中或与exe文件同目录
|
||||||
|
|
||||||
|
### 图形化界面启动失败
|
||||||
|
- 检查SDL3库是否正确安装在指定路径
|
||||||
|
- 确保SDL3.dll文件在exe同目录下
|
||||||
|
- 检查显卡驱动是否支持OpenGL
|
||||||
|
- 尝试以管理员权限运行
|
||||||
|
- 确认Windows版本兼容性
|
||||||
|
|
||||||
|
### 运行时问题
|
||||||
|
- **窗口无法显示**:检查显示器分辨率设置
|
||||||
|
- **鼠标点击无响应**:确认点击在棋盘交叉点附近
|
||||||
|
- **游戏卡顿**:关闭其他占用GPU的程序
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
### 快速开始
|
||||||
|
1. 运行 `compile_gui.bat` 编译图形化版本
|
||||||
|
2. 运行 `gobang_gui.exe` 启动程序
|
||||||
|
3. 在主菜单选择 "8. 图形化界面"
|
||||||
|
4. 使用鼠标点击棋盘进行游戏
|
||||||
|
5. 按ESC键退出图形化界面
|
||||||
|
|
||||||
|
### 游戏规则
|
||||||
|
- 黑子先行,轮流落子
|
||||||
|
- 率先形成五子连珠者获胜
|
||||||
|
- 支持横、竖、斜四个方向的连珠判定
|
||||||
|
|
||||||
|
## 开发说明
|
||||||
|
|
||||||
|
### 文件结构
|
||||||
|
- `gui.h` - 图形化界面头文件
|
||||||
|
- `gui.c` - 图形化界面实现
|
||||||
|
- `main.c` - 主程序(已添加图形化模式)
|
||||||
|
- `compile_gui.bat` - 图形化版本编译脚本
|
||||||
|
|
||||||
|
### 扩展功能
|
||||||
|
图形化界面支持进一步扩展:
|
||||||
|
- 音效支持
|
||||||
|
- 动画效果
|
||||||
|
- 主题切换
|
||||||
|
- 网络对战界面
|
||||||
|
- AI难度可视化调节
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**注意**:首次使用图形化界面前,请确保SDL3库已正确配置。
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# 五子棋游戏 Makefile
|
||||||
|
# 支持编译控制台版本和GUI版本
|
||||||
|
|
||||||
|
# 编译器设置
|
||||||
|
CC = gcc
|
||||||
|
CFLAGS = -Wall -Wextra -std=c17 -O2 -Iinclude
|
||||||
|
LDFLAGS = -lws2_32
|
||||||
|
|
||||||
|
# SDL3路径设置
|
||||||
|
SDL3_PATH = D:/settings/SDL/SDL3-3.2.22/x86_64-w64-mingw32
|
||||||
|
SDL3_INCLUDE = -I$(SDL3_PATH)/include
|
||||||
|
SDL3_LIBS = -L$(SDL3_PATH)/lib -lSDL3 -lmingw32
|
||||||
|
|
||||||
|
# 源文件
|
||||||
|
COMMON_SOURCES = src/main.c src/gobang.c src/ai.c src/config.c src/game_mode.c src/globals.c \
|
||||||
|
src/init_board.c src/network.c src/record.c src/ui.c src/gui.c
|
||||||
|
|
||||||
|
GUI_SOURCES = $(COMMON_SOURCES)
|
||||||
|
CONSOLE_SOURCES = $(COMMON_SOURCES)
|
||||||
|
|
||||||
|
# 目标文件
|
||||||
|
COMMON_OBJECTS = $(patsubst src/%.c,src/%.o,$(filter %.c,$(COMMON_SOURCES)))
|
||||||
|
|
||||||
|
# 可执行文件
|
||||||
|
CONSOLE_TARGET = gobang_console.exe
|
||||||
|
GUI_TARGET = gobang_gui.exe
|
||||||
|
|
||||||
|
# 默认目标
|
||||||
|
all: $(CONSOLE_TARGET) $(GUI_TARGET)
|
||||||
|
|
||||||
|
# 控制台版本
|
||||||
|
$(CONSOLE_TARGET): $(COMMON_OBJECTS)
|
||||||
|
$(CC) $(CFLAGS) $(SDL3_INCLUDE) -o $@ $^ $(SDL3_LIBS) $(LDFLAGS)
|
||||||
|
|
||||||
|
# GUI版本
|
||||||
|
$(GUI_TARGET): $(COMMON_OBJECTS)
|
||||||
|
$(CC) $(CFLAGS) $(SDL3_INCLUDE) -o $@ $^ $(SDL3_LIBS) $(LDFLAGS)
|
||||||
|
|
||||||
|
# 通用目标文件编译规则(包含SDL3头文件路径,因为多个文件包含gui.h)
|
||||||
|
src/%.o: src/%.c
|
||||||
|
$(CC) $(CFLAGS) $(SDL3_INCLUDE) -c -o $@ $<
|
||||||
|
|
||||||
|
# 清理规则
|
||||||
|
clean:
|
||||||
|
del /Q src\*.o *.exe 2>nul || true
|
||||||
|
|
||||||
|
# 只编译控制台版本
|
||||||
|
console: $(CONSOLE_TARGET)
|
||||||
|
|
||||||
|
# 只编译GUI版本
|
||||||
|
gui: $(GUI_TARGET)
|
||||||
|
|
||||||
|
# 安装规则(可选)
|
||||||
|
install: all
|
||||||
|
@echo Installing executables...
|
||||||
|
copy $(CONSOLE_TARGET) C:\Program Files\Gobang\ 2>nul || echo Install directory not found
|
||||||
|
copy $(GUI_TARGET) C:\Program Files\Gobang\ 2>nul || echo Install directory not found
|
||||||
|
|
||||||
|
# 运行控制台版本
|
||||||
|
run-console: $(CONSOLE_TARGET)
|
||||||
|
.\$(CONSOLE_TARGET)
|
||||||
|
|
||||||
|
# 运行GUI版本
|
||||||
|
run-gui: $(GUI_TARGET)
|
||||||
|
.\$(GUI_TARGET)
|
||||||
|
|
||||||
|
# 帮助信息
|
||||||
|
help:
|
||||||
|
@echo Available targets:
|
||||||
|
@echo all - Build both console and GUI versions
|
||||||
|
@echo console - Build console version only
|
||||||
|
@echo gui - Build GUI version only
|
||||||
|
@echo clean - Remove all object files and executables
|
||||||
|
@echo run-console - Build and run console version
|
||||||
|
@echo run-gui - Build and run GUI version
|
||||||
|
@echo install - Install executables to system directory
|
||||||
|
@echo help - Show this help message
|
||||||
|
|
||||||
|
# 声明伪目标
|
||||||
|
.PHONY: all clean console gui install run-console run-gui help
|
||||||
@@ -2,28 +2,26 @@
|
|||||||
|
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
> 🎯 **最新版本 v7.0** - 代码架构重构更新,实现结构体定义集中化、配置参数统一管理、代码模块化优化等核心改进
|
> 🎯 **最新版本 v8.2** - 编译脚本优化更新,完善批处理脚本功能、增强用户编译体验、优化开发工作流程
|
||||||
|
|
||||||
## 📋 大版本更新
|
## 📋 大版本更新
|
||||||
|
|
||||||
### v7.0 (2025-07-20) - 代码架构重构更新
|
### v8.2 (2025-01-20) - 编译脚本优化更新
|
||||||
- 🏗️ **结构体定义集中化** - 所有数据结构统一管理在type.h中
|
- 🔧 **交互式编译脚本** - compile_gui.bat支持选择编译控制台或GUI版本
|
||||||
- ⚙️ **配置参数统一管理** - 所有配置宏定义集中在config.h中
|
- 📋 **用户友好界面** - 提供清晰的编译选项菜单和操作指引
|
||||||
- 🔧 **代码模块化优化** - 消除重复定义,提高代码可维护性
|
- ⚡ **编译流程优化** - 统一编译参数,解决SDL3依赖问题
|
||||||
- 📋 **菜单选项优化** - 退出选项调整为"0. 退出游戏"
|
- 🌐 **多语言支持** - 英文界面避免编码问题,确保兼容性
|
||||||
- 🎯 **类型系统完善** - 独立的type.h文件管理所有数据类型
|
- 📦 **安装包制作** - 提供Inno Setup和NSIS两种安装包方案
|
||||||
- 🌐 **网络配置重构** - 网络相关宏定义统一到config.h
|
- ✅ **错误处理完善** - 添加无效输入处理和详细错误提示
|
||||||
- 📊 **全局变量管理** - 优化全局变量声明和定义结构
|
- 🎯 **开发体验提升** - 简化编译流程,提高开发效率
|
||||||
- 🔄 **头文件依赖优化** - 改进模块间依赖关系和包含结构
|
|
||||||
|
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
- [C语言五子棋人机对战AI](#c语言五子棋人机对战ai)
|
- [C语言五子棋人机对战AI](#c语言五子棋人机对战ai)
|
||||||
- [📋 大版本更新](#-大版本更新)
|
- [📋 大版本更新](#-大版本更新)
|
||||||
- [v7.0 (2025-07-20) - 代码架构重构更新](#v70-2025-07-20---代码架构重构更新)
|
- [v8.2 (2025-01-20) - 编译脚本优化更新](#v82-2025-01-20---编译脚本优化更新)
|
||||||
- [目录](#目录)
|
- [目录](#目录)
|
||||||
- [项目简介](#项目简介)
|
- [项目简介](#项目简介)
|
||||||
- [功能特性](#功能特性)
|
- [功能特性](#功能特性)
|
||||||
@@ -34,7 +32,11 @@
|
|||||||
- [🔧 技术特性](#-技术特性)
|
- [🔧 技术特性](#-技术特性)
|
||||||
- [快速开始](#快速开始)
|
- [快速开始](#快速开始)
|
||||||
- [编译项目](#编译项目)
|
- [编译项目](#编译项目)
|
||||||
|
- [控制台版本编译](#控制台版本编译)
|
||||||
|
- [GUI版本编译(需要SDL3)](#gui版本编译需要sdl3)
|
||||||
- [运行游戏](#运行游戏)
|
- [运行游戏](#运行游戏)
|
||||||
|
- [控制台版本](#控制台版本)
|
||||||
|
- [GUI版本](#gui版本)
|
||||||
- [游戏玩法](#游戏玩法)
|
- [游戏玩法](#游戏玩法)
|
||||||
- [🚀 快速开始](#-快速开始)
|
- [🚀 快速开始](#-快速开始)
|
||||||
- [🎯 对局操作](#-对局操作)
|
- [🎯 对局操作](#-对局操作)
|
||||||
@@ -64,7 +66,7 @@
|
|||||||
- [🔧 技术优化](#-技术优化)
|
- [🔧 技术优化](#-技术优化)
|
||||||
|
|
||||||
## 项目简介
|
## 项目简介
|
||||||
这是一个使用C语言实现的现代化五子棋对战系统,支持人机对战、双人对战和网络对战三种模式。系统基于 Alpha-Beta 剪枝优化的 Minimax 算法,具备完整的配置管理、复盘分析、智能评分和网络通信功能。
|
这是一个使用C语言实现的现代化五子棋对战系统,支持人机对战、双人对战和网络对战三种模式。系统基于 Alpha-Beta 剪枝优化的 Minimax 算法,具备完整的配置管理、复盘分析、智能评分和网络通信功能。v8.0版本新增SDL3图形化界面,提供现代化的可视化游戏体验,同时保持控制台版本的完整功能。
|
||||||
|
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
@@ -72,6 +74,8 @@
|
|||||||
- **人机对战模式** - 与智能AI进行对弈
|
- **人机对战模式** - 与智能AI进行对弈
|
||||||
- **双人对战模式** - 支持本地双人游戏
|
- **双人对战模式** - 支持本地双人游戏
|
||||||
- **复盘模式** - 回顾和分析历史对局
|
- **复盘模式** - 回顾和分析历史对局
|
||||||
|
- **GUI图形化模式** - 现代化图形界面游戏体验
|
||||||
|
- **控制台模式** - 传统终端界面游戏模式
|
||||||
|
|
||||||
### ⚙️ 游戏设置
|
### ⚙️ 游戏设置
|
||||||
- **自定义棋盘尺寸** - 支持5x5至25x25可调节棋盘
|
- **自定义棋盘尺寸** - 支持5x5至25x25可调节棋盘
|
||||||
@@ -88,24 +92,33 @@
|
|||||||
- **MVP评选系统** - 自动评选对局最佳表现者
|
- **MVP评选系统** - 自动评选对局最佳表现者
|
||||||
|
|
||||||
### 💻 用户体验
|
### 💻 用户体验
|
||||||
- **现代化UI界面** - 清晰直观的终端用户界面
|
- **双界面支持** - 同时提供图形化界面和终端界面
|
||||||
|
- **SDL3图形化界面** - 现代化的可视化游戏体验
|
||||||
|
- **鼠标操作支持** - 直观的点击落子操作
|
||||||
- **完备输入验证** - 确保所有用户输入的有效性和安全性
|
- **完备输入验证** - 确保所有用户输入的有效性和安全性
|
||||||
- **智能错误提示** - 详细的错误信息和操作指导
|
- **智能错误提示** - 详细的错误信息和操作指导
|
||||||
|
- **响应式设计** - 自适应窗口大小和分辨率
|
||||||
- **跨平台兼容** - 支持Windows系统,预留跨平台扩展
|
- **跨平台兼容** - 支持Windows系统,预留跨平台扩展
|
||||||
|
|
||||||
### 🔧 技术特性
|
### 🔧 技术特性
|
||||||
- **模块化架构** - 清晰的代码结构,便于维护和扩展
|
- **模块化架构** - 清晰的代码结构,便于维护和扩展
|
||||||
|
- **SDL3图形库集成** - 现代化的跨平台图形渲染支持
|
||||||
|
- **双版本架构** - 控制台版本和GUI版本并行开发
|
||||||
- **结构体定义集中化** - 所有数据结构统一在type.h中管理
|
- **结构体定义集中化** - 所有数据结构统一在type.h中管理
|
||||||
- **配置参数统一管理** - 所有配置宏定义集中在config.h中
|
- **配置参数统一管理** - 所有配置宏定义集中在config.h中
|
||||||
- **全局变量统一管理** - 所有全局变量集中在globals模块中管理
|
- **全局变量统一管理** - 所有全局变量集中在globals模块中管理
|
||||||
- **内存优化管理** - 高效的内存使用和资源管理
|
- **内存优化管理** - 高效的内存使用和资源管理
|
||||||
- **配置文件支持** - INI格式配置文件自动加载保存
|
- **配置文件支持** - INI格式配置文件自动加载保存
|
||||||
- **UTF-8编码支持** - 完美支持中文显示
|
- **UTF-8编码支持** - 完美支持中文显示
|
||||||
|
- **事件驱动架构** - 高效的图形界面事件处理机制
|
||||||
|
- **安装包支持** - 提供专业的软件安装和分发方案
|
||||||
- **网络对战功能** - 完整的在线多人对战系统
|
- **网络对战功能** - 完整的在线多人对战系统
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
|
|
||||||
### 编译项目
|
### 编译项目
|
||||||
|
|
||||||
|
#### 控制台版本编译
|
||||||
```bash
|
```bash
|
||||||
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
gcc -std=c17 -o gobang.exe *.c -lws2_32
|
||||||
```
|
```
|
||||||
@@ -115,13 +128,34 @@ gcc -std=c17 -o gobang.exe *.c -lws2_32
|
|||||||
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
|
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` 链接库以支持网络功能
|
#### GUI版本编译(需要SDL3)
|
||||||
|
```bash
|
||||||
|
gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
|
||||||
|
```
|
||||||
|
|
||||||
|
或者使用批处理文件:
|
||||||
|
```bash
|
||||||
|
.\compile_gui.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:**
|
||||||
|
- Windows系统需要添加 `-lws2_32` 链接库以支持网络功能
|
||||||
|
- GUI版本需要SDL3库支持,请确保SDL3路径正确
|
||||||
|
- 编译GUI版本后需要将SDL3.dll复制到可执行文件目录
|
||||||
|
|
||||||
### 运行游戏
|
### 运行游戏
|
||||||
|
|
||||||
|
#### 控制台版本
|
||||||
```bash
|
```bash
|
||||||
.\gobang.exe
|
.\gobang.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### GUI版本
|
||||||
|
```bash
|
||||||
|
.\gobang_gui.exe
|
||||||
|
```
|
||||||
|
|
||||||
## 游戏玩法
|
## 游戏玩法
|
||||||
|
|
||||||
### 🚀 快速开始
|
### 🚀 快速开始
|
||||||
@@ -225,6 +259,7 @@ chcp 65001
|
|||||||
- **`gobang.c/h`** - 核心游戏逻辑,包括棋盘操作、胜负判断
|
- **`gobang.c/h`** - 核心游戏逻辑,包括棋盘操作、胜负判断
|
||||||
- **`game_mode.c/h`** - 游戏模式实现 (人机对战、双人对战、复盘模式)
|
- **`game_mode.c/h`** - 游戏模式实现 (人机对战、双人对战、复盘模式)
|
||||||
- **`ai.c/h`** - AI算法实现 (Minimax + Alpha-Beta剪枝)
|
- **`ai.c/h`** - AI算法实现 (Minimax + Alpha-Beta剪枝)
|
||||||
|
- **`gui.c/h`** - SDL3图形化界面模块 (窗口管理、事件处理、图形渲染)
|
||||||
|
|
||||||
### 🎮 功能模块
|
### 🎮 功能模块
|
||||||
- **`ui.c/h`** - 用户界面模块,负责所有显示和交互
|
- **`ui.c/h`** - 用户界面模块,负责所有显示和交互
|
||||||
@@ -238,7 +273,12 @@ chcp 65001
|
|||||||
- **`gobang_config.ini`** - 游戏配置文件 (自动生成和保存)
|
- **`gobang_config.ini`** - 游戏配置文件 (自动生成和保存)
|
||||||
- **`records/`** - 对局记录目录 (CSV格式存储)
|
- **`records/`** - 对局记录目录 (CSV格式存储)
|
||||||
- **`type.h`** - 数据结构和类型定义集中文件
|
- **`type.h`** - 数据结构和类型定义集中文件
|
||||||
|
- **`compile_gui.bat`** - GUI版本编译脚本
|
||||||
|
- **`installer/`** - 安装包制作目录
|
||||||
|
- **`setup.iss`** - Inno Setup安装脚本
|
||||||
|
- **`installer.nsi`** - NSIS安装脚本
|
||||||
- **`MD/README.md`** - 项目说明文档
|
- **`MD/README.md`** - 项目说明文档
|
||||||
|
- **`MD/README_GUI.md`** - GUI版本使用指南
|
||||||
- **`MD/AI_Enhancement_Guide.md`** - AI算法增强指南
|
- **`MD/AI_Enhancement_Guide.md`** - AI算法增强指南
|
||||||
- **`MD/NETWORK_README.md`** - 网络功能使用说明
|
- **`MD/NETWORK_README.md`** - 网络功能使用说明
|
||||||
- **`MD/Architecture_Refactoring_Guide.md`** - 代码架构重构详细指南
|
- **`MD/Architecture_Refactoring_Guide.md`** - 代码架构重构详细指南
|
||||||
@@ -266,6 +306,11 @@ chcp 65001
|
|||||||
## 未来计划
|
## 未来计划
|
||||||
|
|
||||||
### ✅ 已完成功能
|
### ✅ 已完成功能
|
||||||
|
- [x] **SDL3图形化界面** - 完成现代化GUI界面开发,支持可视化棋盘操作
|
||||||
|
- [x] **双版本架构** - 实现控制台版本和GUI版本并行支持
|
||||||
|
- [x] **安装包制作** - 提供Inno Setup和NSIS两种专业安装包方案
|
||||||
|
- [x] **鼠标交互支持** - 实现直观的点击落子和界面操作
|
||||||
|
- [x] **窗口管理优化** - 完善的窗口显示、居中和事件处理机制
|
||||||
- [x] **模块化架构设计** - 完成代码重构,实现清晰的模块分离
|
- [x] **模块化架构设计** - 完成代码重构,实现清晰的模块分离
|
||||||
- [x] **全局变量统一管理** - 所有全局变量集中在globals模块中管理
|
- [x] **全局变量统一管理** - 所有全局变量集中在globals模块中管理
|
||||||
- [x] **宏定义优化** - 消除重复定义,统一管理所有宏定义
|
- [x] **宏定义优化** - 消除重复定义,统一管理所有宏定义
|
||||||
@@ -279,9 +324,11 @@ chcp 65001
|
|||||||
### 🚀 开发路线图
|
### 🚀 开发路线图
|
||||||
|
|
||||||
#### 📱 用户体验提升
|
#### 📱 用户体验提升
|
||||||
- [ ] **图形用户界面 (GUI)**:使用 `SDL2` 或 `Qt` 开发现代化图形界面
|
- [x] **图形用户界面 (GUI)**:使用 `SDL3` 开发现代化图形界面
|
||||||
- [ ] **主题系统**:支持多种UI主题和棋盘样式
|
- [ ] **主题系统**:支持多种UI主题和棋盘样式
|
||||||
- [ ] **音效系统**:添加落子音效和背景音乐
|
- [ ] **音效系统**:添加落子音效和背景音乐
|
||||||
|
- [ ] **动画效果**:添加落子动画和界面过渡效果
|
||||||
|
- [ ] **多语言支持**:支持中英文界面切换
|
||||||
|
|
||||||
#### 🌐 网络功能
|
#### 🌐 网络功能
|
||||||
- [ ] **在线对战模式**:实现网络多人对战功能
|
- [ ] **在线对战模式**:实现网络多人对战功能
|
||||||
@@ -296,4 +343,4 @@ chcp 65001
|
|||||||
#### 🔧 技术优化
|
#### 🔧 技术优化
|
||||||
- [ ] **跨平台支持**:完整支持Linux和macOS系统
|
- [ ] **跨平台支持**:完整支持Linux和macOS系统
|
||||||
- [ ] **性能优化**:多线程搜索和内存优化
|
- [ ] **性能优化**:多线程搜索和内存优化
|
||||||
- [ ] **数据库支持**:使用SQLite存储对局历史和统计
|
- [ ] **数据库支持**:使用SQLite存储对局历史和统计
|
||||||
|
|||||||
+49
-14
@@ -2,9 +2,9 @@
|
|||||||
========================================
|
========================================
|
||||||
|
|
||||||
项目名称:五子棋多模式对战系统
|
项目名称:五子棋多模式对战系统
|
||||||
统计时间:2025年7月20日
|
统计时间:2025年10月8日
|
||||||
项目版本:v7.0
|
项目版本:v8.2
|
||||||
开发语言:C语言
|
开发语言:C语言 + SDL3图形库
|
||||||
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
||||||
|
|
||||||
========================================
|
========================================
|
||||||
@@ -21,7 +21,8 @@ GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
|||||||
├── ui.c :204行
|
├── ui.c :204行
|
||||||
├── config.c :331行
|
├── config.c :331行
|
||||||
├── network.c :426行
|
├── network.c :426行
|
||||||
└── globals.c :37行
|
├── globals.c :37行
|
||||||
|
└── gui.c :450行 (v8.0新增)
|
||||||
|
|
||||||
【头文件 (.h)】
|
【头文件 (.h)】
|
||||||
├── gobang.h :101行
|
├── gobang.h :101行
|
||||||
@@ -33,21 +34,28 @@ GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
|||||||
├── config.h :170行
|
├── config.h :170行
|
||||||
├── network.h :186行
|
├── network.h :186行
|
||||||
├── globals.h :41行
|
├── globals.h :41行
|
||||||
└── type.h :93行
|
├── type.h :93行
|
||||||
|
└── gui.h :85行 (v8.0新增)
|
||||||
|
|
||||||
========================================
|
========================================
|
||||||
📈 总计统计
|
📈 总计统计
|
||||||
========================================
|
========================================
|
||||||
|
|
||||||
总代码行数:4,398行
|
总代码行数:4,933行 (v8.0新增535行)
|
||||||
|
|
||||||
文件类型分布:
|
文件类型分布:
|
||||||
• C源文件:3,527行 (80.2%)
|
• C源文件:3,977行 (80.6%)
|
||||||
• 头文件:871行 (19.8%)
|
• 头文件:956行 (19.4%)
|
||||||
|
|
||||||
|
v8.0版本新增:
|
||||||
|
• GUI图形界面模块:535行 (10.8%)
|
||||||
|
• 安装包配置文件:2个
|
||||||
|
• 编译脚本文件:1个
|
||||||
|
|
||||||
模块代码分布:
|
模块代码分布:
|
||||||
• 游戏模式模块:917行 (20.9%)
|
• 游戏模式模块:917行 (18.6%)
|
||||||
• AI智能模块:589行 (13.4%)
|
• AI智能模块:589行 (11.9%)
|
||||||
|
• GUI图形界面模块:535行 (10.8%) (v8.0新增)
|
||||||
• 记录系统模块:531行 (12.1%)
|
• 记录系统模块:531行 (12.1%)
|
||||||
• 网络对战模块:426行 (9.7%)
|
• 网络对战模块:426行 (9.7%)
|
||||||
• 配置管理模块:331行 (7.5%)
|
• 配置管理模块:331行 (7.5%)
|
||||||
@@ -100,6 +108,12 @@ GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
|||||||
✓ 跨平台支持:Windows和Linux双平台兼容
|
✓ 跨平台支持:Windows和Linux双平台兼容
|
||||||
|
|
||||||
【技术亮点】
|
【技术亮点】
|
||||||
|
• SDL3图形化界面实现(v8.0新增)
|
||||||
|
• 双版本架构设计(控制台+GUI)(v8.0新增)
|
||||||
|
• 鼠标交互和事件驱动架构(v8.0新增)
|
||||||
|
• 专业安装包制作支持(v8.2新增)
|
||||||
|
• Inno Setup和NSIS双重打包方案(v8.2新增)
|
||||||
|
• 完整的软件分发体系(v8.2新增)
|
||||||
• 完整的网络对战功能实现
|
• 完整的网络对战功能实现
|
||||||
• 智能AI算法与评估系统
|
• 智能AI算法与评估系统
|
||||||
• 灵活的配置管理系统
|
• 灵活的配置管理系统
|
||||||
@@ -108,9 +122,9 @@ GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
|||||||
• 实时计时器系统
|
• 实时计时器系统
|
||||||
• 全局变量统一管理
|
• 全局变量统一管理
|
||||||
• 跨平台网络通信支持
|
• 跨平台网络通信支持
|
||||||
• 代码架构模块化重构(v7.0新增)
|
• 代码架构模块化重构
|
||||||
• 配置参数集中化管理(v7.0新增)
|
• 配置参数集中化管理
|
||||||
• 类型定义标准化(v7.0新增)
|
• 类型定义标准化
|
||||||
|
|
||||||
【总体评价】
|
【总体评价】
|
||||||
这是一个非常优秀的C语言项目,代码量适中但功能完整,
|
这是一个非常优秀的C语言项目,代码量适中但功能完整,
|
||||||
@@ -120,12 +134,24 @@ GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
|||||||
包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
|
包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
|
||||||
是C语言项目开发的优秀范例。
|
是C语言项目开发的优秀范例。
|
||||||
|
|
||||||
|
v8.2版本进一步完善了软件分发体系,
|
||||||
|
通过Inno Setup和NSIS双重安装包方案,
|
||||||
|
实现了专业级的软件打包和部署功能,
|
||||||
|
为用户提供了便捷的一键安装体验,
|
||||||
|
标志着项目从开发阶段向产品化阶段的重要转变。
|
||||||
|
|
||||||
|
v8.0版本的图形化界面是项目发展的重大突破,
|
||||||
|
通过SDL3图形库实现了现代化的可视化界面,
|
||||||
|
支持鼠标交互操作,大幅提升了用户体验。
|
||||||
|
双版本架构设计既保持了控制台版本的轻量特性,
|
||||||
|
又提供了GUI版本的现代化体验,满足不同用户需求。
|
||||||
|
|
||||||
网络对战功能的加入使得项目具备了现代化游戏的特征,
|
网络对战功能的加入使得项目具备了现代化游戏的特征,
|
||||||
支持实时在线对战,为用户提供了更丰富的游戏体验。
|
支持实时在线对战,为用户提供了更丰富的游戏体验。
|
||||||
|
|
||||||
v7.0版本的代码架构重构是项目发展的重要里程碑,
|
v7.0版本的代码架构重构是项目发展的重要里程碑,
|
||||||
通过配置统一管理、全局变量规范化、类型定义标准化等措施,
|
通过配置统一管理、全局变量规范化、类型定义标准化等措施,
|
||||||
大幅提升了代码的可维护性和扩展性,为后续功能开发
|
大幅提升了代码的可维护性和扩展性,为v8.0的GUI功能
|
||||||
奠定了坚实的架构基础。
|
奠定了坚实的架构基础。
|
||||||
|
|
||||||
========================================
|
========================================
|
||||||
@@ -136,6 +162,7 @@ v7.0版本的代码架构重构是项目发展的重要里程碑,
|
|||||||
• main.c/gobang.c - 主程序和核心游戏逻辑
|
• main.c/gobang.c - 主程序和核心游戏逻辑
|
||||||
• game_mode.c/h - 游戏模式管理(人机/双人/网络)
|
• game_mode.c/h - 游戏模式管理(人机/双人/网络)
|
||||||
• ai.c/h - AI智能算法实现
|
• ai.c/h - AI智能算法实现
|
||||||
|
• gui.c/h - SDL3图形化界面模块(v8.0新增)
|
||||||
• network.c/h - 网络对战功能
|
• network.c/h - 网络对战功能
|
||||||
• record.c/h - 游戏记录与复盘
|
• record.c/h - 游戏记录与复盘
|
||||||
• ui.c/h - 用户界面管理
|
• ui.c/h - 用户界面管理
|
||||||
@@ -146,9 +173,17 @@ v7.0版本的代码架构重构是项目发展的重要里程碑,
|
|||||||
|
|
||||||
配置文件:
|
配置文件:
|
||||||
• gobang_config.ini - 游戏配置文件
|
• gobang_config.ini - 游戏配置文件
|
||||||
|
• compile_gui.bat - GUI版本编译脚本(v8.0新增)
|
||||||
|
|
||||||
|
安装包目录:
|
||||||
|
• installer/ - 安装包制作目录(v8.0新增)
|
||||||
|
• setup.iss - Inno Setup安装脚本
|
||||||
|
• installer.nsi - NSIS安装脚本
|
||||||
|
|
||||||
文档目录:
|
文档目录:
|
||||||
• MD/ - 项目文档目录
|
• MD/ - 项目文档目录
|
||||||
|
• README_GUI.md - GUI版本使用指南(v8.0新增)
|
||||||
|
• TXT/ - 文本文档目录
|
||||||
• records/ - 游戏记录存储目录
|
• records/ - 游戏记录存储目录
|
||||||
|
|
||||||
开发环境:
|
开发环境:
|
||||||
|
|||||||
+20
-4
@@ -1,12 +1,28 @@
|
|||||||
/**
|
/**
|
||||||
* @file 五子棋对战系统
|
* @file 五子棋对战系统
|
||||||
* @brief C语言五子棋多模式对战系统
|
* @brief C语言五子棋多模式对战系统
|
||||||
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统
|
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统,v8.0新增SDL3图形化界面
|
||||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||||
* @date 2025-07-20
|
* @date 2025-10-8
|
||||||
* @version 7.0
|
* @version 8.2
|
||||||
* @note
|
* @note
|
||||||
* 1. v7.0架构重构:
|
* 1. v8.2专业安装包:
|
||||||
|
* - 📦 双重打包方案:支持Inno Setup和NSIS两种安装包制作
|
||||||
|
* - 🚀 一键安装部署:完整的软件分发解决方案
|
||||||
|
* - 🔧 安装程序优化:自动创建快捷方式和注册表项
|
||||||
|
* - 🗂️ 完整文件打包:包含所有源码、文档和依赖文件
|
||||||
|
* - 🔄 完整卸载功能:支持干净的软件卸载和清理
|
||||||
|
* - 💼 产品化部署:从开发工具向商业软件的转变
|
||||||
|
*
|
||||||
|
* 2. v8.0图形化界面:
|
||||||
|
* - 🎨 SDL3图形化界面:实现现代化可视化棋盘界面
|
||||||
|
* - 🖱️ 鼠标交互支持:直观的点击落子操作
|
||||||
|
* - 🏗️ 双版本架构:控制台版本和GUI版本并行支持
|
||||||
|
* - 🪟 窗口管理优化:自动居中、响应式设计
|
||||||
|
* - ⚡ 事件驱动架构:流畅的用户交互体验
|
||||||
|
* - 🔧 编译脚本优化:简化GUI版本编译流程
|
||||||
|
* - 🌐 GUI网络支持:图形化界面支持网络对战
|
||||||
|
* 2. v7.0架构重构:
|
||||||
* - 🏗️ 代码架构全面重构,实现模块化设计
|
* - 🏗️ 代码架构全面重构,实现模块化设计
|
||||||
* - 📋 配置参数统一管理,所有配置集中到config.h
|
* - 📋 配置参数统一管理,所有配置集中到config.h
|
||||||
* - 🔧 全局变量规范化,统一在globals模块管理
|
* - 🔧 全局变量规范化,统一在globals模块管理
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
项目要求文档 - 五子棋游戏 (v8.2)
|
||||||
|
|
||||||
|
1. 项目概述
|
||||||
|
- 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。
|
||||||
|
- v8.0版本实现双版本架构:支持命令行界面和SDL3图形化界面。
|
||||||
|
- v8.2版本(当前版本)完善了专业安装包制作体系,提供企业级的软件分发解决方案。
|
||||||
|
- v8.2版本完善了软件分发体系,支持Inno Setup和NSIS双重安装包制作,提供专业级的软件打包和部署解决方案。
|
||||||
|
- 包括游戏配置、记录保存、复盘功能和专业安装包。
|
||||||
|
- 提供现代化的可视化用户体验和传统控制台体验。
|
||||||
|
|
||||||
|
2. 功能需求
|
||||||
|
- **游戏模式**:
|
||||||
|
- 单人模式:玩家 vs AI
|
||||||
|
- 双人模式:本地两人对战
|
||||||
|
- 网络模式:通过TCP/IP进行远程对战
|
||||||
|
- **棋盘和规则**:
|
||||||
|
- 默认15x15棋盘,支持自定义大小(5-25)
|
||||||
|
- 支持禁手规则选项
|
||||||
|
- 先手黑子,五连珠获胜
|
||||||
|
- **AI功能**:
|
||||||
|
- 实现AI算法,支持不同难度级别(1-5级)
|
||||||
|
- AI应能计算最佳落子位置
|
||||||
|
- 支持Alpha-Beta剪枝算法和威胁检测
|
||||||
|
- **配置管理**:
|
||||||
|
- 支持修改棋盘大小、禁手规则、计时器、网络端口等
|
||||||
|
- 配置保存到INI文件
|
||||||
|
- 默认配置和重置功能
|
||||||
|
- 统一的输入验证机制
|
||||||
|
- **游戏记录**:
|
||||||
|
- 自动保存对局记录到CSV文件
|
||||||
|
- 支持复盘功能,查看历史对局
|
||||||
|
- 记录包含时间戳、玩家信息、棋局步骤等
|
||||||
|
- **用户界面**:
|
||||||
|
- **控制台界面**:传统命令行界面显示棋盘
|
||||||
|
- **图形化界面**(v8.0新增):基于SDL3的现代化GUI界面
|
||||||
|
- **鼠标交互**:支持点击落子操作
|
||||||
|
- **窗口管理**:自动居中、响应式设计
|
||||||
|
- **事件驱动**:流畅的用户交互体验
|
||||||
|
- 支持坐标输入落子(数字格式:行 列)
|
||||||
|
- 显示当前玩家、计时、游戏状态等信息
|
||||||
|
- 菜单系统和配置界面
|
||||||
|
- **网络功能**:
|
||||||
|
- 支持服务器/客户端模式
|
||||||
|
- 实时传输棋子位置
|
||||||
|
- 超时和断线处理
|
||||||
|
- 支持端口配置
|
||||||
|
|
||||||
|
3. 技术要求
|
||||||
|
- 使用C语言开发
|
||||||
|
- Windows平台,包含Winsock网络库(-lws2_32链接)
|
||||||
|
- **SDL3图形库**(v8.0新增):用于GUI界面开发
|
||||||
|
- **双版本架构**:控制台版本和GUI版本并行支持
|
||||||
|
- 模块化设计:分离游戏逻辑、AI、配置、网络、UI、GUI等模块
|
||||||
|
- **安装包制作**:支持Inno Setup专业安装程序
|
||||||
|
- 错误处理和统一的输入验证
|
||||||
|
- 支持跨平台编译(Windows/Linux)
|
||||||
|
|
||||||
|
4. 非功能需求
|
||||||
|
- 性能:响应时间<1秒(AI计算除外)
|
||||||
|
- 可维护性:代码模块化,注释清晰
|
||||||
|
- 兼容性:Windows 11,支持GCC编译器
|
||||||
|
- 可扩展性:易于添加新的AI算法和游戏模式
|
||||||
|
|
||||||
|
5. 交付物
|
||||||
|
- 源代码文件(.c/.h文件)
|
||||||
|
- 配置文件(gobang_config.ini)
|
||||||
|
- 记录文件夹(records/)
|
||||||
|
- 文档:README.md、AI增强指南、架构重构指南、网络功能说明、图标指南等
|
||||||
|
- 代码统计报告和项目简介
|
||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
@echo off
|
||||||
|
echo ===== Gobang Game Compile Script =====
|
||||||
|
echo.
|
||||||
|
echo Please select compile version:
|
||||||
|
echo 1. Console version (gobang_console.exe)
|
||||||
|
echo 2. GUI version (gobang_gui.exe)
|
||||||
|
echo 3. Compile all versions
|
||||||
|
echo 0. Exit
|
||||||
|
echo.
|
||||||
|
set /p choice="Please enter your choice (0-3): "
|
||||||
|
|
||||||
|
if "%choice%"=="0" goto :exit
|
||||||
|
if "%choice%"=="1" goto :console
|
||||||
|
if "%choice%"=="2" goto :gui
|
||||||
|
if "%choice%"=="3" goto :all
|
||||||
|
echo Invalid choice!
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:console
|
||||||
|
echo.
|
||||||
|
echo Compiling console version...
|
||||||
|
echo.
|
||||||
|
gcc -std=c17 -o gobang_console.exe src/*.c -Iinclude -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo.
|
||||||
|
echo Compilation successful! Generated: gobang_console.exe
|
||||||
|
echo Run command: .\gobang_console.exe
|
||||||
|
echo.
|
||||||
|
) else (
|
||||||
|
echo.
|
||||||
|
echo Compilation failed! Please check source files and compiler installation
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
goto :end
|
||||||
|
|
||||||
|
:gui
|
||||||
|
echo.
|
||||||
|
echo Compiling GUI version...
|
||||||
|
echo.
|
||||||
|
REM Check if SDL3 library exists
|
||||||
|
if not exist "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include" (
|
||||||
|
echo Error: SDL3 library not found!
|
||||||
|
echo Please ensure SDL3 is extracted to: D:\settings\SDL\SDL3-3.2.22\
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
gcc -std=c17 -o gobang_gui.exe src/*.c -Iinclude -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo.
|
||||||
|
echo Compilation successful! Generated: gobang_gui.exe
|
||||||
|
echo.
|
||||||
|
echo Copying SDL3.dll to current directory...
|
||||||
|
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" . >nul 2>&1
|
||||||
|
echo Run command: .\gobang_gui.exe
|
||||||
|
echo.
|
||||||
|
) else (
|
||||||
|
echo.
|
||||||
|
echo Compilation failed! Please check:
|
||||||
|
echo 1. SDL3 library path is correct
|
||||||
|
echo 2. All source files exist
|
||||||
|
echo 3. gcc compiler is installed
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
goto :end
|
||||||
|
|
||||||
|
:all
|
||||||
|
echo.
|
||||||
|
echo Compiling all versions...
|
||||||
|
echo.
|
||||||
|
echo [1/2] Compiling console version...
|
||||||
|
gcc -std=c17 -o gobang_console.exe src/*.c -Iinclude -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo Console version compilation successful!
|
||||||
|
) else (
|
||||||
|
echo Console version compilation failed!
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
echo [2/2] Compiling GUI version...
|
||||||
|
if not exist "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include" (
|
||||||
|
echo Error: SDL3 library not found! Skipping GUI version compilation
|
||||||
|
goto :end
|
||||||
|
)
|
||||||
|
gcc -std=c17 -o gobang_gui.exe src/*.c -Iinclude -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
if %ERRORLEVEL% EQU 0 (
|
||||||
|
echo GUI version compilation successful!
|
||||||
|
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" . >nul 2>&1
|
||||||
|
) else (
|
||||||
|
echo GUI version compilation failed!
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
echo Compilation complete! Generated files:
|
||||||
|
dir *.exe 2>nul
|
||||||
|
echo.
|
||||||
|
goto :end
|
||||||
|
|
||||||
|
:end
|
||||||
|
pause
|
||||||
|
|
||||||
|
:exit
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file globals.c
|
|
||||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@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] = "平局或未完成"; // 存储胜负信息
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
* 2. 评估指定位置的价值
|
* 2. 评估指定位置的价值
|
||||||
* 3. 评估棋盘价值
|
* 3. 评估棋盘价值
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef AI_H
|
#ifndef AI_H
|
||||||
#define AI_H
|
#define AI_H
|
||||||
|
|
||||||
+39
-23
@@ -32,36 +32,36 @@
|
|||||||
#define INPUT_SURRENDER -4 // 认输
|
#define INPUT_SURRENDER -4 // 认输
|
||||||
|
|
||||||
//---------- 游戏设置默认值 ----------//
|
//---------- 游戏设置默认值 ----------//
|
||||||
#define DEFAULT_USE_FORBIDDEN_MOVES false // 默认不启用禁手规则
|
#define DEFAULT_USE_FORBIDDEN_MOVES false // 默认不启用禁手规则
|
||||||
#define DEFAULT_USE_TIMER 0 // 默认不启用计时器
|
#define DEFAULT_USE_TIMER 0 // 默认不启用计时器
|
||||||
#define DEFAULT_TIME_LIMIT 30 // 默认时间限制为30秒(内部存储)
|
#define DEFAULT_TIME_LIMIT 30 // 默认时间限制为30秒(内部存储)
|
||||||
|
|
||||||
//---------- AI参数 ----------//
|
//---------- AI参数 ----------//
|
||||||
#define DEFAULT_AI_DEPTH 5 // 默认AI搜索深度(从3提升到5)
|
#define DEFAULT_AI_DEPTH 5 // 默认AI搜索深度
|
||||||
#define DEFAULT_DEFENSE_COEFFICIENT 1.5 // 默认防守系数(从1.2提升到1.5)
|
#define DEFAULT_DEFENSE_COEFFICIENT 1.5 // 默认防守系数
|
||||||
|
|
||||||
//---------- 网络参数 ----------//
|
//---------- 网络参数 ----------//
|
||||||
#define DEFAULT_NETWORK_PORT 8888 // 默认网络端口
|
#define DEFAULT_NETWORK_PORT 8888 // 默认网络端口
|
||||||
#define MIN_NETWORK_PORT 1024 // 最小网络端口
|
#define MIN_NETWORK_PORT 1024 // 最小网络端口
|
||||||
#define MAX_NETWORK_PORT 65535 // 最大网络端口
|
#define MAX_NETWORK_PORT 65535 // 最大网络端口
|
||||||
#define NETWORK_TIMEOUT_MS 5000 // 网络超时时间(毫秒)
|
#define NETWORK_TIMEOUT_MS 5000 // 网络超时时间(毫秒)
|
||||||
#define NETWORK_BUFFER_SIZE 1024 // 网络缓冲区大小
|
#define NETWORK_BUFFER_SIZE 1024 // 网络缓冲区大小
|
||||||
|
|
||||||
// 网络配置
|
// 网络配置
|
||||||
#define DEFAULT_PORT 8888 // 默认端口(与DEFAULT_NETWORK_PORT保持一致)
|
#define DEFAULT_PORT 8888 // 默认端口(与DEFAULT_NETWORK_PORT保持一致)
|
||||||
#define BUFFER_SIZE 1024 // 缓冲区大小(与NETWORK_BUFFER_SIZE保持一致)
|
#define BUFFER_SIZE 1024 // 缓冲区大小(与NETWORK_BUFFER_SIZE保持一致)
|
||||||
#define MAX_IP_LENGTH 16 // 最大IP地址长度
|
#define MAX_IP_LENGTH 16 // 最大IP地址长度
|
||||||
|
|
||||||
// 网络消息类型
|
// 网络消息类型
|
||||||
#define MSG_MOVE 1 // 落子消息
|
#define MSG_MOVE 1 // 落子消息
|
||||||
#define MSG_CHAT 2 // 聊天消息
|
#define MSG_CHAT 2 // 聊天消息
|
||||||
#define MSG_SURRENDER 3 // 认输消息
|
#define MSG_SURRENDER 3 // 认输消息
|
||||||
#define MSG_UNDO_REQUEST 4 // 悔棋请求
|
#define MSG_UNDO_REQUEST 4 // 悔棋请求
|
||||||
#define MSG_UNDO_RESPONSE 5 // 悔棋回应
|
#define MSG_UNDO_RESPONSE 5 // 悔棋回应
|
||||||
#define MSG_GAME_START 6 // 游戏开始
|
#define MSG_GAME_START 6 // 游戏开始
|
||||||
#define MSG_GAME_END 7 // 游戏结束
|
#define MSG_GAME_END 7 // 游戏结束
|
||||||
#define MSG_HEARTBEAT 8 // 心跳包
|
#define MSG_HEARTBEAT 8 // 心跳包
|
||||||
#define MSG_DISCONNECT 9 // 断线消息
|
#define MSG_DISCONNECT 9 // 断线消息
|
||||||
|
|
||||||
//---------- 评分参数 ----------//
|
//---------- 评分参数 ----------//
|
||||||
// 棋型评分 - 用于calculate_step_score函数
|
// 棋型评分 - 用于calculate_step_score函数
|
||||||
@@ -116,7 +116,23 @@
|
|||||||
#define TIME_WEIGHT_FACTOR 0.5 // 时间权重因子
|
#define TIME_WEIGHT_FACTOR 0.5 // 时间权重因子
|
||||||
#define WIN_BONUS 2000 // 胜利奖励分数
|
#define WIN_BONUS 2000 // 胜利奖励分数
|
||||||
|
|
||||||
// 文件路径参数
|
//---------- GUI界面参数 ----------//
|
||||||
|
// 窗口和棋盘配置
|
||||||
|
#define WINDOW_WIDTH 800
|
||||||
|
#define WINDOW_HEIGHT 600
|
||||||
|
#define BOARD_OFFSET_X 50
|
||||||
|
#define BOARD_OFFSET_Y 50
|
||||||
|
#define CELL_SIZE 30
|
||||||
|
#define STONE_RADIUS 12
|
||||||
|
|
||||||
|
// 颜色定义
|
||||||
|
#define GUI_COLOR_BACKGROUND {240, 217, 181, 255}
|
||||||
|
#define GUI_COLOR_BOARD_LINE {0, 0, 0, 255}
|
||||||
|
#define GUI_COLOR_BLACK_STONE {0, 0, 0, 255}
|
||||||
|
#define GUI_COLOR_WHITE_STONE {255, 255, 255, 255}
|
||||||
|
#define GUI_COLOR_STONE_BORDER {100, 100, 100, 255}
|
||||||
|
|
||||||
|
//---------- 文件路径参数 ----------//
|
||||||
#define RECORDS_DIR "records" // 记录文件目录
|
#define RECORDS_DIR "records" // 记录文件目录
|
||||||
#define CONFIG_FILE "gobang_config.ini" // 配置文件路径
|
#define CONFIG_FILE "gobang_config.ini" // 配置文件路径
|
||||||
#define MAX_PATH_LENGTH 256 // 最大路径长度
|
#define MAX_PATH_LENGTH 256 // 最大路径长度
|
||||||
@@ -86,8 +86,23 @@ bool handle_network_player_turn(int current_player, bool is_local_turn);
|
|||||||
/**
|
/**
|
||||||
* @brief 网络游戏主循环
|
* @brief 网络游戏主循环
|
||||||
* @return true 游戏正常结束
|
* @return true 游戏正常结束
|
||||||
* @return false 网络错误或异常退出
|
* @return false 网络错误
|
||||||
*/
|
*/
|
||||||
bool network_game_loop();
|
bool network_game_loop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示游戏规则
|
||||||
|
*/
|
||||||
|
void show_game_rules();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示关于游戏信息
|
||||||
|
*/
|
||||||
|
void show_about_game();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启动图形化界面
|
||||||
|
*/
|
||||||
|
void run_gui_mode();
|
||||||
|
|
||||||
#endif // GAME_MODE_H
|
#endif // GAME_MODE_H
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
// ==================== 游戏核心变量 ====================
|
// ==================== 游戏核心变量 ====================
|
||||||
extern int BOARD_SIZE; // 当前实际使用的棋盘尺寸
|
extern int BOARD_SIZE; // 当前实际使用的棋盘尺寸
|
||||||
@@ -32,6 +33,14 @@ extern double defense_coefficient; // 防守系数
|
|||||||
// ==================== 网络相关变量 ====================
|
// ==================== 网络相关变量 ====================
|
||||||
extern NetworkGameState network_state; // 网络游戏状态
|
extern NetworkGameState network_state; // 网络游戏状态
|
||||||
|
|
||||||
|
// ==================== GUI相关变量 ====================
|
||||||
|
extern SDL_Window* window; // SDL窗口指针
|
||||||
|
extern SDL_Renderer* renderer; // SDL渲染器指针
|
||||||
|
extern int gui_running; // GUI运行状态标志
|
||||||
|
extern int current_player_gui; // GUI当前玩家
|
||||||
|
extern int game_over; // 游戏结束标志
|
||||||
|
extern char status_message[256]; // 状态消息
|
||||||
|
|
||||||
// ==================== 记录相关变量 ====================
|
// ==================== 记录相关变量 ====================
|
||||||
extern int player1_final_score; // 玩家1最终得分
|
extern int player1_final_score; // 玩家1最终得分
|
||||||
extern int player2_final_score; // 玩家2最终得分
|
extern int player2_final_score; // 玩家2最终得分
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
* @note 本文件定义了五子棋游戏的主要数据结构、函数和全局变量。
|
* @note 本文件定义了五子棋游戏的主要数据结构、函数和全局变量。
|
||||||
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
|
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef GO_BANG_H
|
#ifndef GO_BANG_H
|
||||||
#define GO_BANG_H
|
#define GO_BANG_H
|
||||||
|
|
||||||
+154
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* @file gui.h
|
||||||
|
* @brief 图形化用户界面头文件
|
||||||
|
* @note 使用SDL3库实现五子棋的图形化界面
|
||||||
|
* @author 刘航宇
|
||||||
|
* @date 2025-01-15
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GUI_H
|
||||||
|
#define GUI_H
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include "gobang.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "globals.h"
|
||||||
|
|
||||||
|
// GUI函数声明
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化GUI
|
||||||
|
* @details 初始化SDL3图形库和游戏界面组件:
|
||||||
|
* - 初始化SDL视频子系统
|
||||||
|
* - 创建游戏窗口(可调整大小)
|
||||||
|
* - 创建SDL渲染器
|
||||||
|
* - 初始化游戏状态和棋盘数据
|
||||||
|
* @return 成功返回0,失败返回-1
|
||||||
|
* @note 窗口标题为"五子棋游戏 - SDL3版本"
|
||||||
|
* 窗口尺寸由WINDOW_WIDTH和WINDOW_HEIGHT定义
|
||||||
|
* 失败时会自动清理已创建的资源
|
||||||
|
*/
|
||||||
|
int init_gui();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理GUI资源
|
||||||
|
* @details 按顺序释放所有SDL相关资源:
|
||||||
|
* - 销毁SDL渲染器
|
||||||
|
* - 销毁SDL窗口
|
||||||
|
* - 退出SDL子系统
|
||||||
|
* @note 函数会检查资源是否存在再进行释放
|
||||||
|
* 释放后将指针设置为NULL防止重复释放
|
||||||
|
* 程序退出时必须调用此函数避免内存泄漏
|
||||||
|
*/
|
||||||
|
void cleanup_gui();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染游戏画面
|
||||||
|
* @details 完整的游戏画面渲染流程:
|
||||||
|
* - 清空屏幕并设置背景色
|
||||||
|
* - 绘制棋盘网格和标记点
|
||||||
|
* - 绘制所有棋子
|
||||||
|
* - 绘制UI界面元素
|
||||||
|
* - 将渲染结果显示到屏幕
|
||||||
|
* @note 使用双缓冲技术,通过SDL_RenderPresent显示最终结果
|
||||||
|
* 背景色由GUI_COLOR_BACKGROUND定义
|
||||||
|
* 每帧都会完全重绘整个画面
|
||||||
|
*/
|
||||||
|
void render_game();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理事件
|
||||||
|
* @details 处理所有SDL事件并执行相应操作:
|
||||||
|
* - SDL_EVENT_QUIT:用户关闭窗口
|
||||||
|
* - SDL_EVENT_KEY_DOWN:键盘按键(ESC退出)
|
||||||
|
* - SDL_EVENT_MOUSE_BUTTON_DOWN:鼠标点击落子
|
||||||
|
* @return 继续运行返回1,退出返回0
|
||||||
|
* @note 鼠标左键点击会转换为棋盘坐标并尝试落子
|
||||||
|
* 落子后会检查胜负并切换玩家
|
||||||
|
* 游戏结束后不再响应落子操作
|
||||||
|
*/
|
||||||
|
int handle_events();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制棋盘
|
||||||
|
* @details 绘制15x15的五子棋棋盘,包括:
|
||||||
|
* - 横竖交叉的网格线
|
||||||
|
* - 天元点(棋盘中心的标记点)
|
||||||
|
* - 四个星位(棋盘上的定位点)
|
||||||
|
* @note 使用SDL3渲染器绘制线条和填充矩形
|
||||||
|
* 棋盘线条颜色由GUI_COLOR_BOARD_LINE定义
|
||||||
|
* 天元点和星位用黑色小矩形标记
|
||||||
|
*/
|
||||||
|
void draw_board();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制棋子
|
||||||
|
* @details 遍历整个棋盘数组,绘制所有已落下的棋子:
|
||||||
|
* - 黑子:使用GUI_COLOR_BLACK_STONE颜色
|
||||||
|
* - 白子:使用GUI_COLOR_WHITE_STONE颜色
|
||||||
|
* - 每个棋子都有边框:使用GUI_COLOR_STONE_BORDER颜色
|
||||||
|
* @note 棋子绘制为圆形,半径由STONE_RADIUS定义
|
||||||
|
* 通过draw_circle函数实现圆形绘制
|
||||||
|
* 棋子位置根据棋盘坐标和CELL_SIZE计算屏幕坐标
|
||||||
|
*/
|
||||||
|
void draw_stones();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制UI元素
|
||||||
|
* @details 绘制游戏界面的用户交互元素:
|
||||||
|
* - 状态信息区域背景和边框
|
||||||
|
* - 当前玩家指示器(黑子或白子圆形)
|
||||||
|
* - 游戏状态显示区域
|
||||||
|
* @note 暂时使用简单图形代替文字显示
|
||||||
|
* 需要额外字体库支持文字渲染
|
||||||
|
* 指示器位置在棋盘右侧固定区域
|
||||||
|
*/
|
||||||
|
void draw_ui_elements();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形
|
||||||
|
* @param center_x 圆心X坐标
|
||||||
|
* @param center_y 圆心Y坐标
|
||||||
|
* @param radius 半径
|
||||||
|
* @param color 颜色
|
||||||
|
* @details 使用像素级绘制实现圆形:
|
||||||
|
* - 遍历圆形外接矩形内的所有像素点
|
||||||
|
* - 计算每个像素到圆心的距离
|
||||||
|
* - 距离小于等于半径的像素点进行着色
|
||||||
|
* @note 采用暴力算法,性能较低但实现简单
|
||||||
|
* 适用于绘制棋子等小尺寸圆形
|
||||||
|
* SDL3没有内置圆形绘制函数,需要自实现
|
||||||
|
*/
|
||||||
|
void draw_circle(int center_x, int center_y, int radius, SDL_Color color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 屏幕坐标转棋盘坐标
|
||||||
|
* @param screen_x 屏幕X坐标
|
||||||
|
* @param screen_y 屏幕Y坐标
|
||||||
|
* @param board_x 输出棋盘X坐标
|
||||||
|
* @param board_y 输出棋盘Y坐标
|
||||||
|
* @return 转换成功返回1,失败返回0
|
||||||
|
* @details 坐标转换算法:
|
||||||
|
* - 减去棋盘偏移量得到相对坐标
|
||||||
|
* - 加上半个格子尺寸实现就近取整
|
||||||
|
* - 除以格子尺寸得到棋盘坐标
|
||||||
|
* - 检查坐标是否在有效范围内
|
||||||
|
* @note 使用就近取整算法,点击格子中心附近都会定位到该格子
|
||||||
|
* 坐标范围检查确保不会越界访问棋盘数组
|
||||||
|
*/
|
||||||
|
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示消息
|
||||||
|
* @param message 要显示的消息
|
||||||
|
* @details 消息显示功能:
|
||||||
|
* - 将消息复制到全局状态消息缓冲区
|
||||||
|
* - 同时在控制台输出消息内容
|
||||||
|
* - 确保字符串安全复制,防止缓冲区溢出
|
||||||
|
* @note 消息会存储在status_message全局变量中
|
||||||
|
* 字符串长度限制为缓冲区大小减1
|
||||||
|
* 消息可用于游戏状态提示和错误信息显示
|
||||||
|
*/
|
||||||
|
void show_message(const char *message);
|
||||||
|
|
||||||
|
#endif // GUI_H
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
* @note 本文件定义了初始化游戏棋盘的相关函数和全局变量。
|
* @note 本文件定义了初始化游戏棋盘的相关函数和全局变量。
|
||||||
* 它负责设置游戏的初始状态,包括棋盘大小、玩家标识、游戏规则等。
|
* 它负责设置游戏的初始状态,包括棋盘大小、玩家标识、游戏规则等。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef INIT_BOARD_H
|
#ifndef INIT_BOARD_H
|
||||||
#define INIT_BOARD_H
|
#define INIT_BOARD_H
|
||||||
|
|
||||||
+20
-20
@@ -23,9 +23,9 @@
|
|||||||
* @brief 记录一步棋的详细信息
|
* @brief 记录一步棋的详细信息
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int player; // 执行该步的玩家标识
|
int player; // 执行该步的玩家标识
|
||||||
int x; // 落子的行坐标 (0-based)
|
int x; // 落子的行坐标 (0-based)
|
||||||
int y; // 落子的列坐标 (0-based)
|
int y; // 落子的列坐标 (0-based)
|
||||||
} Step;
|
} Step;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,9 +33,9 @@ typedef struct {
|
|||||||
* @details 用于评估棋形,例如判断活三、冲四等关键形态
|
* @details 用于评估棋形,例如判断活三、冲四等关键形态
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int continuous_chess; // 连续同色棋子的数量
|
int continuous_chess; // 连续同色棋子的数量
|
||||||
bool check_start; // 棋子序列的起始端是否为空位(即是否开放)
|
bool check_start; // 棋子序列的起始端是否为空位(即是否开放)
|
||||||
bool check_end; // 棋子序列的末尾端是否为空位(即是否开放)
|
bool check_end; // 棋子序列的末尾端是否为空位(即是否开放)
|
||||||
} DirInfo;
|
} DirInfo;
|
||||||
|
|
||||||
// ==================== AI相关数据结构 ====================
|
// ==================== AI相关数据结构 ====================
|
||||||
@@ -45,8 +45,8 @@ typedef struct {
|
|||||||
* @details 用于AI移动排序,存储候选移动及其评估分数
|
* @details 用于AI移动排序,存储候选移动及其评估分数
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int x, y; // 位置坐标
|
int x, y; // 位置坐标
|
||||||
int score; // 评估分数
|
int score; // 评估分数
|
||||||
} ScoredMove;
|
} ScoredMove;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,12 +54,12 @@ typedef struct {
|
|||||||
* @details 用于AI威胁检测系统
|
* @details 用于AI威胁检测系统
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
typedef enum {
|
||||||
THREAT_NONE = 0, // 无威胁
|
THREAT_NONE = 0, // 无威胁
|
||||||
THREAT_WIN = 5, // 直接获胜
|
THREAT_WIN = 5, // 直接获胜
|
||||||
THREAT_FOUR = 4, // 活四/冲四
|
THREAT_FOUR = 4, // 活四/冲四
|
||||||
THREAT_THREE = 3, // 活三
|
THREAT_THREE = 3, // 活三
|
||||||
THREAT_DOUBLE = 2, // 双威胁
|
THREAT_DOUBLE = 2, // 双威胁
|
||||||
THREAT_POTENTIAL = 1 // 潜在威胁
|
THREAT_POTENTIAL = 1 // 潜在威胁
|
||||||
} ThreatLevel;
|
} ThreatLevel;
|
||||||
|
|
||||||
// ==================== 网络相关数据结构 ====================
|
// ==================== 网络相关数据结构 ====================
|
||||||
@@ -69,11 +69,11 @@ typedef enum {
|
|||||||
* @details 用于网络对战中的消息传输
|
* @details 用于网络对战中的消息传输
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int type; // 消息类型
|
int type; // 消息类型
|
||||||
int player_id; // 玩家ID
|
int player_id; // 玩家ID
|
||||||
int x, y; // 坐标(用于落子)
|
int x, y; // 坐标(用于落子)
|
||||||
char message[256]; // 消息内容(用于聊天等)
|
char message[256]; // 消息内容(用于聊天等)
|
||||||
time_t timestamp; // 时间戳
|
time_t timestamp; // 时间戳
|
||||||
} NetworkMessage;
|
} NetworkMessage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +87,7 @@ typedef struct {
|
|||||||
int local_player_id; // 本地玩家ID
|
int local_player_id; // 本地玩家ID
|
||||||
int remote_player_id; // 远程玩家ID
|
int remote_player_id; // 远程玩家ID
|
||||||
char remote_ip[16]; // 远程IP地址(MAX_IP_LENGTH = 16)
|
char remote_ip[16]; // 远程IP地址(MAX_IP_LENGTH = 16)
|
||||||
int port; // 端口号
|
int port; // 端口号
|
||||||
} NetworkGameState;
|
} NetworkGameState;
|
||||||
|
|
||||||
#endif // TYPE_H
|
#endif // TYPE_H
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
* @note 本文件定义了用户界面相关的函数和数据结构。
|
* @note 本文件定义了用户界面相关的函数和数据结构。
|
||||||
* 它负责处理用户输入、显示游戏界面、提示信息等与用户交互的功能。
|
* 它负责处理用户输入、显示游戏界面、提示信息等与用户交互的功能。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef UI_H
|
#ifndef UI_H
|
||||||
#define UI_H
|
#define UI_H
|
||||||
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
[Setup]
|
||||||
|
AppName=五子棋游戏
|
||||||
|
AppVersion=8.3
|
||||||
|
AppPublisher=LHY
|
||||||
|
AppPublisherURL=https://github.com/LHY0125/gobang.git
|
||||||
|
AppSupportURL=https://github.com/LHY0125/gobang.git
|
||||||
|
AppUpdatesURL=https://github.com/LHY0125/gobang.git
|
||||||
|
DefaultDirName={autopf}\Gobang
|
||||||
|
DefaultGroupName=五子棋游戏
|
||||||
|
AllowNoIcons=yes
|
||||||
|
LicenseFile=..\README.md
|
||||||
|
OutputDir=dist
|
||||||
|
OutputBaseFilename=Gobang_Inno_Setup
|
||||||
|
SetupIconFile=
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\gobang_console.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\gobang_gui.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\SDL3.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\include\*"; DestDir: "{app}\include"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\src\*"; DestDir: "{app}\src"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\MD\*"; DestDir: "{app}\MD"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\TXT\*"; DestDir: "{app}\TXT"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\records\*"; DestDir: "{app}\records"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\installer\*"; DestDir: "{app}\installer"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
Source: "..\compile.bat"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\gobang_config.ini"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\Makefile"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\五子棋游戏(控制台版)"; Filename: "{app}\gobang_console.exe"
|
||||||
|
Name: "{group}\五子棋游戏(图形界面版)"; Filename: "{app}\gobang_gui.exe"
|
||||||
|
Name: "{group}\{cm:UninstallProgram,五子棋游戏}"; Filename: "{uninstallexe}"
|
||||||
|
Name: "{autodesktop}\五子棋游戏"; Filename: "{app}\gobang_gui.exe"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\gobang_gui.exe"; Description: "{cm:LaunchProgram,五子棋游戏}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: filesandordirs; Name: "{app}\records"
|
||||||
|
Type: filesandordirs; Name: "{app}\include"
|
||||||
|
Type: filesandordirs; Name: "{app}\src"
|
||||||
|
Type: filesandordirs; Name: "{app}\MD"
|
||||||
|
Type: filesandordirs; Name: "{app}\TXT"
|
||||||
|
Type: filesandordirs; Name: "{app}\installer"
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
; NSIS Install Script - Gobang Game
|
||||||
|
; Version: v8.3
|
||||||
|
; Author: LHY
|
||||||
|
|
||||||
|
!define PRODUCT_NAME "Gobang Game"
|
||||||
|
!define PRODUCT_VERSION "8.3"
|
||||||
|
!define PRODUCT_PUBLISHER "LHY"
|
||||||
|
!define PRODUCT_WEB_SITE "https://github.com/LHY0125/gobang.git"
|
||||||
|
!define PRODUCT_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\gobang_gui.exe"
|
||||||
|
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
|
||||||
|
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
|
||||||
|
|
||||||
|
; Include Modern UI
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
|
||||||
|
; MUI Settings
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
||||||
|
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
|
||||||
|
|
||||||
|
; Welcome page
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
; License page
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "..\README.md"
|
||||||
|
; Components page
|
||||||
|
!insertmacro MUI_PAGE_COMPONENTS
|
||||||
|
; Directory page
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
; Install page
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
; Finish page
|
||||||
|
!define MUI_FINISHPAGE_RUN "$INSTDIR\gobang_gui.exe"
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
|
||||||
|
; Uninstall page
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
|
||||||
|
; Language files
|
||||||
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
|
|
||||||
|
; Installer attributes
|
||||||
|
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||||
|
OutFile "dist\Gobang_NSIS_Setup.exe"
|
||||||
|
InstallDir "$PROGRAMFILES\Gobang"
|
||||||
|
InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
|
||||||
|
ShowInstDetails show
|
||||||
|
ShowUnInstDetails show
|
||||||
|
|
||||||
|
; Version information
|
||||||
|
VIProductVersion "1.0.0.0"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "ProductName" "${PRODUCT_NAME}"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "Comments" "Gobang Game - Classic five-in-a-row strategy game with AI and network support"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "CompanyName" "${PRODUCT_PUBLISHER}"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "LegalTrademarks" "MIT License"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "LegalCopyright" "© 2025 ${PRODUCT_PUBLISHER}"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileDescription" "${PRODUCT_NAME} Setup"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileVersion" "${PRODUCT_VERSION}"
|
||||||
|
|
||||||
|
Section "Main Program" SEC01
|
||||||
|
SectionIn RO
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
SetOverwrite ifnewer
|
||||||
|
File "..\gobang_console.exe"
|
||||||
|
File "..\gobang_gui.exe"
|
||||||
|
File "..\SDL3.dll"
|
||||||
|
File "..\compile.bat"
|
||||||
|
File "..\gobang_config.ini"
|
||||||
|
File "..\Makefile"
|
||||||
|
File "..\README.md"
|
||||||
|
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Gobang Console.lnk" "$INSTDIR\gobang_console.exe"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Gobang GUI.lnk" "$INSTDIR\gobang_gui.exe"
|
||||||
|
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\gobang_gui.exe"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Source Code" SEC02
|
||||||
|
SetOutPath "$INSTDIR\include"
|
||||||
|
File /r "..\include\*.*"
|
||||||
|
SetOutPath "$INSTDIR\src"
|
||||||
|
File /r "..\src\*.*"
|
||||||
|
SetOutPath "$INSTDIR\installer"
|
||||||
|
File /r "..\installer\*.*"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Game Records" SEC03
|
||||||
|
SetOutPath "$INSTDIR\records"
|
||||||
|
File /r "..\records\*.*"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Documentation" SEC04
|
||||||
|
SetOutPath "$INSTDIR\MD"
|
||||||
|
File /r "..\MD\*.*"
|
||||||
|
SetOutPath "$INSTDIR\TXT"
|
||||||
|
File /r "..\TXT\*.*"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section -AdditionalIcons
|
||||||
|
WriteIniStr "$INSTDIR\${PRODUCT_NAME}.url" "InternetShortcut" "URL" "${PRODUCT_WEB_SITE}"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Website.lnk" "$INSTDIR\${PRODUCT_NAME}.url"
|
||||||
|
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section -Post
|
||||||
|
WriteUninstaller "$INSTDIR\uninst.exe"
|
||||||
|
WriteRegStr HKLM "${PRODUCT_DIR_REGKEY}" "" "$INSTDIR\gobang_gui.exe"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\gobang_gui.exe"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||||
|
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
; Component descriptions
|
||||||
|
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${SEC01} "Install main program files. This is a required component."
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${SEC02} "Install source code files, including headers and implementation files."
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${SEC03} "Install game records and save files."
|
||||||
|
!insertmacro MUI_DESCRIPTION_TEXT ${SEC04} "Install project documentation, including user manual and technical documents."
|
||||||
|
!insertmacro MUI_FUNCTION_DESCRIPTION_END
|
||||||
|
|
||||||
|
Function un.onUninstSuccess
|
||||||
|
HideWindow
|
||||||
|
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) has been successfully removed from your computer."
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function un.onInit
|
||||||
|
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2
|
||||||
|
Abort
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section Uninstall
|
||||||
|
Delete "$INSTDIR\${PRODUCT_NAME}.url"
|
||||||
|
Delete "$INSTDIR\uninst.exe"
|
||||||
|
Delete "$INSTDIR\gobang_console.exe"
|
||||||
|
Delete "$INSTDIR\gobang_gui.exe"
|
||||||
|
Delete "$INSTDIR\SDL3.dll"
|
||||||
|
Delete "$INSTDIR\compile.bat"
|
||||||
|
Delete "$INSTDIR\gobang_config.ini"
|
||||||
|
Delete "$INSTDIR\Makefile"
|
||||||
|
Delete "$INSTDIR\README.md"
|
||||||
|
|
||||||
|
RMDir /r "$INSTDIR\include"
|
||||||
|
RMDir /r "$INSTDIR\src"
|
||||||
|
RMDir /r "$INSTDIR\installer"
|
||||||
|
RMDir /r "$INSTDIR\records"
|
||||||
|
RMDir /r "$INSTDIR\MD"
|
||||||
|
RMDir /r "$INSTDIR\TXT"
|
||||||
|
|
||||||
|
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk"
|
||||||
|
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Website.lnk"
|
||||||
|
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Gobang Console.lnk"
|
||||||
|
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Gobang GUI.lnk"
|
||||||
|
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||||
|
|
||||||
|
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||||
|
RMDir "$INSTDIR"
|
||||||
|
|
||||||
|
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
|
||||||
|
DeleteRegKey HKLM "${PRODUCT_DIR_REGKEY}"
|
||||||
|
SetAutoClose true
|
||||||
|
SectionEnd
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,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,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,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,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,7 +0,0 @@
|
|||||||
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
|
|
||||||
2,15,135,452,平局或未完成
|
|
||||||
|
|
||||||
步数,玩家,行坐标,列坐标
|
|
||||||
1,2,3,3
|
|
||||||
2,1,4,4
|
|
||||||
3,2,4,3
|
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
|
|
||||||
2,15,110,212,平局或未完成
|
|
||||||
|
|
||||||
步数,玩家,行坐标,列坐标
|
|
||||||
1,1,3,3
|
|
||||||
2,2,6,6
|
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
|
|
||||||
2,15,110,212,平局或未完成
|
|
||||||
|
|
||||||
步数,玩家,行坐标,列坐标
|
|
||||||
1,1,3,3
|
|
||||||
2,2,6,6
|
|
||||||
|
@@ -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,5 +0,0 @@
|
|||||||
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
|
|
||||||
3,15,0,130,平局或未完成
|
|
||||||
|
|
||||||
步数,玩家,行坐标,列坐标
|
|
||||||
1,2,4,4
|
|
||||||
|
@@ -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,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
|
|
||||||
|
+65
-56
@@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* @file ai.c
|
||||||
|
* @note 本文件定义了AI模块的函数和变量
|
||||||
|
* @note 包括:
|
||||||
|
* 1. 评估一个落子位置的综合得分(结合进攻和防守)
|
||||||
|
* 2. 评估指定位置的价值
|
||||||
|
* 3. 评估棋盘价值
|
||||||
|
*/
|
||||||
|
|
||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
#include "ai.h"
|
#include "ai.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@@ -60,7 +69,7 @@ int evaluate_pos(int x, int y, int player)
|
|||||||
// 直接形成五连珠为必胜
|
// 直接形成五连珠为必胜
|
||||||
if (info.continuous_chess >= 5)
|
if (info.continuous_chess >= 5)
|
||||||
{
|
{
|
||||||
board[x][y] = original; // 还原棋盘
|
board[x][y] = original; // 还原棋盘
|
||||||
return SEARCH_WIN_BONUS; // 返回最大分
|
return SEARCH_WIN_BONUS; // 返回最大分
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +130,8 @@ int evaluate_pos(int x, int y, int player)
|
|||||||
// 位置奖励:越靠近中心分数越高
|
// 位置奖励:越靠近中心分数越高
|
||||||
int center_x = BOARD_SIZE / 2;
|
int center_x = BOARD_SIZE / 2;
|
||||||
int center_y = BOARD_SIZE / 2;
|
int center_y = BOARD_SIZE / 2;
|
||||||
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
|
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
|
||||||
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
|
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
|
||||||
|
|
||||||
board[x][y] = original; // 还原棋盘状态
|
board[x][y] = original; // 还原棋盘状态
|
||||||
return total_score + position_bonus; // 返回总评估分
|
return total_score + position_bonus; // 返回总评估分
|
||||||
@@ -164,7 +173,7 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi
|
|||||||
// 使用移动排序优化搜索效率
|
// 使用移动排序优化搜索效率
|
||||||
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
|
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
|
||||||
int move_count = generate_candidate_moves(candidate_moves, player);
|
int move_count = generate_candidate_moves(candidate_moves, player);
|
||||||
|
|
||||||
// 限制搜索的候选移动数量以提高性能
|
// 限制搜索的候选移动数量以提高性能
|
||||||
int max_candidates = (depth >= 3) ? 15 : 25; // 深度越大,候选移动越少
|
int max_candidates = (depth >= 3) ? 15 : 25; // 深度越大,候选移动越少
|
||||||
if (move_count > max_candidates)
|
if (move_count > max_candidates)
|
||||||
@@ -241,13 +250,13 @@ void ai_move(int depth)
|
|||||||
// 1. 使用增强的威胁检测系统
|
// 1. 使用增强的威胁检测系统
|
||||||
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
|
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
|
||||||
int move_count = generate_candidate_moves(candidate_moves, AI);
|
int move_count = generate_candidate_moves(candidate_moves, AI);
|
||||||
|
|
||||||
// 首先检查是否有直接获胜的机会
|
// 首先检查是否有直接获胜的机会
|
||||||
for (int idx = 0; idx < move_count; idx++)
|
for (int idx = 0; idx < move_count; idx++)
|
||||||
{
|
{
|
||||||
int i = candidate_moves[idx].x;
|
int i = candidate_moves[idx].x;
|
||||||
int j = candidate_moves[idx].y;
|
int j = candidate_moves[idx].y;
|
||||||
|
|
||||||
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
||||||
if (ai_threat == THREAT_WIN)
|
if (ai_threat == THREAT_WIN)
|
||||||
{
|
{
|
||||||
@@ -258,13 +267,13 @@ void ai_move(int depth)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否需要阻止玩家的威胁
|
// 检查是否需要阻止玩家的威胁
|
||||||
for (int idx = 0; idx < move_count; idx++)
|
for (int idx = 0; idx < move_count; idx++)
|
||||||
{
|
{
|
||||||
int i = candidate_moves[idx].x;
|
int i = candidate_moves[idx].x;
|
||||||
int j = candidate_moves[idx].y;
|
int j = candidate_moves[idx].y;
|
||||||
|
|
||||||
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
|
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
|
||||||
if (player_threat >= THREAT_FOUR)
|
if (player_threat >= THREAT_FOUR)
|
||||||
{
|
{
|
||||||
@@ -275,13 +284,13 @@ void ai_move(int depth)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否需要阻止玩家的活三威胁
|
// 检查是否需要阻止玩家的活三威胁
|
||||||
for (int idx = 0; idx < move_count; idx++)
|
for (int idx = 0; idx < move_count; idx++)
|
||||||
{
|
{
|
||||||
int i = candidate_moves[idx].x;
|
int i = candidate_moves[idx].x;
|
||||||
int j = candidate_moves[idx].y;
|
int j = candidate_moves[idx].y;
|
||||||
|
|
||||||
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
|
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
|
||||||
if (player_threat == THREAT_THREE)
|
if (player_threat == THREAT_THREE)
|
||||||
{
|
{
|
||||||
@@ -299,7 +308,7 @@ void ai_move(int depth)
|
|||||||
{
|
{
|
||||||
int i = candidate_moves[idx].x;
|
int i = candidate_moves[idx].x;
|
||||||
int j = candidate_moves[idx].y;
|
int j = candidate_moves[idx].y;
|
||||||
|
|
||||||
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
||||||
if (ai_threat >= THREAT_FOUR)
|
if (ai_threat >= THREAT_FOUR)
|
||||||
{
|
{
|
||||||
@@ -310,13 +319,13 @@ void ai_move(int depth)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 寻找能形成活三的位置
|
// 寻找能形成活三的位置
|
||||||
for (int idx = 0; idx < move_count; idx++)
|
for (int idx = 0; idx < move_count; idx++)
|
||||||
{
|
{
|
||||||
int i = candidate_moves[idx].x;
|
int i = candidate_moves[idx].x;
|
||||||
int j = candidate_moves[idx].y;
|
int j = candidate_moves[idx].y;
|
||||||
|
|
||||||
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
||||||
if (ai_threat == THREAT_THREE)
|
if (ai_threat == THREAT_THREE)
|
||||||
{
|
{
|
||||||
@@ -327,14 +336,14 @@ void ai_move(int depth)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 如果没有明显的威胁机会,选择评分最高的位置
|
// 3. 如果没有明显的威胁机会,选择评分最高的位置
|
||||||
if (move_count > 0)
|
if (move_count > 0)
|
||||||
{
|
{
|
||||||
// candidate_moves已经按分数排序,直接选择第一个
|
// candidate_moves已经按分数排序,直接选择第一个
|
||||||
int best_x = candidate_moves[0].x;
|
int best_x = candidate_moves[0].x;
|
||||||
int best_y = candidate_moves[0].y;
|
int best_y = candidate_moves[0].y;
|
||||||
|
|
||||||
board[best_x][best_y] = AI;
|
board[best_x][best_y] = AI;
|
||||||
steps[step_count++] = (Step){AI, best_x, best_y};
|
steps[step_count++] = (Step){AI, best_x, best_y};
|
||||||
printf("AI落子(%d, %d) - 最佳位置!\n", best_x + 1, best_y + 1);
|
printf("AI落子(%d, %d) - 最佳位置!\n", best_x + 1, best_y + 1);
|
||||||
@@ -379,7 +388,7 @@ static int compare_moves(const void *a, const void *b)
|
|||||||
int generate_candidate_moves(ScoredMove *moves, int player)
|
int generate_candidate_moves(ScoredMove *moves, int player)
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
|
||||||
for (int i = 0; i < BOARD_SIZE; i++)
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
{
|
{
|
||||||
for (int j = 0; j < BOARD_SIZE; j++)
|
for (int j = 0; j < BOARD_SIZE; j++)
|
||||||
@@ -388,51 +397,51 @@ int generate_candidate_moves(ScoredMove *moves, int player)
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只考虑有意义的位置(附近有棋子)
|
// 只考虑有意义的位置(附近有棋子)
|
||||||
if (step_count > AI_SEARCH_RANGE_THRESHOLD && !is_near_stones(i, j))
|
if (step_count > AI_SEARCH_RANGE_THRESHOLD && !is_near_stones(i, j))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算该位置的评估分数
|
// 计算该位置的评估分数
|
||||||
moves[count].x = i;
|
moves[count].x = i;
|
||||||
moves[count].y = j;
|
moves[count].y = j;
|
||||||
|
|
||||||
// 结合威胁检测和位置评估
|
// 结合威胁检测和位置评估
|
||||||
ThreatLevel threat = detect_threat(i, j, player);
|
ThreatLevel threat = detect_threat(i, j, player);
|
||||||
int base_score = evaluate_move(i, j);
|
int base_score = evaluate_move(i, j);
|
||||||
|
|
||||||
// 根据威胁等级调整分数
|
// 根据威胁等级调整分数
|
||||||
switch (threat)
|
switch (threat)
|
||||||
{
|
{
|
||||||
case THREAT_WIN:
|
case THREAT_WIN:
|
||||||
moves[count].score = base_score + 10000;
|
moves[count].score = base_score + 10000;
|
||||||
break;
|
break;
|
||||||
case THREAT_FOUR:
|
case THREAT_FOUR:
|
||||||
moves[count].score = base_score + 5000;
|
moves[count].score = base_score + 5000;
|
||||||
break;
|
break;
|
||||||
case THREAT_THREE:
|
case THREAT_THREE:
|
||||||
moves[count].score = base_score + 2000;
|
moves[count].score = base_score + 2000;
|
||||||
break;
|
break;
|
||||||
case THREAT_DOUBLE:
|
case THREAT_DOUBLE:
|
||||||
moves[count].score = base_score + 1000;
|
moves[count].score = base_score + 1000;
|
||||||
break;
|
break;
|
||||||
case THREAT_POTENTIAL:
|
case THREAT_POTENTIAL:
|
||||||
moves[count].score = base_score + 500;
|
moves[count].score = base_score + 500;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
moves[count].score = base_score;
|
moves[count].score = base_score;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按分数降序排序
|
// 按分数降序排序
|
||||||
qsort(moves, count, sizeof(ScoredMove), compare_moves);
|
qsort(moves, count, sizeof(ScoredMove), compare_moves);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,16 +480,16 @@ ThreatLevel detect_threat(int x, int y, int player)
|
|||||||
{
|
{
|
||||||
// 模拟落子
|
// 模拟落子
|
||||||
board[x][y] = player;
|
board[x][y] = player;
|
||||||
|
|
||||||
ThreatLevel max_threat = THREAT_NONE;
|
ThreatLevel max_threat = THREAT_NONE;
|
||||||
int threat_count = 0;
|
int threat_count = 0;
|
||||||
|
|
||||||
// 检查四个方向
|
// 检查四个方向
|
||||||
for (int k = 0; k < 4; k++)
|
for (int k = 0; k < 4; k++)
|
||||||
{
|
{
|
||||||
DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player);
|
DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player);
|
||||||
ThreatLevel current_threat = THREAT_NONE;
|
ThreatLevel current_threat = THREAT_NONE;
|
||||||
|
|
||||||
// 检查是否形成五子连珠(获胜)
|
// 检查是否形成五子连珠(获胜)
|
||||||
if (info.continuous_chess >= 5)
|
if (info.continuous_chess >= 5)
|
||||||
{
|
{
|
||||||
@@ -508,27 +517,27 @@ ThreatLevel detect_threat(int x, int y, int player)
|
|||||||
{
|
{
|
||||||
current_threat = THREAT_POTENTIAL;
|
current_threat = THREAT_POTENTIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_threat > max_threat)
|
if (current_threat > max_threat)
|
||||||
{
|
{
|
||||||
max_threat = current_threat;
|
max_threat = current_threat;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (current_threat >= THREAT_THREE)
|
if (current_threat >= THREAT_THREE)
|
||||||
{
|
{
|
||||||
threat_count++;
|
threat_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复棋盘
|
// 恢复棋盘
|
||||||
board[x][y] = EMPTY;
|
board[x][y] = EMPTY;
|
||||||
|
|
||||||
// 如果有多个威胁,提升威胁等级
|
// 如果有多个威胁,提升威胁等级
|
||||||
if (threat_count >= 2 && max_threat >= THREAT_THREE)
|
if (threat_count >= 2 && max_threat >= THREAT_THREE)
|
||||||
{
|
{
|
||||||
max_threat = THREAT_DOUBLE;
|
max_threat = THREAT_DOUBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return max_threat;
|
return max_threat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,18 +551,18 @@ ThreatLevel detect_threat(int x, int y, int player)
|
|||||||
int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
||||||
{
|
{
|
||||||
int threats = 0;
|
int threats = 0;
|
||||||
|
|
||||||
// 向前搜索
|
// 向前搜索
|
||||||
for (int i = 1; i < 5; i++)
|
for (int i = 1; i < 5; i++)
|
||||||
{
|
{
|
||||||
int nx = x + i * dx;
|
int nx = x + i * dx;
|
||||||
int ny = y + i * dy;
|
int ny = y + i * dy;
|
||||||
|
|
||||||
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
|
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (board[nx][ny] == player)
|
if (board[nx][ny] == player)
|
||||||
{
|
{
|
||||||
threats++;
|
threats++;
|
||||||
@@ -563,18 +572,18 @@ int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向后搜索
|
// 向后搜索
|
||||||
for (int i = 1; i < 5; i++)
|
for (int i = 1; i < 5; i++)
|
||||||
{
|
{
|
||||||
int nx = x - i * dx;
|
int nx = x - i * dx;
|
||||||
int ny = y - i * dy;
|
int ny = y - i * dy;
|
||||||
|
|
||||||
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
|
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (board[nx][ny] == player)
|
if (board[nx][ny] == player)
|
||||||
{
|
{
|
||||||
threats++;
|
threats++;
|
||||||
@@ -584,6 +593,6 @@ int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return threats;
|
return threats;
|
||||||
}
|
}
|
||||||
+62
-139
@@ -1,3 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* @file config.c
|
||||||
|
* @brief 五子棋游戏参数配置源文件
|
||||||
|
* @note 本文件集中定义了五子棋游戏的所有参数配置,便于统一管理和修改
|
||||||
|
*/
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "game_mode.h"
|
#include "game_mode.h"
|
||||||
@@ -18,13 +24,13 @@ void load_game_config()
|
|||||||
printf("配置文件不存在,使用默认配置\n");
|
printf("配置文件不存在,使用默认配置\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char line[256];
|
char line[256];
|
||||||
while (fgets(line, sizeof(line), file))
|
while (fgets(line, sizeof(line), file))
|
||||||
{
|
{
|
||||||
// 去除换行符
|
// 去除换行符
|
||||||
line[strcspn(line, "\n")] = 0;
|
line[strcspn(line, "\n")] = 0;
|
||||||
|
|
||||||
// 解析配置项
|
// 解析配置项
|
||||||
if (strncmp(line, "BOARD_SIZE=", 11) == 0)
|
if (strncmp(line, "BOARD_SIZE=", 11) == 0)
|
||||||
{
|
{
|
||||||
@@ -72,7 +78,7 @@ void load_game_config()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
printf("配置加载完成\n");
|
printf("配置加载完成\n");
|
||||||
}
|
}
|
||||||
@@ -88,7 +94,7 @@ void save_game_config()
|
|||||||
printf("无法保存配置文件\n");
|
printf("无法保存配置文件\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(file, "# 五子棋游戏配置文件\n");
|
fprintf(file, "# 五子棋游戏配置文件\n");
|
||||||
fprintf(file, "# 棋盘大小 (范围: %d-%d)\n", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
|
fprintf(file, "# 棋盘大小 (范围: %d-%d)\n", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
|
||||||
fprintf(file, "BOARD_SIZE=%d\n", BOARD_SIZE);
|
fprintf(file, "BOARD_SIZE=%d\n", BOARD_SIZE);
|
||||||
@@ -102,7 +108,7 @@ void save_game_config()
|
|||||||
fprintf(file, "NETWORK_PORT=%d\n", network_port);
|
fprintf(file, "NETWORK_PORT=%d\n", network_port);
|
||||||
fprintf(file, "\n# 网络超时时间 (毫秒)\n");
|
fprintf(file, "\n# 网络超时时间 (毫秒)\n");
|
||||||
fprintf(file, "NETWORK_TIMEOUT=%d\n", network_timeout);
|
fprintf(file, "NETWORK_TIMEOUT=%d\n", network_timeout);
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
printf("配置保存完成\n");
|
printf("配置保存完成\n");
|
||||||
}
|
}
|
||||||
@@ -118,7 +124,7 @@ void reset_to_default_config()
|
|||||||
time_limit = DEFAULT_TIME_LIMIT;
|
time_limit = DEFAULT_TIME_LIMIT;
|
||||||
network_port = DEFAULT_NETWORK_PORT;
|
network_port = DEFAULT_NETWORK_PORT;
|
||||||
network_timeout = NETWORK_TIMEOUT_MS;
|
network_timeout = NETWORK_TIMEOUT_MS;
|
||||||
|
|
||||||
printf("已重置为默认配置\n");
|
printf("已重置为默认配置\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,27 +152,10 @@ void display_current_config()
|
|||||||
void config_board_size()
|
void config_board_size()
|
||||||
{
|
{
|
||||||
printf("\n当前棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
|
printf("\n当前棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
|
||||||
printf("请输入新的棋盘大小 (%d-%d): ", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
|
|
||||||
|
int new_size = get_integer_input("请输入新的棋盘大小: ", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
|
||||||
int new_size;
|
BOARD_SIZE = new_size;
|
||||||
if (scanf("%d", &new_size) == 1)
|
printf("棋盘大小已设置为: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
|
||||||
{
|
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -175,19 +164,10 @@ void config_board_size()
|
|||||||
void config_forbidden_moves()
|
void config_forbidden_moves()
|
||||||
{
|
{
|
||||||
printf("\n当前禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
|
printf("\n当前禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
|
||||||
printf("是否启用禁手规则?(1=开启, 0=关闭): ");
|
|
||||||
|
int choice = get_integer_input("是否启用禁手规则?(1=开启, 0=关闭): ", 0, 1);
|
||||||
int choice;
|
use_forbidden_moves = (choice != 0);
|
||||||
if (scanf("%d", &choice) == 1)
|
printf("禁手规则已%s\n", use_forbidden_moves ? "开启" : "关闭");
|
||||||
{
|
|
||||||
use_forbidden_moves = (choice != 0);
|
|
||||||
printf("禁手规则已%s\n", use_forbidden_moves ? "开启" : "关闭");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("输入格式错误!\n");
|
|
||||||
while (getchar() != '\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -196,36 +176,18 @@ void config_forbidden_moves()
|
|||||||
void config_timer()
|
void config_timer()
|
||||||
{
|
{
|
||||||
printf("\n当前计时器: %s\n", use_timer ? "开启" : "关闭");
|
printf("\n当前计时器: %s\n", use_timer ? "开启" : "关闭");
|
||||||
printf("是否启用计时器?(1=开启, 0=关闭): ");
|
|
||||||
|
int choice = get_integer_input("是否启用计时器?(1=开启, 0=关闭): ", 0, 1);
|
||||||
int choice;
|
use_timer = choice;
|
||||||
if (scanf("%d", &choice) == 1)
|
if (use_timer)
|
||||||
{
|
{
|
||||||
use_timer = choice;
|
int new_limit = get_integer_input("请输入时间限制(分钟): ", 1, 999);
|
||||||
if (use_timer)
|
time_limit = new_limit * 60; // 转换为秒数存储
|
||||||
{
|
printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60);
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
printf("输入格式错误!\n");
|
printf("计时器已关闭\n");
|
||||||
while (getchar() != '\n');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,46 +199,14 @@ void config_network()
|
|||||||
printf("\n===== 网络配置 =====\n");
|
printf("\n===== 网络配置 =====\n");
|
||||||
printf("当前网络端口: %d\n", network_port);
|
printf("当前网络端口: %d\n", network_port);
|
||||||
printf("当前网络超时: %d 毫秒\n", network_timeout);
|
printf("当前网络超时: %d 毫秒\n", network_timeout);
|
||||||
|
|
||||||
printf("\n请输入新的网络端口 (%d-%d): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
int new_port = get_integer_input("请输入新的网络端口: ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
||||||
int new_port;
|
network_port = new_port;
|
||||||
if (scanf("%d", &new_port) == 1)
|
printf("网络端口已设置为: %d\n", network_port);
|
||||||
{
|
|
||||||
if (new_port >= MIN_NETWORK_PORT && new_port <= MAX_NETWORK_PORT)
|
int new_timeout = get_integer_input("请输入网络超时时间(毫秒, 建议1000-10000): ", 1000, 60000);
|
||||||
{
|
network_timeout = new_timeout;
|
||||||
network_port = new_port;
|
printf("网络超时已设置为: %d 毫秒\n", network_timeout);
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -285,47 +215,40 @@ void config_network()
|
|||||||
void config_management_menu()
|
void config_management_menu()
|
||||||
{
|
{
|
||||||
int choice;
|
int choice;
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
clear_screen();
|
clear_screen();
|
||||||
display_settings_menu();
|
display_settings_menu();
|
||||||
display_current_config();
|
display_current_config();
|
||||||
|
|
||||||
printf("请选择操作: ");
|
choice = get_integer_input("请选择操作(0-5): ", 0, 5);
|
||||||
if (scanf("%d", &choice) != 1)
|
|
||||||
{
|
|
||||||
printf("输入格式错误!\n");
|
|
||||||
while (getchar() != '\n');
|
|
||||||
pause_for_input("按任意键继续...");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (choice)
|
switch (choice)
|
||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
config_board_size();
|
config_board_size();
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
config_forbidden_moves();
|
config_forbidden_moves();
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
config_timer();
|
config_timer();
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
config_network();
|
config_network();
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
printf("AI难度设置功能开发中...\n");
|
printf("AI难度设置功能开发中...\n");
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 0:
|
||||||
save_game_config();
|
save_game_config();
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
printf("无效的选择!\n");
|
printf("无效的选择!\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pause_for_input("按任意键继续...");
|
pause_for_input("按任意键继续...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+126
-71
@@ -1,3 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* @file game_mode.c
|
||||||
|
* @brief 五子棋游戏框架源文件
|
||||||
|
* @note 本文件定义了五子棋游戏的四种主要模式:
|
||||||
|
* 1. AI对战模式
|
||||||
|
* 2. 双人对战模式
|
||||||
|
* 3. 网络对战模式
|
||||||
|
* 4. 复盘模式
|
||||||
|
*/
|
||||||
|
|
||||||
#include "game_mode.h"
|
#include "game_mode.h"
|
||||||
#include "init_board.h"
|
#include "init_board.h"
|
||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
@@ -6,6 +16,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
|
#include "gui.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
@@ -192,16 +203,16 @@ bool parse_network_player_input(int *x, int *y)
|
|||||||
{
|
{
|
||||||
int steps_to_undo;
|
int steps_to_undo;
|
||||||
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
|
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
|
||||||
|
|
||||||
printf("发送悔棋请求给对方...\n");
|
printf("发送悔棋请求给对方...\n");
|
||||||
if (send_undo_request(steps_to_undo))
|
if (send_undo_request(steps_to_undo))
|
||||||
{
|
{
|
||||||
printf("悔棋请求已发送,等待对方回应...\n");
|
printf("悔棋请求已发送,等待对方回应...\n");
|
||||||
|
|
||||||
// 等待对方回应
|
// 等待对方回应
|
||||||
NetworkMessage msg;
|
NetworkMessage msg;
|
||||||
time_t start_time = time(NULL);
|
time_t start_time = time(NULL);
|
||||||
|
|
||||||
while (difftime(time(NULL), start_time) < 30) // 30秒超时
|
while (difftime(time(NULL), start_time) < 30) // 30秒超时
|
||||||
{
|
{
|
||||||
if (receive_network_message(&msg, 1000))
|
if (receive_network_message(&msg, 1000))
|
||||||
@@ -229,14 +240,14 @@ bool parse_network_player_input(int *x, int *y)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_network_connected())
|
if (!is_network_connected())
|
||||||
{
|
{
|
||||||
printf("网络连接断开\n");
|
printf("网络连接断开\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("悔棋请求超时,对方未回应。\n");
|
printf("悔棋请求超时,对方未回应。\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -349,18 +360,18 @@ void run_ai_game()
|
|||||||
scores_calculated = 0;
|
scores_calculated = 0;
|
||||||
|
|
||||||
// AI对战模式
|
// AI对战模式
|
||||||
int AI_DEPTH = 3;
|
int AI_DEPTH = DEFAULT_AI_DEPTH;
|
||||||
AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5);
|
AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief AI的防守系数,系数越大越倾向于防守
|
* @brief AI的防守系数,系数越大越倾向于防守
|
||||||
* @note 1~1.2
|
* @note 1~1.5
|
||||||
* 2~1.3
|
* 2~1.6
|
||||||
* 3~1.4
|
* 3~1.7
|
||||||
* 4~1.5
|
* 4~1.8
|
||||||
* 5~1.6
|
* 5~1.9
|
||||||
*/
|
*/
|
||||||
defense_coefficient = 1.2 + (AI_DEPTH - 1) * 0.1;
|
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (AI_DEPTH - 1) * 0.1;
|
||||||
|
|
||||||
empty_board();
|
empty_board();
|
||||||
int current_player = determine_first_player(PLAYER, AI);
|
int current_player = determine_first_player(PLAYER, AI);
|
||||||
@@ -558,7 +569,7 @@ void run_network_game()
|
|||||||
{
|
{
|
||||||
// 重置评分计算标志
|
// 重置评分计算标志
|
||||||
scores_calculated = 0;
|
scores_calculated = 0;
|
||||||
|
|
||||||
// 初始化网络模块
|
// 初始化网络模块
|
||||||
if (!init_network())
|
if (!init_network())
|
||||||
{
|
{
|
||||||
@@ -566,29 +577,30 @@ void run_network_game()
|
|||||||
pause_for_input("按任意键返回主菜单...");
|
pause_for_input("按任意键返回主菜单...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("=== 网络对战模式 ===\n");
|
printf("=== 网络对战模式 ===\n");
|
||||||
printf("1. 创建房间(作为主机)\n");
|
printf("1. 创建房间(作为主机)\n");
|
||||||
printf("2. 加入房间(连接到主机)\n");
|
printf("2. 加入房间(连接到主机)\n");
|
||||||
|
|
||||||
int choice = get_integer_input("请选择模式(1-2): ", 1, 2);
|
int choice = get_integer_input("请选择模式(1-2): ", 1, 2);
|
||||||
|
|
||||||
bool connection_success = false;
|
bool connection_success = false;
|
||||||
|
|
||||||
if (choice == 1)
|
if (choice == 1)
|
||||||
{
|
{
|
||||||
// 服务器模式
|
// 服务器模式
|
||||||
int port = get_integer_input("请输入监听端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
int port = get_integer_input("请输入监听端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
||||||
if (port == 0) port = network_port;
|
if (port == 0)
|
||||||
|
port = network_port;
|
||||||
|
|
||||||
printf("\n正在创建房间...\n");
|
printf("\n正在创建房间...\n");
|
||||||
connection_success = create_server(port);
|
connection_success = create_server(port);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 客户端模式
|
// 客户端模式
|
||||||
char ip[MAX_IP_LENGTH];
|
char ip[MAX_IP_LENGTH];
|
||||||
|
|
||||||
// 循环直到输入有效的IP地址或用户选择退出
|
// 循环直到输入有效的IP地址或用户选择退出
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
@@ -597,10 +609,11 @@ void run_network_game()
|
|||||||
{
|
{
|
||||||
printf("输入错误,请重新输入。\n");
|
printf("输入错误,请重新输入。\n");
|
||||||
// 清除输入缓冲区
|
// 清除输入缓冲区
|
||||||
while (getchar() != '\n');
|
while (getchar() != '\n')
|
||||||
|
;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否要退出
|
// 检查是否要退出
|
||||||
if (strcmp(ip, "exit") == 0 || strcmp(ip, "EXIT") == 0)
|
if (strcmp(ip, "exit") == 0 || strcmp(ip, "EXIT") == 0)
|
||||||
{
|
{
|
||||||
@@ -609,14 +622,14 @@ void run_network_game()
|
|||||||
pause_for_input("按任意键返回主菜单...");
|
pause_for_input("按任意键返回主菜单...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 简单的IP地址格式验证
|
// 简单的IP地址格式验证
|
||||||
if (strlen(ip) < 7 || strlen(ip) > 15)
|
if (strlen(ip) < 7 || strlen(ip) > 15)
|
||||||
{
|
{
|
||||||
printf("IP地址格式错误!请输入有效的IP地址(如:192.168.1.100)\n");
|
printf("IP地址格式错误!请输入有效的IP地址(如:192.168.1.100)\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查IP地址是否包含有效字符
|
// 检查IP地址是否包含有效字符
|
||||||
bool valid_ip = true;
|
bool valid_ip = true;
|
||||||
for (int i = 0; i < strlen(ip); i++)
|
for (int i = 0; i < strlen(ip); i++)
|
||||||
@@ -627,26 +640,27 @@ void run_network_game()
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid_ip)
|
if (!valid_ip)
|
||||||
{
|
{
|
||||||
printf("IP地址格式错误!只能包含数字和点号。\n");
|
printf("IP地址格式错误!只能包含数字和点号。\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查点号数量
|
// 检查点号数量
|
||||||
int dot_count = 0;
|
int dot_count = 0;
|
||||||
for (int i = 0; i < strlen(ip); i++)
|
for (int i = 0; i < strlen(ip); i++)
|
||||||
{
|
{
|
||||||
if (ip[i] == '.') dot_count++;
|
if (ip[i] == '.')
|
||||||
|
dot_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dot_count != 3)
|
if (dot_count != 3)
|
||||||
{
|
{
|
||||||
printf("IP地址格式错误!应包含3个点号(如:192.168.1.100)\n");
|
printf("IP地址格式错误!应包含3个点号(如:192.168.1.100)\n");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("输入的IP地址: %s\n", ip);
|
printf("输入的IP地址: %s\n", ip);
|
||||||
int confirm = get_integer_input("确认连接到此IP?(1:是/0:否,重新输入): ", 0, 1);
|
int confirm = get_integer_input("确认连接到此IP?(1:是/0:否,重新输入): ", 0, 1);
|
||||||
if (confirm)
|
if (confirm)
|
||||||
@@ -655,14 +669,15 @@ void run_network_game()
|
|||||||
}
|
}
|
||||||
// 如果选择否,继续循环重新输入
|
// 如果选择否,继续循环重新输入
|
||||||
}
|
}
|
||||||
|
|
||||||
int port = get_integer_input("请输入服务器端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
int port = get_integer_input("请输入服务器端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
||||||
if (port == 0) port = network_port;
|
if (port == 0)
|
||||||
|
port = network_port;
|
||||||
|
|
||||||
printf("\n正在连接到服务器 %s:%d...\n", ip, port);
|
printf("\n正在连接到服务器 %s:%d...\n", ip, port);
|
||||||
connection_success = connect_to_server(ip, port);
|
connection_success = connect_to_server(ip, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connection_success)
|
if (!connection_success)
|
||||||
{
|
{
|
||||||
printf("网络连接失败!\n");
|
printf("网络连接失败!\n");
|
||||||
@@ -670,16 +685,16 @@ void run_network_game()
|
|||||||
pause_for_input("按任意键返回主菜单...");
|
pause_for_input("按任意键返回主菜单...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n网络连接成功!游戏即将开始...\n");
|
printf("\n网络连接成功!游戏即将开始...\n");
|
||||||
printf("你是玩家%d,%s先手\n",
|
printf("你是玩家%d,%s先手\n",
|
||||||
network_state.local_player_id,
|
network_state.local_player_id,
|
||||||
network_state.local_player_id == PLAYER1 ? "你" : "对方");
|
network_state.local_player_id == PLAYER1 ? "你" : "对方");
|
||||||
|
|
||||||
// 开始网络游戏
|
// 开始网络游戏
|
||||||
empty_board();
|
empty_board();
|
||||||
print_board();
|
print_board();
|
||||||
|
|
||||||
if (network_game_loop())
|
if (network_game_loop())
|
||||||
{
|
{
|
||||||
printf("===== 游戏结束 =====\n");
|
printf("===== 游戏结束 =====\n");
|
||||||
@@ -690,12 +705,57 @@ void run_network_game()
|
|||||||
{
|
{
|
||||||
printf("游戏因网络错误而结束\n");
|
printf("游戏因网络错误而结束\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理网络连接
|
// 清理网络连接
|
||||||
disconnect_network();
|
disconnect_network();
|
||||||
pause_for_input("按任意键返回主菜单...");
|
pause_for_input("按任意键返回主菜单...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示游戏规则
|
||||||
|
*/
|
||||||
|
void show_game_rules()
|
||||||
|
{
|
||||||
|
clear_screen();
|
||||||
|
display_game_rules();
|
||||||
|
pause_for_input("\n按任意键返回主菜单...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示关于游戏信息
|
||||||
|
*/
|
||||||
|
void show_about_game()
|
||||||
|
{
|
||||||
|
clear_screen();
|
||||||
|
display_about();
|
||||||
|
pause_for_input("\n按任意键返回主菜单...");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启动图形化界面
|
||||||
|
*/
|
||||||
|
void run_gui_mode()
|
||||||
|
{
|
||||||
|
if (init_gui() == 0)
|
||||||
|
{
|
||||||
|
printf("启动图形化界面...\n");
|
||||||
|
printf("图形化界面已启动,窗口应该可见\n");
|
||||||
|
printf("如果看不到窗口,请检查任务栏或按Alt+Tab切换\n");
|
||||||
|
while (gui_running && handle_events())
|
||||||
|
{
|
||||||
|
render_game();
|
||||||
|
SDL_Delay(16); // 约60FPS
|
||||||
|
}
|
||||||
|
printf("退出图形化界面\n");
|
||||||
|
cleanup_gui();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("图形化界面启动失败!请检查SDL3库是否正确安装。\n");
|
||||||
|
pause_for_input("按任意键返回主菜单...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 处理网络玩家回合
|
* @brief 处理网络玩家回合
|
||||||
*/
|
*/
|
||||||
@@ -706,17 +766,16 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
// 本地玩家回合
|
// 本地玩家回合
|
||||||
int x, y;
|
int x, y;
|
||||||
time_t start_time, end_time;
|
time_t start_time, end_time;
|
||||||
|
|
||||||
if (use_timer)
|
if (use_timer)
|
||||||
{
|
{
|
||||||
time(&start_time);
|
time(&start_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
printf("\n轮到你了,请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输: ", BOARD_SIZE);
|
printf("\n轮到你了,请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输: ", BOARD_SIZE);
|
||||||
|
|
||||||
bool input_received = false;
|
bool input_received = false;
|
||||||
while (!input_received)
|
while (!input_received)
|
||||||
{
|
{
|
||||||
@@ -727,10 +786,10 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
{
|
{
|
||||||
printf("\n你超时了,对方获胜!\n");
|
printf("\n你超时了,对方获胜!\n");
|
||||||
send_surrender(); // 发送认输消息
|
send_surrender(); // 发送认输消息
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int parse_result = parse_network_player_input(&x, &y);
|
int parse_result = parse_network_player_input(&x, &y);
|
||||||
if (parse_result == 1) // 有效坐标输入
|
if (parse_result == 1) // 有效坐标输入
|
||||||
{
|
{
|
||||||
@@ -752,9 +811,10 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
x--; y--; // 转换为0-based坐标
|
x--;
|
||||||
|
y--; // 转换为0-based坐标
|
||||||
|
|
||||||
if (player_move(x, y, current_player))
|
if (player_move(x, y, current_player))
|
||||||
{
|
{
|
||||||
break; // 成功落子,跳出循环
|
break; // 成功落子,跳出循环
|
||||||
@@ -765,35 +825,34 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
// 继续循环,重新输入坐标
|
// 继续循环,重新输入坐标
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送落子消息
|
// 发送落子消息
|
||||||
if (!send_move(x, y, current_player))
|
if (!send_move(x, y, current_player))
|
||||||
{
|
{
|
||||||
printf("发送落子消息失败!\n");
|
printf("发送落子消息失败!\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
}
|
}
|
||||||
|
|
||||||
print_board();
|
print_board();
|
||||||
|
|
||||||
if (check_win(x, y, current_player))
|
if (check_win(x, y, current_player))
|
||||||
{
|
{
|
||||||
printf("\n你获胜了!\n");
|
printf("\n你获胜了!\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 等待对方落子
|
// 等待对方落子
|
||||||
printf("\n等待对方落子...\n");
|
printf("\n等待对方落子...\n");
|
||||||
|
|
||||||
NetworkMessage msg;
|
NetworkMessage msg;
|
||||||
time_t start_time = time(NULL);
|
time_t start_time = time(NULL);
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
if (receive_network_message(&msg, 1000))
|
if (receive_network_message(&msg, 1000))
|
||||||
{
|
{
|
||||||
// 1秒超时
|
// 1秒超时
|
||||||
if (msg.type == MSG_MOVE && msg.player_id == current_player)
|
if (msg.type == MSG_MOVE && msg.player_id == current_player)
|
||||||
{
|
{
|
||||||
@@ -803,41 +862,37 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
printf("收到无效的落子坐标!\n");
|
printf("收到无效的落子坐标!\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("对方落子: (%d, %d)\n", msg.x + 1, msg.y + 1);
|
printf("对方落子: (%d, %d)\n", msg.x + 1, msg.y + 1);
|
||||||
print_board();
|
print_board();
|
||||||
|
|
||||||
if (check_win(msg.x, msg.y, current_player))
|
if (check_win(msg.x, msg.y, current_player))
|
||||||
{
|
{
|
||||||
printf("\n对方获胜!\n");
|
printf("\n对方获胜!\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
|
||||||
else if (msg.type == MSG_SURRENDER)
|
else if (msg.type == MSG_SURRENDER)
|
||||||
{
|
{
|
||||||
printf("\n对方认输,你获胜了!\n");
|
printf("\n对方认输,你获胜了!\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (msg.type == MSG_DISCONNECT)
|
else if (msg.type == MSG_DISCONNECT)
|
||||||
{
|
{
|
||||||
printf("\n对方已断开连接\n");
|
printf("\n对方已断开连接\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (msg.type == MSG_CHAT)
|
else if (msg.type == MSG_CHAT)
|
||||||
{
|
{
|
||||||
printf("[对方]: %s\n", msg.message);
|
printf("[对方]: %s\n", msg.message);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (msg.type == MSG_UNDO_REQUEST)
|
else if (msg.type == MSG_UNDO_REQUEST)
|
||||||
{
|
{
|
||||||
int steps = msg.x;
|
int steps = msg.x;
|
||||||
printf("\n对方请求悔棋 %d 步,是否同意?(1:同意/0:拒绝): ", steps);
|
printf("\n对方请求悔棋 %d 步,是否同意?(1:同意/0:拒绝): ", steps);
|
||||||
int response = get_integer_input("", 0, 1);
|
int response = get_integer_input("", 0, 1);
|
||||||
|
|
||||||
if (response && return_move(steps * 2))
|
if (response && return_move(steps * 2))
|
||||||
{
|
{
|
||||||
printf("同意悔棋,双方各退 %d 步\n", steps);
|
printf("同意悔棋,双方各退 %d 步\n", steps);
|
||||||
@@ -854,14 +909,14 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查超时
|
// 检查超时
|
||||||
if (use_timer && difftime(time(NULL), start_time) > time_limit)
|
if (use_timer && difftime(time(NULL), start_time) > time_limit)
|
||||||
{
|
{
|
||||||
printf("\n对方超时,你获胜!\n");
|
printf("\n对方超时,你获胜!\n");
|
||||||
return 0; // 游戏结束
|
return 0; // 游戏结束
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查网络连接
|
// 检查网络连接
|
||||||
if (!is_network_connected())
|
if (!is_network_connected())
|
||||||
{
|
{
|
||||||
@@ -870,7 +925,7 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1; // 正常回合完成
|
return 1; // 正常回合完成
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -880,11 +935,11 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
|
|||||||
bool network_game_loop()
|
bool network_game_loop()
|
||||||
{
|
{
|
||||||
int current_player = PLAYER1; // 总是从玩家1开始
|
int current_player = PLAYER1; // 总是从玩家1开始
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
bool is_local_turn = (current_player == network_state.local_player_id);
|
bool is_local_turn = (current_player == network_state.local_player_id);
|
||||||
|
|
||||||
int turn_result = handle_network_player_turn(current_player, is_local_turn);
|
int turn_result = handle_network_player_turn(current_player, is_local_turn);
|
||||||
if (turn_result == 0) // 游戏结束
|
if (turn_result == 0) // 游戏结束
|
||||||
{
|
{
|
||||||
@@ -894,17 +949,17 @@ bool network_game_loop()
|
|||||||
{
|
{
|
||||||
continue; // 不切换玩家,重新开始当前回合
|
continue; // 不切换玩家,重新开始当前回合
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查平局
|
// 检查平局
|
||||||
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
if (step_count == BOARD_SIZE * BOARD_SIZE)
|
||||||
{
|
{
|
||||||
printf("\n平局!\n");
|
printf("\n平局!\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 切换玩家
|
// 切换玩家
|
||||||
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
|
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
|
||||||
|
|
||||||
// 检查网络连接
|
// 检查网络连接
|
||||||
if (!is_network_connected())
|
if (!is_network_connected())
|
||||||
{
|
{
|
||||||
@@ -912,6 +967,6 @@ bool network_game_loop()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* @file globals.c
|
||||||
|
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||||
|
* @brief 全局变量定义和初始化文件
|
||||||
|
* @note 集中管理所有全局变量的定义和初始化,提高代码可维护性
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "globals.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <SDL3/SDL.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}; // 网络游戏状态
|
||||||
|
|
||||||
|
// ==================== GUI相关变量定义 ====================
|
||||||
|
SDL_Window *window = NULL; // SDL窗口指针
|
||||||
|
SDL_Renderer *renderer = NULL; // SDL渲染器指针
|
||||||
|
int gui_running = 1; // GUI运行状态标志
|
||||||
|
int current_player_gui = PLAYER; // GUI当前玩家
|
||||||
|
int game_over = 0; // 游戏结束标志
|
||||||
|
char status_message[256] = "五子棋游戏 - 黑子先行"; // 状态消息
|
||||||
|
|
||||||
|
// ==================== 记录相关变量定义 ====================
|
||||||
|
int player1_final_score = 0; // 玩家1最终得分
|
||||||
|
int player2_final_score = 0; // 玩家2最终得分
|
||||||
|
int scores_calculated = 0; // 评分计算标志
|
||||||
|
char winner_info[50] = "平局或未完成"; // 存储胜负信息
|
||||||
+10
-3
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @file gobang.c
|
||||||
|
* @brief 五子棋游戏源文件
|
||||||
|
* @note 本文件定义了五子棋游戏的主要数据结构、函数和全局变量。
|
||||||
|
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
|
||||||
|
*/
|
||||||
|
|
||||||
#include "game_mode.h"
|
#include "game_mode.h"
|
||||||
#include "init_board.h"
|
#include "init_board.h"
|
||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
@@ -258,12 +265,12 @@ int calculate_step_score(int x, int y, int player)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 位置奖励:越靠近中心分数越高
|
// 位置奖励:越靠近中心分数越高
|
||||||
int center_x = BOARD_SIZE / 2;
|
int center_x = BOARD_SIZE / 2;
|
||||||
int center_y = BOARD_SIZE / 2;
|
int center_y = BOARD_SIZE / 2;
|
||||||
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
|
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
|
||||||
int position_bonus = POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
|
int position_bonus = POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
|
||||||
|
|
||||||
return step_score + position_bonus;
|
return step_score + position_bonus;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,441 @@
|
|||||||
|
/**
|
||||||
|
* @file gui.c
|
||||||
|
* @brief 图形化用户界面实现文件
|
||||||
|
* @note 使用SDL3库实现五子棋的图形化界面
|
||||||
|
* @author 刘航宇
|
||||||
|
* @date 2025-01-15
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gui.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "game_mode.h"
|
||||||
|
#include "init_board.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化GUI
|
||||||
|
* @details 初始化SDL3图形库和游戏界面组件:
|
||||||
|
* - 初始化SDL视频子系统
|
||||||
|
* - 创建游戏窗口(可调整大小)
|
||||||
|
* - 创建SDL渲染器
|
||||||
|
* - 初始化游戏状态和棋盘数据
|
||||||
|
* @return 成功返回0,失败返回-1
|
||||||
|
* @note 窗口标题为"五子棋游戏 - SDL3版本"
|
||||||
|
* 窗口尺寸由WINDOW_WIDTH和WINDOW_HEIGHT定义
|
||||||
|
* 失败时会自动清理已创建的资源
|
||||||
|
*/
|
||||||
|
int init_gui()
|
||||||
|
{
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||||
|
{
|
||||||
|
printf("SDL初始化失败: %s\n", SDL_GetError());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
window = SDL_CreateWindow(
|
||||||
|
"五子棋游戏 - SDL3版本",
|
||||||
|
WINDOW_WIDTH, WINDOW_HEIGHT,
|
||||||
|
SDL_WINDOW_RESIZABLE);
|
||||||
|
|
||||||
|
// 设置窗口位置到屏幕中央
|
||||||
|
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
|
||||||
|
if (!window)
|
||||||
|
{
|
||||||
|
printf("窗口创建失败: %s\n", SDL_GetError());
|
||||||
|
SDL_Quit();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示窗口
|
||||||
|
SDL_ShowWindow(window);
|
||||||
|
|
||||||
|
renderer = SDL_CreateRenderer(window, NULL);
|
||||||
|
if (!renderer)
|
||||||
|
{
|
||||||
|
printf("渲染器创建失败: %s\n", SDL_GetError());
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
SDL_Quit();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化游戏状态
|
||||||
|
// 初始化棋盘
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < BOARD_SIZE; j++)
|
||||||
|
{
|
||||||
|
board[i][j] = EMPTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current_player_gui = PLAYER;
|
||||||
|
game_over = 0;
|
||||||
|
|
||||||
|
printf("图形化界面初始化成功!\n");
|
||||||
|
printf("使用鼠标点击棋盘进行落子\n");
|
||||||
|
printf("按ESC键退出游戏\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理GUI资源
|
||||||
|
* @details 按顺序释放所有SDL相关资源:
|
||||||
|
* - 销毁SDL渲染器
|
||||||
|
* - 销毁SDL窗口
|
||||||
|
* - 退出SDL子系统
|
||||||
|
* @note 函数会检查资源是否存在再进行释放
|
||||||
|
* 释放后将指针设置为NULL防止重复释放
|
||||||
|
* 程序退出时必须调用此函数避免内存泄漏
|
||||||
|
*/
|
||||||
|
void cleanup_gui()
|
||||||
|
{
|
||||||
|
if (renderer)
|
||||||
|
{
|
||||||
|
SDL_DestroyRenderer(renderer);
|
||||||
|
renderer = NULL;
|
||||||
|
}
|
||||||
|
if (window)
|
||||||
|
{
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
window = NULL;
|
||||||
|
}
|
||||||
|
SDL_Quit();
|
||||||
|
printf("图形化界面已关闭\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染游戏画面
|
||||||
|
* @details 完整的游戏画面渲染流程:
|
||||||
|
* - 清空屏幕并设置背景色
|
||||||
|
* - 绘制棋盘网格和标记点
|
||||||
|
* - 绘制所有棋子
|
||||||
|
* - 绘制UI界面元素
|
||||||
|
* - 将渲染结果显示到屏幕
|
||||||
|
* @note 使用双缓冲技术,通过SDL_RenderPresent显示最终结果
|
||||||
|
* 背景色由GUI_COLOR_BACKGROUND定义
|
||||||
|
* 每帧都会完全重绘整个画面
|
||||||
|
*/
|
||||||
|
void render_game()
|
||||||
|
{
|
||||||
|
// 清空屏幕 - 设置背景色
|
||||||
|
SDL_Color bg_color = GUI_COLOR_BACKGROUND;
|
||||||
|
SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a);
|
||||||
|
SDL_RenderClear(renderer);
|
||||||
|
|
||||||
|
// 绘制棋盘
|
||||||
|
draw_board();
|
||||||
|
|
||||||
|
// 绘制棋子
|
||||||
|
draw_stones();
|
||||||
|
|
||||||
|
// 绘制UI元素
|
||||||
|
draw_ui_elements();
|
||||||
|
|
||||||
|
// 显示渲染结果
|
||||||
|
SDL_RenderPresent(renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 处理事件
|
||||||
|
* @details 处理所有SDL事件并执行相应操作:
|
||||||
|
* - SDL_EVENT_QUIT:用户关闭窗口
|
||||||
|
* - SDL_EVENT_KEY_DOWN:键盘按键(ESC退出)
|
||||||
|
* - SDL_EVENT_MOUSE_BUTTON_DOWN:鼠标点击落子
|
||||||
|
* @return 继续运行返回1,退出返回0
|
||||||
|
* @note 鼠标左键点击会转换为棋盘坐标并尝试落子
|
||||||
|
* 落子后会检查胜负并切换玩家
|
||||||
|
* 游戏结束后不再响应落子操作
|
||||||
|
*/
|
||||||
|
int handle_events()
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
switch (event.type)
|
||||||
|
{
|
||||||
|
case SDL_EVENT_QUIT:
|
||||||
|
gui_running = 0;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case SDL_EVENT_KEY_DOWN:
|
||||||
|
if (event.key.key == SDLK_ESCAPE)
|
||||||
|
{
|
||||||
|
gui_running = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||||
|
if (event.button.button == SDL_BUTTON_LEFT && !game_over)
|
||||||
|
{
|
||||||
|
int board_x, board_y;
|
||||||
|
if (screen_to_board(event.button.x, event.button.y, &board_x, &board_y))
|
||||||
|
{
|
||||||
|
if (have_space(board_x, board_y))
|
||||||
|
{
|
||||||
|
// 执行落子操作
|
||||||
|
if (player_move(board_x, board_y, current_player_gui))
|
||||||
|
{
|
||||||
|
// 检查是否获胜
|
||||||
|
if (check_win(board_x, board_y, current_player_gui))
|
||||||
|
{
|
||||||
|
game_over = 1;
|
||||||
|
if (current_player_gui == PLAYER)
|
||||||
|
{
|
||||||
|
sprintf(status_message, "游戏结束 - 黑子获胜!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "游戏结束 - 白子获胜!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 切换玩家
|
||||||
|
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER;
|
||||||
|
if (current_player_gui == PLAYER)
|
||||||
|
{
|
||||||
|
sprintf(status_message, "轮到黑子下棋");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "轮到白子下棋");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "该位置已有棋子,请选择其他位置");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制棋盘
|
||||||
|
* @details 绘制15x15的五子棋棋盘,包括:
|
||||||
|
* - 横竖交叉的网格线
|
||||||
|
* - 天元点(棋盘中心的标记点)
|
||||||
|
* - 四个星位(棋盘上的定位点)
|
||||||
|
* @note 使用SDL3渲染器绘制线条和填充矩形
|
||||||
|
* 棋盘线条颜色由GUI_COLOR_BOARD_LINE定义
|
||||||
|
* 天元点和星位用黑色小矩形标记
|
||||||
|
*/
|
||||||
|
void draw_board()
|
||||||
|
{
|
||||||
|
SDL_Color line_color = GUI_COLOR_BOARD_LINE;
|
||||||
|
SDL_SetRenderDrawColor(renderer, line_color.r, line_color.g, line_color.b, line_color.a);
|
||||||
|
|
||||||
|
// 绘制横线
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
|
{
|
||||||
|
int y = BOARD_OFFSET_Y + i * CELL_SIZE;
|
||||||
|
SDL_RenderLine(renderer,
|
||||||
|
BOARD_OFFSET_X, y,
|
||||||
|
BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制竖线
|
||||||
|
for (int j = 0; j < BOARD_SIZE; j++)
|
||||||
|
{
|
||||||
|
int x = BOARD_OFFSET_X + j * CELL_SIZE;
|
||||||
|
SDL_RenderLine(renderer,
|
||||||
|
x, BOARD_OFFSET_Y,
|
||||||
|
x, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制天元点和星位
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
|
int center = BOARD_SIZE / 2;
|
||||||
|
|
||||||
|
// 天元点
|
||||||
|
int center_x = BOARD_OFFSET_X + center * CELL_SIZE;
|
||||||
|
int center_y = BOARD_OFFSET_Y + center * CELL_SIZE;
|
||||||
|
SDL_FRect center_rect = {center_x - 2, center_y - 2, 4, 4};
|
||||||
|
SDL_RenderFillRect(renderer, ¢er_rect);
|
||||||
|
|
||||||
|
// 四个星位
|
||||||
|
int star_offset = 3;
|
||||||
|
int positions[][2] = {
|
||||||
|
{center - star_offset, center - star_offset},
|
||||||
|
{center + star_offset, center - star_offset},
|
||||||
|
{center - star_offset, center + star_offset},
|
||||||
|
{center + star_offset, center + star_offset}};
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
int x = BOARD_OFFSET_X + positions[i][1] * CELL_SIZE;
|
||||||
|
int y = BOARD_OFFSET_Y + positions[i][0] * CELL_SIZE;
|
||||||
|
SDL_FRect star_rect = {x - 1, y - 1, 2, 2};
|
||||||
|
SDL_RenderFillRect(renderer, &star_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制棋子
|
||||||
|
* @details 遍历整个棋盘数组,绘制所有已落下的棋子:
|
||||||
|
* - 黑子:使用GUI_COLOR_BLACK_STONE颜色
|
||||||
|
* - 白子:使用GUI_COLOR_WHITE_STONE颜色
|
||||||
|
* - 每个棋子都有边框:使用GUI_COLOR_STONE_BORDER颜色
|
||||||
|
* @note 棋子绘制为圆形,半径由STONE_RADIUS定义
|
||||||
|
* 通过draw_circle函数实现圆形绘制
|
||||||
|
* 棋子位置根据棋盘坐标和CELL_SIZE计算屏幕坐标
|
||||||
|
*/
|
||||||
|
void draw_stones()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < BOARD_SIZE; j++)
|
||||||
|
{
|
||||||
|
if (board[i][j] != EMPTY)
|
||||||
|
{
|
||||||
|
int x = BOARD_OFFSET_X + j * CELL_SIZE;
|
||||||
|
int y = BOARD_OFFSET_Y + i * CELL_SIZE;
|
||||||
|
|
||||||
|
// 设置棋子颜色
|
||||||
|
SDL_Color stone_color, border_color;
|
||||||
|
if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
|
||||||
|
{
|
||||||
|
stone_color = (SDL_Color)GUI_COLOR_BLACK_STONE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
stone_color = (SDL_Color)GUI_COLOR_WHITE_STONE;
|
||||||
|
}
|
||||||
|
border_color = (SDL_Color)GUI_COLOR_STONE_BORDER;
|
||||||
|
|
||||||
|
// 绘制圆形棋子
|
||||||
|
draw_circle(x, y, STONE_RADIUS, stone_color);
|
||||||
|
draw_circle(x, y, STONE_RADIUS, border_color);
|
||||||
|
|
||||||
|
// 重新绘制内部
|
||||||
|
draw_circle(x, y, STONE_RADIUS - 1, stone_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形
|
||||||
|
* @param center_x 圆心X坐标
|
||||||
|
* @param center_y 圆心Y坐标
|
||||||
|
* @param radius 半径
|
||||||
|
* @param color 颜色
|
||||||
|
* @details 使用像素级绘制实现圆形:
|
||||||
|
* - 遍历圆形外接矩形内的所有像素点
|
||||||
|
* - 计算每个像素到圆心的距离
|
||||||
|
* - 距离小于等于半径的像素点进行着色
|
||||||
|
* @note 采用暴力算法,性能较低但实现简单
|
||||||
|
* 适用于绘制棋子等小尺寸圆形
|
||||||
|
* SDL3没有内置圆形绘制函数,需要自实现
|
||||||
|
*/
|
||||||
|
void draw_circle(int center_x, int center_y, int radius, SDL_Color color)
|
||||||
|
{
|
||||||
|
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||||
|
|
||||||
|
for (int w = 0; w < radius * 2; w++)
|
||||||
|
{
|
||||||
|
for (int h = 0; h < radius * 2; h++)
|
||||||
|
{
|
||||||
|
int dx = radius - w;
|
||||||
|
int dy = radius - h;
|
||||||
|
if ((dx * dx + dy * dy) <= (radius * radius))
|
||||||
|
{
|
||||||
|
SDL_RenderPoint(renderer, center_x + dx, center_y + dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制UI元素
|
||||||
|
*/
|
||||||
|
void draw_ui_elements()
|
||||||
|
{
|
||||||
|
// 绘制状态信息区域背景
|
||||||
|
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
|
||||||
|
SDL_FRect info_rect = {BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE + 20, BOARD_OFFSET_Y, 200, 100};
|
||||||
|
SDL_RenderFillRect(renderer, &info_rect);
|
||||||
|
|
||||||
|
// 绘制边框
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
|
SDL_RenderRect(renderer, &info_rect);
|
||||||
|
|
||||||
|
// 这里可以添加文字渲染,但SDL3需要额外的字体库
|
||||||
|
// 暂时用简单的图形表示当前玩家
|
||||||
|
int indicator_x = info_rect.x + 20;
|
||||||
|
int indicator_y = info_rect.y + 20;
|
||||||
|
|
||||||
|
if (!game_over)
|
||||||
|
{
|
||||||
|
if (current_player_gui == PLAYER)
|
||||||
|
{
|
||||||
|
// 黑子回合
|
||||||
|
draw_circle(indicator_x, indicator_y, 10, (SDL_Color){0, 0, 0, 255});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 白子回合
|
||||||
|
draw_circle(indicator_x, indicator_y, 10, (SDL_Color){255, 255, 255, 255});
|
||||||
|
// 绘制当前玩家指示器(简单的矩形代替圆形)
|
||||||
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||||
|
SDL_FRect indicator_rect = {indicator_x - 10, indicator_y - 10, 20, 20};
|
||||||
|
SDL_RenderFillRect(renderer, &indicator_rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 屏幕坐标转棋盘坐标
|
||||||
|
* @param screen_x 屏幕X坐标
|
||||||
|
* @param screen_y 屏幕Y坐标
|
||||||
|
* @param board_x 输出棋盘X坐标
|
||||||
|
* @param board_y 输出棋盘Y坐标
|
||||||
|
* @return 转换成功返回1,失败返回0
|
||||||
|
* @details 坐标转换算法:
|
||||||
|
* - 减去棋盘偏移量得到相对坐标
|
||||||
|
* - 加上半个格子尺寸实现就近取整
|
||||||
|
* - 除以格子尺寸得到棋盘坐标
|
||||||
|
* - 检查坐标是否在有效范围内
|
||||||
|
* @note 使用就近取整算法,点击格子中心附近都会定位到该格子
|
||||||
|
* 坐标范围检查确保不会越界访问棋盘数组
|
||||||
|
*/
|
||||||
|
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y)
|
||||||
|
{
|
||||||
|
int rel_x = screen_x - BOARD_OFFSET_X;
|
||||||
|
int rel_y = screen_y - BOARD_OFFSET_Y;
|
||||||
|
|
||||||
|
*board_x = (rel_x + CELL_SIZE / 2) / CELL_SIZE;
|
||||||
|
*board_y = (rel_y + CELL_SIZE / 2) / CELL_SIZE;
|
||||||
|
|
||||||
|
return (*board_x >= 0 && *board_x < BOARD_SIZE &&
|
||||||
|
*board_y >= 0 && *board_y < BOARD_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示消息
|
||||||
|
* @param message 要显示的消息
|
||||||
|
* @details 消息显示功能:
|
||||||
|
* - 将消息复制到全局状态消息缓冲区
|
||||||
|
* - 同时在控制台输出消息内容
|
||||||
|
* - 确保字符串安全复制,防止缓冲区溢出
|
||||||
|
* @note 消息会存储在status_message全局变量中
|
||||||
|
* 字符串长度限制为缓冲区大小减1
|
||||||
|
* 消息可用于游戏状态提示和错误信息显示
|
||||||
|
*/
|
||||||
|
void show_message(const char *message)
|
||||||
|
{
|
||||||
|
strncpy(status_message, message, sizeof(status_message) - 1);
|
||||||
|
status_message[sizeof(status_message) - 1] = '\0';
|
||||||
|
printf("%s\n", message);
|
||||||
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @file init_board.c
|
||||||
|
* @brief 初始化游戏棋盘源文件
|
||||||
|
* @note 本文件定义了初始化游戏棋盘的相关函数。
|
||||||
|
* 它负责设置游戏的初始状态,包括棋盘大小、玩家标识、游戏规则等。
|
||||||
|
*/
|
||||||
|
|
||||||
#include "init_board.h"
|
#include "init_board.h"
|
||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
#include "game_mode.h"
|
#include "game_mode.h"
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* @file main.c
|
||||||
|
* @brief 五子棋游戏主函数文件
|
||||||
|
* @note 本文件包含了游戏的主循环、模式选择和游戏初始化等功能
|
||||||
|
* @brief 将以下指令复制到powershell
|
||||||
|
*
|
||||||
|
* !控制台版本编译:
|
||||||
|
* gcc -std=c17 -o gobang_console.exe *.c -lws2_32
|
||||||
|
.\gobang_console.exe
|
||||||
|
*
|
||||||
|
* !图形化版本编译(需要SDL3):
|
||||||
|
* gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||||
|
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
|
||||||
|
.\gobang_gui.exe
|
||||||
|
*
|
||||||
|
* @note gcc 为编译器,添加了-lws2_32链接Windows网络库
|
||||||
|
* @note SDL3 的路径:D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32
|
||||||
|
* @brief & "D:\Program Files (x86)\NSIS\makensis.exe" "installer\\installer.nsi"
|
||||||
|
* @brief & "D:\Program Files (x86)\Inno Setup 6\iscc.exe" installer\\installer.iss
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "game_mode.h"
|
||||||
|
#include "ui.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#include <direct.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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-8): ", 0, 8);
|
||||||
|
|
||||||
|
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:
|
||||||
|
show_game_rules();
|
||||||
|
break;
|
||||||
|
// 7. 关于游戏
|
||||||
|
case 7:
|
||||||
|
show_about_game();
|
||||||
|
break;
|
||||||
|
// 8. 图形化界面
|
||||||
|
case 8:
|
||||||
|
run_gui_mode();
|
||||||
|
break;
|
||||||
|
// 0. 退出游戏
|
||||||
|
case 0:
|
||||||
|
save_game_config();
|
||||||
|
printf("感谢使用五子棋游戏!\n");
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
printf("无效的选择!\n");
|
||||||
|
pause_for_input("按任意键继续...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+59
-61
@@ -2,10 +2,6 @@
|
|||||||
* @file network.c
|
* @file network.c
|
||||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||||
* @brief 五子棋网络对战模块实现
|
* @brief 五子棋网络对战模块实现
|
||||||
* @version 6.0
|
|
||||||
* @date 2025-07-10
|
|
||||||
*
|
|
||||||
* @copyright Copyright (c) 2025
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
@@ -48,11 +44,11 @@ bool init_network()
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
memset(&network_state, 0, sizeof(NetworkGameState));
|
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||||
network_state.socket = INVALID_SOCKET;
|
network_state.socket = INVALID_SOCKET;
|
||||||
network_state.port = DEFAULT_PORT;
|
network_state.port = DEFAULT_PORT;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,11 +62,11 @@ void cleanup_network()
|
|||||||
closesocket(network_state.socket);
|
closesocket(network_state.socket);
|
||||||
network_state.socket = INVALID_SOCKET;
|
network_state.socket = INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
network_state.is_connected = false;
|
network_state.is_connected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +77,7 @@ bool create_server(int port)
|
|||||||
{
|
{
|
||||||
struct sockaddr_in server_addr, client_addr;
|
struct sockaddr_in server_addr, client_addr;
|
||||||
int addr_len = sizeof(client_addr);
|
int addr_len = sizeof(client_addr);
|
||||||
|
|
||||||
// 创建套接字
|
// 创建套接字
|
||||||
SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
|
SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (listen_socket == INVALID_SOCKET)
|
if (listen_socket == INVALID_SOCKET)
|
||||||
@@ -89,28 +85,28 @@ bool create_server(int port)
|
|||||||
printf("创建套接字失败\n");
|
printf("创建套接字失败\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置地址重用
|
// 设置地址重用
|
||||||
int opt = 1;
|
int opt = 1;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
|
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||||
#else
|
#else
|
||||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 绑定地址
|
// 绑定地址
|
||||||
memset(&server_addr, 0, sizeof(server_addr));
|
memset(&server_addr, 0, sizeof(server_addr));
|
||||||
server_addr.sin_family = AF_INET;
|
server_addr.sin_family = AF_INET;
|
||||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
server_addr.sin_port = htons(port);
|
server_addr.sin_port = htons(port);
|
||||||
|
|
||||||
if (bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
printf("绑定端口失败\n");
|
printf("绑定端口失败\n");
|
||||||
closesocket(listen_socket);
|
closesocket(listen_socket);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始监听
|
// 开始监听
|
||||||
if (listen(listen_socket, 1) == SOCKET_ERROR)
|
if (listen(listen_socket, 1) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
@@ -118,31 +114,31 @@ bool create_server(int port)
|
|||||||
closesocket(listen_socket);
|
closesocket(listen_socket);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
char local_ip[MAX_IP_LENGTH];
|
char local_ip[MAX_IP_LENGTH];
|
||||||
if (get_local_ip(local_ip, sizeof(local_ip)))
|
if (get_local_ip(local_ip, sizeof(local_ip)))
|
||||||
{
|
{
|
||||||
printf("服务器已启动,等待客户端连接...\n");
|
printf("服务器已启动,等待客户端连接...\n");
|
||||||
printf("本机IP地址: %s\n", local_ip);
|
printf("本机IP地址: %s\n", local_ip);
|
||||||
printf("监听端口: %d\n", port);
|
printf("监听端口: %d\n", port);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf("服务器已启动,监听端口: %d\n", port);
|
printf("服务器已启动,监听端口: %d\n", port);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 等待客户端连接
|
// 等待客户端连接
|
||||||
SOCKET client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &addr_len);
|
SOCKET client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
if (client_socket == INVALID_SOCKET)
|
if (client_socket == INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
printf("接受连接失败\n");
|
printf("接受连接失败\n");
|
||||||
closesocket(listen_socket);
|
closesocket(listen_socket);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭监听套接字
|
// 关闭监听套接字
|
||||||
closesocket(listen_socket);
|
closesocket(listen_socket);
|
||||||
|
|
||||||
// 保存连接信息
|
// 保存连接信息
|
||||||
network_state.socket = client_socket;
|
network_state.socket = client_socket;
|
||||||
network_state.is_server = true;
|
network_state.is_server = true;
|
||||||
@@ -151,7 +147,7 @@ bool create_server(int port)
|
|||||||
network_state.remote_player_id = PLAYER2;
|
network_state.remote_player_id = PLAYER2;
|
||||||
network_state.port = port;
|
network_state.port = port;
|
||||||
strcpy(network_state.remote_ip, inet_ntoa(client_addr.sin_addr));
|
strcpy(network_state.remote_ip, inet_ntoa(client_addr.sin_addr));
|
||||||
|
|
||||||
printf("客户端已连接: %s\n", network_state.remote_ip);
|
printf("客户端已连接: %s\n", network_state.remote_ip);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -159,10 +155,10 @@ bool create_server(int port)
|
|||||||
/**
|
/**
|
||||||
* @brief 连接到服务器(客户端模式)
|
* @brief 连接到服务器(客户端模式)
|
||||||
*/
|
*/
|
||||||
bool connect_to_server(const char* ip, int port)
|
bool connect_to_server(const char *ip, int port)
|
||||||
{
|
{
|
||||||
struct sockaddr_in server_addr;
|
struct sockaddr_in server_addr;
|
||||||
|
|
||||||
// 创建套接字
|
// 创建套接字
|
||||||
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
|
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (client_socket == INVALID_SOCKET)
|
if (client_socket == INVALID_SOCKET)
|
||||||
@@ -170,15 +166,15 @@ bool connect_to_server(const char* ip, int port)
|
|||||||
printf("创建套接字失败\n");
|
printf("创建套接字失败\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置服务器地址
|
// 设置服务器地址
|
||||||
memset(&server_addr, 0, sizeof(server_addr));
|
memset(&server_addr, 0, sizeof(server_addr));
|
||||||
server_addr.sin_family = AF_INET;
|
server_addr.sin_family = AF_INET;
|
||||||
server_addr.sin_port = htons(port);
|
server_addr.sin_port = htons(port);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
server_addr.sin_addr.s_addr = inet_addr(ip);
|
server_addr.sin_addr.s_addr = inet_addr(ip);
|
||||||
if (server_addr.sin_addr.s_addr == INADDR_NONE)
|
if (server_addr.sin_addr.s_addr == INADDR_NONE)
|
||||||
{
|
{
|
||||||
#else
|
#else
|
||||||
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0)
|
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0)
|
||||||
@@ -188,17 +184,17 @@ bool connect_to_server(const char* ip, int port)
|
|||||||
closesocket(client_socket);
|
closesocket(client_socket);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("正在连接到服务器 %s:%d...\n", ip, port);
|
printf("正在连接到服务器 %s:%d...\n", ip, port);
|
||||||
|
|
||||||
// 连接到服务器
|
// 连接到服务器
|
||||||
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
printf("连接服务器失败\n");
|
printf("连接服务器失败\n");
|
||||||
closesocket(client_socket);
|
closesocket(client_socket);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存连接信息
|
// 保存连接信息
|
||||||
network_state.socket = client_socket;
|
network_state.socket = client_socket;
|
||||||
network_state.is_server = false;
|
network_state.is_server = false;
|
||||||
@@ -207,7 +203,7 @@ bool connect_to_server(const char* ip, int port)
|
|||||||
network_state.remote_player_id = PLAYER1;
|
network_state.remote_player_id = PLAYER1;
|
||||||
network_state.port = port;
|
network_state.port = port;
|
||||||
strcpy(network_state.remote_ip, ip);
|
strcpy(network_state.remote_ip, ip);
|
||||||
|
|
||||||
printf("成功连接到服务器\n");
|
printf("成功连接到服务器\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -215,33 +211,33 @@ bool connect_to_server(const char* ip, int port)
|
|||||||
/**
|
/**
|
||||||
* @brief 发送网络消息
|
* @brief 发送网络消息
|
||||||
*/
|
*/
|
||||||
bool send_network_message(const NetworkMessage* msg)
|
bool send_network_message(const NetworkMessage *msg)
|
||||||
{
|
{
|
||||||
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytes_sent = send(network_state.socket, (const char*)msg, sizeof(NetworkMessage), 0);
|
int bytes_sent = send(network_state.socket, (const char *)msg, sizeof(NetworkMessage), 0);
|
||||||
return bytes_sent == sizeof(NetworkMessage);
|
return bytes_sent == sizeof(NetworkMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 接收网络消息
|
* @brief 接收网络消息
|
||||||
*/
|
*/
|
||||||
bool receive_network_message(NetworkMessage* msg, int timeout_ms)
|
bool receive_network_message(NetworkMessage *msg, int timeout_ms)
|
||||||
{
|
{
|
||||||
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置超时
|
// 设置超时
|
||||||
if (timeout_ms > 0)
|
if (timeout_ms > 0)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
DWORD timeout = timeout_ms;
|
DWORD timeout = timeout_ms;
|
||||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
|
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||||
#else
|
#else
|
||||||
struct timeval timeout;
|
struct timeval timeout;
|
||||||
timeout.tv_sec = timeout_ms / 1000;
|
timeout.tv_sec = timeout_ms / 1000;
|
||||||
@@ -249,18 +245,20 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
|
|||||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int bytes_received = recv(network_state.socket, (char*)msg, sizeof(NetworkMessage), 0);
|
int bytes_received = recv(network_state.socket, (char *)msg, sizeof(NetworkMessage), 0);
|
||||||
|
|
||||||
if (bytes_received == sizeof(NetworkMessage))
|
if (bytes_received == sizeof(NetworkMessage))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
} else if (bytes_received == 0)
|
}
|
||||||
|
else if (bytes_received == 0)
|
||||||
{
|
{
|
||||||
// 连接已关闭
|
// 连接已关闭
|
||||||
network_state.is_connected = false;
|
network_state.is_connected = false;
|
||||||
printf("对方已断开连接\n");
|
printf("对方已断开连接\n");
|
||||||
} else if (bytes_received == SOCKET_ERROR)
|
}
|
||||||
|
else if (bytes_received == SOCKET_ERROR)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
int error = WSAGetLastError();
|
int error = WSAGetLastError();
|
||||||
@@ -274,7 +272,7 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
|
|||||||
printf("网络接收错误\n");
|
printf("网络接收错误\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,10 +287,10 @@ void disconnect_network()
|
|||||||
msg.type = MSG_DISCONNECT;
|
msg.type = MSG_DISCONNECT;
|
||||||
msg.player_id = network_state.local_player_id;
|
msg.player_id = network_state.local_player_id;
|
||||||
msg.timestamp = time(NULL);
|
msg.timestamp = time(NULL);
|
||||||
|
|
||||||
send_network_message(&msg);
|
send_network_message(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_network();
|
cleanup_network();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,18 +305,18 @@ bool is_network_connected()
|
|||||||
/**
|
/**
|
||||||
* @brief 获取本机IP地址
|
* @brief 获取本机IP地址
|
||||||
*/
|
*/
|
||||||
bool get_local_ip(char* ip_buffer, int buffer_size)
|
bool get_local_ip(char *ip_buffer, int buffer_size)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Windows实现
|
// Windows实现
|
||||||
char hostname[256];
|
char hostname[256];
|
||||||
if (gethostname(hostname, sizeof(hostname)) == 0)
|
if (gethostname(hostname, sizeof(hostname)) == 0)
|
||||||
{
|
{
|
||||||
struct hostent* host_entry = gethostbyname(hostname);
|
struct hostent *host_entry = gethostbyname(hostname);
|
||||||
if (host_entry != NULL)
|
if (host_entry != NULL)
|
||||||
{
|
{
|
||||||
struct in_addr addr;
|
struct in_addr addr;
|
||||||
addr.s_addr = *((unsigned long*)host_entry->h_addr_list[0]);
|
addr.s_addr = *((unsigned long *)host_entry->h_addr_list[0]);
|
||||||
strncpy(ip_buffer, inet_ntoa(addr), buffer_size - 1);
|
strncpy(ip_buffer, inet_ntoa(addr), buffer_size - 1);
|
||||||
ip_buffer[buffer_size - 1] = '\0';
|
ip_buffer[buffer_size - 1] = '\0';
|
||||||
return true;
|
return true;
|
||||||
@@ -333,11 +331,11 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
|
|||||||
addr.sin_family = AF_INET;
|
addr.sin_family = AF_INET;
|
||||||
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
|
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
|
||||||
addr.sin_port = htons(80);
|
addr.sin_port = htons(80);
|
||||||
|
|
||||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0)
|
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
|
||||||
{
|
{
|
||||||
socklen_t addr_len = sizeof(addr);
|
socklen_t addr_len = sizeof(addr);
|
||||||
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == 0)
|
if (getsockname(sock, (struct sockaddr *)&addr, &addr_len) == 0)
|
||||||
{
|
{
|
||||||
strncpy(ip_buffer, inet_ntoa(addr.sin_addr), buffer_size - 1);
|
strncpy(ip_buffer, inet_ntoa(addr.sin_addr), buffer_size - 1);
|
||||||
ip_buffer[buffer_size - 1] = '\0';
|
ip_buffer[buffer_size - 1] = '\0';
|
||||||
@@ -348,7 +346,7 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
|
|||||||
close(sock);
|
close(sock);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 默认返回本地回环地址
|
// 默认返回本地回环地址
|
||||||
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||||
ip_buffer[buffer_size - 1] = '\0';
|
ip_buffer[buffer_size - 1] = '\0';
|
||||||
@@ -366,21 +364,21 @@ bool send_move(int x, int y, int player_id)
|
|||||||
msg.x = x;
|
msg.x = x;
|
||||||
msg.y = y;
|
msg.y = y;
|
||||||
msg.timestamp = time(NULL);
|
msg.timestamp = time(NULL);
|
||||||
|
|
||||||
return send_network_message(&msg);
|
return send_network_message(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 发送聊天消息
|
* @brief 发送聊天消息
|
||||||
*/
|
*/
|
||||||
bool send_chat_message(const char* message)
|
bool send_chat_message(const char *message)
|
||||||
{
|
{
|
||||||
NetworkMessage msg = {0};
|
NetworkMessage msg = {0};
|
||||||
msg.type = MSG_CHAT;
|
msg.type = MSG_CHAT;
|
||||||
msg.player_id = network_state.local_player_id;
|
msg.player_id = network_state.local_player_id;
|
||||||
strncpy(msg.message, message, sizeof(msg.message) - 1);
|
strncpy(msg.message, message, sizeof(msg.message) - 1);
|
||||||
msg.timestamp = time(NULL);
|
msg.timestamp = time(NULL);
|
||||||
|
|
||||||
return send_network_message(&msg);
|
return send_network_message(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,7 +391,7 @@ bool send_surrender()
|
|||||||
msg.type = MSG_SURRENDER;
|
msg.type = MSG_SURRENDER;
|
||||||
msg.player_id = network_state.local_player_id;
|
msg.player_id = network_state.local_player_id;
|
||||||
msg.timestamp = time(NULL);
|
msg.timestamp = time(NULL);
|
||||||
|
|
||||||
return send_network_message(&msg);
|
return send_network_message(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +405,7 @@ bool send_undo_request(int steps)
|
|||||||
msg.player_id = network_state.local_player_id;
|
msg.player_id = network_state.local_player_id;
|
||||||
msg.x = steps; // 使用x字段存储步数
|
msg.x = steps; // 使用x字段存储步数
|
||||||
msg.timestamp = time(NULL);
|
msg.timestamp = time(NULL);
|
||||||
|
|
||||||
return send_network_message(&msg);
|
return send_network_message(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,9 +417,9 @@ bool send_undo_response(bool accepted, int steps)
|
|||||||
NetworkMessage msg = {0};
|
NetworkMessage msg = {0};
|
||||||
msg.type = MSG_UNDO_RESPONSE;
|
msg.type = MSG_UNDO_RESPONSE;
|
||||||
msg.player_id = network_state.local_player_id;
|
msg.player_id = network_state.local_player_id;
|
||||||
msg.x = steps; // 使用x字段存储步数
|
msg.x = steps; // 使用x字段存储步数
|
||||||
msg.y = accepted ? 1 : 0; // 使用y字段存储是否同意
|
msg.y = accepted ? 1 : 0; // 使用y字段存储是否同意
|
||||||
msg.timestamp = time(NULL);
|
msg.timestamp = time(NULL);
|
||||||
|
|
||||||
return send_network_message(&msg);
|
return send_network_message(&msg);
|
||||||
}
|
}
|
||||||
+23
-18
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @file record.c
|
||||||
|
* @brief 游戏复盘与记录源文件
|
||||||
|
* @note 本文件定义了游戏复盘与记录相关的函数和数据结构。
|
||||||
|
* 它负责管理游戏的历史记录、加载和保存游戏文件、计算游戏评分等功能。
|
||||||
|
*/
|
||||||
|
|
||||||
#include "record.h"
|
#include "record.h"
|
||||||
#include "game_mode.h"
|
#include "game_mode.h"
|
||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
@@ -43,7 +50,7 @@
|
|||||||
void review_process(int game_mode)
|
void review_process(int game_mode)
|
||||||
{
|
{
|
||||||
int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
|
int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
|
||||||
|
|
||||||
// 如果评分尚未计算,则计算评分
|
// 如果评分尚未计算,则计算评分
|
||||||
if (!scores_calculated)
|
if (!scores_calculated)
|
||||||
{
|
{
|
||||||
@@ -54,7 +61,7 @@ void review_process(int game_mode)
|
|||||||
// 评分已从文件中加载,直接使用
|
// 评分已从文件中加载,直接使用
|
||||||
printf("从记录文件中加载评分数据\n");
|
printf("从记录文件中加载评分数据\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (review_choice == 1)
|
if (review_choice == 1)
|
||||||
{
|
{
|
||||||
printf("\n===== 复盘记录(总步数:%d) =====\n", step_count);
|
printf("\n===== 复盘记录(总步数:%d) =====\n", step_count);
|
||||||
@@ -108,7 +115,7 @@ void review_process(int game_mode)
|
|||||||
for (int col = 0; col < BOARD_SIZE; col++)
|
for (int col = 0; col < BOARD_SIZE; col++)
|
||||||
{
|
{
|
||||||
printf("%2d", col + 1); // 列号
|
printf("%2d", col + 1); // 列号
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
for (int row = 0; row < BOARD_SIZE; row++)
|
for (int row = 0; row < BOARD_SIZE; row++)
|
||||||
@@ -140,7 +147,7 @@ void review_process(int game_mode)
|
|||||||
; // 等待回车
|
; // 等待回车
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示胜负结果(直接使用文件中的信息)
|
// 显示胜负结果(直接使用文件中的信息)
|
||||||
printf("\n===== 对局结果 =====");
|
printf("\n===== 对局结果 =====");
|
||||||
if (strcmp(winner_info, "玩家获胜") == 0)
|
if (strcmp(winner_info, "玩家获胜") == 0)
|
||||||
@@ -163,7 +170,7 @@ void review_process(int game_mode)
|
|||||||
{
|
{
|
||||||
printf("\n?? 对局平局或未完成\n");
|
printf("\n?? 对局平局或未完成\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\n复盘结束!按Enter查看评分...");
|
printf("\n复盘结束!按Enter查看评分...");
|
||||||
getchar(); // 等待用户按键
|
getchar(); // 等待用户按键
|
||||||
}
|
}
|
||||||
@@ -188,7 +195,7 @@ void calculate_game_scores()
|
|||||||
{
|
{
|
||||||
// 计算时间权重因子:步数越靠后,权重越大
|
// 计算时间权重因子:步数越靠后,权重越大
|
||||||
double time_weight = 1.0 + (double)i / step_count * TIME_WEIGHT_FACTOR; // 最后的步骤权重是开始步骤的(1+TIME_WEIGHT_FACTOR)倍
|
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)
|
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);
|
player1_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight);
|
||||||
@@ -198,7 +205,7 @@ void calculate_game_scores()
|
|||||||
player2_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight);
|
player2_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 胜负加权:获胜方获得额外的评分奖励
|
// 胜负加权:获胜方获得额外的评分奖励
|
||||||
if (step_count > 0)
|
if (step_count > 0)
|
||||||
{
|
{
|
||||||
@@ -216,7 +223,7 @@ void calculate_game_scores()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scores_calculated = 1; // 标记评分已计算
|
scores_calculated = 1; // 标记评分已计算
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,10 +296,8 @@ void display_game_scores(int game_mode)
|
|||||||
*/
|
*/
|
||||||
void handle_save_record(int game_mode)
|
void handle_save_record(int game_mode)
|
||||||
{
|
{
|
||||||
int save_choice = 0;
|
|
||||||
printf("===== 游戏结束 =====\n");
|
printf("===== 游戏结束 =====\n");
|
||||||
printf("是否保存游戏记录? (1-是, 0-否): ");
|
int save_choice = get_integer_input("是否保存游戏记录? (1-是, 0-否): ", 0, 1);
|
||||||
scanf("%d", &save_choice);
|
|
||||||
|
|
||||||
if (save_choice == 1)
|
if (save_choice == 1)
|
||||||
{
|
{
|
||||||
@@ -416,14 +421,14 @@ int save_game_to_file(const char *filename, int game_mode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入CSV文件头部
|
// 写入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)
|
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);
|
fclose(file);
|
||||||
return 3; // 文件写入失败
|
return 3; // 文件写入失败
|
||||||
}
|
}
|
||||||
|
|
||||||
// 写入CSV表头
|
// 写入CSV表头
|
||||||
if (fprintf(file, "步数,玩家,行坐标,列坐标\n") < 0)
|
if (fprintf(file, "步数,玩家,行坐标,列坐标\n") < 0)
|
||||||
{
|
{
|
||||||
@@ -434,7 +439,7 @@ int save_game_to_file(const char *filename, int game_mode)
|
|||||||
// 写入所有落子步骤(CSV格式)
|
// 写入所有落子步骤(CSV格式)
|
||||||
for (int i = 0; i < step_count; i++)
|
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)
|
if (fprintf(file, "%d,%d,%d,%d\n", i + 1, steps[i].player, steps[i].x + 1, steps[i].y + 1) < 0)
|
||||||
{
|
{
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return 3; // 文件写入失败
|
return 3; // 文件写入失败
|
||||||
@@ -476,10 +481,10 @@ int load_game_from_file(const char *filename)
|
|||||||
|
|
||||||
// 读取游戏模式、棋盘大小和评分结果
|
// 读取游戏模式、棋盘大小和评分结果
|
||||||
int game_mode, size;
|
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);
|
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)
|
if (read_count == 4)
|
||||||
{
|
{
|
||||||
// 旧格式文件,没有胜负信息
|
// 旧格式文件,没有胜负信息
|
||||||
@@ -491,7 +496,7 @@ int load_game_from_file(const char *filename)
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game_mode != GAME_MODE_AI && game_mode != GAME_MODE_PVP && game_mode != GAME_MODE_NETWORK)
|
if (game_mode != GAME_MODE_AI && game_mode != GAME_MODE_PVP && game_mode != GAME_MODE_NETWORK)
|
||||||
{
|
{
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@@ -502,7 +507,7 @@ int load_game_from_file(const char *filename)
|
|||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置评分已计算标志
|
// 设置评分已计算标志
|
||||||
scores_calculated = 1;
|
scores_calculated = 1;
|
||||||
|
|
||||||
+12
-4
@@ -1,3 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* @file ui.c
|
||||||
|
* @brief
|
||||||
|
* @note 本文件定义了用户界面相关的函数和数据结构。
|
||||||
|
* 它负责处理用户输入、显示游戏界面、提示信息等与用户交互的功能。
|
||||||
|
*/
|
||||||
|
|
||||||
#include "ui.h"
|
#include "ui.h"
|
||||||
#include "gobang.h"
|
#include "gobang.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
@@ -24,6 +31,7 @@ void display_main_menu()
|
|||||||
printf("5. 游戏设置\n");
|
printf("5. 游戏设置\n");
|
||||||
printf("6. 游戏规则\n");
|
printf("6. 游戏规则\n");
|
||||||
printf("7. 关于游戏\n");
|
printf("7. 关于游戏\n");
|
||||||
|
printf("8. 图形化界面\n");
|
||||||
printf("0. 退出游戏\n");
|
printf("0. 退出游戏\n");
|
||||||
printf("=====================\n");
|
printf("=====================\n");
|
||||||
}
|
}
|
||||||
@@ -40,7 +48,7 @@ void display_board()
|
|||||||
printf("%2d", j);
|
printf("%2d", j);
|
||||||
}
|
}
|
||||||
printf("\n");
|
printf("\n");
|
||||||
|
|
||||||
// 打印棋盘内容
|
// 打印棋盘内容
|
||||||
for (int i = 0; i < BOARD_SIZE; i++)
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
{
|
{
|
||||||
@@ -123,7 +131,7 @@ void display_settings_menu()
|
|||||||
printf("3. 计时器设置\n");
|
printf("3. 计时器设置\n");
|
||||||
printf("4. 网络配置设置\n");
|
printf("4. 网络配置设置\n");
|
||||||
printf("5. AI难度设置\n");
|
printf("5. AI难度设置\n");
|
||||||
printf("6. 返回主菜单\n");
|
printf("0. 返回主菜单\n");
|
||||||
printf("==================\n");
|
printf("==================\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +151,7 @@ void clear_screen()
|
|||||||
* @brief 暂停等待用户输入
|
* @brief 暂停等待用户输入
|
||||||
* @param prompt 提示信息
|
* @param prompt 提示信息
|
||||||
*/
|
*/
|
||||||
void pause_for_input(const char* prompt)
|
void pause_for_input(const char *prompt)
|
||||||
{
|
{
|
||||||
printf("%s", prompt);
|
printf("%s", prompt);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -186,7 +194,7 @@ void display_about()
|
|||||||
{
|
{
|
||||||
printf("\n===== 关于五子棋游戏 =====\n");
|
printf("\n===== 关于五子棋游戏 =====\n");
|
||||||
printf("🎮 游戏名称:五子棋人机对战\n");
|
printf("🎮 游戏名称:五子棋人机对战\n");
|
||||||
printf("📦 版本:4.0\n");
|
printf("📦 版本:7.0\n");
|
||||||
printf("👨💻 开发者:刘航宇\n");
|
printf("👨💻 开发者:刘航宇\n");
|
||||||
printf("📧 联系邮箱:3364451258@qq.com\n");
|
printf("📧 联系邮箱:3364451258@qq.com\n");
|
||||||
printf("🌐 项目主页:https://github.com/LHY0125/Gobang-Game\n\n");
|
printf("🌐 项目主页:https://github.com/LHY0125/Gobang-Game\n\n");
|
||||||
Reference in New Issue
Block a user