一.前言
1.使用到的屏幕是TFT屏,如果是OLED屏需要改一下显示的接口
2.上下左右四个按键按照自己的实际硬件配置就行
3.我是在rt-thread例程上改的,部分延时函数等需要另外实现
二.完整代码
#include <rtdevice.h>
#include <board.h>
#include <drv_lcd.h>
#include <rttlogo.h>
#include "stm32l4xx_hal.h"
#include <stdlib.h>
#include <time.h>
#include <rtdbg.h>
#ifndef bool
#define bool _Bool
#define true 1
#define false 0
#endif
// 假设的Direction枚举定义
typedef enum {
Direction_Up,
Direction_Down,
Direction_Left,
Direction_Right,
} Direction;
Direction dir = Direction_Right;
// 屏幕尺寸
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 240
// 蛇的尺寸
#define SNAKE_SIZE 10
// 更新间隔时间
#define DELAY_MS 100
#define MAX_SNAKE_LENGTH 100 // 假设蛇的最大长度
typedef struct {
int x;
int y;
} Point;
// 初始化蛇体数组
Point snakeBody[MAX_SNAKE_LENGTH];
int snakeLength = 0; // 记录蛇的实际长度
Point generateRandomFoodPosition(const Point snakeBody[], int length);
bool isPositionInSnake(const Point *position, const Point snakeBody[], int length);
void initSnake(Point snakeBody[], int* snakeLength);
void resetSnake(Point snakeBody[], int* snakeLength, Direction* dir);
int updateSnake(Point snakeBody[], int* snakeLength, Direction dir, Point* food, bool* hasEaten);
//3x3像素合并的绘制函数
void lcd_draw_super_pixel(int x, int y, uint16_t color) {
// 计算超像素左上角的实际像素坐标
int superPixelX = x * 3;
int superPixelY = y * 3;
// 填充3x3像素块
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
lcd_draw_point(superPixelX + i, superPixelY + j, color);
}
}
}
// 生成随机食物位置函数需要修改为接受最大长度和当前蛇体数组
// 生成不在蛇身上的随机食物坐标
Point generateRandomFoodPosition(const Point snakeBody[], int length){
srand((unsigned int)rt_tick_get());
Point foodPosition;
do {
foodPosition.x = rand() % SCREEN_WIDTH;
foodPosition.y = rand() % SCREEN_HEIGHT;
} while (isPositionInSnake(&foodPosition, snakeBody, snakeLength));
return foodPosition;
}
// 检查给定坐标是否在蛇身中
bool isPositionInSnake(const Point *position, const Point snakeBody[], int length) {
for (int i = 0; i < length; i++) {
if (snakeBody[i].x == position->x && snakeBody[i].y == position->y) {
return true;
}
}
return false;
}
// 初始化蛇
void initSnake(Point snakeBody[], int* snakeLength)
{
*snakeLength = SNAKE_SIZE;
for(int i = 0; i < SNAKE_SIZE; ++i)
{
snakeBody[i].x = 120+i;
snakeBody[i].y = 120;
}
}
// 重置蛇状态
void resetSnake(Point snakeBody[], int* snakeLength, Direction* dir) {
*snakeLength = SNAKE_SIZE; // 重置蛇长度
*dir = Direction_Right; // 或其他默认方向
// 无需free,因为snakeBody是静态分配的
// 重新调用initSnake初始化蛇的位置
initSnake(snakeBody, snakeLength);
}
// 更新蛇的位置,并处理增长逻辑
int updateSnake(Point snakeBody[], int* snakeLength, Direction dir, Point* food, bool* hasEaten)
{
Point lodHead = snakeBody[*snakeLength - 1];
// 移动蛇的每一个部分,从尾巴开始向前移动
for(int i = *snakeLength - 1; i > 0; --i)
{
snakeBody[i] = snakeBody[i - 1]; // 每个节点变为前一个节点的位置
}
// 移动蛇头
switch (dir)
{
case Direction_Up: snakeBody[0].y--; break;
case Direction_Down: snakeBody[0].y++; break;
case Direction_Left: snakeBody[0].x--; break;
case Direction_Right: snakeBody[0].x++; break;
}
// 检查蛇头是否撞墙
if (snakeBody[0].x < 0 || snakeBody[0].x >= SCREEN_WIDTH || snakeBody[0].y < 0 || snakeBody[0].y >= SCREEN_HEIGHT) {
return -1; // 表示游戏结束
}
// 检查蛇是否撞到自己(除了头部)
for(int i = 1; i < *snakeLength; ++i) {
if (snakeBody[i].x == snakeBody[0].x && snakeBody[i].y == snakeBody[0].y) {
return -1; // 表示游戏结束
}
}
// 检查是否吃到食物
if (snakeBody[0].x == food->x && snakeBody[0].y == food->y)
{
snakeBody[*snakeLength] = lodHead;
*snakeLength++;
*hasEaten = true; // 设置吃到食物标志
return 1; // 返回特殊状态码表示吃到食物
}
return 0; // 更新成功
}
// 使用超像素渲染蛇到LCD
void renderSnake(const Point* snakeBody, int snakeLength) {
// 清除屏幕
lcd_clear(BLACK);
// 绘制蛇
for(int i = 0; i < snakeLength; ++i) {
lcd_draw_super_pixel(snakeBody[i].x, snakeBody[i].y, GREEN);
}
// 绘制蛇头,可以考虑使用不同的颜色或样式以突出
lcd_draw_super_pixel(snakeBody[0].x, snakeBody[0].y, RED);
}
// 食物的绘制也需要更新为使用超像素
void renderFood(const Point* food) {
lcd_draw_super_pixel(food->x, food->y, YELLOW);
}
// 检查按键并改变方向
void checkInput(Direction* dir, int key)
{
// 防止蛇立即反向移动的逻辑
switch(key) {
case 2: // 左
if(*dir != Direction_Right) *dir = Direction_Left;
break;
case 1: // 下
if(*dir != Direction_Up) *dir = Direction_Down;
break;
case 0: // 右
if(*dir != Direction_Left) *dir = Direction_Right;
break;
case 3: // 上
if(*dir != Direction_Down) *dir = Direction_Up;
break;
default:
break;
}
}
//按键初始化函数
void key_init(void) {
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT);
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT);
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT);
rt_pin_mode(PIN_WK_UP, PIN_MODE_INPUT);
}
//按键扫描函数
rt_int16_t key_scan(void) {
if (rt_pin_read(PIN_KEY0) == PIN_LOW) {
rt_thread_mdelay(20);
if (rt_pin_read(PIN_KEY0) == PIN_LOW) {
return 0;
}
} else if (rt_pin_read(PIN_KEY1) == PIN_LOW) {
rt_thread_mdelay(20);
if (rt_pin_read(PIN_KEY1) == PIN_LOW) {
return 1;
}
} else if (rt_pin_read(PIN_KEY2) == PIN_LOW) {
rt_thread_mdelay(20);
if(rt_pin_read(PIN_KEY2) == PIN_LOW) {
return 2;
}
} else if (rt_pin_read(PIN_WK_UP) == PIN_HIGH) {
rt_thread_mdelay(20);
if (rt_pin_read(PIN_WK_UP) == PIN_HIGH) {
return 3;
}
}
return -RT_ERROR;
}
// 主函数
int main(void)
{
rt_int16_t key;
Point food;
int i;
int updateResult;
bool hasEaten = false;
/* 清屏 */
lcd_clear(WHITE);
lcd_show_image(0, 0, 240, 69, image_rttlogo);
lcd_set_color(WHITE, BLACK);
key_init();
initSnake(snakeBody, &snakeLength);
food = generateRandomFoodPosition(snakeBody, snakeLength);
rt_thread_mdelay(3000);
while (1)
{
key = key_scan();
if(key!=-1)
{
checkInput(&dir, key);
LOG_E("key %d", key);
}
updateResult = updateSnake(snakeBody, &snakeLength, dir, &food, &hasEaten);
if(updateResult != 0 && updateResult != -1)
{ // 判断是否吃到食物
//确保新食物位置不在蛇身上
do{
food = generateRandomFoodPosition(snakeBody, snakeLength);
}while(isPositionInSnake(&food, snakeBody, snakeLength));
} else if (updateResult == -1) {
// 处理游戏结束逻辑
resetSnake(snakeBody, &snakeLength, &dir);
food = generateRandomFoodPosition(snakeBody, snakeLength); // 重置游戏时也生成新食物
hasEaten = false;
}
// 渲染蛇到LCD
renderSnake(snakeBody, snakeLength);
//生成食物
renderFood(food);
rt_thread_mdelay(DELAY_MS);
}
}
三.显示
单个像素点太小,所以用了3*3的方案