8 Commits

Author SHA1 Message Date
Serendipity 17d7079c41 从GitHub仓库中移除CSV游戏记录文件\n\n- 在.gitignore中添加*.csv规则\n- 从版本控制中移除所有CSV文件\n- 本地文件保留,但不再跟踪 2025-10-08 00:57:54 +08:00
Serendipity 1abde99a68 v8.2: 完善专业安装包制作功能\n\n- 支持Inno Setup和NSIS双重安装包方案\n- 完整的软件分发体系\n- 一键安装部署和完整卸载功能\n- 更新所有文档以反映v8.2版本特性 2025-10-08 00:54:56 +08:00
Serendipity 205e943bcb 重构项目结构:将源文件和头文件分别移动到src和include目录 2025-09-22 22:53:14 +08:00
Serendipity ae0629ef6e feat: v8.2版本更新 - 编译脚本优化\n\n- 🔧 交互式编译脚本:compile_gui.bat支持选择编译控制台或GUI版本\n- 📋 用户友好界面:提供清晰的编译选项菜单和操作指引\n- 编译流程优化:统一编译参数,解决SDL3依赖问题\n- �� 多语言支持:英文界面避免编码问题,确保兼容性\n- 错误处理完善:添加无效输入处理和详细错误提示\n- 🎯 开发体验提升:简化编译流程,提高开发效率 2025-09-20 11:08:46 +08:00
Serendipity 2121831478 Merge branch 'main' of https://github.com/LHY0125/Gobang-Game 2025-09-20 10:52:28 +08:00
Serendipity a11ddeb8d1 feat: 添加Makefile编译系统和BUILD文档
- 创建完整的Makefile支持make编译
- 支持编译控制台版本和GUI版本
- 添加清理、运行、帮助等目标
- 自动处理SDL3和网络库依赖
- 创建BUILD.md编译指南文档
- 完善GUI相关全局变量管理
- 丰富函数注释和文档
2025-09-20 10:50:58 +08:00
Serendipity 39d84f38cf Update README.md 2025-09-18 19:23:18 +08:00
Serendipity 198a8a2943 Update README.md 2025-09-18 19:22:46 +08:00
53 changed files with 1590 additions and 1532 deletions
+5
View File
@@ -2,6 +2,7 @@
*.exe
*.out
*.app
*.o
# IDE配置文件
.idea/
@@ -46,3 +47,7 @@ Thumbs.db
*.ico
# 打包文件
dist/
# 临时游戏存档
*.csv
+105 -463
View File
@@ -1,463 +1,105 @@
# 🚀 五子棋AI增强指南 (v8.0)
## 📋 概述
本文档详细介绍了提升五子棋AI水平的各种方法和实现策略,基于当前项目的代码架构(v8.0),提供从简单参数调优到复杂算法改进的完整方案。v8.0版本新增了GUI界面支持,为AI可视化分析和用户交互提供了更好的平台。
## 🎯 当前AI分析
### 现有优势
- ✅ 模块化设计良好
- ✅ 基础Minimax + α-β剪枝算法
- ✅ 完整的棋型评估系统
- ✅ 威胁检测机制
- ✅ 防御优先策略
- ✅ SDL3图形化界面支持AI可视化分析
- ✅ 双版本架构(控制台+GUI)提供更好的调试环境
### 改进空间
- 🔄 搜索深度有限(当前3层
- 🔄 评估函数相对简单
- 🔄 缺乏开局库和残局库
- 🔄 没有学习机制
- 🔄 搜索效率可优化
## 🛠️ 改进方案
### 1. 立即可实施的改进(难度:⭐)
#### 1.1 参数调优
**修改 `config.h` 中的关键参数:**
```c
// 增加搜索深度
#define DEFAULT_AI_DEPTH 5 // 从3提升到5
// 优化防守系数
#define DEFAULT_DEFENSE_COEFFICIENT 1.5 // 从1.2提升到1.5
// 扩大搜索范围
#define AI_NEARBY_RANGE 3 // 从2扩大到3
// 降低搜索范围限制阈值
#define AI_SEARCH_RANGE_THRESHOLD 8 // 从10降低到8
```
#### 1.2 评分系统优化
**在 `config.h` 中添加新的评分项:**
```c
// 组合棋型评分
#define AI_SCORE_DOUBLE_THREE 50000 // 双三
#define AI_SCORE_FOUR_THREE 200000 // 四三
#define AI_SCORE_THREAT_SEQUENCE 80000 // 威胁序列
#define AI_SCORE_POTENTIAL_FIVE 300000 // 潜在五连
```
### 2. 短期改进(1-2周,难度:⭐⭐)
#### 2.1 移动排序优化
**在 `ai.c` 中添加移动排序函数:**
```c
// 移动排序结构
typedef struct {
int x, y;
int score;
} ScoredMove;
// 移动排序函数
int compare_moves(const void *a, const void *b) {
ScoredMove *moveA = (ScoredMove*)a;
ScoredMove *moveB = (ScoredMove*)b;
return moveB->score - moveA->score;
}
// 生成并排序候选移动
int generate_candidate_moves(ScoredMove *moves, int player) {
int count = 0;
for (int i = 0; i < BOARD_SIZE; i++) {
for (int j = 0; j < BOARD_SIZE; j++) {
if (board[i][j] == EMPTY && is_near_stones(i, j)) {
moves[count].x = i;
moves[count].y = j;
moves[count].score = evaluate_move(i, j);
count++;
}
}
}
qsort(moves, count, sizeof(ScoredMove), compare_moves);
return count;
}
```
#### 2.2 威胁检测增强
**添加多层次威胁检测:**
```c
// 威胁类型枚举
typedef enum {
THREAT_NONE = 0,
THREAT_WIN = 5, // 直接获胜
THREAT_FOUR = 4, // 活四/冲四
THREAT_THREE = 3, // 活三
THREAT_DOUBLE = 2, // 双威胁
THREAT_POTENTIAL = 1 // 潜在威胁
} ThreatLevel;
// 威胁检测函数
ThreatLevel detect_threat(int x, int y, int player) {
// 实现威胁检测逻辑
// ...
}
```
### 3. 中期改进(1-2月,难度:⭐⭐⭐)
#### 3.1 置换表实现
**添加置换表缓存系统:**
```c
// 置换表项
typedef struct {
uint64_t hash_key; // 棋盘哈希值
int score; // 评估分数
int depth; // 搜索深度
int best_x, best_y; // 最佳移动
int flag; // 节点类型(精确值/上界/下界)
} TranspositionEntry;
// 置换表
#define TT_SIZE 1048576 // 2^20
TranspositionEntry transposition_table[TT_SIZE];
// 棋盘哈希函数
uint64_t zobrist_hash() {
// 实现Zobrist哈希
// ...
}
```
#### 3.2 开局库系统
**创建开局库数据结构:**
```c
// 开局库项
typedef struct {
int moves[20][2]; // 开局移动序列
int move_count; // 移动数量
int win_rate; // 胜率
char name[50]; // 开局名称
} OpeningEntry;
// 开局库
OpeningEntry opening_book[] = {
// 天元开局
{{{7,7}, {6,6}, {8,8}, {6,8}, {8,6}}, 5, 65, "天元开局"},
// 花月开局
{{{7,7}, {6,7}, {8,7}, {7,6}, {7,8}}, 5, 70, "花月开局"},
// 更多开局...
};
```
#### 3.3 改进的评估函数
**实现更复杂的位置评估:**
```c
// 高级评估函数
int advanced_evaluate_pos(int x, int y, int player) {
int score = 0;
// 1. 基础棋型评分
score += basic_pattern_score(x, y, player);
// 2. 组合棋型评分
score += combination_pattern_score(x, y, player);
// 3. 位置价值评分
score += positional_value_score(x, y);
// 4. 威胁序列评分
score += threat_sequence_score(x, y, player);
// 5. 防守价值评分
score += defensive_value_score(x, y, player);
return score;
}
```
### 4. 长期改进(3-6月,难度:⭐⭐⭐⭐)
#### 4.1 蒙特卡洛树搜索(MCTS
**MCTS节点结构:**
```c
// MCTS节点
typedef struct MCTSNode {
int x, y; // 移动位置
int visits; // 访问次数
double wins; // 胜利次数
struct MCTSNode *parent; // 父节点
struct MCTSNode **children; // 子节点数组
int child_count; // 子节点数量
bool fully_expanded; // 是否完全展开
} MCTSNode;
// MCTS主函数
int mcts_search(int simulations) {
MCTSNode *root = create_node(-1, -1, NULL);
for (int i = 0; i < simulations; i++) {
MCTSNode *node = selection(root);
node = expansion(node);
double result = simulation(node);
backpropagation(node, result);
}
return best_child(root);
}
```
#### 4.2 神经网络集成
**神经网络评估接口:**
```c
// 神经网络评估函数
float neural_network_evaluate(int board[BOARD_SIZE][BOARD_SIZE]) {
// 调用训练好的神经网络模型
// 返回位置评估值
// ...
}
// 混合评估函数
int hybrid_evaluate(int x, int y, int player) {
// 传统评估
int traditional_score = evaluate_pos(x, y, player);
// 神经网络评估
float nn_score = neural_network_evaluate(board);
// 加权组合
return (int)(0.7 * traditional_score + 0.3 * nn_score * 10000);
}
```
## 📊 性能优化策略
### 1. 搜索优化
#### 1.1 迭代加深搜索
```c
int iterative_deepening_search(int max_depth, int time_limit) {
int best_move = -1;
clock_t start_time = clock();
for (int depth = 1; depth <= max_depth; depth++) {
if ((clock() - start_time) * 1000 / CLOCKS_PER_SEC > time_limit) {
break;
}
best_move = alpha_beta_search(depth);
}
return best_move;
}
```
#### 1.2 空窗搜索
```c
int null_window_search(int depth, int beta) {
return alpha_beta_search(depth, beta - 1, beta);
}
```
### 2. 内存优化
#### 2.1 位棋盘表示
```c
// 使用位操作优化棋盘表示
typedef struct {
uint64_t player1_board[4]; // 玩家1的棋盘(4个64位整数)
uint64_t player2_board[4]; // 玩家2的棋盘
} BitBoard;
```
## 🎮 实战策略
### 1. 自适应难度系统
```c
// 难度等级
typedef enum {
DIFFICULTY_EASY = 1,
DIFFICULTY_NORMAL = 2,
DIFFICULTY_HARD = 3,
DIFFICULTY_EXPERT = 4,
DIFFICULTY_MASTER = 5
} DifficultyLevel;
// 根据难度调整AI参数
void adjust_ai_parameters(DifficultyLevel level) {
switch (level) {
case DIFFICULTY_EASY:
ai_depth = 2;
defense_coefficient = 1.0;
break;
case DIFFICULTY_NORMAL:
ai_depth = 3;
defense_coefficient = 1.2;
break;
case DIFFICULTY_HARD:
ai_depth = 4;
defense_coefficient = 1.5;
break;
// ...
}
}
```
### 2. 学习系统
```c
// 对局记录
typedef struct {
int moves[MAX_STEPS][2];
int move_count;
int winner;
int ai_mistakes;
float game_quality;
} GameRecord;
// 学习函数
void learn_from_game(GameRecord *record) {
// 分析对局,更新评估参数
// 识别AI的错误决策
// 调整相关参数
}
```
## 📈 测试与评估
### 1. 性能测试
```c
// 性能测试函数
void performance_test() {
clock_t start = clock();
// 执行1000次AI决策
for (int i = 0; i < 1000; i++) {
ai_move(DEFAULT_AI_DEPTH);
// 重置棋盘
}
clock_t end = clock();
double time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("平均决策时间: %.2f ms\n", time_taken);
}
```
### 2. 棋力测试
```c
// 自我对弈测试
int self_play_test(int games) {
int wins = 0;
for (int i = 0; i < games; i++) {
// AI vs AI(不同版本或参数)
int result = play_game();
if (result == 1) wins++;
}
return (wins * 100) / games; // 胜率
}
```
## 🚀 实施路线图
### 阶段1:基础优化(1周)
- [ ] 调整搜索深度和评分参数
- [ ] 优化移动排序
- [ ] 增强威胁检测
### 阶段2:算法改进(2-4周)
- [ ] 实现置换表
- [ ] 添加开局库
- [ ] 改进评估函数
### 阶段3:高级功能(1-2月)
- [ ] 实现MCTS
- [ ] 添加学习机制
- [ ] 性能优化
### 阶段4AI增强(2-3月)
- [ ] 神经网络集成
- [ ] 并行搜索
- [ ] 完整的自适应系统
## 📚 参考资源
### 算法资料
- 《人工智能:一种现代方法》- Stuart Russell
- 《游戏编程中的人工智能技术》- Mat Buckland
- AlphaGo论文系列
### 开源项目
- Stockfish(国际象棋引擎)
- Leela Zero(围棋AI
- Gomoku AI项目
### 在线资源
- Chess Programming Wiki
- Computer Olympiad
- AI游戏编程社区
## 🏗️ 代码架构优化 (v7.0新增)
### 配置管理统一化
在v7.0版本中,我们完成了重要的代码架构重构:
#### 配置参数集中管理
- **统一配置文件**:所有AI相关参数现在集中在`config.h`中定义
- **参数分类管理**:AI参数按功能分组(搜索深度、评分权重、时间限制等)
- **配置文件支持**AI参数可通过`gobang_config.ini`文件动态调整
- **运行时修改**:支持游戏过程中实时调整AI难度和参数
#### 代码模块化优化
- **清晰的模块分离**:AI逻辑与游戏逻辑完全分离
- **接口标准化**:统一的AI接口设计,便于算法替换和升级
- **全局变量管理**:AI相关全局变量集中在`globals`模块中
- **类型定义统一**:所有数据结构定义集中在`type.h`
#### 维护性提升
- **宏定义优化**:消除重复定义,提高代码一致性
- **注释规范化**:完善的代码注释和文档
- **错误处理统一**:标准化的错误处理机制
- **调试支持增强**:更好的调试信息和日志记录
这些架构优化为后续的AI算法改进奠定了坚实的基础,使得实施复杂的AI增强方案变得更加容易和可靠。
## 💡 总结
通过系统性的改进,可以将当前的五子棋AI从业余水平提升到接近专业水平。关键是要循序渐进,先实施简单的改进,再逐步引入复杂的算法。每个阶段都要进行充分的测试和评估,确保改进的有效性。
建议按照优先级顺序实施:
1. **短期目标**:参数调优 + 开局库 + 棋型优化
2. **中期目标**:搜索算法优化 + 评估函数改进
3. **长期目标**:机器学习集成 + MCTS实现
v7.0的架构重构为所有这些改进提供了更好的代码基础。
记住:**好的AI不仅要算得深,更要算得准!**
## 🎯 项目优势
### ✅ 已经做得很好的方面
- 📚 代码质量优秀 :注释覆盖率30.1%,使用Doxygen格式文档
- 🏗️ 架构设计合理 :模块化设计,职责分离清晰
- 🎮 功能完整丰富 :支持人机对战、双人对战、网络对战、GUI界面
- 🔧 技术栈现代化 :SDL3图形库、双版本架构、专业安装包
- 📋 文档完善 :详细的README、AI增强指南、架构重构指南
- 🌐 跨平台考虑 :已有条件编译支持Windows/Linux
## 🚀 改进建议
### 1. 🧪 测试体系建设(高优先级)
当前状况 :项目缺乏自动化测试
建议改进
- 添加单元测试框架(如Unity或自制简单测试框架)
- 为核心模块编写测试用例:AI算法、棋盘逻辑、网络通信
- 添加集成测试验证各模块协作
- 创建性能基准测试
### 2. 🛡️ 错误处理和安全性增强(高优先级
当前状况 :基础错误处理存在,但可以更完善
建议改进
- 增强输入验证,防止缓冲区溢出
- 添加网络通信的安全验证机制
- 完善内存管理,添加内存泄漏检测
- 增加异常恢复机制
### 3. 🎯 AI算法优化(中优先级)
当前状况 :基础Minimax+α-β剪枝,搜索深度3层
建议改进
- 实现置换表缓存系统
- 添加开局库和残局库
- 实现迭代加深搜索
- 增加威胁检测优化
- 支持多线程并行搜索
### 4. 🔧 构建和部署优化(中优先级)
当前状况 :编译脚本功能完善,但可以更自动化
建议改进
- 添加CMake构建系统支持
- 实现持续集成/持续部署(CI/CD)
- 添加依赖管理和版本控制
- 完善跨平台构建脚本
### 5. 📊 性能监控和分析(中优先级)
建议添加
- AI决策时间统计
- 内存使用监控
- 网络延迟分析
- 性能瓶颈识别工具
### 6. 🎮 用户体验提升(低优先级)
建议改进
- 添加音效和动画效果
- 实现主题和皮肤系统
- 增加游戏统计和成就系统
- 支持自定义快捷键
### 7. 🌐 网络功能增强(低优先级)
建议改进
- 实现房间系统和匹配机制
- 添加观战功能
- 支持断线重连
- 实现聊天系统
## 📋 具体实施建议
### 立即可实施(1-2周)
1. 1.
创建基础测试框架
2. 2.
添加输入验证和边界检查
3. 3.
完善错误日志记录
4. 4.
优化编译警告处理
### 短期目标(1-2月)
1. 1.
完善单元测试覆盖
2. 2.
实现置换表和开局库
3. 3.
添加性能监控
4. 4.
增强网络安全性
### 长期规划(3-6月)
1. 1.
实现神经网络AI
2. 2.
完整跨平台支持
3. 3.
添加数据库支持
4. 4.
实现云端对战
## 🏆 总体评价
你的项目已经达到了 专业级别的开源项目标准 !代码架构清晰、功能完整、文档详尽。主要的改进空间在于测试覆盖和一些高级特性的添加。
项目亮点
- 从v7.0的架构重构到v8.2的编译优化,版本迭代思路清晰
- SDL3图形化界面实现现代化
- 网络对战功能完整
- 代码注释和文档非常专业
这是一个非常值得继续完善和推广的优秀项目!🎉
+1 -1
View File
@@ -1,4 +1,4 @@
# 🧠 五子棋AI实现详解 (v8.0)
# 🧠 五子棋AI实现详解
## 📜 算法概述
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。
+1 -1
View File
@@ -1,4 +1,4 @@
# 五子棋项目代码架构重构指南 (v8.0)
# 五子棋项目代码架构重构指南
## 📋 概述
+73
View File
@@ -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
```
+7 -23
View File
@@ -1,14 +1,14 @@
# 五子棋网络对战使用说明 (v8.0)
# 五子棋网络对战使用说明
## 功能概述
本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。v8.0版本新增了GUI界面的网络对战支持,提供更直观的可视化网络游戏体验。
## v8.0网络功能增强
- **GUI网络对战**:图形化界面支持网络游戏
- **可视化连接状态**:实时显示网络连接状态
- **双版本网络支持**:控制台和GUI版本均支持网络功能
- **网络状态指示**:图形化显示连接、等待、游戏状态
### v8.2 (2025-10-08)
- 专业安装包制作 - 支持Inno Setup和NSIS双重安装方案
- 安装程序优化 - 完整的文件打包和快捷方式创建
- 分发体系完善 - 提供标准化的软件分发解决方案
- 用户体验提升 - 一键安装部署,支持完整卸载功能
## 编译方法
@@ -17,7 +17,7 @@
gcc -std=c17 -o gobang.exe *.c -lws2_32
```
### GUI版本v8.0新增)
### 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" .
@@ -143,23 +143,7 @@ telnet <对方IP地址> <端口号>
- 游戏结束后及时关闭程序
- 注意保护个人网络信息
## 更新日志
### v7.0 (2025-07-20)
- 网络配置参数统一管理 - 所有网络相关配置集中到config.h
- 消息类型定义优化 - 统一消息协议宏定义
- 代码架构重构 - 提升网络模块的可维护性
- 配置文件支持 - 网络参数可通过配置文件调整
### v6.1 (2025-07-10)
- 完善网络对战功能
- 支持TCP/IP通信
- 实现棋盘同步
- 连接状态和协议优化
- 支持多种操作
- 添加延时控制等游戏功能(延时显示、认输、悔棋等)
---
**开发者:** 刘航宇
**联系邮箱:** 3364451258@qq.com
+80
View File
@@ -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
+15 -24
View File
@@ -2,39 +2,26 @@
![Build Status](https://img.shields.io/badge/build-passing-brightgreen)
![License](https://img.shields.io/badge/license-MIT-blue)
![Version](https://img.shields.io/badge/version-v8.0-blue)
![Version](https://img.shields.io/badge/version-v8.2-blue)
![Platform](https://img.shields.io/badge/platform-Windows-lightgrey)
> 🎯 **最新版本 v8.0** - GUI图形化界面更新,实现SDL3图形化界面、现代化用户体验、可视化棋盘操作等重大功能升级
> 🎯 **最新版本 v8.2** - 编译脚本优化更新,完善批处理脚本功能、增强用户编译体验、优化开发工作流程
## 📋 大版本更新
### v8.0 (2025-01-18) - GUI图形化界面更新
- 🖥️ **SDL3图形化界面** - 全新的现代化图形用户界面
- 🎮 **可视化棋盘操作** - 支持鼠标点击落子和直观的棋盘显示
- 🎨 **现代化UI设计** - 美观的界面布局和用户体验优化
### v8.2 (2025-01-20) - 编译脚本优化更新
- 🔧 **交互式编译脚本** - compile_gui.bat支持选择编译控制台或GUI版本
- 📋 **用户友好界面** - 提供清晰的编译选项菜单和操作指引
- **编译流程优化** - 统一编译参数,解决SDL3依赖问题
- 🌐 **多语言支持** - 英文界面避免编码问题,确保兼容性
- 📦 **安装包制作** - 提供Inno Setup和NSIS两种安装包方案
- 🔧 **双版本支持** - 同时支持控制台版本和GUI版本
- 🎯 **窗口管理优化** - 完善的窗口显示和事件处理机制
- 📱 **响应式界面** - 自适应窗口大小和分辨率
- 🚀 **性能优化** - 图形渲染和事件处理性能提升
### v7.0 (2025-07-20) - 代码架构重构更新
- 🏗️ **结构体定义集中化** - 所有数据结构统一管理在type.h中
- ⚙️ **配置参数统一管理** - 所有配置宏定义集中在config.h中
- 🔧 **代码模块化优化** - 消除重复定义,提高代码可维护性
- 📋 **菜单选项优化** - 退出选项调整为"0. 退出游戏"
- 🎯 **类型系统完善** - 独立的type.h文件管理所有数据类型
- 🌐 **网络配置重构** - 网络相关宏定义统一到config.h
- 📊 **全局变量管理** - 优化全局变量声明和定义结构
- 🔄 **头文件依赖优化** - 改进模块间依赖关系和包含结构
- **错误处理完善** - 添加无效输入处理和详细错误提示
- 🎯 **开发体验提升** - 简化编译流程,提高开发效率
## 目录
- [C语言五子棋人机对战AI](#c语言五子棋人机对战ai)
- [📋 大版本更新](#-大版本更新)
- [v8.0 (2025-01-18) - GUI图形化界面更新](#v80-2025-01-18---gui图形化界面更新)
- [v7.0 (2025-07-20) - 代码架构重构更新](#v70-2025-07-20---代码架构重构更新)
- [v8.2 (2025-01-20) - 编译脚本优化更新](#v82-2025-01-20---编译脚本优化更新)
- [目录](#目录)
- [项目简介](#项目简介)
- [功能特性](#功能特性)
@@ -45,7 +32,11 @@
- [🔧 技术特性](#-技术特性)
- [快速开始](#快速开始)
- [编译项目](#编译项目)
- [控制台版本编译](#控制台版本编译)
- [GUI版本编译(需要SDL3](#gui版本编译需要sdl3)
- [运行游戏](#运行游戏)
- [控制台版本](#控制台版本)
- [GUI版本](#gui版本)
- [游戏玩法](#游戏玩法)
- [🚀 快速开始](#-快速开始)
- [🎯 对局操作](#-对局操作)
@@ -352,4 +343,4 @@ chcp 65001
#### 🔧 技术优化
- [ ] **跨平台支持**:完整支持Linux和macOS系统
- [ ] **性能优化**:多线程搜索和内存优化
- [ ] **数据库支持**:使用SQLite存储对局历史和统计
- [ ] **数据库支持**:使用SQLite存储对局历史和统计
+14 -6
View File
@@ -2,8 +2,8 @@
========================================
项目名称:五子棋多模式对战系统
统计时间:2025年9月18日
项目版本:v8.0
统计时间:2025年10月8日
项目版本:v8.2
开发语言:C语言 + SDL3图形库
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
@@ -111,7 +111,9 @@ v8.0版本新增:
• SDL3图形化界面实现(v8.0新增)
• 双版本架构设计(控制台+GUI)(v8.0新增)
• 鼠标交互和事件驱动架构(v8.0新增)
• 专业安装包制作支持(v8.0新增)
• 专业安装包制作支持(v8.2新增)
• Inno Setup和NSIS双重打包方案(v8.2新增)
• 完整的软件分发体系(v8.2新增)
• 完整的网络对战功能实现
• 智能AI算法与评估系统
• 灵活的配置管理系统
@@ -120,9 +122,9 @@ v8.0版本新增:
• 实时计时器系统
• 全局变量统一管理
• 跨平台网络通信支持
• 代码架构模块化重构v7.0新增)
• 配置参数集中化管理v7.0新增)
• 类型定义标准化v7.0新增)
• 代码架构模块化重构
• 配置参数集中化管理
• 类型定义标准化
【总体评价】
这是一个非常优秀的C语言项目,代码量适中但功能完整,
@@ -132,6 +134,12 @@ v8.0版本新增:
包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
是C语言项目开发的优秀范例。
v8.2版本进一步完善了软件分发体系,
通过Inno Setup和NSIS双重安装包方案,
实现了专业级的软件打包和部署功能,
为用户提供了便捷的一键安装体验,
标志着项目从开发阶段向产品化阶段的重要转变。
v8.0版本的图形化界面是项目发展的重大突破,
通过SDL3图形库实现了现代化的可视化界面,
支持鼠标交互操作,大幅提升了用户体验。
+11 -4
View File
@@ -3,16 +3,23 @@
* @brief C语言五子棋多模式对战系统
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统,v8.0新增SDL3图形化界面
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
* @date 2025-09-18
* @version 8.0
* @date 2025-10-8
* @version 8.2
* @note
* 1. v8.0图形化界面
* 1. v8.2专业安装包
* - 📦 双重打包方案:支持Inno Setup和NSIS两种安装包制作
* - 🚀 一键安装部署:完整的软件分发解决方案
* - 🔧 安装程序优化:自动创建快捷方式和注册表项
* - 🗂️ 完整文件打包:包含所有源码、文档和依赖文件
* - 🔄 完整卸载功能:支持干净的软件卸载和清理
* - 💼 产品化部署:从开发工具向商业软件的转变
*
* 2. v8.0图形化界面:
* - 🎨 SDL3图形化界面:实现现代化可视化棋盘界面
* - 🖱️ 鼠标交互支持:直观的点击落子操作
* - 🏗️ 双版本架构:控制台版本和GUI版本并行支持
* - 🪟 窗口管理优化:自动居中、响应式设计
* - ⚡ 事件驱动架构:流畅的用户交互体验
* - 📦 安装包支持:提供Inno Setup专业安装程序
* - 🔧 编译脚本优化:简化GUI版本编译流程
* - 🌐 GUI网络支持:图形化界面支持网络对战
* 2. v7.0架构重构:
+3 -1
View File
@@ -1,8 +1,10 @@
项目要求文档 - 五子棋游戏 (v8.0)
项目要求文档 - 五子棋游戏 (v8.2)
1. 项目概述
- 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。
- v8.0版本实现双版本架构:支持命令行界面和SDL3图形化界面。
- v8.2版本(当前版本)完善了专业安装包制作体系,提供企业级的软件分发解决方案。
- v8.2版本完善了软件分发体系,支持Inno Setup和NSIS双重安装包制作,提供专业级的软件打包和部署解决方案。
- 包括游戏配置、记录保存、复盘功能和专业安装包。
- 提供现代化的可视化用户体验和传统控制台体验。
+99
View File
@@ -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
-36
View File
@@ -1,36 +0,0 @@
@echo off
chcp 65001 >nul
echo Compiling Gobang 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\
pause
exit /b 1
)
REM Compile GUI version
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
if %ERRORLEVEL% EQU 0 (
echo.
echo Compilation successful! Generated: gobang_gui.exe
echo.
echo Copy SDL3.dll to current directory...
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
echo.
echo Run GUI version:
echo .\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.
)
pause
-35
View File
@@ -1,35 +0,0 @@
/**
* @file globals.c
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
* @brief 全局变量定义和初始化文件
* @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] = "平局或未完成"; // 存储胜负信息
-334
View File
@@ -1,334 +0,0 @@
/**
* @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>
// 全局变量
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
int gui_running = 1;
int current_player_gui = PLAYER;
int game_over = 0;
char status_message[256] = "五子棋游戏 - 黑子先行";
/**
* @brief 初始化GUI
* @return 成功返回0,失败返回-1
*/
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资源
*/
void cleanup_gui() {
if (renderer) {
SDL_DestroyRenderer(renderer);
renderer = NULL;
}
if (window) {
SDL_DestroyWindow(window);
window = NULL;
}
SDL_Quit();
printf("图形化界面已关闭\n");
}
/**
* @brief 渲染游戏画面
*/
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 处理事件
* @return 继续运行返回1,退出返回0
*/
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 绘制棋盘
*/
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, &center_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 绘制棋子
*/
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 颜色
*/
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
*/
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 要显示的消息
*/
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);
}
-50
View File
@@ -1,50 +0,0 @@
/**
* @file gui.h
* @brief 图形化用户界面头文件
* @note 使用SDL3库实现五子棋的图形化界面
* @author 刘航宇
* @date 2025-01-15
*/
#ifndef GUI_H
#define GUI_H
#include <SDL3/SDL.h>
#include "gobang.h"
// 窗口和棋盘配置
#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}
// GUI函数声明
int init_gui();
void cleanup_gui();
void render_game();
int handle_events();
void draw_board();
void draw_stones();
void draw_ui_elements();
void draw_circle(int center_x, int center_y, int radius, SDL_Color color);
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
void show_message(const char *message);
// 全局GUI变量
extern SDL_Window *window;
extern SDL_Renderer *renderer;
extern int gui_running;
extern int current_player_gui;
extern int game_over;
extern char status_message[256];
#endif // GUI_H
View File
+17 -1
View File
@@ -116,7 +116,23 @@
#define TIME_WEIGHT_FACTOR 0.5 // 时间权重因子
#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 CONFIG_FILE "gobang_config.ini" // 配置文件路径
#define MAX_PATH_LENGTH 256 // 最大路径长度
+16 -1
View File
@@ -86,8 +86,23 @@ bool handle_network_player_turn(int current_player, bool is_local_turn);
/**
* @brief
* @return true
* @return false 退
* @return false
*/
bool network_game_loop();
/**
* @brief
*/
void show_game_rules();
/**
* @brief
*/
void show_about_game();
/**
* @brief
*/
void run_gui_mode();
#endif // GAME_MODE_H
+9
View File
@@ -11,6 +11,7 @@
#include "gobang.h"
#include "network.h"
#include <stdbool.h>
#include <SDL3/SDL.h>
// ==================== 游戏核心变量 ====================
extern int BOARD_SIZE; // 当前实际使用的棋盘尺寸
@@ -32,6 +33,14 @@ extern double defense_coefficient; // 防守系数
// ==================== 网络相关变量 ====================
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 player2_final_score; // 玩家2最终得分
View File
+154
View File
@@ -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
View File
View File
View File
View File
+56
View File
@@ -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"
+149 -46
View File
@@ -1,59 +1,162 @@
; NSIS Installation Script
; 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"
; Basic Information
Name "Gobang Game"
OutFile "..\\Gobang_Setup.exe"
InstallDir "$PROGRAMFILES\Gobang"
RequestExecutionLevel admin
; Interface Settings
; 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"
; Installation Pages
; 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
; Language Settings
; Uninstall page
!insertmacro MUI_UNPAGE_INSTFILES
; Language files
!insertmacro MUI_LANGUAGE "SimpChinese"
; Installation Section
Section "Main"
SetOutPath "$INSTDIR"
; Copy configuration and documentation files
File "..\\gobang_config.ini"
File "..\\README.md"
; Copy GUI executable file if exists
IfFileExists "..\\gobang_gui.exe" 0 +2
File "..\\gobang_gui.exe"
; Copy SDL3 library if exists
IfFileExists "..\\SDL3.dll" 0 +2
File "..\\SDL3.dll"
; Create program group directory
CreateDirectory "$SMPROGRAMS\Gobang"
; Create shortcuts (only if executable exists)
IfFileExists "$INSTDIR\gobang_gui.exe" 0 +3
CreateShortCut "$DESKTOP\Gobang.lnk" "$INSTDIR\gobang_gui.exe"
CreateShortCut "$SMPROGRAMS\Gobang\Gobang.lnk" "$INSTDIR\gobang_gui.exe"
; Write uninstall information
WriteUninstaller "$INSTDIR\Uninstall.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gobang" \
"DisplayName" "Gobang Game"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gobang" \
"UninstallString" "$\"$INSTDIR\Uninstall.exe$\""
; 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
; Uninstall Section
Section "Uninstall"
RMDir /r "$INSTDIR"
Delete "$DESKTOP\Gobang.lnk"
RMDir /r "$SMPROGRAMS\Gobang"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gobang"
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
-55
View File
@@ -1,55 +0,0 @@
; Inno Setup Script for Gobang Game
; Generated by Inno Setup Script Wizard
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
AppId={{A92AAE42-5C7E-4C8E-9F2B-8D4E5F6A7B8C}
AppName=Gobang Game
AppVersion=1.0
AppPublisher=Gobang Game Developer
DefaultDirName={autopf}\Gobang
DefaultGroupName=Gobang Game
AllowNoIcons=yes
OutputDir=..
OutputBaseFilename=Gobang_Setup
SetupIconFile=compiler:SetupClassicIcon.ico
Compression=lzma
SolidCompression=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files]
; GUI可执行文件
Source: "..\\gobang_gui.exe"; DestDir: "{app}"; Flags: ignoreversion
; SDL3动态库
Source: "..\\SDL3.dll"; DestDir: "{app}"; Flags: ignoreversion
; 配置文件
Source: "..\\gobang_config.ini"; DestDir: "{app}"; Flags: ignoreversion
; 指定的TXT文件
Source: "..\\TXT\\*"; DestDir: "{app}\TXT"; Flags: ignoreversion
; 文档文件
Source: "..\\README.md"; DestDir: "{app}"; Flags: ignoreversion
[Dirs]
; 创建空的records目录(不包含存档文件)
Name: "{app}\records"
[Icons]
Name: "{group}\Gobang Game"; Filename: "{app}\gobang_gui.exe"
Name: "{group}\{cm:UninstallProgram,Gobang Game}"; Filename: "{uninstallexe}"
Name: "{autodesktop}\Gobang Game"; Filename: "{app}\gobang_gui.exe"; Tasks: desktopicon
[Run]
Filename: "{app}\gobang_gui.exe"; Description: "{cm:LaunchProgram,Gobang Game}"; Flags: nowait postinstall skipifsilent
-9
View File
@@ -1,9 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,游戏结果
1,15,289,757,玩家2获胜
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,3,3
3,2,4,4
4,1,5,5
5,2,4,5
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,游戏结果
2 1,15,289,757,玩家2获胜
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,3,3
6 3,2,4,4
7 4,1,5,5
8 5,2,4,5
-18
View File
@@ -1,18 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分
1,15,1581,33119,玩家1得分
步数,玩家,行坐标,列坐标
1,1,2,2
2,2,3,3
3,1,2,3
4,2,2,4
5,1,4,4
6,2,4,2
7,1,5,5
8,2,1,5
9,1,6,6
10,2,7,7
11,1,7,6
12,2,5,6
13,1,9,9
14,2,5,1
1 游戏模式,棋盘大小,玩家1得分,玩家2得分
2 1,15,1581,33119,玩家1得分
3 步数,玩家,行坐标,列坐标
4 1,1,2,2
5 2,2,3,3
6 3,1,2,3
7 4,2,2,4
8 5,1,4,4
9 6,2,4,2
10 7,1,5,5
11 8,2,1,5
12 9,1,6,6
13 10,2,7,7
14 11,1,7,6
15 12,2,5,6
16 13,1,9,9
17 14,2,5,1
-35
View File
@@ -1,35 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分
1,15,5985,7221,玩家1得分
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,4,4
3,2,5,5
4,1,7,7
5,2,7,8
6,1,3,3
7,2,6,8
8,1,5,8
9,2,9,8
10,1,10,8
11,2,8,7
12,1,8,6
13,2,9,7
14,1,9,6
15,2,7,6
16,1,6,5
17,2,10,9
18,1,6,4
19,2,5,4
20,1,6,3
21,2,6,2
22,1,6,6
23,2,6,7
24,1,5,6
25,2,4,7
26,1,5,7
27,2,4,8
28,1,5,9
29,2,5,10
30,1,1,1
31,2,11,10
1 游戏模式,棋盘大小,玩家1得分,玩家2得分
2 1,15,5985,7221,玩家1得分
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,4,4
6 3,2,5,5
7 4,1,7,7
8 5,2,7,8
9 6,1,3,3
10 7,2,6,8
11 8,1,5,8
12 9,2,9,8
13 10,1,10,8
14 11,2,8,7
15 12,1,8,6
16 13,2,9,7
17 14,1,9,6
18 15,2,7,6
19 16,1,6,5
20 17,2,10,9
21 18,1,6,4
22 19,2,5,4
23 20,1,6,3
24 21,2,6,2
25 22,1,6,6
26 23,2,6,7
27 24,1,5,6
28 25,2,4,7
29 26,1,5,7
30 27,2,4,8
31 28,1,5,9
32 29,2,5,10
33 30,1,1,1
34 31,2,11,10
-22
View File
@@ -1,22 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
1,15,5155,2527,玩家获胜
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,4,4
3,2,5,5
4,1,6,6
5,2,5,6
6,1,7,7
7,2,5,7
8,1,5,4
9,2,3,4
10,1,6,4
11,2,6,5
12,1,7,4
13,2,8,4
14,1,7,5
15,2,5,3
16,1,7,6
17,2,7,3
18,1,7,8
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 1,15,5155,2527,玩家获胜
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,4,4
6 3,2,5,5
7 4,1,6,6
8 5,2,5,6
9 6,1,7,7
10 7,2,5,7
11 8,1,5,4
12 9,2,3,4
13 10,1,6,4
14 11,2,6,5
15 12,1,7,4
16 13,2,8,4
17 14,1,7,5
18 15,2,5,3
19 16,1,7,6
20 17,2,7,3
21 18,1,7,8
-15
View File
@@ -1,15 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,2898,5601,玩家1获胜
步数,玩家,行坐标,列坐标
1,1,1,1
2,2,2,2
3,1,3,3
4,2,1,2
5,1,4,4
6,2,1,3
7,1,5,5
8,2,1,4
9,1,6,6
10,2,1,5
11,1,7,7
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,2898,5601,玩家1获胜
3 步数,玩家,行坐标,列坐标
4 1,1,1,1
5 2,2,2,2
6 3,1,3,3
7 4,2,1,2
8 5,1,4,4
9 6,2,1,3
10 7,1,5,5
11 8,2,1,4
12 9,1,6,6
13 10,2,1,5
14 11,1,7,7
-7
View File
@@ -1,7 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,135,452,平局或未完成
步数,玩家,行坐标,列坐标
1,2,3,3
2,1,4,4
3,2,4,3
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,135,452,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,2,3,3
5 2,1,4,4
6 3,2,4,3
-6
View File
@@ -1,6 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,110,212,平局或未完成
步数,玩家,行坐标,列坐标
1,1,3,3
2,2,6,6
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,110,212,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,1,3,3
5 2,2,6,6
-6
View File
@@ -1,6 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2,15,110,212,平局或未完成
步数,玩家,行坐标,列坐标
1,1,3,3
2,2,6,6
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 2,15,110,212,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,1,3,3
5 2,2,6,6
-14
View File
@@ -1,14 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
3,15,13500,1039,平局或未完成
步数,玩家,行坐标,列坐标
1,1,4,4
2,2,5,5
3,1,6,6
4,2,7,7
5,1,5,4
6,2,4,5
7,1,6,5
8,2,5,6
9,1,6,3
10,1,6,4
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 3,15,13500,1039,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,1,4,4
5 2,2,5,5
6 3,1,6,6
7 4,2,7,7
8 5,1,5,4
9 6,2,4,5
10 7,1,6,5
11 8,2,5,6
12 9,1,6,3
13 10,1,6,4
-5
View File
@@ -1,5 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
3,15,0,130,平局或未完成
步数,玩家,行坐标,列坐标
1,2,4,4
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 3,15,0,130,平局或未完成
3 步数,玩家,行坐标,列坐标
4 1,2,4,4
-28
View File
@@ -1,28 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
1,20,4575,8652,玩家获胜
步数,玩家,行坐标,列坐标
1,2,11,11
2,1,3,3
3,2,4,4
4,1,5,5
5,2,4,5
6,1,4,6
7,2,3,7
8,1,4,7
9,2,4,8
10,1,2,6
11,2,3,6
12,1,5,9
13,2,3,8
14,1,5,8
15,2,5,7
16,1,6,9
17,2,4,9
18,1,3,9
19,2,7,10
20,1,5,10
21,2,4,11
22,1,5,11
23,2,4,10
24,1,5,12
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 1,20,4575,8652,玩家获胜
3 步数,玩家,行坐标,列坐标
4 1,2,11,11
5 2,1,3,3
6 3,2,4,4
7 4,1,5,5
8 5,2,4,5
9 6,1,4,6
10 7,2,3,7
11 8,1,4,7
12 9,2,4,8
13 10,1,2,6
14 11,2,3,6
15 12,1,5,9
16 13,2,3,8
17 14,1,5,8
18 15,2,5,7
19 16,1,6,9
20 17,2,4,9
21 18,1,3,9
22 19,2,7,10
23 20,1,5,10
24 21,2,4,11
25 22,1,5,11
26 23,2,4,10
27 24,1,5,12
-21
View File
@@ -1,21 +0,0 @@
游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
1,15,1478,3769,AI获胜
步数,玩家,行坐标,列坐标
1,2,8,8
2,1,3,3
3,2,4,4
4,1,3,4
5,2,3,5
6,1,5,3
7,2,4,3
8,1,4,5
9,2,5,6
10,1,6,6
11,2,3,2
12,1,5,5
13,2,5,4
14,1,7,7
15,2,6,5
16,1,1,1
17,2,7,6
1 游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果
2 1,15,1478,3769,AI获胜
3 步数,玩家,行坐标,列坐标
4 1,2,8,8
5 2,1,3,3
6 3,2,4,4
7 4,1,3,4
8 5,2,3,5
9 6,1,5,3
10 7,2,4,3
11 8,1,4,5
12 9,2,5,6
13 10,1,6,6
14 11,2,3,2
15 12,1,5,5
16 13,2,5,4
17 14,1,7,7
18 15,2,6,5
19 16,1,1,1
20 17,2,7,6
+56 -56
View File
@@ -69,7 +69,7 @@ int evaluate_pos(int x, int y, int player)
// 直接形成五连珠为必胜
if (info.continuous_chess >= 5)
{
board[x][y] = original; // 还原棋盘
board[x][y] = original; // 还原棋盘
return SEARCH_WIN_BONUS; // 返回最大分
}
@@ -130,8 +130,8 @@ int evaluate_pos(int x, int y, int player)
// 位置奖励:越靠近中心分数越高
int center_x = BOARD_SIZE / 2;
int center_y = BOARD_SIZE / 2;
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
board[x][y] = original; // 还原棋盘状态
return total_score + position_bonus; // 返回总评估分
@@ -173,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];
int move_count = generate_candidate_moves(candidate_moves, player);
// 限制搜索的候选移动数量以提高性能
int max_candidates = (depth >= 3) ? 15 : 25; // 深度越大,候选移动越少
if (move_count > max_candidates)
@@ -250,13 +250,13 @@ void ai_move(int depth)
// 1. 使用增强的威胁检测系统
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
int move_count = generate_candidate_moves(candidate_moves, AI);
// 首先检查是否有直接获胜的机会
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel ai_threat = detect_threat(i, j, AI);
if (ai_threat == THREAT_WIN)
{
@@ -267,13 +267,13 @@ void ai_move(int depth)
return;
}
}
// 检查是否需要阻止玩家的威胁
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
if (player_threat >= THREAT_FOUR)
{
@@ -284,13 +284,13 @@ void ai_move(int depth)
return;
}
}
// 检查是否需要阻止玩家的活三威胁
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
if (player_threat == THREAT_THREE)
{
@@ -308,7 +308,7 @@ void ai_move(int depth)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel ai_threat = detect_threat(i, j, AI);
if (ai_threat >= THREAT_FOUR)
{
@@ -319,13 +319,13 @@ void ai_move(int depth)
return;
}
}
// 寻找能形成活三的位置
for (int idx = 0; idx < move_count; idx++)
{
int i = candidate_moves[idx].x;
int j = candidate_moves[idx].y;
ThreatLevel ai_threat = detect_threat(i, j, AI);
if (ai_threat == THREAT_THREE)
{
@@ -336,14 +336,14 @@ void ai_move(int depth)
return;
}
}
// 3. 如果没有明显的威胁机会,选择评分最高的位置
if (move_count > 0)
{
// candidate_moves已经按分数排序,直接选择第一个
int best_x = candidate_moves[0].x;
int best_y = candidate_moves[0].y;
board[best_x][best_y] = AI;
steps[step_count++] = (Step){AI, best_x, best_y};
printf("AI落子(%d, %d) - 最佳位置!\n", best_x + 1, best_y + 1);
@@ -388,7 +388,7 @@ static int compare_moves(const void *a, const void *b)
int generate_candidate_moves(ScoredMove *moves, int player)
{
int count = 0;
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
@@ -397,51 +397,51 @@ int generate_candidate_moves(ScoredMove *moves, int player)
{
continue;
}
// 只考虑有意义的位置(附近有棋子)
if (step_count > AI_SEARCH_RANGE_THRESHOLD && !is_near_stones(i, j))
{
continue;
}
// 计算该位置的评估分数
moves[count].x = i;
moves[count].y = j;
// 结合威胁检测和位置评估
ThreatLevel threat = detect_threat(i, j, player);
int base_score = evaluate_move(i, j);
// 根据威胁等级调整分数
switch (threat)
{
case THREAT_WIN:
moves[count].score = base_score + 10000;
break;
case THREAT_FOUR:
moves[count].score = base_score + 5000;
break;
case THREAT_THREE:
moves[count].score = base_score + 2000;
break;
case THREAT_DOUBLE:
moves[count].score = base_score + 1000;
break;
case THREAT_POTENTIAL:
moves[count].score = base_score + 500;
break;
default:
moves[count].score = base_score;
break;
case THREAT_WIN:
moves[count].score = base_score + 10000;
break;
case THREAT_FOUR:
moves[count].score = base_score + 5000;
break;
case THREAT_THREE:
moves[count].score = base_score + 2000;
break;
case THREAT_DOUBLE:
moves[count].score = base_score + 1000;
break;
case THREAT_POTENTIAL:
moves[count].score = base_score + 500;
break;
default:
moves[count].score = base_score;
break;
}
count++;
}
}
// 按分数降序排序
qsort(moves, count, sizeof(ScoredMove), compare_moves);
return count;
}
@@ -480,16 +480,16 @@ ThreatLevel detect_threat(int x, int y, int player)
{
// 模拟落子
board[x][y] = player;
ThreatLevel max_threat = THREAT_NONE;
int threat_count = 0;
// 检查四个方向
for (int k = 0; k < 4; k++)
{
DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player);
ThreatLevel current_threat = THREAT_NONE;
// 检查是否形成五子连珠(获胜)
if (info.continuous_chess >= 5)
{
@@ -517,27 +517,27 @@ ThreatLevel detect_threat(int x, int y, int player)
{
current_threat = THREAT_POTENTIAL;
}
if (current_threat > max_threat)
{
max_threat = current_threat;
}
if (current_threat >= THREAT_THREE)
{
threat_count++;
}
}
// 恢复棋盘
board[x][y] = EMPTY;
// 如果有多个威胁,提升威胁等级
if (threat_count >= 2 && max_threat >= THREAT_THREE)
{
max_threat = THREAT_DOUBLE;
}
return max_threat;
}
@@ -551,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 threats = 0;
// 向前搜索
for (int i = 1; i < 5; i++)
{
int nx = x + i * dx;
int ny = y + i * dy;
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
{
break;
}
if (board[nx][ny] == player)
{
threats++;
@@ -572,18 +572,18 @@ int count_threats_in_direction(int x, int y, int dx, int dy, int player)
break;
}
}
// 向后搜索
for (int i = 1; i < 5; i++)
{
int nx = x - i * dx;
int ny = y - i * dy;
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
{
break;
}
if (board[nx][ny] == player)
{
threats++;
@@ -593,6 +593,6 @@ int count_threats_in_direction(int x, int y, int dx, int dy, int player)
break;
}
}
return threats;
}
+37 -37
View File
@@ -24,13 +24,13 @@ void load_game_config()
printf("配置文件不存在,使用默认配置\n");
return;
}
char line[256];
while (fgets(line, sizeof(line), file))
{
// 去除换行符
line[strcspn(line, "\n")] = 0;
// 解析配置项
if (strncmp(line, "BOARD_SIZE=", 11) == 0)
{
@@ -78,7 +78,7 @@ void load_game_config()
}
}
}
fclose(file);
printf("配置加载完成\n");
}
@@ -94,7 +94,7 @@ void save_game_config()
printf("无法保存配置文件\n");
return;
}
fprintf(file, "# 五子棋游戏配置文件\n");
fprintf(file, "# 棋盘大小 (范围: %d-%d)\n", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
fprintf(file, "BOARD_SIZE=%d\n", BOARD_SIZE);
@@ -108,7 +108,7 @@ void save_game_config()
fprintf(file, "NETWORK_PORT=%d\n", network_port);
fprintf(file, "\n# 网络超时时间 (毫秒)\n");
fprintf(file, "NETWORK_TIMEOUT=%d\n", network_timeout);
fclose(file);
printf("配置保存完成\n");
}
@@ -124,7 +124,7 @@ void reset_to_default_config()
time_limit = DEFAULT_TIME_LIMIT;
network_port = DEFAULT_NETWORK_PORT;
network_timeout = NETWORK_TIMEOUT_MS;
printf("已重置为默认配置\n");
}
@@ -152,7 +152,7 @@ void display_current_config()
void config_board_size()
{
printf("\n当前棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
int new_size = get_integer_input("请输入新的棋盘大小: ", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
BOARD_SIZE = new_size;
printf("棋盘大小已设置为: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
@@ -164,7 +164,7 @@ void config_board_size()
void config_forbidden_moves()
{
printf("\n当前禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
int choice = get_integer_input("是否启用禁手规则?(1=开启, 0=关闭): ", 0, 1);
use_forbidden_moves = (choice != 0);
printf("禁手规则已%s\n", use_forbidden_moves ? "开启" : "关闭");
@@ -176,13 +176,13 @@ void config_forbidden_moves()
void config_timer()
{
printf("\n当前计时器: %s\n", use_timer ? "开启" : "关闭");
int choice = get_integer_input("是否启用计时器?(1=开启, 0=关闭): ", 0, 1);
use_timer = choice;
if (use_timer)
{
int new_limit = get_integer_input("请输入时间限制(分钟): ", 1, 999);
time_limit = new_limit * 60; // 转换为秒数存储
time_limit = new_limit * 60; // 转换为秒数存储
printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60);
}
else
@@ -199,11 +199,11 @@ void config_network()
printf("\n===== 网络配置 =====\n");
printf("当前网络端口: %d\n", network_port);
printf("当前网络超时: %d 毫秒\n", network_timeout);
int new_port = get_integer_input("请输入新的网络端口: ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
network_port = new_port;
printf("网络端口已设置为: %d\n", network_port);
int new_timeout = get_integer_input("请输入网络超时时间(毫秒, 建议1000-10000): ", 1000, 60000);
network_timeout = new_timeout;
printf("网络超时已设置为: %d 毫秒\n", network_timeout);
@@ -215,40 +215,40 @@ void config_network()
void config_management_menu()
{
int choice;
while (1)
{
clear_screen();
display_settings_menu();
display_current_config();
choice = get_integer_input("请选择操作(0-5): ", 0, 5);
switch (choice)
{
case 1:
config_board_size();
break;
case 2:
config_forbidden_moves();
break;
case 3:
config_timer();
break;
case 4:
config_network();
break;
case 5:
printf("AI难度设置功能开发中...\n");
break;
case 0:
save_game_config();
return;
default:
printf("无效的选择!\n");
break;
case 1:
config_board_size();
break;
case 2:
config_forbidden_moves();
break;
case 3:
config_timer();
break;
case 4:
config_network();
break;
case 5:
printf("AI难度设置功能开发中...\n");
break;
case 0:
save_game_config();
return;
default:
printf("无效的选择!\n");
break;
}
pause_for_input("按任意键继续...");
}
}
+109 -64
View File
@@ -16,6 +16,7 @@
#include "config.h"
#include "network.h"
#include "ui.h"
#include "gui.h"
#include "globals.h"
#include <stdio.h>
#include <time.h>
@@ -202,16 +203,16 @@ bool parse_network_player_input(int *x, int *y)
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
printf("发送悔棋请求给对方...\n");
if (send_undo_request(steps_to_undo))
{
printf("悔棋请求已发送,等待对方回应...\n");
// 等待对方回应
NetworkMessage msg;
time_t start_time = time(NULL);
while (difftime(time(NULL), start_time) < 30) // 30秒超时
{
if (receive_network_message(&msg, 1000))
@@ -239,14 +240,14 @@ bool parse_network_player_input(int *x, int *y)
}
}
}
if (!is_network_connected())
{
printf("网络连接断开\n");
return 0;
}
}
printf("悔棋请求超时,对方未回应。\n");
}
else
@@ -568,7 +569,7 @@ void run_network_game()
{
// 重置评分计算标志
scores_calculated = 0;
// 初始化网络模块
if (!init_network())
{
@@ -576,29 +577,30 @@ void run_network_game()
pause_for_input("按任意键返回主菜单...");
return;
}
printf("=== 网络对战模式 ===\n");
printf("1. 创建房间(作为主机)\n");
printf("2. 加入房间(连接到主机)\n");
int choice = get_integer_input("请选择模式(1-2): ", 1, 2);
bool connection_success = false;
if (choice == 1)
{
// 服务器模式
int port = get_integer_input("请输入监听端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0) port = network_port;
if (port == 0)
port = network_port;
printf("\n正在创建房间...\n");
connection_success = create_server(port);
}
}
else
{
// 客户端模式
char ip[MAX_IP_LENGTH];
// 循环直到输入有效的IP地址或用户选择退出
while (1)
{
@@ -607,10 +609,11 @@ void run_network_game()
{
printf("输入错误,请重新输入。\n");
// 清除输入缓冲区
while (getchar() != '\n');
while (getchar() != '\n')
;
continue;
}
// 检查是否要退出
if (strcmp(ip, "exit") == 0 || strcmp(ip, "EXIT") == 0)
{
@@ -619,14 +622,14 @@ void run_network_game()
pause_for_input("按任意键返回主菜单...");
return;
}
// 简单的IP地址格式验证
if (strlen(ip) < 7 || strlen(ip) > 15)
{
printf("IP地址格式错误!请输入有效的IP地址(如:192.168.1.100\n");
continue;
}
// 检查IP地址是否包含有效字符
bool valid_ip = true;
for (int i = 0; i < strlen(ip); i++)
@@ -637,26 +640,27 @@ void run_network_game()
break;
}
}
if (!valid_ip)
{
printf("IP地址格式错误!只能包含数字和点号。\n");
continue;
}
// 检查点号数量
int dot_count = 0;
for (int i = 0; i < strlen(ip); i++)
{
if (ip[i] == '.') dot_count++;
if (ip[i] == '.')
dot_count++;
}
if (dot_count != 3)
{
printf("IP地址格式错误!应包含3个点号(如:192.168.1.100\n");
continue;
}
printf("输入的IP地址: %s\n", ip);
int confirm = get_integer_input("确认连接到此IP?(1:是/0:否,重新输入): ", 0, 1);
if (confirm)
@@ -665,14 +669,15 @@ void run_network_game()
}
// 如果选择否,继续循环重新输入
}
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);
connection_success = connect_to_server(ip, port);
}
if (!connection_success)
{
printf("网络连接失败!\n");
@@ -680,16 +685,16 @@ void run_network_game()
pause_for_input("按任意键返回主菜单...");
return;
}
printf("\n网络连接成功!游戏即将开始...\n");
printf("你是玩家%d%s先手\n",
printf("你是玩家%d%s先手\n",
network_state.local_player_id,
network_state.local_player_id == PLAYER1 ? "" : "对方");
// 开始网络游戏
empty_board();
print_board();
if (network_game_loop())
{
printf("===== 游戏结束 =====\n");
@@ -700,12 +705,57 @@ void run_network_game()
{
printf("游戏因网络错误而结束\n");
}
// 清理网络连接
disconnect_network();
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
*/
@@ -716,17 +766,16 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
// 本地玩家回合
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n轮到你了,请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输: ", BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
@@ -737,10 +786,10 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
{
printf("\n你超时了,对方获胜!\n");
send_surrender(); // 发送认输消息
return 0; // 游戏结束
return 0; // 游戏结束
}
}
int parse_result = parse_network_player_input(&x, &y);
if (parse_result == 1) // 有效坐标输入
{
@@ -762,9 +811,10 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
continue;
}
}
x--; y--; // 转换为0-based坐标
x--;
y--; // 转换为0-based坐标
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
@@ -775,35 +825,34 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
// 继续循环,重新输入坐标
}
}
// 发送落子消息
if (!send_move(x, y, current_player))
{
printf("发送落子消息失败!\n");
return 0; // 游戏结束
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n你获胜了!\n");
return 0; // 游戏结束
}
}
else
{
// 等待对方落子
printf("\n等待对方落子...\n");
NetworkMessage msg;
time_t start_time = time(NULL);
while (1)
{
if (receive_network_message(&msg, 1000))
{
{
// 1秒超时
if (msg.type == MSG_MOVE && msg.player_id == current_player)
{
@@ -813,41 +862,37 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
printf("收到无效的落子坐标!\n");
return 0; // 游戏结束
}
printf("对方落子: (%d, %d)\n", msg.x + 1, msg.y + 1);
print_board();
if (check_win(msg.x, msg.y, current_player))
{
printf("\n对方获胜!\n");
return 0; // 游戏结束
}
break;
}
}
else if (msg.type == MSG_SURRENDER)
{
printf("\n对方认输,你获胜了!\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_DISCONNECT)
{
printf("\n对方已断开连接\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_CHAT)
{
printf("[对方]: %s\n", msg.message);
}
else if (msg.type == MSG_UNDO_REQUEST)
{
int steps = msg.x;
printf("\n对方请求悔棋 %d 步,是否同意?(1:同意/0:拒绝): ", steps);
int response = get_integer_input("", 0, 1);
if (response && return_move(steps * 2))
{
printf("同意悔棋,双方各退 %d 步\n", steps);
@@ -864,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)
{
printf("\n对方超时,你获胜!\n");
return 0; // 游戏结束
}
// 检查网络连接
if (!is_network_connected())
{
@@ -880,7 +925,7 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
}
}
}
return 1; // 正常回合完成
}
@@ -890,11 +935,11 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
bool network_game_loop()
{
int current_player = PLAYER1; // 总是从玩家1开始
while (1)
{
bool is_local_turn = (current_player == network_state.local_player_id);
int turn_result = handle_network_player_turn(current_player, is_local_turn);
if (turn_result == 0) // 游戏结束
{
@@ -904,17 +949,17 @@ bool network_game_loop()
{
continue; // 不切换玩家,重新开始当前回合
}
// 检查平局
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
return true;
}
// 切换玩家
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
// 检查网络连接
if (!is_network_connected())
{
@@ -922,6 +967,6 @@ bool network_game_loop()
return false;
}
}
return true;
}
+44
View File
@@ -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] = "平局或未完成"; // 存储胜负信息
+3 -3
View File
@@ -265,12 +265,12 @@ int calculate_step_score(int x, int y, int player)
break;
}
}
// 位置奖励:越靠近中心分数越高
int center_x = 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); // 距离中心越近奖励越高
return step_score + position_bonus;
}
+441
View File
@@ -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, &center_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);
}
View File
+9 -31
View File
@@ -5,23 +5,22 @@
* @brief powershell
*
* !
* gcc -std=c17 -o gobang.exe *.c -lws2_32
.\gobang.exe
* 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
*
* @detail gcc -lws2_32链接Windows网络库
* @detail SDL3 D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32
*
* @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\\setup.iss
* @brief & "D:\Program Files (x86)\Inno Setup 6\iscc.exe" installer\\installer.iss
*/
#include "game_mode.h"
#include "ui.h"
#include "gui.h"
#include "config.h"
#include <stdio.h>
#ifdef _WIN32
@@ -73,36 +72,15 @@ int main(int argc, char *argv[])
break;
// 6. 游戏规则
case 6:
clear_screen();
display_game_rules();
pause_for_input("\n按任意键返回主菜单...");
show_game_rules();
break;
// 7. 关于游戏
case 7:
clear_screen();
display_about();
pause_for_input("\n按任意键返回主菜单...");
show_about_game();
break;
// 8. 图形化界面
case 8:
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("按任意键返回主菜单...");
}
run_gui_mode();
break;
// 0. 退出游戏
case 0:
+59 -57
View File
@@ -44,11 +44,11 @@ bool init_network()
return false;
}
#endif
memset(&network_state, 0, sizeof(NetworkGameState));
network_state.socket = INVALID_SOCKET;
network_state.port = DEFAULT_PORT;
return true;
}
@@ -62,11 +62,11 @@ void cleanup_network()
closesocket(network_state.socket);
network_state.socket = INVALID_SOCKET;
}
#ifdef _WIN32
WSACleanup();
#endif
network_state.is_connected = false;
}
@@ -77,7 +77,7 @@ bool create_server(int port)
{
struct sockaddr_in server_addr, client_addr;
int addr_len = sizeof(client_addr);
// 创建套接字
SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == INVALID_SOCKET)
@@ -85,28 +85,28 @@ bool create_server(int port)
printf("创建套接字失败\n");
return false;
}
// 设置地址重用
int opt = 1;
#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
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
// 绑定地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
if (bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
{
printf("绑定端口失败\n");
closesocket(listen_socket);
return false;
}
// 开始监听
if (listen(listen_socket, 1) == SOCKET_ERROR)
{
@@ -114,31 +114,31 @@ bool create_server(int port)
closesocket(listen_socket);
return false;
}
char local_ip[MAX_IP_LENGTH];
if (get_local_ip(local_ip, sizeof(local_ip)))
{
printf("服务器已启动,等待客户端连接...\n");
printf("本机IP地址: %s\n", local_ip);
printf("监听端口: %d\n", port);
}
else
}
else
{
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)
{
printf("接受连接失败\n");
closesocket(listen_socket);
return false;
}
// 关闭监听套接字
closesocket(listen_socket);
// 保存连接信息
network_state.socket = client_socket;
network_state.is_server = true;
@@ -147,7 +147,7 @@ bool create_server(int port)
network_state.remote_player_id = PLAYER2;
network_state.port = port;
strcpy(network_state.remote_ip, inet_ntoa(client_addr.sin_addr));
printf("客户端已连接: %s\n", network_state.remote_ip);
return true;
}
@@ -155,10 +155,10 @@ bool create_server(int port)
/**
* @brief
*/
bool connect_to_server(const char* ip, int port)
bool connect_to_server(const char *ip, int port)
{
struct sockaddr_in server_addr;
// 创建套接字
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket == INVALID_SOCKET)
@@ -166,15 +166,15 @@ bool connect_to_server(const char* ip, int port)
printf("创建套接字失败\n");
return false;
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
#ifdef _WIN32
server_addr.sin_addr.s_addr = inet_addr(ip);
if (server_addr.sin_addr.s_addr == INADDR_NONE)
if (server_addr.sin_addr.s_addr == INADDR_NONE)
{
#else
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0)
@@ -184,17 +184,17 @@ bool connect_to_server(const char* ip, int port)
closesocket(client_socket);
return false;
}
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");
closesocket(client_socket);
return false;
}
// 保存连接信息
network_state.socket = client_socket;
network_state.is_server = false;
@@ -203,7 +203,7 @@ bool connect_to_server(const char* ip, int port)
network_state.remote_player_id = PLAYER1;
network_state.port = port;
strcpy(network_state.remote_ip, ip);
printf("成功连接到服务器\n");
return true;
}
@@ -211,33 +211,33 @@ bool connect_to_server(const char* ip, int port)
/**
* @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)
{
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);
}
/**
* @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)
{
return false;
}
// 设置超时
if (timeout_ms > 0)
{
#ifdef _WIN32
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
struct timeval timeout;
timeout.tv_sec = timeout_ms / 1000;
@@ -245,18 +245,20 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
#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))
{
return true;
} else if (bytes_received == 0)
}
else if (bytes_received == 0)
{
// 连接已关闭
network_state.is_connected = false;
printf("对方已断开连接\n");
} else if (bytes_received == SOCKET_ERROR)
}
else if (bytes_received == SOCKET_ERROR)
{
#ifdef _WIN32
int error = WSAGetLastError();
@@ -270,7 +272,7 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
printf("网络接收错误\n");
}
}
return false;
}
@@ -285,10 +287,10 @@ void disconnect_network()
msg.type = MSG_DISCONNECT;
msg.player_id = network_state.local_player_id;
msg.timestamp = time(NULL);
send_network_message(&msg);
}
cleanup_network();
}
@@ -303,18 +305,18 @@ bool is_network_connected()
/**
* @brief IP地址
*/
bool get_local_ip(char* ip_buffer, int buffer_size)
bool get_local_ip(char *ip_buffer, int buffer_size)
{
#ifdef _WIN32
// Windows实现
char hostname[256];
if (gethostname(hostname, sizeof(hostname)) == 0)
{
struct hostent* host_entry = gethostbyname(hostname);
struct hostent *host_entry = gethostbyname(hostname);
if (host_entry != NULL)
{
struct in_addr addr;
addr.s_addr = *((unsigned long*)host_entry->h_addr_list[0]);
addr.s_addr = *((unsigned long *)host_entry->h_addr_list[0]);
strncpy(ip_buffer, inet_ntoa(addr), buffer_size - 1);
ip_buffer[buffer_size - 1] = '\0';
return true;
@@ -329,11 +331,11 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
addr.sin_port = htons(80);
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0)
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
{
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);
ip_buffer[buffer_size - 1] = '\0';
@@ -344,7 +346,7 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
close(sock);
}
#endif
// 默认返回本地回环地址
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
ip_buffer[buffer_size - 1] = '\0';
@@ -362,21 +364,21 @@ bool send_move(int x, int y, int player_id)
msg.x = x;
msg.y = y;
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
/**
* @brief
*/
bool send_chat_message(const char* message)
bool send_chat_message(const char *message)
{
NetworkMessage msg = {0};
msg.type = MSG_CHAT;
msg.player_id = network_state.local_player_id;
strncpy(msg.message, message, sizeof(msg.message) - 1);
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
@@ -389,7 +391,7 @@ bool send_surrender()
msg.type = MSG_SURRENDER;
msg.player_id = network_state.local_player_id;
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
@@ -403,7 +405,7 @@ bool send_undo_request(int steps)
msg.player_id = network_state.local_player_id;
msg.x = steps; // 使用x字段存储步数
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
@@ -415,9 +417,9 @@ bool send_undo_response(bool accepted, int steps)
NetworkMessage msg = {0};
msg.type = MSG_UNDO_RESPONSE;
msg.player_id = network_state.local_player_id;
msg.x = steps; // 使用x字段存储步数
msg.x = steps; // 使用x字段存储步数
msg.y = accepted ? 1 : 0; // 使用y字段存储是否同意
msg.timestamp = time(NULL);
return send_network_message(&msg);
}
+15 -15
View File
@@ -50,7 +50,7 @@
void review_process(int game_mode)
{
int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
// 如果评分尚未计算,则计算评分
if (!scores_calculated)
{
@@ -61,7 +61,7 @@ void review_process(int game_mode)
// 评分已从文件中加载,直接使用
printf("从记录文件中加载评分数据\n");
}
if (review_choice == 1)
{
printf("\n===== 复盘记录(总步数:%d) =====\n", step_count);
@@ -115,7 +115,7 @@ void review_process(int game_mode)
for (int col = 0; col < BOARD_SIZE; col++)
{
printf("%2d", col + 1); // 列号
}
}
printf("\n");
for (int row = 0; row < BOARD_SIZE; row++)
@@ -147,7 +147,7 @@ void review_process(int game_mode)
; // 等待回车
}
}
// 显示胜负结果(直接使用文件中的信息)
printf("\n===== 对局结果 =====");
if (strcmp(winner_info, "玩家获胜") == 0)
@@ -170,7 +170,7 @@ void review_process(int game_mode)
{
printf("\n?? 对局平局或未完成\n");
}
printf("\n复盘结束!按Enter查看评分...");
getchar(); // 等待用户按键
}
@@ -195,7 +195,7 @@ void calculate_game_scores()
{
// 计算时间权重因子:步数越靠后,权重越大
double time_weight = 1.0 + (double)i / step_count * TIME_WEIGHT_FACTOR; // 最后的步骤权重是开始步骤的(1+TIME_WEIGHT_FACTOR)倍
if (steps[i].player == PLAYER || steps[i].player == PLAYER1)
{
player1_final_score += (int)(calculate_step_score(steps[i].x, steps[i].y, steps[i].player) * time_weight);
@@ -205,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);
}
}
// 胜负加权:获胜方获得额外的评分奖励
if (step_count > 0)
{
@@ -223,7 +223,7 @@ void calculate_game_scores()
}
}
}
scores_calculated = 1; // 标记评分已计算
}
@@ -421,14 +421,14 @@ int save_game_to_file(const char *filename, int game_mode)
}
}
}
// 写入CSV文件头部
if (fprintf(file, "游戏模式,棋盘大小,玩家1得分,玩家2得分,对局结果\n%d,%d,%d,%d,%s\n\n", game_mode, BOARD_SIZE, player1_final_score, player2_final_score, winner_info) < 0)
{
fclose(file);
return 3; // 文件写入失败
}
// 写入CSV表头
if (fprintf(file, "步数,玩家,行坐标,列坐标\n") < 0)
{
@@ -439,7 +439,7 @@ int save_game_to_file(const char *filename, int game_mode)
// 写入所有落子步骤(CSV格式)
for (int i = 0; i < step_count; i++)
{
if (fprintf(file, "%d,%d,%d,%d\n", i+1, steps[i].player, steps[i].x+1, steps[i].y+1) < 0)
if (fprintf(file, "%d,%d,%d,%d\n", i + 1, steps[i].player, steps[i].x + 1, steps[i].y + 1) < 0)
{
fclose(file);
return 3; // 文件写入失败
@@ -481,10 +481,10 @@ int load_game_from_file(const char *filename)
// 读取游戏模式、棋盘大小和评分结果
int game_mode, size;
// 尝试读取新格式(包含胜负信息)
int read_count = fscanf(file, "%d,%d,%d,%d,%49s", &game_mode, &size, &player1_final_score, &player2_final_score, winner_info);
if (read_count == 4)
{
// 旧格式文件,没有胜负信息
@@ -496,7 +496,7 @@ int load_game_from_file(const char *filename)
fclose(file);
return 0;
}
if (game_mode != GAME_MODE_AI && game_mode != GAME_MODE_PVP && game_mode != GAME_MODE_NETWORK)
{
fclose(file);
@@ -507,7 +507,7 @@ int load_game_from_file(const char *filename)
fclose(file);
return false;
}
// 设置评分已计算标志
scores_calculated = 1;
+2 -2
View File
@@ -48,7 +48,7 @@ void display_board()
printf("%2d", j);
}
printf("\n");
// 打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
@@ -151,7 +151,7 @@ void clear_screen()
* @brief
* @param prompt
*/
void pause_for_input(const char* prompt)
void pause_for_input(const char *prompt)
{
printf("%s", prompt);
#ifdef _WIN32