C++控制台实现双人五子棋:原理与完整代码

慢慢来
慢慢来
发布时间:2026-10-15| 2人浏览过
原创

在控制台中使用C++开发双人五子棋,主要依赖Windows API进行字符界面操作。文章介绍了基于15x15二维数组的棋盘数据结构设计、键盘输入处理机制(方向键移动、回车确认)以及核心的五子连珠胜负判定算法,并附带完整的可编译运行代码。

核心设计思路

本项目的核心在于通过控制台窗口模拟图形交互界面。我们需要解决三个关键问题:数据的存储、输入的响应以及胜利条件的判定。

第1步:构建棋盘数据结构

棋盘采用 15 x 15 的二维整型数组 p[15][15] 表示。约定数值含义如下:

  • 0:空格,无棋子;
  • 1:红方棋子;
  • 2:蓝方棋子。

为了管理两名玩家的状态,定义一个 Chess 类,实例化为对象 A(红方)和 B(蓝方),各自拥有独立的棋盘数据副本(在实际双人对弈中通常共享同一个全局状态或类实例,此处代码结构展示了类的封装逻辑)。

第2步:控制台光标与字符渲染

标准 C++ IO 无法直接控制光标位置,需引入 Windows 头文件 <windows.h>。核心功能包括:

  • 移动光标:创建 gotoxy(int x, int y) 函数,调用 SetConsoleCursorPosition 将光标跳转至指定行列坐标。
  • 字符着色:使用 SetConsoleTextAttribute 设置前景色属性。例如红方棋子 ● 设置为红色,蓝方棋子 ◆ 设置为蓝色,以区分双方。
  • 绘制棋盘:使用 Unicode 边框字符(如 ┏, ━, │)打印出标准的围棋/五子棋网格样式。

第3步:交互逻辑与落子流程

游戏循环交替执行以下操作:

  1. 等待输入:使用 _getch() 函数阻塞等待按键,该函数无需按回车即可获取输入值,适合实时控制。
  2. 方向移动:识别方向键虚拟码(上72, 下80, 左75, 右77)。每次按键仅移动一格,且必须落在空位(值为0的位置)。
  3. 确认落子:当按下回车键(值13)时,记录当前光标所在的 COORD 坐标,将其转换为数组索引 (x/4, y/2)(因屏幕字符间距原因进行的比例换算),并将对应数组位置置为当前玩家标识。

关键算法实现细节

第4步:胜负判定逻辑 (win 函数)

每落一子后,检查全盘是否存在连续五个同色棋子。算法遍历四个方向:

  1. 横向扫描:遍历每一行 i,维护一个计数器 f。若当前格子有棋子则 f++,否则 f=0。当 f 达到5时返回胜利标志。
  2. 竖向扫描:类似横向,遍历列 j。
  3. 下斜(↘)扫描:从左上到右下。注意边界限制,起始点范围为 i: 0~12, j: 0~12,步长增加。
  4. 上斜(↗)扫描:从左下到右上。起始点范围为 i: 4~15, j: 0~12,行坐标减小,列坐标增加。

完整源代码

以下是整合了上述所有逻辑的单文件 C++ 代码,可直接保存为 .cpp 文件在 Windows 环境下编译运行。

#include <windows.h>
#include <iostream>
#include <conio.h>
#include <cstring>
using namespace std;

// 全局光标变量
COORD c;

class Chess {
private:
    int p[15][15]; // 15x15 棋盘数组
public:
    void newchess() {
        // 初始化空棋盘,全部置0
        for(int i = 0; i < 16; i++)
            for(int j = 0; j < 16; j++)
                p[i][j] = 0;
    }

    int win() {
        int i, j, f;
        
        // 横向检测
        for(i = 0; i < 16; i++) {
            j = f = 0;
            while(f < 5 && j < 16) {
                if(p[i][j]) f++; else f = 0;
                j++;
            }
            if(f == 5) return 1;
        }
        
        // 竖向检测
        for(j = 0; j < 16; j++) {
            i = f = 0;
            while(f < 5 && i < 16) {
                if(p[i][j]) f++; else f = 0;
                i++;
            }
            if(f == 5) return 1;
        }
        
        // 下斜检测 (\)
        for(i = 0; i < 12; i++) {
            j = f = 0;
            while(f < 5 && j < 12) {
                if(p[i+f][j+f]) f++; else {f = 0; j++;}
            }
            if(f == 5) return 1;
        }
        
        // 上斜检测 (/)
        for(i = 4; i < 16; i++) {
            j = f = 0;
            while(f < 5 && j < 12) {
                if(p[i-f][j+f]) f++; else {f = 0; j++;}
            }
            if(f == 5) return 1;
        }
        
        return 0;
    }

    friend int gochess(int);
} A, B;

void gotoxy(int x, int y) {
    COORD coord;
    coord.X = x;
    coord.Y = y;
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

void chessboard() {
    gotoxy(0, 0);
    cout << "┏━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┯━━━┓\n";
    for(int i = 0; i < 13; i++) {
        cout << "┃   │   │   │   │   │   │   │   │   │   │   │   │   │   ┃\n";
        cout << "┠───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┨\n";
    }
    cout << "┃   │   │   │   │   │   │   │   │   │   │   │   │   │   ┃\n";
    cout << "┗━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┷━━━┛\n";
}

void put(int n) {
    if(n == 1) {
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
        cout << "●\b";
    } else {
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE);
        cout << "◆\b";
    }
}

int gochess(int n) {
    int k, w;
    while(k = _getch()) {
        switch(k) {
        case 13: // 回车键确认
            if(n == 1) {
                put(n);
                A.p[c.X / 4][c.Y / 2] = 1; // 记录A方落子
                return 1;
            } else {
                put(n);
                B.p[c.X / 4][c.Y / 2] = 1; // 记录B方落子
                return 1;
            }
        case 224: // 方向键前缀
            w = _getch();
            switch(w) {
            case 72: // 上
                for(int i = 1; c.Y - 2 * i >= 0; i++)
                    if(!A.p[c.X / 4][(c.Y - 2 * i) / 2] && !B.p[c.X / 4][(c.Y - 2 * i) / 2]) {
                        c.Y -= 2 * i; break;
                    }
                break;
            case 80: // 下
                for(int i = 1; c.Y + 2 * i <= 28; i++)
                    if(!A.p[c.X / 4][(c.Y + 2 * i) / 2] && !B.p[c.X / 4][(c.Y + 2 * i) / 2]) {
                        c.Y += 2 * i; break;
                    }
                break;
            case 75: // 左
                for(int i = 1; c.X - 4 * i >= 0; i++)
                    if(!A.p[(c.X - 4 * i) / 4][c.Y / 2] && !B.p[(c.X - 4 * i) / 4][c.Y / 2]) {
                        c.X -= 4 * i; break;
                    }
                break;
            case 77: // 右
                for(int i = 1; c.X + 4 * i <= 56; i++)
                    if(!A.p[(c.X + 4 * i) / 4][c.Y / 2] && !B.p[(c.X + 4 * i) / 4][c.Y / 2]) {
                        c.X += 4 * i; break;
                    }
                break;
            }
            gotoxy(c.X, c.Y); // 更新光标显示位置
        }
    }
}

int game() {
    chessboard();          // 画棋盘
    A.newchess(); B.newchess(); // 初始化
    c.X = 28; c.Y = 14;   // 初始光标居中
    gotoxy(c.X, c.Y);
    
    while(1) {
        gochess(1); // 红方回合
        if(A.win()) return 1;
        gochess(2); // 蓝方回合
        if(B.win()) return 2;
    }
}

int main() {
    gotoxy(70, 5);
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE | FOREGROUND_GREEN);
    cout << "五子棋游戏";
    
    gotoxy(60, 9);
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED);
    cout << "操作说明:";
    gotoxy(60, 11);
    cout << "选择落点:←↑→↓";
    gotoxy(60, 12);
    cout << "落子:回车键(←┘)";
    
    int f;
    while(f = game()) {
        gotoxy(60, 16);
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED);
        if(f == 1) {
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED);
            cout << "红方胜!!";
        } else {
            SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_BLUE);
            cout << "蓝方胜!!";
        }

        gotoxy(60, 18);
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
        cout << "按任意键开始新游戏";
        f = _getch();
        cout << "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b                ";
        gotoxy(60, 16);
        cout << "\b\b\b\b\b\b\b\b\b                  ";
    }
}

注意事项与优化建议

1. 平台限制:本代码大量使用了 windows.h 中的 API(如 SetConsoleCursorPosition),因此仅在 Windows 系统下有效,Linux/Mac 用户需要替换为 ncurses 等库。 2. 坐标换算精度:代码中使用 X/4 和 Y/2 将屏幕像素坐标映射到数组索引,这依赖于特定的字体宽度和控制台默认设置。如果在不同分辨率或字体下运行,可能出现偏移,建议实际项目中根据当前控制台缓冲区大小动态计算步长。 3. 平局处理:当前代码未实现“满盘皆输”的平局判断,仅实现了先手五连获胜的逻辑。实际应用中可增加总步数计数,若超过225步仍未分胜负则判定为平局。 以上就是C++实现控制台双人五子棋游戏的详细内容,更多关于C++小游戏开发的资料请关注本站其它相关文章!

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。