mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-29 17:15:55 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6053585d4 | |||
| e41e856fe0 | |||
| bf21efbbc0 | |||
| b616a4662b | |||
| 96a94aaddf | |||
| f897536a45 |
@@ -35,6 +35,13 @@ dist/
|
||||
|
||||
# 编译生成的对象文件
|
||||
obj/
|
||||
build/
|
||||
|
||||
# 临时游戏存档
|
||||
records/
|
||||
|
||||
# 运行时配置(含 API Key)
|
||||
bin/gobang_config.ini
|
||||
|
||||
# 编译产物
|
||||
bin/gobang_gui.exe
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(Gobang VERSION 9.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 17)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
||||
# === 输出目录:与原 Makefile 保持一致 ===
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)
|
||||
|
||||
# === 编译选项 ===
|
||||
add_compile_options(-Wall -Wextra -O2)
|
||||
add_compile_options(-finput-charset=UTF-8 -fexec-charset=UTF-8)
|
||||
|
||||
# === ENet(从源码编译静态库)===
|
||||
set(ENET_DIR ${CMAKE_SOURCE_DIR}/libs/ENET)
|
||||
add_library(enet STATIC
|
||||
${ENET_DIR}/callbacks.c
|
||||
${ENET_DIR}/compress.c
|
||||
${ENET_DIR}/host.c
|
||||
${ENET_DIR}/list.c
|
||||
${ENET_DIR}/packet.c
|
||||
${ENET_DIR}/peer.c
|
||||
${ENET_DIR}/protocol.c
|
||||
${ENET_DIR}/win32.c
|
||||
)
|
||||
target_include_directories(enet PUBLIC ${ENET_DIR}/include)
|
||||
|
||||
# === IUP(预构建导入库)===
|
||||
set(IUP_DIR ${CMAKE_SOURCE_DIR}/libs/IUP)
|
||||
add_library(iup SHARED IMPORTED)
|
||||
set_target_properties(iup PROPERTIES
|
||||
IMPORTED_IMPLIB ${IUP_DIR}/libiup.a
|
||||
IMPORTED_LOCATION ${IUP_DIR}/iup.dll
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${IUP_DIR}/include
|
||||
)
|
||||
|
||||
# === cJSON(JSON处理库)===
|
||||
add_library(cjson STATIC ${CMAKE_SOURCE_DIR}/libs/cJSON/cJSON.c)
|
||||
target_include_directories(cjson PUBLIC ${CMAKE_SOURCE_DIR}/libs/cJSON)
|
||||
|
||||
# === 主可执行文件 ===
|
||||
add_executable(gobang_gui
|
||||
src/core/main.c
|
||||
src/core/globals.c
|
||||
src/core/config.c
|
||||
src/core/gobang.c
|
||||
src/core/ai.c
|
||||
src/network/network.c
|
||||
src/record/record.c
|
||||
src/gui/gui_core.c
|
||||
src/gui/gui_game.c
|
||||
src/gui/gui_menu.c
|
||||
src/gui/gui_replay.c
|
||||
src/llm/llm_ai.c
|
||||
)
|
||||
|
||||
target_include_directories(gobang_gui PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
|
||||
target_link_libraries(gobang_gui PRIVATE
|
||||
enet
|
||||
cjson
|
||||
iup
|
||||
winhttp
|
||||
ws2_32
|
||||
winmm
|
||||
gdi32
|
||||
comdlg32
|
||||
comctl32
|
||||
uuid
|
||||
ole32
|
||||
)
|
||||
|
||||
# === 构建后拷贝 iup.dll 到 bin/ ===
|
||||
add_custom_command(TARGET gobang_gui POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${IUP_DIR}/iup.dll
|
||||
$<TARGET_FILE_DIR:gobang_gui>
|
||||
COMMENT "拷贝 iup.dll 到输出目录"
|
||||
)
|
||||
|
||||
# === 快捷运行目标 ===
|
||||
add_custom_target(run
|
||||
COMMAND gobang_gui
|
||||
DEPENDS gobang_gui
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/bin
|
||||
COMMENT "运行五子棋游戏"
|
||||
)
|
||||
@@ -1,102 +0,0 @@
|
||||
# 五子棋游戏 Makefile
|
||||
# 支持编译GUI版本 (IUP)
|
||||
|
||||
# 编译器设置
|
||||
CC = gcc
|
||||
# 显式指定 Shell 为 PowerShell
|
||||
SHELL = D:/PowerShell/PowerShell-7.5.4/PowerShell.exe
|
||||
.SHELLFLAGS = -NoProfile -Command
|
||||
|
||||
CFLAGS = -Wall -Wextra -std=c17 -O2 -Iinclude -finput-charset=UTF-8 -fexec-charset=UTF-8
|
||||
# ENet 包含路径
|
||||
ENET_INC = -Ilibs/enet/include
|
||||
CFLAGS += $(ENET_INC)
|
||||
|
||||
LDFLAGS = -lws2_32 -lwinmm
|
||||
|
||||
|
||||
# IUP路径设置
|
||||
IUP_PATH = libs/iup-3.31_Win64_dllw6_lib
|
||||
IUP_INCLUDE = "-I$(IUP_PATH)/include"
|
||||
# IUP链接库: iup, gdi32, comdlg32, comctl32, uuid, ole32
|
||||
IUP_LIBS = "-L$(IUP_PATH)" -liup -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32
|
||||
|
||||
# 目录设置
|
||||
SRC_DIR = src
|
||||
OBJ_DIR = obj
|
||||
BIN_DIR = bin
|
||||
|
||||
# 源文件
|
||||
COMMON_SOURCES = $(SRC_DIR)/gobang.c $(SRC_DIR)/ai.c $(SRC_DIR)/config.c \
|
||||
$(SRC_DIR)/globals.c \
|
||||
$(SRC_DIR)/network.c $(SRC_DIR)/record.c \
|
||||
$(SRC_DIR)/gui_core.c $(SRC_DIR)/gui_draw.c \
|
||||
$(SRC_DIR)/gui_game.c $(SRC_DIR)/gui_replay.c \
|
||||
$(SRC_DIR)/gui_menu.c
|
||||
|
||||
# ENet 源文件
|
||||
ENET_SOURCES = libs/enet/callbacks.c libs/enet/compress.c libs/enet/host.c \
|
||||
libs/enet/list.c libs/enet/packet.c libs/enet/peer.c \
|
||||
libs/enet/protocol.c libs/enet/win32.c
|
||||
|
||||
# 目标文件 (src/xxx.c -> obj/xxx.o)
|
||||
COMMON_OBJECTS = $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(COMMON_SOURCES))
|
||||
ENET_OBJECTS = $(patsubst libs/enet/%.c,$(OBJ_DIR)/enet_%.o,$(ENET_SOURCES))
|
||||
|
||||
# 可执行文件
|
||||
GUI_TARGET = $(BIN_DIR)/gobang_gui.exe
|
||||
|
||||
# 默认目标
|
||||
all: directories $(GUI_TARGET)
|
||||
|
||||
# 创建目录 (PowerShell 语法)
|
||||
directories:
|
||||
if (!(Test-Path "$(OBJ_DIR)")) { New-Item -ItemType Directory -Path "$(OBJ_DIR)" | Out-Null }
|
||||
if (!(Test-Path "$(BIN_DIR)")) { New-Item -ItemType Directory -Path "$(BIN_DIR)" | Out-Null }
|
||||
|
||||
# GUI版本
|
||||
$(GUI_TARGET): $(COMMON_OBJECTS) $(ENET_OBJECTS) $(OBJ_DIR)/main.o
|
||||
$(CC) $(CFLAGS) $(IUP_INCLUDE) -o $@ $^ $(IUP_LIBS) $(LDFLAGS)
|
||||
Copy-Item -Path "$(subst /,\,$(IUP_PATH))\iup.dll" -Destination "$(BIN_DIR)" -Force
|
||||
|
||||
# 通用目标文件编译规则
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
|
||||
$(CC) $(CFLAGS) $(IUP_INCLUDE) -c -o $@ $<
|
||||
|
||||
# 编译 ENet 源文件
|
||||
$(OBJ_DIR)/enet_%.o: libs/enet/%.c | directories
|
||||
$(CC) $(CFLAGS) $(ENET_INC) -c -o $@ $<
|
||||
|
||||
# 编译 main.c
|
||||
$(OBJ_DIR)/main.o: $(SRC_DIR)/main.c
|
||||
$(CC) $(CFLAGS) $(IUP_INCLUDE) -c -o $@ $<
|
||||
|
||||
# 清理规则 (PowerShell 语法)
|
||||
clean:
|
||||
if (Test-Path "$(OBJ_DIR)") { Remove-Item -Path "$(OBJ_DIR)" -Recurse -Force }
|
||||
if (Test-Path "$(BIN_DIR)") { Remove-Item -Path "$(BIN_DIR)" -Recurse -Force }
|
||||
|
||||
# 编译GUI版本
|
||||
gui: directories $(GUI_TARGET)
|
||||
|
||||
# 安装规则(可选)
|
||||
install: all
|
||||
Write-Host "Installing executables..."
|
||||
Copy-Item -Path "$(GUI_TARGET)" -Destination "C:\Program Files\Gobang\" -Force
|
||||
|
||||
# 运行GUI版本
|
||||
run-gui: $(GUI_TARGET)
|
||||
& ".\$(GUI_TARGET)"
|
||||
|
||||
# 帮助信息
|
||||
help:
|
||||
@echo Available targets:
|
||||
@echo all - Build GUI version
|
||||
@echo gui - Build GUI version
|
||||
@echo clean - Remove all object files and executables
|
||||
@echo run-gui - Build and run GUI version
|
||||
@echo install - Install executables to system directory
|
||||
@echo help - Show this help message
|
||||
|
||||
# 声明伪目标
|
||||
.PHONY: all clean gui install run-gui help directories
|
||||
@@ -32,13 +32,6 @@ int evaluate_move(int x, int y);
|
||||
*/
|
||||
int evaluate_pos(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 评估棋盘价值
|
||||
*
|
||||
* @param player 玩家标识(PLAYER/AI)
|
||||
*/
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing);
|
||||
|
||||
/**
|
||||
* @brief AI下棋
|
||||
*
|
||||
@@ -71,13 +64,5 @@ bool is_near_stones(int x, int y);
|
||||
*/
|
||||
ThreatLevel detect_threat(int x, int y, int player);
|
||||
|
||||
/**
|
||||
* @brief 计算指定方向的威胁数量
|
||||
* @param x, y 起始位置
|
||||
* @param dx, dy 方向向量
|
||||
* @param player 玩家
|
||||
* @return 威胁数量
|
||||
*/
|
||||
int count_threats_in_direction(int x, int y, int dx, int dy, int player);
|
||||
|
||||
#endif // AI_H
|
||||
+58
-13
@@ -34,7 +34,7 @@
|
||||
//---------- 游戏设置默认值 ----------//
|
||||
#define DEFAULT_USE_FORBIDDEN_MOVES false // 默认不启用禁手规则
|
||||
#define DEFAULT_USE_TIMER 0 // 默认不启用计时器
|
||||
#define DEFAULT_TIME_LIMIT 30 // 默认时间限制为30秒(内部存储)
|
||||
#define DEFAULT_TIME_LIMIT 1800 // 默认时间限制为30分钟(内部以秒存储: 30*60)
|
||||
|
||||
//---------- AI参数 ----------//
|
||||
#define DEFAULT_AI_DEPTH 5 // 默认AI搜索深度
|
||||
@@ -48,8 +48,6 @@
|
||||
#define NETWORK_BUFFER_SIZE 1024 // 网络缓冲区大小
|
||||
|
||||
// 网络配置
|
||||
#define DEFAULT_PORT 8888 // 默认端口(与DEFAULT_NETWORK_PORT保持一致)
|
||||
#define BUFFER_SIZE 1024 // 缓冲区大小(与NETWORK_BUFFER_SIZE保持一致)
|
||||
#define MAX_IP_LENGTH 16 // 最大IP地址长度
|
||||
|
||||
// 网络消息类型
|
||||
@@ -65,7 +63,7 @@
|
||||
|
||||
//---------- 评分参数 ----------//
|
||||
// 棋型评分 - 用于calculate_step_score函数
|
||||
#define SCORE_FIVE 0 // 五连
|
||||
#define SCORE_FIVE 5000 // 五连
|
||||
#define SCORE_LIVE_FOUR 2000 // 活四
|
||||
#define SCORE_RUSH_FOUR 1000 // 冲四
|
||||
#define SCORE_DEAD_FOUR 300 // 死四
|
||||
@@ -120,23 +118,70 @@
|
||||
// 窗口和棋盘配置
|
||||
#define WINDOW_WIDTH 1000
|
||||
#define WINDOW_HEIGHT 800
|
||||
#define BOARD_OFFSET_X 50
|
||||
#define BOARD_OFFSET_Y 50
|
||||
#define BOARD_OFFSET_X 45
|
||||
#define BOARD_OFFSET_Y 45
|
||||
#define CELL_SIZE 30
|
||||
#define STONE_RADIUS 12
|
||||
#define STONE_RADIUS 13
|
||||
|
||||
// 颜色定义
|
||||
#define GUI_COLOR_BACKGROUND {240, 217, 181, 255}
|
||||
#define GUI_COLOR_BOARD_LINE {0, 0, 0, 255}
|
||||
#define GUI_COLOR_BLACK_STONE {0, 0, 0, 255}
|
||||
#define GUI_COLOR_WHITE_STONE {255, 255, 255, 255}
|
||||
#define GUI_COLOR_STONE_BORDER {100, 100, 100, 255}
|
||||
// 配色方案 - 经典木纹风格 (RGB)
|
||||
#define CLR_BOARD_BG_R 0xD4 // 棋盘背景 - 暖木色
|
||||
#define CLR_BOARD_BG_G 0xA5
|
||||
#define CLR_BOARD_BG_B 0x74
|
||||
#define CLR_BOARD_BORDER_R 0x8B // 棋盘边框 - 深木色
|
||||
#define CLR_BOARD_BORDER_G 0x5E
|
||||
#define CLR_BOARD_BORDER_B 0x3C
|
||||
#define CLR_WINDOW_BG "245 240 235" // 窗口背景 - 米白
|
||||
#define CLR_PANEL_BG "237 229 218" // 面板背景 - 浅米色
|
||||
#define CLR_GRID_LINE_R 0x5C // 网格线 - 深棕
|
||||
#define CLR_GRID_LINE_G 0x3D
|
||||
#define CLR_GRID_LINE_B 0x2E
|
||||
#define CLR_TEXT_TITLE "60 36 21" // 标题文字 - 深棕
|
||||
#define CLR_TEXT_NORMAL "74 55 40" // 正文文字 - 中棕
|
||||
#define CLR_TEXT_SECONDARY "139 115 85" // 次要文字 - 浅棕
|
||||
#define CLR_BTN_PRIMARY_BG "139 94 60" // 主按钮背景 - 深棕
|
||||
#define CLR_BTN_PRIMARY_FG "255 255 255" // 主按钮文字 - 白色
|
||||
#define CLR_BTN_NORMAL_BG "237 229 218" // 普通按钮背景 - 浅棕
|
||||
#define CLR_BTN_NORMAL_FG "107 66 38" // 普通按钮文字 - 深棕
|
||||
#define CLR_LAST_MOVE_R 0x2E // 最后落子标记 - 蓝色
|
||||
#define CLR_LAST_MOVE_G 0x86
|
||||
#define CLR_LAST_MOVE_B 0xAB
|
||||
#define CLR_STAR_POINT_R 0x3C // 星位/天元 - 深棕
|
||||
#define CLR_STAR_POINT_G 0x24
|
||||
#define CLR_STAR_POINT_B 0x15
|
||||
// 黑子渐变色
|
||||
#define CLR_BLACK_STONE_R 0x10 // 黑子外圈
|
||||
#define CLR_BLACK_STONE_G 0x10
|
||||
#define CLR_BLACK_STONE_B 0x10
|
||||
#define CLR_BLACK_HIGHLIGHT_R 0x50 // 黑子高光
|
||||
#define CLR_BLACK_HIGHLIGHT_G 0x50
|
||||
#define CLR_BLACK_HIGHLIGHT_B 0x50
|
||||
// 白子渐变色
|
||||
#define CLR_WHITE_STONE_R 0xF0 // 白子外圈
|
||||
#define CLR_WHITE_STONE_G 0xF0
|
||||
#define CLR_WHITE_STONE_B 0xF0
|
||||
#define CLR_WHITE_HIGHLIGHT_R 0xFF // 白子高光
|
||||
#define CLR_WHITE_HIGHLIGHT_G 0xFF
|
||||
#define CLR_WHITE_HIGHLIGHT_B 0xFF
|
||||
#define CLR_WHITE_BORDER_R 0xA0 // 白子边框
|
||||
#define CLR_WHITE_BORDER_G 0xA0
|
||||
#define CLR_WHITE_BORDER_B 0xA0
|
||||
|
||||
//---------- 文件路径参数 ----------//
|
||||
#define RECORDS_DIR "records" // 记录文件目录
|
||||
#define CONFIG_FILE "gobang_config.ini" // 配置文件路径
|
||||
#define MAX_PATH_LENGTH 256 // 最大路径长度
|
||||
|
||||
//---------- LLM大模型参数 ----------//
|
||||
#define DEFAULT_LLM_USE 0 // 默认不使用LLM (0=算法AI, 1=大模型)
|
||||
#define DEFAULT_LLM_ENDPOINT "https://api.minimax.chat/v1/chat/completions" // 默认API地址
|
||||
#define DEFAULT_LLM_API_KEY "" // 默认API Key (需用户填写)
|
||||
#define DEFAULT_LLM_MODEL "MiniMax-Text-01" // 默认模型名
|
||||
#define MAX_LLM_ENDPOINT_LEN 256 // API地址最大长度
|
||||
#define MAX_LLM_API_KEY_LEN 128 // API Key最大长度
|
||||
#define MAX_LLM_MODEL_LEN 64 // 模型名最大长度
|
||||
#define LLM_MAX_RETRIES 3 // LLM返回非法坐标时最大重试次数
|
||||
#define LLM_TIMEOUT_MS 30000 // LLM HTTP请求超时(毫秒)
|
||||
|
||||
//---------- 配置管理函数声明 ----------//
|
||||
/**
|
||||
* @brief 加载游戏配置
|
||||
|
||||
+6
-2
@@ -29,12 +29,16 @@ extern int network_timeout; // 网络超时时间
|
||||
extern double defense_coefficient; // 防守系数
|
||||
extern int ai_difficulty; // AI难度 (1-5)
|
||||
|
||||
// ==================== LLM大模型相关变量 ====================
|
||||
extern int llm_use; // 是否使用LLM (0=算法AI, 1=大模型)
|
||||
extern char llm_endpoint[MAX_LLM_ENDPOINT_LEN]; // API地址
|
||||
extern char llm_api_key[MAX_LLM_API_KEY_LEN]; // API Key
|
||||
extern char llm_model[MAX_LLM_MODEL_LEN]; // 模型名
|
||||
|
||||
// ==================== 网络相关变量 ====================
|
||||
extern NetworkGameState network_state; // 网络游戏状态
|
||||
|
||||
// ==================== GUI相关变量 ====================
|
||||
// Raylib 不需要暴露窗口和渲染器指针
|
||||
extern int gui_running; // GUI运行状态标志
|
||||
extern int current_player_gui; // GUI当前玩家
|
||||
extern int game_over; // 游戏结束标志
|
||||
extern char status_message[256]; // 状态消息
|
||||
|
||||
+3
-3
@@ -5,8 +5,8 @@
|
||||
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
|
||||
*/
|
||||
|
||||
#ifndef GO_BANG_H
|
||||
#define GO_BANG_H
|
||||
#ifndef GOBANG_H
|
||||
#define GOBANG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
@@ -88,4 +88,4 @@ bool return_move(int steps_to_undo);
|
||||
*/
|
||||
int calculate_step_score(int x, int y, int player);
|
||||
|
||||
#endif // GO_BANG_H
|
||||
#endif // GOBANG_H
|
||||
+2
-38
@@ -1,9 +1,8 @@
|
||||
/**
|
||||
* @file gui.h
|
||||
* @brief 图形化用户界面头文件
|
||||
* @note 使用Raylib库实现五子棋的图形化界面
|
||||
* @note 使用IUP库实现五子棋的图形化界面
|
||||
* @author 刘航宇
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
|
||||
#ifndef GUI_H
|
||||
@@ -17,45 +16,16 @@
|
||||
|
||||
/**
|
||||
* @brief 初始化GUI
|
||||
* @details 初始化Raylib图形库和游戏界面组件
|
||||
* @details 初始化IUP图形库和游戏界面组件
|
||||
* @return 成功返回0,失败返回-1
|
||||
*/
|
||||
int init_gui();
|
||||
|
||||
/**
|
||||
* @brief 清理GUI资源
|
||||
* @details 关闭窗口
|
||||
*/
|
||||
void cleanup_gui();
|
||||
|
||||
/**
|
||||
* @brief 渲染游戏画面
|
||||
* @details 完整的游戏画面渲染流程
|
||||
*/
|
||||
void render_game();
|
||||
|
||||
/**
|
||||
* @brief 处理事件
|
||||
* @details 处理所有Raylib事件并执行相应操作
|
||||
* @return 继续运行返回1,退出返回0
|
||||
*/
|
||||
int handle_events();
|
||||
|
||||
/**
|
||||
* @brief 绘制棋盘
|
||||
*/
|
||||
void draw_board();
|
||||
|
||||
/**
|
||||
* @brief 绘制棋子
|
||||
*/
|
||||
void draw_stones();
|
||||
|
||||
/**
|
||||
* @brief 绘制UI元素
|
||||
*/
|
||||
void draw_ui_elements();
|
||||
|
||||
/**
|
||||
* @brief 屏幕坐标转棋盘坐标
|
||||
*/
|
||||
@@ -81,12 +51,6 @@ void start_pve_game_gui();
|
||||
*/
|
||||
void start_replay_gui();
|
||||
|
||||
/**
|
||||
* @brief 启动图形化界面模式
|
||||
* @note 替代原来的 main 函数中的 GUI 分支逻辑
|
||||
*/
|
||||
int init_gui(); // Already declared
|
||||
|
||||
/**
|
||||
* @brief 运行图形化界面模式
|
||||
* @details 主循环处理事件、渲染画面和更新状态
|
||||
|
||||
@@ -8,15 +8,9 @@ extern Ihandle *dlg;
|
||||
extern Ihandle *board_canvas;
|
||||
extern Ihandle *lbl_player;
|
||||
extern Ihandle *lbl_status;
|
||||
extern int gui_loop_running;
|
||||
extern int gui_game_mode; // 0: PvP, 1: PvE, 2: Replay
|
||||
extern int gui_game_mode; // 0: PvP, 1: PvE, 2: Replay, 3: Network
|
||||
extern int replay_total_steps; // 复盘总步数
|
||||
|
||||
// 绘图函数 (在 gui_draw.c 中定义)
|
||||
void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b);
|
||||
void draw_board_iup(Ihandle *ih);
|
||||
void draw_stones_iup(Ihandle *ih);
|
||||
|
||||
// 核心功能 (在 gui_core.c 中定义)
|
||||
void update_ui_labels();
|
||||
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
||||
@@ -25,7 +19,7 @@ int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
||||
void create_game_window();
|
||||
void start_pvp_game_gui();
|
||||
void start_pve_game_gui();
|
||||
void start_network_game_gui(); // 新增:启动网络对战游戏窗口
|
||||
void start_network_game_gui();
|
||||
int action_cb(Ihandle *ih);
|
||||
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status);
|
||||
int k_any_cb(Ihandle *ih, int c);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* @file llm_ai.h
|
||||
* @brief 大模型AI模块头文件
|
||||
* @note 通过OpenAI兼容API调用大模型进行五子棋对弈
|
||||
*/
|
||||
|
||||
#ifndef LLM_AI_H
|
||||
#define LLM_AI_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief 调用大模型获取落子坐标(同步,会阻塞)
|
||||
* @param out_x 输出:落子行坐标 (0-based)
|
||||
* @param out_y 输出:落子列坐标 (0-based)
|
||||
* @return true 获取成功,坐标合法且位置为空
|
||||
* @return false 获取失败(网络错误/坐标非法/重试耗尽)
|
||||
*/
|
||||
bool llm_ai_move(int *out_x, int *out_y);
|
||||
|
||||
/**
|
||||
* @brief 异步启动大模型思考(后台线程)
|
||||
* @note 调用后用 llm_ai_poll_result 轮询结果
|
||||
*/
|
||||
void llm_ai_start_move(void);
|
||||
|
||||
/**
|
||||
* @brief 轮询大模型结果(非阻塞)
|
||||
* @param out_x 输出:落子行坐标
|
||||
* @param out_y 输出:落子列坐标
|
||||
* @return 0 仍在思考, 1 成功获取坐标, -1 失败(应回退算法AI)
|
||||
*/
|
||||
int llm_ai_poll_result(int *out_x, int *out_y);
|
||||
|
||||
#endif // LLM_AI_H
|
||||
+2
-6
@@ -13,13 +13,9 @@
|
||||
#include "type.h"
|
||||
#include "config.h"
|
||||
#include <stdbool.h>
|
||||
#include <enet/enet.h> // 引入 ENet 头文件
|
||||
#include <enet/enet.h>
|
||||
|
||||
// 网络状态结构体在 type.h 中定义,我们需要修改 type.h 来包含 ENet 类型
|
||||
// 或者在这里重新定义(如果 type.h 中可以移除旧的定义)
|
||||
|
||||
// 全局网络状态
|
||||
extern NetworkGameState network_state;
|
||||
// network_state 的 extern 声明在 globals.h 中
|
||||
|
||||
// 函数声明
|
||||
|
||||
|
||||
+3143
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef cJSON__h
|
||||
#define cJSON__h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
|
||||
#define __WINDOWS__
|
||||
#endif
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
|
||||
|
||||
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
|
||||
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
|
||||
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
|
||||
|
||||
For *nix builds that support visibility attribute, you can define similar behavior by
|
||||
|
||||
setting default visibility to hidden by adding
|
||||
-fvisibility=hidden (for gcc)
|
||||
or
|
||||
-xldscope=hidden (for sun cc)
|
||||
to CFLAGS
|
||||
|
||||
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
|
||||
|
||||
*/
|
||||
|
||||
#define CJSON_CDECL __cdecl
|
||||
#define CJSON_STDCALL __stdcall
|
||||
|
||||
/* export symbols by default, this is necessary for copy pasting the C and header file */
|
||||
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_EXPORT_SYMBOLS
|
||||
#endif
|
||||
|
||||
#if defined(CJSON_HIDE_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) type CJSON_STDCALL
|
||||
#elif defined(CJSON_EXPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
|
||||
#elif defined(CJSON_IMPORT_SYMBOLS)
|
||||
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
|
||||
#endif
|
||||
#else /* !__WINDOWS__ */
|
||||
#define CJSON_CDECL
|
||||
#define CJSON_STDCALL
|
||||
|
||||
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
|
||||
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
|
||||
#else
|
||||
#define CJSON_PUBLIC(type) type
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* project version */
|
||||
#define CJSON_VERSION_MAJOR 1
|
||||
#define CJSON_VERSION_MINOR 7
|
||||
#define CJSON_VERSION_PATCH 18
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/* cJSON Types: */
|
||||
#define cJSON_Invalid (0)
|
||||
#define cJSON_False (1 << 0)
|
||||
#define cJSON_True (1 << 1)
|
||||
#define cJSON_NULL (1 << 2)
|
||||
#define cJSON_Number (1 << 3)
|
||||
#define cJSON_String (1 << 4)
|
||||
#define cJSON_Array (1 << 5)
|
||||
#define cJSON_Object (1 << 6)
|
||||
#define cJSON_Raw (1 << 7) /* raw json */
|
||||
|
||||
#define cJSON_IsReference 256
|
||||
#define cJSON_StringIsConst 512
|
||||
|
||||
/* The cJSON structure: */
|
||||
typedef struct cJSON
|
||||
{
|
||||
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
|
||||
struct cJSON *next;
|
||||
struct cJSON *prev;
|
||||
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
|
||||
struct cJSON *child;
|
||||
|
||||
/* The type of the item, as above. */
|
||||
int type;
|
||||
|
||||
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
|
||||
char *valuestring;
|
||||
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
|
||||
int valueint;
|
||||
/* The item's number, if type==cJSON_Number */
|
||||
double valuedouble;
|
||||
|
||||
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
|
||||
char *string;
|
||||
} cJSON;
|
||||
|
||||
typedef struct cJSON_Hooks
|
||||
{
|
||||
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
|
||||
void *(CJSON_CDECL *malloc_fn)(size_t sz);
|
||||
void (CJSON_CDECL *free_fn)(void *ptr);
|
||||
} cJSON_Hooks;
|
||||
|
||||
typedef int cJSON_bool;
|
||||
|
||||
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
|
||||
* This is to prevent stack overflows. */
|
||||
#ifndef CJSON_NESTING_LIMIT
|
||||
#define CJSON_NESTING_LIMIT 1000
|
||||
#endif
|
||||
|
||||
/* returns the version of cJSON as a string */
|
||||
CJSON_PUBLIC(const char*) cJSON_Version(void);
|
||||
|
||||
/* Supply malloc, realloc and free functions to cJSON */
|
||||
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
|
||||
|
||||
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
|
||||
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
|
||||
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
|
||||
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
|
||||
|
||||
/* Render a cJSON entity to text for transfer/storage. */
|
||||
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
|
||||
/* Render a cJSON entity to text for transfer/storage without any formatting. */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
|
||||
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
|
||||
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
|
||||
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
|
||||
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
|
||||
/* Delete a cJSON entity and all subentities. */
|
||||
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
|
||||
|
||||
/* Returns the number of items in an array (or object). */
|
||||
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
|
||||
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
|
||||
/* Get item "string" from object. Case insensitive. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
|
||||
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
|
||||
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
|
||||
|
||||
/* Check item type and return its value */
|
||||
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
|
||||
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
|
||||
|
||||
/* These functions check the type of an item */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
|
||||
|
||||
/* These calls create a cJSON item of the appropriate type. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
|
||||
/* raw json */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
|
||||
|
||||
/* Create a string where valuestring references a string so
|
||||
* it will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
|
||||
/* Create an object/array that only references it's elements so
|
||||
* they will not be freed by cJSON_Delete */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
|
||||
|
||||
/* These utilities create an Array of count items.
|
||||
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
|
||||
|
||||
/* Append item to the specified array/object. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
|
||||
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
|
||||
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
|
||||
* writing to `item->string` */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
|
||||
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
|
||||
|
||||
/* Remove/Detach items from Arrays/Objects. */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
|
||||
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
|
||||
|
||||
/* Update array items. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
|
||||
|
||||
/* Duplicate a cJSON item */
|
||||
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
|
||||
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
|
||||
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
|
||||
* The item->next and ->prev pointers are always zero on return from Duplicate. */
|
||||
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
|
||||
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
|
||||
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
|
||||
|
||||
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
|
||||
* The input pointer json cannot point to a read-only address area, such as a string constant,
|
||||
* but should point to a readable and writable address area. */
|
||||
CJSON_PUBLIC(void) cJSON_Minify(char *json);
|
||||
|
||||
/* Helper functions for creating and adding items to an object at the same time.
|
||||
* They return the added item or NULL on failure. */
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
|
||||
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
|
||||
|
||||
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
|
||||
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
|
||||
/* helper for the cJSON_SetNumberValue macro */
|
||||
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
|
||||
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
|
||||
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
|
||||
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
|
||||
|
||||
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
|
||||
#define cJSON_SetBoolValue(object, boolValue) ( \
|
||||
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
|
||||
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
|
||||
cJSON_Invalid\
|
||||
)
|
||||
|
||||
/* Macro for iterating over an array or object */
|
||||
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
|
||||
|
||||
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
|
||||
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
|
||||
CJSON_PUBLIC(void) cJSON_free(void *object);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+3
-145
@@ -137,94 +137,6 @@ int evaluate_pos(int x, int y, int player)
|
||||
return total_score + position_bonus; // 返回总评估分
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 带α-β剪枝的深度优先搜索(极小极大算法实现)
|
||||
* @param x 当前行坐标
|
||||
* @param y 当前列坐标
|
||||
* @param player 当前玩家
|
||||
* @param depth 剩余搜索深度
|
||||
* @param alpha α值(当前最大值)
|
||||
* @param beta β值(当前最小值)
|
||||
* @param is_maximizing 是否为极大化玩家(AI)
|
||||
* @return int 最佳评估分数
|
||||
* @note 算法流程:
|
||||
* 1. 检查是否获胜或达到搜索深度
|
||||
* 2. 遍历所有可能落子位置
|
||||
* 3. 递归评估每个位置的分数
|
||||
* 4. 根据is_maximizing选择最大/最小值
|
||||
* 5. 使用α-β剪枝优化搜索过程
|
||||
*/
|
||||
int dfs(int x, int y, int player, int depth, int alpha, int beta, bool is_maximizing)
|
||||
{
|
||||
// 检查当前落子是否获胜
|
||||
if (check_win(x, y, player))
|
||||
{
|
||||
return (player == AI) ? SEARCH_WIN_BONUS + depth : -SEARCH_WIN_BONUS - depth;
|
||||
}
|
||||
|
||||
// 达到搜索深度或平局
|
||||
if (depth == 0 || step_count >= BOARD_SIZE * BOARD_SIZE)
|
||||
{
|
||||
return evaluate_pos(x, y, AI) - evaluate_pos(x, y, PLAYER);
|
||||
}
|
||||
|
||||
int best_score = is_maximizing ? -1000000 : 1000000;
|
||||
|
||||
// 使用移动排序优化搜索效率
|
||||
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)
|
||||
{
|
||||
move_count = max_candidates;
|
||||
}
|
||||
|
||||
// 遍历排序后的候选移动
|
||||
for (int idx = 0; idx < move_count; idx++)
|
||||
{
|
||||
int i = candidate_moves[idx].x;
|
||||
int j = candidate_moves[idx].y;
|
||||
|
||||
// 模拟当前玩家落子
|
||||
board[i][j] = player;
|
||||
step_count++;
|
||||
|
||||
// 递归搜索(切换玩家和搜索深度)
|
||||
int current_score = dfs(i, j, (player == AI) ? PLAYER : AI, depth - 1, alpha, beta, !is_maximizing);
|
||||
|
||||
// 撤销落子
|
||||
board[i][j] = EMPTY;
|
||||
step_count--;
|
||||
|
||||
// 极大值玩家(AI)逻辑
|
||||
if (is_maximizing)
|
||||
{
|
||||
best_score = (current_score > best_score) ? current_score : best_score;
|
||||
alpha = (best_score > alpha) ? best_score : alpha;
|
||||
// α剪枝
|
||||
if (beta <= alpha)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 极小值玩家(人类)逻辑
|
||||
else
|
||||
{
|
||||
best_score = (current_score < best_score) ? current_score : best_score;
|
||||
beta = (best_score < beta) ? best_score : beta;
|
||||
// β剪枝
|
||||
if (beta <= alpha)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best_score;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief AI决策主函数,使用评估函数和搜索算法选择最佳落子位置
|
||||
* @note 采用两阶段决策逻辑:
|
||||
@@ -532,8 +444,9 @@ ThreatLevel detect_threat(int x, int y, int player)
|
||||
// 恢复棋盘
|
||||
board[x][y] = EMPTY;
|
||||
|
||||
// 如果有多个威胁,提升威胁等级
|
||||
if (threat_count >= 2 && max_threat >= THREAT_THREE)
|
||||
// 如果有多个活三威胁,升级为双威胁(如双三、四三等组合)
|
||||
// 注意:不能把已经是活四的威胁降级为双威胁
|
||||
if (threat_count >= 2 && max_threat == THREAT_THREE)
|
||||
{
|
||||
max_threat = THREAT_DOUBLE;
|
||||
}
|
||||
@@ -541,58 +454,3 @@ ThreatLevel detect_threat(int x, int y, int player)
|
||||
return max_threat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算指定方向的威胁数量
|
||||
* @param x, y 起始位置
|
||||
* @param dx, dy 方向向量
|
||||
* @param player 玩家
|
||||
* @return 威胁数量
|
||||
*/
|
||||
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++;
|
||||
}
|
||||
else if (board[nx][ny] != EMPTY)
|
||||
{
|
||||
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++;
|
||||
}
|
||||
else if (board[nx][ny] != EMPTY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return threats;
|
||||
}
|
||||
@@ -23,7 +23,7 @@ void load_game_config()
|
||||
return;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
char line[512];
|
||||
while (fgets(line, sizeof(line), file))
|
||||
{
|
||||
// 去除换行符
|
||||
@@ -48,7 +48,11 @@ void load_game_config()
|
||||
}
|
||||
else if (strncmp(line, "TIME_LIMIT=", 11) == 0)
|
||||
{
|
||||
time_limit = atoi(line + 11);
|
||||
int minutes = atoi(line + 11);
|
||||
if (minutes > 0)
|
||||
{
|
||||
time_limit = minutes * 60; // 配置文件存分钟,内部转为秒
|
||||
}
|
||||
}
|
||||
else if (strncmp(line, "NETWORK_PORT=", 13) == 0)
|
||||
{
|
||||
@@ -75,6 +79,25 @@ void load_game_config()
|
||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
||||
}
|
||||
}
|
||||
else if (strncmp(line, "LLM_USE=", 8) == 0)
|
||||
{
|
||||
llm_use = (atoi(line + 8) != 0) ? 1 : 0;
|
||||
}
|
||||
else if (strncmp(line, "LLM_ENDPOINT=", 13) == 0)
|
||||
{
|
||||
strncpy(llm_endpoint, line + 13, MAX_LLM_ENDPOINT_LEN - 1);
|
||||
llm_endpoint[MAX_LLM_ENDPOINT_LEN - 1] = '\0';
|
||||
}
|
||||
else if (strncmp(line, "LLM_API_KEY=", 12) == 0)
|
||||
{
|
||||
strncpy(llm_api_key, line + 12, MAX_LLM_API_KEY_LEN - 1);
|
||||
llm_api_key[MAX_LLM_API_KEY_LEN - 1] = '\0';
|
||||
}
|
||||
else if (strncmp(line, "LLM_MODEL=", 10) == 0)
|
||||
{
|
||||
strncpy(llm_model, line + 10, MAX_LLM_MODEL_LEN - 1);
|
||||
llm_model[MAX_LLM_MODEL_LEN - 1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
@@ -101,7 +124,7 @@ void save_game_config()
|
||||
fprintf(file, "\n# 计时器 (0=关闭, 1=开启)\n");
|
||||
fprintf(file, "USE_TIMER=%d\n", use_timer);
|
||||
fprintf(file, "\n# 时间限制 (分钟)\n");
|
||||
fprintf(file, "TIME_LIMIT=%d\n", time_limit);
|
||||
fprintf(file, "TIME_LIMIT=%d\n", time_limit / 60); // 内部存秒,配置文件存分钟
|
||||
fprintf(file, "\n# 网络端口 (范围: %d-%d)\n", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
|
||||
fprintf(file, "NETWORK_PORT=%d\n", network_port);
|
||||
fprintf(file, "\n# 网络超时时间 (毫秒)\n");
|
||||
@@ -110,6 +133,16 @@ void save_game_config()
|
||||
fprintf(file, "\n# AI难度 (1-5)\n");
|
||||
fprintf(file, "AI_DIFFICULTY=%d\n", ai_difficulty);
|
||||
|
||||
fprintf(file, "\n# 大模型AI设置\n");
|
||||
fprintf(file, "# 是否使用大模型 (0=算法AI, 1=大模型)\n");
|
||||
fprintf(file, "LLM_USE=%d\n", llm_use);
|
||||
fprintf(file, "# API地址\n");
|
||||
fprintf(file, "LLM_ENDPOINT=%s\n", llm_endpoint);
|
||||
fprintf(file, "# API Key\n");
|
||||
fprintf(file, "LLM_API_KEY=%s\n", llm_api_key);
|
||||
fprintf(file, "# 模型名称\n");
|
||||
fprintf(file, "LLM_MODEL=%s\n", llm_model);
|
||||
|
||||
fclose(file);
|
||||
printf("配置保存完成\n");
|
||||
}
|
||||
@@ -128,5 +161,13 @@ void reset_to_default_config()
|
||||
ai_difficulty = 3; // 默认AI难度
|
||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
||||
|
||||
llm_use = DEFAULT_LLM_USE;
|
||||
strncpy(llm_endpoint, DEFAULT_LLM_ENDPOINT, MAX_LLM_ENDPOINT_LEN - 1);
|
||||
llm_endpoint[MAX_LLM_ENDPOINT_LEN - 1] = '\0';
|
||||
strncpy(llm_api_key, DEFAULT_LLM_API_KEY, MAX_LLM_API_KEY_LEN - 1);
|
||||
llm_api_key[MAX_LLM_API_KEY_LEN - 1] = '\0';
|
||||
strncpy(llm_model, DEFAULT_LLM_MODEL, MAX_LLM_MODEL_LEN - 1);
|
||||
llm_model[MAX_LLM_MODEL_LEN - 1] = '\0';
|
||||
|
||||
printf("已重置为默认配置\n");
|
||||
}
|
||||
@@ -26,11 +26,16 @@ int network_timeout = NETWORK_TIMEOUT_MS; // 网络超时时间
|
||||
double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
|
||||
int ai_difficulty = 3; // AI难度 (1-5)
|
||||
|
||||
// ==================== LLM大模型相关变量定义 ====================
|
||||
int llm_use = DEFAULT_LLM_USE; // 是否使用LLM
|
||||
char llm_endpoint[MAX_LLM_ENDPOINT_LEN] = DEFAULT_LLM_ENDPOINT; // API地址
|
||||
char llm_api_key[MAX_LLM_API_KEY_LEN] = DEFAULT_LLM_API_KEY; // API Key
|
||||
char llm_model[MAX_LLM_MODEL_LEN] = DEFAULT_LLM_MODEL; // 模型名
|
||||
|
||||
// ==================== 网络相关变量定义 ====================
|
||||
NetworkGameState network_state = {0}; // 网络游戏状态
|
||||
|
||||
// ==================== GUI相关变量定义 ====================
|
||||
int gui_running = 1; // GUI运行状态标志
|
||||
int current_player_gui = PLAYER; // GUI当前玩家
|
||||
int game_over = 0; // 游戏结束标志
|
||||
char status_message[256] = "五子棋游戏 - 黑子先行"; // 状态消息
|
||||
@@ -25,7 +25,6 @@ int main(int argc, char *argv[])
|
||||
{
|
||||
// 设置控制台编码为UTF-8
|
||||
#ifdef _WIN32
|
||||
system("chcp 65001 > nul"); // 设置控制台编码为UTF-8
|
||||
SetConsoleOutputCP(65001); // 设置控制台输出编码
|
||||
SetConsoleCP(65001); // 设置控制台输入编码
|
||||
_mkdir("records");
|
||||
@@ -11,7 +11,6 @@ Ihandle *dlg = NULL;
|
||||
Ihandle *board_canvas = NULL;
|
||||
Ihandle *lbl_player = NULL;
|
||||
Ihandle *lbl_status = NULL;
|
||||
int gui_loop_running = 0;
|
||||
int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay, 3: Network
|
||||
int replay_total_steps = 0; // 复盘总步数
|
||||
|
||||
@@ -29,11 +28,12 @@ int init_gui()
|
||||
// 启用UTF-8模式,确保中文正常显示
|
||||
IupSetGlobal("UTF8MODE", "YES");
|
||||
|
||||
// 设置全局默认字体
|
||||
IupSetGlobal("DEFAULTFONT", "SimHei, 11");
|
||||
|
||||
create_main_menu();
|
||||
show_main_menu();
|
||||
|
||||
gui_loop_running = 1;
|
||||
|
||||
printf("图形化界面初始化成功!(IUP)\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,783 @@
|
||||
#include "gui.h"
|
||||
#include "gui_internal.h"
|
||||
#include "gui_menu.h"
|
||||
#include "globals.h"
|
||||
#include "gobang.h"
|
||||
#include "ai.h"
|
||||
#include "record.h"
|
||||
#include "network.h"
|
||||
#include "llm_ai.h"
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
#include <iup.h>
|
||||
#include <iupdraw.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static Ihandle *timer = NULL; // 网络轮询定时器
|
||||
static Ihandle *llm_timer = NULL; // LLM异步轮询定时器
|
||||
|
||||
/**
|
||||
* @brief 网络事件轮询回调
|
||||
*/
|
||||
static int timer_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (gui_game_mode != 3 || game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
NetworkMessage msg;
|
||||
// 非阻塞接收消息
|
||||
if (receive_network_message(&msg, 0))
|
||||
{
|
||||
if (msg.type == MSG_MOVE)
|
||||
{
|
||||
int bx = msg.x;
|
||||
int by = msg.y;
|
||||
int pid = msg.player_id;
|
||||
|
||||
if (have_space(bx, by))
|
||||
{
|
||||
player_move(bx, by, pid);
|
||||
if (check_win(bx, by, pid))
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "对手获胜!");
|
||||
IupMessage("游戏结束", "对手获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
current_player_gui = network_state.local_player_id;
|
||||
sprintf(status_message, "轮到你落子");
|
||||
}
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
}
|
||||
else if (msg.type == MSG_DISCONNECT || msg.type == MSG_SURRENDER)
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "对手已断开连接/认输");
|
||||
IupMessage("游戏结束", "对手已退出游戏,你赢了!");
|
||||
update_ui_labels();
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_network_connected() && !game_over)
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "与服务器断开连接");
|
||||
IupMessage("错误", "网络连接已断开");
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理AI落子结果(LLM或算法)
|
||||
*/
|
||||
static void process_ai_move_result(void)
|
||||
{
|
||||
Step last_step = steps[step_count - 1];
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "AI获胜!");
|
||||
IupMessage("游戏结束", "AI获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
current_player_gui = PLAYER;
|
||||
sprintf(status_message, "轮到玩家");
|
||||
}
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LLM异步轮询定时器回调
|
||||
*/
|
||||
static int llm_timer_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
int x, y;
|
||||
int result = llm_ai_poll_result(&x, &y);
|
||||
|
||||
if (result == 0)
|
||||
return IUP_DEFAULT; // 仍在思考
|
||||
|
||||
// 停止轮询定时器
|
||||
if (llm_timer)
|
||||
{
|
||||
IupSetAttribute(llm_timer, "RUN", "NO");
|
||||
}
|
||||
|
||||
if (result == 1 && x >= 0 && y >= 0 && player_move(x, y, AI))
|
||||
{
|
||||
// LLM成功且落子合法
|
||||
}
|
||||
else
|
||||
{
|
||||
// LLM失败或坐标非法,回退到算法AI
|
||||
if (result == 1)
|
||||
snprintf(status_message, sizeof(status_message), "大模型返回非法位置,使用算法AI");
|
||||
else
|
||||
snprintf(status_message, sizeof(status_message), "大模型响应失败,使用算法AI");
|
||||
update_ui_labels();
|
||||
ai_move(ai_difficulty);
|
||||
}
|
||||
|
||||
process_ai_move_result();
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief MAP_CB 回调:Canvas映射后强制重绘
|
||||
*/
|
||||
static int map_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
IupUpdate(board_canvas);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ACTION 回调:负责重绘(经典木纹风格)
|
||||
*/
|
||||
int action_cb(Ihandle *ih)
|
||||
{
|
||||
HWND hwnd = (HWND)IupGetAttribute(ih, "WID");
|
||||
if (!hwnd)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
HDC hdc = GetDC(hwnd);
|
||||
if (!hdc)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
RECT rc;
|
||||
GetClientRect(hwnd, &rc);
|
||||
|
||||
// === 预创建所有 GDI 对象 ===
|
||||
HBRUSH bg_brush = CreateSolidBrush(RGB(CLR_BOARD_BG_R, CLR_BOARD_BG_G, CLR_BOARD_BG_B));
|
||||
HBRUSH black_outer = CreateSolidBrush(RGB(CLR_BLACK_STONE_R, CLR_BLACK_STONE_G, CLR_BLACK_STONE_B));
|
||||
HBRUSH black_core = CreateSolidBrush(RGB(0, 0, 0));
|
||||
HBRUSH black_hl = CreateSolidBrush(RGB(CLR_BLACK_HIGHLIGHT_R, CLR_BLACK_HIGHLIGHT_G, CLR_BLACK_HIGHLIGHT_B));
|
||||
HBRUSH white_outer = CreateSolidBrush(RGB(CLR_WHITE_BORDER_R, CLR_WHITE_BORDER_G, CLR_WHITE_BORDER_B));
|
||||
HBRUSH white_core = CreateSolidBrush(RGB(CLR_WHITE_STONE_R, CLR_WHITE_STONE_G, CLR_WHITE_STONE_B));
|
||||
HBRUSH white_hl = CreateSolidBrush(RGB(CLR_WHITE_HIGHLIGHT_R, CLR_WHITE_HIGHLIGHT_G, CLR_WHITE_HIGHLIGHT_B));
|
||||
HBRUSH star_brush = CreateSolidBrush(RGB(CLR_STAR_POINT_R, CLR_STAR_POINT_G, CLR_STAR_POINT_B));
|
||||
HPEN grid_pen = CreatePen(PS_SOLID, 1, RGB(CLR_GRID_LINE_R, CLR_GRID_LINE_G, CLR_GRID_LINE_B));
|
||||
HPEN border_pen = CreatePen(PS_SOLID, 2, RGB(CLR_BOARD_BORDER_R, CLR_BOARD_BORDER_G, CLR_BOARD_BORDER_B));
|
||||
HPEN last_move_pen = CreatePen(PS_SOLID, 2, RGB(CLR_LAST_MOVE_R, CLR_LAST_MOVE_G, CLR_LAST_MOVE_B));
|
||||
HPEN null_pen = CreatePen(PS_NULL, 0, RGB(0, 0, 0));
|
||||
HFONT hfont_coord = CreateFont(12, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
|
||||
DEFAULT_CHARSET, 0, 0, 0, 0, "SimHei");
|
||||
|
||||
// 1. 填充背景
|
||||
FillRect(hdc, &rc, bg_brush);
|
||||
|
||||
// 2. 绘制棋盘边框(深色外框)
|
||||
HPEN prev_pen = (HPEN)SelectObject(hdc, border_pen);
|
||||
HBRUSH prev_brush = (HBRUSH)SelectObject(hdc, (HBRUSH)GetStockObject(NULL_BRUSH));
|
||||
int grid_left = BOARD_OFFSET_X;
|
||||
int grid_top = BOARD_OFFSET_Y;
|
||||
int grid_right = BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE;
|
||||
int grid_bottom = BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE;
|
||||
Rectangle(hdc, grid_left - 2, grid_top - 2, grid_right + 3, grid_bottom + 3);
|
||||
|
||||
// 3. 绘制棋盘网格
|
||||
SelectObject(hdc, grid_pen);
|
||||
SelectObject(hdc, (HBRUSH)GetStockObject(NULL_BRUSH));
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
MoveToEx(hdc, grid_left, BOARD_OFFSET_Y + i * CELL_SIZE, NULL);
|
||||
LineTo(hdc, grid_right, BOARD_OFFSET_Y + i * CELL_SIZE);
|
||||
MoveToEx(hdc, BOARD_OFFSET_X + i * CELL_SIZE, grid_top, NULL);
|
||||
LineTo(hdc, BOARD_OFFSET_X + i * CELL_SIZE, grid_bottom);
|
||||
}
|
||||
|
||||
// 4. 星位/天元(实心圆)
|
||||
SelectObject(hdc, null_pen);
|
||||
SelectObject(hdc, star_brush);
|
||||
if (BOARD_SIZE == 15)
|
||||
{
|
||||
int stars[] = {3, 7, 11};
|
||||
for (int si = 0; si < 3; si++)
|
||||
for (int sj = 0; sj < 3; sj++)
|
||||
{
|
||||
int cx = BOARD_OFFSET_X + stars[si] * CELL_SIZE;
|
||||
int cy = BOARD_OFFSET_Y + stars[sj] * CELL_SIZE;
|
||||
Ellipse(hdc, cx - 4, cy - 4, cx + 5, cy + 5);
|
||||
}
|
||||
}
|
||||
else if (BOARD_SIZE >= 9)
|
||||
{
|
||||
int cx = BOARD_OFFSET_X + (BOARD_SIZE / 2) * CELL_SIZE;
|
||||
int cy = BOARD_OFFSET_Y + (BOARD_SIZE / 2) * CELL_SIZE;
|
||||
Ellipse(hdc, cx - 4, cy - 4, cx + 5, cy + 5);
|
||||
}
|
||||
|
||||
// 5. 绘制坐标标注
|
||||
{
|
||||
HFONT prev_font = (HFONT)SelectObject(hdc, hfont_coord);
|
||||
SetBkMode(hdc, TRANSPARENT);
|
||||
SetTextColor(hdc, RGB(CLR_BOARD_BORDER_R, CLR_BOARD_BORDER_G, CLR_BOARD_BORDER_B));
|
||||
|
||||
// 列坐标 (A, B, C, ...)
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
char label[2] = {'A' + j, '\0'};
|
||||
int tx = BOARD_OFFSET_X + j * CELL_SIZE - 4;
|
||||
TextOut(hdc, tx, grid_top - 18, label, 1);
|
||||
TextOut(hdc, tx, grid_bottom + 5, label, 1);
|
||||
}
|
||||
// 行坐标 (1, 2, 3, ...)
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
char label[4];
|
||||
int len = snprintf(label, sizeof(label), "%d", i + 1);
|
||||
int ty = BOARD_OFFSET_Y + i * CELL_SIZE - 7;
|
||||
TextOut(hdc, grid_left - 18 - (len > 1 ? 4 : 0), ty, label, len);
|
||||
TextOut(hdc, grid_right + 6, ty, label, len);
|
||||
}
|
||||
SelectObject(hdc, prev_font);
|
||||
}
|
||||
|
||||
// 6. 绘制棋子(渐变效果:3层同心圆)
|
||||
SelectObject(hdc, null_pen);
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
if (board[i][j] == EMPTY)
|
||||
continue;
|
||||
|
||||
int cx = BOARD_OFFSET_X + j * CELL_SIZE;
|
||||
int cy = BOARD_OFFSET_Y + i * CELL_SIZE;
|
||||
|
||||
if (board[i][j] == PLAYER)
|
||||
{
|
||||
// 黑子:外圈深灰 → 中圈黑 → 中心高光
|
||||
SelectObject(hdc, black_outer);
|
||||
Ellipse(hdc, cx - STONE_RADIUS, cy - STONE_RADIUS,
|
||||
cx + STONE_RADIUS + 1, cy + STONE_RADIUS + 1);
|
||||
SelectObject(hdc, black_core);
|
||||
Ellipse(hdc, cx - STONE_RADIUS + 2, cy - STONE_RADIUS + 2,
|
||||
cx + STONE_RADIUS - 1, cy + STONE_RADIUS - 1);
|
||||
SelectObject(hdc, black_hl);
|
||||
Ellipse(hdc, cx - 4, cy - 5, cx - 1, cy - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 白子:外圈灰边 → 中圈白 → 中心高光
|
||||
SelectObject(hdc, white_outer);
|
||||
Ellipse(hdc, cx - STONE_RADIUS, cy - STONE_RADIUS,
|
||||
cx + STONE_RADIUS + 1, cy + STONE_RADIUS + 1);
|
||||
SelectObject(hdc, white_core);
|
||||
Ellipse(hdc, cx - STONE_RADIUS + 2, cy - STONE_RADIUS + 2,
|
||||
cx + STONE_RADIUS - 1, cy + STONE_RADIUS - 1);
|
||||
SelectObject(hdc, white_hl);
|
||||
Ellipse(hdc, cx - 4, cy - 5, cx - 1, cy - 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 标记最后落子位置(蓝色圆环)
|
||||
if (step_count > 0 && step_count <= MAX_STEPS)
|
||||
{
|
||||
Step last = steps[step_count - 1];
|
||||
int cx = BOARD_OFFSET_X + last.y * CELL_SIZE;
|
||||
int cy = BOARD_OFFSET_Y + last.x * CELL_SIZE;
|
||||
SelectObject(hdc, last_move_pen);
|
||||
SelectObject(hdc, (HBRUSH)GetStockObject(NULL_BRUSH));
|
||||
Ellipse(hdc, cx - 5, cy - 5, cx + 6, cy + 6);
|
||||
}
|
||||
|
||||
// 恢复原始 GDI 对象,然后清理
|
||||
SelectObject(hdc, prev_pen);
|
||||
SelectObject(hdc, prev_brush);
|
||||
ReleaseDC(hwnd, hdc);
|
||||
|
||||
DeleteObject(bg_brush);
|
||||
DeleteObject(black_outer);
|
||||
DeleteObject(black_core);
|
||||
DeleteObject(black_hl);
|
||||
DeleteObject(white_outer);
|
||||
DeleteObject(white_core);
|
||||
DeleteObject(white_hl);
|
||||
DeleteObject(star_brush);
|
||||
DeleteObject(grid_pen);
|
||||
DeleteObject(border_pen);
|
||||
DeleteObject(last_move_pen);
|
||||
DeleteObject(null_pen);
|
||||
DeleteObject(hfont_coord);
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 悔棋按钮回调
|
||||
*/
|
||||
int btn_undo_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
int steps_to_undo = 1;
|
||||
if (gui_game_mode == 1) // PvE
|
||||
{
|
||||
steps_to_undo = 2; // 悔棋两步(玩家+AI)
|
||||
}
|
||||
else if (gui_game_mode == 3) // Network
|
||||
{
|
||||
IupMessage("提示", "网络模式暂不支持悔棋");
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
if (step_count >= steps_to_undo)
|
||||
{
|
||||
return_move(steps_to_undo);
|
||||
|
||||
// 更新当前玩家
|
||||
if (step_count % 2 == 0)
|
||||
current_player_gui = PLAYER;
|
||||
else
|
||||
current_player_gui = AI; // or PLAYER2
|
||||
|
||||
sprintf(status_message, "已悔棋");
|
||||
update_ui_labels();
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "无法悔棋");
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存按钮回调
|
||||
*/
|
||||
int btn_save_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
|
||||
Ihandle *file_dlg = IupFileDlg();
|
||||
IupSetAttribute(file_dlg, "DIALOGTYPE", "SAVE");
|
||||
IupSetAttribute(file_dlg, "TITLE", "保存游戏记录");
|
||||
IupSetAttribute(file_dlg, "FILTER", "*.csv");
|
||||
IupSetAttribute(file_dlg, "FILTERINFO", "CSV Files");
|
||||
|
||||
IupPopup(file_dlg, IUP_CENTER, IUP_CENTER);
|
||||
|
||||
if (IupGetInt(file_dlg, "STATUS") != -1)
|
||||
{
|
||||
char *filename = IupGetAttribute(file_dlg, "VALUE");
|
||||
|
||||
char *base_name = strrchr(filename, '\\');
|
||||
if (!base_name)
|
||||
base_name = strrchr(filename, '/');
|
||||
if (base_name)
|
||||
base_name++;
|
||||
else
|
||||
base_name = filename;
|
||||
|
||||
int mode;
|
||||
if (gui_game_mode == 0)
|
||||
mode = GAME_MODE_PVP;
|
||||
else if (gui_game_mode == 3)
|
||||
mode = GAME_MODE_NETWORK;
|
||||
else
|
||||
mode = GAME_MODE_AI;
|
||||
if (save_game_to_file(base_name, mode) == 0)
|
||||
{
|
||||
snprintf(status_message, sizeof(status_message), "保存成功: %s", base_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(status_message, sizeof(status_message), "保存失败");
|
||||
}
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
IupDestroy(file_dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回菜单回调
|
||||
*/
|
||||
int btn_back_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
|
||||
// 停止所有定时器
|
||||
if (timer)
|
||||
{
|
||||
IupSetAttribute(timer, "RUN", "NO");
|
||||
IupDestroy(timer);
|
||||
timer = NULL;
|
||||
}
|
||||
if (llm_timer)
|
||||
{
|
||||
IupSetAttribute(llm_timer, "RUN", "NO");
|
||||
IupDestroy(llm_timer);
|
||||
llm_timer = NULL;
|
||||
}
|
||||
|
||||
// 如果是网络模式,彻底清理网络资源
|
||||
if (gui_game_mode == 3)
|
||||
{
|
||||
cleanup_network();
|
||||
}
|
||||
|
||||
// 1. 先显示主菜单
|
||||
show_main_menu();
|
||||
|
||||
// 2. 销毁游戏窗口
|
||||
if (dlg)
|
||||
{
|
||||
Ihandle *old_dlg = dlg;
|
||||
dlg = NULL; // 先清除全局指针
|
||||
IupDestroy(old_dlg);
|
||||
}
|
||||
|
||||
return IUP_IGNORE; // 返回 IUP_IGNORE 以阻止默认处理
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 鼠标点击回调
|
||||
*/
|
||||
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
{
|
||||
(void)status; // 未使用
|
||||
if (gui_game_mode == 2)
|
||||
return IUP_DEFAULT; // 复盘模式禁用点击
|
||||
|
||||
if (button == IUP_BUTTON1 && pressed)
|
||||
{ // 左键按下
|
||||
if (game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
if (gui_game_mode == 3 && current_player_gui != network_state.local_player_id)
|
||||
{
|
||||
return IUP_DEFAULT; // 网络模式下,非自己回合不可落子
|
||||
}
|
||||
|
||||
int board_x, board_y;
|
||||
if (screen_to_board(x, 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 (gui_game_mode == 3)
|
||||
send_move(board_x, board_y, current_player_gui); // 发送最后一步
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "黑子获胜!");
|
||||
IupMessage("游戏结束", "黑子获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "白子获胜!");
|
||||
IupMessage("游戏结束", "白子获胜!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gui_game_mode == 0) // PvP
|
||||
{
|
||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER; // AI 在这里表示玩家2
|
||||
if (current_player_gui == PLAYER)
|
||||
sprintf(status_message, "轮到黑子");
|
||||
else
|
||||
sprintf(status_message, "轮到白子");
|
||||
}
|
||||
else if (gui_game_mode == 3) // Network
|
||||
{
|
||||
send_move(board_x, board_y, current_player_gui);
|
||||
current_player_gui = network_state.remote_player_id;
|
||||
sprintf(status_message, "等待对手落子...");
|
||||
}
|
||||
else // PvE
|
||||
{
|
||||
current_player_gui = AI;
|
||||
update_ui_labels();
|
||||
IupUpdate(ih); // 立即更新显示
|
||||
IupFlush(); // 强制刷新事件队列
|
||||
|
||||
if (llm_use)
|
||||
{
|
||||
// 大模型AI - 异步调用,不阻塞UI
|
||||
sprintf(status_message, "AI思考中(大模型)...");
|
||||
update_ui_labels();
|
||||
|
||||
// 创建或复用轮询定时器
|
||||
if (!llm_timer)
|
||||
{
|
||||
llm_timer = IupTimer();
|
||||
IupSetCallback(llm_timer, "ACTION_CB", (Icallback)llm_timer_cb);
|
||||
IupSetAttribute(llm_timer, "TIME", "100"); // 100ms轮询
|
||||
}
|
||||
llm_ai_start_move();
|
||||
IupSetAttribute(llm_timer, "RUN", "YES");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 算法AI - 同步调用
|
||||
sprintf(status_message, "AI思考中...");
|
||||
update_ui_labels();
|
||||
|
||||
ai_move(ai_difficulty);
|
||||
process_ai_move_result();
|
||||
}
|
||||
}
|
||||
}
|
||||
update_ui_labels();
|
||||
IupUpdate(ih); // 请求重绘
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "无效位置!");
|
||||
update_ui_labels();
|
||||
}
|
||||
}
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 键盘回调
|
||||
*/
|
||||
int k_any_cb(Ihandle *ih, int c)
|
||||
{
|
||||
(void)ih;
|
||||
if (c == K_ESC)
|
||||
{
|
||||
if (dlg && IupGetInt(dlg, "VISIBLE"))
|
||||
{
|
||||
btn_back_cb(ih); // 调用返回菜单逻辑
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建游戏窗口
|
||||
*/
|
||||
void create_game_window()
|
||||
{
|
||||
if (dlg)
|
||||
{
|
||||
IupDestroy(dlg);
|
||||
dlg = NULL;
|
||||
}
|
||||
|
||||
// 创建Canvas (棋盘)
|
||||
board_canvas = IupCanvas(NULL);
|
||||
if (!board_canvas)
|
||||
printf("ERROR: Failed to create board_canvas\n");
|
||||
|
||||
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
|
||||
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
|
||||
IupSetCallback(board_canvas, "K_ANY", (Icallback)k_any_cb);
|
||||
IupSetCallback(board_canvas, "MAP_CB", (Icallback)map_cb);
|
||||
|
||||
// 计算棋盘像素大小(含坐标标注区域)
|
||||
int board_pixel_size = (BOARD_SIZE - 1) * CELL_SIZE + BOARD_OFFSET_X * 2 + 20;
|
||||
char size[32];
|
||||
sprintf(size, "%dx%d", board_pixel_size, board_pixel_size);
|
||||
IupSetAttribute(board_canvas, "RASTERSIZE", size);
|
||||
IupSetAttribute(board_canvas, "EXPAND", "NO");
|
||||
IupSetAttribute(board_canvas, "BORDER", "NO");
|
||||
IupSetAttribute(board_canvas, "BGCOLOR", "212 165 116");
|
||||
|
||||
// === 创建标签 ===
|
||||
lbl_player = IupLabel("当前玩家: 黑子");
|
||||
IupSetAttribute(lbl_player, "FONT", "SimHei, 13");
|
||||
IupSetAttribute(lbl_player, "FGCOLOR", CLR_TEXT_TITLE);
|
||||
|
||||
lbl_status = IupLabel("准备开始");
|
||||
IupSetAttribute(lbl_status, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(lbl_status, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === 对局信息面板 ===
|
||||
Ihandle *info_vbox = IupVbox(lbl_player, lbl_status, NULL);
|
||||
IupSetAttribute(info_vbox, "GAP", "4");
|
||||
IupSetAttribute(info_vbox, "MARGIN", "10x8");
|
||||
Ihandle *frm_info = IupFrame(info_vbox);
|
||||
IupSetAttribute(frm_info, "TITLE", "对局信息");
|
||||
IupSetAttribute(frm_info, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_info, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === 按钮样式宏 ===
|
||||
#define SET_BTN_STYLE(btn, w, h, font) \
|
||||
IupSetAttribute(btn, "SIZE", #w "x" #h); \
|
||||
IupSetAttribute(btn, "FONT", font); \
|
||||
IupSetAttribute(btn, "BGCOLOR", CLR_BTN_NORMAL_BG); \
|
||||
IupSetAttribute(btn, "FGCOLOR", CLR_BTN_NORMAL_FG); \
|
||||
IupSetAttribute(btn, "FLAT", "YES")
|
||||
|
||||
Ihandle *vbox_controls;
|
||||
|
||||
if (gui_game_mode == 2) // 复盘模式
|
||||
{
|
||||
Ihandle *btn_prev = IupButton("上一步", NULL);
|
||||
IupSetCallback(btn_prev, "ACTION", (Icallback)btn_replay_prev_cb);
|
||||
SET_BTN_STYLE(btn_prev, 120, 35, "SimHei, 11");
|
||||
|
||||
Ihandle *btn_next = IupButton("下一步", NULL);
|
||||
IupSetCallback(btn_next, "ACTION", (Icallback)btn_replay_next_cb);
|
||||
SET_BTN_STYLE(btn_next, 120, 35, "SimHei, 11");
|
||||
|
||||
Ihandle *hbox_nav = IupHbox(btn_prev, btn_next, NULL);
|
||||
IupSetAttribute(hbox_nav, "GAP", "8");
|
||||
IupSetAttribute(hbox_nav, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *btn_back = IupButton("返回菜单", NULL);
|
||||
IupSetCallback(btn_back, "ACTION", (Icallback)btn_back_cb);
|
||||
SET_BTN_STYLE(btn_back, 120, 35, "SimHei, 11");
|
||||
|
||||
vbox_controls = IupVbox(
|
||||
frm_info,
|
||||
hbox_nav,
|
||||
btn_back,
|
||||
NULL);
|
||||
}
|
||||
else // 游戏模式 (PvP / PvE / Network)
|
||||
{
|
||||
Ihandle *btn_undo = IupButton("悔棋", NULL);
|
||||
IupSetCallback(btn_undo, "ACTION", (Icallback)btn_undo_cb);
|
||||
SET_BTN_STYLE(btn_undo, 120, 35, "SimHei, 11");
|
||||
|
||||
Ihandle *btn_save = IupButton("保存棋谱", NULL);
|
||||
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_cb);
|
||||
SET_BTN_STYLE(btn_save, 120, 35, "SimHei, 11");
|
||||
|
||||
Ihandle *btn_back = IupButton("返回菜单", NULL);
|
||||
IupSetCallback(btn_back, "ACTION", (Icallback)btn_back_cb);
|
||||
SET_BTN_STYLE(btn_back, 120, 35, "SimHei, 11");
|
||||
|
||||
vbox_controls = IupVbox(
|
||||
frm_info,
|
||||
btn_undo,
|
||||
btn_save,
|
||||
btn_back,
|
||||
NULL);
|
||||
}
|
||||
|
||||
#undef SET_BTN_STYLE
|
||||
|
||||
IupSetAttribute(vbox_controls, "GAP", "10");
|
||||
IupSetAttribute(vbox_controls, "MARGIN", "10x10");
|
||||
IupSetAttribute(vbox_controls, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *hbox_main = IupHbox(board_canvas, vbox_controls, NULL);
|
||||
IupSetAttribute(hbox_main, "MARGIN", "10x10");
|
||||
IupSetAttribute(hbox_main, "GAP", "8");
|
||||
|
||||
// 创建Dialog
|
||||
dlg = IupDialog(hbox_main);
|
||||
if (!dlg)
|
||||
printf("ERROR: Failed to create dialog\n");
|
||||
|
||||
IupSetAttribute(dlg, "TITLE", "五子棋");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
IupSetAttribute(dlg, "BGCOLOR", CLR_WINDOW_BG);
|
||||
|
||||
// 设置 CLOSE_CB 回调,确保点击X也能正确返回菜单
|
||||
IupSetCallback(dlg, "CLOSE_CB", (Icallback)btn_back_cb);
|
||||
}
|
||||
|
||||
void start_pvp_game_gui()
|
||||
{
|
||||
gui_game_mode = 0;
|
||||
empty_board();
|
||||
current_player_gui = PLAYER;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupFlush(); // 确保窗口完全映射
|
||||
sprintf(status_message, "玩家对战模式 - 黑方先行");
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
void start_pve_game_gui()
|
||||
{
|
||||
gui_game_mode = 1;
|
||||
empty_board();
|
||||
current_player_gui = PLAYER;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
|
||||
if (dlg)
|
||||
{
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupFlush();
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprintf(status_message, "人机对战模式 - 玩家执黑先行");
|
||||
update_ui_labels();
|
||||
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
void start_network_game_gui()
|
||||
{
|
||||
gui_game_mode = 3;
|
||||
empty_board();
|
||||
|
||||
current_player_gui = PLAYER1;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
|
||||
if (dlg)
|
||||
{
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupFlush();
|
||||
}
|
||||
|
||||
if (network_state.is_server)
|
||||
sprintf(status_message, "局域网联机 - 你是主机(黑子),轮到你落子");
|
||||
else
|
||||
sprintf(status_message, "局域网联机 - 你是客机(白子),等待对手落子...");
|
||||
|
||||
update_ui_labels();
|
||||
|
||||
// 强制初始重绘
|
||||
if (board_canvas)
|
||||
{
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
// 启动网络轮询定时器
|
||||
timer = IupTimer();
|
||||
IupSetCallback(timer, "ACTION_CB", (Icallback)timer_cb);
|
||||
IupSetAttribute(timer, "TIME", "50"); // 50ms 轮询一次
|
||||
IupSetAttribute(timer, "RUN", "YES");
|
||||
}
|
||||
@@ -0,0 +1,559 @@
|
||||
#include <iup.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "gui_menu.h"
|
||||
#include "gui.h"
|
||||
#include "gui_internal.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include "network.h"
|
||||
|
||||
Ihandle *menu_dlg = NULL;
|
||||
|
||||
static int btn_pvp_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
start_pvp_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_pve_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
start_pve_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// --- 网络对战相关回调 ---
|
||||
static int btn_network_host_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_port = IupGetDialogChild(dlg, "NET_PORT");
|
||||
int port = IupGetInt(txt_port, "VALUE");
|
||||
if (port <= 0 || port > 65535) port = DEFAULT_NETWORK_PORT;
|
||||
|
||||
if (create_server(port))
|
||||
{
|
||||
IupMessage("成功", "房间创建成功,等待玩家加入...");
|
||||
IupHide(dlg);
|
||||
start_network_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "创建房间失败,可能是端口被占用");
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_join_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_ip = IupGetDialogChild(dlg, "NET_IP");
|
||||
Ihandle *txt_port = IupGetDialogChild(dlg, "NET_PORT");
|
||||
|
||||
char *ip = IupGetAttribute(txt_ip, "VALUE");
|
||||
int port = IupGetInt(txt_port, "VALUE");
|
||||
if (port <= 0 || port > 65535) port = DEFAULT_NETWORK_PORT;
|
||||
|
||||
if (connect_to_server(ip, port))
|
||||
{
|
||||
IupMessage("成功", "成功加入房间!");
|
||||
IupHide(dlg);
|
||||
start_network_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "加入房间失败,请检查IP和端口");
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_cancel_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
|
||||
Ihandle *lbl_ip = IupLabel("目标 IP:");
|
||||
Ihandle *txt_ip = IupText(NULL);
|
||||
IupSetAttribute(txt_ip, "NAME", "NET_IP");
|
||||
IupSetAttribute(txt_ip, "VALUE", "127.0.0.1");
|
||||
IupSetAttribute(txt_ip, "SIZE", "120x");
|
||||
|
||||
Ihandle *hbox_ip = IupHbox(lbl_ip, txt_ip, NULL);
|
||||
IupSetAttribute(hbox_ip, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_ip, "GAP", "10");
|
||||
|
||||
Ihandle *lbl_port = IupLabel("端口:");
|
||||
Ihandle *txt_port = IupText(NULL);
|
||||
IupSetAttribute(txt_port, "NAME", "NET_PORT");
|
||||
char port_str[16];
|
||||
sprintf(port_str, "%d", DEFAULT_NETWORK_PORT);
|
||||
IupSetAttribute(txt_port, "VALUE", port_str);
|
||||
IupSetAttribute(txt_port, "SIZE", "60x");
|
||||
|
||||
Ihandle *hbox_port = IupHbox(lbl_port, txt_port, NULL);
|
||||
IupSetAttribute(hbox_port, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_port, "GAP", "10");
|
||||
|
||||
Ihandle *vbox_input = IupVbox(hbox_ip, hbox_port, NULL);
|
||||
IupSetAttribute(vbox_input, "GAP", "8");
|
||||
IupSetAttribute(vbox_input, "MARGIN", "10x8");
|
||||
|
||||
Ihandle *frm_input = IupFrame(vbox_input);
|
||||
IupSetAttribute(frm_input, "TITLE", "连接设置");
|
||||
IupSetAttribute(frm_input, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_input, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
Ihandle *btn_host = IupButton("创建房间", NULL);
|
||||
IupSetCallback(btn_host, "ACTION", (Icallback)btn_network_host_cb);
|
||||
IupSetAttribute(btn_host, "SIZE", "100x32");
|
||||
IupSetAttribute(btn_host, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_host, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_host, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_join = IupButton("加入房间", NULL);
|
||||
IupSetCallback(btn_join, "ACTION", (Icallback)btn_network_join_cb);
|
||||
IupSetAttribute(btn_join, "SIZE", "100x32");
|
||||
IupSetAttribute(btn_join, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_join, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_join, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_network_cancel_cb);
|
||||
IupSetAttribute(btn_cancel, "SIZE", "80x32");
|
||||
IupSetAttribute(btn_cancel, "BGCOLOR", CLR_BTN_NORMAL_BG);
|
||||
IupSetAttribute(btn_cancel, "FGCOLOR", CLR_BTN_NORMAL_FG);
|
||||
IupSetAttribute(btn_cancel, "FLAT", "YES");
|
||||
|
||||
Ihandle *hbox_btns = IupHbox(btn_host, btn_join, btn_cancel, NULL);
|
||||
IupSetAttribute(hbox_btns, "GAP", "8");
|
||||
IupSetAttribute(hbox_btns, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *vbox = IupVbox(frm_input, hbox_btns, NULL);
|
||||
IupSetAttribute(vbox, "MARGIN", "15x15");
|
||||
IupSetAttribute(vbox, "GAP", "12");
|
||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *dlg = IupDialog(vbox);
|
||||
IupSetAttribute(dlg, "TITLE", "局域网联机");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
IupSetAttribute(dlg, "BGCOLOR", CLR_WINDOW_BG);
|
||||
|
||||
IupPopup(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupDestroy(dlg);
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
// --- 网络对战结束 ---
|
||||
|
||||
static int btn_replay_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
start_replay_gui();
|
||||
IupHide(menu_dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_save_settings_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
|
||||
// Get values
|
||||
Ihandle *txt_board_size = IupGetDialogChild(dlg, "BOARD_SIZE");
|
||||
Ihandle *tgl_forbidden = IupGetDialogChild(dlg, "FORBIDDEN");
|
||||
Ihandle *tgl_timer = IupGetDialogChild(dlg, "TIMER");
|
||||
Ihandle *txt_time_limit = IupGetDialogChild(dlg, "TIME_LIMIT");
|
||||
Ihandle *lst_ai = IupGetDialogChild(dlg, "AI_DIFFICULTY");
|
||||
|
||||
// Update globals
|
||||
int new_size = IupGetInt(txt_board_size, "VALUE");
|
||||
if (new_size < MIN_BOARD_SIZE)
|
||||
new_size = MIN_BOARD_SIZE;
|
||||
if (new_size > MAX_BOARD_SIZE)
|
||||
new_size = MAX_BOARD_SIZE;
|
||||
BOARD_SIZE = new_size;
|
||||
|
||||
use_forbidden_moves = IupGetInt(tgl_forbidden, "VALUE");
|
||||
|
||||
use_timer = IupGetInt(tgl_timer, "VALUE");
|
||||
if (use_timer)
|
||||
{
|
||||
int minutes = IupGetInt(txt_time_limit, "VALUE");
|
||||
if (minutes < 1)
|
||||
minutes = 1;
|
||||
time_limit = minutes * 60;
|
||||
}
|
||||
|
||||
int ai_level = IupGetInt(lst_ai, "VALUE");
|
||||
if (ai_level < 1)
|
||||
ai_level = 1;
|
||||
if (ai_level > 5)
|
||||
ai_level = 5;
|
||||
ai_difficulty = ai_level;
|
||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
||||
|
||||
// LLM 设置
|
||||
Ihandle *lst_ai_mode = IupGetDialogChild(dlg, "AI_MODE");
|
||||
Ihandle *txt_endpoint = IupGetDialogChild(dlg, "LLM_ENDPOINT");
|
||||
Ihandle *txt_apikey = IupGetDialogChild(dlg, "LLM_API_KEY");
|
||||
Ihandle *txt_model = IupGetDialogChild(dlg, "LLM_MODEL");
|
||||
|
||||
int ai_mode = IupGetInt(lst_ai_mode, "VALUE");
|
||||
llm_use = (ai_mode == 2) ? 1 : 0;
|
||||
|
||||
char *endpoint = IupGetAttribute(txt_endpoint, "VALUE");
|
||||
if (endpoint)
|
||||
{
|
||||
strncpy(llm_endpoint, endpoint, MAX_LLM_ENDPOINT_LEN - 1);
|
||||
llm_endpoint[MAX_LLM_ENDPOINT_LEN - 1] = '\0';
|
||||
}
|
||||
|
||||
char *apikey = IupGetAttribute(txt_apikey, "VALUE");
|
||||
if (apikey)
|
||||
{
|
||||
strncpy(llm_api_key, apikey, MAX_LLM_API_KEY_LEN - 1);
|
||||
llm_api_key[MAX_LLM_API_KEY_LEN - 1] = '\0';
|
||||
}
|
||||
|
||||
char *model = IupGetAttribute(txt_model, "VALUE");
|
||||
if (model)
|
||||
{
|
||||
strncpy(llm_model, model, MAX_LLM_MODEL_LEN - 1);
|
||||
llm_model[MAX_LLM_MODEL_LEN - 1] = '\0';
|
||||
}
|
||||
|
||||
// Save config
|
||||
save_game_config();
|
||||
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_cancel_settings_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int tgl_timer_cb(Ihandle *ih, int state)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_time_limit = IupGetDialogChild(dlg, "TIME_LIMIT");
|
||||
IupSetAttribute(txt_time_limit, "ACTIVE", state ? "YES" : "NO");
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_settings_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
|
||||
// === 基本设置 ===
|
||||
Ihandle *lbl_board_size = IupLabel("棋盘大小:");
|
||||
Ihandle *txt_board_size = IupText(NULL);
|
||||
IupSetAttribute(txt_board_size, "NAME", "BOARD_SIZE");
|
||||
IupSetAttribute(txt_board_size, "SPIN", "YES");
|
||||
IupSetAttribute(txt_board_size, "SPINMIN", "5");
|
||||
IupSetAttribute(txt_board_size, "SPINMAX", "25");
|
||||
IupSetInt(txt_board_size, "VALUE", BOARD_SIZE);
|
||||
IupSetAttribute(txt_board_size, "SIZE", "50x");
|
||||
|
||||
Ihandle *hbox_board = IupHbox(lbl_board_size, txt_board_size, NULL);
|
||||
IupSetAttribute(hbox_board, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_board, "GAP", "10");
|
||||
|
||||
Ihandle *tgl_forbidden = IupToggle("启用禁手规则", NULL);
|
||||
IupSetAttribute(tgl_forbidden, "NAME", "FORBIDDEN");
|
||||
IupSetInt(tgl_forbidden, "VALUE", use_forbidden_moves);
|
||||
|
||||
Ihandle *tgl_timer = IupToggle("启用计时器", NULL);
|
||||
IupSetAttribute(tgl_timer, "NAME", "TIMER");
|
||||
IupSetInt(tgl_timer, "VALUE", use_timer);
|
||||
IupSetCallback(tgl_timer, "ACTION", (Icallback)tgl_timer_cb);
|
||||
|
||||
Ihandle *lbl_time_limit = IupLabel("时间限制(分钟):");
|
||||
Ihandle *txt_time_limit = IupText(NULL);
|
||||
IupSetAttribute(txt_time_limit, "NAME", "TIME_LIMIT");
|
||||
IupSetAttribute(txt_time_limit, "SPIN", "YES");
|
||||
IupSetAttribute(txt_time_limit, "SPINMIN", "1");
|
||||
IupSetAttribute(txt_time_limit, "SPINMAX", "60");
|
||||
IupSetInt(txt_time_limit, "VALUE", time_limit / 60);
|
||||
IupSetAttribute(txt_time_limit, "ACTIVE", use_timer ? "YES" : "NO");
|
||||
IupSetAttribute(txt_time_limit, "SIZE", "50x");
|
||||
|
||||
Ihandle *hbox_time = IupHbox(lbl_time_limit, txt_time_limit, NULL);
|
||||
IupSetAttribute(hbox_time, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_time, "GAP", "10");
|
||||
|
||||
Ihandle *vbox_basic = IupVbox(hbox_board, tgl_forbidden, tgl_timer, hbox_time, NULL);
|
||||
IupSetAttribute(vbox_basic, "GAP", "8");
|
||||
IupSetAttribute(vbox_basic, "MARGIN", "10x8");
|
||||
|
||||
Ihandle *frm_basic = IupFrame(vbox_basic);
|
||||
IupSetAttribute(frm_basic, "TITLE", "基本设置");
|
||||
IupSetAttribute(frm_basic, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_basic, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === AI 设置 ===
|
||||
Ihandle *lbl_ai = IupLabel("AI 难度:");
|
||||
Ihandle *lst_ai = IupList(NULL);
|
||||
IupSetAttribute(lst_ai, "NAME", "AI_DIFFICULTY");
|
||||
IupSetAttribute(lst_ai, "DROPDOWN", "YES");
|
||||
IupSetAttribute(lst_ai, "1", "1 简单");
|
||||
IupSetAttribute(lst_ai, "2", "2 普通");
|
||||
IupSetAttribute(lst_ai, "3", "3 中等");
|
||||
IupSetAttribute(lst_ai, "4", "4 困难");
|
||||
IupSetAttribute(lst_ai, "5", "5 专家");
|
||||
IupSetInt(lst_ai, "VALUE", ai_difficulty);
|
||||
IupSetAttribute(lst_ai, "SIZE", "80x");
|
||||
|
||||
Ihandle *hbox_ai = IupHbox(lbl_ai, lst_ai, NULL);
|
||||
IupSetAttribute(hbox_ai, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_ai, "GAP", "10");
|
||||
|
||||
Ihandle *lbl_ai_mode = IupLabel("AI模式:");
|
||||
Ihandle *lst_ai_mode = IupList(NULL);
|
||||
IupSetAttribute(lst_ai_mode, "NAME", "AI_MODE");
|
||||
IupSetAttribute(lst_ai_mode, "DROPDOWN", "YES");
|
||||
IupSetAttribute(lst_ai_mode, "1", "算法AI (本地)");
|
||||
IupSetAttribute(lst_ai_mode, "2", "大模型AI (在线)");
|
||||
IupSetInt(lst_ai_mode, "VALUE", llm_use ? 2 : 1);
|
||||
IupSetAttribute(lst_ai_mode, "SIZE", "120x");
|
||||
|
||||
Ihandle *hbox_ai_mode = IupHbox(lbl_ai_mode, lst_ai_mode, NULL);
|
||||
IupSetAttribute(hbox_ai_mode, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_ai_mode, "GAP", "10");
|
||||
|
||||
Ihandle *vbox_ai = IupVbox(hbox_ai, hbox_ai_mode, NULL);
|
||||
IupSetAttribute(vbox_ai, "GAP", "8");
|
||||
IupSetAttribute(vbox_ai, "MARGIN", "10x8");
|
||||
|
||||
Ihandle *frm_ai = IupFrame(vbox_ai);
|
||||
IupSetAttribute(frm_ai, "TITLE", "AI 设置");
|
||||
IupSetAttribute(frm_ai, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_ai, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === 大模型设置 ===
|
||||
Ihandle *lbl_endpoint = IupLabel("API地址:");
|
||||
Ihandle *txt_endpoint = IupText(NULL);
|
||||
IupSetAttribute(txt_endpoint, "NAME", "LLM_ENDPOINT");
|
||||
IupSetAttribute(txt_endpoint, "VALUE", llm_endpoint);
|
||||
IupSetAttribute(txt_endpoint, "SIZE", "250x");
|
||||
|
||||
Ihandle *hbox_endpoint = IupHbox(lbl_endpoint, txt_endpoint, NULL);
|
||||
IupSetAttribute(hbox_endpoint, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_endpoint, "GAP", "10");
|
||||
|
||||
Ihandle *lbl_apikey = IupLabel("API Key:");
|
||||
Ihandle *txt_apikey = IupText(NULL);
|
||||
IupSetAttribute(txt_apikey, "NAME", "LLM_API_KEY");
|
||||
IupSetAttribute(txt_apikey, "VALUE", llm_api_key);
|
||||
IupSetAttribute(txt_apikey, "PASSWORD", "YES");
|
||||
IupSetAttribute(txt_apikey, "SIZE", "200x");
|
||||
|
||||
Ihandle *hbox_apikey = IupHbox(lbl_apikey, txt_apikey, NULL);
|
||||
IupSetAttribute(hbox_apikey, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_apikey, "GAP", "10");
|
||||
|
||||
Ihandle *lbl_model = IupLabel("模型名称:");
|
||||
Ihandle *txt_model = IupText(NULL);
|
||||
IupSetAttribute(txt_model, "NAME", "LLM_MODEL");
|
||||
IupSetAttribute(txt_model, "VALUE", llm_model);
|
||||
IupSetAttribute(txt_model, "SIZE", "150x");
|
||||
|
||||
Ihandle *hbox_model = IupHbox(lbl_model, txt_model, NULL);
|
||||
IupSetAttribute(hbox_model, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_model, "GAP", "10");
|
||||
|
||||
Ihandle *vbox_llm = IupVbox(hbox_endpoint, hbox_apikey, hbox_model, NULL);
|
||||
IupSetAttribute(vbox_llm, "GAP", "8");
|
||||
IupSetAttribute(vbox_llm, "MARGIN", "10x8");
|
||||
|
||||
Ihandle *frm_llm = IupFrame(vbox_llm);
|
||||
IupSetAttribute(frm_llm, "TITLE", "大模型设置");
|
||||
IupSetAttribute(frm_llm, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_llm, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === 按钮 ===
|
||||
Ihandle *btn_save = IupButton("保存", NULL);
|
||||
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_settings_cb);
|
||||
IupSetAttribute(btn_save, "SIZE", "80x30");
|
||||
IupSetAttribute(btn_save, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_save, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_save, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_settings_cb);
|
||||
IupSetAttribute(btn_cancel, "SIZE", "80x30");
|
||||
IupSetAttribute(btn_cancel, "BGCOLOR", CLR_BTN_NORMAL_BG);
|
||||
IupSetAttribute(btn_cancel, "FGCOLOR", CLR_BTN_NORMAL_FG);
|
||||
IupSetAttribute(btn_cancel, "FLAT", "YES");
|
||||
|
||||
Ihandle *hbox_btns = IupHbox(btn_save, btn_cancel, NULL);
|
||||
IupSetAttribute(hbox_btns, "GAP", "15");
|
||||
IupSetAttribute(hbox_btns, "MARGIN", "5x0");
|
||||
IupSetAttribute(hbox_btns, "ALIGNMENT", "ACENTER");
|
||||
|
||||
// === 主布局 ===
|
||||
Ihandle *vbox = IupVbox(frm_basic, frm_ai, frm_llm, hbox_btns, NULL);
|
||||
IupSetAttribute(vbox, "GAP", "10");
|
||||
IupSetAttribute(vbox, "MARGIN", "15x15");
|
||||
|
||||
Ihandle *dlg = IupDialog(vbox);
|
||||
IupSetAttribute(dlg, "TITLE", "游戏设置");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
IupSetAttribute(dlg, "MINBOX", "NO");
|
||||
IupSetAttribute(dlg, "MAXBOX", "NO");
|
||||
IupSetAttribute(dlg, "BGCOLOR", CLR_WINDOW_BG);
|
||||
|
||||
IupPopup(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupDestroy(dlg);
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_exit_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
cleanup_gui(); // 清理GUI资源
|
||||
exit(0);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建主菜单
|
||||
*/
|
||||
void create_main_menu()
|
||||
{
|
||||
if (menu_dlg)
|
||||
return;
|
||||
|
||||
// === 标题区 ===
|
||||
Ihandle *lbl_title = IupLabel("五子棋");
|
||||
IupSetAttribute(lbl_title, "FONT", "SimHei, 32");
|
||||
IupSetAttribute(lbl_title, "FGCOLOR", CLR_TEXT_TITLE);
|
||||
IupSetAttribute(lbl_title, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *lbl_subtitle = IupLabel("Gobang");
|
||||
IupSetAttribute(lbl_subtitle, "FONT", "SimHei, 14");
|
||||
IupSetAttribute(lbl_subtitle, "FGCOLOR", CLR_TEXT_SECONDARY);
|
||||
IupSetAttribute(lbl_subtitle, "ALIGNMENT", "ACENTER");
|
||||
|
||||
// === 游戏模式按钮(主按钮样式:深棕底白字)===
|
||||
Ihandle *btn_pvp = IupButton("玩家对战", NULL);
|
||||
IupSetCallback(btn_pvp, "ACTION", (Icallback)btn_pvp_cb);
|
||||
IupSetAttribute(btn_pvp, "SIZE", "150x38");
|
||||
IupSetAttribute(btn_pvp, "FONT", "SimHei, 13");
|
||||
IupSetAttribute(btn_pvp, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_pvp, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_pvp, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_pve = IupButton("人机对战", NULL);
|
||||
IupSetCallback(btn_pve, "ACTION", (Icallback)btn_pve_cb);
|
||||
IupSetAttribute(btn_pve, "SIZE", "150x38");
|
||||
IupSetAttribute(btn_pve, "FONT", "SimHei, 13");
|
||||
IupSetAttribute(btn_pve, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_pve, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_pve, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_net = IupButton("局域网联机", NULL);
|
||||
IupSetCallback(btn_net, "ACTION", (Icallback)btn_network_cb);
|
||||
IupSetAttribute(btn_net, "SIZE", "150x38");
|
||||
IupSetAttribute(btn_net, "FONT", "SimHei, 13");
|
||||
IupSetAttribute(btn_net, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_net, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_net, "FLAT", "YES");
|
||||
|
||||
Ihandle *vbox_modes = IupVbox(btn_pvp, btn_pve, btn_net, NULL);
|
||||
IupSetAttribute(vbox_modes, "GAP", "8");
|
||||
IupSetAttribute(vbox_modes, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(vbox_modes, "MARGIN", "15x10");
|
||||
|
||||
Ihandle *frm_modes = IupFrame(vbox_modes);
|
||||
IupSetAttribute(frm_modes, "TITLE", "选择模式");
|
||||
IupSetAttribute(frm_modes, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_modes, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === 功能按钮(普通按钮样式:浅棕底深棕字)===
|
||||
Ihandle *btn_replay = IupButton("复盘回放", NULL);
|
||||
IupSetCallback(btn_replay, "ACTION", (Icallback)btn_replay_cb);
|
||||
IupSetAttribute(btn_replay, "SIZE", "120x32");
|
||||
IupSetAttribute(btn_replay, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(btn_replay, "BGCOLOR", CLR_BTN_NORMAL_BG);
|
||||
IupSetAttribute(btn_replay, "FGCOLOR", CLR_BTN_NORMAL_FG);
|
||||
IupSetAttribute(btn_replay, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_settings = IupButton("游戏设置", NULL);
|
||||
IupSetCallback(btn_settings, "ACTION", (Icallback)btn_settings_cb);
|
||||
IupSetAttribute(btn_settings, "SIZE", "120x32");
|
||||
IupSetAttribute(btn_settings, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(btn_settings, "BGCOLOR", CLR_BTN_NORMAL_BG);
|
||||
IupSetAttribute(btn_settings, "FGCOLOR", CLR_BTN_NORMAL_FG);
|
||||
IupSetAttribute(btn_settings, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_exit = IupButton("退出游戏", NULL);
|
||||
IupSetCallback(btn_exit, "ACTION", (Icallback)btn_exit_cb);
|
||||
IupSetAttribute(btn_exit, "SIZE", "120x32");
|
||||
IupSetAttribute(btn_exit, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(btn_exit, "BGCOLOR", CLR_BTN_NORMAL_BG);
|
||||
IupSetAttribute(btn_exit, "FGCOLOR", CLR_BTN_NORMAL_FG);
|
||||
IupSetAttribute(btn_exit, "FLAT", "YES");
|
||||
|
||||
Ihandle *hbox_func = IupHbox(btn_replay, btn_settings, btn_exit, NULL);
|
||||
IupSetAttribute(hbox_func, "GAP", "10");
|
||||
IupSetAttribute(hbox_func, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_func, "MARGIN", "10x8");
|
||||
|
||||
Ihandle *frm_func = IupFrame(hbox_func);
|
||||
IupSetAttribute(frm_func, "TITLE", "功能");
|
||||
IupSetAttribute(frm_func, "FONT", "SimHei, 11");
|
||||
IupSetAttribute(frm_func, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
// === 主布局 ===
|
||||
Ihandle *vbox = IupVbox(
|
||||
lbl_title,
|
||||
lbl_subtitle,
|
||||
IupLabel(NULL), // 间隔
|
||||
frm_modes,
|
||||
frm_func,
|
||||
NULL);
|
||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(vbox, "GAP", "8");
|
||||
IupSetAttribute(vbox, "MARGIN", "30x25");
|
||||
|
||||
menu_dlg = IupDialog(vbox);
|
||||
IupSetAttribute(menu_dlg, "TITLE", "五子棋 - 主菜单");
|
||||
IupSetAttribute(menu_dlg, "RESIZE", "NO");
|
||||
IupSetAttribute(menu_dlg, "MINBOX", "NO");
|
||||
IupSetAttribute(menu_dlg, "MAXBOX", "NO");
|
||||
IupSetAttribute(menu_dlg, "BGCOLOR", CLR_WINDOW_BG);
|
||||
IupSetAttribute(menu_dlg, "SIZE", "380x520");
|
||||
|
||||
// 设置对话框关闭回调 (点X关闭程序)
|
||||
IupSetCallback(menu_dlg, "CLOSE_CB", (Icallback)btn_exit_cb);
|
||||
IupMap(menu_dlg); // Map immediately
|
||||
}
|
||||
|
||||
void show_main_menu()
|
||||
{
|
||||
if (!menu_dlg)
|
||||
create_main_menu();
|
||||
IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER);
|
||||
}
|
||||
|
||||
void hide_main_menu()
|
||||
{
|
||||
if (menu_dlg)
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "globals.h"
|
||||
#include "gobang.h"
|
||||
#include "record.h"
|
||||
#include "config.h"
|
||||
#include <iup.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -173,20 +174,28 @@ void select_replay_file_gui()
|
||||
// 创建确定和取消按钮
|
||||
Ihandle *btn_ok = IupButton("确定", NULL);
|
||||
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_replay_sel_ok_cb);
|
||||
IupSetAttribute(btn_ok, "SIZE", "60x30");
|
||||
IupSetAttribute(btn_ok, "SIZE", "80x32");
|
||||
IupSetAttribute(btn_ok, "BGCOLOR", CLR_BTN_PRIMARY_BG);
|
||||
IupSetAttribute(btn_ok, "FGCOLOR", CLR_BTN_PRIMARY_FG);
|
||||
IupSetAttribute(btn_ok, "FLAT", "YES");
|
||||
|
||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_replay_sel_cancel_cb);
|
||||
IupSetAttribute(btn_cancel, "SIZE", "60x30");
|
||||
IupSetAttribute(btn_cancel, "SIZE", "80x32");
|
||||
IupSetAttribute(btn_cancel, "BGCOLOR", CLR_BTN_NORMAL_BG);
|
||||
IupSetAttribute(btn_cancel, "FGCOLOR", CLR_BTN_NORMAL_FG);
|
||||
IupSetAttribute(btn_cancel, "FLAT", "YES");
|
||||
|
||||
Ihandle *vbox = IupVbox(
|
||||
IupLabel("请选择复盘文件:"),
|
||||
list,
|
||||
IupHbox(btn_ok, btn_cancel, NULL),
|
||||
NULL);
|
||||
Ihandle *lbl_prompt = IupLabel("选择复盘文件:");
|
||||
IupSetAttribute(lbl_prompt, "FGCOLOR", CLR_TEXT_NORMAL);
|
||||
|
||||
Ihandle *hbox_btns = IupHbox(btn_ok, btn_cancel, NULL);
|
||||
IupSetAttribute(hbox_btns, "GAP", "10");
|
||||
IupSetAttribute(hbox_btns, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *vbox = IupVbox(lbl_prompt, list, hbox_btns, NULL);
|
||||
IupSetAttribute(vbox, "GAP", "10");
|
||||
IupSetAttribute(vbox, "MARGIN", "10x10");
|
||||
IupSetAttribute(vbox, "MARGIN", "15x15");
|
||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *dlg_sel = IupDialog(vbox);
|
||||
@@ -195,10 +204,11 @@ void select_replay_file_gui()
|
||||
printf("ERROR: Failed to create selection dialog\n");
|
||||
return;
|
||||
}
|
||||
IupSetAttribute(dlg_sel, "TITLE", "选择复盘文件");
|
||||
IupSetAttribute(dlg_sel, "TITLE", "复盘回放");
|
||||
IupSetAttribute(dlg_sel, "MINBOX", "NO");
|
||||
IupSetAttribute(dlg_sel, "MAXBOX", "NO");
|
||||
IupSetAttribute(dlg_sel, "RESIZE", "NO");
|
||||
IupSetAttribute(dlg_sel, "BGCOLOR", CLR_WINDOW_BG);
|
||||
|
||||
// 存储列表框句柄到对话框属性
|
||||
IupSetAttribute(dlg_sel, "MY_LIST", (char *)list);
|
||||
-531
@@ -1,531 +0,0 @@
|
||||
#include "gui.h"
|
||||
#include "gui_internal.h"
|
||||
#include "gui_menu.h"
|
||||
#include "globals.h"
|
||||
#include "gobang.h"
|
||||
#include "ai.h"
|
||||
#include "record.h"
|
||||
#include "network.h"
|
||||
#include <iup.h>
|
||||
#include <iupdraw.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static Ihandle *timer = NULL; // 网络轮询定时器
|
||||
|
||||
/**
|
||||
* @brief 网络事件轮询回调
|
||||
*/
|
||||
static int timer_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (gui_game_mode != 3 || game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
NetworkMessage msg;
|
||||
// 非阻塞接收消息
|
||||
if (receive_network_message(&msg, 0))
|
||||
{
|
||||
if (msg.type == MSG_MOVE)
|
||||
{
|
||||
int bx = msg.x;
|
||||
int by = msg.y;
|
||||
int pid = msg.player_id;
|
||||
|
||||
if (have_space(bx, by))
|
||||
{
|
||||
player_move(bx, by, pid);
|
||||
if (check_win(bx, by, pid))
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "对手获胜!");
|
||||
IupMessage("游戏结束", "对手获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
current_player_gui = network_state.local_player_id;
|
||||
sprintf(status_message, "轮到你落子");
|
||||
}
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
}
|
||||
else if (msg.type == MSG_DISCONNECT || msg.type == MSG_SURRENDER)
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "对手已断开连接/认输");
|
||||
IupMessage("游戏结束", "对手已退出游戏,你赢了!");
|
||||
update_ui_labels();
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_network_connected() && !game_over)
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "与服务器断开连接");
|
||||
IupMessage("错误", "网络连接已断开");
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ACTION 回调:负责重绘
|
||||
*/
|
||||
int action_cb(Ihandle *ih)
|
||||
{
|
||||
IupDrawBegin(ih);
|
||||
|
||||
int w, h;
|
||||
IupGetIntInt(ih, "DRAWSIZE", &w, &h);
|
||||
|
||||
set_draw_color(ih, 240, 217, 181); // 棋盘背景色 (木纹色近似)
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||
IupDrawRectangle(ih, 0, 0, w, h);
|
||||
|
||||
draw_board_iup(ih);
|
||||
draw_stones_iup(ih);
|
||||
|
||||
IupDrawEnd(ih);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 悔棋按钮回调
|
||||
*/
|
||||
int btn_undo_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
int steps_to_undo = 1;
|
||||
if (gui_game_mode == 1) // PvE
|
||||
{
|
||||
steps_to_undo = 2; // 悔棋两步(玩家+AI)
|
||||
}
|
||||
else if (gui_game_mode == 3) // Network
|
||||
{
|
||||
IupMessage("提示", "网络模式暂不支持悔棋");
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
if (step_count >= steps_to_undo)
|
||||
{
|
||||
return_move(steps_to_undo);
|
||||
|
||||
// 更新当前玩家
|
||||
if (step_count % 2 == 0)
|
||||
current_player_gui = PLAYER;
|
||||
else
|
||||
current_player_gui = AI; // or PLAYER2
|
||||
|
||||
sprintf(status_message, "已悔棋");
|
||||
update_ui_labels();
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "无法悔棋");
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存按钮回调
|
||||
*/
|
||||
int btn_save_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
|
||||
Ihandle *file_dlg = IupFileDlg();
|
||||
IupSetAttribute(file_dlg, "DIALOGTYPE", "SAVE");
|
||||
IupSetAttribute(file_dlg, "TITLE", "保存游戏记录");
|
||||
IupSetAttribute(file_dlg, "FILTER", "*.csv");
|
||||
IupSetAttribute(file_dlg, "FILTERINFO", "CSV Files");
|
||||
|
||||
IupPopup(file_dlg, IUP_CENTER, IUP_CENTER);
|
||||
|
||||
if (IupGetInt(file_dlg, "STATUS") != -1)
|
||||
{
|
||||
char *filename = IupGetAttribute(file_dlg, "VALUE");
|
||||
|
||||
char *base_name = strrchr(filename, '\\');
|
||||
if (!base_name)
|
||||
base_name = strrchr(filename, '/');
|
||||
if (base_name)
|
||||
base_name++;
|
||||
else
|
||||
base_name = filename;
|
||||
|
||||
int mode = (gui_game_mode == 0) ? GAME_MODE_PVP : GAME_MODE_AI;
|
||||
if (save_game_to_file(base_name, mode) == 0)
|
||||
{
|
||||
sprintf(status_message, "保存成功: %s", base_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "保存失败");
|
||||
}
|
||||
update_ui_labels();
|
||||
}
|
||||
|
||||
IupDestroy(file_dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 返回菜单回调
|
||||
*/
|
||||
int btn_back_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
printf("DEBUG: Back to Menu clicked\n");
|
||||
|
||||
// 如果是网络模式,断开连接
|
||||
if (gui_game_mode == 3)
|
||||
{
|
||||
disconnect_network();
|
||||
if (timer)
|
||||
{
|
||||
IupSetAttribute(timer, "RUN", "NO");
|
||||
IupDestroy(timer);
|
||||
timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 先显示主菜单
|
||||
show_main_menu();
|
||||
printf("DEBUG: Main menu shown\n");
|
||||
|
||||
// 2. 销毁游戏窗口
|
||||
if (dlg)
|
||||
{
|
||||
Ihandle *old_dlg = dlg;
|
||||
dlg = NULL; // 先清除全局指针
|
||||
IupDestroy(old_dlg);
|
||||
printf("DEBUG: Destroyed game window\n");
|
||||
}
|
||||
|
||||
return IUP_IGNORE; // 返回 IUP_IGNORE 以阻止默认处理
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 鼠标点击回调
|
||||
*/
|
||||
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
{
|
||||
(void)status; // 未使用
|
||||
if (gui_game_mode == 2)
|
||||
return IUP_DEFAULT; // 复盘模式禁用点击
|
||||
|
||||
if (button == IUP_BUTTON1 && pressed)
|
||||
{ // 左键按下
|
||||
if (game_over)
|
||||
return IUP_DEFAULT;
|
||||
|
||||
if (gui_game_mode == 3 && current_player_gui != network_state.local_player_id)
|
||||
{
|
||||
return IUP_DEFAULT; // 网络模式下,非自己回合不可落子
|
||||
}
|
||||
|
||||
int board_x, board_y;
|
||||
if (screen_to_board(x, 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 (gui_game_mode == 3)
|
||||
send_move(board_x, board_y, current_player_gui); // 发送最后一步
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "黑子获胜!");
|
||||
IupMessage("游戏结束", "黑子获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "白子获胜!");
|
||||
IupMessage("游戏结束", "白子获胜!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gui_game_mode == 0) // PvP
|
||||
{
|
||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER; // AI 在这里表示玩家2
|
||||
if (current_player_gui == PLAYER)
|
||||
sprintf(status_message, "轮到黑子");
|
||||
else
|
||||
sprintf(status_message, "轮到白子");
|
||||
}
|
||||
else if (gui_game_mode == 3) // Network
|
||||
{
|
||||
send_move(board_x, board_y, current_player_gui);
|
||||
current_player_gui = network_state.remote_player_id;
|
||||
sprintf(status_message, "等待对手落子...");
|
||||
}
|
||||
else // PvE
|
||||
{
|
||||
current_player_gui = AI;
|
||||
sprintf(status_message, "AI思考中...");
|
||||
update_ui_labels();
|
||||
IupUpdate(ih); // 立即更新显示
|
||||
IupFlush(); // 强制刷新事件队列
|
||||
|
||||
// AI 回合
|
||||
ai_move(ai_difficulty);
|
||||
|
||||
Step last_step = steps[step_count - 1];
|
||||
if (check_win(last_step.x, last_step.y, AI))
|
||||
{
|
||||
game_over = 1;
|
||||
sprintf(status_message, "AI获胜!");
|
||||
IupMessage("游戏结束", "AI获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
current_player_gui = PLAYER;
|
||||
sprintf(status_message, "轮到玩家");
|
||||
}
|
||||
}
|
||||
}
|
||||
update_ui_labels();
|
||||
IupUpdate(ih); // 请求重绘
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "无效位置!");
|
||||
update_ui_labels();
|
||||
}
|
||||
}
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 键盘回调
|
||||
*/
|
||||
int k_any_cb(Ihandle *ih, int c)
|
||||
{
|
||||
(void)ih;
|
||||
if (c == K_ESC)
|
||||
{
|
||||
if (dlg && IupGetInt(dlg, "VISIBLE"))
|
||||
{
|
||||
btn_back_cb(ih); // 调用返回菜单逻辑
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建游戏窗口
|
||||
*/
|
||||
void create_game_window()
|
||||
{
|
||||
printf("DEBUG: create_game_window start\n");
|
||||
|
||||
if (dlg)
|
||||
{
|
||||
IupDestroy(dlg);
|
||||
dlg = NULL;
|
||||
}
|
||||
|
||||
// 创建Canvas (棋盘)
|
||||
board_canvas = IupCanvas(NULL);
|
||||
if (!board_canvas)
|
||||
printf("ERROR: Failed to create board_canvas\n");
|
||||
|
||||
IupSetAttribute(board_canvas, "ACTION", "action_cb");
|
||||
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
|
||||
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
|
||||
IupSetCallback(board_canvas, "K_ANY", (Icallback)k_any_cb);
|
||||
|
||||
// 计算棋盘像素大小
|
||||
int board_pixel_size = BOARD_SIZE * CELL_SIZE + BOARD_OFFSET_X * 2;
|
||||
char size[32];
|
||||
sprintf(size, "%dx%d", board_pixel_size, board_pixel_size);
|
||||
IupSetAttribute(board_canvas, "RASTERSIZE", size);
|
||||
IupSetAttribute(board_canvas, "EXPAND", "NO");
|
||||
|
||||
// 创建标签 (玩家信息和游戏状态)
|
||||
lbl_player = IupLabel("当前玩家: 黑子");
|
||||
lbl_status = IupLabel("准备开始");
|
||||
|
||||
Ihandle *vbox_controls;
|
||||
|
||||
if (gui_game_mode == 2) // 复盘模式
|
||||
{
|
||||
Ihandle *btn_prev = IupButton("上一步 (Prev)", NULL);
|
||||
IupSetCallback(btn_prev, "ACTION", (Icallback)btn_replay_prev_cb);
|
||||
IupSetAttribute(btn_prev, "SIZE", "100x30");
|
||||
|
||||
Ihandle *btn_next = IupButton("下一步 (Next)", NULL);
|
||||
IupSetCallback(btn_next, "ACTION", (Icallback)btn_replay_next_cb);
|
||||
IupSetAttribute(btn_next, "SIZE", "100x30");
|
||||
|
||||
Ihandle *btn_back = IupButton("返回菜单", NULL);
|
||||
IupSetCallback(btn_back, "ACTION", (Icallback)btn_back_cb);
|
||||
IupSetAttribute(btn_back, "SIZE", "100x30");
|
||||
|
||||
vbox_controls = IupVbox(
|
||||
lbl_player,
|
||||
lbl_status,
|
||||
IupLabel(NULL), // Spacer
|
||||
btn_prev,
|
||||
btn_next,
|
||||
IupLabel(NULL), // Spacer
|
||||
btn_back,
|
||||
NULL);
|
||||
}
|
||||
else // 游戏模式 (PvP / PvE)
|
||||
{
|
||||
Ihandle *btn_undo = IupButton("悔棋 (Undo)", NULL);
|
||||
IupSetCallback(btn_undo, "ACTION", (Icallback)btn_undo_cb);
|
||||
IupSetAttribute(btn_undo, "SIZE", "100x30");
|
||||
|
||||
Ihandle *btn_save = IupButton("保存 (Save)", NULL);
|
||||
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_cb);
|
||||
IupSetAttribute(btn_save, "SIZE", "100x30");
|
||||
|
||||
Ihandle *btn_back = IupButton("返回菜单", NULL);
|
||||
IupSetCallback(btn_back, "ACTION", (Icallback)btn_back_cb);
|
||||
IupSetAttribute(btn_back, "SIZE", "100x30");
|
||||
|
||||
vbox_controls = IupVbox(
|
||||
lbl_player,
|
||||
lbl_status,
|
||||
IupLabel(NULL), // Spacer
|
||||
btn_undo,
|
||||
btn_save,
|
||||
IupLabel(NULL), // Spacer
|
||||
btn_back,
|
||||
NULL);
|
||||
}
|
||||
|
||||
IupSetAttribute(vbox_controls, "GAP", "15");
|
||||
IupSetAttribute(vbox_controls, "MARGIN", "10x10");
|
||||
IupSetAttribute(vbox_controls, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *hbox_main = IupHbox(board_canvas, vbox_controls, NULL);
|
||||
IupSetAttribute(hbox_main, "MARGIN", "10x10");
|
||||
IupSetAttribute(hbox_main, "GAP", "10");
|
||||
|
||||
// 创建Dialog
|
||||
dlg = IupDialog(hbox_main);
|
||||
if (!dlg)
|
||||
printf("ERROR: Failed to create dialog\n");
|
||||
|
||||
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
|
||||
// 设置 CLOSE_CB 回调,确保点击X也能正确返回菜单
|
||||
IupSetCallback(dlg, "CLOSE_CB", (Icallback)btn_back_cb);
|
||||
|
||||
printf("DEBUG: create_game_window end\n");
|
||||
}
|
||||
|
||||
void start_pvp_game_gui()
|
||||
{
|
||||
gui_game_mode = 0;
|
||||
empty_board();
|
||||
current_player_gui = PLAYER;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
sprintf(status_message, "玩家对战模式 - 黑方先行");
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
void start_pve_game_gui()
|
||||
{
|
||||
printf("DEBUG: start_pve_game_gui start\n");
|
||||
gui_game_mode = 1;
|
||||
// ai_difficulty 是全局变量
|
||||
empty_board();
|
||||
current_player_gui = PLAYER;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
printf("DEBUG: create_game_window returned\n");
|
||||
|
||||
if (dlg)
|
||||
{
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
printf("DEBUG: IupShowXY called\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: dlg is NULL in start_pve_game_gui\n");
|
||||
return;
|
||||
}
|
||||
|
||||
sprintf(status_message, "人机对战模式 - 玩家执黑先行");
|
||||
update_ui_labels();
|
||||
printf("DEBUG: update_ui_labels returned\n");
|
||||
|
||||
// 强制初始重绘
|
||||
if (board_canvas)
|
||||
{
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
printf("DEBUG: start_pve_game_gui end\n");
|
||||
}
|
||||
|
||||
void start_network_game_gui()
|
||||
{
|
||||
printf("DEBUG: start_network_game_gui start\n");
|
||||
gui_game_mode = 3;
|
||||
empty_board();
|
||||
|
||||
// 主机执黑先行
|
||||
current_player_gui = PLAYER1;
|
||||
game_over = 0;
|
||||
|
||||
create_game_window();
|
||||
printf("DEBUG: create_game_window returned\n");
|
||||
|
||||
if (dlg)
|
||||
{
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
printf("DEBUG: IupShowXY called\n");
|
||||
}
|
||||
|
||||
if (network_state.is_server)
|
||||
sprintf(status_message, "局域网联机 - 你是主机(黑子),轮到你落子");
|
||||
else
|
||||
sprintf(status_message, "局域网联机 - 你是客机(白子),等待对手落子...");
|
||||
|
||||
update_ui_labels();
|
||||
|
||||
// 强制初始重绘
|
||||
if (board_canvas)
|
||||
{
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
|
||||
// 启动网络轮询定时器
|
||||
timer = IupTimer();
|
||||
IupSetCallback(timer, "ACTION_CB", (Icallback)timer_cb);
|
||||
IupSetAttribute(timer, "TIME", "50"); // 50ms 轮询一次
|
||||
IupSetAttribute(timer, "RUN", "YES");
|
||||
|
||||
printf("DEBUG: start_network_game_gui end\n");
|
||||
}
|
||||
-394
@@ -1,394 +0,0 @@
|
||||
#include <iup.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "gui_menu.h"
|
||||
#include "gui.h"
|
||||
#include "gui_internal.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include "network.h"
|
||||
|
||||
Ihandle *menu_dlg = NULL;
|
||||
|
||||
static int btn_pvp_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
printf("DEBUG: Starting PvP Game\n");
|
||||
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
|
||||
start_pvp_game_gui();
|
||||
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_pve_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
printf("DEBUG: Starting PvE Game\n");
|
||||
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
|
||||
start_pve_game_gui();
|
||||
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// --- 网络对战相关回调 ---
|
||||
static int btn_network_host_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_port = IupGetDialogChild(dlg, "NET_PORT");
|
||||
int port = IupGetInt(txt_port, "VALUE");
|
||||
if (port <= 0 || port > 65535) port = DEFAULT_PORT;
|
||||
|
||||
if (create_server(port))
|
||||
{
|
||||
IupMessage("成功", "房间创建成功,等待玩家加入...");
|
||||
IupHide(dlg);
|
||||
start_network_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "创建房间失败,可能是端口被占用");
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_join_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_ip = IupGetDialogChild(dlg, "NET_IP");
|
||||
Ihandle *txt_port = IupGetDialogChild(dlg, "NET_PORT");
|
||||
|
||||
char *ip = IupGetAttribute(txt_ip, "VALUE");
|
||||
int port = IupGetInt(txt_port, "VALUE");
|
||||
if (port <= 0 || port > 65535) port = DEFAULT_PORT;
|
||||
|
||||
if (connect_to_server(ip, port))
|
||||
{
|
||||
IupMessage("成功", "成功加入房间!");
|
||||
IupHide(dlg);
|
||||
start_network_game_gui();
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "加入房间失败,请检查IP和端口");
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_cancel_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_network_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
printf("DEBUG: Opening Network Menu\n");
|
||||
|
||||
Ihandle *txt_ip = IupText(NULL);
|
||||
IupSetAttribute(txt_ip, "NAME", "NET_IP");
|
||||
IupSetAttribute(txt_ip, "VALUE", "127.0.0.1");
|
||||
IupSetAttribute(txt_ip, "SIZE", "100x");
|
||||
|
||||
Ihandle *txt_port = IupText(NULL);
|
||||
IupSetAttribute(txt_port, "NAME", "NET_PORT");
|
||||
char port_str[16];
|
||||
sprintf(port_str, "%d", DEFAULT_PORT);
|
||||
IupSetAttribute(txt_port, "VALUE", port_str);
|
||||
IupSetAttribute(txt_port, "SIZE", "50x");
|
||||
|
||||
Ihandle *btn_host = IupButton("创建房间", NULL);
|
||||
IupSetCallback(btn_host, "ACTION", (Icallback)btn_network_host_cb);
|
||||
|
||||
Ihandle *btn_join = IupButton("加入房间", NULL);
|
||||
IupSetCallback(btn_join, "ACTION", (Icallback)btn_network_join_cb);
|
||||
|
||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_network_cancel_cb);
|
||||
|
||||
Ihandle *vbox = IupVbox(
|
||||
IupHbox(IupLabel("目标 IP: "), txt_ip, NULL),
|
||||
IupHbox(IupLabel("端口: "), txt_port, NULL),
|
||||
IupLabel(""),
|
||||
IupHbox(btn_host, btn_join, btn_cancel, NULL),
|
||||
NULL
|
||||
);
|
||||
IupSetAttribute(vbox, "MARGIN", "20x20");
|
||||
IupSetAttribute(vbox, "GAP", "10");
|
||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *dlg = IupDialog(vbox);
|
||||
IupSetAttribute(dlg, "TITLE", "局域网联机");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
|
||||
IupPopup(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupDestroy(dlg);
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
// --- 网络对战结束 ---
|
||||
|
||||
static int btn_replay_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
printf("DEBUG: Starting Replay Mode\n");
|
||||
// hide_main_menu(); // Don't hide main menu yet, wait for file selection
|
||||
start_replay_gui();
|
||||
IupHide(menu_dlg); // Hide main menu
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_save_settings_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
|
||||
// Get values
|
||||
Ihandle *txt_board_size = IupGetDialogChild(dlg, "BOARD_SIZE");
|
||||
Ihandle *tgl_forbidden = IupGetDialogChild(dlg, "FORBIDDEN");
|
||||
Ihandle *tgl_timer = IupGetDialogChild(dlg, "TIMER");
|
||||
Ihandle *txt_time_limit = IupGetDialogChild(dlg, "TIME_LIMIT");
|
||||
Ihandle *lst_ai = IupGetDialogChild(dlg, "AI_DIFFICULTY");
|
||||
|
||||
// Update globals
|
||||
int new_size = IupGetInt(txt_board_size, "VALUE");
|
||||
if (new_size < MIN_BOARD_SIZE)
|
||||
new_size = MIN_BOARD_SIZE;
|
||||
if (new_size > MAX_BOARD_SIZE)
|
||||
new_size = MAX_BOARD_SIZE;
|
||||
BOARD_SIZE = new_size;
|
||||
|
||||
use_forbidden_moves = IupGetInt(tgl_forbidden, "VALUE");
|
||||
|
||||
use_timer = IupGetInt(tgl_timer, "VALUE");
|
||||
if (use_timer)
|
||||
{
|
||||
int minutes = IupGetInt(txt_time_limit, "VALUE");
|
||||
if (minutes < 1)
|
||||
minutes = 1;
|
||||
time_limit = minutes * 60;
|
||||
}
|
||||
|
||||
int ai_level = IupGetInt(lst_ai, "VALUE");
|
||||
if (ai_level < 1)
|
||||
ai_level = 1;
|
||||
if (ai_level > 5)
|
||||
ai_level = 5;
|
||||
ai_difficulty = ai_level;
|
||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
||||
|
||||
// Save config
|
||||
save_game_config();
|
||||
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_cancel_settings_cb(Ihandle *ih)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
IupHide(dlg);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int tgl_timer_cb(Ihandle *ih, int state)
|
||||
{
|
||||
Ihandle *dlg = IupGetDialog(ih);
|
||||
Ihandle *txt_time_limit = IupGetDialogChild(dlg, "TIME_LIMIT");
|
||||
IupSetAttribute(txt_time_limit, "ACTIVE", state ? "YES" : "NO");
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_settings_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
|
||||
// 1. Board Size
|
||||
Ihandle *lbl_board_size = IupLabel("棋盘大小 (5-25):");
|
||||
Ihandle *txt_board_size = IupText(NULL);
|
||||
IupSetAttribute(txt_board_size, "NAME", "BOARD_SIZE");
|
||||
IupSetAttribute(txt_board_size, "SPIN", "YES");
|
||||
IupSetAttribute(txt_board_size, "SPINMIN", "5");
|
||||
IupSetAttribute(txt_board_size, "SPINMAX", "25");
|
||||
IupSetInt(txt_board_size, "VALUE", BOARD_SIZE);
|
||||
IupSetAttribute(txt_board_size, "SIZE", "50x");
|
||||
|
||||
// 2. Forbidden Moves
|
||||
Ihandle *tgl_forbidden = IupToggle("启用禁手规则", NULL);
|
||||
IupSetAttribute(tgl_forbidden, "NAME", "FORBIDDEN");
|
||||
IupSetInt(tgl_forbidden, "VALUE", use_forbidden_moves);
|
||||
|
||||
// 3. Timer
|
||||
Ihandle *tgl_timer = IupToggle("启用计时器", NULL);
|
||||
IupSetAttribute(tgl_timer, "NAME", "TIMER");
|
||||
IupSetInt(tgl_timer, "VALUE", use_timer);
|
||||
IupSetCallback(tgl_timer, "ACTION", (Icallback)tgl_timer_cb);
|
||||
|
||||
// 4. Time Limit
|
||||
Ihandle *lbl_time_limit = IupLabel("时间限制 (分钟):");
|
||||
Ihandle *txt_time_limit = IupText(NULL);
|
||||
IupSetAttribute(txt_time_limit, "NAME", "TIME_LIMIT");
|
||||
IupSetAttribute(txt_time_limit, "SPIN", "YES");
|
||||
IupSetAttribute(txt_time_limit, "SPINMIN", "1");
|
||||
IupSetAttribute(txt_time_limit, "SPINMAX", "60");
|
||||
IupSetInt(txt_time_limit, "VALUE", time_limit / 60);
|
||||
IupSetAttribute(txt_time_limit, "ACTIVE", use_timer ? "YES" : "NO");
|
||||
IupSetAttribute(txt_time_limit, "SIZE", "50x");
|
||||
|
||||
// 5. AI Difficulty
|
||||
Ihandle *lbl_ai = IupLabel("AI 难度 (1-5):");
|
||||
Ihandle *lst_ai = IupList(NULL);
|
||||
IupSetAttribute(lst_ai, "NAME", "AI_DIFFICULTY");
|
||||
IupSetAttribute(lst_ai, "DROPDOWN", "YES");
|
||||
IupSetAttribute(lst_ai, "1", "1 (简单)");
|
||||
IupSetAttribute(lst_ai, "2", "2 (普通)");
|
||||
IupSetAttribute(lst_ai, "3", "3 (中等)");
|
||||
IupSetAttribute(lst_ai, "4", "4 (困难)");
|
||||
IupSetAttribute(lst_ai, "5", "5 (专家)");
|
||||
IupSetInt(lst_ai, "VALUE", ai_difficulty);
|
||||
IupSetAttribute(lst_ai, "SIZE", "80x");
|
||||
|
||||
// Buttons
|
||||
Ihandle *btn_save = IupButton("保存", NULL);
|
||||
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_settings_cb);
|
||||
IupSetAttribute(btn_save, "SIZE", "60x");
|
||||
|
||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_cancel_settings_cb);
|
||||
IupSetAttribute(btn_cancel, "SIZE", "60x");
|
||||
|
||||
// Layout
|
||||
Ihandle *hbox_board = IupHbox(lbl_board_size, txt_board_size, NULL);
|
||||
IupSetAttribute(hbox_board, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_board, "GAP", "10");
|
||||
|
||||
Ihandle *hbox_time = IupHbox(lbl_time_limit, txt_time_limit, NULL);
|
||||
IupSetAttribute(hbox_time, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_time, "GAP", "10");
|
||||
|
||||
Ihandle *hbox_ai = IupHbox(lbl_ai, lst_ai, NULL);
|
||||
IupSetAttribute(hbox_ai, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(hbox_ai, "GAP", "10");
|
||||
|
||||
Ihandle *hbox_btns = IupHbox(btn_save, btn_cancel, NULL);
|
||||
IupSetAttribute(hbox_btns, "GAP", "20");
|
||||
IupSetAttribute(hbox_btns, "MARGIN", "10x0");
|
||||
IupSetAttribute(hbox_btns, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *vbox = IupVbox(
|
||||
hbox_board,
|
||||
tgl_forbidden,
|
||||
tgl_timer,
|
||||
hbox_time,
|
||||
hbox_ai,
|
||||
IupLabel(NULL), // Spacer
|
||||
hbox_btns,
|
||||
NULL);
|
||||
|
||||
IupSetAttribute(vbox, "GAP", "15");
|
||||
IupSetAttribute(vbox, "MARGIN", "30x30");
|
||||
|
||||
Ihandle *dlg = IupDialog(vbox);
|
||||
IupSetAttribute(dlg, "TITLE", "游戏设置");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
IupSetAttribute(dlg, "MINBOX", "NO");
|
||||
IupSetAttribute(dlg, "MAXBOX", "NO");
|
||||
|
||||
IupPopup(dlg, IUP_CENTER, IUP_CENTER);
|
||||
IupDestroy(dlg);
|
||||
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_exit_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
cleanup_gui(); // 清理GUI资源
|
||||
exit(0);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
void create_main_menu()
|
||||
{
|
||||
if (menu_dlg)
|
||||
return;
|
||||
printf("DEBUG: create_main_menu\n");
|
||||
|
||||
Ihandle *lbl_title = IupLabel("五子棋 (Gobang)");
|
||||
IupSetAttribute(lbl_title, "FONT", "SimHei, 24");
|
||||
IupSetAttribute(lbl_title, "ALIGNMENT", "ACENTER");
|
||||
|
||||
Ihandle *btn_pvp = IupButton("玩家对战 (PvP)", NULL);
|
||||
IupSetCallback(btn_pvp, "ACTION", (Icallback)btn_pvp_cb);
|
||||
IupSetAttribute(btn_pvp, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_pvp, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_pve = IupButton("人机对战 (PvE)", NULL);
|
||||
IupSetCallback(btn_pve, "ACTION", (Icallback)btn_pve_cb);
|
||||
IupSetAttribute(btn_pve, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_pve, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_net = IupButton("局域网联机", NULL);
|
||||
IupSetCallback(btn_net, "ACTION", (Icallback)btn_network_cb);
|
||||
IupSetAttribute(btn_net, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_net, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_replay = IupButton("复盘模式", NULL);
|
||||
IupSetCallback(btn_replay, "ACTION", (Icallback)btn_replay_cb);
|
||||
IupSetAttribute(btn_replay, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_replay, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_settings = IupButton("设置", NULL);
|
||||
IupSetCallback(btn_settings, "ACTION", (Icallback)btn_settings_cb);
|
||||
IupSetAttribute(btn_settings, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_settings, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *btn_exit = IupButton("退出", NULL);
|
||||
IupSetCallback(btn_exit, "ACTION", (Icallback)btn_exit_cb);
|
||||
IupSetAttribute(btn_exit, "SIZE", "120x30");
|
||||
IupSetAttribute(btn_exit, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *vbox = IupVbox(
|
||||
lbl_title,
|
||||
btn_pvp,
|
||||
btn_pve,
|
||||
btn_net,
|
||||
btn_replay,
|
||||
btn_settings,
|
||||
btn_exit,
|
||||
NULL);
|
||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||
IupSetAttribute(vbox, "GAP", "15");
|
||||
IupSetAttribute(vbox, "MARGIN", "40x40");
|
||||
|
||||
menu_dlg = IupDialog(vbox);
|
||||
IupSetAttribute(menu_dlg, "TITLE", "五子棋 - 主菜单");
|
||||
IupSetAttribute(menu_dlg, "RESIZE", "NO");
|
||||
IupSetAttribute(menu_dlg, "MINBOX", "NO");
|
||||
IupSetAttribute(menu_dlg, "MAXBOX", "NO");
|
||||
|
||||
// 设置对话框关闭回调 (点X关闭程序)
|
||||
IupSetCallback(menu_dlg, "CLOSE_CB", (Icallback)btn_exit_cb);
|
||||
IupMap(menu_dlg); // Map immediately
|
||||
}
|
||||
|
||||
void show_main_menu()
|
||||
{
|
||||
printf("DEBUG: show_main_menu start\n");
|
||||
if (!menu_dlg)
|
||||
{
|
||||
printf("DEBUG: Creating main menu\n");
|
||||
create_main_menu();
|
||||
}
|
||||
IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER);
|
||||
printf("DEBUG: show_main_menu end\n");
|
||||
}
|
||||
|
||||
void hide_main_menu()
|
||||
{
|
||||
if (menu_dlg)
|
||||
IupHide(menu_dlg);
|
||||
}
|
||||
@@ -0,0 +1,616 @@
|
||||
/**
|
||||
* @file llm_ai.c
|
||||
* @brief 大模型AI模块实现
|
||||
* @note 通过OpenAI兼容API调用大模型进行五子棋对弈
|
||||
* 支持 MiniMax、DeepSeek、GPT 等兼容接口
|
||||
*/
|
||||
|
||||
#include "llm_ai.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include "gobang.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#include <process.h>
|
||||
#endif
|
||||
|
||||
// ==================== 内部函数声明 ====================
|
||||
|
||||
static bool http_post_json(const char *url, const char *api_key,
|
||||
const char *json_body, char *response, int response_size);
|
||||
static char *build_prompt(void);
|
||||
static char *build_request_json(const char *prompt);
|
||||
static bool parse_response(const char *response, int *out_x, int *out_y);
|
||||
static bool extract_coords(const char *text, int *out_x, int *out_y);
|
||||
|
||||
// ==================== 异步请求支持 ====================
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
typedef struct {
|
||||
int result; // 0=思考中, 1=成功, -1=失败
|
||||
int x, y; // 成功时的坐标
|
||||
HANDLE thread; // 后台线程句柄
|
||||
} LLMAsyncResult;
|
||||
|
||||
static LLMAsyncResult g_llm_async = {0, 0, 0, NULL};
|
||||
|
||||
static unsigned __stdcall llm_thread_func(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
int x = -1, y = -1;
|
||||
bool ok = llm_ai_move(&x, &y);
|
||||
g_llm_async.x = x;
|
||||
g_llm_async.y = y;
|
||||
g_llm_async.result = ok ? 1 : -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void llm_ai_start_move(void)
|
||||
{
|
||||
// 等待上一次线程结束(如果有)
|
||||
if (g_llm_async.thread)
|
||||
{
|
||||
WaitForSingleObject(g_llm_async.thread, INFINITE);
|
||||
CloseHandle(g_llm_async.thread);
|
||||
g_llm_async.thread = NULL;
|
||||
}
|
||||
|
||||
g_llm_async.result = 0;
|
||||
g_llm_async.x = -1;
|
||||
g_llm_async.y = -1;
|
||||
|
||||
g_llm_async.thread = (HANDLE)_beginthreadex(NULL, 0, llm_thread_func, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
int llm_ai_poll_result(int *out_x, int *out_y)
|
||||
{
|
||||
if (g_llm_async.thread == NULL)
|
||||
return -1;
|
||||
|
||||
// 检查线程是否完成
|
||||
DWORD wait = WaitForSingleObject(g_llm_async.thread, 0);
|
||||
if (wait == WAIT_OBJECT_0)
|
||||
{
|
||||
// 线程已完成
|
||||
CloseHandle(g_llm_async.thread);
|
||||
g_llm_async.thread = NULL;
|
||||
*out_x = g_llm_async.x;
|
||||
*out_y = g_llm_async.y;
|
||||
return g_llm_async.result;
|
||||
}
|
||||
|
||||
return 0; // 仍在思考
|
||||
}
|
||||
|
||||
#else
|
||||
// 非Windows平台的同步回退
|
||||
void llm_ai_start_move(void) {}
|
||||
|
||||
int llm_ai_poll_result(int *out_x, int *out_y)
|
||||
{
|
||||
(void)out_x;
|
||||
(void)out_y;
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ==================== 公共接口 ====================
|
||||
|
||||
bool llm_ai_move(int *out_x, int *out_y)
|
||||
{
|
||||
if (llm_api_key[0] == '\0')
|
||||
{
|
||||
printf("[LLM] 错误:未配置API Key\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int retry = 0; retry < LLM_MAX_RETRIES; retry++)
|
||||
{
|
||||
// 1. 构造 prompt
|
||||
char *prompt = build_prompt();
|
||||
if (!prompt)
|
||||
return false;
|
||||
|
||||
// 2. 构造 JSON 请求体
|
||||
char *json_body = build_request_json(prompt);
|
||||
free(prompt);
|
||||
if (!json_body)
|
||||
return false;
|
||||
|
||||
// 3. 发送 HTTP 请求
|
||||
char response[8192] = {0};
|
||||
bool ok = http_post_json(llm_endpoint, llm_api_key, json_body, response, sizeof(response));
|
||||
free(json_body);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
printf("[LLM] HTTP请求失败 (第%d次)\n", retry + 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. 解析响应
|
||||
if (parse_response(response, out_x, out_y))
|
||||
{
|
||||
// 5. 验证坐标合法性
|
||||
if (*out_x >= 0 && *out_x < BOARD_SIZE &&
|
||||
*out_y >= 0 && *out_y < BOARD_SIZE &&
|
||||
board[*out_x][*out_y] == EMPTY)
|
||||
{
|
||||
printf("[LLM] 落子(%d, %d)\n", *out_x, *out_y);
|
||||
return true;
|
||||
}
|
||||
printf("[LLM] 坐标(%d, %d)非法,重试 (%d/%d)\n", *out_x, *out_y, retry + 1, LLM_MAX_RETRIES);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("[LLM] 解析响应失败,重试 (%d/%d)\n", retry + 1, LLM_MAX_RETRIES);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==================== Prompt 构造 ====================
|
||||
|
||||
// 检查坐标是否在棋盘范围内
|
||||
static bool in_board(int x, int y)
|
||||
{
|
||||
return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE;
|
||||
}
|
||||
|
||||
static char *build_prompt(void)
|
||||
{
|
||||
// 估算所需空间
|
||||
int max_len = 3072 + step_count * 20 + 512;
|
||||
char *buf = (char *)malloc(max_len);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
int pos = 0;
|
||||
|
||||
// 棋盘基本信息(不用()格式,避免被坐标提取误匹配)
|
||||
pos += snprintf(buf + pos, max_len - pos,
|
||||
"棋盘 %d×%d,坐标范围 0-%d\n"
|
||||
"你=白O,对手=黑X\n\n",
|
||||
BOARD_SIZE, BOARD_SIZE, BOARD_SIZE - 1);
|
||||
|
||||
// 黑子位置(用方括号格式,不用圆括号)
|
||||
pos += snprintf(buf + pos, max_len - pos, "黑子X位置:");
|
||||
int black_count = 0;
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
|
||||
{
|
||||
pos += snprintf(buf + pos, max_len - pos, " [%d,%d]", i, j);
|
||||
black_count++;
|
||||
}
|
||||
if (black_count == 0)
|
||||
pos += snprintf(buf + pos, max_len - pos, " 无");
|
||||
pos += snprintf(buf + pos, max_len - pos, "\n");
|
||||
|
||||
// 白子位置
|
||||
pos += snprintf(buf + pos, max_len - pos, "白子O位置:");
|
||||
int white_count = 0;
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
if (board[i][j] == AI || board[i][j] == PLAYER2)
|
||||
{
|
||||
pos += snprintf(buf + pos, max_len - pos, " [%d,%d]", i, j);
|
||||
white_count++;
|
||||
}
|
||||
if (white_count == 0)
|
||||
pos += snprintf(buf + pos, max_len - pos, " 无");
|
||||
pos += snprintf(buf + pos, max_len - pos, "\n");
|
||||
|
||||
// 最近走法
|
||||
if (step_count > 0)
|
||||
{
|
||||
int show_count = step_count < 6 ? step_count : 6;
|
||||
pos += snprintf(buf + pos, max_len - pos, "\n最近%d步:\n", show_count);
|
||||
for (int i = step_count - show_count; i < step_count; i++)
|
||||
{
|
||||
const char *who = (steps[i].player == PLAYER || steps[i].player == PLAYER1) ? "X" : "O";
|
||||
pos += snprintf(buf + pos, max_len - pos, " %s [%d,%d]\n", who, steps[i].x, steps[i].y);
|
||||
}
|
||||
}
|
||||
|
||||
// 收集候选空位(已有棋子周围2格内的空位)
|
||||
char candidate[BOARD_SIZE][BOARD_SIZE];
|
||||
memset(candidate, 0, sizeof(candidate));
|
||||
|
||||
int total_stones = black_count + white_count;
|
||||
if (total_stones == 0)
|
||||
{
|
||||
candidate[BOARD_SIZE / 2][BOARD_SIZE / 2] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
if (board[i][j] != EMPTY)
|
||||
{
|
||||
// 标记周围2格内的空位
|
||||
for (int di = -2; di <= 2; di++)
|
||||
{
|
||||
for (int dj = -2; dj <= 2; dj++)
|
||||
{
|
||||
int ni = i + di, nj = j + dj;
|
||||
if (in_board(ni, nj) && board[ni][nj] == EMPTY)
|
||||
candidate[ni][nj] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 输出候选位置(用[]格式,避免与LLM回复的()格式冲突)
|
||||
int cand_count = 0;
|
||||
pos += snprintf(buf + pos, max_len - pos, "\n可选空位:\n");
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
if (candidate[i][j])
|
||||
{
|
||||
pos += snprintf(buf + pos, max_len - pos, "[%d,%d] ", i, j);
|
||||
cand_count++;
|
||||
if (cand_count % 10 == 0)
|
||||
pos += snprintf(buf + pos, max_len - pos, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
pos += snprintf(buf + pos, max_len - pos, "\n共%d个可选。\n", cand_count);
|
||||
|
||||
// 输出格式要求
|
||||
pos += snprintf(buf + pos, max_len - pos,
|
||||
"\n从上面选一个最佳位置,用(行,列)格式回复。只回复坐标。\n");
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
// ==================== JSON 构造 ====================
|
||||
|
||||
static char *build_request_json(const char *prompt)
|
||||
{
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
if (!root)
|
||||
return NULL;
|
||||
|
||||
cJSON_AddStringToObject(root, "model", llm_model);
|
||||
|
||||
// messages 数组
|
||||
cJSON *messages = cJSON_AddArrayToObject(root, "messages");
|
||||
|
||||
// system 消息
|
||||
cJSON *sys_msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(sys_msg, "role", "system");
|
||||
cJSON_AddStringToObject(sys_msg, "content",
|
||||
"你是五子棋AI。从给定的可选空位列表中选一个最佳位置落子。"
|
||||
"只回复(行,列)格式的坐标,不要任何解释。");
|
||||
cJSON_AddItemToArray(messages, sys_msg);
|
||||
|
||||
// user 消息(包含棋盘状态)
|
||||
cJSON *user_msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(user_msg, "role", "user");
|
||||
cJSON_AddStringToObject(user_msg, "content", prompt);
|
||||
cJSON_AddItemToArray(messages, user_msg);
|
||||
|
||||
// 其他参数
|
||||
// 推理模型需要更多token(思考+输出),非推理模型够用即可
|
||||
cJSON_AddNumberToObject(root, "temperature", 0.1);
|
||||
cJSON_AddNumberToObject(root, "max_tokens", 512);
|
||||
|
||||
char *json = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
return json;
|
||||
}
|
||||
|
||||
// ==================== HTTP 请求(WinHTTP)====================
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
static bool http_post_json(const char *url, const char *api_key,
|
||||
const char *json_body, char *response, int response_size)
|
||||
{
|
||||
bool success = false;
|
||||
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
|
||||
|
||||
// 解析 URL:提取 host、path、端口、是否 HTTPS
|
||||
WCHAR whost[256] = {0};
|
||||
WCHAR wpath[512] = {0};
|
||||
wchar_t wauth[256] = {0};
|
||||
INTERNET_PORT port = INTERNET_DEFAULT_HTTPS_PORT;
|
||||
BOOL is_https = TRUE;
|
||||
|
||||
// 简单解析 URL
|
||||
const char *host_start = url;
|
||||
const char *path_start = "/";
|
||||
int host_len = 0;
|
||||
|
||||
if (strncmp(url, "https://", 8) == 0)
|
||||
{
|
||||
host_start = url + 8;
|
||||
is_https = TRUE;
|
||||
port = INTERNET_DEFAULT_HTTPS_PORT;
|
||||
}
|
||||
else if (strncmp(url, "http://", 7) == 0)
|
||||
{
|
||||
host_start = url + 7;
|
||||
is_https = FALSE;
|
||||
port = INTERNET_DEFAULT_HTTP_PORT;
|
||||
}
|
||||
|
||||
// 找到 path 的起始位置
|
||||
const char *p = strchr(host_start, '/');
|
||||
if (p)
|
||||
{
|
||||
host_len = (int)(p - host_start);
|
||||
path_start = p;
|
||||
}
|
||||
else
|
||||
{
|
||||
host_len = (int)strlen(host_start);
|
||||
path_start = "/";
|
||||
}
|
||||
|
||||
// 检查是否有端口号
|
||||
const char *colon = strchr(host_start, ':');
|
||||
if (colon && colon < host_start + host_len)
|
||||
{
|
||||
host_len = (int)(colon - host_start);
|
||||
port = (INTERNET_PORT)atoi(colon + 1);
|
||||
}
|
||||
|
||||
// 转换为宽字符
|
||||
MultiByteToWideChar(CP_UTF8, 0, host_start, host_len, whost, 256);
|
||||
MultiByteToWideChar(CP_UTF8, 0, path_start, -1, wpath, 512);
|
||||
|
||||
// 构造 Authorization header
|
||||
wchar_t wapi_key[128] = {0};
|
||||
MultiByteToWideChar(CP_UTF8, 0, api_key, -1, wapi_key, 128);
|
||||
swprintf(wauth, 256, L"Authorization: Bearer %s", wapi_key);
|
||||
|
||||
// 打开 WinHTTP 会话
|
||||
hSession = WinHttpOpen(L"Gobang/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
|
||||
if (!hSession)
|
||||
goto cleanup;
|
||||
|
||||
// 设置超时
|
||||
WinHttpSetTimeouts(hSession, 5000, 10000, 15000, LLM_TIMEOUT_MS);
|
||||
|
||||
// 连接服务器
|
||||
hConnect = WinHttpConnect(hSession, whost, port, 0);
|
||||
if (!hConnect)
|
||||
goto cleanup;
|
||||
|
||||
// 创建请求
|
||||
hRequest = WinHttpOpenRequest(hConnect, L"POST", wpath,
|
||||
NULL, WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
is_https ? WINHTTP_FLAG_SECURE : 0);
|
||||
if (!hRequest)
|
||||
goto cleanup;
|
||||
|
||||
// 添加请求头
|
||||
WinHttpAddRequestHeaders(hRequest, wauth, -1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
WinHttpAddRequestHeaders(hRequest, L"Content-Type: application/json", -1L, WINHTTP_ADDREQ_FLAG_ADD);
|
||||
|
||||
// 发送请求
|
||||
int body_len = (int)strlen(json_body);
|
||||
if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
|
||||
(LPVOID)json_body, body_len, body_len, 0))
|
||||
goto cleanup;
|
||||
|
||||
// 接收响应
|
||||
if (!WinHttpReceiveResponse(hRequest, NULL))
|
||||
goto cleanup;
|
||||
|
||||
// 读取响应体
|
||||
{
|
||||
int total_read = 0;
|
||||
DWORD bytes_available = 0;
|
||||
DWORD bytes_read = 0;
|
||||
|
||||
while (WinHttpQueryDataAvailable(hRequest, &bytes_available) && bytes_available > 0)
|
||||
{
|
||||
if (total_read + (int)bytes_available >= response_size - 1)
|
||||
break;
|
||||
|
||||
WinHttpReadData(hRequest, response + total_read, bytes_available, &bytes_read);
|
||||
total_read += bytes_read;
|
||||
bytes_available = 0;
|
||||
}
|
||||
response[total_read] = '\0';
|
||||
success = (total_read > 0);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (hRequest)
|
||||
WinHttpCloseHandle(hRequest);
|
||||
if (hConnect)
|
||||
WinHttpCloseHandle(hConnect);
|
||||
if (hSession)
|
||||
WinHttpCloseHandle(hSession);
|
||||
return success;
|
||||
}
|
||||
|
||||
#else
|
||||
// 非 Windows 平台的空实现
|
||||
static bool http_post_json(const char *url, const char *api_key,
|
||||
const char *json_body, char *response, int response_size)
|
||||
{
|
||||
(void)url;
|
||||
(void)api_key;
|
||||
(void)json_body;
|
||||
(void)response;
|
||||
(void)response_size;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
// ==================== 响应解析 ====================
|
||||
|
||||
static bool parse_response(const char *response, int *out_x, int *out_y)
|
||||
{
|
||||
cJSON *root = cJSON_Parse(response);
|
||||
if (!root)
|
||||
{
|
||||
printf("[LLM] JSON解析失败\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// OpenAI 格式:choices[0].message.content
|
||||
cJSON *choices = cJSON_GetObjectItem(root, "choices");
|
||||
if (!choices || !cJSON_IsArray(choices))
|
||||
{
|
||||
// 兼容某些API的错误格式
|
||||
cJSON *error = cJSON_GetObjectItem(root, "error");
|
||||
if (error)
|
||||
{
|
||||
cJSON *msg = cJSON_GetObjectItem(error, "message");
|
||||
if (msg && cJSON_IsString(msg))
|
||||
printf("[LLM] API错误: %s\n", msg->valuestring);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
return false;
|
||||
}
|
||||
|
||||
cJSON *first = cJSON_GetArrayItem(choices, 0);
|
||||
if (!first)
|
||||
{
|
||||
cJSON_Delete(root);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 提取 content
|
||||
cJSON *message = cJSON_GetObjectItem(first, "message");
|
||||
cJSON *content = NULL;
|
||||
if (message)
|
||||
content = cJSON_GetObjectItem(message, "content");
|
||||
else
|
||||
content = cJSON_GetObjectItem(first, "text"); // 某些API直接返回text
|
||||
|
||||
if (!content || !cJSON_IsString(content))
|
||||
{
|
||||
cJSON_Delete(root);
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("[LLM] 模型回复: %s\n", content->valuestring);
|
||||
bool ok = extract_coords(content->valuestring, out_x, out_y);
|
||||
cJSON_Delete(root);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ==================== 坐标提取 ====================
|
||||
|
||||
// 跳过所有 <think>...</think> 块,返回处理后的文本(调用者需 free)
|
||||
static char *strip_think_tags(const char *text)
|
||||
{
|
||||
int len = (int)strlen(text);
|
||||
char *buf = (char *)malloc(len + 1);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
int out = 0;
|
||||
const char *p = text;
|
||||
|
||||
while (*p)
|
||||
{
|
||||
const char *think_start = strstr(p, "<think>");
|
||||
if (think_start == p)
|
||||
{
|
||||
// 找到 <think> 标签,跳到 </think> 之后
|
||||
const char *think_end = strstr(p, "</think>");
|
||||
if (think_end)
|
||||
{
|
||||
p = think_end + 8; // strlen("</think>")
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有闭合的 <think>,跳过剩余内容
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 复制 <think> 之前的普通文本
|
||||
int copy_len = think_start ? (int)(think_start - p) : (int)strlen(p);
|
||||
memcpy(buf + out, p, copy_len);
|
||||
out += copy_len;
|
||||
p += copy_len;
|
||||
}
|
||||
|
||||
buf[out] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
// 在文本中查找最后一个 (行,列) 坐标
|
||||
static bool find_last_coord(const char *text, int *out_x, int *out_y)
|
||||
{
|
||||
int last_x = -1, last_y = -1;
|
||||
bool found = false;
|
||||
const char *p = text;
|
||||
|
||||
while (*p)
|
||||
{
|
||||
if (*p == '(')
|
||||
{
|
||||
int x = -1, y = -1;
|
||||
if (sscanf(p, "(%d,%d)", &x, &y) == 2 ||
|
||||
sscanf(p, "(%d, %d)", &x, &y) == 2)
|
||||
{
|
||||
if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE)
|
||||
{
|
||||
last_x = x;
|
||||
last_y = y;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
p++;
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
*out_x = last_x;
|
||||
*out_y = last_y;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool extract_coords(const char *text, int *out_x, int *out_y)
|
||||
{
|
||||
// 第一步:去掉 <think>...</think>,从回复正文中提取
|
||||
char *clean = strip_think_tags(text);
|
||||
if (clean)
|
||||
{
|
||||
// 跳过空白字符
|
||||
const char *p = clean;
|
||||
while (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')
|
||||
p++;
|
||||
|
||||
if (*p && find_last_coord(p, out_x, out_y))
|
||||
{
|
||||
free(clean);
|
||||
return true;
|
||||
}
|
||||
free(clean);
|
||||
}
|
||||
|
||||
// 第二步(兜底):从完整文本(含推理)中提取最后一个坐标
|
||||
// 推理模型可能把最终答案写在 <think> 标签里
|
||||
return find_last_coord(text, out_x, out_y);
|
||||
}
|
||||
@@ -17,16 +17,22 @@
|
||||
/**
|
||||
* @brief 初始化网络模块
|
||||
*/
|
||||
static bool enet_initialized = false;
|
||||
|
||||
bool init_network()
|
||||
{
|
||||
if (!enet_initialized)
|
||||
{
|
||||
if (enet_initialize() != 0)
|
||||
{
|
||||
printf("An error occurred while initializing ENet.\n");
|
||||
return false;
|
||||
}
|
||||
enet_initialized = true;
|
||||
}
|
||||
|
||||
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||
network_state.port = DEFAULT_PORT;
|
||||
network_state.port = DEFAULT_NETWORK_PORT;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -44,8 +50,13 @@ void cleanup_network()
|
||||
network_state.host = NULL;
|
||||
}
|
||||
|
||||
if (enet_initialized)
|
||||
{
|
||||
enet_deinitialize();
|
||||
network_state.is_connected = false;
|
||||
enet_initialized = false;
|
||||
}
|
||||
|
||||
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,6 +64,9 @@ void cleanup_network()
|
||||
*/
|
||||
bool create_server(int port)
|
||||
{
|
||||
if (!init_network())
|
||||
return false;
|
||||
|
||||
ENetAddress address;
|
||||
|
||||
// 绑定所有接口
|
||||
@@ -117,6 +131,9 @@ bool create_server(int port)
|
||||
*/
|
||||
bool connect_to_server(const char *ip, int port)
|
||||
{
|
||||
if (!init_network())
|
||||
return false;
|
||||
|
||||
// 创建客户端主机
|
||||
network_state.host = (void *)enet_host_create(NULL, // 创建客户端
|
||||
1, // 仅允许1个传出连接
|
||||
@@ -286,17 +303,54 @@ bool is_network_connected()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取本机IP地址
|
||||
* @brief 获取本机局域网IP地址
|
||||
*/
|
||||
bool get_local_ip(char *ip_buffer, int buffer_size)
|
||||
{
|
||||
// ENet 没有直接获取本机局域网 IP 的简单跨平台函数。
|
||||
// 这里我们可以回退到原生 socket 方法,或者简单返回本地回环。
|
||||
// 为了不引入额外的系统头文件,暂时返回通用提示。
|
||||
// 在真实应用中,可以保留之前的 gethostname/gethostbyname 逻辑。
|
||||
strncpy(ip_buffer, "查看本机网络适配器", buffer_size - 1);
|
||||
#ifdef _WIN32
|
||||
// 使用 Winsock 获取本机 IP(ws2_32 已链接)
|
||||
char hostname[256];
|
||||
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR)
|
||||
{
|
||||
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return true; // 总是返回 true 以允许服务器继续启动
|
||||
return false;
|
||||
}
|
||||
|
||||
struct hostent *host = gethostbyname(hostname);
|
||||
if (host == NULL || host->h_addr_list[0] == NULL)
|
||||
{
|
||||
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历所有地址,找一个非回环的 IPv4 地址
|
||||
for (int i = 0; host->h_addr_list[i] != NULL; i++)
|
||||
{
|
||||
struct in_addr addr;
|
||||
memcpy(&addr, host->h_addr_list[i], sizeof(struct in_addr));
|
||||
const char *ip = inet_ntoa(addr);
|
||||
if (ip && strcmp(ip, "127.0.0.1") != 0)
|
||||
{
|
||||
strncpy(ip_buffer, ip, buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 没找到非回环地址,返回第一个
|
||||
struct in_addr addr;
|
||||
memcpy(&addr, host->h_addr_list[0], sizeof(struct in_addr));
|
||||
const char *ip = inet_ntoa(addr);
|
||||
strncpy(ip_buffer, ip ? ip : "127.0.0.1", buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return true;
|
||||
#else
|
||||
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||
ip_buffer[buffer_size - 1] = '\0';
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,29 +23,6 @@
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 复盘游戏全过程并展示评分
|
||||
* @note 实现流程:
|
||||
* 1. 初始化临时复盘棋盘
|
||||
* 2. 按步数顺序逐步重现每个落子
|
||||
* 3. 每步显示:
|
||||
* - 当前步数/总步数
|
||||
* - 落子方(玩家/AI)
|
||||
* - 落子位置(1-based坐标)
|
||||
* - 当前棋盘状态
|
||||
* 4. 通过用户按Enter键控制步骤前进
|
||||
* 5. 复盘结束后自动进入评分环节:
|
||||
* - 评估双方表现
|
||||
* - 显示得分
|
||||
* - 评选MVP
|
||||
* @note 技术细节:
|
||||
* - 使用独立临时棋盘避免影响主游戏状态
|
||||
* - 坐标显示转换为1-based方便用户理解
|
||||
* - 包含输入缓冲区清理防止意外输入
|
||||
* - 评分环节调用calculate_final_score()函数
|
||||
*/
|
||||
void calculate_game_scores();
|
||||
|
||||
/**
|
||||
* @brief 计算游戏评分
|
||||
*/
|
||||
@@ -99,7 +76,7 @@ void calculate_game_scores()
|
||||
void display_game_scores(int game_mode)
|
||||
{
|
||||
printf("\n===== 对局评分 =====\n");
|
||||
double sum_score = (long double)player1_final_score + (long double)player2_final_score;
|
||||
double sum_score = (double)player1_final_score + (double)player2_final_score;
|
||||
|
||||
if (sum_score > 0)
|
||||
{
|
||||
@@ -284,7 +261,7 @@ int load_game_from_file(const char *filename)
|
||||
FILE *file = fopen(fullpath, "r");
|
||||
if (!file)
|
||||
{
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 跳过CSV文件头部行
|
||||
@@ -379,11 +356,21 @@ int load_game_from_file(const char *filename)
|
||||
|
||||
while (fgets(buffer, sizeof(buffer), file) != NULL)
|
||||
{
|
||||
if (step_count >= MAX_STEPS)
|
||||
{
|
||||
break; // 防止数组越界
|
||||
}
|
||||
if (sscanf(buffer, "%d,%d,%d,%d", &step_num, &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 4)
|
||||
{
|
||||
// 将1-based坐标转换为0-based坐标
|
||||
steps[step_count].x--;
|
||||
steps[step_count].y--;
|
||||
// 验证坐标合法性
|
||||
if (steps[step_count].x < 0 || steps[step_count].x >= size ||
|
||||
steps[step_count].y < 0 || steps[step_count].y >= size)
|
||||
{
|
||||
continue; // 跳过非法坐标
|
||||
}
|
||||
step_count++;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user