C语言编程题,实现一个简易的打砖块游戏。打砖块游戏是一种经典的街机游戏,玩家通过控制一个挡板来反弹小球,击碎屏幕上方的砖块。游戏的目标是清除所有砖块而不让小球掉落。该游戏在终端中运行,使用ASCII字符进行绘制。玩家可以使用'a'和'd'键来移动挡板。游戏的目标是用挡板接住弹球,击碎所有砖块。

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设置非阻塞读取,最小读取字符数为0raw.c_cc[VTIME] = 0设置非阻塞读取,超时时间为0tcsetattr将新的终端属性应用到标准输入。

#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 = 2i < 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_dxball_dy,计算弹球的下一个位置。将弹球在旧位置的字符设为空格,表示弹球已离开该位置。如果新位置new_ball_x碰到左右墙壁(位置小于等于1或大于等于WIDTH - 2),反转横向方向ball_dx。如果新位置new_ball_y碰到上墙壁(位置小于等于1),反转纵向方向ball_dy。如果弹球的下一个位置在挡板所在的行(HEIGHT - 3),并且横坐标在挡板范围内(paddle_x - 1paddle_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;
}

推荐文档

相关文档

大家感兴趣的内容

随机列表