1、全局变量和结构体
struct termios orig_termios
用于保存终端的原始设置,以便在程序结束时恢复。board[HEIGHT][WIDTH]
二维字符数组,表示游戏板,每个元素对应屏幕上的一个位置。ball_x
, ball_y
弹球的当前坐标(横坐标和纵坐标)。ball_dx
, ball_dy
弹球的移动方向,ball_dx
为横向,ball_dy
为纵向。paddle_x
挡板的横向位置,挡板在纵向上是固定的。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
struct termios orig_termios;
char board[HEIGHT][WIDTH];
int ball_x, ball_y;
int ball_dx = 1, ball_dy = -1;
int paddle_x;
int main() {
printf("Hello, World!");
return 0;
}
2、终端模式设置函数
使用tcsetattr
函数将终端设置回原始模式,STDIN_FILENO
表示标准输入文件描述符,TCSAFLUSH
表示在更改属性前刷新输入和输出队列。tcgetattr
获取终端的当前属性,保存到orig_termios中atexit(disableRawMode)
注册程序退出时要调用的函数,确保程序结束时恢复终端的原始设置。raw.c_lflag &= ~(ECHO | ICANON)
关闭回显(ECHO)和规范模式(ICANON),使输入字符不需要按回车键即可被读取。raw.c_cc[VMIN] = 0
设置非阻塞读取,最小读取字符数为0
。raw.c_cc[VTIME] = 0
设置非阻塞读取,超时时间为0
。tcsetattr
将新的终端属性应用到标准输入。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
struct termios orig_termios;
char board[HEIGHT][WIDTH];
int ball_x, ball_y;
int ball_dx = 1, ball_dy = -1;
int paddle_x;
void disableRawMode() {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}
void enableRawMode() {
struct termios raw;
tcgetattr(STDIN_FILENO, &orig_termios);
atexit(disableRawMode);
raw = orig_termios;
raw.c_lflag &= ~(ECHO | ICANON);
raw.c_cc[VMIN] = 0; // 最小读取字符数
raw.c_cc[VTIME] = 0; // 超时时间
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
int main() {
printf("Hello, World!");
return 0;
}
3、设置非阻塞输入
cntl
用于操作文件描述符。F_GETFL
获取文件描述符的标志。F_SETFL
设置文件描述符的标志。O_NONBLOCK
设置为非阻塞模式,使读取操作不会因为没有数据而阻塞。该函数使标准输入变为非阻塞模式,读取输入时如果没有数据会立即返回,而不是等待。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
struct termios orig_termios;
char board[HEIGHT][WIDTH];
int ball_x, ball_y;
int ball_dx = 1, ball_dy = -1;
int paddle_x;
void setNonBlocking() {
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
}
int main() {
printf("Hello, World!");
return 0;
}
4、初始化游戏
初始化游戏板,将board
数组的每个位置初始化为空格字符' '
,表示空白位置。
设置墙壁,左右边界设置为竖线'|'
,上下边界设置为横线'-'
。
设置砖块,从第2
行到第5
行(i = 2
到i < 6
),在每一行的特定位置放置砖块'#'
。砖块在水平方向上每隔一个位置放置一次(j += 2
),形成均匀的砖块排列。
设置挡板,挡板位于倒数第二行(HEIGHT - 2
),初始位置在屏幕中间(WIDTH / 2
)。挡板由三个等号'='
组成,表示挡板的宽度为3
。
设置弹球,弹球初始位置在挡板上方一行的中间位置(HEIGHT - 3,WIDTH / 2)
。用字符'O'
表示弹球。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
struct termios orig_termios;
char board[HEIGHT][WIDTH];
int ball_x, ball_y;
int ball_dx = 1, ball_dy = -1;
int paddle_x;
void initGame() {
int i, j;
// 初始化游戏板
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j++) {
board[i][j] = ' ';
}
}
// 设置墙壁
for (i = 0; i < HEIGHT; i++) {
board[i][0] = '|';
board[i][WIDTH - 1] = '|';
}
for (j = 0; j < WIDTH; j++) {
board[0][j] = '-';
board[HEIGHT - 1][j] = '-';
}
// 设置砖块
for (i = 2; i < 6; i++) {
for (j = 2; j < WIDTH - 2; j += 2) {
board[i][j] = '#';
}
}
// 设置挡板
paddle_x = WIDTH / 2;
board[HEIGHT - 2][paddle_x - 1] = '=';
board[HEIGHT - 2][paddle_x] = '=';
board[HEIGHT - 2][paddle_x + 1] = '=';
// 设置弹球
ball_x = WIDTH / 2;
ball_y = HEIGHT - 3;
board[ball_y][ball_x] = 'O';
}
int main() {
printf("Hello, World!");
return 0;
}
5、绘制游戏板和更新游戏状态
清屏,使用ANSI转义序列\033[H\033[J
清除屏幕并将光标移到左上角。绘制游戏板,双重循环遍历board
数组,将每个字符输出到屏幕上。每行结束后输出一个换行符。刷新输出缓冲区,使用fflush(stdout)
确保所有输出立即显示。根据当前方向ball_dx
和ball_dy
,计算弹球的下一个位置。将弹球在旧位置的字符设为空格,表示弹球已离开该位置。如果新位置new_ball_x
碰到左右墙壁(位置小于等于1
或大于等于WIDTH - 2
),反转横向方向ball_dx
。如果新位置new_ball_y
碰到上墙壁(位置小于等于1
),反转纵向方向ball_dy
。如果弹球的下一个位置在挡板所在的行(HEIGHT - 3
),并且横坐标在挡板范围内(paddle_x - 1
到paddle_x + 1
),则反转纵向方向ball_dy
,表示弹球被挡板反弹。 如果弹球的下一个位置有砖块(字符为'#'
),则移除该砖块,将其位置设为空格,并反转纵向方向ball_dy
。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
struct termios orig_termios;
char board[HEIGHT][WIDTH];
int ball_x, ball_y;
int ball_dx = 1, ball_dy = -1;
int paddle_x;
void drawBoard() {
int i, j;
// 使用ANSI转义序列清屏
printf("\033[H\033[J");
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j++) {
putchar(board[i][j]);
}
putchar('\n');
}
fflush(stdout);
}
void updateGame() {
int new_ball_x = ball_x + ball_dx;
int new_ball_y = ball_y + ball_dy;
// 从旧位置移除弹球
board[ball_y][ball_x] = ' ';
// 检查墙壁碰撞
if (new_ball_x <= 1 || new_ball_x >= WIDTH - 2) {
ball_dx = -ball_dx;
new_ball_x = ball_x + ball_dx;
}
if (new_ball_y <= 1) {
ball_dy = -ball_dy;
new_ball_y = ball_y + ball_dy;
}
// 检查与挡板的碰撞
if (new_ball_y == HEIGHT - 3) {
if (new_ball_x >= paddle_x - 1 && new_ball_x <= paddle_x + 1) {
ball_dy = -ball_dy;
new_ball_y = ball_y + ball_dy;
}
}
// 检查与砖块的碰撞
if (board[new_ball_y][new_ball_x] == '#') {
ball_dy = -ball_dy;
board[new_ball_y][new_ball_x] = ' ';
new_ball_y = ball_y + ball_dy;
}
// 更新弹球位置
ball_x = new_ball_x;
ball_y = new_ball_y;
// 在新位置放置弹球
board[ball_y][ball_x] = 'O';
}
int main() {
printf("Hello, World!");
return 0;
}
6、完整示例
一个简易的打砖块游戏,可以根据自己的需求进行扩展和完善。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#define WIDTH 40
#define HEIGHT 20
struct termios orig_termios;
char board[HEIGHT][WIDTH];
int ball_x, ball_y;
int ball_dx = 1, ball_dy = -1;
int paddle_x;
void disableRawMode() {
tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios);
}
void enableRawMode() {
struct termios raw;
tcgetattr(STDIN_FILENO, &orig_termios);
atexit(disableRawMode);
raw = orig_termios;
raw.c_lflag &= ~(ECHO | ICANON);
raw.c_cc[VMIN] = 0; // 最小读取字符数
raw.c_cc[VTIME] = 0; // 超时时间
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
void setNonBlocking() {
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
}
void initGame() {
int i, j;
// 初始化游戏板
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j++) {
board[i][j] = ' ';
}
}
// 设置墙壁
for (i = 0; i < HEIGHT; i++) {
board[i][0] = '|';
board[i][WIDTH - 1] = '|';
}
for (j = 0; j < WIDTH; j++) {
board[0][j] = '-';
board[HEIGHT - 1][j] = '-';
}
// 设置砖块
for (i = 2; i < 6; i++) {
for (j = 2; j < WIDTH - 2; j += 2) {
board[i][j] = '#';
}
}
// 设置挡板
paddle_x = WIDTH / 2;
board[HEIGHT - 2][paddle_x - 1] = '=';
board[HEIGHT - 2][paddle_x] = '=';
board[HEIGHT - 2][paddle_x + 1] = '=';
// 设置弹球
ball_x = WIDTH / 2;
ball_y = HEIGHT - 3;
board[ball_y][ball_x] = 'O';
}
void drawBoard() {
int i, j;
// 使用ANSI转义序列清屏
printf("\033[H\033[J");
for (i = 0; i < HEIGHT; i++) {
for (j = 0; j < WIDTH; j++) {
putchar(board[i][j]);
}
putchar('\n');
}
fflush(stdout);
}
void updateGame() {
int new_ball_x = ball_x + ball_dx;
int new_ball_y = ball_y + ball_dy;
// 从旧位置移除弹球
board[ball_y][ball_x] = ' ';
// 检查墙壁碰撞
if (new_ball_x <= 1 || new_ball_x >= WIDTH - 2) {
ball_dx = -ball_dx;
new_ball_x = ball_x + ball_dx;
}
if (new_ball_y <= 1) {
ball_dy = -ball_dy;
new_ball_y = ball_y + ball_dy;
}
// 检查与挡板的碰撞
if (new_ball_y == HEIGHT - 3) {
if (new_ball_x >= paddle_x - 1 && new_ball_x <= paddle_x + 1) {
ball_dy = -ball_dy;
new_ball_y = ball_y + ball_dy;
}
}
// 检查与砖块的碰撞
if (board[new_ball_y][new_ball_x] == '#') {
ball_dy = -ball_dy;
board[new_ball_y][new_ball_x] = ' ';
new_ball_y = ball_y + ball_dy;
}
// 更新弹球位置
ball_x = new_ball_x;
ball_y = new_ball_y;
// 在新位置放置弹球
board[ball_y][ball_x] = 'O';
}
void processInput() {
char c;
while (read(STDIN_FILENO, &c, 1) == 1) {
if (c == 'a') {
// 向左移动挡板
if (paddle_x > 2) {
// 移除旧挡板
board[HEIGHT - 2][paddle_x - 1] = ' ';
board[HEIGHT - 2][paddle_x] = ' ';
board[HEIGHT - 2][paddle_x + 1] = ' ';
paddle_x--;
// 绘制新挡板
board[HEIGHT - 2][paddle_x - 1] = '=';
board[HEIGHT - 2][paddle_x] = '=';
board[HEIGHT - 2][paddle_x + 1] = '=';
}
} else if (c == 'd') {
// 向右移动挡板
if (paddle_x < WIDTH - 3) {
// 移除旧挡板
board[HEIGHT - 2][paddle_x - 1] = ' ';
board[HEIGHT - 2][paddle_x] = ' ';
board[HEIGHT - 2][paddle_x + 1] = ' ';
paddle_x++;
// 绘制新挡板
board[HEIGHT - 2][paddle_x - 1] = '=';
board[HEIGHT - 2][paddle_x] = '=';
board[HEIGHT - 2][paddle_x + 1] = '=';
}
} else if (c == 'q') {
// 退出游戏
disableRawMode();
exit(0);
}
}
}
int main() {
enableRawMode();
setNonBlocking();
initGame();
while (1) {
processInput();
updateGame();
drawBoard();
usleep(50000); // 休眠50毫秒
// 检查游戏是否结束
if (ball_y >= HEIGHT - 2) {
printf("游戏结束!\n");
break;
}
}
disableRawMode();
return 0;
}