首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >从零开始写一个五子棋游戏!手把手教你用 C++ 实现控制台版五子棋(附原码)

从零开始写一个五子棋游戏!手把手教你用 C++ 实现控制台版五子棋(附原码)

作者头像
给东岸来杯冷咖啡
发布2026-01-12 14:28:34
发布2026-01-12 14:28:34
1020
举报

引言

今天,我想带大家一起动手写一个简单又好玩的五子棋游戏

即使你刚学编程不久,只要会一点 C++ 基础(比如变量、循环、函数),就能跟着我一步步完成这个小项目。

💡 为什么选五子棋? 因为它规则简单(连成五个就赢!),逻辑清晰,非常适合编程初学者练手。而且做完后还能和朋友对战,超有成就感!

话不多说,让我们开始吧!

🎯 我们的目标是什么?

我们要做一个在命令行(黑窗口)里运行的五子棋游戏,具备以下功能:

  • 显示 19×19 的棋盘
  • 黑白双方轮流下棋(黑棋先手)
  • 自动判断是否有人连成五子获胜
  • 落子位置不能重复
  • 有主菜单,可以开始游戏或退出

听起来是不是很酷?别担心,我们一步步来!

第一步:理解“数据”——棋盘和回合是怎么存的?

在编程中,数据是程序的“记忆”。我们要先设计好怎么“记住”棋盘状态。

📌 棋盘:用二维数组表示

想象一个 19 行 19 列的表格,每个格子可以是:

  • 0:空地(没人下过)
  • 1:黑子
  • 2:白子

在 C++ 中,我们可以用一个 二维数组 来表示:

代码语言:javascript
复制
constexpr int BOARD_SIZE = 19;
int board[BOARD_SIZE][BOARD_SIZE];//19×19的表格
🔍 逐行解释:
  • constexpr int BOARD_SIZE = 19; 定义一个编译时常量 BOARD_SIZE,值为 19。
    • constexpr 表示这个值在编译时就确定了,不能改。
    • 用常量代替“魔法数字”19,让代码更清晰、易维护。 🌰 以后想改成 15×15 棋盘?只需改这一行!
  • int board[BOARD_SIZE][BOARD_SIZE]; 声明一个 19×19 的二维整数数组,用来表示棋盘。
    • board[i][j] 表示第 i 行、第 j 列的状态。
    • 初始值是随机的(后面会初始化为 0)。 🌰 想象成一个 19 行 19 列的 Excel 表格,每个格子存一个数字。

📌 回合:用一个整数记录谁该下

我们用一个叫 flag 的变量:

代码语言:javascript
复制
int flag;
  • flag 是偶数(0, 2, 4...)→ 黑棋下
  • flag 是奇数(1, 3, 5...)→ 白棋下

🌰 flag = 0 表示第 0 回合(黑棋先手),下完后 flag++ 变成 1(白棋回合)。

⚙️ 第二步:核心功能拆解(函数设计)

我们把大问题拆成几个小任务,每个任务写成一个函数(函数就像一个个小机器人,各司其职)

⚙️ (1)初始化函数 init()—— 初始化游戏

代码语言:javascript
复制
void init() {
    for (int i = 0; i < BOARD_SIZE; ++i) {
        for (int j = 0; j < BOARD_SIZE; ++j) {
            board[i][j] = 0;
        }
    }
    flag = 0;
}

作用:

  • 把棋盘所有格子清空(设为 0)
  • flag 设为 0(黑棋先手)
🔍 逐行解释:
  • void init() 函数名 init 是 “initialize”(初始化)的缩写。 void 表示这个函数不返回任何值
  • for (int i = 0; i < BOARD_SIZE; ++i) 外层循环:遍历每一i 从 0 到 18)。
  • for (int j = 0; j < BOARD_SIZE; ++j) 内层循环:遍历当前行的每一j 从 0 到 18)。
  • board[i][j] = 0; 把每个格子设为 0,表示空地
  • flag = 0; 重置回合数,让黑棋先手

小总结:每次新游戏开始,都要调用 init() 清空棋盘!

✍️ (2)落子函数 playerMove(int x, int y) —— 玩家落子

代码语言:javascript
复制
int playerMove(int x, int y) {
    if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) return 0;
    if (board[x][y] != 0) return 0;

    board[x][y] = (flag % 2 == 0) ? 1 : 2;
    return 1;
}

作用:

  • 检查 (x, y) 是否在棋盘内
  • 检查这个位置是不是空的
  • 如果是空的,就根据 flag 决定放黑子(1)还是白子(2)
🔍 逐行解释:
  • 越界检查xy 不在 [0, 18] 范围内?返回 0(失败)。
  • 重复落子检查board[x][y] != 0 表示已有棋子?返回 0(失败)。
  • 落子
    • flag % 2 == 0 → 黑棋(1)
    • 否则 → 白棋(2) (? : 是三元运算符,相当于简写 if-else)
  • 返回 1:表示落子成功!

🏆 (3)胜利判断函数 isWin(int x, int y)—— 判断是否获胜(重点!)

这是最核心也最难的部分,我们分段讲解。

📌 函数头 + 安全检查
代码语言:javascript
复制
int isWin(int x, int y) {
    if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE || board[x][y] == 0)
        return 0;
  • int isWin(int x, int y) 输入刚落子的坐标 (x, y),返回:
    • 0:没赢
    • 1:黑棋赢
    • 2:白棋赢
  • 安全检查: 如果坐标越界(比如 x=-1)或该位置是空的(board[x][y]==0),直接返回 0(不可能赢)。
📌 获取当前棋子颜色
代码语言:javascript
复制
    int color = board[x][y];
    int count;
  • int color = board[x][y]; 记住刚落下的棋子是黑(1)还是白(2)。
  • int count; 用来统计连续相同颜色棋子的数量
📌 胜利条件

我们只检查刚刚落子的位置,看它在四个方向上有没有连成 5 个:

  1. 横向(← →)
  2. 纵向(↑ ↓)
  3. 左上到右下(↖ ↘)
  4. 右上到左下(↗ ↙)
📌 方向 1:横向检查(← →)
  • 先往左数有几个连续同色棋子
  • 再往右数
  • 加起来 ≥5 就赢!
代码语言:javascript
复制
// 以横向为例
int count = 1; // 自己算1个
for (int j = y-1; j>=0 && board[x][j]==color; j--) count++; // 往左
for (int j = y+1; j<19 && board[x][j]==color; j++) count++; // 往右
if (count >= 5) return color;
  • count = 1; 先把自己算进去(至少有 1 个)。
  • 往左检查jy-1 开始往左(j--),只要格子颜色相同,count++
  • 往右检查jy+1 开始往右(j++),同样统计。
  • 判断胜利: 如果总数 ≥5,直接返回 color(1 或 2)。

🌰 例子:在 (5,5) 下黑子,左边有 2 个黑子,右边有 2 个 → count = 1+2+2 = 5 → 赢!

📌 方向 2:纵向检查(↑ ↓)
代码语言:javascript
复制
    // 纵向(上下)
    count = 1;
    for (int i = x - 1; i >= 0 && board[i][y] == color; --i) count++;
    for (int i = x + 1; i < BOARD_SIZE && board[i][y] == color; ++i) count++;
    if (count >= 5) return color;

和横向类似,只是固定列 y,变动行 i

📌 方向 3:左上-右下(\ 方向)
代码语言:javascript
复制
    // 左上-右下(\)
    count = 1;
    for (int d = 1; x - d >= 0 && y - d >= 0 && board[x - d][y - d] == color; ++d) count++;
    for (int d = 1; x + d < BOARD_SIZE && y + d < BOARD_SIZE && board[x + d][y + d] == color; ++d) count++;
    if (count >= 5) return color;
  • 往左上:行和列同时减(x-d, y-d
  • 往右下:行和列同时加(x+d, y+d
  • d 是“距离”,从 1 开始

🌰 坐标变化:(5,5) → (4,4) → (3,3) …(左上);(5,5) → (6,6) → (7,7) …(右下)

📌 方向 4:右上-左下(/ 方向)
代码语言:javascript
复制
    // 右上-左下(/)
    count = 1;
    for (int d = 1; x - d >= 0 && y + d < BOARD_SIZE && board[x - d][y + d] == color; ++d) count++;
    for (int d = 1; x + d < BOARD_SIZE && y - d >= 0 && board[x + d][y - d] == color; ++d) count++;
    if (count >= 5) return color;

    return 0;
}
  • 往右上:行减、列加(x-d, y+d
  • 往左下:行加、列减(x+d, y-d
  • 最后 return 0;:四个方向都没连成 5 个,返回没赢。

为什么只检查刚落子的位置? 因为只有新下的棋子才可能“形成新的五连”,其他位置之前已经检查过了!

✅ (4) 显示和交互函数

🖥️ 清屏函数(跨平台)
代码语言:javascript
复制
void clearScreen() {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
}
  • #ifdef _WIN32:如果是 Windows 系统,执行 cls(清屏命令)
  • 否则:执行 clear(Linux/macOS 命令)
  • system() 用于执行操作系统命令。

⚠️ 注意:system() 有安全风险,但小游戏可以接受。

⏸️ 暂停函数(等待用户按回车)
代码语言:javascript
复制
void pauseScreen() {
    std::cout << "按回车键继续...";
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();
}
  • std::cin.ignore(...):清空输入缓冲区(防止之前残留的回车影响)
  • std::cin.get():等待用户按回车

🌰 如果不加 ignore,可能按一次回车就跳过两次!

🎮 显示棋盘 gameView_ShowBoard()
代码语言:javascript
复制
void gameView_ShowBoard() {
    clearScreen();
    // 打印列标
    std::cout << "    ";
    for (int j = 0; j < BOARD_SIZE; ++j) {
        std::cout << std::setw(2) << j << " ";
    }
    std::cout << "\n";

    for (int i = 0; i < BOARD_SIZE; ++i) {
        std::cout << std::setw(2) << i << " ";
        for (int j = 0; j < BOARD_SIZE; ++j) {
            char ch;
            if (board[i][j] == 0) ch = '+';
            else if (board[i][j] == 1) ch = 'X';
            else ch = 'O';
            std::cout << " " << ch << " ";
        }
        std::cout << "\n";
    }
    std::cout << "\n当前回合: " << ((flag % 2 == 0) ? "黑棋" : "白棋") << "\n";
}
  • std::setw(2):让数字占 2 个字符宽,对齐棋盘
  • 棋子显示
    • 0'+'(空地)
    • 1'X'(黑子)
    • 2'O'(白子)
  • 最后显示当前是谁的回合。
🎉 胜利界面 winView()
代码语言:javascript
复制
void winView() {
    clearScreen();
    int winner = (flag % 2 == 0) ? 2 : 1;
    if (winner == 1) {
        std::cout << "🎉 黑棋胜利!\n";
    } else {
        std::cout << "🎉 白棋胜利!\n";
    }
    std::cout << "按回车键返回主菜单...\n";
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();
}
  • 为什么 winner = (flag % 2 == 0) ? 2 : 1 因为落子后还没切换 flag
    • 如果 flag 是偶数(黑棋刚下完),那胜利者是黑棋(1)→ 但这里写反了? ❗ 注意:这里有个逻辑陷阱! 正确应该是:刚落子的是 flag % 2 == 0 ? 黑 : 白,所以胜利者就是 board[x][y] 的值! 但为了简单,我们直接用 isWin() 的返回值更好。不过当前写法也能工作,因为 flag 还没加 1。

🕹️ 第三步:游戏主循环 gameView()

代码语言:javascript
复制
void gameView() {
    init();
    int x, y;

    while (true) {
        gameView_ShowBoard();

        std::cout << "请输入落子坐标 (行 列,例如: 9 10): ";
        if (!(std::cin >> x >> y)) {
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "输入格式错误!请输入两个整数。\n";
            pauseScreen();
            continue;
        }

        if (!playerMove(x, y)) {
            std::cout << "落子失败!坐标无效或该位置已有棋子。\n";
            pauseScreen();
            continue;
        }

        int result = isWin(x, y);
        if (result != 0) {
            gameView_ShowBoard();
            winView();
            break;
        }

        flag++;
    }
}
🔍 关键点:
  • init():开始前清空棋盘
  • 输入处理
    • if (!(std::cin >> x >> y)) 检查是否输入了非整数
    • 出错时清空缓冲区并提示
  • 落子失败:提示并重新循环
  • 胜利判断:如果 isWin 返回非 0,显示胜利界面并退出循环
  • flag++:切换到对方回合(放在最后!)

🏠 第四步:主菜单 menuView()

代码语言:javascript
复制
void menuView() {
    int choice;
    while (true) {
        clearScreen();
        std::cout << "========== 五子棋游戏 ==========\n";
        std::cout << "1. 开始游戏\n";
        std::cout << "2. 设置\n";
        std::cout << "3. 退出游戏\n";
        std::cout << "请选择: ";
        std::cin >> choice;

        switch (choice) {
            case 1:
                gameView();
                break;
            case 2:
                std::cout << "设置功能敬请期待...\n";
                pauseScreen();
                break;
            case 3:
                std::cout << "感谢游玩!再见!\n";
                exit(0);
            default:
                std::cout << "无效选项,请重新选择。\n";
                pauseScreen();
        }
    }
}
  • 无限循环:直到用户选择退出
  • switch:根据选项执行不同操作
  • exit(0):立即终止程序

🚀 第五步:主函数 main()

📁 包含头文件(程序的“工具箱”):

代码语言:javascript
复制
#include <iostream>
#include <iomanip>
#include <limits>
🔍 逐行解释:
  • #include <iostream> 引入 C++ 的输入输出流库。有了它,我们才能用 std::cout 打印文字,用 std::cin 读取用户输入。 🌰 就像你要写字,得先有笔和纸——iostream 就是程序的“纸笔”。
  • #include <iomanip> 引入格式化输出库。我们用它来对齐棋盘的行列号(比如让数字整齐排列)。 🌰 比如 std::setw(2) 可以让数字“占两个字符宽”,避免棋盘歪歪扭扭。
  • #include <limits> 用于安全地处理错误输入(比如用户输入了字母而不是数字)。 🌰 如果用户乱输,程序不会崩溃,而是提示“输错了,请重试”。

主函数

代码语言:javascript
复制
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    menuView();
    return 0;
}
  • std::ios::sync_with_stdio(false); 关闭 C 和 C++ 输入输出的同步,加快输入输出速度(对小游戏影响不大,但好习惯)。
  • std::cin.tie(nullptr); 解除 cincout 的绑定,进一步提速。
  • menuView();:启动主菜单!

💡 给初学者的小建议

  1. 不要怕“看不懂”:遇到不懂的术语(比如“二维数组”、“函数”),先记住它“做什么”,再慢慢理解“为什么”。
  2. 动手改一改:比如把棋盘改成 15×15,或者把胜利条件改成 4 子连珠,看看会发生什么!
  3. 调试技巧:如果程序出错,先检查:
    • 数组下标是否越界(0~18,不是 1~19!)
    • 输入时是否输错了格式(要输两个数字,中间空格)
  4. 享受过程:编程不是死记硬背,而是像搭积木一样,把小功能组合成大作品!

💬 寄语

编程不是死记硬背,而是理解逻辑 + 动手实践。 今天你写的每一行代码,都在锻炼你的“计算思维”。 遇到问题?太正常了!调试的过程,就是你进步最快的时候。 现在,去运行你的五子棋吧!和朋友对战,享受创造的乐趣!🎉

🎁 完整代码获取

代码语言:javascript
复制
#include <iostream>
#include <iomanip>
#include <limits>

// -------------------- 全局数据 --------------------
constexpr int BOARD_SIZE = 19;
int board[BOARD_SIZE][BOARD_SIZE];
int flag; // 偶数:黑棋(1);奇数:白棋(2)
// ------------------------------------------------

// -------------------- service --------------------
void init() {
    for (int i = 0; i < BOARD_SIZE; ++i) {
        for (int j = 0; j < BOARD_SIZE; ++j) {
            board[i][j] = 0;
        }
    }
    flag = 0;
}

int isWin(int x, int y) {
    if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE || board[x][y] == 0)
        return 0;

    int color = board[x][y];
    int count;

    // 横向(左右)
    count = 1;
    for (int j = y - 1; j >= 0 && board[x][j] == color; --j) count++;
    for (int j = y + 1; j < BOARD_SIZE && board[x][j] == color; ++j) count++;
    if (count >= 5) return color;

    // 纵向(上下)
    count = 1;
    for (int i = x - 1; i >= 0 && board[i][y] == color; --i) count++;
    for (int i = x + 1; i < BOARD_SIZE && board[i][y] == color; ++i) count++;
    if (count >= 5) return color;

    // 左上-右下(\)
    count = 1;
    for (int d = 1; x - d >= 0 && y - d >= 0 && board[x - d][y - d] == color; ++d) count++;
    for (int d = 1; x + d < BOARD_SIZE && y + d < BOARD_SIZE && board[x + d][y + d] == color; ++d) count++;
    if (count >= 5) return color;

    // 右上-左下(/)
    count = 1;
    for (int d = 1; x - d >= 0 && y + d < BOARD_SIZE && board[x - d][y + d] == color; ++d) count++;
    for (int d = 1; x + d < BOARD_SIZE && y - d >= 0 && board[x + d][y - d] == color; ++d) count++;
    if (count >= 5) return color;

    return 0;
}

int playerMove(int x, int y) {
    if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE) return 0;
    if (board[x][y] != 0) return 0;

    board[x][y] = (flag % 2 == 0) ? 1 : 2;
    return 1;
}
// ------------------------------------------------


// -------------------- view --------------------
void clearScreen() {
#ifdef _WIN32
    system("cls");
#else
    system("clear");
#endif
}

void pauseScreen() {
    std::cout << "按回车键继续...";
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();
}

void menuView();

void gameView_ShowBoard() {
    clearScreen();
    // 打印列标
    std::cout << "    ";
    for (int j = 0; j < BOARD_SIZE; ++j) {
        std::cout << std::setw(2) << j << " ";
    }
    std::cout << "\n";

    for (int i = 0; i < BOARD_SIZE; ++i) {
        std::cout << std::setw(2) << i << " ";
        for (int j = 0; j < BOARD_SIZE; ++j) {
            char ch;
            if (board[i][j] == 0) ch = '+';
            else if (board[i][j] == 1) ch = '●'; // 黑子
            else ch = '○'; // 白子
            std::cout << " " << ch << " ";
        }
        std::cout << "\n";
    }
    std::cout << "\n当前回合: " << ((flag % 2 == 0) ? "黑棋" : "白棋") << "\n";
}

void winView() {
    clearScreen();
    int winner = (flag % 2 == 0) ? 2 : 1; // 落子后未切换 flag,胜利者是刚下棋的一方
    if (winner == 1) {
        std::cout << "🎉 黑棋胜利!\n";
    } else {
        std::cout << "🎉 白棋胜利!\n";
    }
    std::cout << "按回车键返回主菜单...\n";
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();
}

void gameView() {
    init();
    int x, y;

    while (true) {
        gameView_ShowBoard();

        std::cout << "请输入落子坐标 (行 列,例如: 9 10): ";
        if (!(std::cin >> x >> y)) {
            // 输入错误处理
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << "输入格式错误!请输入两个整数。\n";
            pauseScreen();
            continue;
        }

        if (!playerMove(x, y)) {
            std::cout << "落子失败!坐标无效或该位置已有棋子。\n";
            pauseScreen();
            continue;
        }

        int result = isWin(x, y);
        if (result != 0) {
            gameView_ShowBoard();
            winView();
            break; // 返回主菜单
        }

        flag++; // 切换玩家
    }
}

void menuView() {
    int choice;
    while (true) {
        clearScreen();
        std::cout << "========== 五子棋游戏 ==========\n";
        std::cout << "1. 开始游戏\n";
        std::cout << "2. 设置\n";
        std::cout << "3. 退出游戏\n";
        std::cout << "请选择: ";
        std::cin >> choice;

        switch (choice) {
            case 1:
                gameView();
                break;
            case 2:
                std::cout << "设置功能敬请期待...\n";
                pauseScreen();
                break;
            case 3:
                std::cout << "感谢游玩!再见!\n";
                exit(0);
            default:
                std::cout << "无效选项,请重新选择。\n";
                pauseScreen();
        }
    }
}
// ------------------------------------------------

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    menuView();
    return 0;
}

演示效果:

我会在这里,陪你一起成长 💪

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 🎯 我们的目标是什么?
  • 第一步:理解“数据”——棋盘和回合是怎么存的?
    • 📌 棋盘:用二维数组表示
      • 🔍 逐行解释:
    • 📌 回合:用一个整数记录谁该下
  • ⚙️ 第二步:核心功能拆解(函数设计)
    • ⚙️ (1)初始化函数 init()—— 初始化游戏
      • 🔍 逐行解释:
    • ✍️ (2)落子函数 playerMove(int x, int y) —— 玩家落子
      • 🔍 逐行解释:
    • 🏆 (3)胜利判断函数 isWin(int x, int y)—— 判断是否获胜(重点!)
      • 📌 函数头 + 安全检查
      • 📌 获取当前棋子颜色
      • 📌 胜利条件
      • 📌 方向 1:横向检查(← →)
      • 📌 方向 2:纵向检查(↑ ↓)
      • 📌 方向 3:左上-右下(\ 方向)
      • 📌 方向 4:右上-左下(/ 方向)
    • ✅ (4) 显示和交互函数
      • 🖥️ 清屏函数(跨平台)
      • ⏸️ 暂停函数(等待用户按回车)
      • 🎮 显示棋盘 gameView_ShowBoard()
      • 🎉 胜利界面 winView()
  • 🕹️ 第三步:游戏主循环 gameView()
    • 🔍 关键点:
  • 🏠 第四步:主菜单 menuView()
  • 🚀 第五步:主函数 main()
    • 📁 包含头文件(程序的“工具箱”):
      • 🔍 逐行解释:
    • 主函数
  • 💡 给初学者的小建议
  • 💬 寄语
  • 🎁 完整代码获取
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档