Pong 是史上第一款商业电子游戏。在控制台里做 Pong 不需要图形库:用 ANSI 转义序列清屏重绘,用 termios 实现无阻塞按键,一个 while 循环就能跑出实时动画。这也是"游戏主循环 + 帧率控制"的入门套路。
玩法介绍
60×20 场地里小球自动运动,碰左右上墙反弹;底部挡板用 a/d 移动接球,接到得分,漏接结束,按 q 退出。每帧暂停 80 毫秒,动画就"动"起来了。
第 1 步:场地与状态变量
游戏状态就是一组变量:球的位置、速度分量、挡板位置。速度每帧叠加到位置,就产生了运动——最简单的"物理":
#include <iostream>
#include <thread>
#include <chrono>
#include <cstdlib>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
const int W = 60, H = 20;
int ballX = W / 2, ballY = H / 2;
int vx = 1, vy = 1; // 每帧移动 1 格
int paddleX = W / 2 - 3; // 挡板左端坐标
const int paddleW = 6;
int score = 0;
第 2 步:无阻塞键盘输入
默认 std::cin 要等回车,游戏可等不起。用 termios 关掉"行缓冲 + 回显",用 fcntl 把标准输入设为非阻塞:有键立即返回,没键返回 -1:
void setNonBlocking() {
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
}
void hideEcho() {
termios t;
tcgetattr(STDIN_FILENO, &t);
t.c_lflag &= ~(ICANON | ECHO); // 关行缓冲与回显
tcsetattr(STDIN_FILENO, TCSANOW, &t);
}
int readKey() {
char c;
if (read(STDIN_FILENO, &c, 1) == 1) return c;
return -1;
}
第 3 步:绘制一帧
动画原理:先清屏再整帧重画。\033[2J 清屏、\033[H 光标回左上角。逐字符判断:球画 O,挡板画 =,墙画 | 和 -:
void draw() {
std::cout << "\033[2J\033[H"; // 清屏 + 光标回原点
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (x == ballX && y == ballY) std::cout << "O";
else if (y == H - 2 && x >= paddleX && x < paddleX + paddleW)
std::cout << "=";
else if (x == 0 || x == W - 1) std::cout << "|";
else if (y == 0) std::cout << "-";
else std::cout << " ";
}
std::cout << "\n";
}
std::cout << "得分:" << score << " a/d 移动,q 退出\n";
}
第 4 步:球的运动与碰撞
每帧把速度加到位置。撞左右墙或上墙时速度取反,球就"反弹"。球到挡板行时判断:接住则反弹得分,否则游戏结束:
void update() {
ballX += vx;
ballY += vy;
if (ballX <= 1 || ballX >= W - 2) vx = -vx; // 左右墙
if (ballY <= 1) vy = -vy; // 上墙
if (ballY == H - 3) { // 挡板行
if (ballX >= paddleX && ballX < paddleX + paddleW) {
vy = -vy;
++score;
} else {
std::cout << "游戏结束!得分:" << score << "\n";
std::exit(0);
}
}
if (ballY >= H - 1) ballY = H - 2; // 出界保护
}
第 5 步:主循环与编译运行
主循环节奏固定:读按键 → 移挡板 → 更新球 → 绘制 → 睡 80 毫秒。sleep_for 控制帧率。把第 1~4 步的代码与下面的 main() 存进 pong.cpp:
int main() {
setNonBlocking();
hideEcho();
while (true) {
int key = readKey();
if (key == 'a' || key == 'A') paddleX -= 2;
else if (key == 'd' || key == 'D') paddleX += 2;
else if (key == 'q') break;
if (paddleX < 1) paddleX = 1;
if (paddleX + paddleW > W - 1) paddleX = W - 1 - paddleW;
update();
draw();
std::this_thread::sleep_for(std::chrono::milliseconds(80));
}
return 0;
}
g++ -std=c++17 -o pong pong.cpp
./pong
本代码面向 Linux / macOS 的 g++;Windows 用户可换成 <conio.h> 的 _kbhit() / _getch(),其余不变。
💡 改进方向:① 接球位置决定反弹角度,更有手感;② 每得 5 分加速;③ 加第二个挡板双人对战;④ 用 std::vector 记录最高分并存入文件。