#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 "globals.h" #include #include #include #include #include // 全局变量现在在globals.c中定义 #ifdef _WIN32 #include #include #include #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 = 3; AI_DEPTH = get_integer_input("请选择AI难度(1~5), 数字越大越强,注意数字越大AI思考时间越长!):", 1, 5); /** * @brief AI的防守系数,系数越大越倾向于防守 * @note 1~1.2 * 2~1.3 * 3~1.4 * 4~1.5 * 5~1.6 */ defense_coefficient = 1.2 + (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 处理网络玩家回合 */ 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; }