Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
lcd驱动
基于 RT-Thread 星火一号开发板的俄罗斯方块
发布于 2024-10-30 21:06:37 浏览:37
订阅该版
[tocm] 感谢 RB 大佬教我写游戏 本次的小游戏使用的是 RTT 的星火一号开发板,用到的驱动有 lcd 驱动和 gpio 驱动,工作之余,实现小时候的游戏,重新寻找第一次点亮开发板和第一次玩俄罗斯方块的乐趣 # 环境搭建 因为想尝试一下 vscode 调试的快乐,搭建一套 vscode 为主的代码开发环境,于是参考了一份超干货的 RTT 环境搭建教程: 【腾讯文档】星火1号+VSCode+Env环境搭建 https://docs.qq.com/doc/DY294dGJ0WldNVkxQ # menuconfig配置 搭建好环境后,就开始代码的开发啦 打开 menuconfig 中,将 LCD 的 驱动配置打开 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20241030/a8b4096f99540be98c8c03189d2cd8b0.png.webp) ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20241030/239e176d156a02998ef4d9d0ddf6dacc.png.webp) 然后就可以在 application 的文件夹下面添加应用文件啦 # 游戏配置及入口 定义了一些游戏配置,并且将游戏的主要逻辑注册到了控制台 ## tetris.h ```c #ifndef __TETRIS_H__ #define __TETRIS_H__ #include
#include
#include
#include
#include "board/ports/lcd/drv_lcd.h" #include
// 游戏配置 #define GRID_SIZE 12 #define BOARD_WIDTH 12 #define BOARD_HEIGHT 18 #define SHAPE_COUNT 7 #define PREVIEW_X (BOARD_WIDTH * GRID_SIZE + 20) #define PREVIEW_Y (20) #define PREVIEW_SIZE (4 * 12) // 游戏速度配置 #define INITIAL_DROP_INTERVAL 500 #define MIN_DROP_INTERVAL 100 #define SPEED_INCREASE_INTERVAL 30000 #define SPEED_INCREASE_STEP 100 // 按键定义 #define PIN_KEY0 GET_PIN(C, 0) #define PIN_KEY1 GET_PIN(C, 1) #define PIN_KEY2 GET_PIN(C, 4) #define PIN_WK_UP GET_PIN(C, 5) // 方块形状和颜色 extern uint8_t shapes[SHAPE_COUNT][4][4]; extern uint16_t shape_colors[SHAPE_COUNT]; // 游戏状态 typedef struct { uint8_t board[BOARD_HEIGHT][BOARD_WIDTH]; int current_x; int current_y; int current_shape; int next_shape; int score; int difficulty_level; int current_drop_interval; rt_tick_t last_speed_increase_time; uint8_t is_game_over; } GameState; extern GameState game_state; // 初始化显示 void init_display(void); void draw_game_board(void); void update_current_piece(void); // 游戏主函数 void tetris_game(void); #endif ``` ## tetris_game.c ```c #include "tetris.h" #include "tetris_render.h" #include "tetris_input.h" #include "tetris_core.h" static rt_thread_t game_thread = RT_NULL; // 游戏主循环 void tetris_game(void) { render_init(); input_init(); game_init(); while (!game_state.is_game_over) { InputState input = get_input_state(); update_game_state(input); if (game_state.is_game_over) { render_game_over(); rt_thread_mdelay(3000); break; } render_board(); render_preview(); render_score(); rt_thread_mdelay(5); } } static void game_thread_entry(void *parameter) { tetris_game(); game_thread = RT_NULL; } int tetris(void) { // 如果游戏线程已经存在,先等待其结束 if (game_thread != RT_NULL) { rt_kprintf("Game is already running\n"); return -1; } rt_kprintf("Starting Tetris game\n"); game_state.is_game_over = 0; game_thread = rt_thread_create("tetris", game_thread_entry, RT_NULL, 4096, 10, 10); if (game_thread != RT_NULL) { rt_thread_startup(game_thread); } return 0; } int stop_tetris(void) { if (game_thread != RT_NULL) { game_state.is_game_over = 1; rt_thread_mdelay(100); rt_kprintf("Game stopped\n"); } return 0; } MSH_CMD_EXPORT(tetris, start tetris game); MSH_CMD_EXPORT(stop_tetris, stop tetris game); ``` # 核心逻辑 实现了游戏初始化,碰撞检测,新方块生成,消除,难度升级等 ## tetris_core.h ```c #ifndef __TETRIS_CORE_H__ #define __TETRIS_CORE_H__ #include "tetris.h" #include "tetris_input.h" void game_init(void); void update_game_state(InputState input); int check_collision(uint8_t shape[4][4], int x, int y); void new_shape(void); void clear_lines(void); void update_difficulty(void); #endif ``` ## tetris_core.c ```c #include "tetris_core.h" GameState game_state = {0}; uint8_t shapes[SHAPE_COUNT][4][4] = { {{1,1,1,1},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, // I {{1,1,0,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}}, // O {{1,1,1,0},{0,1,0,0},{0,0,0,0},{0,0,0,0}}, // T {{1,1,0,0},{0,1,1,0},{0,0,0,0},{0,0,0,0}}, // S {{0,1,1,0},{1,1,0,0},{0,0,0,0},{0,0,0,0}}, // Z {{1,0,0,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}}, // J {{0,0,1,0},{1,1,1,0},{0,0,0,0},{0,0,0,0}} // L }; uint16_t shape_colors[SHAPE_COUNT] = { CYAN, YELLOW, MAGENTA, GREEN, GRED, BLUE, ORANGE }; static void init_random(void) { srand(rt_tick_get()); } static int get_random(int max) { return rand() % max; } int check_collision(uint8_t shape[4][4], int x, int y) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (shape[i][j]) { if (x + j < 0 || x + j >= BOARD_WIDTH || y + i >= BOARD_HEIGHT) return 1; if (y + i >= 0 && game_state.board[y + i][x + j]) return 1; } } } return 0; } static void merge_shape(void) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (shapes[game_state.current_shape][i][j]) { if (game_state.current_y + i >= 0 && game_state.current_y + i < BOARD_HEIGHT && game_state.current_x + j >= 0 && game_state.current_x + j < BOARD_WIDTH) { game_state.board[game_state.current_y + i][game_state.current_x + j] = game_state.current_shape + 1; } } } } } void clear_lines(void) { for (int i = BOARD_HEIGHT - 1; i >= 0; i--) { int full = 1; for (int j = 0; j < BOARD_WIDTH; j++) { if (!game_state.board[i][j]) { full = 0; break; } } if (full) { game_state.score += 100; for (int k = i; k > 0; k--) { for (int j = 0; j < BOARD_WIDTH; j++) { game_state.board[k][j] = game_state.board[k-1][j]; } } i++; } } } void new_shape(void) { game_state.current_shape = game_state.next_shape; game_state.next_shape = get_random(SHAPE_COUNT); game_state.current_x = BOARD_WIDTH / 2 - 2; game_state.current_y = 0; if (check_collision(shapes[game_state.current_shape], game_state.current_x, game_state.current_y)) { game_state.is_game_over = 1; } } void update_difficulty(void) { rt_tick_t now = rt_tick_get(); if (now - game_state.last_speed_increase_time >= rt_tick_from_millisecond(SPEED_INCREASE_INTERVAL)) { if (game_state.current_drop_interval > MIN_DROP_INTERVAL) { game_state.current_drop_interval -= SPEED_INCREASE_STEP; if (game_state.current_drop_interval < MIN_DROP_INTERVAL) { game_state.current_drop_interval = MIN_DROP_INTERVAL; } game_state.difficulty_level++; game_state.last_speed_increase_time = now; } } } void game_init(void) { rt_memset(&game_state, 0, sizeof(GameState)); game_state.current_drop_interval = INITIAL_DROP_INTERVAL; game_state.last_speed_increase_time = rt_tick_get(); game_state.next_shape = get_random(SHAPE_COUNT); init_random(); new_shape(); } void update_game_state(InputState input) { static rt_tick_t last_drop_time = 0; rt_tick_t now = rt_tick_get(); if (game_state.is_game_over) return; if (input.move_left && !check_collision(shapes[game_state.current_shape], game_state.current_x - 1, game_state.current_y)) { game_state.current_x--; clear_input_state(); } if (input.move_right && !check_collision(shapes[game_state.current_shape], game_state.current_x + 1, game_state.current_y)) { game_state.current_x++; clear_input_state(); } if (input.rotate) { uint8_t new_shape[4][4] = {0}; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { new_shape[i][j] = shapes[game_state.current_shape][3-j][i]; } } if (!check_collision(new_shape, game_state.current_x, game_state.current_y)) { rt_memcpy(shapes[game_state.current_shape], new_shape, sizeof(new_shape)); } clear_input_state(); } if (input.drop) { while (!check_collision(shapes[game_state.current_shape], game_state.current_x, game_state.current_y + 1)) { game_state.current_y++; } clear_input_state(); } if (now - last_drop_time >= rt_tick_from_millisecond(game_state.current_drop_interval)) { last_drop_time = now; if (!check_collision(shapes[game_state.current_shape], game_state.current_x, game_state.current_y + 1)) { game_state.current_y++; } else { merge_shape(); clear_lines(); new_shape(); } } update_difficulty(); } ``` # 界面渲染 这个是负责渲染界面的逻辑,包括分数,难度,方块预测 ## tetris_render.h ```c #ifndef __TETRIS_RENDER_H__ #define __TETRIS_RENDER_H__ #include "tetris.h" void render_init(void); void render_board(void); void render_preview(void); void render_score(void); void render_game_over(void); void draw_block(int x, int y, uint16_t color); void update_current_piece_buffer(void); #endif ``` ## tetris_render.c ```c #include "tetris_render.h" #define MAX(a, b) ((a) > (b) ? (a) : (b)) static uint8_t board_buffer[BOARD_HEIGHT][BOARD_WIDTH] = {0}; static uint16_t current_piece_buffer[BOARD_HEIGHT][BOARD_WIDTH] = {0}; void render_init(void) { drv_lcd_init(); lcd_clear(BLACK); // 绘制游戏区域边框 lcd_draw_rectangle(0, 0, BOARD_WIDTH * GRID_SIZE, BOARD_HEIGHT * GRID_SIZE); // 绘制预览区域边框 lcd_draw_rectangle(PREVIEW_X, PREVIEW_Y, PREVIEW_X + PREVIEW_SIZE, PREVIEW_Y + PREVIEW_SIZE); } void draw_block(int x, int y, uint16_t color) { lcd_fill(x * GRID_SIZE, y * GRID_SIZE, (x + 1) * GRID_SIZE, (y + 1) * GRID_SIZE, color); } void update_current_piece_buffer(void) { // 清空当前方块缓冲区 rt_memset(current_piece_buffer, 0, sizeof(current_piece_buffer)); // 更新当前方块到缓冲区 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (shapes[game_state.current_shape][i][j] && game_state.current_y + i >= 0 && game_state.current_y + i < BOARD_HEIGHT && game_state.current_x + j >= 0 && game_state.current_x + j < BOARD_WIDTH) { current_piece_buffer[game_state.current_y + i][game_state.current_x + j] = shape_colors[game_state.current_shape]; } } } } void render_board(void) { // 先更新当前方块的缓冲区 update_current_piece_buffer(); lcd_draw_rectangle(0, 0, BOARD_WIDTH * GRID_SIZE, BOARD_HEIGHT * GRID_SIZE); // 其余渲染代码保持不变 for (int i = 0; i < BOARD_HEIGHT; i++) { for (int j = 0; j < BOARD_WIDTH; j++) { uint16_t color = BLACK; if (current_piece_buffer[i][j] != 0) { color = current_piece_buffer[i][j]; } else if (game_state.board[i][j] != 0) { color = shape_colors[game_state.board[i][j] - 1]; } if (board_buffer[i][j] != color) { draw_block(j, i, color); board_buffer[i][j] = color; } } } } void render_preview(void) { // 扩大清除区域 lcd_fill(PREVIEW_X, PREVIEW_Y, PREVIEW_X + PREVIEW_SIZE, PREVIEW_Y + PREVIEW_SIZE, BLACK); // 重绘预览框 lcd_draw_rectangle(PREVIEW_X, PREVIEW_Y, PREVIEW_X + PREVIEW_SIZE, PREVIEW_Y + PREVIEW_SIZE); uint16_t color = shape_colors[game_state.next_shape]; // 计算方块的实际大小 int width = 0, height = 0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (shapes[game_state.next_shape][i][j]) { width = MAX(width, j + 1); height = MAX(height, i + 1); } } } // 居中显示 int offset_x = (PREVIEW_SIZE - width * GRID_SIZE) / 2; int offset_y = (PREVIEW_SIZE - height * GRID_SIZE) / 2; // 渲染方块 for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (shapes[game_state.next_shape][i][j]) { int x = PREVIEW_X + offset_x + j * GRID_SIZE; int y = PREVIEW_Y + offset_y + i * GRID_SIZE; lcd_fill(x, y, x + GRID_SIZE - 1, y + GRID_SIZE - 1, color); } } } } void render_score(void) { // 调整分数显示位置,移到预览区域下方 lcd_show_string(PREVIEW_X, PREVIEW_Y + PREVIEW_SIZE + 20, 16, "Score:"); lcd_show_num(PREVIEW_X, PREVIEW_Y + PREVIEW_SIZE + 40, game_state.score, 5, 16); lcd_show_string(PREVIEW_X, PREVIEW_Y + PREVIEW_SIZE + 70, 16, "Level:"); lcd_show_num(PREVIEW_X, PREVIEW_Y + PREVIEW_SIZE + 90, game_state.difficulty_level + 1, 2, 16); } void render_game_over(void) { lcd_clear(RED); // 调整游戏结束显示的位置,使其更居中 lcd_show_string(70, 100, 24, "Game Over!"); lcd_show_string(70, 130, 16, "Score:"); lcd_show_num(130, 130, game_state.score, 5, 16); lcd_show_string(70, 160, 16, "Level:"); lcd_show_num(130, 160, game_state.difficulty_level + 1, 2, 16); } ``` # 输入处理 这个是检测玩家输入的线程 ## tetris_input.h ```c #ifndef __TETRIS_INPUT_H__ #define __TETRIS_INPUT_H__ #include "tetris.h" typedef struct { int move_left; int move_right; int rotate; int drop; } InputState; void input_init(void); void input_deinit(void); InputState get_input_state(void); void clear_input_state(void); #endif ``` ## tetris_input.c ```c #include "tetris_input.h" #define KEY_SCAN_INTERVAL 1 #define KEY_HOLD_THRESHOLD 300 // 长按阈值(毫秒) static struct { rt_uint8_t last_state; rt_uint8_t current_state; rt_uint32_t hold_time; } keys[4]; static InputState input_state = {0}; static rt_thread_t key_thread = RT_NULL; static rt_sem_t exit_sem = RT_NULL; static rt_mutex_t input_mutex = RT_NULL; static void key_thread_entry(void *parameter) { rt_tick_t last_scan_time = rt_tick_get(); while (!game_state.is_game_over) { rt_tick_t now = rt_tick_get(); if (now - last_scan_time >= rt_tick_from_millisecond(KEY_SCAN_INTERVAL)) { last_scan_time = now; keys[0].current_state = rt_pin_read(PIN_KEY0); keys[1].current_state = rt_pin_read(PIN_KEY1); keys[2].current_state = rt_pin_read(PIN_KEY2); keys[3].current_state = rt_pin_read(PIN_WK_UP); for (int i = 0; i < 4; i++) { if (keys[i].current_state == PIN_LOW && keys[i].last_state == PIN_HIGH) { // 按键按下时打印 rt_kprintf("Key %d pressed\n", i); keys[i].hold_time = 0; switch (i) { case 0: input_state.move_left = 1; rt_kprintf("Move left\n"); break; case 1: input_state.drop = 1; rt_kprintf("Drop\n"); break; case 2: input_state.move_right = 1; rt_kprintf("Move right\n"); break; case 3: input_state.rotate = 1; rt_kprintf("Rotate\n"); break; } } else if (keys[i].current_state == PIN_LOW) { // 按键持续按下 keys[i].hold_time += KEY_SCAN_INTERVAL; if (keys[i].hold_time > KEY_HOLD_THRESHOLD) { // 长按触发时打印 switch (i) { case 0: input_state.move_left = 1; rt_kprintf("Long press: Move left\n"); break; case 2: input_state.move_right = 1; rt_kprintf("Long press: Move right\n"); break; } } } else if (keys[i].current_state == PIN_HIGH && keys[i].last_state == PIN_LOW) { // 按键释放时打印 rt_kprintf("Key %d released\n", i); } keys[i].last_state = keys[i].current_state; } } rt_thread_mdelay(1); } } void input_init(void) { rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP); rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP); rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP); rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT_PULLUP); key_thread = rt_thread_create("key_thread", key_thread_entry, RT_NULL, 1024, 5, 5); if (key_thread != RT_NULL) { rt_thread_startup(key_thread); } input_mutex = rt_mutex_create("input_mutex", RT_IPC_FLAG_FIFO); } InputState get_input_state(void) { InputState state; rt_mutex_take(input_mutex, RT_WAITING_FOREVER); state = input_state; rt_mutex_release(input_mutex); return state; } void clear_input_state(void) { rt_mutex_take(input_mutex, RT_WAITING_FOREVER); rt_memset(&input_state, 0, sizeof(InputState)); rt_mutex_release(input_mutex); } ``` # 驱动修改 颜色有些不够用,增加了一个橘色 ## drv_lcd.h ```c //POINT_COLOR #define WHITE 0xFFFF #define BLACK 0x0000 #define BLUE 0x001F #define BRED 0XF81F #define GRED 0XFFE0 #define GBLUE 0X07FF #define RED 0xF800 #define MAGENTA 0xF81F #define GREEN 0x07E0 #define CYAN 0x7FFF #define YELLOW 0xFFE0 #define BROWN 0XBC40 #define BRRED 0XFC07 #define ORANGE 0XFF20 #define GRAY 0X8430 #define GRAY175 0XAD75 #define GRAY151 0X94B2 #define GRAY187 0XBDD7 #define GRAY240 0XF79E ``` # 效果演示 打开命令行,输入命令即可开始游戏啦 ``` msh> tetris ``` 最后附上完整代码、游玩视频和编译好的 hex 完整代码:[tetris.zip](https://club.rt-thread.org/file_download/680a6d19f6cf8db2) hex:[rtthread.hex](https://club.rt-thread.org/file_download/65d82d3ad60bd62f) 视频:未末喵喵刚刚发布的视频https://b23.tv/SfAUQOw
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
weimo
这家伙很懒,什么也没写!
文章
1
回答
1
被采纳
0
关注TA
发私信
相关文章
1
为啥没有 LCD 驱动框架呢
2
RT-Thread Studio 环境下如何实现LCD屏的翻转?
3
大家都是如何使用stm32驱动nt35510这个LCD显示IC的啊
4
请问stm32f103vet6怎么做ili9341 TFT_LCD显示啊?
5
RT-Thread Stduio + ST7789LCD液晶显示屏
6
用户手册LCD例程使用问题
7
星火一号LCD取模软件有没有
8
在rtthread系统中STM32103VET6使用硬件FSMC驱动LCD刷屏速度慢
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
ART-Pi
FinSH
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
cubemx
PWM
flash
packages_软件包
freemodbus
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
keil_MDK
SFUD
rt_mq_消息队列_msg_queue
C++_cpp
at_device
ulog
本月问答贡献
用户名由3_15位
20
个答案
3
次被采纳
张世争
8
个答案
3
次被采纳
rv666
11
个答案
2
次被采纳
加缪
16
个答案
1
次被采纳
a1012112796
10
个答案
1
次被采纳
本月文章贡献
alight
3
篇文章
10
次点赞
AJS琥珀清年
2
篇文章
5
次点赞
Cfly
1
篇文章
12
次点赞
Ghost_Girls
1
篇文章
8
次点赞
KunYi
1
篇文章
5
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部