mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-05-09 18:09:46 +08:00
refactor(gui): 拆分大型 GUI 模块为多个功能文件
将单一的 gui.c 文件拆分为 gui_core.c、gui_draw.c、gui_game.c 和 gui_replay.c,并引入 gui_internal.h 作为内部头文件。更新 Makefile 以包含新的源文件。同时修复了复盘模式中主菜单的隐藏时机,并改进了记录文件的加载逻辑以更安全地处理 CSV 解析。 - 提取核心 GUI 初始化、事件循环和坐标转换到 gui_core.c - 分离绘图功能(棋盘、棋子)到 gui_draw.c - 将游戏逻辑(PvP/PvE)移动到 gui_game.c - 独立复盘功能到 gui_replay.c,优化文件选择流程 - 修复 btn_replay_cb 中过早隐藏主菜单的问题 - 增强 load_game_from_file 的健壮性,使用 sscanf 替代 fscanf 并改进行处理
This commit is contained in:
@@ -24,7 +24,9 @@ BIN_DIR = bin
|
|||||||
# 源文件
|
# 源文件
|
||||||
COMMON_SOURCES = $(SRC_DIR)/gobang.c $(SRC_DIR)/ai.c $(SRC_DIR)/config.c \
|
COMMON_SOURCES = $(SRC_DIR)/gobang.c $(SRC_DIR)/ai.c $(SRC_DIR)/config.c \
|
||||||
$(SRC_DIR)/globals.c \
|
$(SRC_DIR)/globals.c \
|
||||||
$(SRC_DIR)/network.c $(SRC_DIR)/record.c $(SRC_DIR)/gui.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
|
$(SRC_DIR)/gui_menu.c
|
||||||
|
|
||||||
# 目标文件 (src/xxx.c -> obj/xxx.o)
|
# 目标文件 (src/xxx.c -> obj/xxx.o)
|
||||||
|
|||||||
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,40 @@
|
|||||||
|
#ifndef GUI_INTERNAL_H
|
||||||
|
#define GUI_INTERNAL_H
|
||||||
|
|
||||||
|
#include <iup.h>
|
||||||
|
|
||||||
|
// 全局变量声明 (在 gui_core.c 中定义)
|
||||||
|
extern Ihandle *dlg;
|
||||||
|
extern Ihandle *board_canvas;
|
||||||
|
extern Ihandle *lbl_player;
|
||||||
|
extern Ihandle *lbl_status;
|
||||||
|
extern int gui_loop_running;
|
||||||
|
extern int gui_game_mode; // 0: PvP, 1: PvE, 2: Replay
|
||||||
|
extern int replay_total_steps; // 复盘总步数
|
||||||
|
|
||||||
|
// 绘图函数 (在 gui_draw.c 中定义)
|
||||||
|
void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b);
|
||||||
|
void draw_board_iup(Ihandle *ih);
|
||||||
|
void draw_stones_iup(Ihandle *ih);
|
||||||
|
|
||||||
|
// 核心功能 (在 gui_core.c 中定义)
|
||||||
|
void update_ui_labels();
|
||||||
|
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y);
|
||||||
|
|
||||||
|
// 游戏窗口 (在 gui_game.c 中定义)
|
||||||
|
void create_game_window();
|
||||||
|
int action_cb(Ihandle *ih);
|
||||||
|
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status);
|
||||||
|
int k_any_cb(Ihandle *ih, int c);
|
||||||
|
int btn_back_cb(Ihandle *ih);
|
||||||
|
int btn_undo_cb(Ihandle *ih);
|
||||||
|
int btn_save_cb(Ihandle *ih);
|
||||||
|
|
||||||
|
// 复盘功能 (在 gui_replay.c 中定义)
|
||||||
|
void select_replay_file_gui();
|
||||||
|
int btn_replay_prev_cb(Ihandle *ih);
|
||||||
|
int btn_replay_next_cb(Ihandle *ih);
|
||||||
|
int btn_replay_sel_ok_cb(Ihandle *ih);
|
||||||
|
int btn_replay_sel_cancel_cb(Ihandle *ih);
|
||||||
|
|
||||||
|
#endif // GUI_INTERNAL_H
|
||||||
@@ -1,845 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file gui.c
|
|
||||||
* @brief 图形化用户界面实现文件
|
|
||||||
* @note 使用IUP库实现五子棋的图形化界面
|
|
||||||
* @author 刘航宇
|
|
||||||
* @date 2025-01-15
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "gui.h"
|
|
||||||
#include <iup.h>
|
|
||||||
#include <iupdraw.h>
|
|
||||||
#include "globals.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 *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; // 为了复盘而记录的总步数
|
|
||||||
|
|
||||||
// 回调函数
|
|
||||||
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 int btn_replay_sel_ok_cb(Ihandle *ih);
|
|
||||||
static int btn_replay_sel_cancel_cb(Ihandle *ih);
|
|
||||||
|
|
||||||
// 辅助函数:设置绘图颜色
|
|
||||||
static void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b)
|
|
||||||
{
|
|
||||||
char color[32];
|
|
||||||
sprintf(color, "%d %d %d", r, g, b);
|
|
||||||
IupSetAttribute(ih, "DRAWCOLOR", color);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制棋盘
|
|
||||||
static void draw_board_iup(Ihandle *ih)
|
|
||||||
{
|
|
||||||
set_draw_color(ih, 0, 0, 0); // Black
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "STROKE");
|
|
||||||
|
|
||||||
for (int i = 0; i < BOARD_SIZE; i++)
|
|
||||||
{
|
|
||||||
// 横线
|
|
||||||
IupDrawLine(ih,
|
|
||||||
BOARD_OFFSET_X,
|
|
||||||
BOARD_OFFSET_Y + i * CELL_SIZE,
|
|
||||||
BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE,
|
|
||||||
BOARD_OFFSET_Y + i * CELL_SIZE);
|
|
||||||
// 竖线
|
|
||||||
IupDrawLine(ih,
|
|
||||||
BOARD_OFFSET_X + i * CELL_SIZE,
|
|
||||||
BOARD_OFFSET_Y,
|
|
||||||
BOARD_OFFSET_X + i * CELL_SIZE,
|
|
||||||
BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 星位/天元
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
|
||||||
int stars[] = {3, 7, 11}; // 15路棋盘的星位坐标
|
|
||||||
if (BOARD_SIZE == 15)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < 3; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < 3; j++)
|
|
||||||
{
|
|
||||||
int cx = BOARD_OFFSET_X + stars[i] * CELL_SIZE;
|
|
||||||
int cy = BOARD_OFFSET_Y + stars[j] * CELL_SIZE;
|
|
||||||
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int center = BOARD_SIZE / 2;
|
|
||||||
int cx = BOARD_OFFSET_X + center * CELL_SIZE;
|
|
||||||
int cy = BOARD_OFFSET_Y + center * CELL_SIZE;
|
|
||||||
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绘制棋子
|
|
||||||
static void draw_stones_iup(Ihandle *ih)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < BOARD_SIZE; i++)
|
|
||||||
{
|
|
||||||
for (int j = 0; j < BOARD_SIZE; j++)
|
|
||||||
{
|
|
||||||
if (board[i][j] != EMPTY)
|
|
||||||
{
|
|
||||||
int cx = BOARD_OFFSET_X + j * CELL_SIZE; // j是x坐标(列)
|
|
||||||
int cy = BOARD_OFFSET_Y + i * CELL_SIZE; // i是y坐标(行)
|
|
||||||
|
|
||||||
if (board[i][j] == PLAYER)
|
|
||||||
{
|
|
||||||
// 黑子
|
|
||||||
set_draw_color(ih, 0, 0, 0);
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
|
||||||
IupDrawArc(ih, cx - STONE_RADIUS, cy - STONE_RADIUS, cx + STONE_RADIUS, cy + STONE_RADIUS, 0.0, 360.0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 白子
|
|
||||||
set_draw_color(ih, 255, 255, 255);
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
|
||||||
IupDrawArc(ih, cx - STONE_RADIUS, cy - STONE_RADIUS, cx + STONE_RADIUS, cy + STONE_RADIUS, 0.0, 360.0);
|
|
||||||
|
|
||||||
set_draw_color(ih, 0, 0, 0);
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "STROKE");
|
|
||||||
IupDrawArc(ih, cx - STONE_RADIUS, cy - STONE_RADIUS, cx + STONE_RADIUS, cy + STONE_RADIUS, 0.0, 360.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记最后落子位置 (红色小点)
|
|
||||||
if (step_count > 0 && step_count <= MAX_STEPS)
|
|
||||||
{
|
|
||||||
// 绘制最后一步的标记
|
|
||||||
// 最后一步的坐标是 steps[step_count-1]
|
|
||||||
// 所以 step_count-1 是最后一步的索引
|
|
||||||
// 所以 steps[step_count-1] 是最后一步
|
|
||||||
|
|
||||||
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, 255, 0, 0);
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
|
||||||
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 屏幕坐标转棋盘坐标
|
|
||||||
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y)
|
|
||||||
{
|
|
||||||
int rel_x = screen_x - BOARD_OFFSET_X;
|
|
||||||
int rel_y = screen_y - BOARD_OFFSET_Y;
|
|
||||||
|
|
||||||
*board_x = (rel_y + CELL_SIZE / 2) / CELL_SIZE; // 注意:行号对应y
|
|
||||||
*board_y = (rel_x + CELL_SIZE / 2) / CELL_SIZE; // 列号对应x
|
|
||||||
|
|
||||||
return (*board_x >= 0 && *board_x < BOARD_SIZE &&
|
|
||||||
*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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 显示消息
|
|
||||||
*/
|
|
||||||
void show_message(const char *message)
|
|
||||||
{
|
|
||||||
strncpy(status_message, message, sizeof(status_message) - 1);
|
|
||||||
status_message[sizeof(status_message) - 1] = '\0';
|
|
||||||
update_ui_labels();
|
|
||||||
printf("%s\n", message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ACTION 回调:负责重绘
|
|
||||||
static int action_cb(Ihandle *ih)
|
|
||||||
{
|
|
||||||
IupDrawBegin(ih);
|
|
||||||
|
|
||||||
int w, h;
|
|
||||||
IupGetIntInt(ih, "DRAWSIZE", &w, &h);
|
|
||||||
|
|
||||||
set_draw_color(ih, 240, 217, 181); // 棋盘背景色 (木纹色近似)
|
|
||||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
|
||||||
IupDrawRectangle(ih, 0, 0, w, h);
|
|
||||||
|
|
||||||
draw_board_iup(ih);
|
|
||||||
draw_stones_iup(ih);
|
|
||||||
|
|
||||||
IupDrawEnd(ih);
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 悔棋按钮回调
|
|
||||||
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_count >= 0) // Ensure step_count is valid
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
printf("DEBUG: Back to Menu clicked\n");
|
|
||||||
|
|
||||||
// 1. Show main menu FIRST
|
|
||||||
show_main_menu();
|
|
||||||
printf("DEBUG: Main menu shown\n");
|
|
||||||
|
|
||||||
// 2. Destroy game window
|
|
||||||
// Important: We must destroy the game window to free resources and potentially stop its event loop contribution.
|
|
||||||
// But we must NOT close the application.
|
|
||||||
// If we only Hide, it stays in memory.
|
|
||||||
// If we Destroy, it's gone.
|
|
||||||
|
|
||||||
if (dlg)
|
|
||||||
{
|
|
||||||
// IupHide(dlg); // Hiding is safer if Destroy causes loop exit
|
|
||||||
// printf("DEBUG: Hiding game window\n");
|
|
||||||
Ihandle *old_dlg = dlg;
|
|
||||||
dlg = NULL; // Clear global pointer first
|
|
||||||
IupDestroy(old_dlg);
|
|
||||||
printf("DEBUG: Destroyed game window\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return IUP_IGNORE; // Return IUP_IGNORE to prevent default processing (like closing if CLOSE_CB)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 鼠标点击回调
|
|
||||||
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)
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
|
|
||||||
int board_x, board_y;
|
|
||||||
if (screen_to_board(x, y, &board_x, &board_y))
|
|
||||||
{
|
|
||||||
if (have_space(board_x, board_y))
|
|
||||||
{
|
|
||||||
// 执行落子操作
|
|
||||||
if (player_move(board_x, board_y, current_player_gui))
|
|
||||||
{
|
|
||||||
// 检查是否获胜
|
|
||||||
if (check_win(board_x, board_y, current_player_gui))
|
|
||||||
{
|
|
||||||
game_over = 1;
|
|
||||||
if (current_player_gui == PLAYER)
|
|
||||||
{
|
|
||||||
sprintf(status_message, "黑子获胜!");
|
|
||||||
IupMessage("游戏结束", "黑子获胜!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sprintf(status_message, "白子获胜!");
|
|
||||||
IupMessage("游戏结束", "白子获胜!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (gui_game_mode == 0) // PvP
|
|
||||||
{
|
|
||||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER; // AI 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, "无效位置!");
|
|
||||||
update_ui_labels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 键盘回调
|
|
||||||
static int k_any_cb(Ihandle *ih, int c)
|
|
||||||
{
|
|
||||||
(void)ih;
|
|
||||||
if (c == K_ESC)
|
|
||||||
{
|
|
||||||
if (dlg && IupGetInt(dlg, "VISIBLE"))
|
|
||||||
{
|
|
||||||
IupHide(dlg);
|
|
||||||
show_main_menu();
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建游戏窗口
|
|
||||||
void create_game_window()
|
|
||||||
{
|
|
||||||
printf("DEBUG: create_game_window start\n");
|
|
||||||
// if (dlg)
|
|
||||||
// {
|
|
||||||
// IupDestroy(dlg); // 销毁旧窗口
|
|
||||||
// dlg = NULL;
|
|
||||||
// }
|
|
||||||
// Only destroy if it exists and is not the current dialog?
|
|
||||||
// Actually, creating a new dialog while old one is valid is fine, but we should destroy old one to save memory.
|
|
||||||
// However, if destroying dlg causes main loop to exit because it was the last visible window (if main menu was hidden)...
|
|
||||||
|
|
||||||
if (dlg)
|
|
||||||
{
|
|
||||||
IupDestroy(dlg);
|
|
||||||
dlg = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建Canvas (Board)
|
|
||||||
board_canvas = IupCanvas(NULL);
|
|
||||||
if (!board_canvas)
|
|
||||||
printf("ERROR: Failed to create board_canvas\n");
|
|
||||||
|
|
||||||
IupSetAttribute(board_canvas, "ACTION", "action_cb");
|
|
||||||
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
|
|
||||||
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
|
|
||||||
IupSetCallback(board_canvas, "K_ANY", (Icallback)k_any_cb);
|
|
||||||
|
|
||||||
//
|
|
||||||
int board_pixel_size = BOARD_SIZE * CELL_SIZE + BOARD_OFFSET_X * 2;
|
|
||||||
char size[32];
|
|
||||||
sprintf(size, "%dx%d", board_pixel_size, board_pixel_size);
|
|
||||||
IupSetAttribute(board_canvas, "RASTERSIZE", size);
|
|
||||||
IupSetAttribute(board_canvas, "EXPAND", "NO");
|
|
||||||
|
|
||||||
// 创建标签 (玩家信息和游戏状态)
|
|
||||||
lbl_player = IupLabel("当前玩家: 黑子");
|
|
||||||
// IupSetAttribute(lbl_player, "FONT", "SimHei, 14"); // Comment out potentially problematic font setting
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (!dlg)
|
|
||||||
printf("ERROR: Failed to create dialog\n");
|
|
||||||
|
|
||||||
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
|
|
||||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
|
||||||
|
|
||||||
IupMap(dlg);
|
|
||||||
printf("DEBUG: create_game_window end\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_pvp_game_gui()
|
|
||||||
{
|
|
||||||
gui_game_mode = 0;
|
|
||||||
empty_board();
|
|
||||||
current_player_gui = PLAYER;
|
|
||||||
game_over = 0;
|
|
||||||
|
|
||||||
create_game_window();
|
|
||||||
|
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
|
||||||
sprintf(status_message, "玩家对战模式 - 黑方先行");
|
|
||||||
update_ui_labels();
|
|
||||||
if (board_canvas)
|
|
||||||
IupUpdate(board_canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_pve_game_gui()
|
|
||||||
{
|
|
||||||
printf("DEBUG: start_pve_game_gui start\n");
|
|
||||||
gui_game_mode = 1;
|
|
||||||
// ai_difficulty is global
|
|
||||||
empty_board();
|
|
||||||
current_player_gui = PLAYER;
|
|
||||||
game_over = 0;
|
|
||||||
|
|
||||||
create_game_window();
|
|
||||||
printf("DEBUG: create_game_window returned\n");
|
|
||||||
|
|
||||||
if (dlg)
|
|
||||||
{
|
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
|
||||||
printf("DEBUG: IupShowXY called\n");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
printf("ERROR: dlg is NULL in start_pve_game_gui\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sprintf(status_message, "人机对战模式 - 玩家执黑先行");
|
|
||||||
update_ui_labels();
|
|
||||||
printf("DEBUG: update_ui_labels returned\n");
|
|
||||||
|
|
||||||
// Force initial draw
|
|
||||||
if (board_canvas)
|
|
||||||
{
|
|
||||||
IupUpdate(board_canvas);
|
|
||||||
// IupFlush(); // Comment out to check if this caused the crash
|
|
||||||
}
|
|
||||||
printf("DEBUG: start_pve_game_gui end\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// 选择复盘文件
|
|
||||||
static void select_replay_file_gui()
|
|
||||||
{
|
|
||||||
// List files in records/ directory
|
|
||||||
char record_files[100][100];
|
|
||||||
int file_count = 0;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
WIN32_FIND_DATA ffd;
|
|
||||||
HANDLE hFind = FindFirstFile("records\\*.csv", &ffd);
|
|
||||||
if (hFind != INVALID_HANDLE_VALUE)
|
|
||||||
{
|
|
||||||
do
|
|
||||||
{
|
|
||||||
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
||||||
{
|
|
||||||
strcpy(record_files[file_count++], ffd.cFileName);
|
|
||||||
if (file_count >= 100)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (FindNextFile(hFind, &ffd) != 0);
|
|
||||||
FindClose(hFind);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (file_count == 0)
|
|
||||||
{
|
|
||||||
IupMessage("提示", "未找到复盘记录文件 (records/*.csv)");
|
|
||||||
show_main_menu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建列表框
|
|
||||||
Ihandle *list = IupList(NULL);
|
|
||||||
IupSetAttribute(list, "EXPAND", "YES");
|
|
||||||
IupSetAttribute(list, "VISIBLELINES", "10");
|
|
||||||
|
|
||||||
for (int i = 0; i < file_count; i++)
|
|
||||||
{
|
|
||||||
IupSetAttributeId(list, "", i + 1, record_files[i]);
|
|
||||||
}
|
|
||||||
IupSetAttribute(list, "VALUE", "1");
|
|
||||||
|
|
||||||
// 创建确定和取消按钮
|
|
||||||
Ihandle *btn_ok = IupButton("确定", NULL);
|
|
||||||
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_replay_sel_ok_cb);
|
|
||||||
IupSetAttribute(btn_ok, "SIZE", "60x30");
|
|
||||||
|
|
||||||
Ihandle *btn_cancel = IupButton("取消", NULL);
|
|
||||||
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_replay_sel_cancel_cb);
|
|
||||||
IupSetAttribute(btn_cancel, "SIZE", "60x30");
|
|
||||||
|
|
||||||
Ihandle *vbox = IupVbox(
|
|
||||||
IupLabel("请选择复盘文件:"),
|
|
||||||
list,
|
|
||||||
IupHbox(btn_ok, btn_cancel, NULL),
|
|
||||||
NULL);
|
|
||||||
IupSetAttribute(vbox, "MARGIN", "10x10");
|
|
||||||
IupSetAttribute(vbox, "GAP", "10");
|
|
||||||
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
|
||||||
|
|
||||||
Ihandle *dlg_sel = IupDialog(vbox);
|
|
||||||
IupSetAttribute(dlg_sel, "TITLE", "选择复盘文件");
|
|
||||||
IupSetAttribute(dlg_sel, "MINBOX", "NO");
|
|
||||||
IupSetAttribute(dlg_sel, "MAXBOX", "NO");
|
|
||||||
IupSetAttribute(dlg_sel, "RESIZE", "NO");
|
|
||||||
|
|
||||||
// 存储列表框句柄到对话框属性
|
|
||||||
IupSetAttribute(dlg_sel, "MY_LIST", (char *)list);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int btn_replay_sel_ok_cb(Ihandle *ih)
|
|
||||||
{
|
|
||||||
Ihandle *dlg = IupGetDialog(ih);
|
|
||||||
Ihandle *list = (Ihandle *)IupGetAttribute(dlg, "MY_LIST");
|
|
||||||
char *filename = IupGetAttribute(list, "VALUE"); // Returns index
|
|
||||||
int index = atoi(filename);
|
|
||||||
char *selected_file = IupGetAttributeId(list, "", index);
|
|
||||||
|
|
||||||
if (selected_file)
|
|
||||||
{
|
|
||||||
if (load_game_from_file(selected_file))
|
|
||||||
{
|
|
||||||
replay_total_steps = step_count;
|
|
||||||
step_count = 0;
|
|
||||||
gui_game_mode = 2; // Replay
|
|
||||||
create_game_window();
|
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER); // 展示
|
|
||||||
|
|
||||||
Ihandle *dlg_sel = dlg;
|
|
||||||
IupHide(dlg_sel);
|
|
||||||
IupDestroy(dlg_sel);
|
|
||||||
|
|
||||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
|
||||||
|
|
||||||
sprintf(status_message, "复盘模式 - %s", selected_file);
|
|
||||||
update_ui_labels();
|
|
||||||
if (board_canvas)
|
|
||||||
IupUpdate(board_canvas);
|
|
||||||
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
IupMessage("错误", "无法加载复盘文件");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IupHide(dlg);
|
|
||||||
IupDestroy(dlg);
|
|
||||||
show_main_menu();
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int btn_replay_sel_cancel_cb(Ihandle *ih)
|
|
||||||
{
|
|
||||||
Ihandle *dlg = IupGetDialog(ih);
|
|
||||||
IupHide(dlg);
|
|
||||||
IupDestroy(dlg);
|
|
||||||
show_main_menu();
|
|
||||||
return IUP_DEFAULT;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_replay_gui()
|
|
||||||
{
|
|
||||||
select_replay_file_gui();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化GUI
|
|
||||||
*/
|
|
||||||
int init_gui()
|
|
||||||
{
|
|
||||||
if (IupOpen(NULL, NULL) == IUP_ERROR)
|
|
||||||
{
|
|
||||||
printf("IupOpen failed\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 启用UTF-8模式,确保中文正常显示
|
|
||||||
IupSetGlobal("UTF8MODE", "YES");
|
|
||||||
|
|
||||||
create_main_menu();
|
|
||||||
show_main_menu();
|
|
||||||
|
|
||||||
gui_loop_running = 1;
|
|
||||||
|
|
||||||
printf("图形化界面初始化成功!(IUP)\n");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清理GUI资源
|
|
||||||
*/
|
|
||||||
void cleanup_gui()
|
|
||||||
{
|
|
||||||
if (dlg)
|
|
||||||
{
|
|
||||||
IupDestroy(dlg);
|
|
||||||
dlg = NULL;
|
|
||||||
}
|
|
||||||
IupClose();
|
|
||||||
printf("图形化界面已关闭\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 处理事件
|
|
||||||
*/
|
|
||||||
int handle_events()
|
|
||||||
{
|
|
||||||
if (!gui_loop_running)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
int ret = IupLoopStep();
|
|
||||||
if (ret == IUP_CLOSE)
|
|
||||||
{
|
|
||||||
gui_loop_running = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染游戏画面
|
|
||||||
*/
|
|
||||||
void render_game()
|
|
||||||
{
|
|
||||||
// 事件驱动,不需要手动渲染
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制棋盘 (保留空函数以兼容接口)
|
|
||||||
*/
|
|
||||||
void draw_board()
|
|
||||||
{
|
|
||||||
// Implemented in action_cb
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制棋子 (保留空函数以兼容接口)
|
|
||||||
*/
|
|
||||||
void draw_stones()
|
|
||||||
{
|
|
||||||
// Implemented in action_cb
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制UI元素 (保留空函数以兼容接口)
|
|
||||||
*/
|
|
||||||
void draw_ui_elements()
|
|
||||||
{
|
|
||||||
// Implemented in action_cb
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 运行图形化界面模式
|
|
||||||
* @note 包含初始化、主循环和清理
|
|
||||||
*/
|
|
||||||
void run_gui_mode()
|
|
||||||
{
|
|
||||||
if (init_gui() == 0)
|
|
||||||
{
|
|
||||||
IupMainLoop(); // 使用IUP的主循环
|
|
||||||
cleanup_gui();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
#include "gui.h"
|
||||||
|
#include "gui_internal.h"
|
||||||
|
#include "gui_menu.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include <iup.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
// 全局变量定义
|
||||||
|
Ihandle *dlg = NULL;
|
||||||
|
Ihandle *board_canvas = NULL;
|
||||||
|
Ihandle *lbl_player = NULL;
|
||||||
|
Ihandle *lbl_status = NULL;
|
||||||
|
int gui_loop_running = 0;
|
||||||
|
int gui_game_mode = 0; // 0: PvP, 1: PvE, 2: Replay
|
||||||
|
int replay_total_steps = 0; // 复盘总步数
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化GUI
|
||||||
|
*/
|
||||||
|
int init_gui()
|
||||||
|
{
|
||||||
|
if (IupOpen(NULL, NULL) == IUP_ERROR)
|
||||||
|
{
|
||||||
|
printf("IupOpen failed\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启用UTF-8模式,确保中文正常显示
|
||||||
|
IupSetGlobal("UTF8MODE", "YES");
|
||||||
|
|
||||||
|
create_main_menu();
|
||||||
|
show_main_menu();
|
||||||
|
|
||||||
|
gui_loop_running = 1;
|
||||||
|
|
||||||
|
printf("图形化界面初始化成功!(IUP)\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理GUI资源
|
||||||
|
*/
|
||||||
|
void cleanup_gui()
|
||||||
|
{
|
||||||
|
if (dlg)
|
||||||
|
{
|
||||||
|
IupDestroy(dlg);
|
||||||
|
dlg = NULL;
|
||||||
|
}
|
||||||
|
IupClose();
|
||||||
|
printf("图形化界面已关闭\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 运行图形化界面模式
|
||||||
|
* @note 包含初始化、主循环和清理
|
||||||
|
*/
|
||||||
|
void run_gui_mode()
|
||||||
|
{
|
||||||
|
if (init_gui() == 0)
|
||||||
|
{
|
||||||
|
IupMainLoop(); // 使用IUP的主循环
|
||||||
|
cleanup_gui();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 显示消息
|
||||||
|
*/
|
||||||
|
void show_message(const char *message)
|
||||||
|
{
|
||||||
|
strncpy(status_message, message, sizeof(status_message) - 1);
|
||||||
|
status_message[sizeof(status_message) - 1] = '\0';
|
||||||
|
update_ui_labels();
|
||||||
|
printf("%s\n", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新UI标签状态
|
||||||
|
*/
|
||||||
|
void update_ui_labels()
|
||||||
|
{
|
||||||
|
if (lbl_player)
|
||||||
|
{
|
||||||
|
if (gui_game_mode == 2) // 复盘模式
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 屏幕坐标转棋盘坐标
|
||||||
|
*/
|
||||||
|
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y)
|
||||||
|
{
|
||||||
|
int rel_x = screen_x - BOARD_OFFSET_X;
|
||||||
|
int rel_y = screen_y - BOARD_OFFSET_Y;
|
||||||
|
|
||||||
|
*board_x = (rel_y + CELL_SIZE / 2) / CELL_SIZE; // 注意:行号对应y
|
||||||
|
*board_y = (rel_x + CELL_SIZE / 2) / CELL_SIZE; // 列号对应x
|
||||||
|
|
||||||
|
return (*board_x >= 0 && *board_x < BOARD_SIZE &&
|
||||||
|
*board_y >= 0 && *board_y < BOARD_SIZE);
|
||||||
|
}
|
||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
#include "gui_internal.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gobang.h" // for BOARD_SIZE, etc.
|
||||||
|
#include <iup.h>
|
||||||
|
#include <iupdraw.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置绘图颜色
|
||||||
|
*/
|
||||||
|
void set_draw_color(Ihandle *ih, unsigned char r, unsigned char g, unsigned char b)
|
||||||
|
{
|
||||||
|
char color[32];
|
||||||
|
sprintf(color, "%d %d %d", r, g, b);
|
||||||
|
IupSetAttribute(ih, "DRAWCOLOR", color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制棋盘
|
||||||
|
*/
|
||||||
|
void draw_board_iup(Ihandle *ih)
|
||||||
|
{
|
||||||
|
set_draw_color(ih, 0, 0, 0); // 黑色
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "STROKE");
|
||||||
|
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
|
{
|
||||||
|
// 横线
|
||||||
|
IupDrawLine(ih,
|
||||||
|
BOARD_OFFSET_X,
|
||||||
|
BOARD_OFFSET_Y + i * CELL_SIZE,
|
||||||
|
BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE,
|
||||||
|
BOARD_OFFSET_Y + i * CELL_SIZE);
|
||||||
|
// 竖线
|
||||||
|
IupDrawLine(ih,
|
||||||
|
BOARD_OFFSET_X + i * CELL_SIZE,
|
||||||
|
BOARD_OFFSET_Y,
|
||||||
|
BOARD_OFFSET_X + i * CELL_SIZE,
|
||||||
|
BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 星位/天元
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||||
|
int stars[] = {3, 7, 11}; // 15路棋盘的星位坐标
|
||||||
|
if (BOARD_SIZE == 15)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < 3; j++)
|
||||||
|
{
|
||||||
|
int cx = BOARD_OFFSET_X + stars[i] * CELL_SIZE;
|
||||||
|
int cy = BOARD_OFFSET_Y + stars[j] * CELL_SIZE;
|
||||||
|
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int center = BOARD_SIZE / 2;
|
||||||
|
int cx = BOARD_OFFSET_X + center * CELL_SIZE;
|
||||||
|
int cy = BOARD_OFFSET_Y + center * CELL_SIZE;
|
||||||
|
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制棋子
|
||||||
|
*/
|
||||||
|
void draw_stones_iup(Ihandle *ih)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < BOARD_SIZE; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < BOARD_SIZE; j++)
|
||||||
|
{
|
||||||
|
if (board[i][j] != EMPTY)
|
||||||
|
{
|
||||||
|
int cx = BOARD_OFFSET_X + j * CELL_SIZE; // j是x坐标(列)
|
||||||
|
int cy = BOARD_OFFSET_Y + i * CELL_SIZE; // i是y坐标(行)
|
||||||
|
|
||||||
|
if (board[i][j] == PLAYER)
|
||||||
|
{
|
||||||
|
// 黑子
|
||||||
|
set_draw_color(ih, 0, 0, 0);
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||||
|
IupDrawArc(ih, cx - STONE_RADIUS, cy - STONE_RADIUS, cx + STONE_RADIUS, cy + STONE_RADIUS, 0.0, 360.0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 白子
|
||||||
|
set_draw_color(ih, 255, 255, 255);
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||||
|
IupDrawArc(ih, cx - STONE_RADIUS, cy - STONE_RADIUS, cx + STONE_RADIUS, cy + STONE_RADIUS, 0.0, 360.0);
|
||||||
|
|
||||||
|
set_draw_color(ih, 0, 0, 0);
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "STROKE");
|
||||||
|
IupDrawArc(ih, cx - STONE_RADIUS, cy - STONE_RADIUS, cx + STONE_RADIUS, cy + STONE_RADIUS, 0.0, 360.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记最后落子位置 (红色小点)
|
||||||
|
if (step_count > 0 && step_count <= MAX_STEPS)
|
||||||
|
{
|
||||||
|
// 绘制最后一步的标记
|
||||||
|
// 最后一步的坐标是 steps[step_count-1]
|
||||||
|
// 所以 step_count-1 是最后一步的索引
|
||||||
|
|
||||||
|
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, 255, 0, 0);
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||||
|
IupDrawRectangle(ih, cx - 3, cy - 3, cx + 3, cy + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
+398
@@ -0,0 +1,398 @@
|
|||||||
|
#include "gui.h"
|
||||||
|
#include "gui_internal.h"
|
||||||
|
#include "gui_menu.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gobang.h"
|
||||||
|
#include "ai.h"
|
||||||
|
#include "record.h"
|
||||||
|
#include <iup.h>
|
||||||
|
#include <iupdraw.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief ACTION 回调:负责重绘
|
||||||
|
*/
|
||||||
|
int action_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
IupDrawBegin(ih);
|
||||||
|
|
||||||
|
int w, h;
|
||||||
|
IupGetIntInt(ih, "DRAWSIZE", &w, &h);
|
||||||
|
|
||||||
|
set_draw_color(ih, 240, 217, 181); // 棋盘背景色 (木纹色近似)
|
||||||
|
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||||
|
IupDrawRectangle(ih, 0, 0, w, h);
|
||||||
|
|
||||||
|
draw_board_iup(ih);
|
||||||
|
draw_stones_iup(ih);
|
||||||
|
|
||||||
|
IupDrawEnd(ih);
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 悔棋按钮回调
|
||||||
|
*/
|
||||||
|
int btn_undo_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
(void)ih;
|
||||||
|
if (game_over)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
int steps_to_undo = 1;
|
||||||
|
if (gui_game_mode == 1) // PvE
|
||||||
|
{
|
||||||
|
steps_to_undo = 2; // 悔棋两步(玩家+AI)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step_count >= steps_to_undo)
|
||||||
|
{
|
||||||
|
return_move(steps_to_undo);
|
||||||
|
|
||||||
|
// 更新当前玩家
|
||||||
|
if (step_count % 2 == 0)
|
||||||
|
current_player_gui = PLAYER;
|
||||||
|
else
|
||||||
|
current_player_gui = AI; // or PLAYER2
|
||||||
|
|
||||||
|
sprintf(status_message, "已悔棋");
|
||||||
|
update_ui_labels();
|
||||||
|
IupUpdate(board_canvas);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "无法悔棋");
|
||||||
|
update_ui_labels();
|
||||||
|
}
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存按钮回调
|
||||||
|
*/
|
||||||
|
int btn_save_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
(void)ih;
|
||||||
|
|
||||||
|
Ihandle *file_dlg = IupFileDlg();
|
||||||
|
IupSetAttribute(file_dlg, "DIALOGTYPE", "SAVE");
|
||||||
|
IupSetAttribute(file_dlg, "TITLE", "保存游戏记录");
|
||||||
|
IupSetAttribute(file_dlg, "FILTER", "*.csv");
|
||||||
|
IupSetAttribute(file_dlg, "FILTERINFO", "CSV Files");
|
||||||
|
|
||||||
|
IupPopup(file_dlg, IUP_CENTER, IUP_CENTER);
|
||||||
|
|
||||||
|
if (IupGetInt(file_dlg, "STATUS") != -1)
|
||||||
|
{
|
||||||
|
char *filename = IupGetAttribute(file_dlg, "VALUE");
|
||||||
|
|
||||||
|
char *base_name = strrchr(filename, '\\');
|
||||||
|
if (!base_name)
|
||||||
|
base_name = strrchr(filename, '/');
|
||||||
|
if (base_name)
|
||||||
|
base_name++;
|
||||||
|
else
|
||||||
|
base_name = filename;
|
||||||
|
|
||||||
|
int mode = (gui_game_mode == 0) ? GAME_MODE_PVP : GAME_MODE_AI;
|
||||||
|
if (save_game_to_file(base_name, mode) == 0)
|
||||||
|
{
|
||||||
|
sprintf(status_message, "保存成功: %s", base_name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "保存失败");
|
||||||
|
}
|
||||||
|
update_ui_labels();
|
||||||
|
}
|
||||||
|
|
||||||
|
IupDestroy(file_dlg);
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 返回菜单回调
|
||||||
|
*/
|
||||||
|
int btn_back_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
(void)ih;
|
||||||
|
printf("DEBUG: Back to Menu clicked\n");
|
||||||
|
|
||||||
|
// 1. 先显示主菜单
|
||||||
|
show_main_menu();
|
||||||
|
printf("DEBUG: Main menu shown\n");
|
||||||
|
|
||||||
|
// 2. 销毁游戏窗口
|
||||||
|
if (dlg)
|
||||||
|
{
|
||||||
|
Ihandle *old_dlg = dlg;
|
||||||
|
dlg = NULL; // 先清除全局指针
|
||||||
|
IupDestroy(old_dlg);
|
||||||
|
printf("DEBUG: Destroyed game window\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return IUP_IGNORE; // 返回 IUP_IGNORE 以阻止默认处理
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 鼠标点击回调
|
||||||
|
*/
|
||||||
|
int button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||||
|
{
|
||||||
|
(void)status; // 未使用
|
||||||
|
if (gui_game_mode == 2)
|
||||||
|
return IUP_DEFAULT; // 复盘模式禁用点击
|
||||||
|
|
||||||
|
if (button == IUP_BUTTON1 && pressed)
|
||||||
|
{ // 左键按下
|
||||||
|
if (game_over)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
int board_x, board_y;
|
||||||
|
if (screen_to_board(x, y, &board_x, &board_y))
|
||||||
|
{
|
||||||
|
if (have_space(board_x, board_y))
|
||||||
|
{
|
||||||
|
// 执行落子操作
|
||||||
|
if (player_move(board_x, board_y, current_player_gui))
|
||||||
|
{
|
||||||
|
// 检查是否获胜
|
||||||
|
if (check_win(board_x, board_y, current_player_gui))
|
||||||
|
{
|
||||||
|
game_over = 1;
|
||||||
|
if (current_player_gui == PLAYER)
|
||||||
|
{
|
||||||
|
sprintf(status_message, "黑子获胜!");
|
||||||
|
IupMessage("游戏结束", "黑子获胜!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "白子获胜!");
|
||||||
|
IupMessage("游戏结束", "白子获胜!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (gui_game_mode == 0) // PvP
|
||||||
|
{
|
||||||
|
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER; // AI 在这里表示玩家2
|
||||||
|
if (current_player_gui == PLAYER)
|
||||||
|
sprintf(status_message, "轮到黑子");
|
||||||
|
else
|
||||||
|
sprintf(status_message, "轮到白子");
|
||||||
|
}
|
||||||
|
else // PvE
|
||||||
|
{
|
||||||
|
current_player_gui = AI;
|
||||||
|
sprintf(status_message, "AI思考中...");
|
||||||
|
update_ui_labels();
|
||||||
|
IupUpdate(ih); // 立即更新显示
|
||||||
|
IupFlush(); // 强制刷新事件队列
|
||||||
|
|
||||||
|
// AI 回合
|
||||||
|
ai_move(ai_difficulty);
|
||||||
|
|
||||||
|
Step last_step = steps[step_count - 1];
|
||||||
|
if (check_win(last_step.x, last_step.y, AI))
|
||||||
|
{
|
||||||
|
game_over = 1;
|
||||||
|
sprintf(status_message, "AI获胜!");
|
||||||
|
IupMessage("游戏结束", "AI获胜!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current_player_gui = PLAYER;
|
||||||
|
sprintf(status_message, "轮到玩家");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_ui_labels();
|
||||||
|
IupUpdate(ih); // 请求重绘
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sprintf(status_message, "无效位置!");
|
||||||
|
update_ui_labels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 键盘回调
|
||||||
|
*/
|
||||||
|
int k_any_cb(Ihandle *ih, int c)
|
||||||
|
{
|
||||||
|
(void)ih;
|
||||||
|
if (c == K_ESC)
|
||||||
|
{
|
||||||
|
if (dlg && IupGetInt(dlg, "VISIBLE"))
|
||||||
|
{
|
||||||
|
btn_back_cb(ih); // 调用返回菜单逻辑
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建游戏窗口
|
||||||
|
*/
|
||||||
|
void create_game_window()
|
||||||
|
{
|
||||||
|
printf("DEBUG: create_game_window start\n");
|
||||||
|
|
||||||
|
if (dlg)
|
||||||
|
{
|
||||||
|
IupDestroy(dlg);
|
||||||
|
dlg = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建Canvas (棋盘)
|
||||||
|
board_canvas = IupCanvas(NULL);
|
||||||
|
if (!board_canvas)
|
||||||
|
printf("ERROR: Failed to create board_canvas\n");
|
||||||
|
|
||||||
|
IupSetAttribute(board_canvas, "ACTION", "action_cb");
|
||||||
|
IupSetCallback(board_canvas, "ACTION", (Icallback)action_cb);
|
||||||
|
IupSetCallback(board_canvas, "BUTTON_CB", (Icallback)button_cb);
|
||||||
|
IupSetCallback(board_canvas, "K_ANY", (Icallback)k_any_cb);
|
||||||
|
|
||||||
|
// 计算棋盘像素大小
|
||||||
|
int board_pixel_size = BOARD_SIZE * CELL_SIZE + BOARD_OFFSET_X * 2;
|
||||||
|
char size[32];
|
||||||
|
sprintf(size, "%dx%d", board_pixel_size, board_pixel_size);
|
||||||
|
IupSetAttribute(board_canvas, "RASTERSIZE", size);
|
||||||
|
IupSetAttribute(board_canvas, "EXPAND", "NO");
|
||||||
|
|
||||||
|
// 创建标签 (玩家信息和游戏状态)
|
||||||
|
lbl_player = IupLabel("当前玩家: 黑子");
|
||||||
|
lbl_status = IupLabel("准备开始");
|
||||||
|
|
||||||
|
Ihandle *vbox_controls;
|
||||||
|
|
||||||
|
if (gui_game_mode == 2) // 复盘模式
|
||||||
|
{
|
||||||
|
Ihandle *btn_prev = IupButton("上一步 (Prev)", NULL);
|
||||||
|
IupSetCallback(btn_prev, "ACTION", (Icallback)btn_replay_prev_cb);
|
||||||
|
IupSetAttribute(btn_prev, "SIZE", "100x30");
|
||||||
|
|
||||||
|
Ihandle *btn_next = IupButton("下一步 (Next)", NULL);
|
||||||
|
IupSetCallback(btn_next, "ACTION", (Icallback)btn_replay_next_cb);
|
||||||
|
IupSetAttribute(btn_next, "SIZE", "100x30");
|
||||||
|
|
||||||
|
Ihandle *btn_back = IupButton("返回菜单", NULL);
|
||||||
|
IupSetCallback(btn_back, "ACTION", (Icallback)btn_back_cb);
|
||||||
|
IupSetAttribute(btn_back, "SIZE", "100x30");
|
||||||
|
|
||||||
|
vbox_controls = IupVbox(
|
||||||
|
lbl_player,
|
||||||
|
lbl_status,
|
||||||
|
IupLabel(NULL), // Spacer
|
||||||
|
btn_prev,
|
||||||
|
btn_next,
|
||||||
|
IupLabel(NULL), // Spacer
|
||||||
|
btn_back,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
else // 游戏模式 (PvP / PvE)
|
||||||
|
{
|
||||||
|
Ihandle *btn_undo = IupButton("悔棋 (Undo)", NULL);
|
||||||
|
IupSetCallback(btn_undo, "ACTION", (Icallback)btn_undo_cb);
|
||||||
|
IupSetAttribute(btn_undo, "SIZE", "100x30");
|
||||||
|
|
||||||
|
Ihandle *btn_save = IupButton("保存 (Save)", NULL);
|
||||||
|
IupSetCallback(btn_save, "ACTION", (Icallback)btn_save_cb);
|
||||||
|
IupSetAttribute(btn_save, "SIZE", "100x30");
|
||||||
|
|
||||||
|
Ihandle *btn_back = IupButton("返回菜单", NULL);
|
||||||
|
IupSetCallback(btn_back, "ACTION", (Icallback)btn_back_cb);
|
||||||
|
IupSetAttribute(btn_back, "SIZE", "100x30");
|
||||||
|
|
||||||
|
vbox_controls = IupVbox(
|
||||||
|
lbl_player,
|
||||||
|
lbl_status,
|
||||||
|
IupLabel(NULL), // Spacer
|
||||||
|
btn_undo,
|
||||||
|
btn_save,
|
||||||
|
IupLabel(NULL), // Spacer
|
||||||
|
btn_back,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
IupSetAttribute(vbox_controls, "GAP", "15");
|
||||||
|
IupSetAttribute(vbox_controls, "MARGIN", "10x10");
|
||||||
|
IupSetAttribute(vbox_controls, "ALIGNMENT", "ACENTER");
|
||||||
|
|
||||||
|
Ihandle *hbox_main = IupHbox(board_canvas, vbox_controls, NULL);
|
||||||
|
IupSetAttribute(hbox_main, "MARGIN", "10x10");
|
||||||
|
IupSetAttribute(hbox_main, "GAP", "10");
|
||||||
|
|
||||||
|
// 创建Dialog
|
||||||
|
dlg = IupDialog(hbox_main);
|
||||||
|
if (!dlg)
|
||||||
|
printf("ERROR: Failed to create dialog\n");
|
||||||
|
|
||||||
|
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
|
||||||
|
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||||
|
|
||||||
|
// 设置 CLOSE_CB 回调,确保点击X也能正确返回菜单
|
||||||
|
IupSetCallback(dlg, "CLOSE_CB", (Icallback)btn_back_cb);
|
||||||
|
|
||||||
|
printf("DEBUG: create_game_window end\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_pvp_game_gui()
|
||||||
|
{
|
||||||
|
gui_game_mode = 0;
|
||||||
|
empty_board();
|
||||||
|
current_player_gui = PLAYER;
|
||||||
|
game_over = 0;
|
||||||
|
|
||||||
|
create_game_window();
|
||||||
|
|
||||||
|
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||||
|
sprintf(status_message, "玩家对战模式 - 黑方先行");
|
||||||
|
update_ui_labels();
|
||||||
|
if (board_canvas)
|
||||||
|
IupUpdate(board_canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
void start_pve_game_gui()
|
||||||
|
{
|
||||||
|
printf("DEBUG: start_pve_game_gui start\n");
|
||||||
|
gui_game_mode = 1;
|
||||||
|
// ai_difficulty 是全局变量
|
||||||
|
empty_board();
|
||||||
|
current_player_gui = PLAYER;
|
||||||
|
game_over = 0;
|
||||||
|
|
||||||
|
create_game_window();
|
||||||
|
printf("DEBUG: create_game_window returned\n");
|
||||||
|
|
||||||
|
if (dlg)
|
||||||
|
{
|
||||||
|
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||||
|
printf("DEBUG: IupShowXY called\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("ERROR: dlg is NULL in start_pve_game_gui\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(status_message, "人机对战模式 - 玩家执黑先行");
|
||||||
|
update_ui_labels();
|
||||||
|
printf("DEBUG: update_ui_labels returned\n");
|
||||||
|
|
||||||
|
// 强制初始重绘
|
||||||
|
if (board_canvas)
|
||||||
|
{
|
||||||
|
IupUpdate(board_canvas);
|
||||||
|
}
|
||||||
|
printf("DEBUG: start_pve_game_gui end\n");
|
||||||
|
}
|
||||||
+3
-1
@@ -31,8 +31,10 @@ static int btn_pve_cb(Ihandle *ih)
|
|||||||
static int btn_replay_cb(Ihandle *ih)
|
static int btn_replay_cb(Ihandle *ih)
|
||||||
{
|
{
|
||||||
(void)ih;
|
(void)ih;
|
||||||
hide_main_menu();
|
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
|
||||||
return IUP_DEFAULT;
|
return IUP_DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#include "gui.h"
|
||||||
|
#include "gui_internal.h"
|
||||||
|
#include "gui_menu.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "gobang.h"
|
||||||
|
#include "record.h"
|
||||||
|
#include <iup.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 复盘功能函数
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复盘文件选择对话框的确定回调
|
||||||
|
*/
|
||||||
|
int btn_replay_sel_ok_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
Ihandle *dlg_sel = IupGetDialog(ih);
|
||||||
|
Ihandle *list = (Ihandle *)IupGetAttribute(dlg_sel, "MY_LIST");
|
||||||
|
char *val = IupGetAttribute(list, "VALUE"); // 返回选中项索引
|
||||||
|
if (!val)
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
|
||||||
|
int index = atoi(val);
|
||||||
|
char *selected_file = IupGetAttributeId(list, "", index);
|
||||||
|
|
||||||
|
if (selected_file)
|
||||||
|
{
|
||||||
|
printf("DEBUG: Loading file %s\n", selected_file);
|
||||||
|
if (load_game_from_file(selected_file))
|
||||||
|
{
|
||||||
|
printf("DEBUG: File loaded successfully\n");
|
||||||
|
replay_total_steps = step_count;
|
||||||
|
step_count = 0;
|
||||||
|
gui_game_mode = 2; // 复盘模式
|
||||||
|
|
||||||
|
// 关闭选择对话框
|
||||||
|
IupHide(dlg_sel);
|
||||||
|
|
||||||
|
// 启动游戏窗口
|
||||||
|
create_game_window();
|
||||||
|
IupShowXY(dlg, IUP_CENTER, IUP_CENTER); // 显示游戏窗口
|
||||||
|
|
||||||
|
sprintf(status_message, "复盘模式 - %s", selected_file);
|
||||||
|
update_ui_labels();
|
||||||
|
if (board_canvas)
|
||||||
|
IupUpdate(board_canvas);
|
||||||
|
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
IupMessage("错误", "无法加载复盘文件");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复盘文件选择对话框的取消回调
|
||||||
|
*/
|
||||||
|
int btn_replay_sel_cancel_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
Ihandle *dlg_sel = IupGetDialog(ih);
|
||||||
|
IupHide(dlg_sel);
|
||||||
|
IupDestroy(dlg_sel);
|
||||||
|
show_main_menu(); // 返回主菜单
|
||||||
|
return IUP_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 上一步按钮回调
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 下一步按钮回调
|
||||||
|
*/
|
||||||
|
int btn_replay_next_cb(Ihandle *ih)
|
||||||
|
{
|
||||||
|
(void)ih;
|
||||||
|
if (step_count < replay_total_steps && step_count >= 0) // 确保步数有效
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 选择复盘文件界面
|
||||||
|
*/
|
||||||
|
void select_replay_file_gui()
|
||||||
|
{
|
||||||
|
printf("DEBUG: select_replay_file_gui start\n");
|
||||||
|
// 列出 records/ 目录下的文件
|
||||||
|
char record_files[100][100];
|
||||||
|
int file_count = 0;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
WIN32_FIND_DATA ffd;
|
||||||
|
HANDLE hFind = FindFirstFile("records\\*", &ffd);
|
||||||
|
|
||||||
|
if (hFind != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
|
||||||
|
{
|
||||||
|
// 确保不溢出缓冲区
|
||||||
|
if (file_count < 100)
|
||||||
|
{
|
||||||
|
strncpy(record_files[file_count], ffd.cFileName, 99);
|
||||||
|
record_files[file_count][99] = '\0';
|
||||||
|
file_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (FindNextFile(hFind, &ffd) != 0);
|
||||||
|
FindClose(hFind);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
printf("DEBUG: Found %d files\n", file_count);
|
||||||
|
|
||||||
|
if (file_count == 0)
|
||||||
|
{
|
||||||
|
IupMessage("提示", "未找到复盘记录文件 (records/*)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建列表框
|
||||||
|
Ihandle *list = IupList(NULL);
|
||||||
|
if (!list)
|
||||||
|
{
|
||||||
|
printf("ERROR: Failed to create list\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IupSetAttribute(list, "EXPAND", "YES");
|
||||||
|
IupSetAttribute(list, "VISIBLELINES", "10");
|
||||||
|
|
||||||
|
for (int i = 0; i < file_count; i++)
|
||||||
|
{
|
||||||
|
IupSetAttributeId(list, "", i + 1, record_files[i]);
|
||||||
|
}
|
||||||
|
IupSetAttribute(list, "VALUE", "1"); // 默认选择第一项
|
||||||
|
|
||||||
|
// 创建确定和取消按钮
|
||||||
|
Ihandle *btn_ok = IupButton("确定", NULL);
|
||||||
|
IupSetCallback(btn_ok, "ACTION", (Icallback)btn_replay_sel_ok_cb);
|
||||||
|
IupSetAttribute(btn_ok, "SIZE", "60x30");
|
||||||
|
|
||||||
|
Ihandle *btn_cancel = IupButton("取消", NULL);
|
||||||
|
IupSetCallback(btn_cancel, "ACTION", (Icallback)btn_replay_sel_cancel_cb);
|
||||||
|
IupSetAttribute(btn_cancel, "SIZE", "60x30");
|
||||||
|
|
||||||
|
Ihandle *vbox = IupVbox(
|
||||||
|
IupLabel("请选择复盘文件:"),
|
||||||
|
list,
|
||||||
|
IupHbox(btn_ok, btn_cancel, NULL),
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
IupSetAttribute(vbox, "GAP", "10");
|
||||||
|
IupSetAttribute(vbox, "MARGIN", "10x10");
|
||||||
|
IupSetAttribute(vbox, "ALIGNMENT", "ACENTER");
|
||||||
|
|
||||||
|
Ihandle *dlg_sel = IupDialog(vbox);
|
||||||
|
if (!dlg_sel)
|
||||||
|
{
|
||||||
|
printf("ERROR: Failed to create selection dialog\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
IupSetAttribute(dlg_sel, "TITLE", "选择复盘文件");
|
||||||
|
IupSetAttribute(dlg_sel, "MINBOX", "NO");
|
||||||
|
IupSetAttribute(dlg_sel, "MAXBOX", "NO");
|
||||||
|
IupSetAttribute(dlg_sel, "RESIZE", "NO");
|
||||||
|
|
||||||
|
// 存储列表框句柄到对话框属性
|
||||||
|
IupSetAttribute(dlg_sel, "MY_LIST", (char *)list);
|
||||||
|
|
||||||
|
// 显示对话框 (模态)
|
||||||
|
IupPopup(dlg_sel, IUP_CENTER, IUP_CENTER);
|
||||||
|
// 对话框关闭后销毁
|
||||||
|
IupDestroy(dlg_sel);
|
||||||
|
printf("DEBUG: select_replay_file_gui end\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启动复盘GUI流程
|
||||||
|
*/
|
||||||
|
void start_replay_gui()
|
||||||
|
{
|
||||||
|
select_replay_file_gui();
|
||||||
|
// 不要在这里隐藏主菜单,等待文件选择完成
|
||||||
|
}
|
||||||
+51
-10
@@ -298,8 +298,22 @@ int load_game_from_file(const char *filename)
|
|||||||
// 读取游戏模式、棋盘大小和评分结果
|
// 读取游戏模式、棋盘大小和评分结果
|
||||||
int game_mode, size;
|
int game_mode, size;
|
||||||
|
|
||||||
|
// 读取数据行
|
||||||
|
if (fgets(buffer, sizeof(buffer), file) == NULL)
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 尝试读取新格式(包含胜负信息)
|
// 尝试读取新格式(包含胜负信息)
|
||||||
int read_count = fscanf(file, "%d,%d,%d,%d,%49s", &game_mode, &size, &player1_final_score, &player2_final_score, winner_info);
|
// Use sscanf instead of fscanf to handle line buffers safely
|
||||||
|
// format: mode,size,score1,score2,winner_info
|
||||||
|
// Note: winner_info might contain newlines if not stripped, but sscanf %s stops at whitespace.
|
||||||
|
// However, CSV usually doesn't have spaces unless in quotes.
|
||||||
|
// Our winner_info is like "Player 1 Wins" or "Draw".
|
||||||
|
// If we use %[^,\n] it reads until comma or newline.
|
||||||
|
|
||||||
|
int read_count = sscanf(buffer, "%d,%d,%d,%d,%49[^,\n]", &game_mode, &size, &player1_final_score, &player2_final_score, winner_info);
|
||||||
|
|
||||||
if (read_count == 4)
|
if (read_count == 4)
|
||||||
{
|
{
|
||||||
@@ -321,16 +335,39 @@ int load_game_from_file(const char *filename)
|
|||||||
if (size < 5 || size > MAX_BOARD_SIZE)
|
if (size < 5 || size > MAX_BOARD_SIZE)
|
||||||
{
|
{
|
||||||
fclose(file);
|
fclose(file);
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置评分已计算标志
|
// 设置评分已计算标志
|
||||||
scores_calculated = 1;
|
scores_calculated = 1;
|
||||||
|
|
||||||
// 跳过空行和表头行
|
// 跳过空行和表头行
|
||||||
fgets(buffer, sizeof(buffer), file); // 跳过换行
|
// The previous fgets consumed the line with data and its newline.
|
||||||
fgets(buffer, sizeof(buffer), file); // 跳过空行
|
// Next line should be empty or headers.
|
||||||
fgets(buffer, sizeof(buffer), file); // 跳过"步数,玩家,行坐标,列坐标"
|
|
||||||
|
// Check next line
|
||||||
|
if (fgets(buffer, sizeof(buffer), file) != NULL)
|
||||||
|
{
|
||||||
|
// If it's just a newline (empty line)
|
||||||
|
if (buffer[0] == '\n' || buffer[0] == '\r')
|
||||||
|
{
|
||||||
|
// Consume one more line for headers
|
||||||
|
fgets(buffer, sizeof(buffer), file);
|
||||||
|
}
|
||||||
|
else if (strncmp(buffer, "步数", 4) != 0)
|
||||||
|
{
|
||||||
|
// If it's not headers, maybe we consumed the empty line already?
|
||||||
|
// Let's be flexible. If it doesn't start with "步数", try reading one more.
|
||||||
|
if (fgets(buffer, sizeof(buffer), file) == NULL)
|
||||||
|
{
|
||||||
|
fclose(file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now buffer should contain headers "步数..." or we are ready to read data?
|
||||||
|
// Actually, let's just loop until we find a digit or EOF
|
||||||
|
|
||||||
// 初始化棋盘
|
// 初始化棋盘
|
||||||
BOARD_SIZE = size;
|
BOARD_SIZE = size;
|
||||||
@@ -339,12 +376,16 @@ int load_game_from_file(const char *filename)
|
|||||||
// 读取所有落子步骤
|
// 读取所有落子步骤
|
||||||
step_count = 0;
|
step_count = 0;
|
||||||
int step_num; // 用于存储步数,但不使用
|
int step_num; // 用于存储步数,但不使用
|
||||||
while (fscanf(file, "%d,%d,%d,%d", &step_num, &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 4)
|
|
||||||
|
while (fgets(buffer, sizeof(buffer), file) != NULL)
|
||||||
{
|
{
|
||||||
// 将1-based坐标转换为0-based坐标
|
if (sscanf(buffer, "%d,%d,%d,%d", &step_num, &steps[step_count].player, &steps[step_count].x, &steps[step_count].y) == 4)
|
||||||
steps[step_count].x--;
|
{
|
||||||
steps[step_count].y--;
|
// 将1-based坐标转换为0-based坐标
|
||||||
step_count++;
|
steps[step_count].x--;
|
||||||
|
steps[step_count].y--;
|
||||||
|
step_count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
|
|||||||
Reference in New Issue
Block a user