井字棋规则简单,却是练习"棋盘建模"和"AI 决策"的绝佳素材。你和电脑轮流落子,电脑带策略 AI——能赢就赢,赢不了就堵,堵不住占中心。只用标准库。

玩法介绍

3×3 棋盘,你执 X,AI 执 O。输入行号列号(1~3)落子,横竖斜连成三个即胜,下满则平局。例如 2 2 是正中间。

第 1 步:用 vector 表示棋盘

std::vector<std::vector<char>> 表示棋盘:外层 3 行,内层 3 字符。空位用空格 ' ',绘制时直接输出即可:

#include <iostream>
#include <vector>

using Board = std::vector<std::vector<char>>;

Board makeBoard() {
    return Board(3, std::vector<char>(3, ' '));
}

第 2 步:绘制棋盘

逐行输出,行间用分隔线隔开。传 const Board& 引用避免复制,也防止函数内意外修改:

void draw(const Board& b) {
    for (int r = 0; r < 3; ++r) {
        std::cout << " " << b[r][0] << " | " << b[r][1] << " | " << b[r][2] << "\n";
        if (r < 2) std::cout << "---+---+---\n";
    }
}

第 3 步:落子与胜负判定

胜负判定是核心:遍历 3 行、3 列、两条对角线,看是否被同一棋子占满;full() 检测平局:

bool win(const Board& b, char p) {
    for (int i = 0; i < 3; ++i) {
        if (b[i][0] == p && b[i][1] == p && b[i][2] == p) return true;
        if (b[0][i] == p && b[1][i] == p && b[2][i] == p) return true;
    }
    return (b[0][0] == p && b[1][1] == p && b[2][2] == p) ||
           (b[0][2] == p && b[1][1] == p && b[2][0] == p);
}

bool full(const Board& b) {
    for (const auto& row : b)
        for (char c : row)
            if (c == ' ') return false;
    return true;
}

第 4 步:AI 决策

AI 的策略是"先想一步":① 遍历空位,试着落 O,能赢就落;② 再假装落 X,玩家会赢就堵住;③ 都不行就占中心,最后找第一个空位。这种"试了再撤销"的技巧叫回溯试探,是 Minimax 算法的雏形:

bool moveAI(Board& b) {
    // 1 能赢就赢
    for (int r = 0; r < 3; ++r)
        for (int c = 0; c < 3; ++c)
            if (b[r][c] == ' ') {
                b[r][c] = 'O';
                if (win(b, 'O')) return true;
                b[r][c] = ' ';   // 撤销试探
            }
    // 2 玩家要赢就堵
    for (int r = 0; r < 3; ++r)
        for (int c = 0; c < 3; ++c)
            if (b[r][c] == ' ') {
                b[r][c] = 'X';
                if (win(b, 'X')) { b[r][c] = 'O'; return true; }
                b[r][c] = ' ';
            }
    // 3 占中心,否则找空位
    if (b[1][1] == ' ') { b[1][1] = 'O'; return true; }
    for (int r = 0; r < 3; ++r)
        for (int c = 0; c < 3; ++c)
            if (b[r][c] == ' ') { b[r][c] = 'O'; return true; }
    return false;   // 棋盘满了
}

第 5 步:主循环与编译运行

最后写 main():落子前校验位置合法性,落子后依次检查"玩家赢 / 平局 / AI 落子 / AI 赢 / 平局"。把第 1~4 步的函数与下面的 main() 存进同一个 tictactoe.cpp:

int main() {
    Board b = makeBoard();
    std::cout << "井字棋!你执 X,AI 执 O。输入行列(1~3),如 2 2\n";
    while (true) {
        draw(b);
        int r, c;
        std::cout << "你的回合(行 列): ";
        std::cin >> r >> c;
        --r; --c;
        if (r < 0 || r > 2 || c < 0 || c > 2 || b[r][c] != ' ') {
            std::cout << "无效位置,重新输入!\n";
            continue;
        }
        b[r][c] = 'X';
        if (win(b, 'X')) { draw(b); std::cout << "你赢了!\n"; break; }
        if (full(b))     { draw(b); std::cout << "平局。\n"; break; }
        moveAI(b);
        if (win(b, 'O')) { draw(b); std::cout << "AI 赢了!\n"; break; }
        if (full(b))     { draw(b); std::cout << "平局。\n"; break; }
    }
    return 0;
}
g++ -std=c++17 -o tictactoe tictactoe.cpp
./tictactoe

这个 AI 赢不了你时一定会堵住你。把策略 3 改成"随机落子",棋路更有变化。

💡 改进方向:① 用 Minimax 实现"不输"的完美 AI;② 支持先手/后手;③ 加计分板;④ 改成 N×N 连四子。