refactor: 重构项目为纯GUI版本并清理冗余代码

- 移除控制台版本相关代码,包括game_mode、ui、init_board等模块
- 将empty_board函数移至gobang.c核心模块
- 简化main.c仅保留GUI启动逻辑
- 更新Makefile仅构建GUI版本
- 清理过时文档和配置文件
- 优化GUI菜单和游戏窗口交互逻辑
- 添加AI难度配置支持
This commit is contained in:
2026-03-17 16:57:27 +08:00
parent 0baab8bec6
commit dd2b6fd903
30 changed files with 295 additions and 3402 deletions
-127
View File
@@ -5,8 +5,6 @@
*/
#include "config.h"
#include "ui.h"
#include "game_mode.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
@@ -131,129 +129,4 @@ void reset_to_default_config()
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (ai_difficulty - 1) * 0.1;
printf("已重置为默认配置\n");
}
/**
* @brief 显示当前配置
*/
void display_current_config()
{
printf("\n===== 当前游戏配置 =====\n");
printf("棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
printf("禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
printf("计时器: %s\n", use_timer ? "开启" : "关闭");
if (use_timer)
{
printf("时间限制: %d 分钟\n", time_limit / 60);
}
printf("网络端口: %d\n", network_port);
printf("网络超时: %d 毫秒\n", network_timeout);
printf("=====================\n");
}
/**
* @brief 配置棋盘大小
*/
void config_board_size()
{
printf("\n当前棋盘大小: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
int new_size = get_integer_input("请输入新的棋盘大小: ", MIN_BOARD_SIZE, MAX_BOARD_SIZE);
BOARD_SIZE = new_size;
printf("棋盘大小已设置为: %d x %d\n", BOARD_SIZE, BOARD_SIZE);
}
/**
* @brief 配置禁手规则
*/
void config_forbidden_moves()
{
printf("\n当前禁手规则: %s\n", use_forbidden_moves ? "开启" : "关闭");
int choice = get_integer_input("是否启用禁手规则?(1=开启, 0=关闭): ", 0, 1);
use_forbidden_moves = (choice != 0);
printf("禁手规则已%s\n", use_forbidden_moves ? "开启" : "关闭");
}
/**
* @brief 配置计时器
*/
void config_timer()
{
printf("\n当前计时器: %s\n", use_timer ? "开启" : "关闭");
int choice = get_integer_input("是否启用计时器?(1=开启, 0=关闭): ", 0, 1);
use_timer = choice;
if (use_timer)
{
int new_limit = get_integer_input("请输入时间限制(分钟): ", 1, 999);
time_limit = new_limit * 60; // 转换为秒数存储
printf("计时器已开启,时间限制: %d 分钟\n", time_limit / 60);
}
else
{
printf("计时器已关闭\n");
}
}
/**
* @brief 配置网络参数
*/
void config_network()
{
printf("\n===== 网络配置 =====\n");
printf("当前网络端口: %d\n", network_port);
printf("当前网络超时: %d 毫秒\n", network_timeout);
int new_port = get_integer_input("请输入新的网络端口: ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
network_port = new_port;
printf("网络端口已设置为: %d\n", network_port);
int new_timeout = get_integer_input("请输入网络超时时间(毫秒, 建议1000-10000): ", 1000, 60000);
network_timeout = new_timeout;
printf("网络超时已设置为: %d 毫秒\n", network_timeout);
}
/**
* @brief 配置管理主菜单
*/
void config_management_menu()
{
int choice;
while (1)
{
clear_screen();
display_settings_menu();
display_current_config();
choice = get_integer_input("请选择操作(0-5): ", 0, 5);
switch (choice)
{
case 1:
config_board_size();
break;
case 2:
config_forbidden_moves();
break;
case 3:
config_timer();
break;
case 4:
config_network();
break;
case 5:
printf("AI难度设置功能开发中...\n");
break;
case 0:
save_game_config();
return;
default:
printf("无效的选择!\n");
break;
}
pause_for_input("按任意键继续...");
}
}
-971
View File
@@ -1,971 +0,0 @@
/**
* @file game_mode.c
* @brief 五子棋游戏框架源文件
* @note 本文件定义了五子棋游戏的四种主要模式:
* 1. AI对战模式
* 2. 双人对战模式
* 3. 网络对战模式
* 4. 复盘模式
*/
#include "game_mode.h"
#include "init_board.h"
#include "gobang.h"
#include "ai.h"
#include "record.h"
#include "config.h"
#include "network.h"
#include "ui.h"
#include "gui.h"
#include "globals.h"
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <ctype.h>
// 全局变量现在在globals.c中定义
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <conio.h>
#endif
/**
* @brief 从用户获取整数输入
*
* @param prompt 提示信息
* @param min 最小值
* @param max 最大值
* @return int 用户输入的整数
*/
int get_integer_input(const char *prompt, int min, int max)
{
int value;
int result;
char ch;
while (1)
{
printf("%s", prompt);
result = scanf("%d", &value);
if (result == 1 && value >= min && value <= max)
{
// 清除输入缓冲区中剩余的字符
while ((ch = getchar()) != '\n' && ch != EOF)
;
return value;
}
else
{
// 清除无效输入
while ((ch = getchar()) != '\n' && ch != EOF)
;
printf("输入无效,请输入一个介于 %d 和 %d 之间的整数。\n", min, max);
}
}
}
/**
* @brief 处理玩家回合
*
* @param current_player
* @return true
* @return false
*/
bool parse_player_input(int *x, int *y)
{
char input[10];
while (1)
{
if (_kbhit())
{
scanf("%s", input);
break;
}
Sleep(100); // 短暂延迟以防止CPU占用过高
}
if (sscanf(input, "%d", x) == 1)
{
// 成功解析第一个数字,现在解析第二个
if (scanf("%d", y) != 1)
{
// 如果第二个数字不可用,则检查特殊命令
if (*x == INPUT_UNDO)
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
if (return_move(steps_to_undo * 2))
{
printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
}
else
{
printf("无法悔棋!\n");
}
return 0; // 特殊命令已处理
}
else if (*x == INPUT_SAVE)
{
// ... 处理保存 ...
return false; // 特殊命令
}
else if (*x == INPUT_EXIT)
{
// ... 处理退出 ...
return false; // 特殊命令
}
printf("无效输入,请输入两个数字坐标。\n");
while (getchar() != '\n')
;
return 0; // 无效输入
}
}
else
{
// sscanf失败,检查特殊命令
if (input[0] == 'r' || input[0] == 'R')
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
if (return_move(steps_to_undo * 2))
{
printf("成功悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
}
else
{
printf("无法悔棋!\n");
}
return false; // 特殊命令
}
else if (input[0] == 's' || input[0] == 'S')
{
*x = INPUT_SURRENDER;
int confirm = get_integer_input("确认认输?(1:是/0:否): ", 0, 1);
if (confirm)
{
printf("玩家选择认输!\n");
return 1; // 正常回合完成 // 返回认输命令
}
else
{
printf("取消认输!\n");
return false; // 取消认输
}
}
printf("无效输入,请输入数字坐标、'r'悔棋或's'认输。\n");
return 0; // 无效输入
}
return 1; // 有效坐标
}
/**
* @brief 解析网络对战模式下的玩家输入
* @param x 行坐标指针
* @param y 列坐标指针
* @return true 有效坐标输入
* @return false 特殊命令或无效输入
*/
bool parse_network_player_input(int *x, int *y)
{
char input[10];
while (1)
{
if (_kbhit())
{
scanf("%s", input);
break;
}
Sleep(100); // 短暂延迟以防止CPU占用过高
}
if (sscanf(input, "%d", x) == 1)
{
// 成功解析第一个数字,现在解析第二个
if (scanf("%d", y) != 1)
{
printf("无效输入,请输入两个数字坐标。\n");
while (getchar() != '\n')
;
return false; // 无效输入
}
}
else
{
// sscanf失败,检查特殊命令
if (input[0] == 'r' || input[0] == 'R')
{
int steps_to_undo;
steps_to_undo = get_integer_input("请输入要悔棋的步数(双方各退步数相同): ", 1, step_count / 2);
printf("发送悔棋请求给对方...\n");
if (send_undo_request(steps_to_undo))
{
printf("悔棋请求已发送,等待对方回应...\n");
// 等待对方回应
NetworkMessage msg;
time_t start_time = time(NULL);
while (difftime(time(NULL), start_time) < 30) // 30秒超时
{
if (receive_network_message(&msg, 1000))
{
if (msg.type == MSG_UNDO_RESPONSE && msg.x == steps_to_undo)
{
if (msg.y == 1) // 对方同意
{
if (return_move(steps_to_undo * 2))
{
printf("对方同意悔棋,双方各退 %d 步!\n", steps_to_undo);
print_board();
return 2; // 悔棋成功,需要重新开始回合
}
else
{
printf("悔棋失败!\n");
return 0; // 悔棋失败,继续当前回合
}
}
else // 对方拒绝
{
printf("对方拒绝了悔棋请求。\n");
return 0; // 悔棋被拒绝,继续当前回合
}
}
}
if (!is_network_connected())
{
printf("网络连接断开\n");
return 0;
}
}
printf("悔棋请求超时,对方未回应。\n");
}
else
{
printf("发送悔棋请求失败!\n");
}
return 0; // 特殊命令已处理
}
else if (input[0] == 's' || input[0] == 'S')
{
*x = INPUT_SURRENDER;
int confirm = get_integer_input("确认认输?(1:是/0:否): ", 0, 1);
if (confirm)
{
printf("你选择认输!\n");
*x = INPUT_SURRENDER;
return -1; // 返回认输命令
}
else
{
printf("取消认输!\n");
return 0; // 取消认输
}
}
printf("无效输入,请输入数字坐标、'r'悔棋或's'认输。\n");
return false; // 无效输入
}
return true; // 有效坐标
}
/**
* @brief 处理玩家回合
* @param current_player 当前玩家
* @return true
*/
bool handle_player_turn(int current_player)
{
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n玩家%d, 请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输:", current_player, BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\n玩家%d超时, 对方获胜!\n", current_player);
return false; // Timeout
}
}
if (parse_player_input(&x, &y))
{
if (x == INPUT_SURRENDER)
{
printf("\n玩家%d选择认输,对方获胜!\n", current_player);
return false; // 游戏结束,认输
}
input_received = true;
}
else
{
// 已处理特殊命令或无效输入,继续循环
return true;
}
}
x--;
y--;
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
}
else
{
printf("坐标无效!请重新输入。\n");
// 继续循环,重新输入坐标
}
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n玩家%d获胜!\n", current_player);
return false; // 游戏结束
}
return true; // 成功落子
}
/**
* @brief 运行AI游戏
* @note 从文件中加载历史记录并进行复盘
* @param AI_DEPTH AI的搜索深度
*/
void run_ai_game()
{
// 重置评分计算标志,确保每局游戏都会重新计算评分
scores_calculated = 0;
// AI对战模式
int AI_DEPTH = DEFAULT_AI_DEPTH;
AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5);
/**
* @brief AI的防守系数,系数越大越倾向于防守
* @note 1~1.5
* 2~1.6
* 3~1.7
* 4~1.8
* 5~1.9
*/
defense_coefficient = DEFAULT_DEFENSE_COEFFICIENT + (AI_DEPTH - 1) * 0.1;
empty_board();
int current_player = determine_first_player(PLAYER, AI);
print_board();
while (1)
{
if (current_player == PLAYER)
{
int old_step_count = step_count;
if (!handle_player_turn(current_player))
{
break; // 游戏结束或超时
}
if (step_count > old_step_count)
{
// 检查玩家是否获胜
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, PLAYER))
{
printf("\n玩家获胜!\n");
break;
}
current_player = AI;
}
}
else
{
printf("\nAI思考中...\n");
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
ai_move(AI_DEPTH);
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\nAI超时, 玩家获胜!\n");
break;
}
}
print_board();
Step last_step = steps[step_count - 1];
if (check_win(last_step.x, last_step.y, AI))
{
printf("\nAI获胜!\n");
break;
}
current_player = PLAYER;
}
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
break;
}
}
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_AI); // AI对战模式
handle_save_record(GAME_MODE_AI); // AI对战模式
}
/**
* @brief 运行双人对战模式
* @note 从文件中加载历史记录并进行复盘
*/
void run_pvp_game()
{
// 重置评分计算标志,确保每局游戏都会重新计算评分
scores_calculated = 0;
// 双人对战模式
empty_board();
int current_player = determine_first_player(PLAYER1, PLAYER2);
print_board();
while (1)
{
int old_step_count = step_count;
if (!handle_player_turn(current_player))
{
break; // 游戏结束或超时
}
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
break;
}
if (step_count > old_step_count)
{
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
}
}
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_PVP); // 双人对战模式
handle_save_record(GAME_MODE_PVP); // 双人对战模式
}
/**
* @brief 运行复盘模式
* @note 从文件中加载历史记录并进行复盘
*/
void run_review_mode()
{
char filename[100];
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))
{
strcpy(record_files[file_count++], ffd.cFileName);
}
} while (FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
#endif
if (file_count > 0)
{
printf("发现以下复盘文件:\n");
for (int i = 0; i < file_count; i++)
{
printf("%d. %s\n", i + 1, record_files[i]);
}
char prompt[150];
sprintf(prompt, "请输入复盘文件编号(1-%d),或输入0以手动输入文件名: ", file_count);
int choice = get_integer_input(prompt, 0, file_count);
if (choice > 0)
{
strcpy(filename, record_files[choice - 1]);
}
else
{
printf("请输入完整文件名: ");
scanf("%s", filename);
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
int possible_choice = atoi(filename);
if (possible_choice > 0 && possible_choice <= file_count)
{
strcpy(filename, record_files[possible_choice - 1]);
}
}
}
else
{
printf("未找到任何复盘文件,请输入复盘文件地址: ");
scanf("%s", filename);
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
}
int game_mode = load_game_from_file(filename);
if (game_mode != 0)
{
if (game_mode == 1)
{
printf("加载AI对战模式复盘文件成功!\n");
}
else if (game_mode == 2)
{
printf("加载双人对战模式复盘文件成功!\n");
}
review_process(game_mode);
}
else
{
printf("加载复盘文件失败!可能是旧版本文件格式或文件损坏\n");
}
}
/**
* @brief 网络对战模式
*/
void run_network_game()
{
// 重置评分计算标志
scores_calculated = 0;
// 初始化网络模块
if (!init_network())
{
printf("网络初始化失败!\n");
pause_for_input("按任意键返回主菜单...");
return;
}
printf("=== 网络对战模式 ===\n");
printf("1. 创建房间(作为主机)\n");
printf("2. 加入房间(连接到主机)\n");
int choice = get_integer_input("请选择模式(1-2): ", 1, 2);
bool connection_success = false;
if (choice == 1)
{
// 服务器模式
int port = get_integer_input("请输入监听端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0)
port = network_port;
printf("\n正在创建房间...\n");
connection_success = create_server(port);
}
else
{
// 客户端模式
char ip[MAX_IP_LENGTH];
// 循环直到输入有效的IP地址或用户选择退出
while (1)
{
printf("请输入服务器IP地址 (输入'exit'退出): ");
if (scanf("%s", ip) != 1)
{
printf("输入错误,请重新输入。\n");
// 清除输入缓冲区
while (getchar() != '\n')
;
continue;
}
// 检查是否要退出
if (strcmp(ip, "exit") == 0 || strcmp(ip, "EXIT") == 0)
{
printf("取消连接,返回主菜单。\n");
cleanup_network();
pause_for_input("按任意键返回主菜单...");
return;
}
// 简单的IP地址格式验证
if (strlen(ip) < 7 || strlen(ip) > 15)
{
printf("IP地址格式错误!请输入有效的IP地址(如:192.168.1.100\n");
continue;
}
// 检查IP地址是否包含有效字符
bool valid_ip = true;
for (int i = 0; i < strlen(ip); i++)
{
if (!(isdigit(ip[i]) || ip[i] == '.'))
{
valid_ip = false;
break;
}
}
if (!valid_ip)
{
printf("IP地址格式错误!只能包含数字和点号。\n");
continue;
}
// 检查点号数量
int dot_count = 0;
for (int i = 0; i < strlen(ip); i++)
{
if (ip[i] == '.')
dot_count++;
}
if (dot_count != 3)
{
printf("IP地址格式错误!应包含3个点号(如:192.168.1.100\n");
continue;
}
printf("输入的IP地址: %s\n", ip);
int confirm = get_integer_input("确认连接到此IP?(1:是/0:否,重新输入): ", 0, 1);
if (confirm)
{
break; // 确认IP地址,跳出循环
}
// 如果选择否,继续循环重新输入
}
int port = get_integer_input("请输入服务器端口(默认8888): ", MIN_NETWORK_PORT, MAX_NETWORK_PORT);
if (port == 0)
port = network_port;
printf("\n正在连接到服务器 %s:%d...\n", ip, port);
connection_success = connect_to_server(ip, port);
}
if (!connection_success)
{
printf("网络连接失败!\n");
cleanup_network();
pause_for_input("按任意键返回主菜单...");
return;
}
printf("\n网络连接成功!游戏即将开始...\n");
printf("你是玩家%d%s先手\n",
network_state.local_player_id,
network_state.local_player_id == PLAYER1 ? "" : "对方");
// 开始网络游戏
empty_board();
print_board();
if (network_game_loop())
{
printf("===== 游戏结束 =====\n");
review_process(GAME_MODE_NETWORK); // 网络对战模式的复盘
handle_save_record(GAME_MODE_NETWORK); // 保存为网络对战模式记录
}
else
{
printf("游戏因网络错误而结束\n");
}
// 清理网络连接
disconnect_network();
pause_for_input("按任意键返回主菜单...");
}
/**
* @brief 显示游戏规则
*/
void show_game_rules()
{
clear_screen();
display_game_rules();
pause_for_input("\n按任意键返回主菜单...");
}
/**
* @brief 显示关于游戏信息
*/
void show_about_game()
{
clear_screen();
display_about();
pause_for_input("\n按任意键返回主菜单...");
}
/**
* @brief 启动图形化界面
*/
void run_gui_mode()
{
if (init_gui() == 0)
{
printf("启动图形化界面...\n");
printf("图形化界面已启动,窗口应该可见\n");
printf("如果看不到窗口,请检查任务栏或按Alt+Tab切换\n");
while (gui_running && handle_events())
{
render_game();
}
printf("退出图形化界面\n");
cleanup_gui();
}
else
{
printf("图形化界面启动失败!请检查SDL3库是否正确安装。\n");
pause_for_input("按任意键返回主菜单...");
}
}
/**
* @brief 处理网络玩家回合
*/
bool handle_network_player_turn(int current_player, bool is_local_turn)
{
if (is_local_turn)
{
// 本地玩家回合
int x, y;
time_t start_time, end_time;
if (use_timer)
{
time(&start_time);
}
while (1)
{
printf("\n轮到你了,请输入落子坐标(行 列,1~%d),或输入R/r悔棋,S/s认输: ", BOARD_SIZE);
bool input_received = false;
while (!input_received)
{
if (use_timer)
{
time(&end_time);
if (difftime(end_time, start_time) > time_limit)
{
printf("\n你超时了,对方获胜!\n");
send_surrender(); // 发送认输消息
return 0; // 游戏结束
}
}
int parse_result = parse_network_player_input(&x, &y);
if (parse_result == 1) // 有效坐标输入
{
input_received = true;
}
else if (parse_result == -1) // 认输命令
{
printf("\n你选择认输,对方获胜!\n");
send_surrender();
return 0; // 游戏结束
}
else if (parse_result == 2) // 悔棋成功
{
return 2; // 悔棋发生,需要重新开始回合
}
else // parse_result == 0, 特殊命令已处理或无效输入
{
// 继续等待输入
continue;
}
}
x--;
y--; // 转换为0-based坐标
if (player_move(x, y, current_player))
{
break; // 成功落子,跳出循环
}
else
{
printf("坐标无效!请重新输入。\n");
// 继续循环,重新输入坐标
}
}
// 发送落子消息
if (!send_move(x, y, current_player))
{
printf("发送落子消息失败!\n");
return 0; // 游戏结束
}
print_board();
if (check_win(x, y, current_player))
{
printf("\n你获胜了!\n");
return 0; // 游戏结束
}
}
else
{
// 等待对方落子
printf("\n等待对方落子...\n");
NetworkMessage msg;
time_t start_time = time(NULL);
while (1)
{
if (receive_network_message(&msg, 1000))
{
// 1秒超时
if (msg.type == MSG_MOVE && msg.player_id == current_player)
{
// 收到落子消息
if (!player_move(msg.x, msg.y, current_player))
{
printf("收到无效的落子坐标!\n");
return 0; // 游戏结束
}
printf("对方落子: (%d, %d)\n", msg.x + 1, msg.y + 1);
print_board();
if (check_win(msg.x, msg.y, current_player))
{
printf("\n对方获胜!\n");
return 0; // 游戏结束
}
break;
}
else if (msg.type == MSG_SURRENDER)
{
printf("\n对方认输,你获胜了!\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_DISCONNECT)
{
printf("\n对方已断开连接\n");
return 0; // 游戏结束
}
else if (msg.type == MSG_CHAT)
{
printf("[对方]: %s\n", msg.message);
}
else if (msg.type == MSG_UNDO_REQUEST)
{
int steps = msg.x;
printf("\n对方请求悔棋 %d 步,是否同意?(1:同意/0:拒绝): ", steps);
int response = get_integer_input("", 0, 1);
if (response && return_move(steps * 2))
{
printf("同意悔棋,双方各退 %d 步\n", steps);
send_undo_response(true, steps);
print_board();
// 悔棋后需要重新开始当前回合,不改变current_player
return 2; // 悔棋发生,需要重新开始回合
}
else
{
printf("拒绝悔棋\n");
send_undo_response(false, steps);
// 继续等待对方落子
}
}
}
// 检查超时
if (use_timer && difftime(time(NULL), start_time) > time_limit)
{
printf("\n对方超时,你获胜!\n");
return 0; // 游戏结束
}
// 检查网络连接
if (!is_network_connected())
{
printf("\n网络连接断开\n");
return 0; // 游戏结束
}
}
}
return 1; // 正常回合完成
}
/**
* @brief 网络游戏主循环
*/
bool network_game_loop()
{
int current_player = PLAYER1; // 总是从玩家1开始
while (1)
{
bool is_local_turn = (current_player == network_state.local_player_id);
int turn_result = handle_network_player_turn(current_player, is_local_turn);
if (turn_result == 0) // 游戏结束
{
return true;
}
else if (turn_result == 2) // 悔棋发生,重新开始当前回合
{
continue; // 不切换玩家,重新开始当前回合
}
// 检查平局
if (step_count == BOARD_SIZE * BOARD_SIZE)
{
printf("\n平局!\n");
return true;
}
// 切换玩家
current_player = (current_player == PLAYER1) ? PLAYER2 : PLAYER1;
// 检查网络连接
if (!is_network_connected())
{
printf("\n网络连接断开\n");
return false;
}
}
return true;
}
+17 -2
View File
@@ -5,8 +5,6 @@
* 它包含了游戏棋盘的表示、玩家操作、规则检查以及AI决策等功能。
*/
#include "game_mode.h"
#include "init_board.h"
#include "gobang.h"
#include "ai.h"
#include "record.h"
@@ -16,6 +14,23 @@
#include <sys/stat.h>
#include <time.h>
/**
* @brief 初始化棋盘为全空状态并重置步数计数器
* 清空棋盘数组并将所有位置设为EMPTY,同时将step_count重置为0
*/
void empty_board()
{
// 初始化棋盘状态为全空
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
board[i][j] = EMPTY;
}
}
step_count = 0; // 重置步数计数器
}
/**
* @brief 检查棋盘(x, y)位置是否为空
* @param x 行坐标(0-base)
+194 -51
View File
@@ -9,9 +9,7 @@
#include "gui.h"
#include <iup.h>
#include <iupdraw.h>
#include "ui.h"
#include "globals.h"
#include "init_board.h"
#include "gobang.h"
#include "gui_menu.h"
#include "ai.h"
@@ -26,13 +24,15 @@ 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 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)
@@ -123,7 +123,7 @@ static void draw_stones_iup(Ihandle *ih)
}
// 标记最后落子位置 (红色小点)
if (step_count > 0)
if (step_count > 0 && step_count <= MAX_STEPS)
{
// 绘制最后一步的标记
// 最后一步的坐标是 steps[step_count-1]
@@ -179,6 +179,17 @@ static void update_ui_labels()
}
}
/**
* @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)
{
@@ -294,7 +305,7 @@ static int btn_replay_prev_cb(Ihandle *ih)
static int btn_replay_next_cb(Ihandle *ih)
{
(void)ih;
if (step_count < replay_total_steps)
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;
@@ -310,12 +321,29 @@ static int btn_replay_next_cb(Ihandle *ih)
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);
show_main_menu();
// 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_DEFAULT;
return IUP_IGNORE; // Return IUP_IGNORE to prevent default processing (like closing if CLOSE_CB)
}
// 鼠标点击回调
@@ -421,20 +449,33 @@ static int k_any_cb(Ihandle *ih, int c)
// 创建游戏窗口
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); // 销毁旧窗口
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);
@@ -443,10 +484,10 @@ void create_game_window()
// 创建标签 (玩家信息和游戏状态)
lbl_player = IupLabel("当前玩家: 黑子");
IupSetAttribute(lbl_player, "FONT", "SimHei, 14");
// IupSetAttribute(lbl_player, "FONT", "SimHei, 14"); // Comment out potentially problematic font setting
lbl_status = IupLabel("准备开始");
IupSetAttribute(lbl_status, "FONT", "SimHei, 12");
// IupSetAttribute(lbl_status, "FONT", "SimHei, 12");
Ihandle *vbox_controls;
@@ -509,10 +550,14 @@ void create_game_window()
// 创建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()
@@ -533,6 +578,7 @@ void start_pvp_game_gui()
void start_pve_game_gui()
{
printf("DEBUG: start_pve_game_gui start\n");
gui_game_mode = 1;
// ai_difficulty is global
empty_board();
@@ -540,67 +586,162 @@ void start_pve_game_gui()
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;
}
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
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");
}
void start_replay_gui()
#ifdef _WIN32
#include <windows.h>
#endif
// 选择复盘文件
static void select_replay_file_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");
// List files in records/ directory
char record_files[100][100];
int file_count = 0;
IupPopup(file_dlg, IUP_CENTER, IUP_CENTER);
if (IupGetInt(file_dlg, "STATUS") != -1)
#ifdef _WIN32
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile("records\\*.csv", &ffd);
if (hFind != INVALID_HANDLE_VALUE)
{
char *filename = IupGetAttribute(file_dlg, "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
char *base_name = strrchr(filename, '\\');
if (!base_name)
base_name = strrchr(filename, '/');
if (base_name)
base_name++;
else
base_name = filename;
if (file_count == 0)
{
IupMessage("提示", "未找到复盘记录文件 (records/*.csv)");
show_main_menu();
return;
}
if (load_game_from_file(base_name)) // returns game_mode (non-zero) on success
// 创建列表框
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;
// 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); // 展示
Ihandle *dlg_sel = dlg;
IupHide(dlg_sel);
IupDestroy(dlg_sel);
IupShowXY(dlg, IUP_CENTER, IUP_CENTER);
sprintf(status_message, "复盘模式 - %s", base_name);
sprintf(status_message, "复盘模式 - %s", selected_file);
update_ui_labels();
if (board_canvas)
IupUpdate(board_canvas);
return IUP_DEFAULT;
}
else
{
IupMessage("错误", "无法加载复盘文件");
show_main_menu();
}
}
else
{
show_main_menu();
}
IupDestroy(file_dlg);
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();
}
/**
@@ -691,12 +832,14 @@ void draw_ui_elements()
}
/**
* @brief 显示消息
* @brief 运行图形化界面模式
* @note 包含初始化、主循环和清理
*/
void show_message(const char *message)
void run_gui_mode()
{
strncpy(status_message, message, sizeof(status_message) - 1);
status_message[sizeof(status_message) - 1] = '\0';
update_ui_labels();
printf("%s\n", message);
}
if (init_gui() == 0)
{
IupMainLoop(); // 使用IUP的主循环
cleanup_gui();
}
}
+46 -28
View File
@@ -11,16 +11,20 @@ static Ihandle *menu_dlg = NULL;
static int btn_pvp_cb(Ihandle *ih)
{
(void)ih;
hide_main_menu();
printf("DEBUG: Starting PvP Game\n");
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
start_pvp_game_gui();
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
return IUP_DEFAULT;
}
static int btn_pve_cb(Ihandle *ih)
{
(void)ih;
hide_main_menu();
printf("DEBUG: Starting PvE Game\n");
// hide_main_menu(); // DO NOT HIDE MAIN MENU YET
start_pve_game_gui();
IupHide(menu_dlg); // Hide main menu manually AFTER game window created
return IUP_DEFAULT;
}
@@ -35,38 +39,44 @@ static int btn_replay_cb(Ihandle *ih)
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;
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) {
if (use_timer)
{
int minutes = IupGetInt(txt_time_limit, "VALUE");
if (minutes < 1) minutes = 1;
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;
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;
}
@@ -89,7 +99,7 @@ static int tgl_timer_cb(Ihandle *ih, int state)
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);
@@ -99,18 +109,18 @@ static int btn_settings_cb(Ihandle *ih)
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);
@@ -139,11 +149,11 @@ static int btn_settings_cb(Ihandle *ih)
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");
@@ -161,7 +171,7 @@ static int btn_settings_cb(Ihandle *ih)
IupSetAttribute(hbox_btns, "GAP", "20");
IupSetAttribute(hbox_btns, "MARGIN", "10x0");
IupSetAttribute(hbox_btns, "ALIGNMENT", "ACENTER");
Ihandle *vbox = IupVbox(
hbox_board,
tgl_forbidden,
@@ -171,19 +181,19 @@ static int btn_settings_cb(Ihandle *ih)
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;
}
@@ -197,7 +207,9 @@ static int btn_exit_cb(Ihandle *ih)
void create_main_menu()
{
if (menu_dlg) return;
if (menu_dlg)
return;
printf("DEBUG: create_main_menu\n");
Ihandle *lbl_title = IupLabel("五子棋 (Gobang)");
IupSetAttribute(lbl_title, "FONT", "SimHei, 24");
@@ -245,16 +257,22 @@ void create_main_menu()
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);
IupMap(menu_dlg); // Map immediately
}
void show_main_menu()
{
printf("DEBUG: show_main_menu start\n");
if (!menu_dlg)
{
printf("DEBUG: Creating main menu\n");
create_main_menu();
}
IupShowXY(menu_dlg, IUP_CENTER, IUP_CENTER);
printf("DEBUG: show_main_menu end\n");
}
void hide_main_menu()
-125
View File
@@ -1,125 +0,0 @@
/**
* @file init_board.c
* @brief 初始化游戏棋盘源文件
* @note 本文件定义了初始化游戏棋盘的相关函数。
* 它负责设置游戏的初始状态,包括棋盘大小、玩家标识、游戏规则等。
*/
#include "init_board.h"
#include "gobang.h"
#include "game_mode.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* @brief 初始化棋盘为全空状态并重置步数计数器
* 清空棋盘数组并将所有位置设为EMPTY,同时将step_count重置为0
*/
void empty_board()
{
// 初始化棋盘状态为全空
for (int i = 0; i < BOARD_SIZE; i++)
{
for (int j = 0; j < BOARD_SIZE; j++)
{
board[i][j] = EMPTY;
}
}
step_count = 0; // 重置步数计数器
}
/**
* @brief 打印当前棋盘状态
* 以可读格式输出棋盘,包括行列号和棋子状态
* 玩家棋子显示为'x'AI棋子显示为'○',空位显示为'·'
*/
void print_board()
{
// 打印列号(1-BOARD_SIZE显示)
printf("\n ");
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d", i + 1);
if (i + 1 == 9) // 处理列号9和10+的对齐
{
printf(" ");
}
}
printf("\n");
// 逐行打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d ", i + 1); // 打印行号(1-BOARD_SIZE
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == PLAYER)
{
printf("x "); // 玩家棋子
}
else if (board[i][j] == AI)
{
printf(""); // AI棋子(使用○显示)
}
else
{
printf("· "); // 空位
}
}
printf("\n"); // 每行结束换行
}
}
/**
* @brief 配置棋盘大小
*
* @param player1 玩家1
* @param player2 玩家2
*/
void setup_board_size()
{
printf("通常棋盘大小分为休闲棋盘(13X13)、标准棋盘(15X15)和特殊棋盘(19X19)\n");
char prompt[100];
sprintf(prompt, "请输入棋盘大小(5~%d)(默认为标准棋盘):\n", MAX_BOARD_SIZE);
BOARD_SIZE = get_integer_input(prompt, 5, MAX_BOARD_SIZE);
}
/**
* @brief Set the up game options object
* 配置游戏选项,包括禁手规则、计时器和时间限制
*/
void setup_game_options()
{
use_forbidden_moves = get_integer_input("是否启用禁手规则 (1-是, 0-否): ", 0, 1);
use_timer = get_integer_input("是否启用计时器 (1-是, 0-否): ", 0, 1);
if (use_timer)
{
time_limit = get_integer_input("请输入每回合的时间限制 (1~60分钟): ", 1, 60) * 60;
}
}
/**
* @brief 确定先手玩家
*
* @param player1
* @param player2
* @return int player1 or player2
*/
int determine_first_player(int player1, int player2)
{
char prompt[100];
sprintf(prompt, "请选择先手方 (1 for Player %d, 2 for Player %d): ", player1, player2);
int first_player_choice = get_integer_input(prompt, 1, 2);
if (first_player_choice == 1)
{
return player1;
}
else
{
return player2;
}
}
+3 -60
View File
@@ -4,22 +4,16 @@
* @note 本文件包含了游戏的主循环、模式选择和游戏初始化等功能
* @brief 将以下指令复制到powershell
*
* !控制台版本编译:
* mingw32-make console
.\bin\gobang_console.exe
*
* !图形化版本编译(需要IUP库):
* mingw32-make gui
.\bin\gobang_gui.exe
*
* @note gcc 为编译器,添加了-lws2_32链接Windows网络库
* @note IUP 的路径: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
*/
#include "game_mode.h"
#include "ui.h"
#include "gui.h"
#include "config.h"
#include <stdio.h>
#ifdef _WIN32
@@ -40,58 +34,7 @@ int main(int argc, char *argv[])
// 加载游戏配置
load_game_config();
// 选择模式
while (1)
{
clear_screen();
display_main_menu();
int mode = get_integer_input("请输入模式(0-8): ", 0, 8);
switch (mode)
{
// 1. 人机对战
case 1:
run_ai_game();
break;
// 2. 玩家对战
case 2:
run_pvp_game();
break;
// 3. 网络对战
case 3:
run_network_game();
break;
// 4. 复盘模式
case 4:
run_review_mode();
break;
// 5. 配置管理
case 5:
config_management_menu();
break;
// 6. 游戏规则
case 6:
show_game_rules();
break;
// 7. 关于游戏
case 7:
show_about_game();
break;
// 8. 图形化界面
case 8:
run_gui_mode();
break;
// 0. 退出游戏
case 0:
save_game_config();
printf("感谢使用五子棋游戏!\n");
return 0;
default:
printf("无效的选择!\n");
pause_for_input("按任意键继续...");
break;
}
}
// 启动图形化界面
run_gui_mode();
return 0;
}
+1 -185
View File
@@ -6,10 +6,7 @@
*/
#include "record.h"
#include "game_mode.h"
#include "gobang.h"
#include "init_board.h"
#include "ui.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
@@ -47,139 +44,7 @@
* - 包含输入缓冲区清理防止意外输入
* - 评分环节调用calculate_final_score()函数
*/
void review_process(int game_mode)
{
int review_choice = get_integer_input("是否要复盘本局比赛? (1-是, 0-否): ", 0, 1);
// 如果评分尚未计算,则计算评分
if (!scores_calculated)
{
calculate_game_scores();
}
else
{
// 评分已从文件中加载,直接使用
printf("从记录文件中加载评分数据\n");
}
if (review_choice == 1)
{
printf("\n===== 复盘记录(总步数:%d) =====\n", step_count);
// 清空输入缓冲区
int c;
while ((c = getchar()) != '\n' && c != EOF)
;
// 创建临时复盘棋盘
int temp_board[MAX_BOARD_SIZE][MAX_BOARD_SIZE];
memset(temp_board, EMPTY, sizeof(temp_board)); // 初始化为空棋盘
// 逐步重现游戏过程
for (int i = 0; i < step_count; i++)
{
Step s = steps[i]; // 获取当前步骤
temp_board[s.x][s.y] = s.player; // 在临时棋盘上落子
// 打印当前步骤信息
// 根据游戏模式显示不同的标题和玩家信息
if (game_mode == GAME_MODE_AI)
{
// 人机对战
printf("\n===== 五子棋人机对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER) ? "玩家" : "AI",
s.x + 1, s.y + 1);
}
else if (game_mode == GAME_MODE_PVP)
{
// 双人对战
printf("\n===== 五子棋双人对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER1) ? "玩家1(黑棋)" : "玩家2(白棋)",
s.x + 1, s.y + 1);
}
else if (game_mode == GAME_MODE_NETWORK)
{
// 网络对战
printf("\n===== 五子棋网络对战(%dX%d棋盘) =====", BOARD_SIZE, BOARD_SIZE);
printf("\n 第%d步/%d步: %s 落子于(%d, %d)\n",
i + 1, step_count,
(s.player == PLAYER1) ? "玩家1(黑棋)" : "玩家2(白棋)",
s.x + 1, s.y + 1);
}
// 打印当前复盘棋盘
printf(" ");
for (int col = 0; col < BOARD_SIZE; col++)
{
printf("%2d", col + 1); // 列号
}
printf("\n");
for (int row = 0; row < BOARD_SIZE; row++)
{
printf("%2d ", row + 1); // 行号
for (int col = 0; col < BOARD_SIZE; col++)
{
if (temp_board[row][col] == PLAYER || temp_board[row][col] == PLAYER1)
{
printf("x ");
}
else if (temp_board[row][col] == AI || temp_board[row][col] == PLAYER2)
{
printf("");
}
else
{
printf("· ");
}
}
printf("\n"); // 行结束换行
}
// 如果不是最后一步,等待用户按键继续
if (i < step_count - 1)
{
printf("\n按Enter继续下一步...");
while (getchar() != '\n')
; // 等待回车
}
}
// 显示胜负结果(直接使用文件中的信息)
printf("\n===== 对局结果 =====");
if (strcmp(winner_info, "玩家获胜") == 0)
{
printf("\n? 恭喜!玩家获胜!\n");
}
else if (strcmp(winner_info, "AI获胜") == 0)
{
printf("\n? AI获胜!\n");
}
else if (strcmp(winner_info, "玩家1获胜") == 0)
{
printf("\n? 恭喜!玩家1(黑棋)获胜!\n");
}
else if (strcmp(winner_info, "玩家2获胜") == 0)
{
printf("\n? 恭喜!玩家2(白棋)获胜!\n");
}
else
{
printf("\n?? 对局平局或未完成\n");
}
printf("\n复盘结束!按Enter查看评分...");
getchar(); // 等待用户按键
}
// 显示评分结果
display_game_scores(game_mode);
getchar();
}
void calculate_game_scores();
/**
* @brief 计算游戏评分
@@ -290,57 +155,8 @@ void display_game_scores(int game_mode)
}
}
/**
* @brief 处理游戏结束后的记录保存
* @return int 保存状态码(0-成功, 1-目录创建失败, 2-文件打开失败, 3-文件写入失败)
*/
void handle_save_record(int game_mode)
{
printf("===== 游戏结束 =====\n");
int save_choice = get_integer_input("是否保存游戏记录? (1-是, 0-否): ", 0, 1);
if (save_choice == 1)
{
time_t now = time(NULL);
struct tm *t = localtime(&now);
char filename[256];
strftime(filename, sizeof(filename), "%Y%m%d_%H%M%S.csv", t);
int save_status = save_game_to_file(filename, game_mode);
switch (save_status)
{
case 0: // 成功
printf("\n游戏记录已成功保存至: %s (CSV格式)\n", filename);
printf("您可以使用以下命令进行复盘: .\\gobang.exe -l %s\n", filename);
printf("CSV格式文件可以直接用Excel打开查看和分析\n");
break;
case 1: // 目录创建失败
printf("\n游戏记录保存失败: 无法创建 'records' 目录。\n");
printf("请检查程序是否具有足够的写入权限或磁盘空间是否充足。\n");
break;
case 2: // 文件打开失败
printf("\n游戏记录保存失败: 无法在路径 '%s' 创建文件。\n", filename);
printf("请检查路径是否有效以及程序是否具有写入权限。\n");
break;
case 3: // 文件写入失败
printf("\n游戏记录保存失败: 写入文件时发生错误。\n");
printf("请检查磁盘空间是否已满。\n");
break;
default:
printf("\n游戏记录保存失败: 发生未知错误。\n");
break;
}
}
}
/**
* @brief 将当前游戏记录保存到文件
* @param filename 要保存的文件名
* @return int 错误码:
* 0: 成功
* 1: 目录创建失败
* 2: 文件打开失败
* 3: 文件写入失败
*/
int save_game_to_file(const char *filename, int game_mode)
{
-212
View File
@@ -1,212 +0,0 @@
/**
* @file ui.c
* @brief
* @note 本文件定义了用户界面相关的函数和数据结构。
* 它负责处理用户输入、显示游戏界面、提示信息等与用户交互的功能。
*/
#include "ui.h"
#include "gobang.h"
#include "config.h"
#include "globals.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <unistd.h>
#endif
/**
* @brief 显示游戏主菜单
*/
void display_main_menu()
{
printf("===== 五子棋游戏 =====\n");
printf("1. AI模式\n");
printf("2. 玩家比赛\n");
printf("3. 网络对战\n");
printf("4. 复盘模式\n");
printf("5. 游戏设置\n");
printf("6. 游戏规则\n");
printf("7. 关于游戏\n");
printf("8. 图形化界面\n");
printf("0. 退出游戏\n");
printf("=====================\n");
}
/**
* @brief 显示棋盘
*/
void display_board()
{
printf("\n ");
// 打印列号
for (int j = 0; j < BOARD_SIZE; j++)
{
printf("%2d", j);
}
printf("\n");
// 打印棋盘内容
for (int i = 0; i < BOARD_SIZE; i++)
{
printf("%2d", i); // 打印行号
for (int j = 0; j < BOARD_SIZE; j++)
{
if (board[i][j] == EMPTY)
{
printf(" ·"); // 空位用点表示
}
else if (board[i][j] == PLAYER || board[i][j] == PLAYER1)
{
printf(""); // 玩家1用实心圆表示
}
else
{
printf(""); // 玩家2/AI用空心圆表示
}
}
printf("\n");
}
printf("\n");
}
/**
* @brief 显示游戏状态信息
* @param current_player 当前玩家
* @param step_count 当前步数
*/
void display_game_status(int current_player, int step_count)
{
printf("当前步数: %d\n", step_count);
if (current_player == PLAYER || current_player == PLAYER1)
{
printf("当前玩家: ●\n");
}
else
{
printf("当前玩家: ○\n");
}
}
/**
* @brief 显示获胜信息
* @param winner 获胜者
*/
void display_winner(int winner)
{
printf("\n游戏结束!\n");
if (winner == PLAYER)
{
printf("玩家获胜!\n");
}
else if (winner == AI)
{
printf("AI获胜!\n");
}
else if (winner == PLAYER1)
{
printf("玩家1获胜!\n");
}
else if (winner == PLAYER2)
{
printf("玩家2获胜!\n");
}
else
{
printf("平局!\n");
}
}
/**
* @brief 显示游戏设置菜单
*/
void display_settings_menu()
{
printf("\n===== 游戏设置 =====\n");
printf("1. 棋盘大小设置\n");
printf("2. 禁手规则设置\n");
printf("3. 计时器设置\n");
printf("4. 网络配置设置\n");
printf("5. AI难度设置\n");
printf("0. 返回主菜单\n");
printf("==================\n");
}
/**
* @brief 清屏函数
*/
void clear_screen()
{
#ifdef _WIN32
system("cls");
#else
system("clear");
#endif
}
/**
* @brief 暂停等待用户输入
* @param prompt 提示信息
*/
void pause_for_input(const char *prompt)
{
printf("%s", prompt);
#ifdef _WIN32
_getch();
#else
getchar();
#endif
}
/**
* @brief 显示游戏规则
*/
void display_game_rules()
{
printf("\n===== 五子棋游戏规则 =====\n");
printf("1. 🎮 游戏目标:\n");
printf(" 在棋盘上连成五个同色棋子(横、竖、斜均可)\n\n");
printf("2. 🔄 游戏流程:\n");
printf(" - ⚫ 黑棋先行,双方轮流落子\n");
printf(" - 📍 输入坐标格式:行号 列号(如:7 7)\n");
printf(" - ✨ 棋子落在棋盘交叉点上\n\n");
printf("3. 🏆 胜负判定:\n");
printf(" - 🎉 率先连成五子者获胜\n");
printf(" - 🤝 棋盘下满无人获胜则为平局\n\n");
printf("4. 🚫 禁手规则(可选):\n");
printf(" - ❌ 三三禁手:同时形成两个活三\n");
printf(" - ❌ 四四禁手:同时形成两个冲四\n");
printf(" - ❌ 长连禁手:连成六子或以上\n\n");
printf("5. 🛠️ 特殊功能:\n");
printf(" - ↩️ 悔棋:输入 'R' 或 'r' 可悔棋\n");
printf(" - 💾 保存:游戏结束后可保存对局记录\n");
printf(" - 📖 复盘:可加载历史对局进行复盘\n");
printf("============================\n");
}
/**
* @brief 显示关于信息
*/
void display_about()
{
printf("\n===== 关于五子棋游戏 =====\n");
printf("🎮 游戏名称:五子棋人机对战\n");
printf("📦 版本:7.0\n");
printf("👨‍💻 开发者:刘航宇\n");
printf("📧 联系邮箱:3364451258@qq.com\n");
printf("🌐 项目主页:https://github.com/LHY0125/Gobang-Game\n\n");
printf("✨ 主要特性:\n");
printf(" 🤖 智能AI对战(支持多种难度)\n");
printf(" 👥 双人对战模式\n");
printf(" 🌐 网络对战(局域网/互联网)\n");
printf(" 📝 对局记录与复盘\n");
printf(" 🚫 禁手规则支持\n");
printf(" ⏱️ 计时器功能\n");
printf(" 📏 自定义棋盘大小\n");
printf(" 📊 评分系统\n\n");
printf("🙏 感谢使用!\n");
printf("========================\n");
}