mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-29 00:45:55 +08:00
feat(gui): 迁移图形界面库从SDL3到IUP
- 移除SDL3依赖,引入IUP图形界面库 - 更新Makefile以支持IUP编译配置 - 重构GUI模块,移除SDL相关代码 - 更新全局变量和类型定义,移除SDL依赖 - 添加IUP头文件到项目库目录 - 删除手动帧率控制(SDL_Delay),依赖IUP事件循环 - 更新编译脚本和文档说明
This commit is contained in:
@@ -744,7 +744,6 @@ void run_gui_mode()
|
||||
while (gui_running && handle_events())
|
||||
{
|
||||
render_game();
|
||||
SDL_Delay(16); // 约60FPS
|
||||
}
|
||||
printf("退出图形化界面\n");
|
||||
cleanup_gui();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
// ==================== 游戏核心变量定义 ====================
|
||||
int BOARD_SIZE = DEFAULT_BOARD_SIZE; // 实际使用的棋盘尺寸
|
||||
@@ -30,8 +29,6 @@ double defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT; // 防守系数
|
||||
NetworkGameState network_state = {0}; // 网络游戏状态
|
||||
|
||||
// ==================== GUI相关变量定义 ====================
|
||||
SDL_Window *window = NULL; // SDL窗口指针
|
||||
SDL_Renderer *renderer = NULL; // SDL渲染器指针
|
||||
int gui_running = 1; // GUI运行状态标志
|
||||
int current_player_gui = PLAYER; // GUI当前玩家
|
||||
int game_over = 0; // 游戏结束标志
|
||||
|
||||
@@ -1,69 +1,305 @@
|
||||
/**
|
||||
* @file gui.c
|
||||
* @brief 图形化用户界面实现文件
|
||||
* @note 使用SDL3库实现五子棋的图形化界面
|
||||
* @note 使用IUP库实现五子棋的图形化界面
|
||||
* @author 刘航宇
|
||||
* @date 2025-01-15
|
||||
*/
|
||||
|
||||
#include "gui.h"
|
||||
#include <iup.h>
|
||||
#include <iupdraw.h>
|
||||
#include "ui.h"
|
||||
#include "globals.h"
|
||||
#include "game_mode.h"
|
||||
#include "init_board.h"
|
||||
#include "gobang.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
static Ihandle *dlg = NULL;
|
||||
static Ihandle *canvas = NULL;
|
||||
static int gui_loop_running = 0;
|
||||
|
||||
// 辅助函数:设置绘图颜色
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制UI元素
|
||||
static void draw_ui_iup(Ihandle *ih)
|
||||
{
|
||||
int infoX = BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE + 20;
|
||||
int infoY = BOARD_OFFSET_Y;
|
||||
int infoW = 220;
|
||||
int infoH = 120;
|
||||
|
||||
// 背景
|
||||
set_draw_color(ih, 200, 200, 200); // Light Gray
|
||||
IupSetAttribute(ih, "DRAWSTYLE", "FILL");
|
||||
IupDrawRectangle(ih, infoX, infoY, infoX + infoW, infoY + infoH);
|
||||
|
||||
// 边框
|
||||
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);
|
||||
}
|
||||
|
||||
set_draw_color(ih, 0, 0, 0);
|
||||
IupDrawText(ih, status_message, -1, infoX + 10, infoY + 60, -1, -1);
|
||||
}
|
||||
|
||||
// 屏幕坐标转棋盘坐标
|
||||
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);
|
||||
}
|
||||
|
||||
// ACTION 回调:负责重绘
|
||||
static int action_cb(Ihandle *ih)
|
||||
{
|
||||
IupDrawBegin(ih);
|
||||
|
||||
int w, h;
|
||||
IupGetIntInt(ih, "DRAWSIZE", &w, &h);
|
||||
|
||||
set_draw_color(ih, 245, 245, 245); // Background (Raylib RAYWHITE approx)
|
||||
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 button_cb(Ihandle *ih, int button, int pressed, int x, int y, char *status)
|
||||
{
|
||||
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, "黑子获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "白子获胜!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 切换玩家
|
||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER;
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "轮到黑子");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "轮到白子");
|
||||
}
|
||||
}
|
||||
IupUpdate(ih); // 请求重绘
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "无效位置!");
|
||||
IupUpdate(ih);
|
||||
}
|
||||
}
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
// 键盘回调
|
||||
static int k_any_cb(Ihandle *ih, int c)
|
||||
{
|
||||
if (c == K_ESC)
|
||||
{
|
||||
gui_loop_running = 0;
|
||||
return IUP_CLOSE;
|
||||
}
|
||||
return IUP_DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化GUI
|
||||
* @details 初始化SDL3图形库和游戏界面组件:
|
||||
* - 初始化SDL视频子系统
|
||||
* - 创建游戏窗口(可调整大小)
|
||||
* - 创建SDL渲染器
|
||||
* - 初始化游戏状态和棋盘数据
|
||||
* @return 成功返回0,失败返回-1
|
||||
* @note 窗口标题为"五子棋游戏 - SDL3版本"
|
||||
* 窗口尺寸由WINDOW_WIDTH和WINDOW_HEIGHT定义
|
||||
* 失败时会自动清理已创建的资源
|
||||
*/
|
||||
int init_gui()
|
||||
{
|
||||
if (!SDL_Init(SDL_INIT_VIDEO))
|
||||
if (IupOpen(NULL, NULL) == IUP_ERROR)
|
||||
{
|
||||
printf("SDL初始化失败: %s\n", SDL_GetError());
|
||||
printf("IupOpen failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
window = SDL_CreateWindow(
|
||||
"五子棋游戏 - SDL3版本",
|
||||
WINDOW_WIDTH, WINDOW_HEIGHT,
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
// 启用UTF-8模式,确保中文正常显示
|
||||
IupSetGlobal("UTF8MODE", "YES");
|
||||
|
||||
// 设置窗口位置到屏幕中央
|
||||
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
// 创建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);
|
||||
|
||||
if (!window)
|
||||
{
|
||||
printf("窗口创建失败: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return -1;
|
||||
}
|
||||
// 设置Canvas大小
|
||||
char size[32];
|
||||
sprintf(size, "%dx%d", WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
IupSetAttribute(canvas, "RASTERSIZE", size);
|
||||
IupSetAttribute(canvas, "EXPAND", "NO");
|
||||
|
||||
// 显示窗口
|
||||
SDL_ShowWindow(window);
|
||||
// 创建Dialog
|
||||
dlg = IupDialog(canvas);
|
||||
IupSetAttribute(dlg, "TITLE", "五子棋 - IUP版本");
|
||||
IupSetAttribute(dlg, "RESIZE", "NO");
|
||||
|
||||
renderer = SDL_CreateRenderer(window, NULL);
|
||||
if (!renderer)
|
||||
{
|
||||
printf("渲染器创建失败: %s\n", SDL_GetError());
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
return -1;
|
||||
}
|
||||
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
|
||||
|
||||
// 初始化游戏状态
|
||||
// 初始化棋盘
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
@@ -73,8 +309,10 @@ int init_gui()
|
||||
}
|
||||
current_player_gui = PLAYER;
|
||||
game_over = 0;
|
||||
gui_loop_running = 1;
|
||||
sprintf(status_message, "游戏开始");
|
||||
|
||||
printf("图形化界面初始化成功!\n");
|
||||
printf("图形化界面初始化成功!(IUP)\n");
|
||||
printf("使用鼠标点击棋盘进行落子\n");
|
||||
printf("按ESC键退出游戏\n");
|
||||
|
||||
@@ -83,359 +321,89 @@ int init_gui()
|
||||
|
||||
/**
|
||||
* @brief 清理GUI资源
|
||||
* @details 按顺序释放所有SDL相关资源:
|
||||
* - 销毁SDL渲染器
|
||||
* - 销毁SDL窗口
|
||||
* - 退出SDL子系统
|
||||
* @note 函数会检查资源是否存在再进行释放
|
||||
* 释放后将指针设置为NULL防止重复释放
|
||||
* 程序退出时必须调用此函数避免内存泄漏
|
||||
*/
|
||||
void cleanup_gui()
|
||||
{
|
||||
if (renderer)
|
||||
if (dlg)
|
||||
{
|
||||
SDL_DestroyRenderer(renderer);
|
||||
renderer = NULL;
|
||||
IupDestroy(dlg);
|
||||
dlg = NULL;
|
||||
}
|
||||
if (window)
|
||||
{
|
||||
SDL_DestroyWindow(window);
|
||||
window = NULL;
|
||||
}
|
||||
SDL_Quit();
|
||||
IupClose();
|
||||
printf("图形化界面已关闭\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 渲染游戏画面
|
||||
* @details 完整的游戏画面渲染流程:
|
||||
* - 清空屏幕并设置背景色
|
||||
* - 绘制棋盘网格和标记点
|
||||
* - 绘制所有棋子
|
||||
* - 绘制UI界面元素
|
||||
* - 将渲染结果显示到屏幕
|
||||
* @note 使用双缓冲技术,通过SDL_RenderPresent显示最终结果
|
||||
* 背景色由GUI_COLOR_BACKGROUND定义
|
||||
* 每帧都会完全重绘整个画面
|
||||
*/
|
||||
void render_game()
|
||||
{
|
||||
// 清空屏幕 - 设置背景色
|
||||
SDL_Color bg_color = GUI_COLOR_BACKGROUND;
|
||||
SDL_SetRenderDrawColor(renderer, bg_color.r, bg_color.g, bg_color.b, bg_color.a);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// 绘制棋盘
|
||||
draw_board();
|
||||
|
||||
// 绘制棋子
|
||||
draw_stones();
|
||||
|
||||
// 绘制UI元素
|
||||
draw_ui_elements();
|
||||
|
||||
// 显示渲染结果
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理事件
|
||||
* @details 处理所有SDL事件并执行相应操作:
|
||||
* - SDL_EVENT_QUIT:用户关闭窗口
|
||||
* - SDL_EVENT_KEY_DOWN:键盘按键(ESC退出)
|
||||
* - SDL_EVENT_MOUSE_BUTTON_DOWN:鼠标点击落子
|
||||
* @return 继续运行返回1,退出返回0
|
||||
* @note 鼠标左键点击会转换为棋盘坐标并尝试落子
|
||||
* 落子后会检查胜负并切换玩家
|
||||
* 游戏结束后不再响应落子操作
|
||||
*/
|
||||
int handle_events()
|
||||
{
|
||||
SDL_Event event;
|
||||
if (!gui_loop_running)
|
||||
return 0;
|
||||
|
||||
while (SDL_PollEvent(&event))
|
||||
// 执行一次IUP循环迭代
|
||||
int ret = IupLoopStep();
|
||||
if (ret == IUP_CLOSE)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_EVENT_QUIT:
|
||||
gui_running = 0;
|
||||
return 0;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
if (event.key.key == SDLK_ESCAPE)
|
||||
{
|
||||
gui_running = 0;
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (event.button.button == SDL_BUTTON_LEFT && !game_over)
|
||||
{
|
||||
int board_x, board_y;
|
||||
if (screen_to_board(event.button.x, event.button.y, &board_x, &board_y))
|
||||
{
|
||||
if (have_space(board_x, board_y))
|
||||
{
|
||||
// 执行落子操作
|
||||
if (player_move(board_x, board_y, current_player_gui))
|
||||
{
|
||||
// 检查是否获胜
|
||||
if (check_win(board_x, board_y, current_player_gui))
|
||||
{
|
||||
game_over = 1;
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "游戏结束 - 黑子获胜!");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "游戏结束 - 白子获胜!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 切换玩家
|
||||
current_player_gui = (current_player_gui == PLAYER) ? AI : PLAYER;
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
sprintf(status_message, "轮到黑子下棋");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "轮到白子下棋");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sprintf(status_message, "该位置已有棋子,请选择其他位置");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
gui_loop_running = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 如果窗口被关闭(点X)
|
||||
// 注意:IupLoopStep会自动处理窗口关闭并返回IUP_CLOSE,除非定义了CLOSE_CB
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制棋盘
|
||||
* @details 绘制15x15的五子棋棋盘,包括:
|
||||
* - 横竖交叉的网格线
|
||||
* - 天元点(棋盘中心的标记点)
|
||||
* - 四个星位(棋盘上的定位点)
|
||||
* @note 使用SDL3渲染器绘制线条和填充矩形
|
||||
* 棋盘线条颜色由GUI_COLOR_BOARD_LINE定义
|
||||
* 天元点和星位用黑色小矩形标记
|
||||
* @brief 渲染游戏画面
|
||||
*/
|
||||
void render_game()
|
||||
{
|
||||
// 主动刷新Canvas
|
||||
// 注意:频繁调用IupUpdate可能会导致闪烁或高CPU占用,但在单线程循环模型中通常是必要的
|
||||
// 以确保动画或状态更新能及时反映。
|
||||
// 在我们的例子中,主要依靠 button_cb 触发 IupUpdate。
|
||||
// 但为了兼容原来的循环结构,我们可以保持这个函数。
|
||||
// IupUpdate(canvas);
|
||||
// 实际上不需要每一帧都 Update,只有状态变了才需要。
|
||||
// 原来的Raylib代码是每帧都重绘。为了性能,这里我们什么都不做,让事件驱动。
|
||||
// 除非有外部事件改变了游戏状态(比如网络消息),那时应该调用 IupUpdate。
|
||||
// 鉴于目前是本地PVP/AI,所有状态改变都在 button_cb 里,那里已经调用了 IupUpdate。
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制棋盘 (保留空函数以兼容接口,实际绘图在 action_cb 中)
|
||||
*/
|
||||
void draw_board()
|
||||
{
|
||||
SDL_Color line_color = GUI_COLOR_BOARD_LINE;
|
||||
SDL_SetRenderDrawColor(renderer, line_color.r, line_color.g, line_color.b, line_color.a);
|
||||
|
||||
// 绘制横线
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
int y = BOARD_OFFSET_Y + i * CELL_SIZE;
|
||||
SDL_RenderLine(renderer,
|
||||
BOARD_OFFSET_X, y,
|
||||
BOARD_OFFSET_X + (BOARD_SIZE - 1) * CELL_SIZE, y);
|
||||
}
|
||||
|
||||
// 绘制竖线
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
int x = BOARD_OFFSET_X + j * CELL_SIZE;
|
||||
SDL_RenderLine(renderer,
|
||||
x, BOARD_OFFSET_Y,
|
||||
x, BOARD_OFFSET_Y + (BOARD_SIZE - 1) * CELL_SIZE);
|
||||
}
|
||||
|
||||
// 绘制天元点和星位
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
int center = BOARD_SIZE / 2;
|
||||
|
||||
// 天元点
|
||||
int center_x = BOARD_OFFSET_X + center * CELL_SIZE;
|
||||
int center_y = BOARD_OFFSET_Y + center * CELL_SIZE;
|
||||
SDL_FRect center_rect = {center_x - 2, center_y - 2, 4, 4};
|
||||
SDL_RenderFillRect(renderer, ¢er_rect);
|
||||
|
||||
// 四个星位
|
||||
int star_offset = 3;
|
||||
int positions[][2] = {
|
||||
{center - star_offset, center - star_offset},
|
||||
{center + star_offset, center - star_offset},
|
||||
{center - star_offset, center + star_offset},
|
||||
{center + star_offset, center + star_offset}};
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int x = BOARD_OFFSET_X + positions[i][1] * CELL_SIZE;
|
||||
int y = BOARD_OFFSET_Y + positions[i][0] * CELL_SIZE;
|
||||
SDL_FRect star_rect = {x - 1, y - 1, 2, 2};
|
||||
SDL_RenderFillRect(renderer, &star_rect);
|
||||
}
|
||||
// Implemented in action_cb
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制棋子
|
||||
* @details 遍历整个棋盘数组,绘制所有已落下的棋子:
|
||||
* - 黑子:使用GUI_COLOR_BLACK_STONE颜色
|
||||
* - 白子:使用GUI_COLOR_WHITE_STONE颜色
|
||||
* - 每个棋子都有边框:使用GUI_COLOR_STONE_BORDER颜色
|
||||
* @note 棋子绘制为圆形,半径由STONE_RADIUS定义
|
||||
* 通过draw_circle函数实现圆形绘制
|
||||
* 棋子位置根据棋盘坐标和CELL_SIZE计算屏幕坐标
|
||||
* @brief 绘制棋子 (保留空函数以兼容接口)
|
||||
*/
|
||||
void draw_stones()
|
||||
{
|
||||
for (int i = 0; i < BOARD_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < BOARD_SIZE; j++)
|
||||
{
|
||||
if (board[i][j] != EMPTY)
|
||||
{
|
||||
int x = BOARD_OFFSET_X + j * CELL_SIZE;
|
||||
int y = BOARD_OFFSET_Y + i * CELL_SIZE;
|
||||
|
||||
// 设置棋子颜色
|
||||
SDL_Color stone_color, border_color;
|
||||
if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
|
||||
{
|
||||
stone_color = (SDL_Color)GUI_COLOR_BLACK_STONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
stone_color = (SDL_Color)GUI_COLOR_WHITE_STONE;
|
||||
}
|
||||
border_color = (SDL_Color)GUI_COLOR_STONE_BORDER;
|
||||
|
||||
// 绘制圆形棋子
|
||||
draw_circle(x, y, STONE_RADIUS, stone_color);
|
||||
draw_circle(x, y, STONE_RADIUS, border_color);
|
||||
|
||||
// 重新绘制内部
|
||||
draw_circle(x, y, STONE_RADIUS - 1, stone_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Implemented in action_cb
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制圆形
|
||||
* @param center_x 圆心X坐标
|
||||
* @param center_y 圆心Y坐标
|
||||
* @param radius 半径
|
||||
* @param color 颜色
|
||||
* @details 使用像素级绘制实现圆形:
|
||||
* - 遍历圆形外接矩形内的所有像素点
|
||||
* - 计算每个像素到圆心的距离
|
||||
* - 距离小于等于半径的像素点进行着色
|
||||
* @note 采用暴力算法,性能较低但实现简单
|
||||
* 适用于绘制棋子等小尺寸圆形
|
||||
* SDL3没有内置圆形绘制函数,需要自实现
|
||||
*/
|
||||
void draw_circle(int center_x, int center_y, int radius, SDL_Color color)
|
||||
{
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||
|
||||
for (int w = 0; w < radius * 2; w++)
|
||||
{
|
||||
for (int h = 0; h < radius * 2; h++)
|
||||
{
|
||||
int dx = radius - w;
|
||||
int dy = radius - h;
|
||||
if ((dx * dx + dy * dy) <= (radius * radius))
|
||||
{
|
||||
SDL_RenderPoint(renderer, center_x + dx, center_y + dy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绘制UI元素
|
||||
* @brief 绘制UI元素 (保留空函数以兼容接口)
|
||||
*/
|
||||
void draw_ui_elements()
|
||||
{
|
||||
// 绘制状态信息区域背景
|
||||
SDL_SetRenderDrawColor(renderer, 200, 200, 200, 255);
|
||||
SDL_FRect info_rect = {BOARD_OFFSET_X + BOARD_SIZE * CELL_SIZE + 20, BOARD_OFFSET_Y, 200, 100};
|
||||
SDL_RenderFillRect(renderer, &info_rect);
|
||||
|
||||
// 绘制边框
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderRect(renderer, &info_rect);
|
||||
|
||||
// 这里可以添加文字渲染,但SDL3需要额外的字体库
|
||||
// 暂时用简单的图形表示当前玩家
|
||||
int indicator_x = info_rect.x + 20;
|
||||
int indicator_y = info_rect.y + 20;
|
||||
|
||||
if (!game_over)
|
||||
{
|
||||
if (current_player_gui == PLAYER)
|
||||
{
|
||||
// 黑子回合
|
||||
draw_circle(indicator_x, indicator_y, 10, (SDL_Color){0, 0, 0, 255});
|
||||
}
|
||||
else
|
||||
{
|
||||
// 白子回合
|
||||
draw_circle(indicator_x, indicator_y, 10, (SDL_Color){255, 255, 255, 255});
|
||||
// 绘制当前玩家指示器(简单的矩形代替圆形)
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_FRect indicator_rect = {indicator_x - 10, indicator_y - 10, 20, 20};
|
||||
SDL_RenderFillRect(renderer, &indicator_rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 屏幕坐标转棋盘坐标
|
||||
* @param screen_x 屏幕X坐标
|
||||
* @param screen_y 屏幕Y坐标
|
||||
* @param board_x 输出棋盘X坐标
|
||||
* @param board_y 输出棋盘Y坐标
|
||||
* @return 转换成功返回1,失败返回0
|
||||
* @details 坐标转换算法:
|
||||
* - 减去棋盘偏移量得到相对坐标
|
||||
* - 加上半个格子尺寸实现就近取整
|
||||
* - 除以格子尺寸得到棋盘坐标
|
||||
* - 检查坐标是否在有效范围内
|
||||
* @note 使用就近取整算法,点击格子中心附近都会定位到该格子
|
||||
* 坐标范围检查确保不会越界访问棋盘数组
|
||||
*/
|
||||
int screen_to_board(int screen_x, int screen_y, int *board_x, int *board_y)
|
||||
{
|
||||
int rel_x = screen_x - BOARD_OFFSET_X;
|
||||
int rel_y = screen_y - BOARD_OFFSET_Y;
|
||||
|
||||
*board_x = (rel_x + CELL_SIZE / 2) / CELL_SIZE;
|
||||
*board_y = (rel_y + CELL_SIZE / 2) / CELL_SIZE;
|
||||
|
||||
return (*board_x >= 0 && *board_x < BOARD_SIZE &&
|
||||
*board_y >= 0 && *board_y < BOARD_SIZE);
|
||||
// Implemented in action_cb
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 显示消息
|
||||
* @param message 要显示的消息
|
||||
* @details 消息显示功能:
|
||||
* - 将消息复制到全局状态消息缓冲区
|
||||
* - 同时在控制台输出消息内容
|
||||
* - 确保字符串安全复制,防止缓冲区溢出
|
||||
* @note 消息会存储在status_message全局变量中
|
||||
* 字符串长度限制为缓冲区大小减1
|
||||
* 消息可用于游戏状态提示和错误信息显示
|
||||
*/
|
||||
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);
|
||||
printf("%s\n", message);
|
||||
}
|
||||
+4
-4
@@ -8,13 +8,13 @@
|
||||
* gcc -std=c17 -o gobang_console.exe *.c -lws2_32
|
||||
.\gobang_console.exe
|
||||
*
|
||||
* !图形化版本编译(需要SDL3):
|
||||
* gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\include -LD:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\lib -lSDL3 -lws2_32
|
||||
copy "D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32\bin\SDL3.dll" .
|
||||
* !图形化版本编译(需要IUP库):
|
||||
* gcc -std=c17 -o gobang_gui.exe *.c -ID:\settings\settings\libs\iup-3.31_Win64_dllw6_lib\include -LD:\settings\settings\libs\iup-3.31_Win64_dllw6_lib -liup -lws2_32
|
||||
copy "D:\settings\settings\libs\iup-3.31_Win64_dllw6_lib\iup.dll" .
|
||||
.\gobang_gui.exe
|
||||
*
|
||||
* @note gcc 为编译器,添加了-lws2_32链接Windows网络库
|
||||
* @note SDL3 的路径:D:\settings\SDL\SDL3-3.2.22\x86_64-w64-mingw32
|
||||
* @note IUP 的路径:D:\settings\settings\libs\iup-3.31_Win64_dllw6_lib
|
||||
* @brief & "D:\Program Files (x86)\NSIS\makensis.exe" "installer\\installer.nsi"
|
||||
* @brief & "D:\Program Files (x86)\Inno Setup 6\iscc.exe" installer\\installer.iss
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user