diff --git a/.gitignore b/.gitignore index 9803916..72a57f0 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ Thumbs.db *.ico # 打包文件 +dist/ \ No newline at end of file diff --git a/MD/AI_function.md b/MD/AI_function.md index e2218ac..fa13c2f 100644 --- a/MD/AI_function.md +++ b/MD/AI_function.md @@ -1,4 +1,4 @@ -# 🧠 五子棋AI实现详解 (v8.0) +# 🧠 五子棋AI实现详解 ## 📜 算法概述 本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。 diff --git a/MD/Architecture_Refactoring_Guide.md b/MD/Architecture_Refactoring_Guide.md index f49e558..47725e8 100644 --- a/MD/Architecture_Refactoring_Guide.md +++ b/MD/Architecture_Refactoring_Guide.md @@ -1,4 +1,4 @@ -# 五子棋项目代码架构重构指南 (v8.0) +# 五子棋项目代码架构重构指南 ## 📋 概述 diff --git a/MD/NETWORK_README.md b/MD/NETWORK_README.md index 93caec5..56ed175 100644 --- a/MD/NETWORK_README.md +++ b/MD/NETWORK_README.md @@ -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 diff --git a/README.md b/README.md index 3928b17..70a48e1 100644 --- a/README.md +++ b/README.md @@ -14,23 +14,14 @@ - 📋 **用户友好界面** - 提供清晰的编译选项菜单和操作指引 - ⚡ **编译流程优化** - 统一编译参数,解决SDL3依赖问题 - 🌐 **多语言支持** - 英文界面避免编码问题,确保兼容性 +- 📦 **安装包制作** - 提供Inno Setup和NSIS两种安装包方案 - ✅ **错误处理完善** - 添加无效输入处理和详细错误提示 - 🎯 **开发体验提升** - 简化编译流程,提高开发效率 -### v8.0 (2025-01-18) - GUI图形化界面更新 -- 🖥️ **SDL3图形化界面** - 全新的现代化图形用户界面 -- 🎮 **可视化棋盘操作** - 支持鼠标点击落子和直观的棋盘显示 -- 🎨 **现代化UI设计** - 美观的界面布局和用户体验优化 -- 📦 **安装包制作** - 提供Inno Setup和NSIS两种安装包方案 -- 🔧 **双版本支持** - 同时支持控制台版本和GUI版本 -- 🎯 **窗口管理优化** - 完善的窗口显示和事件处理机制 -- 📱 **响应式界面** - 自适应窗口大小和分辨率 -- 🚀 **性能优化** - 图形渲染和事件处理性能提升 - ## 目录 - [C语言五子棋人机对战AI](#c语言五子棋人机对战ai) - [📋 大版本更新](#-大版本更新) - - [v8.0 (2025-01-18) - GUI图形化界面更新](#v80-2025-01-18---gui图形化界面更新) + - [v8.2 (2025-01-20) - 编译脚本优化更新](#v82-2025-01-20---编译脚本优化更新) - [目录](#目录) - [项目简介](#项目简介) - [功能特性](#功能特性) diff --git a/TXT/代码统计报告.txt b/TXT/代码统计报告.txt index ce50d01..458aef4 100644 --- a/TXT/代码统计报告.txt +++ b/TXT/代码统计报告.txt @@ -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图形库实现了现代化的可视化界面, 支持鼠标交互操作,大幅提升了用户体验。 diff --git a/TXT/简介.txt b/TXT/简介.txt index 452411b..21dd3cd 100644 --- a/TXT/简介.txt +++ b/TXT/简介.txt @@ -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架构重构: diff --git a/TXT/项目要求.txt b/TXT/项目要求.txt index 827c207..3e6c744 100644 --- a/TXT/项目要求.txt +++ b/TXT/项目要求.txt @@ -1,8 +1,10 @@ -项目要求文档 - 五子棋游戏 (v8.0) +项目要求文档 - 五子棋游戏 (v8.2) 1. 项目概述 - 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。 - v8.0版本实现双版本架构:支持命令行界面和SDL3图形化界面。 + - v8.2版本(当前版本)完善了专业安装包制作体系,提供企业级的软件分发解决方案。 + - v8.2版本完善了软件分发体系,支持Inno Setup和NSIS双重安装包制作,提供专业级的软件打包和部署解决方案。 - 包括游戏配置、记录保存、复盘功能和专业安装包。 - 提供现代化的可视化用户体验和传统控制台体验。 diff --git a/installer/installer.iss b/installer/installer.iss new file mode 100644 index 0000000..70ccfbc --- /dev/null +++ b/installer/installer.iss @@ -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" \ No newline at end of file diff --git a/installer/installer.nsi b/installer/installer.nsi index 662ff0b..57d9aba 100644 --- a/installer/installer.nsi +++ b/installer/installer.nsi @@ -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 \ No newline at end of file diff --git a/installer/setup.iss b/installer/setup.iss deleted file mode 100644 index a0e906e..0000000 --- a/installer/setup.iss +++ /dev/null @@ -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 - - diff --git a/src/ai.c b/src/ai.c index e400f41..999b9be 100644 --- a/src/ai.c +++ b/src/ai.c @@ -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; } \ No newline at end of file diff --git a/src/config.c b/src/config.c index 202f80b..f95f8d9 100644 --- a/src/config.c +++ b/src/config.c @@ -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("按任意键继续..."); } } \ No newline at end of file diff --git a/src/game_mode.c b/src/game_mode.c index e57f958..a78e2ef 100644 --- a/src/game_mode.c +++ b/src/game_mode.c @@ -203,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)) @@ -240,14 +240,14 @@ bool parse_network_player_input(int *x, int *y) } } } - + if (!is_network_connected()) { printf("网络连接断开\n"); return 0; } } - + printf("悔棋请求超时,对方未回应。\n"); } else @@ -569,7 +569,7 @@ void run_network_game() { // 重置评分计算标志 scores_calculated = 0; - + // 初始化网络模块 if (!init_network()) { @@ -577,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) { @@ -608,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) { @@ -620,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++) @@ -638,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) @@ -666,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"); @@ -681,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"); @@ -701,7 +705,7 @@ void run_network_game() { printf("游戏因网络错误而结束\n"); } - + // 清理网络连接 disconnect_network(); pause_for_input("按任意键返回主菜单..."); @@ -762,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) { @@ -783,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) // 有效坐标输入 { @@ -808,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; // 成功落子,跳出循环 @@ -821,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) { @@ -859,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); @@ -910,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()) { @@ -926,7 +925,7 @@ bool handle_network_player_turn(int current_player, bool is_local_turn) } } } - + return 1; // 正常回合完成 } @@ -936,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) // 游戏结束 { @@ -950,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()) { @@ -968,6 +967,6 @@ bool network_game_loop() return false; } } - + return true; } \ No newline at end of file diff --git a/src/globals.c b/src/globals.c index 9007f83..5552a4a 100644 --- a/src/globals.c +++ b/src/globals.c @@ -17,28 +17,28 @@ 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; // 网络超时时间 +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; // 防守系数 +double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数 // ==================== 网络相关变量定义 ==================== -NetworkGameState network_state = {0}; // 网络游戏状态 +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] = "五子棋游戏 - 黑子先行"; // 状态消息 +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] = "平局或未完成"; // 存储胜负信息 \ No newline at end of file +int player1_final_score = 0; // 玩家1最终得分 +int player2_final_score = 0; // 玩家2最终得分 +int scores_calculated = 0; // 评分计算标志 +char winner_info[50] = "平局或未完成"; // 存储胜负信息 \ No newline at end of file diff --git a/src/gobang.c b/src/gobang.c index 6e1791e..6ed40ab 100644 --- a/src/gobang.c +++ b/src/gobang.c @@ -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; } \ No newline at end of file diff --git a/src/gui.c b/src/gui.c index 891d497..ee1ffaf 100644 --- a/src/gui.c +++ b/src/gui.c @@ -27,8 +27,10 @@ * 窗口尺寸由WINDOW_WIDTH和WINDOW_HEIGHT定义 * 失败时会自动清理已创建的资源 */ -int init_gui() { - if (SDL_Init(SDL_INIT_VIDEO) < 0) { +int init_gui() +{ + if (SDL_Init(SDL_INIT_VIDEO) < 0) + { printf("SDL初始化失败: %s\n", SDL_GetError()); return -1; } @@ -36,13 +38,13 @@ int init_gui() { window = SDL_CreateWindow( "五子棋游戏 - SDL3版本", WINDOW_WIDTH, WINDOW_HEIGHT, - SDL_WINDOW_RESIZABLE - ); - + SDL_WINDOW_RESIZABLE); + // 设置窗口位置到屏幕中央 SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); - - if (!window) { + + if (!window) + { printf("窗口创建失败: %s\n", SDL_GetError()); SDL_Quit(); return -1; @@ -52,7 +54,8 @@ int init_gui() { SDL_ShowWindow(window); renderer = SDL_CreateRenderer(window, NULL); - if (!renderer) { + if (!renderer) + { printf("渲染器创建失败: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); @@ -61,18 +64,20 @@ int init_gui() { // 初始化游戏状态 // 初始化棋盘 - for (int i = 0; i < BOARD_SIZE; i++) { - for (int j = 0; j < BOARD_SIZE; j++) { + 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; } @@ -86,12 +91,15 @@ int init_gui() { * 释放后将指针设置为NULL防止重复释放 * 程序退出时必须调用此函数避免内存泄漏 */ -void cleanup_gui() { - if (renderer) { +void cleanup_gui() +{ + if (renderer) + { SDL_DestroyRenderer(renderer); renderer = NULL; } - if (window) { + if (window) + { SDL_DestroyWindow(window); window = NULL; } @@ -111,21 +119,22 @@ void cleanup_gui() { * 背景色由GUI_COLOR_BACKGROUND定义 * 每帧都会完全重绘整个画面 */ -void render_game() { +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); } @@ -141,53 +150,72 @@ void render_game() { * 落子后会检查胜负并切换玩家 * 游戏结束后不再响应落子操作 */ -int handle_events() { +int handle_events() +{ SDL_Event event; - - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_EVENT_QUIT: + + 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; - - 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, "轮到白子下棋"); - } + } + 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, "该位置已有棋子,请选择其他位置"); } } + else + { + sprintf(status_message, "该位置已有棋子,请选择其他位置"); + } } - break; + } + break; } } return 1; @@ -203,46 +231,49 @@ int handle_events() { * 棋盘线条颜色由GUI_COLOR_BOARD_LINE定义 * 天元点和星位用黑色小矩形标记 */ -void draw_board() { +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++) { + 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); + SDL_RenderLine(renderer, + BOARD_OFFSET_X, 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; SDL_RenderLine(renderer, - x, BOARD_OFFSET_Y, - x, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE); + x, BOARD_OFFSET_Y, + x, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE); } - + // 绘制天元点和星位 SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); int center = BOARD_SIZE / 2; - + // 天元点 int center_x = BOARD_OFFSET_X + center * CELL_SIZE; int center_y = BOARD_OFFSET_Y + center * CELL_SIZE; SDL_FRect center_rect = {center_x - 2, center_y - 2, 4, 4}; SDL_RenderFillRect(renderer, ¢er_rect); - + // 四个星位 int star_offset = 3; int positions[][2] = { {center - star_offset, center - star_offset}, {center + star_offset, center - star_offset}, {center - star_offset, center + star_offset}, - {center + star_offset, center + star_offset} - }; - - for (int i = 0; i < 4; i++) { + {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}; @@ -260,26 +291,33 @@ void draw_board() { * 通过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) { +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; - + 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); } @@ -301,14 +339,18 @@ void draw_stones() { * 适用于绘制棋子等小尺寸圆形 * 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); - - for (int w = 0; w < radius * 2; w++) { - for (int h = 0; h < radius * 2; h++) { + + 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)) { + if ((dx * dx + dy * dy) <= (radius * radius)) + { SDL_RenderPoint(renderer, center_x + dx, center_y + dy); } } @@ -318,26 +360,31 @@ void draw_circle(int center_x, int center_y, int radius, SDL_Color color) { /** * @brief 绘制UI元素 */ -void draw_ui_elements() { +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) { + + if (!game_over) + { + if (current_player_gui == PLAYER) + { // 黑子回合 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}); // 绘制当前玩家指示器(简单的矩形代替圆形) @@ -363,14 +410,15 @@ void draw_ui_elements() { * @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_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_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); } @@ -385,7 +433,8 @@ int screen_to_board(int screen_x, int screen_y, int* board_x, int* board_y) { * 字符串长度限制为缓冲区大小减1 * 消息可用于游戏状态提示和错误信息显示 */ -void show_message(const char* 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); diff --git a/src/main.c b/src/main.c index 936f066..5eff70e 100644 --- a/src/main.c +++ b/src/main.c @@ -13,10 +13,10 @@ 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" diff --git a/src/network.c b/src/network.c index e82386d..0fff03c 100644 --- a/src/network.c +++ b/src/network.c @@ -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); } \ No newline at end of file diff --git a/src/record.c b/src/record.c index d19bba5..5c19b96 100644 --- a/src/record.c +++ b/src/record.c @@ -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; diff --git a/src/ui.c b/src/ui.c index 2fbe81d..acc73e2 100644 --- a/src/ui.c +++ b/src/ui.c @@ -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