mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-05-10 02:19:46 +08:00
feat(gui): 添加主菜单和游戏模式选择界面
- 新增 GUI 主菜单,支持玩家对战、人机对战、复盘模式和设置选项 - 新增游戏设置对话框,可配置棋盘大小、禁手、计时器和 AI 难度 - 重构 GUI 游戏窗口,集成悔棋、保存和返回菜单功能 - 更新 Makefile 以支持 PowerShell 并添加新源文件编译 - 调整窗口尺寸为 1000x800 以提供更好的游戏体验
This commit is contained in:
@@ -3,14 +3,18 @@
|
||||
|
||||
# 编译器设置
|
||||
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
|
||||
LDFLAGS = -lws2_32
|
||||
|
||||
# IUP路径设置
|
||||
IUP_PATH = libs/iup-3.31_Win64_dllw6_lib
|
||||
IUP_INCLUDE = -I$(IUP_PATH)/include
|
||||
IUP_INCLUDE = "-I$(IUP_PATH)/include"
|
||||
# IUP链接库: iup, gdi32, comdlg32, comctl32, uuid, ole32
|
||||
IUP_LIBS = -L$(IUP_PATH) -liup -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32
|
||||
IUP_LIBS = "-L$(IUP_PATH)" -liup -lgdi32 -lcomdlg32 -lcomctl32 -luuid -lole32
|
||||
|
||||
# 目录设置
|
||||
SRC_DIR = src
|
||||
@@ -20,7 +24,8 @@ BIN_DIR = bin
|
||||
# 源文件
|
||||
COMMON_SOURCES = $(SRC_DIR)/main.c $(SRC_DIR)/gobang.c $(SRC_DIR)/ai.c $(SRC_DIR)/config.c \
|
||||
$(SRC_DIR)/game_mode.c $(SRC_DIR)/globals.c $(SRC_DIR)/init_board.c \
|
||||
$(SRC_DIR)/network.c $(SRC_DIR)/record.c $(SRC_DIR)/ui.c $(SRC_DIR)/gui.c
|
||||
$(SRC_DIR)/network.c $(SRC_DIR)/record.c $(SRC_DIR)/ui.c $(SRC_DIR)/gui.c \
|
||||
$(SRC_DIR)/gui_menu.c
|
||||
|
||||
GUI_SOURCES = $(COMMON_SOURCES)
|
||||
CONSOLE_SOURCES = $(COMMON_SOURCES)
|
||||
@@ -35,10 +40,10 @@ GUI_TARGET = $(BIN_DIR)/gobang_gui.exe
|
||||
# 默认目标
|
||||
all: directories $(CONSOLE_TARGET) $(GUI_TARGET)
|
||||
|
||||
# 创建目录
|
||||
# 创建目录 (PowerShell 语法)
|
||||
directories:
|
||||
-mkdir $(OBJ_DIR)
|
||||
-mkdir $(BIN_DIR)
|
||||
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 }
|
||||
|
||||
# 控制台版本
|
||||
$(CONSOLE_TARGET): $(COMMON_OBJECTS)
|
||||
@@ -47,17 +52,16 @@ $(CONSOLE_TARGET): $(COMMON_OBJECTS)
|
||||
# GUI版本
|
||||
$(GUI_TARGET): $(COMMON_OBJECTS)
|
||||
$(CC) $(CFLAGS) $(IUP_INCLUDE) -o $@ $^ $(IUP_LIBS) $(LDFLAGS)
|
||||
copy $(subst /,\,$(IUP_PATH))\iup.dll $(BIN_DIR) 2>nul || echo Warning: Could not copy iup.dll
|
||||
Copy-Item -Path "$(subst /,\,$(IUP_PATH))\iup.dll" -Destination "$(BIN_DIR)" -Force
|
||||
|
||||
# 通用目标文件编译规则
|
||||
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
|
||||
$(CC) $(CFLAGS) $(IUP_INCLUDE) -c -o $@ $<
|
||||
|
||||
# 清理规则
|
||||
# 清理规则 (PowerShell 语法)
|
||||
clean:
|
||||
-del /Q $(OBJ_DIR)\*.o $(BIN_DIR)\*.exe $(BIN_DIR)\*.dll
|
||||
-rmdir $(OBJ_DIR)
|
||||
-rmdir $(BIN_DIR)
|
||||
if (Test-Path "$(OBJ_DIR)") { Remove-Item -Path "$(OBJ_DIR)" -Recurse -Force }
|
||||
if (Test-Path "$(BIN_DIR)") { Remove-Item -Path "$(BIN_DIR)" -Recurse -Force }
|
||||
|
||||
# 只编译控制台版本
|
||||
console: directories $(CONSOLE_TARGET)
|
||||
@@ -67,17 +71,17 @@ gui: directories $(GUI_TARGET)
|
||||
|
||||
# 安装规则(可选)
|
||||
install: all
|
||||
@echo Installing executables...
|
||||
copy $(CONSOLE_TARGET) C:\Program Files\Gobang\ 2>nul || echo Install directory not found
|
||||
copy $(GUI_TARGET) C:\Program Files\Gobang\ 2>nul || echo Install directory not found
|
||||
Write-Host "Installing executables..."
|
||||
Copy-Item -Path "$(CONSOLE_TARGET)" -Destination "C:\Program Files\Gobang\" -Force
|
||||
Copy-Item -Path "$(GUI_TARGET)" -Destination "C:\Program Files\Gobang\" -Force
|
||||
|
||||
# 运行控制台版本
|
||||
run-console: $(CONSOLE_TARGET)
|
||||
.\$(CONSOLE_TARGET)
|
||||
& ".\$(CONSOLE_TARGET)"
|
||||
|
||||
# 运行GUI版本
|
||||
run-gui: $(GUI_TARGET)
|
||||
.\$(GUI_TARGET)
|
||||
& ".\$(GUI_TARGET)"
|
||||
|
||||
# 帮助信息
|
||||
help:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+2
-2
@@ -118,8 +118,8 @@
|
||||
|
||||
//---------- GUI界面参数 ----------//
|
||||
// 窗口和棋盘配置
|
||||
#define WINDOW_WIDTH 800
|
||||
#define WINDOW_HEIGHT 600
|
||||
#define WINDOW_WIDTH 1000
|
||||
#define WINDOW_HEIGHT 800
|
||||
#define BOARD_OFFSET_X 50
|
||||
#define BOARD_OFFSET_Y 50
|
||||
#define CELL_SIZE 30
|
||||
|
||||
@@ -27,6 +27,7 @@ extern int network_timeout; // 网络超时时间
|
||||
|
||||
// ==================== AI相关变量 ====================
|
||||
extern double defense_coefficient; // 防守系数
|
||||
extern int ai_difficulty; // AI难度 (1-5)
|
||||
|
||||
// ==================== 网络相关变量 ====================
|
||||
extern NetworkGameState network_state; // 网络游戏状态
|
||||
|
||||
@@ -66,4 +66,19 @@ int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
||||
*/
|
||||
void show_message(const char *message);
|
||||
|
||||
/**
|
||||
* @brief 启动玩家对战模式
|
||||
*/
|
||||
void start_pvp_game_gui();
|
||||
|
||||
/**
|
||||
* @brief 启动人机对战模式
|
||||
*/
|
||||
void start_pve_game_gui();
|
||||
|
||||
/**
|
||||
* @brief 启动复盘模式
|
||||
*/
|
||||
void start_replay_gui();
|
||||
|
||||
#endif // GUI_H
|
||||
@@ -0,0 +1,19 @@
|
||||
#ifndef GUI_MENU_H
|
||||
#define GUI_MENU_H
|
||||
|
||||
/**
|
||||
* @brief 创建并显示主菜单
|
||||
*/
|
||||
void create_main_menu();
|
||||
|
||||
/**
|
||||
* @brief 显示主菜单
|
||||
*/
|
||||
void show_main_menu();
|
||||
|
||||
/**
|
||||
* @brief 隐藏主菜单
|
||||
*/
|
||||
void hide_main_menu();
|
||||
|
||||
#endif // GUI_MENU_H
|
||||
+7
-2
@@ -73,8 +73,8 @@ void load_game_config()
|
||||
int difficulty = atoi(line + 14);
|
||||
if (difficulty >= 1 && difficulty <= 5)
|
||||
{
|
||||
// 根据难度设置AI搜索深度
|
||||
// 这里可以添加AI难度相关的配置
|
||||
ai_difficulty = difficulty;
|
||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,9 @@ void save_game_config()
|
||||
fprintf(file, "\n# 网络超时时间 (毫秒)\n");
|
||||
fprintf(file, "NETWORK_TIMEOUT=%d\n", network_timeout);
|
||||
|
||||
fprintf(file, "\n# AI难度 (1-5)\n");
|
||||
fprintf(file, "AI_DIFFICULTY=%d\n", ai_difficulty);
|
||||
|
||||
fclose(file);
|
||||
printf("配置保存完成\n");
|
||||
}
|
||||
@@ -124,6 +127,8 @@ void reset_to_default_config()
|
||||
time_limit = DEFAULT_TIME_LIMIT;
|
||||
network_port = DEFAULT_NETWORK_PORT;
|
||||
network_timeout = NETWORK_TIMEOUT_MS;
|
||||
ai_difficulty = 3; // 默认AI难度
|
||||
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
|
||||
|
||||
printf("已重置为默认配置\n");
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ int network_timeout = NETWORK_TIMEOUT_MS; // 网络超时时间
|
||||
|
||||
// ==================== AI相关变量定义 ====================
|
||||
double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
|
||||
int ai_difficulty = 3; // AI难度 (1-5)
|
||||
|
||||
// ==================== 网络相关变量定义 ====================
|
||||
NetworkGameState network_state = {0}; // 网络游戏状态
|
||||
|
||||
@@ -13,13 +13,26 @@
|
||||
#include "globals.h"
|
||||
#include "init_board.h"
|
||||
#include "gobang.h"
|
||||
#include "gui_menu.h"
|
||||
#include "ai.h"
|
||||
#include "record.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static Ihandle *dlg = NULL;
|
||||
static Ihandle *canvas = NULL;
|
||||
static Ihandle *board_canvas = NULL; // 重命名为 board_canvas
|
||||
static Ihandle *lbl_player = NULL;
|
||||
static Ihandle *lbl_status = NULL;
|
||||
static int gui_loop_running = 0;
|
||||
static int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay
|
||||
static int replay_total_steps = 0; // For Replay Mode
|
||||
|
||||
// 回调函数
|
||||
static int action_cb(Ihandle *ih);
|
||||
static int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status);
|
||||
static int k_any_cb(Ihandle *ih, int c);
|
||||
void create_game_window(); // 移除前向声明
|
||||
|
||||
// 辅助函数:设置绘图颜色
|
||||
static void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b)
|
||||
@@ -108,64 +121,23 @@ static void draw_stones_iup(Ihandle *ih)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制UI元素
|
||||
static void draw_ui_iup(Ihandle *ih)
|
||||
// 标记最后落子位置 (红色小点)
|
||||
if (step_count > 0)
|
||||
{
|
||||
int infoX = BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE + 20;
|
||||
int infoY = BOARD_OFFSET_Y;
|
||||
int infoW = 220;
|
||||
int infoH = 120;
|
||||
// 绘制最后一步的标记
|
||||
// 最后一步的坐标是 steps[step_count-1]
|
||||
// 所以 step_count-1 是最后一步的索引
|
||||
// 所以 steps[step_count-1] 是最后一步
|
||||
|
||||
// 背景
|
||||
set_draw_color(ih, 200, 200, 200); // Light Gray
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||
IupDrawRectangle(ih, infoX, infoY, infoX + infoW, infoY + infoH);
|
||||
Step last = steps[step_count - 1];
|
||||
int cx = BOARD_OFFSET_X + last.y * CELL_SIZE;
|
||||
int cy = BOARD_OFFSET_Y + last.x * CELL_SIZE;
|
||||
|
||||
// 边框
|
||||
set_draw_color(ih, 0, 0, 0);
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "STROKE");
|
||||
IupDrawRectangle(ih, infoX, infoY, infoX + infoW, infoY + infoH);
|
||||
|
||||
// 文本
|
||||
// 注意:IUP使用系统字体,不支持直接加载TTF文件像Raylib那样方便
|
||||
// 我们可以尝试设置字体属性
|
||||
IupSetAttribute(ih, "DRAWFONT", "SimHei, 14");
|
||||
|
||||
IupDrawText(ih, "当前玩家:", -1, infoX + 20, infoY + 20, -1, -1);
|
||||
|
||||
int indicatorX = infoX + 30;
|
||||
int indicatorY = infoY + 30;
|
||||
|
||||
if (!game_over)
|
||||
{
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
IupDrawText(ih, "黑子", -1, indicatorX + 20, indicatorY + 10, -1, -1);
|
||||
set_draw_color(ih, 0, 0, 0);
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||
IupDrawArc(ih, indicatorX, indicatorY + 20 - 5, indicatorX + 10, indicatorY + 20 + 5, 0, 360);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupDrawText(ih, "白子", -1, indicatorX + 20, indicatorY + 10, -1, -1);
|
||||
set_draw_color(ih, 255, 255, 255);
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||
IupDrawArc(ih, indicatorX, indicatorY + 20 - 5, indicatorX + 10, indicatorY + 20 + 5, 0, 360);
|
||||
set_draw_color(ih, 0, 0, 0);
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "STROKE");
|
||||
IupDrawArc(ih, indicatorX, indicatorY + 20 - 5, indicatorX + 10, indicatorY + 20 + 5, 0, 360);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
set_draw_color(ih, 255, 0, 0);
|
||||
IupDrawText(ih, "游戏结束", -1, indicatorX, indicatorY + 10, -1, -1);
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
||||
}
|
||||
|
||||
set_draw_color(ih, 0, 0, 0);
|
||||
IupDrawText(ih, status_message, -1, infoX + 10, infoY + 60, -1, -1);
|
||||
}
|
||||
|
||||
// 屏幕坐标转棋盘坐标
|
||||
@@ -181,6 +153,32 @@ int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y)
|
||||
*board_y >= 0 && *board_y < BOARD_SIZE);
|
||||
}
|
||||
|
||||
// 更新UI标签状态
|
||||
static void update_ui_labels()
|
||||
{
|
||||
if (lbl_player)
|
||||
{
|
||||
if (gui_game_mode == 2) // Replay
|
||||
{
|
||||
char buffer[64];
|
||||
sprintf(buffer, "进度: %d / %d", step_count, replay_total_steps);
|
||||
IupSetAttribute(lbl_player, "TITLE", buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current_player_gui == PLAYER)
|
||||
IupSetAttribute(lbl_player, "TITLE", "当前玩家: 黑子 (玩家)");
|
||||
else
|
||||
IupSetAttribute(lbl_player, "TITLE", "当前玩家: 白子 (AI/玩家2)");
|
||||
}
|
||||
}
|
||||
|
||||
if (lbl_status)
|
||||
{
|
||||
IupSetAttribute(lbl_status, "TITLE", status_message);
|
||||
}
|
||||
}
|
||||
|
||||
// ACTION 回调:负责重绘
|
||||
static int action_cb(Ihandle *ih)
|
||||
{
|
||||
@@ -189,21 +187,144 @@ static int action_cb(Ihandle *ih)
|
||||
int w, h;
|
||||
IupGetIntInt(ih, "DRAWSIZE", &w, &h);
|
||||
|
||||
set_draw_color(ih, 245, 245, 245); // Background (Raylib RAYWHITE approx)
|
||||
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);
|
||||
draw_ui_iup(ih);
|
||||
|
||||
IupDrawEnd(ih);
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// 悔棋按钮回调
|
||||
static 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)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 保存按钮回调
|
||||
static 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;
|
||||
}
|
||||
|
||||
// Replay Prev Callback
|
||||
static int btn_replay_prev_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (step_count > 0)
|
||||
{
|
||||
step_count--;
|
||||
Step s = steps[step_count];
|
||||
board[s.x][s.y] = EMPTY;
|
||||
sprintf(status_message, "回退一步");
|
||||
update_ui_labels();
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// Replay Next Callback
|
||||
static int btn_replay_next_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (step_count < replay_total_steps)
|
||||
{
|
||||
Step s = steps[step_count];
|
||||
board[s.x][s.y] = s.player;
|
||||
step_count++;
|
||||
sprintf(status_message, "前进一步");
|
||||
update_ui_labels();
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// 返回菜单回调
|
||||
static int btn_back_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
if (dlg)
|
||||
{
|
||||
IupHide(dlg);
|
||||
show_main_menu();
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// 鼠标点击回调
|
||||
static int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
{
|
||||
(void)status; // Unused
|
||||
if (gui_game_mode == 2)
|
||||
return IUP_DEFAULT; // Replay mode: disable clicks
|
||||
|
||||
if (button == IUP_BUTTON1 && pressed)
|
||||
{ // 左键按下
|
||||
if (game_over)
|
||||
@@ -224,32 +345,57 @@ static int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *s
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "黑子获胜!");
|
||||
IupMessage("游戏结束", "黑子获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "白子获胜!");
|
||||
IupMessage("游戏结束", "白子获胜!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 切换玩家
|
||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER;
|
||||
if (gui_game_mode == 0) // PvP
|
||||
{
|
||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER; // AI here means Player 2
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "轮到黑子");
|
||||
}
|
||||
else
|
||||
{
|
||||
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, "无效位置!");
|
||||
IupUpdate(ih);
|
||||
update_ui_labels();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,14 +405,204 @@ static int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *s
|
||||
// 键盘回调
|
||||
static int k_any_cb(Ihandle *ih, int c)
|
||||
{
|
||||
(void)ih;
|
||||
if (c == K_ESC)
|
||||
{
|
||||
gui_loop_running = 0;
|
||||
return IUP_CLOSE;
|
||||
if (dlg && IupGetInt(dlg, "VISIBLE"))
|
||||
{
|
||||
IupHide(dlg);
|
||||
show_main_menu();
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// 创建游戏窗口
|
||||
void create_game_window()
|
||||
{
|
||||
if (dlg)
|
||||
{
|
||||
IupDestroy(dlg); // 销毁旧窗口
|
||||
dlg = NULL;
|
||||
}
|
||||
|
||||
// 创建Canvas (Board)
|
||||
board_canvas = IupCanvas(NULL);
|
||||
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("当前玩家: 黑子");
|
||||
IupSetAttribute(lbl_player, "FONT", "SimHei, 14");
|
||||
|
||||
lbl_status = IupLabel("准备开始");
|
||||
IupSetAttribute(lbl_status, "FONT", "SimHei, 12");
|
||||
|
||||
Ihandle *vbox_controls;
|
||||
|
||||
if (gui_game_mode == 2) // Replay
|
||||
{
|
||||
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 // Game Mode
|
||||
{
|
||||
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);
|
||||
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
|
||||
IupMap(dlg);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
gui_game_mode = 1;
|
||||
// ai_difficulty is global
|
||||
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_replay_gui()
|
||||
{
|
||||
// Open file dialog
|
||||
Ihandle *file_dlg = IupFileDlg();
|
||||
IupSetAttribute(file_dlg, "DIALOGTYPE", "OPEN");
|
||||
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;
|
||||
|
||||
if (load_game_from_file(base_name)) // returns game_mode (non-zero) on success
|
||||
{
|
||||
replay_total_steps = step_count;
|
||||
step_count = 0;
|
||||
// load_game_from_file already cleared board when reading steps, but steps were read into array
|
||||
// wait, load_game_from_file calls empty_board() then reads steps.
|
||||
// But it doesn't set board array.
|
||||
// So board is empty.
|
||||
|
||||
gui_game_mode = 2; // Replay
|
||||
create_game_window();
|
||||
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
sprintf(status_message, "复盘模式 - %s", base_name);
|
||||
update_ui_labels();
|
||||
if (board_canvas)
|
||||
IupUpdate(board_canvas);
|
||||
}
|
||||
else
|
||||
{
|
||||
IupMessage("错误", "无法加载复盘文件");
|
||||
show_main_menu();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
show_main_menu();
|
||||
}
|
||||
|
||||
IupDestroy(file_dlg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化GUI
|
||||
*/
|
||||
@@ -281,43 +617,12 @@ int init_gui()
|
||||
// 启用UTF-8模式,确保中文正常显示
|
||||
IupSetGlobal("UTF8MODE", "YES");
|
||||
|
||||
// 创建Canvas
|
||||
canvas = IupCanvas(NULL);
|
||||
IupSetAttribute(canvas, "ACTION", "action_cb");
|
||||
IupSetCallback(canvas, "ACTION", (Icallback)action_cb);
|
||||
IupSetCallback(canvas, "BUTTON_CB", (Icallback)button_cb);
|
||||
IupSetCallback(canvas, "K_ANY", (Icallback)k_any_cb);
|
||||
create_main_menu();
|
||||
show_main_menu();
|
||||
|
||||
// 设置Canvas大小
|
||||
char size[32];
|
||||
sprintf(size, "%dx%d", WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
IupSetAttribute(canvas, "RASTERSIZE", size);
|
||||
IupSetAttribute(canvas, "EXPAND", "NO");
|
||||
|
||||
// 创建Dialog
|
||||
dlg = IupDialog(canvas);
|
||||
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
|
||||
// 初始化游戏状态
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
board[i][j] = EMPTY;
|
||||
}
|
||||
}
|
||||
current_player_gui = PLAYER;
|
||||
game_over = 0;
|
||||
gui_loop_running = 1;
|
||||
sprintf(status_message, "游戏开始");
|
||||
|
||||
printf("图形化界面初始化成功!(IUP)\n");
|
||||
printf("使用鼠标点击棋盘进行落子\n");
|
||||
printf("按ESC键退出游戏\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -343,7 +648,6 @@ int handle_events()
|
||||
if (!gui_loop_running)
|
||||
return 0;
|
||||
|
||||
// 执行一次IUP循环迭代
|
||||
int ret = IupLoopStep();
|
||||
if (ret == IUP_CLOSE)
|
||||
{
|
||||
@@ -351,9 +655,6 @@ int handle_events()
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果窗口被关闭(点X)
|
||||
// 注意:IupLoopStep会自动处理窗口关闭并返回IUP_CLOSE,除非定义了CLOSE_CB
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -362,20 +663,11 @@ int handle_events()
|
||||
*/
|
||||
void render_game()
|
||||
{
|
||||
// 主动刷新Canvas
|
||||
// 注意:频繁调用IupUpdate可能会导致闪烁或高CPU占用,但在单线程循环模型中通常是必要的
|
||||
// 以确保动画或状态更新能及时反映。
|
||||
// 在我们的例子中,主要依靠 button_cb 触发 IupUpdate。
|
||||
// 但为了兼容原来的循环结构,我们可以保持这个函数。
|
||||
// IupUpdate(canvas);
|
||||
// 实际上不需要每一帧都 Update,只有状态变了才需要。
|
||||
// 原来的Raylib代码是每帧都重绘。为了性能,这里我们什么都不做,让事件驱动。
|
||||
// 除非有外部事件改变了游戏状态(比如网络消息),那时应该调用 IupUpdate。
|
||||
// 鉴于目前是本地PVP/AI,所有状态改变都在 button_cb 里,那里已经调用了 IupUpdate。
|
||||
// 事件驱动,不需要手动渲染
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制棋盘 (保留空函数以兼容接口,实际绘图在 action_cb 中)
|
||||
* @brief 绘制棋盘 (保留空函数以兼容接口)
|
||||
*/
|
||||
void draw_board()
|
||||
{
|
||||
@@ -405,7 +697,6 @@ void show_message(const char *message)
|
||||
{
|
||||
strncpy(status_message, message, sizeof(status_message) - 1);
|
||||
status_message[sizeof(status_message) - 1] = '\0';
|
||||
if (canvas)
|
||||
IupUpdate(canvas);
|
||||
update_ui_labels();
|
||||
printf("%s\n", message);
|
||||
}
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
#include <iup.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "gui_menu.h"
|
||||
#include "gui.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
|
||||
static Ihandle *menu_dlg = NULL;
|
||||
|
||||
static int btn_pvp_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
hide_main_menu();
|
||||
start_pvp_game_gui();
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_pve_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
hide_main_menu();
|
||||
start_pve_game_gui();
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
static int btn_replay_cb(Ihandle *ih)
|
||||
{
|
||||
(void)ih;
|
||||
hide_main_menu();
|
||||
start_replay_gui();
|
||||
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;
|
||||
|
||||
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_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_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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user