C++控制台实现双人五子棋:原理与完整代码
在控制台中使用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步:交互逻辑与落子流程
游戏循环交替执行以下操作:
- 等待输入:使用
_getch()函数阻塞等待按键,该函数无需按回车即可获取输入值,适合实时控制。 - 方向移动:识别方向键虚拟码(上72, 下80, 左75, 右77)。每次按键仅移动一格,且必须落在空位(值为0的位置)。
- 确认落子:当按下回车键(值13)时,记录当前光标所在的
COORD坐标,将其转换为数组索引(x/4, y/2)(因屏幕字符间距原因进行的比例换算),并将对应数组位置置为当前玩家标识。

关键算法实现细节
第4步:胜负判定逻辑 (win 函数)
每落一子后,检查全盘是否存在连续五个同色棋子。算法遍历四个方向:
- 横向扫描:遍历每一行
i,维护一个计数器f。若当前格子有棋子则f++,否则f=0。当f达到5时返回胜利标志。 - 竖向扫描:类似横向,遍历列
j。 - 下斜(↘)扫描:从左上到右下。注意边界限制,起始点范围为
i: 0~12,j: 0~12,步长增加。 - 上斜(↗)扫描:从左下到右上。起始点范围为
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++小游戏开发的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













