/** * @file gui.c * @brief 图形化用户界面实现文件 * @note 使用SDL3库实现五子棋的图形化界面 * @author 刘航宇 * @date 2025-01-15 */ #include "gui.h" #include "ui.h" #include "globals.h" #include "game_mode.h" #include "init_board.h" #include #include #include // 全局变量 SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; int gui_running = 1; int current_player_gui = PLAYER; int game_over = 0; char status_message[256] = "五子棋游戏 - 黑子先行"; /** * @brief 初始化GUI * @return 成功返回0,失败返回-1 */ int init_gui() { if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("SDL初始化失败: %s\n", SDL_GetError()); return -1; } window = SDL_CreateWindow( "五子棋游戏 - SDL3版本", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_RESIZABLE ); // 设置窗口位置到屏幕中央 SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); if (!window) { printf("窗口创建失败: %s\n", SDL_GetError()); SDL_Quit(); return -1; } // 显示窗口 SDL_ShowWindow(window); renderer = SDL_CreateRenderer(window, NULL); if (!renderer) { printf("渲染器创建失败: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); return -1; } // 初始化游戏状态 // 初始化棋盘 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; printf("图形化界面初始化成功!\n"); printf("使用鼠标点击棋盘进行落子\n"); printf("按ESC键退出游戏\n"); return 0; } /** * @brief 清理GUI资源 */ void cleanup_gui() { if (renderer) { SDL_DestroyRenderer(renderer); renderer = NULL; } if (window) { SDL_DestroyWindow(window); window = NULL; } SDL_Quit(); printf("图形化界面已关闭\n"); } /** * @brief 渲染游戏画面 */ 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 处理事件 * @return 继续运行返回1,退出返回0 */ int handle_events() { SDL_Event event; while (SDL_PollEvent(&event)) { 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; } } return 1; } /** * @brief 绘制棋盘 */ 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); } } /** * @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); } } } } /** * @brief 绘制圆形 * @param center_x 圆心X坐标 * @param center_y 圆心Y坐标 * @param radius 半径 * @param color 颜色 */ 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元素 */ 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 */ 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); } /** * @brief 显示消息 * @param message 要显示的消息 */ void show_message(const char* message) { strncpy(status_message, message, sizeof(status_message) - 1); status_message[sizeof(status_message) - 1] = '\0'; printf("%s\n", message); }