mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-28 16:35:55 +08:00
feat: 集成大模型AI、重构构建系统并修复多项代码质量问题
- 构建系统:Makefile 迁移至 CMakeLists.txt,支持 cJSON 和 WinHTTP - 项目结构:src/ 按功能拆分为 core/、gui/、network/、record/、llm/ 子目录 - 新功能:集成大模型 AI(WinHTTP + cJSON,兼容 OpenAI 协议),支持异步请求 - 渲染修复:IupDraw* 替换为 Windows GDI,修复画布黑屏问题 - 网络修复:ENet 初始化幂等化,实现真实 get_local_ip() (Winsock) - 代码质量:删除死代码 (dfs/count_threats_in_direction),修复头文件守卫, sprintf→snprintf 防溢出,strncpy 安全终止,GDI 资源泄漏修复 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -35,6 +35,13 @@ dist/
|
|||||||
|
|
||||||
# 编译生成的对象文件
|
# 编译生成的对象文件
|
||||||
obj/
|
obj/
|
||||||
|
build/
|
||||||
|
|
||||||
# 临时游戏存档
|
# 临时游戏存档
|
||||||
records/
|
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-3.31_Win64_dllw6_lib)
|
||||||
|
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);
|
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下棋
|
* @brief AI下棋
|
||||||
*
|
*
|
||||||
@@ -71,13 +64,5 @@ bool is_near_stones(int x, int y);
|
|||||||
*/
|
*/
|
||||||
ThreatLevel detect_threat(int x, int y, int player);
|
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
|
#endif // AI_H
|
||||||
@@ -135,6 +135,17 @@
|
|||||||
#define CONFIG_FILE "gobang_config.ini" // 配置文件路径
|
#define CONFIG_FILE "gobang_config.ini" // 配置文件路径
|
||||||
#define MAX_PATH_LENGTH 256 // 最大路径长度
|
#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 加载游戏配置
|
* @brief 加载游戏配置
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ extern int network_timeout; // 网络超时时间
|
|||||||
extern double defense_coefficient; // 防守系数
|
extern double defense_coefficient; // 防守系数
|
||||||
extern int ai_difficulty; // AI难度 (1-5)
|
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; // 网络游戏状态
|
extern NetworkGameState network_state; // 网络游戏状态
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -5,8 +5,8 @@
|
|||||||
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
|
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef GO_BANG_H
|
#ifndef GOBANG_H
|
||||||
#define GO_BANG_H
|
#define GOBANG_H
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdbool.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);
|
int calculate_step_score(int x, int y, int player);
|
||||||
|
|
||||||
#endif // GO_BANG_H
|
#endif // GOBANG_H
|
||||||
@@ -8,14 +8,9 @@ extern Ihandle *dlg;
|
|||||||
extern Ihandle *board_canvas;
|
extern Ihandle *board_canvas;
|
||||||
extern Ihandle *lbl_player;
|
extern Ihandle *lbl_player;
|
||||||
extern Ihandle *lbl_status;
|
extern Ihandle *lbl_status;
|
||||||
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; // 复盘总步数
|
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 中定义)
|
// 核心功能 (在 gui_core.c 中定义)
|
||||||
void update_ui_labels();
|
void update_ui_labels();
|
||||||
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
||||||
@@ -24,7 +19,7 @@ int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
|||||||
void create_game_window();
|
void create_game_window();
|
||||||
void start_pvp_game_gui();
|
void start_pvp_game_gui();
|
||||||
void start_pve_game_gui();
|
void start_pve_game_gui();
|
||||||
void start_network_game_gui(); // 新增:启动网络对战游戏窗口
|
void start_network_game_gui();
|
||||||
int action_cb(Ihandle *ih);
|
int action_cb(Ihandle *ih);
|
||||||
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status);
|
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status);
|
||||||
int k_any_cb(Ihandle *ih, int c);
|
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 "type.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <enet/enet.h> // 引入 ENet 头文件
|
#include <enet/enet.h>
|
||||||
|
|
||||||
// 网络状态结构体在 type.h 中定义,我们需要修改 type.h 来包含 ENet 类型
|
// network_state 的 extern 声明在 globals.h 中
|
||||||
// 或者在这里重新定义(如果 type.h 中可以移除旧的定义)
|
|
||||||
|
|
||||||
// 全局网络状态
|
|
||||||
extern NetworkGameState network_state;
|
|
||||||
|
|
||||||
// 函数声明
|
// 函数声明
|
||||||
|
|
||||||
|
|||||||
+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
|
||||||
@@ -137,94 +137,6 @@ int evaluate_pos(int x, int y, int player)
|
|||||||
return total_score + position_bonus; // 返回总评估分
|
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决策主函数,使用评估函数和搜索算法选择最佳落子位置
|
* @brief AI决策主函数,使用评估函数和搜索算法选择最佳落子位置
|
||||||
* @note 采用两阶段决策逻辑:
|
* @note 采用两阶段决策逻辑:
|
||||||
@@ -542,58 +454,3 @@ ThreatLevel detect_threat(int x, int y, int player)
|
|||||||
return max_threat;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char line[256];
|
char line[512];
|
||||||
while (fgets(line, sizeof(line), file))
|
while (fgets(line, sizeof(line), file))
|
||||||
{
|
{
|
||||||
// 去除换行符
|
// 去除换行符
|
||||||
@@ -79,6 +79,25 @@ void load_game_config()
|
|||||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
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);
|
fclose(file);
|
||||||
@@ -114,6 +133,16 @@ void save_game_config()
|
|||||||
fprintf(file, "\n# AI难度 (1-5)\n");
|
fprintf(file, "\n# AI难度 (1-5)\n");
|
||||||
fprintf(file, "AI_DIFFICULTY=%d\n", ai_difficulty);
|
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);
|
fclose(file);
|
||||||
printf("配置保存完成\n");
|
printf("配置保存完成\n");
|
||||||
}
|
}
|
||||||
@@ -132,5 +161,13 @@ void reset_to_default_config()
|
|||||||
ai_difficulty = 3; // 默认AI难度
|
ai_difficulty = 3; // 默认AI难度
|
||||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
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");
|
printf("已重置为默认配置\n");
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,12 @@ int network_timeout = NETWORK_TIMEOUT_MS; // 网络超时时间
|
|||||||
double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
|
double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
|
||||||
int ai_difficulty = 3; // AI难度 (1-5)
|
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}; // 网络游戏状态
|
NetworkGameState network_state = {0}; // 网络游戏状态
|
||||||
|
|
||||||
@@ -6,6 +6,10 @@
|
|||||||
#include "ai.h"
|
#include "ai.h"
|
||||||
#include "record.h"
|
#include "record.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include "llm_ai.h"
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
#include <iup.h>
|
#include <iup.h>
|
||||||
#include <iupdraw.h>
|
#include <iupdraw.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -13,6 +17,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
static Ihandle *timer = NULL; // 网络轮询定时器
|
static Ihandle *timer = NULL; // 网络轮询定时器
|
||||||
|
static Ihandle *llm_timer = NULL; // LLM异步轮询定时器
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 网络事件轮询回调
|
* @brief 网络事件轮询回调
|
||||||
@@ -72,24 +77,173 @@ static int timer_cb(Ihandle *ih)
|
|||||||
return IUP_DEFAULT;
|
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 回调:负责重绘
|
* @brief ACTION 回调:负责重绘
|
||||||
*/
|
*/
|
||||||
int action_cb(Ihandle *ih)
|
int action_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
IupDrawBegin(ih);
|
HWND hwnd = (HWND)IupGetAttribute(ih, "WID");
|
||||||
|
if (!hwnd)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
int w, h;
|
HDC hdc = GetDC(hwnd);
|
||||||
IupGetIntInt(ih, "DRAWSIZE", &w, &h);
|
if (!hdc)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
set_draw_color(ih, 240, 217, 181); // 棋盘背景色 (木纹色近似)
|
RECT rc;
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
GetClientRect(hwnd, &rc);
|
||||||
IupDrawRectangle(ih, 0, 0, w, h);
|
|
||||||
|
|
||||||
draw_board_iup(ih);
|
// 预创建所有 GDI 对象(避免循环内反复创建销毁)
|
||||||
draw_stones_iup(ih);
|
HBRUSH bg_brush = CreateSolidBrush(RGB(240, 217, 181));
|
||||||
|
HBRUSH black_brush = CreateSolidBrush(RGB(0, 0, 0));
|
||||||
|
HBRUSH white_brush = CreateSolidBrush(RGB(255, 255, 255));
|
||||||
|
HBRUSH red_brush = CreateSolidBrush(RGB(255, 0, 0));
|
||||||
|
HPEN grid_pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
|
||||||
|
HPEN stone_pen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
|
||||||
|
|
||||||
|
// 1. 填充背景
|
||||||
|
FillRect(hdc, &rc, bg_brush);
|
||||||
|
|
||||||
|
// 2. 绘制棋盘网格
|
||||||
|
HPEN prev_pen = (HPEN)SelectObject(hdc, grid_pen);
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
|
{
|
||||||
|
MoveToEx(hdc, BOARD_OFFSET_X, BOARD_OFFSET_Y + i * CELL_SIZE, NULL);
|
||||||
|
LineTo(hdc, BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE, BOARD_OFFSET_Y + i * CELL_SIZE);
|
||||||
|
MoveToEx(hdc, BOARD_OFFSET_X + i * CELL_SIZE, BOARD_OFFSET_Y, NULL);
|
||||||
|
LineTo(hdc, BOARD_OFFSET_X + i * CELL_SIZE, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 星位/天元
|
||||||
|
SelectObject(hdc, black_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 - 3, cy - 3, cx + 4, cy + 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int cx = BOARD_OFFSET_X + (BOARD_SIZE / 2) * CELL_SIZE;
|
||||||
|
int cy = BOARD_OFFSET_Y + (BOARD_SIZE / 2) * CELL_SIZE;
|
||||||
|
Ellipse(hdc, cx - 3, cy - 3, cx + 4, cy + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 绘制棋子
|
||||||
|
SelectObject(hdc, stone_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_brush);
|
||||||
|
else
|
||||||
|
SelectObject(hdc, white_brush);
|
||||||
|
|
||||||
|
Ellipse(hdc, cx - STONE_RADIUS, cy - STONE_RADIUS,
|
||||||
|
cx + STONE_RADIUS + 1, cy + STONE_RADIUS + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 标记最后落子位置(红色小方块)
|
||||||
|
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;
|
||||||
|
RECT mark = {cx - 3, cy - 3, cx + 4, cy + 4};
|
||||||
|
FillRect(hdc, &mark, red_brush);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复原始 GDI 对象,然后清理
|
||||||
|
SelectObject(hdc, prev_pen);
|
||||||
|
ReleaseDC(hwnd, hdc);
|
||||||
|
|
||||||
|
DeleteObject(bg_brush);
|
||||||
|
DeleteObject(black_brush);
|
||||||
|
DeleteObject(white_brush);
|
||||||
|
DeleteObject(red_brush);
|
||||||
|
DeleteObject(grid_pen);
|
||||||
|
DeleteObject(stone_pen);
|
||||||
|
|
||||||
IupDrawEnd(ih);
|
|
||||||
return IUP_DEFAULT;
|
return IUP_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,14 +317,20 @@ int btn_save_cb(Ihandle *ih)
|
|||||||
else
|
else
|
||||||
base_name = filename;
|
base_name = filename;
|
||||||
|
|
||||||
int mode = (gui_game_mode == 0) ? GAME_MODE_PVP : GAME_MODE_AI;
|
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)
|
if (save_game_to_file(base_name, mode) == 0)
|
||||||
{
|
{
|
||||||
sprintf(status_message, "保存成功: %s", base_name);
|
snprintf(status_message, sizeof(status_message), "保存成功: %s", base_name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sprintf(status_message, "保存失败");
|
snprintf(status_message, sizeof(status_message), "保存失败");
|
||||||
}
|
}
|
||||||
update_ui_labels();
|
update_ui_labels();
|
||||||
}
|
}
|
||||||
@@ -185,23 +345,29 @@ int btn_save_cb(Ihandle *ih)
|
|||||||
int btn_back_cb(Ihandle *ih)
|
int btn_back_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
(void)ih;
|
(void)ih;
|
||||||
printf("DEBUG: Back to Menu clicked\n");
|
|
||||||
|
|
||||||
// 如果是网络模式,断开连接
|
// 停止所有定时器
|
||||||
|
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)
|
if (gui_game_mode == 3)
|
||||||
{
|
{
|
||||||
disconnect_network();
|
cleanup_network();
|
||||||
if (timer)
|
|
||||||
{
|
|
||||||
IupSetAttribute(timer, "RUN", "NO");
|
|
||||||
IupDestroy(timer);
|
|
||||||
timer = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 先显示主菜单
|
// 1. 先显示主菜单
|
||||||
show_main_menu();
|
show_main_menu();
|
||||||
printf("DEBUG: Main menu shown\n");
|
|
||||||
|
|
||||||
// 2. 销毁游戏窗口
|
// 2. 销毁游戏窗口
|
||||||
if (dlg)
|
if (dlg)
|
||||||
@@ -209,7 +375,6 @@ int btn_back_cb(Ihandle *ih)
|
|||||||
Ihandle *old_dlg = dlg;
|
Ihandle *old_dlg = dlg;
|
||||||
dlg = NULL; // 先清除全局指针
|
dlg = NULL; // 先清除全局指针
|
||||||
IupDestroy(old_dlg);
|
IupDestroy(old_dlg);
|
||||||
printf("DEBUG: Destroyed game window\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return IUP_IGNORE; // 返回 IUP_IGNORE 以阻止默认处理
|
return IUP_IGNORE; // 返回 IUP_IGNORE 以阻止默认处理
|
||||||
@@ -278,25 +443,34 @@ int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
|||||||
else // PvE
|
else // PvE
|
||||||
{
|
{
|
||||||
current_player_gui = AI;
|
current_player_gui = AI;
|
||||||
sprintf(status_message, "AI思考中...");
|
|
||||||
update_ui_labels();
|
update_ui_labels();
|
||||||
IupUpdate(ih); // 立即更新显示
|
IupUpdate(ih); // 立即更新显示
|
||||||
IupFlush(); // 强制刷新事件队列
|
IupFlush(); // 强制刷新事件队列
|
||||||
|
|
||||||
// AI 回合
|
if (llm_use)
|
||||||
ai_move(ai_difficulty);
|
|
||||||
|
|
||||||
Step last_step = steps[step_count - 1];
|
|
||||||
if (check_win(last_step.x, last_step.y, AI))
|
|
||||||
{
|
{
|
||||||
game_over = 1;
|
// 大模型AI - 异步调用,不阻塞UI
|
||||||
sprintf(status_message, "AI获胜!");
|
sprintf(status_message, "AI思考中(大模型)...");
|
||||||
IupMessage("游戏结束", "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
|
else
|
||||||
{
|
{
|
||||||
current_player_gui = PLAYER;
|
// 算法AI - 同步调用
|
||||||
sprintf(status_message, "轮到玩家");
|
sprintf(status_message, "AI思考中...");
|
||||||
|
update_ui_labels();
|
||||||
|
|
||||||
|
ai_move(ai_difficulty);
|
||||||
|
process_ai_move_result();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,8 +510,6 @@ int k_any_cb(Ihandle *ih, int c)
|
|||||||
*/
|
*/
|
||||||
void create_game_window()
|
void create_game_window()
|
||||||
{
|
{
|
||||||
printf("DEBUG: create_game_window start\n");
|
|
||||||
|
|
||||||
if (dlg)
|
if (dlg)
|
||||||
{
|
{
|
||||||
IupDestroy(dlg);
|
IupDestroy(dlg);
|
||||||
@@ -349,10 +521,10 @@ void create_game_window()
|
|||||||
if (!board_canvas)
|
if (!board_canvas)
|
||||||
printf("ERROR: Failed to create board_canvas\n");
|
printf("ERROR: Failed to create board_canvas\n");
|
||||||
|
|
||||||
IupSetAttribute(board_canvas, "ACTION", "action_cb");
|
|
||||||
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
|
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
|
||||||
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
|
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
|
||||||
IupSetCallback(board_canvas, "K_ANY", (Icallback)k_any_cb);
|
IupSetCallback(board_canvas, "K_ANY", (Icallback)k_any_cb);
|
||||||
|
IupSetCallback(board_canvas, "MAP_CB", (Icallback)map_cb);
|
||||||
|
|
||||||
// 计算棋盘像素大小
|
// 计算棋盘像素大小
|
||||||
int board_pixel_size = BOARD_SIZE * CELL_SIZE + BOARD_OFFSET_X * 2;
|
int board_pixel_size = BOARD_SIZE * CELL_SIZE + BOARD_OFFSET_X * 2;
|
||||||
@@ -360,6 +532,8 @@ void create_game_window()
|
|||||||
sprintf(size, "%dx%d", board_pixel_size, board_pixel_size);
|
sprintf(size, "%dx%d", board_pixel_size, board_pixel_size);
|
||||||
IupSetAttribute(board_canvas, "RASTERSIZE", size);
|
IupSetAttribute(board_canvas, "RASTERSIZE", size);
|
||||||
IupSetAttribute(board_canvas, "EXPAND", "NO");
|
IupSetAttribute(board_canvas, "EXPAND", "NO");
|
||||||
|
IupSetAttribute(board_canvas, "BORDER", "NO");
|
||||||
|
IupSetAttribute(board_canvas, "BGCOLOR", "240 217 181");
|
||||||
|
|
||||||
// 创建标签 (玩家信息和游戏状态)
|
// 创建标签 (玩家信息和游戏状态)
|
||||||
lbl_player = IupLabel("当前玩家: 黑子");
|
lbl_player = IupLabel("当前玩家: 黑子");
|
||||||
@@ -434,8 +608,6 @@ void create_game_window()
|
|||||||
|
|
||||||
// 设置 CLOSE_CB 回调,确保点击X也能正确返回菜单
|
// 设置 CLOSE_CB 回调,确保点击X也能正确返回菜单
|
||||||
IupSetCallback(dlg, "CLOSE_CB", (Icallback)btn_back_cb);
|
IupSetCallback(dlg, "CLOSE_CB", (Icallback)btn_back_cb);
|
||||||
|
|
||||||
printf("DEBUG: create_game_window end\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_pvp_game_gui()
|
void start_pvp_game_gui()
|
||||||
@@ -448,6 +620,7 @@ void start_pvp_game_gui()
|
|||||||
create_game_window();
|
create_game_window();
|
||||||
|
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||||
|
IupFlush(); // 确保窗口完全映射
|
||||||
sprintf(status_message, "玩家对战模式 - 黑方先行");
|
sprintf(status_message, "玩家对战模式 - 黑方先行");
|
||||||
update_ui_labels();
|
update_ui_labels();
|
||||||
if (board_canvas)
|
if (board_canvas)
|
||||||
@@ -456,56 +629,44 @@ void start_pvp_game_gui()
|
|||||||
|
|
||||||
void start_pve_game_gui()
|
void start_pve_game_gui()
|
||||||
{
|
{
|
||||||
printf("DEBUG: start_pve_game_gui start\n");
|
|
||||||
gui_game_mode = 1;
|
gui_game_mode = 1;
|
||||||
// ai_difficulty 是全局变量
|
|
||||||
empty_board();
|
empty_board();
|
||||||
current_player_gui = PLAYER;
|
current_player_gui = PLAYER;
|
||||||
game_over = 0;
|
game_over = 0;
|
||||||
|
|
||||||
create_game_window();
|
create_game_window();
|
||||||
printf("DEBUG: create_game_window returned\n");
|
|
||||||
|
|
||||||
if (dlg)
|
if (dlg)
|
||||||
{
|
{
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||||
printf("DEBUG: IupShowXY called\n");
|
IupFlush();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
printf("ERROR: dlg is NULL in start_pve_game_gui\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(status_message, "人机对战模式 - 玩家执黑先行");
|
sprintf(status_message, "人机对战模式 - 玩家执黑先行");
|
||||||
update_ui_labels();
|
update_ui_labels();
|
||||||
printf("DEBUG: update_ui_labels returned\n");
|
|
||||||
|
|
||||||
// 强制初始重绘
|
|
||||||
if (board_canvas)
|
if (board_canvas)
|
||||||
{
|
|
||||||
IupUpdate(board_canvas);
|
IupUpdate(board_canvas);
|
||||||
}
|
|
||||||
printf("DEBUG: start_pve_game_gui end\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_network_game_gui()
|
void start_network_game_gui()
|
||||||
{
|
{
|
||||||
printf("DEBUG: start_network_game_gui start\n");
|
|
||||||
gui_game_mode = 3;
|
gui_game_mode = 3;
|
||||||
empty_board();
|
empty_board();
|
||||||
|
|
||||||
// 主机执黑先行
|
|
||||||
current_player_gui = PLAYER1;
|
current_player_gui = PLAYER1;
|
||||||
game_over = 0;
|
game_over = 0;
|
||||||
|
|
||||||
create_game_window();
|
create_game_window();
|
||||||
printf("DEBUG: create_game_window returned\n");
|
|
||||||
|
|
||||||
if (dlg)
|
if (dlg)
|
||||||
{
|
{
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||||
printf("DEBUG: IupShowXY called\n");
|
IupFlush();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (network_state.is_server)
|
if (network_state.is_server)
|
||||||
@@ -526,6 +687,4 @@ void start_network_game_gui()
|
|||||||
IupSetCallback(timer, "ACTION_CB", (Icallback)timer_cb);
|
IupSetCallback(timer, "ACTION_CB", (Icallback)timer_cb);
|
||||||
IupSetAttribute(timer, "TIME", "50"); // 50ms 轮询一次
|
IupSetAttribute(timer, "TIME", "50"); // 50ms 轮询一次
|
||||||
IupSetAttribute(timer, "RUN", "YES");
|
IupSetAttribute(timer, "RUN", "YES");
|
||||||
|
|
||||||
printf("DEBUG: start_network_game_gui end\n");
|
|
||||||
}
|
}
|
||||||
@@ -14,20 +14,16 @@ Ihandle *menu_dlg = NULL;
|
|||||||
static int btn_pvp_cb(Ihandle *ih)
|
static int btn_pvp_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
(void)ih;
|
(void)ih;
|
||||||
printf("DEBUG: Starting PvP Game\n");
|
|
||||||
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
|
|
||||||
start_pvp_game_gui();
|
start_pvp_game_gui();
|
||||||
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
|
IupHide(menu_dlg);
|
||||||
return IUP_DEFAULT;
|
return IUP_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int btn_pve_cb(Ihandle *ih)
|
static int btn_pve_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
(void)ih;
|
(void)ih;
|
||||||
printf("DEBUG: Starting PvE Game\n");
|
|
||||||
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
|
|
||||||
start_pve_game_gui();
|
start_pve_game_gui();
|
||||||
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
|
IupHide(menu_dlg);
|
||||||
return IUP_DEFAULT;
|
return IUP_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +83,6 @@ static int btn_network_cancel_cb(Ihandle *ih)
|
|||||||
static int btn_network_cb(Ihandle *ih)
|
static int btn_network_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
(void)ih;
|
(void)ih;
|
||||||
printf("DEBUG: Opening Network Menu\n");
|
|
||||||
|
|
||||||
Ihandle *txt_ip = IupText(NULL);
|
Ihandle *txt_ip = IupText(NULL);
|
||||||
IupSetAttribute(txt_ip, "NAME", "NET_IP");
|
IupSetAttribute(txt_ip, "NAME", "NET_IP");
|
||||||
@@ -135,10 +130,8 @@ static int btn_network_cb(Ihandle *ih)
|
|||||||
static int btn_replay_cb(Ihandle *ih)
|
static int btn_replay_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
(void)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();
|
start_replay_gui();
|
||||||
IupHide(menu_dlg); // Hide main menu
|
IupHide(menu_dlg);
|
||||||
return IUP_DEFAULT;
|
return IUP_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +173,36 @@ static int btn_save_settings_cb(Ihandle *ih)
|
|||||||
ai_difficulty = ai_level;
|
ai_difficulty = ai_level;
|
||||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
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 config
|
||||||
save_game_config();
|
save_game_config();
|
||||||
|
|
||||||
@@ -251,6 +274,42 @@ static int btn_settings_cb(Ihandle *ih)
|
|||||||
IupSetInt(lst_ai, "VALUE", ai_difficulty);
|
IupSetInt(lst_ai, "VALUE", ai_difficulty);
|
||||||
IupSetAttribute(lst_ai, "SIZE", "80x");
|
IupSetAttribute(lst_ai, "SIZE", "80x");
|
||||||
|
|
||||||
|
// === 大模型AI设置 ===
|
||||||
|
Ihandle *lbl_llm_sep = IupLabel("--- 大模型AI设置 ---");
|
||||||
|
IupSetAttribute(lbl_llm_sep, "ALIGNMENT", "ACENTER");
|
||||||
|
|
||||||
|
// 6. AI 模式选择
|
||||||
|
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");
|
||||||
|
|
||||||
|
// 7. LLM API 地址
|
||||||
|
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");
|
||||||
|
|
||||||
|
// 8. LLM API Key
|
||||||
|
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");
|
||||||
|
|
||||||
|
// 9. LLM 模型名
|
||||||
|
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");
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
Ihandle *btn_save = IupButton("保存", NULL);
|
Ihandle *btn_save = IupButton("保存", NULL);
|
||||||
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_settings_cb);
|
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_settings_cb);
|
||||||
@@ -273,6 +332,23 @@ static int btn_settings_cb(Ihandle *ih)
|
|||||||
IupSetAttribute(hbox_ai, "ALIGNMENT", "ACENTER");
|
IupSetAttribute(hbox_ai, "ALIGNMENT", "ACENTER");
|
||||||
IupSetAttribute(hbox_ai, "GAP", "10");
|
IupSetAttribute(hbox_ai, "GAP", "10");
|
||||||
|
|
||||||
|
// LLM 设置布局
|
||||||
|
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 *hbox_endpoint = IupHbox(lbl_endpoint, txt_endpoint, NULL);
|
||||||
|
IupSetAttribute(hbox_endpoint, "ALIGNMENT", "ACENTER");
|
||||||
|
IupSetAttribute(hbox_endpoint, "GAP", "10");
|
||||||
|
|
||||||
|
Ihandle *hbox_apikey = IupHbox(lbl_apikey, txt_apikey, NULL);
|
||||||
|
IupSetAttribute(hbox_apikey, "ALIGNMENT", "ACENTER");
|
||||||
|
IupSetAttribute(hbox_apikey, "GAP", "10");
|
||||||
|
|
||||||
|
Ihandle *hbox_model = IupHbox(lbl_model, txt_model, NULL);
|
||||||
|
IupSetAttribute(hbox_model, "ALIGNMENT", "ACENTER");
|
||||||
|
IupSetAttribute(hbox_model, "GAP", "10");
|
||||||
|
|
||||||
Ihandle *hbox_btns = IupHbox(btn_save, btn_cancel, NULL);
|
Ihandle *hbox_btns = IupHbox(btn_save, btn_cancel, NULL);
|
||||||
IupSetAttribute(hbox_btns, "GAP", "20");
|
IupSetAttribute(hbox_btns, "GAP", "20");
|
||||||
IupSetAttribute(hbox_btns, "MARGIN", "10x0");
|
IupSetAttribute(hbox_btns, "MARGIN", "10x0");
|
||||||
@@ -285,11 +361,17 @@ static int btn_settings_cb(Ihandle *ih)
|
|||||||
hbox_time,
|
hbox_time,
|
||||||
hbox_ai,
|
hbox_ai,
|
||||||
IupLabel(NULL), // Spacer
|
IupLabel(NULL), // Spacer
|
||||||
|
lbl_llm_sep,
|
||||||
|
hbox_ai_mode,
|
||||||
|
hbox_endpoint,
|
||||||
|
hbox_apikey,
|
||||||
|
hbox_model,
|
||||||
|
IupLabel(NULL), // Spacer
|
||||||
hbox_btns,
|
hbox_btns,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
IupSetAttribute(vbox, "GAP", "15");
|
IupSetAttribute(vbox, "GAP", "10");
|
||||||
IupSetAttribute(vbox, "MARGIN", "30x30");
|
IupSetAttribute(vbox, "MARGIN", "20x20");
|
||||||
|
|
||||||
Ihandle *dlg = IupDialog(vbox);
|
Ihandle *dlg = IupDialog(vbox);
|
||||||
IupSetAttribute(dlg, "TITLE", "游戏设置");
|
IupSetAttribute(dlg, "TITLE", "游戏设置");
|
||||||
@@ -315,7 +397,6 @@ void create_main_menu()
|
|||||||
{
|
{
|
||||||
if (menu_dlg)
|
if (menu_dlg)
|
||||||
return;
|
return;
|
||||||
printf("DEBUG: create_main_menu\n");
|
|
||||||
|
|
||||||
Ihandle *lbl_title = IupLabel("五子棋 (Gobang)");
|
Ihandle *lbl_title = IupLabel("五子棋 (Gobang)");
|
||||||
IupSetAttribute(lbl_title, "FONT", "SimHei, 24");
|
IupSetAttribute(lbl_title, "FONT", "SimHei, 24");
|
||||||
@@ -377,14 +458,9 @@ void create_main_menu()
|
|||||||
|
|
||||||
void show_main_menu()
|
void show_main_menu()
|
||||||
{
|
{
|
||||||
printf("DEBUG: show_main_menu start\n");
|
|
||||||
if (!menu_dlg)
|
if (!menu_dlg)
|
||||||
{
|
|
||||||
printf("DEBUG: Creating main menu\n");
|
|
||||||
create_main_menu();
|
create_main_menu();
|
||||||
}
|
|
||||||
IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER);
|
IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER);
|
||||||
printf("DEBUG: show_main_menu end\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hide_main_menu()
|
void hide_main_menu()
|
||||||
@@ -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,12 +17,18 @@
|
|||||||
/**
|
/**
|
||||||
* @brief 初始化网络模块
|
* @brief 初始化网络模块
|
||||||
*/
|
*/
|
||||||
|
static bool enet_initialized = false;
|
||||||
|
|
||||||
bool init_network()
|
bool init_network()
|
||||||
{
|
{
|
||||||
if (enet_initialize() != 0)
|
if (!enet_initialized)
|
||||||
{
|
{
|
||||||
printf("An error occurred while initializing ENet.\n");
|
if (enet_initialize() != 0)
|
||||||
return false;
|
{
|
||||||
|
printf("An error occurred while initializing ENet.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
enet_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&network_state, 0, sizeof(NetworkGameState));
|
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||||
@@ -44,8 +50,13 @@ void cleanup_network()
|
|||||||
network_state.host = NULL;
|
network_state.host = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
enet_deinitialize();
|
if (enet_initialized)
|
||||||
network_state.is_connected = false;
|
{
|
||||||
|
enet_deinitialize();
|
||||||
|
enet_initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&network_state, 0, sizeof(NetworkGameState));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +64,9 @@ void cleanup_network()
|
|||||||
*/
|
*/
|
||||||
bool create_server(int port)
|
bool create_server(int port)
|
||||||
{
|
{
|
||||||
|
if (!init_network())
|
||||||
|
return false;
|
||||||
|
|
||||||
ENetAddress address;
|
ENetAddress address;
|
||||||
|
|
||||||
// 绑定所有接口
|
// 绑定所有接口
|
||||||
@@ -117,6 +131,9 @@ bool create_server(int port)
|
|||||||
*/
|
*/
|
||||||
bool connect_to_server(const char *ip, int port)
|
bool connect_to_server(const char *ip, int port)
|
||||||
{
|
{
|
||||||
|
if (!init_network())
|
||||||
|
return false;
|
||||||
|
|
||||||
// 创建客户端主机
|
// 创建客户端主机
|
||||||
network_state.host = (void *)enet_host_create(NULL, // 创建客户端
|
network_state.host = (void *)enet_host_create(NULL, // 创建客户端
|
||||||
1, // 仅允许1个传出连接
|
1, // 仅允许1个传出连接
|
||||||
@@ -286,17 +303,54 @@ bool is_network_connected()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取本机IP地址
|
* @brief 获取本机局域网IP地址
|
||||||
*/
|
*/
|
||||||
bool get_local_ip(char *ip_buffer, int buffer_size)
|
bool get_local_ip(char *ip_buffer, int buffer_size)
|
||||||
{
|
{
|
||||||
// ENet 没有直接获取本机局域网 IP 的简单跨平台函数。
|
#ifdef _WIN32
|
||||||
// 这里我们可以回退到原生 socket 方法,或者简单返回本地回环。
|
// 使用 Winsock 获取本机 IP(ws2_32 已链接)
|
||||||
// 为了不引入额外的系统头文件,暂时返回通用提示。
|
char hostname[256];
|
||||||
// 在真实应用中,可以保留之前的 gethostname/gethostbyname 逻辑。
|
if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR)
|
||||||
strncpy(ip_buffer, "查看本机网络适配器", buffer_size - 1);
|
{
|
||||||
|
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||||
|
ip_buffer[buffer_size - 1] = '\0';
|
||||||
|
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';
|
ip_buffer[buffer_size - 1] = '\0';
|
||||||
return true; // 总是返回 true 以允许服务器继续启动
|
return true;
|
||||||
|
#else
|
||||||
|
strncpy(ip_buffer, "127.0.0.1", buffer_size - 1);
|
||||||
|
ip_buffer[buffer_size - 1] = '\0';
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -261,7 +261,7 @@ int load_game_from_file(const char *filename)
|
|||||||
FILE *file = fopen(fullpath, "r");
|
FILE *file = fopen(fullpath, "r");
|
||||||
if (!file)
|
if (!file)
|
||||||
{
|
{
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳过CSV文件头部行
|
// 跳过CSV文件头部行
|
||||||
Reference in New Issue
Block a user