贪吃蛇(链表实现)

正好有需要的材料,做条蛇玩一下,顺便巩固链表知识

在这里插入图片描述

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工程模板就可以玩了。
  • 有疑问可以提出,工程也已上传,想查看下载即可
发布了22 篇原创文章 · 获赞 29 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Emmy_kanly/article/details/105344651