正好有需要的材料,做条蛇玩一下,顺便巩固链表知识
1. 硬件准备
- STM32F103开发板
- TFT9320屏幕
- 双轴按键遥感传感器(可以用四个按键代替)
2. 软件分析
- 蛇身包含的信息:x轴,y轴,蛇身上一个点、下一个点
- 产生食物的随机函数,吃到食物后增加蛇身
- 让蛇动起来就是让每个点的( x,y)信息一个一个替换下去,并需要获得方向信息
- LCD屏幕一个像素点太小了,需要扩充蛇身宽度的函数、显示游戏边界的函数
- 检查蛇的状态,撞墙或吃到食物做出反应
3. 代码实现
蛇的初始信息
/***********************************
snake.h
***********************************/
typedef struct SNAKE SNAKE_T;
struct SNAKE
{
uint16_t x;
uint16_t y;
uint16_t color; //放入了颜色,打算实现每个点带不同的颜色值
SNAKE_T* prev;
SNAKE_T* next;
};
/***********************************
snake.c
***********************************/
SNAKE_T* SnakeList;
static SNAKE_T* Node_Create(void)
{
return malloc(sizeof(SNAKE_T));
}
void SnakeList_LengthAppend(SNAKE_T* p_head)
{
SNAKE_T *new_node = Node_Create();
SNAKE_T *p_operate = p_head;
if (p_head == NULL)
{
DEBUG_Printf("SnakeList_LengthAppend error\r\n");
free(new_node);
return;
}
while (p_operate->next != NULL) //找到最后一个不为空的节点地址
{
p_operate = p_operate->next;
}
new_node->next = p_operate->next; //根据while条件判断下来,其实就是NULL
new_node->prev = p_operate;
new_node->x = p_operate->x;
new_node->y = p_operate->y;
p_operate->next = new_node; //尾结点的下一个接上新节点
/*********************不能忘记了以下************************/
p_head->prev = new_node; //头结点的上一个是尾结点
}
void SnakeList_Init(void)
{
SnakeList = Node_Create();
if (SnakeList == NULL)
{
DEBUG_Printf("SnakeList_Init error\r\n");
return;
}
SnakeList->next = NULL;
SnakeList->prev = SnakeList;
SnakeList->x = 10;
SnakeList->y = 10;
for (uint8_t length = 0; length < 3; length++)
{
SnakeList_LengthAppend(SnakeList);
}
}
蛇的移动
/***********************************
snake.h
***********************************/
#define SNAKE_WIDTH (8)
#define ADC_SAMPLE_TIMES (2)
typedef enum
{
DIR_NONE = 0,
DIR_UP,
DIR_DOWN,
DIR_LEFT,
DIR_RIGHT,
}MOVE_DIR_E;
/***********************************
snake.c
***********************************/
MOVE_DIR_E LastDir = DIR_RIGHT; //蛇身上次移动的方向
uint32_t RandomSeed = 0;
/*
点亮一个蛇身的点
此函数在LCD的驱动中编写
*/
void Trun_Off_Point(uint16_t x, uint16_t y)
{
LCD_Fill(x*SNAKE_WIDTH, y*SNAKE_WIDTH, x*SNAKE_WIDTH+SNAKE_WIDTH, y*SNAKE_WIDTH+SNAKE_WIDTH, WHITE);
}
/*
获取遥感的值判断方向信息,遥感就是一个ADC采样的变阻器
*/
uint8_t Snake_Direction_Input(void)
{
uint16_t adc_value[ADC_SAMPLE_TIMES] = {
0};
uint16_t adc_x = 0;
uint16_t adc_y = 0;
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&adc_value[0], ADC_SAMPLE_TIMES);
delay_ms(10);
for (uint8_t cnt = 0; cnt < ADC_SAMPLE_TIMES/2; cnt++)
{
adc_y += adc_value[cnt*2+1];
}
adc_y /= (ADC_SAMPLE_TIMES/2);
for (uint8_t cnt = 0; cnt < ADC_SAMPLE_TIMES/2; cnt++)
{
adc_x += adc_value[cnt*2];
}
adc_x /= (ADC_SAMPLE_TIMES/2);
if (adc_y >= 4000)
{
return DIR_DOWN;
}
else if(adc_y <= 1000)
{
return DIR_UP;
}
if (adc_x <= 1000)
{
return DIR_RIGHT;
}
else if(adc_x >= 4000)
{
return DIR_LEFT;
}
return DIR_NONE;
}
/*
LCD绘制出蛇身
*/
void Snake_Show(SNAKE_T *p_head)
{
SNAKE_T *p_operate = p_head;
while(p_operate != NULL)
{
Trun_On_Point(p_operate->x, p_operate->y);
p_operate = p_operate->next;
}
}
void Snake_Move(SNAKE_T* p_head)
{
SNAKE_T *p_operate = p_head;
SNAKE_T *p_tail = p_head->prev; //获取到尾结点
MOVE_DIR_E move_dir = LastDir; //获取当前移动的方向
static MOVE_DIR_E cur_dir = DIR_NONE;
if (p_operate == NULL)
{
DEBUG_Printf("SnakeList_Move error\r\n");
return;
}
Trun_Off_Point(p_tail->x, p_tail->y); //熄灭蛇尾的点
/********************从尾结点向前遍历链表并修改值*********************/
while (p_tail != p_head)
{
p_tail->x = p_tail->prev->x; //尾结点的值为上一个节点的值,这样移动的时候就在向前移动
p_tail->y = p_tail->prev->y;
p_tail = p_tail->prev;
}
cur_dir = Snake_Direction_Input(); //KEY_Direction_Input();
if (cur_dir != move_dir) //修改操作后的状态
{
if (cur_dir != DIR_NONE)
{
move_dir = cur_dir;
LastDir = move_dir;
RandomSeed = HAL_GetTick(); //获得产生食物的种子值
}
}
switch(move_dir) //判断头结点的位置改变方向修改头结点的值
{
case DIR_UP:
p_operate->y--;
break;
case DIR_DOWN:
p_operate->y++;
break;
case DIR_RIGHT:
p_operate->x++;
break;
case DIR_LEFT:
p_operate->x--;
break;
default:
break;
}
Snake_Show(p_operate);
}
产生新食物
/***********************************
snake.h
***********************************/
#define BORDER_WIDTH (8)
#define BORDER_LEFT_POS (0)
#define BORDER_RIGHT_POS ((MAX_ROW-BORDER_WIDTH)/8)
#define BORDER_UP_POS (0)
#define BORDER_DOWN_POS ((280-BORDER_WIDTH)/8)
typedef enum
{
FOOD_EMPTY = 0,
FOOD_READY,
FOOD_GOT,
SNAKE_MOVE,
SNAKE_HIT,
GAME_OVER,
}GAME_STATE_E;
/***********************************
snake.c
***********************************/
static uint16_t random(void)
{
uint16_t ret = 0;
/* 初始化随机数发生器 */
srand(RandomSeed);
/* 产生一个0-300的随机数 */
ret = rand() % 300;
return ret;
}
void Create_NewFood(GAME_STATE_E *food_state)
{
uint32_t random_num1 = 0;
uint32_t random_num2 = 0;
uint16_t x_value = 0;
uint16_t y_value = 0;
while(*food_state == FOOD_EMPTY)
{
random_num1 = HAL_GetTick() + random();
random_num2 = HAL_GetTick() + random();
x_value = random_num1 % (240 - BORDER_WIDTH);
y_value = random_num2 % (280 - BORDER_WIDTH);
if (x_value > (BORDER_LEFT_POS+BORDER_WIDTH) && y_value > (BORDER_UP_POS+BORDER_WIDTH)) //放置食物的位置合规
{
DEBUG_Printf("\r\n----Create_NewFood----\r\n");
DEBUG_Printf("\r\n----x_value: %d----\r\n", x_value);
DEBUG_Printf("\r\n----Y_value: %d----\r\n", y_value);
Trun_On_Point(x_value/SNAKE_WIDTH, y_value/SNAKE_WIDTH);
food_pos[0] = x_value/SNAKE_WIDTH;
food_pos[1] = y_value/SNAKE_WIDTH;
*food_state = FOOD_READY;
break;
}
}
}
检查蛇的状态
/***********************************
snake.h
***********************************/
#define MAX_EATEN_CNT 50
/***********************************
snake.c
***********************************/
uint16_t ScorePool[MAX_EATEN_CNT] = {
0};
void Score_Init(uint16_t max_eaten_cnt)
{
for (uint16_t cnt = 1; cnt < max_eaten_cnt; cnt++)
{
ScorePool[cnt] = (cnt * 10);
}
}
/*
展示获得的分数
*/
void Show_AddScore(void)
{
char src[5] = "";
char dest[20] = "";
sprintf(src, "%d", ScorePool[EatenFoodCnt++]);
memmove(dest, "Score: ", strlen("Score: "));
strncat(dest, src, strlen(src));
LCD_ShowString(30, 285, 200, FONT_SIZE_24, (uint8_t *)dest);
}
/*
* 检查蛇的状态
* */
static uint8_t Snake_State(SNAKE_T* p_head, GAME_STATE_E *food_state)
{
if (p_head->x == BORDER_LEFT_POS || p_head->x == BORDER_RIGHT_POS || p_head->y == BORDER_UP_POS || p_head->y == BORDER_DOWN_POS)
return SNAKE_HIT;
if (p_head->x == food_pos[0] && p_head->y == food_pos[1])
{
*food_state = FOOD_EMPTY;
return FOOD_GOT;
}
return SNAKE_MOVE;
}
void Check_SnakeState(GAME_STATE_E *food_state)
{
uint8_t game_state = SNAKE_MOVE;
game_state = Snake_State(SnakeList, food_state);
switch(game_state)
{
case SNAKE_MOVE:
break;
case FOOD_GOT:
Show_AddScore();
SnakeList_LengthAppend(SnakeList);
break;
case SNAKE_HIT:
DEBUG_Printf("your snake hit the wall, game over!\r\n");
game_state = GAME_OVER;
LCD_GameOverShow();
HAL_NVIC_SystemReset();
break;
default:
break;
}
}
让蛇动起来
#define MAX_COLUMN 320 //最大列数
#define MAX_ROW 240 //最大行数
/*
显示游戏的边界
此函数在LCD的驱动中编写
*/
void LCD_BorderShow(void)
{
LCD_Fill(0, 0, BORDER_WIDTH, 280, RED);
LCD_Fill(MAX_ROW-BORDER_WIDTH, 0, MAX_ROW, 280, RED);
LCD_Fill(0, 0, MAX_ROW, BORDER_WIDTH, RED);
LCD_Fill(0, 272, MAX_ROW, 280, RED);
}
void Game_Running(SNAKE_T* p_head)
{
static GAME_STATE_E food_state = FOOD_EMPTY;
Snake_Move(p_head);
Create_NewFood(&food_state);
Check_GameState(&food_state);
}
int main(void)
{
uint16_t time = 400;
MX_GPIO_Init();
...
LCD_Init();
Score_Init(MAX_EATEN_CNT);
SnakeList_Init();
//可以在此设计遥感方向获取来选择游戏难度,也就是while中刷新一次蛇的状态的频率
...
while(1)
{
Game_Running(SnakeList);
delay_ms(time);
}
}
- 至此一个运行在STM32F103的LCD显示贪吃蛇就OK了,Enjoy it ~~
- 文中都已注明各内容放在什么位置,搭配LCD源码和CubeMX生成的STM32工程模板就可以玩了。
- 有疑问可以提出,工程也已上传,想查看点击这里下载即可…
转载:https://blog.csdn.net/Emmy_kanly/article/details/105344651
查看评论