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