Skip to content

Instantly share code, notes, and snippets.

@z0z0r4
Created December 23, 2025 15:35
Show Gist options
  • Select an option

  • Save z0z0r4/2eb5620a2c44d728effa6d551e29e366 to your computer and use it in GitHub Desktop.

Select an option

Save z0z0r4/2eb5620a2c44d728effa6d551e29e366 to your computer and use it in GitHub Desktop.
AI五子棋C++期末作业
#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