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; }