v8.2: 完善专业安装包制作功能\n\n- 支持Inno Setup和NSIS双重安装包方案\n- 完整的软件分发体系\n- 一键安装部署和完整卸载功能\n- 更新所有文档以反映v8.2版本特性

This commit is contained in:
2025-10-08 00:54:56 +08:00
parent 205e943bcb
commit 1abde99a68
21 changed files with 657 additions and 510 deletions
+1
View File
@@ -47,3 +47,4 @@ Thumbs.db
*.ico *.ico
# 打包文件 # 打包文件
dist/
+1 -1
View File
@@ -1,4 +1,4 @@
# 🧠 五子棋AI实现详解 (v8.0) # 🧠 五子棋AI实现详解
## 📜 算法概述 ## 📜 算法概述
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。 本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。
+1 -1
View File
@@ -1,4 +1,4 @@
# 五子棋项目代码架构重构指南 (v8.0) # 五子棋项目代码架构重构指南
## 📋 概述 ## 📋 概述
+7 -23
View File
@@ -1,14 +1,14 @@
# 五子棋网络对战使用说明 (v8.0) # 五子棋网络对战使用说明
## 功能概述 ## 功能概述
本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。v8.0版本新增了GUI界面的网络对战支持,提供更直观的可视化网络游戏体验。 本项目支持网络对战功能,允许两台设备通过网络进行实时五子棋对战,支持服务器/客户端连接。v8.0版本新增了GUI界面的网络对战支持,提供更直观的可视化网络游戏体验。
## v8.0网络功能增强 ### v8.2 (2025-10-08)
- **GUI网络对战**:图形化界面支持网络游戏 - 专业安装包制作 - 支持Inno Setup和NSIS双重安装方案
- **可视化连接状态**:实时显示网络连接状态 - 安装程序优化 - 完整的文件打包和快捷方式创建
- **双版本网络支持**:控制台和GUI版本均支持网络功能 - 分发体系完善 - 提供标准化的软件分发解决方案
- **网络状态指示**:图形化显示连接、等待、游戏状态 - 用户体验提升 - 一键安装部署,支持完整卸载功能
## 编译方法 ## 编译方法
@@ -17,7 +17,7 @@
gcc -std=c17 -o gobang.exe *.c -lws2_32 gcc -std=c17 -o gobang.exe *.c -lws2_32
``` ```
### GUI版本v8.0新增) ### GUI版本
```bash ```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 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" . 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 **联系邮箱:** 3364451258@qq.com
+2 -11
View File
@@ -14,23 +14,14 @@
- 📋 **用户友好界面** - 提供清晰的编译选项菜单和操作指引 - 📋 **用户友好界面** - 提供清晰的编译选项菜单和操作指引
-**编译流程优化** - 统一编译参数,解决SDL3依赖问题 -**编译流程优化** - 统一编译参数,解决SDL3依赖问题
- 🌐 **多语言支持** - 英文界面避免编码问题,确保兼容性 - 🌐 **多语言支持** - 英文界面避免编码问题,确保兼容性
- 📦 **安装包制作** - 提供Inno Setup和NSIS两种安装包方案
-**错误处理完善** - 添加无效输入处理和详细错误提示 -**错误处理完善** - 添加无效输入处理和详细错误提示
- 🎯 **开发体验提升** - 简化编译流程,提高开发效率 - 🎯 **开发体验提升** - 简化编译流程,提高开发效率
### v8.0 (2025-01-18) - GUI图形化界面更新
- 🖥️ **SDL3图形化界面** - 全新的现代化图形用户界面
- 🎮 **可视化棋盘操作** - 支持鼠标点击落子和直观的棋盘显示
- 🎨 **现代化UI设计** - 美观的界面布局和用户体验优化
- 📦 **安装包制作** - 提供Inno Setup和NSIS两种安装包方案
- 🔧 **双版本支持** - 同时支持控制台版本和GUI版本
- 🎯 **窗口管理优化** - 完善的窗口显示和事件处理机制
- 📱 **响应式界面** - 自适应窗口大小和分辨率
- 🚀 **性能优化** - 图形渲染和事件处理性能提升
## 目录 ## 目录
- [C语言五子棋人机对战AI](#c语言五子棋人机对战ai) - [C语言五子棋人机对战AI](#c语言五子棋人机对战ai)
- [📋 大版本更新](#-大版本更新) - [📋 大版本更新](#-大版本更新)
- [v8.0 (2025-01-18) - GUI图形化界面更新](#v80-2025-01-18---gui图形化界面更新) - [v8.2 (2025-01-20) - 编译脚本优化更新](#v82-2025-01-20---编译脚本优化更新)
- [目录](#目录) - [目录](#目录)
- [项目简介](#项目简介) - [项目简介](#项目简介)
- [功能特性](#功能特性) - [功能特性](#功能特性)
+14 -6
View File
@@ -2,8 +2,8 @@
======================================== ========================================
项目名称:五子棋多模式对战系统 项目名称:五子棋多模式对战系统
统计时间:2025年9月18日 统计时间:2025年10月8日
项目版本:v8.0 项目版本:v8.2
开发语言:C语言 + SDL3图形库 开发语言:C语言 + SDL3图形库
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
@@ -111,7 +111,9 @@ v8.0版本新增:
• SDL3图形化界面实现(v8.0新增) • SDL3图形化界面实现(v8.0新增)
• 双版本架构设计(控制台+GUI)(v8.0新增) • 双版本架构设计(控制台+GUI)(v8.0新增)
• 鼠标交互和事件驱动架构(v8.0新增) • 鼠标交互和事件驱动架构(v8.0新增)
• 专业安装包制作支持(v8.0新增) • 专业安装包制作支持(v8.2新增)
• Inno Setup和NSIS双重打包方案(v8.2新增)
• 完整的软件分发体系(v8.2新增)
• 完整的网络对战功能实现 • 完整的网络对战功能实现
• 智能AI算法与评估系统 • 智能AI算法与评估系统
• 灵活的配置管理系统 • 灵活的配置管理系统
@@ -120,9 +122,9 @@ v8.0版本新增:
• 实时计时器系统 • 实时计时器系统
• 全局变量统一管理 • 全局变量统一管理
• 跨平台网络通信支持 • 跨平台网络通信支持
• 代码架构模块化重构v7.0新增) • 代码架构模块化重构
• 配置参数集中化管理v7.0新增) • 配置参数集中化管理
• 类型定义标准化v7.0新增) • 类型定义标准化
【总体评价】 【总体评价】
这是一个非常优秀的C语言项目,代码量适中但功能完整, 这是一个非常优秀的C语言项目,代码量适中但功能完整,
@@ -132,6 +134,12 @@ v8.0版本新增:
包括人机对战、双人对战和网络对战,功能丰富,架构清晰, 包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
是C语言项目开发的优秀范例。 是C语言项目开发的优秀范例。
v8.2版本进一步完善了软件分发体系,
通过Inno Setup和NSIS双重安装包方案,
实现了专业级的软件打包和部署功能,
为用户提供了便捷的一键安装体验,
标志着项目从开发阶段向产品化阶段的重要转变。
v8.0版本的图形化界面是项目发展的重大突破, v8.0版本的图形化界面是项目发展的重大突破,
通过SDL3图形库实现了现代化的可视化界面, 通过SDL3图形库实现了现代化的可视化界面,
支持鼠标交互操作,大幅提升了用户体验。 支持鼠标交互操作,大幅提升了用户体验。
+11 -4
View File
@@ -3,16 +3,23 @@
* @brief C语言五子棋多模式对战系统 * @brief C语言五子棋多模式对战系统
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统,v8.0新增SDL3图形化界面 * @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-09-18 * @date 2025-10-8
* @version 8.0 * @version 8.2
* @note * @note
* 1. v8.0图形化界面 * 1. v8.2专业安装包
* - 📦 双重打包方案:支持Inno Setup和NSIS两种安装包制作
* - 🚀 一键安装部署:完整的软件分发解决方案
* - 🔧 安装程序优化:自动创建快捷方式和注册表项
* - 🗂️ 完整文件打包:包含所有源码、文档和依赖文件
* - 🔄 完整卸载功能:支持干净的软件卸载和清理
* - 💼 产品化部署:从开发工具向商业软件的转变
*
* 2. v8.0图形化界面:
* - 🎨 SDL3图形化界面:实现现代化可视化棋盘界面 * - 🎨 SDL3图形化界面:实现现代化可视化棋盘界面
* - 🖱️ 鼠标交互支持:直观的点击落子操作 * - 🖱️ 鼠标交互支持:直观的点击落子操作
* - 🏗️ 双版本架构:控制台版本和GUI版本并行支持 * - 🏗️ 双版本架构:控制台版本和GUI版本并行支持
* - 🪟 窗口管理优化:自动居中、响应式设计 * - 🪟 窗口管理优化:自动居中、响应式设计
* - ⚡ 事件驱动架构:流畅的用户交互体验 * - ⚡ 事件驱动架构:流畅的用户交互体验
* - 📦 安装包支持:提供Inno Setup专业安装程序
* - 🔧 编译脚本优化:简化GUI版本编译流程 * - 🔧 编译脚本优化:简化GUI版本编译流程
* - 🌐 GUI网络支持:图形化界面支持网络对战 * - 🌐 GUI网络支持:图形化界面支持网络对战
* 2. v7.0架构重构: * 2. v7.0架构重构:
+3 -1
View File
@@ -1,8 +1,10 @@
项目要求文档 - 五子棋游戏 (v8.0) 项目要求文档 - 五子棋游戏 (v8.2)
1. 项目概述 1. 项目概述
- 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。 - 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。
- v8.0版本实现双版本架构:支持命令行界面和SDL3图形化界面。 - v8.0版本实现双版本架构:支持命令行界面和SDL3图形化界面。
- v8.2版本(当前版本)完善了专业安装包制作体系,提供企业级的软件分发解决方案。
- v8.2版本完善了软件分发体系,支持Inno Setup和NSIS双重安装包制作,提供专业级的软件打包和部署解决方案。
- 包括游戏配置、记录保存、复盘功能和专业安装包。 - 包括游戏配置、记录保存、复盘功能和专业安装包。
- 提供现代化的可视化用户体验和传统控制台体验。 - 提供现代化的可视化用户体验和传统控制台体验。
+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"
+147 -44
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" !include "MUI2.nsh"
; Basic Information ; MUI Settings
Name "Gobang Game"
OutFile "..\\Gobang_Setup.exe"
InstallDir "$PROGRAMFILES\Gobang"
RequestExecutionLevel admin
; Interface Settings
!define MUI_ABORTWARNING !define MUI_ABORTWARNING
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico" !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 !insertmacro MUI_PAGE_DIRECTORY
; Install page
!insertmacro MUI_PAGE_INSTFILES !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" !insertmacro MUI_LANGUAGE "SimpChinese"
; Installation Section ; Installer attributes
Section "Main" Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
SetOutPath "$INSTDIR" OutFile "dist\Gobang_NSIS_Setup.exe"
InstallDir "$PROGRAMFILES\Gobang"
InstallDirRegKey HKLM "${PRODUCT_DIR_REGKEY}" ""
ShowInstDetails show
ShowUnInstDetails show
; Copy configuration and documentation files ; Version information
File "..\\gobang_config.ini" VIProductVersion "1.0.0.0"
File "..\\README.md" 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}"
; Copy GUI executable file if exists Section "Main Program" SEC01
IfFileExists "..\\gobang_gui.exe" 0 +2 SectionIn RO
File "..\\gobang_gui.exe" SetOutPath "$INSTDIR"
SetOverwrite ifnewer
; Copy SDL3 library if exists File "..\gobang_console.exe"
IfFileExists "..\\SDL3.dll" 0 +2 File "..\gobang_gui.exe"
File "..\\SDL3.dll" File "..\SDL3.dll"
File "..\compile.bat"
; Create program group directory File "..\gobang_config.ini"
CreateDirectory "$SMPROGRAMS\Gobang" File "..\Makefile"
File "..\README.md"
; Create shortcuts (only if executable exists) CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
IfFileExists "$INSTDIR\gobang_gui.exe" 0 +3 CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Gobang Console.lnk" "$INSTDIR\gobang_console.exe"
CreateShortCut "$DESKTOP\Gobang.lnk" "$INSTDIR\gobang_gui.exe" CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Gobang GUI.lnk" "$INSTDIR\gobang_gui.exe"
CreateShortCut "$SMPROGRAMS\Gobang\Gobang.lnk" "$INSTDIR\gobang_gui.exe" CreateShortCut "$DESKTOP\${PRODUCT_NAME}.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$\""
SectionEnd SectionEnd
; Uninstall Section Section "Source Code" SEC02
Section "Uninstall" SetOutPath "$INSTDIR\include"
RMDir /r "$INSTDIR" File /r "..\include\*.*"
Delete "$DESKTOP\Gobang.lnk" SetOutPath "$INSTDIR\src"
RMDir /r "$SMPROGRAMS\Gobang" File /r "..\src\*.*"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Gobang" 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 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
+21 -21
View File
@@ -69,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; // 返回最大分
} }
@@ -130,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; // 返回总评估分
@@ -415,24 +415,24 @@ int generate_candidate_moves(ScoredMove *moves, int player)
// 根据威胁等级调整分数 // 根据威胁等级调整分数
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++;
+22 -22
View File
@@ -182,7 +182,7 @@ void config_timer()
if (use_timer) if (use_timer)
{ {
int new_limit = get_integer_input("请输入时间限制(分钟): ", 1, 999); int new_limit = get_integer_input("请输入时间限制(分钟): ", 1, 999);
time_limit = new_limit * 60; // 转换为秒数存储 time_limit = new_limit * 60; // 转换为秒数存储
printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60); printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60);
} }
else else
@@ -226,27 +226,27 @@ void config_management_menu()
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 0: case 0:
save_game_config(); save_game_config();
return; return;
default: default:
printf("无效的选择!\n"); printf("无效的选择!\n");
break; break;
} }
pause_for_input("按任意键继续..."); pause_for_input("按任意键继续...");
+11 -12
View File
@@ -590,7 +590,8 @@ 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正在创建房间...\n"); printf("\n正在创建房间...\n");
connection_success = create_server(port); connection_success = create_server(port);
@@ -608,7 +609,8 @@ void run_network_game()
{ {
printf("输入错误,请重新输入。\n"); printf("输入错误,请重新输入。\n");
// 清除输入缓冲区 // 清除输入缓冲区
while (getchar() != '\n'); while (getchar() != '\n')
;
continue; continue;
} }
@@ -649,7 +651,8 @@ void run_network_game()
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)
@@ -668,7 +671,8 @@ 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);
@@ -768,7 +772,6 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
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);
@@ -783,7 +786,7 @@ 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; // 游戏结束
} }
} }
@@ -809,7 +812,8 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
} }
} }
x--; y--; // 转换为0-based坐标 x--;
y--; // 转换为0-based坐标
if (player_move(x, y, current_player)) if (player_move(x, y, current_player))
{ {
@@ -836,7 +840,6 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
printf("\n你获胜了!\n"); printf("\n你获胜了!\n");
return 0; // 游戏结束 return 0; // 游戏结束
} }
} }
else else
{ {
@@ -869,24 +872,20 @@ bool handle_network_player_turn(int current_player, bool is_local_turn)
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)
{ {
+17 -17
View File
@@ -17,28 +17,28 @@ const int direction[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // 四个方向
int step_count = 0; // 当前步数计数器 int step_count = 0; // 当前步数计数器
// ==================== 游戏配置变量定义 ==================== // ==================== 游戏配置变量定义 ====================
bool use_forbidden_moves = DEFAULT_USE_FORBIDDEN_MOVES; // 是否启用禁手规则 bool use_forbidden_moves = DEFAULT_USE_FORBIDDEN_MOVES; // 是否启用禁手规则
int use_timer = DEFAULT_USE_TIMER; // 是否启用计时器 int use_timer = DEFAULT_USE_TIMER; // 是否启用计时器
int time_limit = DEFAULT_TIME_LIMIT; // 每回合的时间限制(秒) int time_limit = DEFAULT_TIME_LIMIT; // 每回合的时间限制(秒)
int network_port = DEFAULT_NETWORK_PORT; // 网络端口 int network_port = DEFAULT_NETWORK_PORT; // 网络端口
int network_timeout = NETWORK_TIMEOUT_MS; // 网络超时时间 int network_timeout = NETWORK_TIMEOUT_MS; // 网络超时时间
// ==================== AI相关变量定义 ==================== // ==================== AI相关变量定义 ====================
double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数 double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
// ==================== 网络相关变量定义 ==================== // ==================== 网络相关变量定义 ====================
NetworkGameState network_state = {0}; // 网络游戏状态 NetworkGameState network_state = {0}; // 网络游戏状态
// ==================== GUI相关变量定义 ==================== // ==================== GUI相关变量定义 ====================
SDL_Window* window = NULL; // SDL窗口指针 SDL_Window *window = NULL; // SDL窗口指针
SDL_Renderer* renderer = NULL; // SDL渲染器指针 SDL_Renderer *renderer = NULL; // SDL渲染器指针
int gui_running = 1; // GUI运行状态标志 int gui_running = 1; // GUI运行状态标志
int current_player_gui = PLAYER; // GUI当前玩家 int current_player_gui = PLAYER; // GUI当前玩家
int game_over = 0; // 游戏结束标志 int game_over = 0; // 游戏结束标志
char status_message[256] = "五子棋游戏 - 黑子先行"; // 状态消息 char status_message[256] = "五子棋游戏 - 黑子先行"; // 状态消息
// ==================== 记录相关变量定义 ==================== // ==================== 记录相关变量定义 ====================
int player1_final_score = 0; // 玩家1最终得分 int player1_final_score = 0; // 玩家1最终得分
int player2_final_score = 0; // 玩家2最终得分 int player2_final_score = 0; // 玩家2最终得分
int scores_calculated = 0; // 评分计算标志 int scores_calculated = 0; // 评分计算标志
char winner_info[50] = "平局或未完成"; // 存储胜负信息 char winner_info[50] = "平局或未完成"; // 存储胜负信息
+1 -1
View File
@@ -269,7 +269,7 @@ int calculate_step_score(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 = 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;
+130 -81
View File
@@ -27,8 +27,10 @@
* 窗口尺寸由WINDOW_WIDTH和WINDOW_HEIGHT定义 * 窗口尺寸由WINDOW_WIDTH和WINDOW_HEIGHT定义
* 失败时会自动清理已创建的资源 * 失败时会自动清理已创建的资源
*/ */
int init_gui() { int init_gui()
if (SDL_Init(SDL_INIT_VIDEO) < 0) { {
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
printf("SDL初始化失败: %s\n", SDL_GetError()); printf("SDL初始化失败: %s\n", SDL_GetError());
return -1; return -1;
} }
@@ -36,13 +38,13 @@ int init_gui() {
window = SDL_CreateWindow( window = SDL_CreateWindow(
"五子棋游戏 - SDL3版本", "五子棋游戏 - SDL3版本",
WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_HEIGHT,
SDL_WINDOW_RESIZABLE SDL_WINDOW_RESIZABLE);
);
// 设置窗口位置到屏幕中央 // 设置窗口位置到屏幕中央
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
if (!window) { if (!window)
{
printf("窗口创建失败: %s\n", SDL_GetError()); printf("窗口创建失败: %s\n", SDL_GetError());
SDL_Quit(); SDL_Quit();
return -1; return -1;
@@ -52,7 +54,8 @@ int init_gui() {
SDL_ShowWindow(window); SDL_ShowWindow(window);
renderer = SDL_CreateRenderer(window, NULL); renderer = SDL_CreateRenderer(window, NULL);
if (!renderer) { if (!renderer)
{
printf("渲染器创建失败: %s\n", SDL_GetError()); printf("渲染器创建失败: %s\n", SDL_GetError());
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
SDL_Quit(); SDL_Quit();
@@ -61,8 +64,10 @@ int init_gui() {
// 初始化游戏状态 // 初始化游戏状态
// 初始化棋盘 // 初始化棋盘
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++)
{
board[i][j] = EMPTY; board[i][j] = EMPTY;
} }
} }
@@ -86,12 +91,15 @@ int init_gui() {
* 释放后将指针设置为NULL防止重复释放 * 释放后将指针设置为NULL防止重复释放
* 程序退出时必须调用此函数避免内存泄漏 * 程序退出时必须调用此函数避免内存泄漏
*/ */
void cleanup_gui() { void cleanup_gui()
if (renderer) { {
if (renderer)
{
SDL_DestroyRenderer(renderer); SDL_DestroyRenderer(renderer);
renderer = NULL; renderer = NULL;
} }
if (window) { if (window)
{
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
window = NULL; window = NULL;
} }
@@ -111,7 +119,8 @@ void cleanup_gui() {
* 背景色由GUI_COLOR_BACKGROUND定义 * 背景色由GUI_COLOR_BACKGROUND定义
* 每帧都会完全重绘整个画面 * 每帧都会完全重绘整个画面
*/ */
void render_game() { void render_game()
{
// 清空屏幕 - 设置背景色 // 清空屏幕 - 设置背景色
SDL_Color bg_color = GUI_COLOR_BACKGROUND; SDL_Color bg_color = GUI_COLOR_BACKGROUND;
SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a); SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a);
@@ -141,53 +150,72 @@ void render_game() {
* 落子后会检查胜负并切换玩家 * 落子后会检查胜负并切换玩家
* 游戏结束后不再响应落子操作 * 游戏结束后不再响应落子操作
*/ */
int handle_events() { int handle_events()
{
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event))
switch (event.type) { {
case SDL_EVENT_QUIT: 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; gui_running = 0;
return 0; return 0;
}
break;
case SDL_EVENT_KEY_DOWN: case SDL_EVENT_MOUSE_BUTTON_DOWN:
if (event.key.key == SDLK_ESCAPE) { if (event.button.button == SDL_BUTTON_LEFT && !game_over)
gui_running = 0; {
return 0; int board_x, board_y;
} if (screen_to_board(event.button.x, event.button.y, &board_x, &board_y))
break; {
if (have_space(board_x, board_y))
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
if (event.button.button == SDL_BUTTON_LEFT && !game_over) { // 执行落子操作
int board_x, board_y; if (player_move(board_x, board_y, current_player_gui))
if (screen_to_board(event.button.x, event.button.y, &board_x, &board_y)) { {
if (have_space(board_x, board_y)) { // 检查是否获胜
// 执行落子操作 if (check_win(board_x, board_y, current_player_gui))
if (player_move(board_x, board_y, current_player_gui)) { {
// 检查是否获胜 game_over = 1;
if (check_win(board_x, board_y, current_player_gui)) { if (current_player_gui == PLAYER)
game_over = 1; {
if (current_player_gui == PLAYER) { sprintf(status_message, "游戏结束 - 黑子获胜!");
sprintf(status_message, "游戏结束 - 黑子获胜!"); }
} else { else
sprintf(status_message, "游戏结束 - 白子获胜!"); {
} sprintf(status_message, "游戏结束 - 白子获胜!");
} else { }
// 切换玩家 }
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER; else
if (current_player_gui == PLAYER) { {
sprintf(status_message, "轮到黑子下棋"); // 切换玩家
} else { current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER;
sprintf(status_message, "轮到白子下棋"); if (current_player_gui == PLAYER)
} {
sprintf(status_message, "轮到黑子下棋");
}
else
{
sprintf(status_message, "轮到白子下棋");
} }
} }
} else {
sprintf(status_message, "该位置已有棋子,请选择其他位置");
} }
} }
else
{
sprintf(status_message, "该位置已有棋子,请选择其他位置");
}
} }
break; }
break;
} }
} }
return 1; return 1;
@@ -203,24 +231,27 @@ int handle_events() {
* 棋盘线条颜色由GUI_COLOR_BOARD_LINE定义 * 棋盘线条颜色由GUI_COLOR_BOARD_LINE定义
* 天元点和星位用黑色小矩形标记 * 天元点和星位用黑色小矩形标记
*/ */
void draw_board() { void draw_board()
{
SDL_Color line_color = GUI_COLOR_BOARD_LINE; SDL_Color line_color = GUI_COLOR_BOARD_LINE;
SDL_SetRenderDrawColor(renderer, line_color.r, line_color.g, line_color.b, line_color.a); SDL_SetRenderDrawColor(renderer, line_color.r, line_color.g, line_color.b, line_color.a);
// 绘制横线 // 绘制横线
for (int i = 0; i < BOARD_SIZE; i++) { for (int i = 0; i < BOARD_SIZE; i++)
{
int y = BOARD_OFFSET_Y + i * CELL_SIZE; int y = BOARD_OFFSET_Y + i * CELL_SIZE;
SDL_RenderLine(renderer, SDL_RenderLine(renderer,
BOARD_OFFSET_X, y, BOARD_OFFSET_X, y,
BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE, y); BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE, y);
} }
// 绘制竖线 // 绘制竖线
for (int j = 0; j < BOARD_SIZE; j++) { for (int j = 0; j < BOARD_SIZE; j++)
{
int x = BOARD_OFFSET_X + j * CELL_SIZE; int x = BOARD_OFFSET_X + j * CELL_SIZE;
SDL_RenderLine(renderer, SDL_RenderLine(renderer,
x, BOARD_OFFSET_Y, x, BOARD_OFFSET_Y,
x, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE); x, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE);
} }
// 绘制天元点和星位 // 绘制天元点和星位
@@ -239,10 +270,10 @@ void draw_board() {
{center - star_offset, center - star_offset}, {center - star_offset, center - star_offset},
{center + star_offset, center - star_offset}, {center + star_offset, center - star_offset},
{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++) { for (int i = 0; i < 4; i++)
{
int x = BOARD_OFFSET_X + positions[i][1] * CELL_SIZE; int x = BOARD_OFFSET_X + positions[i][1] * CELL_SIZE;
int y = BOARD_OFFSET_Y + positions[i][0] * CELL_SIZE; int y = BOARD_OFFSET_Y + positions[i][0] * CELL_SIZE;
SDL_FRect star_rect = {x - 1, y - 1, 2, 2}; SDL_FRect star_rect = {x - 1, y - 1, 2, 2};
@@ -260,21 +291,28 @@ void draw_board() {
* 通过draw_circle函数实现圆形绘制 * 通过draw_circle函数实现圆形绘制
* 棋子位置根据棋盘坐标和CELL_SIZE计算屏幕坐标 * 棋子位置根据棋盘坐标和CELL_SIZE计算屏幕坐标
*/ */
void draw_stones() { void draw_stones()
for (int i = 0; i < BOARD_SIZE; i++) { {
for (int j = 0; j < BOARD_SIZE; j++) { for (int i = 0; i < BOARD_SIZE; i++)
if (board[i][j] != EMPTY) { {
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] != EMPTY)
{
int x = BOARD_OFFSET_X + j * CELL_SIZE; int x = BOARD_OFFSET_X + j * CELL_SIZE;
int y = BOARD_OFFSET_Y + i * CELL_SIZE; int y = BOARD_OFFSET_Y + i * CELL_SIZE;
// 设置棋子颜色 // 设置棋子颜色
SDL_Color stone_color, border_color; SDL_Color stone_color, border_color;
if (board[i][j] == PLAYER || board[i][j] == PLAYER1) { if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
stone_color = (SDL_Color)GUI_COLOR_BLACK_STONE; {
} else { stone_color = (SDL_Color)GUI_COLOR_BLACK_STONE;
stone_color = (SDL_Color)GUI_COLOR_WHITE_STONE; }
} else
border_color = (SDL_Color)GUI_COLOR_STONE_BORDER; {
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, stone_color);
@@ -301,14 +339,18 @@ void draw_stones() {
* 适用于绘制棋子等小尺寸圆形 * 适用于绘制棋子等小尺寸圆形
* SDL3没有内置圆形绘制函数,需要自实现 * SDL3没有内置圆形绘制函数,需要自实现
*/ */
void draw_circle(int center_x, int center_y, int radius, SDL_Color 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); SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
for (int w = 0; w < radius * 2; w++) { for (int w = 0; w < radius * 2; w++)
for (int h = 0; h < radius * 2; h++) { {
for (int h = 0; h < radius * 2; h++)
{
int dx = radius - w; int dx = radius - w;
int dy = radius - h; int dy = radius - h;
if ((dx*dx + dy*dy) <= (radius * radius)) { if ((dx * dx + dy * dy) <= (radius * radius))
{
SDL_RenderPoint(renderer, center_x + dx, center_y + dy); SDL_RenderPoint(renderer, center_x + dx, center_y + dy);
} }
} }
@@ -318,7 +360,8 @@ void draw_circle(int center_x, int center_y, int radius, SDL_Color color) {
/** /**
* @brief 绘制UI元素 * @brief 绘制UI元素
*/ */
void draw_ui_elements() { void draw_ui_elements()
{
// 绘制状态信息区域背景 // 绘制状态信息区域背景
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255); 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_FRect info_rect = {BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE + 20, BOARD_OFFSET_Y, 200, 100};
@@ -333,11 +376,15 @@ void draw_ui_elements() {
int indicator_x = info_rect.x + 20; int indicator_x = info_rect.x + 20;
int indicator_y = info_rect.y + 20; int indicator_y = info_rect.y + 20;
if (!game_over) { if (!game_over)
if (current_player_gui == PLAYER) { {
if (current_player_gui == PLAYER)
{
// 黑子回合 // 黑子回合
draw_circle(indicator_x, indicator_y, 10, (SDL_Color){0, 0, 0, 255}); draw_circle(indicator_x, indicator_y, 10, (SDL_Color){0, 0, 0, 255});
} else { }
else
{
// 白子回合 // 白子回合
draw_circle(indicator_x, indicator_y, 10, (SDL_Color){255, 255, 255, 255}); draw_circle(indicator_x, indicator_y, 10, (SDL_Color){255, 255, 255, 255});
// 绘制当前玩家指示器(简单的矩形代替圆形) // 绘制当前玩家指示器(简单的矩形代替圆形)
@@ -363,12 +410,13 @@ void draw_ui_elements() {
* @note 使用就近取整算法,点击格子中心附近都会定位到该格子 * @note 使用就近取整算法,点击格子中心附近都会定位到该格子
* 坐标范围检查确保不会越界访问棋盘数组 * 坐标范围检查确保不会越界访问棋盘数组
*/ */
int screen_to_board(int screen_x, int screen_y, int* board_x, int* board_y) { 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_x = screen_x - BOARD_OFFSET_X;
int rel_y = screen_y - BOARD_OFFSET_Y; int rel_y = screen_y - BOARD_OFFSET_Y;
*board_x = (rel_x + CELL_SIZE/2) / CELL_SIZE; *board_x = (rel_x + CELL_SIZE / 2) / CELL_SIZE;
*board_y = (rel_y + CELL_SIZE/2) / CELL_SIZE; *board_y = (rel_y + CELL_SIZE / 2) / CELL_SIZE;
return (*board_x >= 0 && *board_x < BOARD_SIZE && return (*board_x >= 0 && *board_x < BOARD_SIZE &&
*board_y >= 0 && *board_y < BOARD_SIZE); *board_y >= 0 && *board_y < BOARD_SIZE);
@@ -385,7 +433,8 @@ int screen_to_board(int screen_x, int screen_y, int* board_x, int* board_y) {
* 字符串长度限制为缓冲区大小减1 * 字符串长度限制为缓冲区大小减1
* 消息可用于游戏状态提示和错误信息显示 * 消息可用于游戏状态提示和错误信息显示
*/ */
void show_message(const char* message) { void show_message(const char *message)
{
strncpy(status_message, message, sizeof(status_message) - 1); strncpy(status_message, message, sizeof(status_message) - 1);
status_message[sizeof(status_message) - 1] = '\0'; status_message[sizeof(status_message) - 1] = '\0';
printf("%s\n", message); printf("%s\n", message);
+3 -3
View File
@@ -13,10 +13,10 @@
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" . copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
.\gobang_gui.exe .\gobang_gui.exe
* *
* @detail gcc 为编译器,添加了-lws2_32链接Windows网络库 * @note gcc 为编译器,添加了-lws2_32链接Windows网络库
* @detail SDL3 的路径:D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32 * @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)\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 "game_mode.h"
+21 -19
View File
@@ -89,7 +89,7 @@ bool create_server(int port)
// 设置地址重用 // 设置地址重用
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
@@ -100,7 +100,7 @@ bool create_server(int port)
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);
@@ -128,7 +128,7 @@ bool create_server(int 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");
@@ -155,7 +155,7 @@ 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;
@@ -188,7 +188,7 @@ bool connect_to_server(const char* ip, int port)
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);
@@ -211,21 +211,21 @@ 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)
{ {
@@ -237,7 +237,7 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
{ {
#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;
@@ -246,17 +246,19 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
#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();
@@ -303,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;
@@ -330,10 +332,10 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
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';
@@ -369,7 +371,7 @@ bool send_move(int x, int y, int player_id)
/** /**
* @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;
@@ -415,7 +417,7 @@ 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);
+1 -1
View File
@@ -439,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; // 文件写入失败
+1 -1
View File
@@ -151,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