《现代C++编程实战:从入门到应用》
第4章 语句
4.6 项目实战: 控制台Pong游戏
4.6.1 Pong 游戏的起源与影响
1972 年,美国雅达利(Atari)公司开发了《PONG》街机游戏,被认为是电子游戏历史的起点。这个游戏模拟了乒乓球比赛,玩家使用挡板(由两条竖线表示)击打一个小球(一个点)并试图让对方无法接住,以获取得分。
最初,雅达利的创始人诺兰·布什内尔(Nolan Bushnell)和泰德·达布尼(Ted Dabney)指派刚加入公司的工程师艾尔·康(Al Alcorn)开发这个游戏,并告诉他这是为 GE 公司开发的项目。尽管 Alcorn 没有游戏开发经验,但他仍然投入大量精力完成了原型。当这款游戏被安装到酒吧后,迅速引起轰动,人们争相体验,最终促使雅达利开始量产《PONG》,并带动了整个电子游戏行业的发展。随后,日本的 Taito 公司开发了类似的游戏《Elepong》,成为日本的第一款电子游戏。
下面是跨平台的C++代码:
#include <iostream>
#include <cstdlib>
#include <ctime>
#include <string>
// 判断操作系统
#define _WIN32
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#else
#include <unistd.h>
#include <term.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#endif
using namespace std;
#ifdef _WIN32
void gotoxy(int x, int y) {
COORD coord = { x, y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
}
void hideCursor() {
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
#else
void gotoxy(int x, int y) {
// 使用 ANSI 转义码进行移动
cout << "\033[" << y << ";" << x << "H";
}
void hideCursor() {
// 使用 ANSI 转义码隐藏光标
cout << "\033[?25l";
}
// 非阻塞键盘输入检测
bool _kbhit() {
struct termios oldt, newt;
int ch;
bool ret = false;
tcgetattr(STDIN_FILENO, &oldt); // 获取终端原始设置
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO); // 禁用规范模式和回显
newt.c_cc[VMIN] = 1; // 至少读取一个字符
newt.c_cc[VTIME] = 0; // 不设置超时
tcsetattr(STDIN_FILENO, TCSANOW, &newt); // 设置新的终端设置
if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK) == -1) {
std::cerr << "Error setting non-blocking mode" << std::endl;
}
ch = getchar();
if (ch != EOF) {
ret = true;
ungetc(ch, stdin); // 将字符放回标准输入流
}
tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // 恢复终端设置
return ret;
}
// 获取一个字符输入,类似于 _getch()
char _getch() {
setTerminalToRaw(); // 设置终端为原始模式
char ch = getchar(); // 获取一个字符输入
restoreTerminal(); // 恢复终端设置
return ch;
}
#endif
int main() {
// 1. 初始化游戏中的数据
auto WIDTH{ 90 }, HEIGHT{ 25 }; // 窗口长宽
auto ball_x{ WIDTH / 2 }, ball_y{ HEIGHT / 2 }, ball_vec_x{ 0 }, ball_vec_y{ 0 }; // 球位置及速度
auto paddle_w{ 3 }, paddle_h{ 8 }; // 挡板的长宽
auto paddle1_x{ 0 }, paddle1_y{ HEIGHT / 2 - paddle_h / 2 }, paddle1_vec{ 3 }; // 挡板1位置及速度
auto paddle2_x{ WIDTH - paddle_w },
paddle2_y{ HEIGHT / 2 - paddle_h / 2 }, paddle2_vec{ 3 }; // 挡板2位置及速度
auto score1{ 0 }, score2{ 0 }, score1_x{ paddle_w + 8 }, score1_y{ 2 },
score2_x{ WIDTH - 8 - paddle_w }, score2_y{ 2 };
srand((unsigned)time(0)); // 生成随机数种子
ball_vec_x = rand() % 5 + 1; // 生成一个随机整数
ball_vec_y = rand() % 5 + 1;
if (rand() % 2 == 1) ball_vec_x = -ball_vec_x;
if (rand() % 2 == 1) ball_vec_y = -ball_vec_y;
while (true) {
// 1. 处理事件
char key;
if (_kbhit()) { // 键盘有输入
key = _getch(); // 得到输入的键值
if ((key == 'w' || key == 'W') && paddle1_y > paddle1_vec)
paddle1_y -= paddle1_vec;
else if ((key == 's' || key == 'S') && paddle1_y + paddle1_vec + paddle_h < HEIGHT)
paddle1_y += paddle1_vec;
else if (key == 72 && paddle2_y > paddle2_vec)
paddle2_y -= paddle2_vec;
else if ((key == 80) && paddle2_y + paddle2_vec + paddle_h < HEIGHT)
paddle2_y += paddle2_vec;
}
// 2. 更新数据
std::string s1{ std::to_string(score1) }, s2{ std::to_string(score2) };
ball_x += ball_vec_x;
ball_y += ball_vec_y;
if (ball_y < 0 || ball_y >= HEIGHT) // 和上下墙碰撞,改变垂直速度方向
ball_vec_y = -ball_vec_y;
if (ball_x < paddle_w && ball_y >= paddle1_y && ball_y < paddle1_y + paddle_h)
{ // 和左挡板碰撞,改变水平速度的方向
ball_vec_x = -ball_vec_x;
score1 += 1;
}
else if (ball_x > WIDTH - paddle_w && ball_y >= paddle2_y
&& ball_y < paddle2_y + paddle_h)
{ // 和右挡板碰撞,改变水平速度的方向
ball_vec_x = -ball_vec_x;
score2 += 1;
}
bool is_out{ false }; // 是否跑出沟渠的bool标志
if (ball_x < 0) { score2 += 1; is_out = true; }
else if (ball_x > WIDTH) { score1 += 1; is_out = true; }
if (is_out) { // 跑出左右沟渠,球回到中心并以新的随机速度出发
ball_x = WIDTH / 2; ball_y = HEIGHT / 2;
ball_vec_x = rand() % 5 + 1;
ball_vec_y = rand() % 5 + 1;
if (rand() % 2 == 1) ball_vec_x = -ball_vec_x;
if (rand() % 2 == 1) ball_vec_y = -ball_vec_y;
}
gotoxy(0, 0); // 定位到(0,0),相当于清空屏幕
hideCursor(); // 隐藏光标
// 3. 绘制场景
// 3.1 绘制背景
for (auto x = 0; x <= WIDTH; x++)
std::cout << '=';
std::cout << '\n';
for (auto y = 0; y <= HEIGHT; y++) {
for (auto x = 0; x <= WIDTH; x++) {
if (x == ball_x && y == ball_y) // 球的位置
std::cout << 'O';
else if (y >= paddle1_y && y < paddle1_y + paddle_h
&& x >= paddle1_x && x < paddle1_x + paddle_w) { // 左挡板位置
std::cout << 'Z';
}
else if (y >= paddle2_y && y < paddle2_y + paddle_h
&& x >= paddle2_x && x < paddle2_x + paddle_w) { // 右挡板位置
std::cout << 'Z';
}
else if (y == score1_y && x == score1_x) { // 左分数位置
std::cout << s1;
x += s1.size();
x--;
}
else if (y == score2_y && x == score2_x) { // 右分数位置
std::cout << s2;
x += s2.size();
x--;
}
else if (x == 0 || x == WIDTH / 2 || x == WIDTH) // 三条竖线
std::cout << '|';
else std::cout << ' ';
}
std::cout << '\n';
}
for (auto x = 0; x <= WIDTH; x++)
std::cout << '=';
std::cout << '\n';
}
return 0;
}