mirror of
https://github.com/LHY0125/Gobang-Game.git
synced 2026-06-29 08:55:53 +08:00
refactor: 重构项目为纯GUI版本并清理冗余代码
- 移除控制台版本相关代码,包括game_mode、ui、init_board等模块 - 将empty_board函数移至gobang.c核心模块 - 简化main.c仅保留GUI启动逻辑 - 更新Makefile仅构建GUI版本 - 清理过时文档和配置文件 - 优化GUI菜单和游戏窗口交互逻辑 - 添加AI难度配置支持
This commit is contained in:
-127
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user