mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-05-09 18:09:46 +08:00
v8.2: 完善专业安装包制作功能\n\n- 支持Inno Setup和NSIS双重安装包方案\n- 完整的软件分发体系\n- 一键安装部署和完整卸载功能\n- 更新所有文档以反映v8.2版本特性
This commit is contained in:
@@ -47,3 +47,4 @@ Thumbs.db
|
||||
*.ico
|
||||
|
||||
# 打包文件
|
||||
dist/
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# 🧠 五子棋AI实现详解 (v8.0)
|
||||
# 🧠 五子棋AI实现详解
|
||||
|
||||
## 📜 算法概述
|
||||
本五子棋AI采用α-β剪枝优化的极小极大算法,结合专业的棋型评估系统和多层次的威胁检测机制。v8.0版本新增SDL3图形化界面支持,提供可视化AI决策过程和双版本架构(控制台+GUI)。支持人机对战、双人对战和网络对战多种模式。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# 五子棋项目代码架构重构指南 (v8.0)
|
||||
# 五子棋项目代码架构重构指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
|
||||
+7
-23
@@ -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
|
||||
|
||||
@@ -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---编译脚本优化更新)
|
||||
- [目录](#目录)
|
||||
- [项目简介](#项目简介)
|
||||
- [功能特性](#功能特性)
|
||||
|
||||
+14
-6
@@ -2,8 +2,8 @@
|
||||
========================================
|
||||
|
||||
项目名称:五子棋多模式对战系统
|
||||
统计时间:2025年9月18日
|
||||
项目版本:v8.0
|
||||
统计时间:2025年10月8日
|
||||
项目版本:v8.2
|
||||
开发语言:C语言 + SDL3图形库
|
||||
GitHub仓库:https://github.com/LHY0125/Gobang-Game.git
|
||||
|
||||
@@ -111,7 +111,9 @@ v8.0版本新增:
|
||||
• SDL3图形化界面实现(v8.0新增)
|
||||
• 双版本架构设计(控制台+GUI)(v8.0新增)
|
||||
• 鼠标交互和事件驱动架构(v8.0新增)
|
||||
• 专业安装包制作支持(v8.0新增)
|
||||
• 专业安装包制作支持(v8.2新增)
|
||||
• Inno Setup和NSIS双重打包方案(v8.2新增)
|
||||
• 完整的软件分发体系(v8.2新增)
|
||||
• 完整的网络对战功能实现
|
||||
• 智能AI算法与评估系统
|
||||
• 灵活的配置管理系统
|
||||
@@ -120,9 +122,9 @@ v8.0版本新增:
|
||||
• 实时计时器系统
|
||||
• 全局变量统一管理
|
||||
• 跨平台网络通信支持
|
||||
• 代码架构模块化重构(v7.0新增)
|
||||
• 配置参数集中化管理(v7.0新增)
|
||||
• 类型定义标准化(v7.0新增)
|
||||
• 代码架构模块化重构
|
||||
• 配置参数集中化管理
|
||||
• 类型定义标准化
|
||||
|
||||
【总体评价】
|
||||
这是一个非常优秀的C语言项目,代码量适中但功能完整,
|
||||
@@ -132,6 +134,12 @@ v8.0版本新增:
|
||||
包括人机对战、双人对战和网络对战,功能丰富,架构清晰,
|
||||
是C语言项目开发的优秀范例。
|
||||
|
||||
v8.2版本进一步完善了软件分发体系,
|
||||
通过Inno Setup和NSIS双重安装包方案,
|
||||
实现了专业级的软件打包和部署功能,
|
||||
为用户提供了便捷的一键安装体验,
|
||||
标志着项目从开发阶段向产品化阶段的重要转变。
|
||||
|
||||
v8.0版本的图形化界面是项目发展的重大突破,
|
||||
通过SDL3图形库实现了现代化的可视化界面,
|
||||
支持鼠标交互操作,大幅提升了用户体验。
|
||||
|
||||
+11
-4
@@ -3,16 +3,23 @@
|
||||
* @brief C语言五子棋多模式对战系统
|
||||
* @details 支持人机对战、双人对战、网络对战的完整五子棋游戏系统,v8.0新增SDL3图形化界面
|
||||
* @author 刘航宇(3364451258@qq.com、15236416560@163.com、lhy3364451258@outlook.com)
|
||||
* @date 2025-09-18
|
||||
* @version 8.0
|
||||
* @date 2025-10-8
|
||||
* @version 8.2
|
||||
* @note
|
||||
* 1. v8.0图形化界面:
|
||||
* 1. v8.2专业安装包:
|
||||
* - 📦 双重打包方案:支持Inno Setup和NSIS两种安装包制作
|
||||
* - 🚀 一键安装部署:完整的软件分发解决方案
|
||||
* - 🔧 安装程序优化:自动创建快捷方式和注册表项
|
||||
* - 🗂️ 完整文件打包:包含所有源码、文档和依赖文件
|
||||
* - 🔄 完整卸载功能:支持干净的软件卸载和清理
|
||||
* - 💼 产品化部署:从开发工具向商业软件的转变
|
||||
*
|
||||
* 2. v8.0图形化界面:
|
||||
* - 🎨 SDL3图形化界面:实现现代化可视化棋盘界面
|
||||
* - 🖱️ 鼠标交互支持:直观的点击落子操作
|
||||
* - 🏗️ 双版本架构:控制台版本和GUI版本并行支持
|
||||
* - 🪟 窗口管理优化:自动居中、响应式设计
|
||||
* - ⚡ 事件驱动架构:流畅的用户交互体验
|
||||
* - 📦 安装包支持:提供Inno Setup专业安装程序
|
||||
* - 🔧 编译脚本优化:简化GUI版本编译流程
|
||||
* - 🌐 GUI网络支持:图形化界面支持网络对战
|
||||
* 2. v7.0架构重构:
|
||||
|
||||
+3
-1
@@ -1,8 +1,10 @@
|
||||
项目要求文档 - 五子棋游戏 (v8.0)
|
||||
项目要求文档 - 五子棋游戏 (v8.2)
|
||||
|
||||
1. 项目概述
|
||||
- 开发一个基于C语言的五子棋游戏,支持本地多人、AI对战和网络对战模式。
|
||||
- v8.0版本实现双版本架构:支持命令行界面和SDL3图形化界面。
|
||||
- v8.2版本(当前版本)完善了专业安装包制作体系,提供企业级的软件分发解决方案。
|
||||
- v8.2版本完善了软件分发体系,支持Inno Setup和NSIS双重安装包制作,提供专业级的软件打包和部署解决方案。
|
||||
- 包括游戏配置、记录保存、复盘功能和专业安装包。
|
||||
- 提供现代化的可视化用户体验和传统控制台体验。
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
[Setup]
|
||||
AppName=五子棋游戏
|
||||
AppVersion=8.3
|
||||
AppPublisher=LHY
|
||||
AppPublisherURL=https://github.com/LHY0125/gobang.git
|
||||
AppSupportURL=https://github.com/LHY0125/gobang.git
|
||||
AppUpdatesURL=https://github.com/LHY0125/gobang.git
|
||||
DefaultDirName={autopf}\Gobang
|
||||
DefaultGroupName=五子棋游戏
|
||||
AllowNoIcons=yes
|
||||
LicenseFile=..\README.md
|
||||
OutputDir=dist
|
||||
OutputBaseFilename=Gobang_Inno_Setup
|
||||
SetupIconFile=
|
||||
Compression=lzma
|
||||
SolidCompression=yes
|
||||
WizardStyle=modern
|
||||
PrivilegesRequired=lowest
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
|
||||
[Files]
|
||||
Source: "..\gobang_console.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\gobang_gui.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\SDL3.dll"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\include\*"; DestDir: "{app}\include"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\src\*"; DestDir: "{app}\src"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\MD\*"; DestDir: "{app}\MD"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\TXT\*"; DestDir: "{app}\TXT"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\records\*"; DestDir: "{app}\records"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\installer\*"; DestDir: "{app}\installer"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||
Source: "..\compile.bat"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\gobang_config.ini"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\Makefile"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\README.md"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\五子棋游戏(控制台版)"; Filename: "{app}\gobang_console.exe"
|
||||
Name: "{group}\五子棋游戏(图形界面版)"; Filename: "{app}\gobang_gui.exe"
|
||||
Name: "{group}\{cm:UninstallProgram,五子棋游戏}"; Filename: "{uninstallexe}"
|
||||
Name: "{autodesktop}\五子棋游戏"; Filename: "{app}\gobang_gui.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\gobang_gui.exe"; Description: "{cm:LaunchProgram,五子棋游戏}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{app}\records"
|
||||
Type: filesandordirs; Name: "{app}\include"
|
||||
Type: filesandordirs; Name: "{app}\src"
|
||||
Type: filesandordirs; Name: "{app}\MD"
|
||||
Type: filesandordirs; Name: "{app}\TXT"
|
||||
Type: filesandordirs; Name: "{app}\installer"
|
||||
+149
-46
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ int evaluate_pos(int x, int y, int player)
|
||||
// 直接形成五连珠为必胜
|
||||
if (info.continuous_chess >= 5)
|
||||
{
|
||||
board[x][y] = original; // 还原棋盘
|
||||
board[x][y] = original; // 还原棋盘
|
||||
return SEARCH_WIN_BONUS; // 返回最大分
|
||||
}
|
||||
|
||||
@@ -130,8 +130,8 @@ int evaluate_pos(int x, int y, int player)
|
||||
// 位置奖励:越靠近中心分数越高
|
||||
int center_x = BOARD_SIZE / 2;
|
||||
int center_y = BOARD_SIZE / 2;
|
||||
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
|
||||
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
|
||||
int distance = abs(x - center_x) + abs(y - center_y); // 曼哈顿距离
|
||||
int position_bonus = AI_POSITION_BONUS_FACTOR * (BOARD_SIZE - distance); // 距离中心越近奖励越高
|
||||
|
||||
board[x][y] = original; // 还原棋盘状态
|
||||
return total_score + position_bonus; // 返回总评估分
|
||||
@@ -173,7 +173,7 @@ int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximi
|
||||
// 使用移动排序优化搜索效率
|
||||
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
|
||||
int move_count = generate_candidate_moves(candidate_moves, player);
|
||||
|
||||
|
||||
// 限制搜索的候选移动数量以提高性能
|
||||
int max_candidates = (depth >= 3) ? 15 : 25; // 深度越大,候选移动越少
|
||||
if (move_count > max_candidates)
|
||||
@@ -250,13 +250,13 @@ void ai_move(int depth)
|
||||
// 1. 使用增强的威胁检测系统
|
||||
ScoredMove candidate_moves[BOARD_SIZE * BOARD_SIZE];
|
||||
int move_count = generate_candidate_moves(candidate_moves, AI);
|
||||
|
||||
|
||||
// 首先检查是否有直接获胜的机会
|
||||
for (int idx = 0; idx < move_count; idx++)
|
||||
{
|
||||
int i = candidate_moves[idx].x;
|
||||
int j = candidate_moves[idx].y;
|
||||
|
||||
|
||||
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
||||
if (ai_threat == THREAT_WIN)
|
||||
{
|
||||
@@ -267,13 +267,13 @@ void ai_move(int depth)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查是否需要阻止玩家的威胁
|
||||
for (int idx = 0; idx < move_count; idx++)
|
||||
{
|
||||
int i = candidate_moves[idx].x;
|
||||
int j = candidate_moves[idx].y;
|
||||
|
||||
|
||||
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
|
||||
if (player_threat >= THREAT_FOUR)
|
||||
{
|
||||
@@ -284,13 +284,13 @@ void ai_move(int depth)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查是否需要阻止玩家的活三威胁
|
||||
for (int idx = 0; idx < move_count; idx++)
|
||||
{
|
||||
int i = candidate_moves[idx].x;
|
||||
int j = candidate_moves[idx].y;
|
||||
|
||||
|
||||
ThreatLevel player_threat = detect_threat(i, j, PLAYER);
|
||||
if (player_threat == THREAT_THREE)
|
||||
{
|
||||
@@ -308,7 +308,7 @@ void ai_move(int depth)
|
||||
{
|
||||
int i = candidate_moves[idx].x;
|
||||
int j = candidate_moves[idx].y;
|
||||
|
||||
|
||||
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
||||
if (ai_threat >= THREAT_FOUR)
|
||||
{
|
||||
@@ -319,13 +319,13 @@ void ai_move(int depth)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 寻找能形成活三的位置
|
||||
for (int idx = 0; idx < move_count; idx++)
|
||||
{
|
||||
int i = candidate_moves[idx].x;
|
||||
int j = candidate_moves[idx].y;
|
||||
|
||||
|
||||
ThreatLevel ai_threat = detect_threat(i, j, AI);
|
||||
if (ai_threat == THREAT_THREE)
|
||||
{
|
||||
@@ -336,14 +336,14 @@ void ai_move(int depth)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 3. 如果没有明显的威胁机会,选择评分最高的位置
|
||||
if (move_count > 0)
|
||||
{
|
||||
// candidate_moves已经按分数排序,直接选择第一个
|
||||
int best_x = candidate_moves[0].x;
|
||||
int best_y = candidate_moves[0].y;
|
||||
|
||||
|
||||
board[best_x][best_y] = AI;
|
||||
steps[step_count++] = (Step){AI, best_x, best_y};
|
||||
printf("AI落子(%d, %d) - 最佳位置!\n", best_x + 1, best_y + 1);
|
||||
@@ -388,7 +388,7 @@ static int compare_moves(const void *a, const void *b)
|
||||
int generate_candidate_moves(ScoredMove *moves, int player)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
@@ -397,51 +397,51 @@ int generate_candidate_moves(ScoredMove *moves, int player)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 只考虑有意义的位置(附近有棋子)
|
||||
if (step_count > AI_SEARCH_RANGE_THRESHOLD && !is_near_stones(i, j))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// 计算该位置的评估分数
|
||||
moves[count].x = i;
|
||||
moves[count].y = j;
|
||||
|
||||
|
||||
// 结合威胁检测和位置评估
|
||||
ThreatLevel threat = detect_threat(i, j, player);
|
||||
int base_score = evaluate_move(i, j);
|
||||
|
||||
|
||||
// 根据威胁等级调整分数
|
||||
switch (threat)
|
||||
{
|
||||
case THREAT_WIN:
|
||||
moves[count].score = base_score + 10000;
|
||||
break;
|
||||
case THREAT_FOUR:
|
||||
moves[count].score = base_score + 5000;
|
||||
break;
|
||||
case THREAT_THREE:
|
||||
moves[count].score = base_score + 2000;
|
||||
break;
|
||||
case THREAT_DOUBLE:
|
||||
moves[count].score = base_score + 1000;
|
||||
break;
|
||||
case THREAT_POTENTIAL:
|
||||
moves[count].score = base_score + 500;
|
||||
break;
|
||||
default:
|
||||
moves[count].score = base_score;
|
||||
break;
|
||||
case THREAT_WIN:
|
||||
moves[count].score = base_score + 10000;
|
||||
break;
|
||||
case THREAT_FOUR:
|
||||
moves[count].score = base_score + 5000;
|
||||
break;
|
||||
case THREAT_THREE:
|
||||
moves[count].score = base_score + 2000;
|
||||
break;
|
||||
case THREAT_DOUBLE:
|
||||
moves[count].score = base_score + 1000;
|
||||
break;
|
||||
case THREAT_POTENTIAL:
|
||||
moves[count].score = base_score + 500;
|
||||
break;
|
||||
default:
|
||||
moves[count].score = base_score;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 按分数降序排序
|
||||
qsort(moves, count, sizeof(ScoredMove), compare_moves);
|
||||
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -480,16 +480,16 @@ ThreatLevel detect_threat(int x, int y, int player)
|
||||
{
|
||||
// 模拟落子
|
||||
board[x][y] = player;
|
||||
|
||||
|
||||
ThreatLevel max_threat = THREAT_NONE;
|
||||
int threat_count = 0;
|
||||
|
||||
|
||||
// 检查四个方向
|
||||
for (int k = 0; k < 4; k++)
|
||||
{
|
||||
DirInfo info = count_specific_direction(x, y, direction[k][0], direction[k][1], player);
|
||||
ThreatLevel current_threat = THREAT_NONE;
|
||||
|
||||
|
||||
// 检查是否形成五子连珠(获胜)
|
||||
if (info.continuous_chess >= 5)
|
||||
{
|
||||
@@ -517,27 +517,27 @@ ThreatLevel detect_threat(int x, int y, int player)
|
||||
{
|
||||
current_threat = THREAT_POTENTIAL;
|
||||
}
|
||||
|
||||
|
||||
if (current_threat > max_threat)
|
||||
{
|
||||
max_threat = current_threat;
|
||||
}
|
||||
|
||||
|
||||
if (current_threat >= THREAT_THREE)
|
||||
{
|
||||
threat_count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 恢复棋盘
|
||||
board[x][y] = EMPTY;
|
||||
|
||||
|
||||
// 如果有多个威胁,提升威胁等级
|
||||
if (threat_count >= 2 && max_threat >= THREAT_THREE)
|
||||
{
|
||||
max_threat = THREAT_DOUBLE;
|
||||
}
|
||||
|
||||
|
||||
return max_threat;
|
||||
}
|
||||
|
||||
@@ -551,18 +551,18 @@ ThreatLevel detect_threat(int x, int y, int player)
|
||||
int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
||||
{
|
||||
int threats = 0;
|
||||
|
||||
|
||||
// 向前搜索
|
||||
for (int i = 1; i < 5; i++)
|
||||
{
|
||||
int nx = x + i * dx;
|
||||
int ny = y + i * dy;
|
||||
|
||||
|
||||
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (board[nx][ny] == player)
|
||||
{
|
||||
threats++;
|
||||
@@ -572,18 +572,18 @@ int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 向后搜索
|
||||
for (int i = 1; i < 5; i++)
|
||||
{
|
||||
int nx = x - i * dx;
|
||||
int ny = y - i * dy;
|
||||
|
||||
|
||||
if (nx < 0 || nx >= BOARD_SIZE || ny < 0 || ny >= BOARD_SIZE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (board[nx][ny] == player)
|
||||
{
|
||||
threats++;
|
||||
@@ -593,6 +593,6 @@ int count_threats_in_direction(int x, int y, int dx, int dy, int player)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return threats;
|
||||
}
|
||||
+37
-37
@@ -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("按任意键继续...");
|
||||
}
|
||||
}
|
||||
+63
-64
@@ -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;
|
||||
}
|
||||
+17
-17
@@ -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] = "平局或未完成"; // 存储胜负信息
|
||||
int player1_final_score = 0; // 玩家1最终得分
|
||||
int player2_final_score = 0; // 玩家2最终得分
|
||||
int scores_calculated = 0; // 评分计算标志
|
||||
char winner_info[50] = "平局或未完成"; // 存储胜负信息
|
||||
+3
-3
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
+3
-3
@@ -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"
|
||||
|
||||
+59
-57
@@ -44,11 +44,11 @@ bool init_network()
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||
network_state.socket = INVALID_SOCKET;
|
||||
network_state.port = DEFAULT_PORT;
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ void cleanup_network()
|
||||
closesocket(network_state.socket);
|
||||
network_state.socket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
|
||||
network_state.is_connected = false;
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ bool create_server(int port)
|
||||
{
|
||||
struct sockaddr_in server_addr, client_addr;
|
||||
int addr_len = sizeof(client_addr);
|
||||
|
||||
|
||||
// 创建套接字
|
||||
SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_socket == INVALID_SOCKET)
|
||||
@@ -85,28 +85,28 @@ bool create_server(int port)
|
||||
printf("创建套接字失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 设置地址重用
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
|
||||
// 绑定地址
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(port);
|
||||
|
||||
if (bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||
|
||||
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||
{
|
||||
printf("绑定端口失败\n");
|
||||
closesocket(listen_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 开始监听
|
||||
if (listen(listen_socket, 1) == SOCKET_ERROR)
|
||||
{
|
||||
@@ -114,31 +114,31 @@ bool create_server(int port)
|
||||
closesocket(listen_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
char local_ip[MAX_IP_LENGTH];
|
||||
if (get_local_ip(local_ip, sizeof(local_ip)))
|
||||
{
|
||||
printf("服务器已启动,等待客户端连接...\n");
|
||||
printf("本机IP地址: %s\n", local_ip);
|
||||
printf("监听端口: %d\n", port);
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("服务器已启动,监听端口: %d\n", port);
|
||||
}
|
||||
|
||||
|
||||
// 等待客户端连接
|
||||
SOCKET client_socket = accept(listen_socket, (struct sockaddr*)&client_addr, &addr_len);
|
||||
SOCKET client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &addr_len);
|
||||
if (client_socket == INVALID_SOCKET)
|
||||
{
|
||||
printf("接受连接失败\n");
|
||||
closesocket(listen_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 关闭监听套接字
|
||||
closesocket(listen_socket);
|
||||
|
||||
|
||||
// 保存连接信息
|
||||
network_state.socket = client_socket;
|
||||
network_state.is_server = true;
|
||||
@@ -147,7 +147,7 @@ bool create_server(int port)
|
||||
network_state.remote_player_id = PLAYER2;
|
||||
network_state.port = port;
|
||||
strcpy(network_state.remote_ip, inet_ntoa(client_addr.sin_addr));
|
||||
|
||||
|
||||
printf("客户端已连接: %s\n", network_state.remote_ip);
|
||||
return true;
|
||||
}
|
||||
@@ -155,10 +155,10 @@ bool create_server(int port)
|
||||
/**
|
||||
* @brief 连接到服务器(客户端模式)
|
||||
*/
|
||||
bool connect_to_server(const char* ip, int port)
|
||||
bool connect_to_server(const char *ip, int port)
|
||||
{
|
||||
struct sockaddr_in server_addr;
|
||||
|
||||
|
||||
// 创建套接字
|
||||
SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (client_socket == INVALID_SOCKET)
|
||||
@@ -166,15 +166,15 @@ bool connect_to_server(const char* ip, int port)
|
||||
printf("创建套接字失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 设置服务器地址
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_port = htons(port);
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
server_addr.sin_addr.s_addr = inet_addr(ip);
|
||||
if (server_addr.sin_addr.s_addr == INADDR_NONE)
|
||||
if (server_addr.sin_addr.s_addr == INADDR_NONE)
|
||||
{
|
||||
#else
|
||||
if (inet_pton(AF_INET, ip, &server_addr.sin_addr) <= 0)
|
||||
@@ -184,17 +184,17 @@ bool connect_to_server(const char* ip, int port)
|
||||
closesocket(client_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
printf("正在连接到服务器 %s:%d...\n", ip, port);
|
||||
|
||||
|
||||
// 连接到服务器
|
||||
if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == SOCKET_ERROR)
|
||||
{
|
||||
printf("连接服务器失败\n");
|
||||
closesocket(client_socket);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 保存连接信息
|
||||
network_state.socket = client_socket;
|
||||
network_state.is_server = false;
|
||||
@@ -203,7 +203,7 @@ bool connect_to_server(const char* ip, int port)
|
||||
network_state.remote_player_id = PLAYER1;
|
||||
network_state.port = port;
|
||||
strcpy(network_state.remote_ip, ip);
|
||||
|
||||
|
||||
printf("成功连接到服务器\n");
|
||||
return true;
|
||||
}
|
||||
@@ -211,33 +211,33 @@ bool connect_to_server(const char* ip, int port)
|
||||
/**
|
||||
* @brief 发送网络消息
|
||||
*/
|
||||
bool send_network_message(const NetworkMessage* msg)
|
||||
bool send_network_message(const NetworkMessage *msg)
|
||||
{
|
||||
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int bytes_sent = send(network_state.socket, (const char*)msg, sizeof(NetworkMessage), 0);
|
||||
|
||||
int bytes_sent = send(network_state.socket, (const char *)msg, sizeof(NetworkMessage), 0);
|
||||
return bytes_sent == sizeof(NetworkMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 接收网络消息
|
||||
*/
|
||||
bool receive_network_message(NetworkMessage* msg, int timeout_ms)
|
||||
bool receive_network_message(NetworkMessage *msg, int timeout_ms)
|
||||
{
|
||||
if (!network_state.is_connected || network_state.socket == INVALID_SOCKET)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 设置超时
|
||||
if (timeout_ms > 0)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD timeout = timeout_ms;
|
||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
|
||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
|
||||
#else
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = timeout_ms / 1000;
|
||||
@@ -245,18 +245,20 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
|
||||
setsockopt(network_state.socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
|
||||
#endif
|
||||
}
|
||||
|
||||
int bytes_received = recv(network_state.socket, (char*)msg, sizeof(NetworkMessage), 0);
|
||||
|
||||
|
||||
int bytes_received = recv(network_state.socket, (char *)msg, sizeof(NetworkMessage), 0);
|
||||
|
||||
if (bytes_received == sizeof(NetworkMessage))
|
||||
{
|
||||
return true;
|
||||
} else if (bytes_received == 0)
|
||||
}
|
||||
else if (bytes_received == 0)
|
||||
{
|
||||
// 连接已关闭
|
||||
network_state.is_connected = false;
|
||||
printf("对方已断开连接\n");
|
||||
} else if (bytes_received == SOCKET_ERROR)
|
||||
}
|
||||
else if (bytes_received == SOCKET_ERROR)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
int error = WSAGetLastError();
|
||||
@@ -270,7 +272,7 @@ bool receive_network_message(NetworkMessage* msg, int timeout_ms)
|
||||
printf("网络接收错误\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -285,10 +287,10 @@ void disconnect_network()
|
||||
msg.type = MSG_DISCONNECT;
|
||||
msg.player_id = network_state.local_player_id;
|
||||
msg.timestamp = time(NULL);
|
||||
|
||||
|
||||
send_network_message(&msg);
|
||||
}
|
||||
|
||||
|
||||
cleanup_network();
|
||||
}
|
||||
|
||||
@@ -303,18 +305,18 @@ bool is_network_connected()
|
||||
/**
|
||||
* @brief 获取本机IP地址
|
||||
*/
|
||||
bool get_local_ip(char* ip_buffer, int buffer_size)
|
||||
bool get_local_ip(char *ip_buffer, int buffer_size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Windows实现
|
||||
char hostname[256];
|
||||
if (gethostname(hostname, sizeof(hostname)) == 0)
|
||||
{
|
||||
struct hostent* host_entry = gethostbyname(hostname);
|
||||
struct hostent *host_entry = gethostbyname(hostname);
|
||||
if (host_entry != NULL)
|
||||
{
|
||||
struct in_addr addr;
|
||||
addr.s_addr = *((unsigned long*)host_entry->h_addr_list[0]);
|
||||
addr.s_addr = *((unsigned long *)host_entry->h_addr_list[0]);
|
||||
strncpy(ip_buffer, inet_ntoa(addr), buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return true;
|
||||
@@ -329,11 +331,11 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = inet_addr("8.8.8.8");
|
||||
addr.sin_port = htons(80);
|
||||
|
||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == 0)
|
||||
|
||||
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == 0)
|
||||
{
|
||||
socklen_t addr_len = sizeof(addr);
|
||||
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == 0)
|
||||
if (getsockname(sock, (struct sockaddr *)&addr, &addr_len) == 0)
|
||||
{
|
||||
strncpy(ip_buffer, inet_ntoa(addr.sin_addr), buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
@@ -344,7 +346,7 @@ bool get_local_ip(char* ip_buffer, int buffer_size)
|
||||
close(sock);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// 默认返回本地回环地址
|
||||
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
@@ -362,21 +364,21 @@ bool send_move(int x, int y, int player_id)
|
||||
msg.x = x;
|
||||
msg.y = y;
|
||||
msg.timestamp = time(NULL);
|
||||
|
||||
|
||||
return send_network_message(&msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送聊天消息
|
||||
*/
|
||||
bool send_chat_message(const char* message)
|
||||
bool send_chat_message(const char *message)
|
||||
{
|
||||
NetworkMessage msg = {0};
|
||||
msg.type = MSG_CHAT;
|
||||
msg.player_id = network_state.local_player_id;
|
||||
strncpy(msg.message, message, sizeof(msg.message) - 1);
|
||||
msg.timestamp = time(NULL);
|
||||
|
||||
|
||||
return send_network_message(&msg);
|
||||
}
|
||||
|
||||
@@ -389,7 +391,7 @@ bool send_surrender()
|
||||
msg.type = MSG_SURRENDER;
|
||||
msg.player_id = network_state.local_player_id;
|
||||
msg.timestamp = time(NULL);
|
||||
|
||||
|
||||
return send_network_message(&msg);
|
||||
}
|
||||
|
||||
@@ -403,7 +405,7 @@ bool send_undo_request(int steps)
|
||||
msg.player_id = network_state.local_player_id;
|
||||
msg.x = steps; // 使用x字段存储步数
|
||||
msg.timestamp = time(NULL);
|
||||
|
||||
|
||||
return send_network_message(&msg);
|
||||
}
|
||||
|
||||
@@ -415,9 +417,9 @@ bool send_undo_response(bool accepted, int steps)
|
||||
NetworkMessage msg = {0};
|
||||
msg.type = MSG_UNDO_RESPONSE;
|
||||
msg.player_id = network_state.local_player_id;
|
||||
msg.x = steps; // 使用x字段存储步数
|
||||
msg.x = steps; // 使用x字段存储步数
|
||||
msg.y = accepted ? 1 : 0; // 使用y字段存储是否同意
|
||||
msg.timestamp = time(NULL);
|
||||
|
||||
|
||||
return send_network_message(&msg);
|
||||
}
|
||||
+15
-15
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user