Created
December 23, 2025 15:35
-
-
Save z0z0r4/2eb5620a2c44d728effa6d551e29e366 to your computer and use it in GitHub Desktop.
AI五子棋C++期末作业
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <iostream> | |
| #include <climits> | |
| #include <cstdio> | |
| #include <filesystem> | |
| #include <iomanip> | |
| #include <utility> | |
| #include <vector> | |
| #include <time.h> | |
| bool debug_mode = false; | |
| struct StepRecord | |
| { | |
| int x; | |
| int y; | |
| int player; // 1 for black, 2 for white | |
| }; | |
| struct GameState | |
| { | |
| int mode; // 1: PvsP, 2: PvsAI, 3: AIvsP | |
| int board[15][15] = {0}; // 0: empty, 1: black, 2: white | |
| int current_player = 1; // 1 for black, 2 for white | |
| int step_count = 0; | |
| StepRecord steps[225] = {}; | |
| }; | |
| void read_state_from_file(const char *filename, GameState &state) | |
| { | |
| FILE *fp = fopen(filename, "rb"); | |
| if (!fp) | |
| { | |
| std::cerr << "Error: Cannot open file for reading: " << filename << "\n"; | |
| return; | |
| } | |
| if (fread(&state, sizeof(GameState), 1, fp) != 1) | |
| { | |
| std::cerr << "Error: Failed to read game state from file (file may be corrupted or empty).\n"; | |
| GameState default_state = {}; | |
| state = default_state; | |
| } | |
| else | |
| { | |
| std::cout << "Game state loaded from " << filename << "\n"; | |
| } | |
| fclose(fp); | |
| } | |
| void save_state_to_file(const char *filename, const GameState &state) | |
| { | |
| FILE *fp = fopen(filename, "wb"); | |
| if (!fp) | |
| { | |
| std::cerr << "Error: Cannot open file for writing: " << filename << "\n"; | |
| return; | |
| } | |
| if (fwrite(&state, sizeof(GameState), 1, fp) != 1) | |
| { | |
| std::cerr << "Error: Failed to write game state to file.\n"; | |
| } | |
| else | |
| { | |
| std::cout << "Game state saved to " << filename << "\n"; | |
| } | |
| fclose(fp); | |
| } | |
| void print_board(const int board[15][15]) | |
| { | |
| std::cout << " "; | |
| for (int j = 0; j < 15; ++j) | |
| { | |
| std::cout << std::setw(3) << j; | |
| } | |
| std::cout << "\n"; | |
| for (int i = 0; i < 15; ++i) | |
| { | |
| std::cout << std::setw(3) << i; | |
| for (int j = 0; j < 15; ++j) | |
| { | |
| if (board[i][j] == 1) | |
| { | |
| std::cout << " B "; | |
| } | |
| else if (board[i][j] == 2) | |
| { | |
| std::cout << " W "; | |
| } | |
| else | |
| { | |
| std::cout << " . "; | |
| } | |
| } | |
| std::cout << "\n"; | |
| } | |
| } | |
| void print_steps(const StepRecord steps[], int step_count) | |
| { | |
| for (int i = 0; i < step_count; ++i) | |
| { | |
| std::cout << "Step " << i + 1 << ": Player " | |
| << (steps[i].player == 1 ? "Black" : "White") | |
| << " -> (" << steps[i].x << ", " << steps[i].y << ")\n"; | |
| } | |
| } | |
| bool is_winning_step(int pos_i, int pos_j, const int board[15][15]) | |
| { | |
| int dx[] = {0, 1, 1, -1}; | |
| int dy[] = {1, 0, 1, 1}; | |
| int player = board[pos_i][pos_j]; // 与 last move 相同的玩家 | |
| for (int dir = 0; dir < 4; ++dir) | |
| { | |
| int count = 1; // include current stone | |
| // positive direction | |
| for (int step = 1; step <= 4; ++step) | |
| { | |
| int ni = pos_i + dx[dir] * step; | |
| int nj = pos_j + dy[dir] * step; | |
| if (ni < 0 || ni >= 15 || nj < 0 || nj >= 15 || board[ni][nj] != player) | |
| break; | |
| count++; | |
| } | |
| // negative direction | |
| for (int step = 1; step <= 4; ++step) | |
| { | |
| int ni = pos_i - dx[dir] * step; | |
| int nj = pos_j - dy[dir] * step; | |
| if (ni < 0 || ni >= 15 || nj < 0 || nj >= 15 || board[ni][nj] != player) | |
| break; | |
| count++; | |
| } | |
| if (count >= 5) | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool is_board_full(const int board[15][15]) | |
| { | |
| for (int i = 0; i < 15; ++i) | |
| for (int j = 0; j < 15; ++j) | |
| if (board[i][j] == 0) | |
| return false; | |
| return true; | |
| } | |
| bool move(int player, int pos_i, int pos_j, int board[15][15], StepRecord steps[225], int &step_count) | |
| { | |
| if (board[pos_i][pos_j] != 0) | |
| return false; | |
| board[pos_i][pos_j] = player; | |
| steps[step_count++] = StepRecord{pos_i, pos_j, player}; | |
| return true; | |
| } | |
| bool withdraw_last_move(int board[15][15], StepRecord steps[225], int &step_count) | |
| { | |
| if (step_count <= 0) | |
| return false; | |
| StepRecord last = steps[step_count - 1]; | |
| if ((last.player == 1 && board[last.x][last.y] != 1) || | |
| (last.player == 2 && board[last.x][last.y] != 2)) | |
| return false; | |
| board[last.x][last.y] = 0; | |
| step_count--; | |
| return true; | |
| } | |
| bool withdraw_steps(int num, int board[15][15], StepRecord steps[225], int &step_count) | |
| { | |
| for (int i = 0; i < num; ++i) | |
| { | |
| if (!withdraw_last_move(board, steps, step_count)) | |
| { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| int pattern_score(int pattern[5], int player) | |
| { | |
| int opponent = (player == 1) ? 2 : 1; | |
| // Check five in a row | |
| bool all_player = true; | |
| for (int i = 0; i < 5; ++i) | |
| if (pattern[i] != player) | |
| all_player = false; | |
| if (all_player) | |
| return 100000; | |
| // Count player and opponent | |
| int player_count = 0, opponent_count = 0, empty_count = 0; | |
| for (int i = 0; i < 5; ++i) | |
| { | |
| if (pattern[i] == player) | |
| player_count++; | |
| else if (pattern[i] == opponent) | |
| opponent_count++; | |
| else | |
| empty_count++; | |
| } | |
| if (opponent_count > 0) | |
| return 0; // opponent blocks | |
| switch (player_count) | |
| { | |
| case 4: | |
| return (pattern[0] == 0 && pattern[4] == 0) ? 5000 : 500; | |
| case 3: | |
| if (pattern[0] == 0 && pattern[4] == 0) | |
| return 500; | |
| else if (pattern[0] == 0 || pattern[4] == 0) | |
| return 100; | |
| else | |
| return 0; | |
| case 2: | |
| if (pattern[0] == 0 && pattern[4] == 0) | |
| return 50; | |
| else | |
| return 10; | |
| case 1: | |
| return 1; | |
| default: | |
| return 0; | |
| } | |
| } | |
| int evaluate_board(const int board[15][15]) | |
| { | |
| int black_score = 0; | |
| int white_score = 0; | |
| int line[5]; | |
| // Horizontal | |
| for (int i = 0; i < 15; ++i) | |
| for (int j = 0; j <= 10; ++j) | |
| { | |
| for (int k = 0; k < 5; ++k) | |
| line[k] = board[i][j + k]; | |
| if (line[0] == 0 && line[1] == 0 && line[2] == 0 && line[3] == 0 && line[4] == 0) | |
| continue; | |
| black_score += pattern_score(line, 1); | |
| white_score += pattern_score(line, 2); | |
| } | |
| // Vertical | |
| for (int j = 0; j < 15; ++j) | |
| for (int i = 0; i <= 10; ++i) | |
| { | |
| for (int k = 0; k < 5; ++k) | |
| line[k] = board[i + k][j]; | |
| if (line[0] == 0 && line[1] == 0 && line[2] == 0 && line[3] == 0 && line[4] == 0) | |
| continue; | |
| black_score += pattern_score(line, 1); | |
| white_score += pattern_score(line, 2); | |
| } | |
| // Diagonal '\' | |
| for (int i = 0; i <= 10; ++i) | |
| for (int j = 0; j <= 10; ++j) | |
| { | |
| for (int k = 0; k < 5; ++k) | |
| line[k] = board[i + k][j + k]; | |
| if (line[0] == 0 && line[1] == 0 && line[2] == 0 && line[3] == 0 && line[4] == 0) | |
| continue; | |
| black_score += pattern_score(line, 1); | |
| white_score += pattern_score(line, 2); | |
| } | |
| // Diagonal / | |
| for (int i = 4; i < 15; ++i) | |
| for (int j = 0; j <= 10; ++j) | |
| { | |
| for (int k = 0; k < 5; ++k) | |
| line[k] = board[i - k][j + k]; | |
| if (line[0] == 0 && line[1] == 0 && line[2] == 0 && line[3] == 0 && line[4] == 0) | |
| continue; | |
| black_score += pattern_score(line, 1); | |
| white_score += pattern_score(line, 2); | |
| } | |
| return black_score - white_score; // 默认为黑方得分减白方得分,所以如果 AI 是黑方,取最大值;如果是白方,取最小值 | |
| } | |
| std::vector<std::pair<int, int>> get_relevant_moves(const int board[15][15]) | |
| { | |
| std::vector<std::pair<int, int>> moves; | |
| for (int i = 0; i < 15; ++i) | |
| { | |
| for (int j = 0; j < 15; ++j) | |
| { | |
| if (board[i][j] == 0) | |
| { | |
| // 检查周围是否有棋子 | |
| bool relevant = false; | |
| for (int dx = -2; dx <= 2 && !relevant; ++dx) | |
| { | |
| for (int dy = -2; dy <= 2 && !relevant; ++dy) | |
| { | |
| int ni = i + dx; | |
| int nj = j + dy; | |
| if (ni >= 0 && ni < 15 && nj >= 0 && nj < 15) | |
| { | |
| if (board[ni][nj] != 0) | |
| { | |
| relevant = true; | |
| } | |
| } | |
| } | |
| } | |
| if (relevant) | |
| { | |
| moves.emplace_back(i, j); | |
| } | |
| } | |
| } | |
| } | |
| return moves; | |
| } | |
| int minimax_ab(int board[15][15], int depth, int alpha, int beta, bool is_maximizing, int player, int &eval_count, int last_move_x, int last_move_y) | |
| { | |
| // 检查胜负 | |
| if (is_winning_step(last_move_x, last_move_y, board)) { | |
| if (board[last_move_x][last_move_y] == 1) | |
| return 100000; // 黑胜 | |
| else | |
| return -100000; // 白胜 | |
| } | |
| if (depth <= 0) | |
| { | |
| eval_count++; | |
| return evaluate_board(board); | |
| } | |
| auto moves = get_relevant_moves(board); | |
| if (moves.empty()) | |
| { | |
| eval_count++; | |
| return evaluate_board(board); | |
| } | |
| if (is_maximizing) | |
| { | |
| // 黑方回合:最大化 (black - white) | |
| int max_eval = INT_MIN; | |
| for (auto [x, y] : moves) | |
| { | |
| if (board[x][y] != 0) | |
| continue; | |
| board[x][y] = 1; // 黑子 | |
| int eval = minimax_ab(board, depth - 1, alpha, beta, false, player,eval_count, x, y); | |
| board[x][y] = 0; // 回溯 | |
| max_eval = std::max(max_eval, eval); | |
| alpha = std::max(alpha, eval); | |
| if (beta <= alpha) | |
| break; // 剪枝 | |
| } | |
| return max_eval; | |
| } | |
| else | |
| { | |
| // 白方回合:最小化 (black - white) 白方希望黑方越小越好 | |
| int min_eval = INT_MAX; | |
| for (auto [x, y] : moves) | |
| { | |
| if (board[x][y] != 0) | |
| continue; | |
| board[x][y] = 2; // 白子 | |
| int eval = minimax_ab(board, depth - 1, alpha, beta, true, player, eval_count, x, y); | |
| board[x][y] = 0; // 回溯 | |
| min_eval = std::min(min_eval, eval); | |
| beta = std::min(beta, eval); | |
| if (beta <= alpha) | |
| break; // 剪枝 | |
| } | |
| return min_eval; | |
| } | |
| } | |
| StepRecord ai_make_move(int player, int board[15][15]) | |
| { | |
| // 记录开始时间 | |
| clock_t start_time = clock(); | |
| const int SEARCH_DEPTH = 2; | |
| auto moves = get_relevant_moves(board); | |
| if (moves.empty()) | |
| { | |
| return {7, 7, player}; // fallback | |
| } | |
| int best_score = (player == 1) ? INT_MIN : INT_MAX; | |
| int best_x = -1, best_y = -1; | |
| int alpha = INT_MIN, beta = INT_MAX; | |
| int eval_count = 0; | |
| for (auto [x, y] : moves) | |
| { | |
| if (board[x][y] != 0) | |
| continue; | |
| board[x][y] = player; | |
| int score; | |
| if (player == 1) | |
| { | |
| // 黑方:下一步是白方 | |
| score = minimax_ab(board, SEARCH_DEPTH - 1, alpha, beta, false, player,eval_count, x, y); | |
| if (score > best_score) | |
| { | |
| best_score = score; | |
| best_x = x; | |
| best_y = y; | |
| } | |
| alpha = std::max(alpha, best_score); | |
| } | |
| else | |
| { | |
| // 白方:下一步是黑方 | |
| score = minimax_ab(board, SEARCH_DEPTH - 1, alpha, beta, true, player,eval_count, x, y); | |
| if (score < best_score) | |
| { // 白方要最小化 (black - white) | |
| best_score = score; | |
| best_x = x; | |
| best_y = y; | |
| } | |
| beta = std::min(beta, best_score); | |
| } | |
| board[x][y] = 0; | |
| if (debug_mode) { | |
| std::cout << "Evaluated move (" << x << ", " << y << ") with score " << score << "\n"; | |
| } | |
| } | |
| if (best_x == -1) | |
| { | |
| // fallback to first relevant move | |
| best_x = moves[0].first; | |
| best_y = moves[0].second; | |
| } | |
| std::cout << "Evaluate " << eval_count << " times in " << (float)(clock() - start_time) / CLOCKS_PER_SEC << " seconds.\n"; | |
| return {best_x, best_y, player}; | |
| } | |
| int main(int argc, char* argv[]) | |
| { | |
| // 检查是否传入 --debug 参数 | |
| static bool debug_mode = false; | |
| for (int i = 1; i < argc; ++i) { | |
| if (std::string(argv[i]) == "--debug") { | |
| debug_mode = true; | |
| break; | |
| } | |
| } | |
| std::cout << "Gobang Game Initializing!" << std::endl; | |
| std::string game_state_filename; | |
| std::cout << "Enter save file name (or press Enter for default 'gobang_save.dat'): "; | |
| std::string input; | |
| std::getline(std::cin, input); | |
| if (input.empty()) | |
| { | |
| game_state_filename = "gobang_save.dat"; | |
| } | |
| else | |
| { | |
| game_state_filename = input; | |
| } | |
| std::cout << "Checking for existing game state file: " << game_state_filename << std::endl; | |
| GameState game_state; // 假设你已将 struct 名修正为 GameState(原为 GmaeState) | |
| if (std::filesystem::exists(game_state_filename)) | |
| { | |
| read_state_from_file(game_state_filename.c_str(), game_state); | |
| std::cout << "Resuming game from saved state." << std::endl; | |
| std::cout << "Current Mode: " << game_state.mode << std::endl; | |
| std::cout << "Current Player: " << (game_state.current_player == 1 ? "Black" : "White") << std::endl; | |
| std::cout << "Step Count: " << game_state.step_count << std::endl; | |
| std::cout << "Continue playing..." << std::endl; | |
| } | |
| else | |
| { | |
| std::cout << "No existing game state file found. Starting a new game." << std::endl; | |
| std::cout << "Choose Game Mode:\n" | |
| << "1. Player vs Player\n" | |
| << "2. You (Black) vs AI (White)\n" | |
| << "3. AI (Black) vs You (White)\n"; | |
| std::cin >> game_state.mode; | |
| } | |
| bool ai_mode = (game_state.mode == 2 || game_state.mode == 3); | |
| int human_player = (game_state.mode == 3) ? 2 : 1; // which side human plays | |
| bool game_over = false; | |
| while (!game_over) | |
| { | |
| std::cout << "\nCurrent Step: " << game_state.step_count << std::endl; | |
| print_board(game_state.board); | |
| int x = -10, y = -10; | |
| if (ai_mode && game_state.current_player != human_player) | |
| { | |
| std::cout << "AI (" << (game_state.current_player == 1 ? "Black" : "White") << ") is thinking...\n"; | |
| StepRecord ai_move = ai_make_move(game_state.current_player, game_state.board); | |
| x = ai_move.x; | |
| y = ai_move.y; | |
| std::cout << "AI plays at (" << x << ", " << y << ")\n"; | |
| } | |
| else | |
| { | |
| std::cout << "Player " << (game_state.current_player == 1 ? "Black" : "White") | |
| << ", enter move (x y) or -1 -1 to undo: "; | |
| std::cin >> x >> y; | |
| if (x == -1 && y == -1) | |
| { | |
| int steps_to_undo = 0; | |
| if (ai_mode) | |
| { | |
| steps_to_undo = (game_state.step_count >= 2) ? 2 : game_state.step_count; | |
| } | |
| else | |
| { | |
| steps_to_undo = (game_state.step_count >= 1) ? 1 : game_state.step_count; | |
| } | |
| if (steps_to_undo > 0 && withdraw_steps(steps_to_undo, game_state.board, game_state.steps, game_state.step_count)) | |
| { | |
| // After undo, reset game_state.current_player | |
| if (ai_mode) | |
| { | |
| // Undo 2 steps: back to human turn | |
| game_state.current_player = human_player; | |
| } | |
| else | |
| { | |
| // Undo 1 step: toggle player | |
| game_state.current_player = (game_state.current_player == 1) ? 2 : 1; | |
| } | |
| std::cout << "Undo successful. Back to your turn.\n"; | |
| } | |
| else | |
| { | |
| std::cout << "Cannot undo.\n"; | |
| } | |
| continue; | |
| } | |
| } | |
| // Validate move | |
| if (x < 0 || x >= 15 || y < 0 || y >= 15) | |
| { | |
| std::cerr << "Invalid coordinates. Try again.\n"; | |
| continue; | |
| } | |
| if (game_state.board[x][y] != 0) | |
| { | |
| std::cerr << "Position occupied. Try again.\n"; | |
| continue; | |
| } | |
| bool move_ok; | |
| if (game_state.current_player == 1) | |
| { | |
| move_ok = move(1, x, y, game_state.board, game_state.steps, game_state.step_count); | |
| } | |
| else | |
| { | |
| move_ok = move(2, x, y, game_state.board, game_state.steps, game_state.step_count); | |
| } | |
| if (!move_ok) | |
| { | |
| std::cerr << "Move failed.\n"; | |
| continue; | |
| } | |
| // 检查刚下子的玩家是否获胜 | |
| bool is_win = false; | |
| if (game_state.current_player == 1) | |
| { | |
| is_win = is_winning_step(x, y, game_state.board); | |
| } | |
| else | |
| { | |
| is_win = is_winning_step(x, y, game_state.board); | |
| } | |
| if (is_win) | |
| { | |
| print_board(game_state.board); | |
| std::cout << "Player " << (game_state.current_player == 1 ? "Black" : "White") << " wins!\n"; | |
| game_over = true; | |
| break; | |
| } | |
| // 切换玩家 | |
| game_state.current_player = (game_state.current_player == 1) ? 2 : 1; | |
| save_state_to_file(game_state_filename.c_str(), game_state); | |
| // Check draw | |
| if (is_board_full(game_state.board)) | |
| { | |
| print_board(game_state.board); | |
| std::cout << "\nThe game is a draw!\n"; | |
| game_over = true; | |
| break; | |
| } | |
| } | |
| // Final move list | |
| std::cout << "\n--- Game Record ---\n"; | |
| print_steps(game_state.steps, game_state.step_count); | |
| // delete saved game state file upon game over | |
| if (std::filesystem::exists(game_state_filename)) | |
| { | |
| std::filesystem::remove(game_state_filename); | |
| std::cout << "Saved game state file deleted: " << game_state_filename << ". Please restart the game." << std::endl; | |
| } | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment