MicroPython-On-ESP8266——8x8LED点阵模块(5)自制贪吃蛇游戏

news/2024/11/29 8:43:33/

MicroPython-On-ESP8266——8x8LED点阵模块(5)自制贪吃蛇游戏

1. 背景知识

连续折腾了一段时间的8x8点阵屏模块,从基本原理到驱动它显示滚动图案效果,常用的功能都使用到了。系列如下:

MicroPython-On-ESP8266——8x8LED点阵模块(1)驱动原理

MicroPython-On-ESP8266——8x8LED点阵模块(2)使用74HC595驱动

MicroPython-On-ESP8266——8x8LED点阵模块(3)使用MAX7219驱动

MicroPython-On-ESP8266——8x8LED点阵模块(4)基于MAX7219滚动显示字符/图案

由于我手上只有这么一块屏,没有做多屏串接显示的效果。那下一步咱们来继续折腾点啥。就基于MAX7219模块做个贪吃蛇游戏吧(掌机粉、诺基亚粉才懂为什么做这个)

2. 贪吃蛇原始分析

8x8点阵屏有64个led灯珠,从长度2开始,理论上可以贪吃成一条长度为63的小蛇。所以还是有一点可玩性的。

2.1. 游戏规则

  • 地图(点阵屏)上初始有一条长度为2的小蛇,间隔一定时间保持惯性向蛇头方向移动
  • 通过四个方向键可以控制小蛇的移动方向
  • 初始在蛇身之外的地方随机有一个食物,蛇头碰到食物会把食物吃掉,小蛇长度加1,同时食物再随机产生一个
  • 蛇头碰到边界或者自身,游戏结束

2.2. 程序逻辑

先构思一下程序需要实现的基础功能模块:

  • 初始化
  • 惯性移动
  • 创建食物
  • 判断吃到食物
  • 判断撞墙或自杀
  • 小蛇长身体

再根据模块组装一下程序流程:

Created with Raphaël 2.3.0开始初始化获取按健最后确定的方向惯性移动吃到食物?身体加1/创建下个食物撞墙/自杀?结束yesnoyesno

2.3. 程序模块分析

基础铺垫:
A.我们基于屏幕坐标的方式来全局进行位置判断和屏幕绘制

在这里插入图片描述

B.用坐标表示食物,用两个数组来表示小蛇的身体

在这里插入图片描述
我们用数组来表示小蛇的身体,且把数组的第一个元素定义为蛇头的位置。基于此,

如果我们要判断吃到食物则用 snake_x[0] == food_x and snake_y[0] == food_y
如果我们判断小蛇撞墙则用snake_x[0] < 0 or snake_x[0] > 7 or snake_y[0] < 0 or snake_y[0] > 7
如果小蛇要长身体则用snake_x.append(...); snake_y.append(...)

这样大体逻辑就出来了。

模块原理拆解

模块原理解释
初始化给小蛇固定一个初始长度(=2)和中间靠左一点点的初始位置 (1,3) 、(2,3)
惯性移动由外部按钮确定方向,初始向右,如果外部按键无操作,就保持当前方向且固定时间间隔地向该方向移动。移动的方法就是从尾巴开始遍历小蛇身体数组,让当前值等于数组的上一个值;而蛇头呢则要根据需要移动的方向去取下个位置的值。
创建食物随机在屏幕范围内找个位置 (x食物, y食物),但不能与小蛇的身体重叠
判断吃到食物这个上面讲到过了,蛇头坐标与食物坐标重合则吃到了
判断撞墙或自杀撞墙是判断蛇头的坐标有没有越界屏幕坐标范围,自杀则是判断蛇头有没有跟任一个身体节点的坐标重合
小蛇长身体先缓存下来蛇尾巴,当小蛇移动一次以后,再把缓存的坐标补到蛇尾巴后面
绘制图案这个会有点绕。每次点亮屏幕前,先把所有位置都当作黑的,再依次把小蛇和食物对应的位置坐标转换为max7219驱动位数据。思路就是这么个思路,具体还是看后面代码慢慢理解吧。

用定时器或者固定间隔的循环来移动小蛇

3. 硬件及接线连接

程序需要不断扫描需要4个按键来确定上下左右四个方向,并保持最后一个按下的方向不变。再有就是使用MAX7219模块来驱动点阵屏。

接线示意图:
在这里插入图片描述
实物连接图:
按键我直接借用的一个焊废的板子上的4个触点按钮,板子斜过来用就是上下左右的布局。
在这里插入图片描述

4. 程序代码

上面已经解析了原理,这里直接整篇代码放上来吧

from machine import Pin
import time
from random import getrandbitsclass Button(object):'四个按钮,用简化接线方式,按钮线与地线进行判断'# def __init__(self, gpio_up=0, gpio_down=5, gpio_left=2, gpio_right=4):def __init__(self, gpio_up=0, gpio_down=4, gpio_left=5, gpio_right=2):self.btn_up = Pin(gpio_up, Pin.IN, pull=Pin.PULL_UP)self.btn_down = Pin(gpio_down, Pin.IN, pull=Pin.PULL_UP)self.btn_left = Pin(gpio_left, Pin.IN, pull=Pin.PULL_UP)self.btn_right = Pin(gpio_right, Pin.IN, pull=Pin.PULL_UP)self.last_press = 'right'def _check(self, _btn):if _btn.value() == 0:time.sleep_ms(20)if _btn.value() == 0:return Truereturn Falsedef press(self):if self._check(self.btn_up): self.last_press = 'up'if self._check(self.btn_down): self.last_press = 'down'if self._check(self.btn_left): self.last_press = 'left'if self._check(self.btn_right): self.last_press = 'right'return self.last_pressclass Matrix(object):'8x8LED点阵屏,MAX7219驱动'def __init__(self, gpio_din=13, gpio_clk=14, gpio_cs=15):'初始化'# 准备数据引脚self.pin_clk = Pin(gpio_clk, Pin.OUT, value=1)  #D5,时钟,上升跳变时数据位移锁存self.pin_cs  = Pin(gpio_cs,  Pin.OUT, value=1)  #D8,上升跳变时,数据全部推入锁存self.pin_din = Pin(gpio_din, Pin.OUT, value=1)  #D7,待移入的数据self.model_init()def write_byte(self, data):"向芯片移入一个字节"for i in range(8):self.pin_clk.off()self.pin_din.value(1 if ((data << i) & 0x80) else 0)  # 从高位开始送数据self.pin_clk.on()def write_data(self, addr, data):"写入地址与值"self.pin_cs.off()self.write_byte(addr)self.write_byte(data)time.sleep_us(5)self.pin_cs.on()def model_init(self):"初始化模块"self.write_data(0x0c, 0x00)  #关断处于关闭状态 self.write_data(0x0f, 0x00)  #不测试self.write_data(0x0b, 0x07)  #扫描所有位码self.write_data(0x0a, 0x0F)  #亮度0x07,半亮self.write_data(0x09, 0x00)  #不译码self.write_data(0x0c, 0x01)  #关断处于显示状态 def show(self, col_data):"亮屏控制,col_data需要为长度为8的数组"for line in range(8):self.write_data(line+1, col_data[line])class Snake(object):'贪吃蛇'def __init__(self):'''初始状态.........................00.....  ->................................'''self.direct = 'right'  # 初始移动方向self.x = [2, 1]  #范围[0,7],第一个元素是蛇头x,蛇身加长时直接append(self.x[-1])self.y = [3, 3]  #范围[0,7],第一个元素是蛇头y,蛇身加长时直接append(self.y[-1])self.long = 2self.foodx, self.foody = 0, 0self.food_create()def move(self):'移动'tmp_x, tmp_y = self.x[-1], self.y[-1]# 蛇身向蛇首路过的方向移动for i in range(self.long, 1, -1):self.x[i-1] = self.x[i-2]self.y[i-1] = self.y[i-2]# 处理蛇首if self.direct=='up':self.y[0] = self.y[0]-1elif self.direct=='down':self.y[0] = self.y[0]+1elif self.direct=='left':self.x[0] = self.x[0]-1else:self.x[0] = self.x[0]+1# 吃到食物if self.food_eat():self.x.append(tmp_x)self.y.append(tmp_y)self.long += 1self.food_create()def is_dead(self):'判断是否撞墙或自杀'if self.x[0]<0 or self.x[0]>7:return Trueif self.y[0]<0 or self.y[0]>7:return Truefor i in range(1, self.long):if self.x[0] == self.x[i] and self.y[0] == self.y[i]:return Truereturn Falsedef food_create(self):'创建食物'while True:self.foodx = getrandbits(3)self.foody = getrandbits(3)bad_food = Falsefor i in range(self.long):if self.x[i] == self.foodx and self.y[i]==self.foody:bad_food = Truebreakif not bad_food:breakdef food_eat(self):'判断吃到食物'return self.x[0]==self.foodx and self.y[0]==self.foodydef drawdata(self):'创建绘制图形数据'data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]  # 待绘制数据# 画蛇for i in range(self.long):data[self.y[i]] |= (1 << (7-self.x[i]))# 画食物data[self.foody] |= (1<<(7-self.foodx))return data# 初始各模块
btn = Button()
led = Matrix()
snake = Snake()led.show(snake.drawdata())
step = 0
while True:time.sleep_ms(10)step+= 1last_press_direct = btn.press()if step > 50:if last_press_direct != snake.direct:snake.direct = last_press_direct  # 方向有变化时才转向snake.move()if snake.is_dead():breakelse:led.show(snake.drawdata())step = 0

5. 实验效果

8x8LED点阵屏制作贪吃蛇游戏

目前存在的问题与改进方向:

  1. 小蛇的移动是的循环里面判断次数达到就移动一次,间隔不是精准的。可以使用micropython的定时器来改进;
  2. 吃到食物长身体时,那个间隔内小蛇没有移动,只是长了一个节点,可以改进一下;
  3. 撞墙或自杀后程序就卡死了,因为小蛇身体已经越界,使用绘制屏幕去亮屏时报错了,这里也可以改进;
  4. 后续可以增加启动、结束的闪屏效果;
  5. 小蛇的移动速度是固定的,可以改进为随着身体越来越长,移动速度也逐步加快;

就这些吧,然后这些改进我就不费时做了,需要的同(主)学(要)自(是)行(我)研(人)究(懒)。


http://www.ppmy.cn/news/3995.html

相关文章

大数据课设

----------------------------------------------------------------------------------------------------------------------------- 由于本人主修嵌入式方向最多使用的就是C语言&#xff0c;由于物联网这个专业的特殊性&#xff0c;javaweb没少 写&#xff0c;所以java也用…

Spring Boot 3.x微服务升级经历

前言 Spring Boot 3.0.0 GA版已经发布&#xff0c;好多人也开始尝试升级&#xff0c;有人测试升级后&#xff0c;启动速度确实快了不少&#xff0c;如下为网络截图&#xff0c;于是我也按捺不住的想尝试下。 历程 首先就是要把Spring Boot、Spring Cloud 相关的依赖升一下 …

复方一枝蒿复合磷脂/IgG二性霉素B/阿糖胞苷修饰载甲氨喋呤/酶促合成半乳糖配体脂质体制备

小编今天为大家分享的科研知识是复方一枝蒿复合磷脂/IgG二性霉素B/阿糖胞苷修饰载甲氨喋呤/酶促合成半乳糖配体脂质体&#xff0c;一起来看&#xff01; 点击输入图片描述&#xff08;最多30字&#xff09; 复方一枝蒿复合磷脂脂质体&#xff1a; 采用硫酸铵梯度法制备复方一枝…

b站黑马的Vue快速入门案例代码——【axios+Vue】天知道(天气信息查询功能)

目录 目标效果&#xff1a; 更换的新接口接口文档&#xff1a; 天知道新的Get请求接口:http://ajax-api.itheima.net/api/weather html文件中注意因为接口更换&#xff0c;要修改原代码为如下红字部分&#xff1a; 重点原理&#xff1a; &#xff08;1&#xff09;v-on可以…

邂逅Vue3和Vue3开发体验

文章目录01-邂逅Vue3和Vue3开发体验Vue3带来的变化如何使用Vue方式一&#xff1a;CDN引入方式二——下载和引入计数器案例原生实现Vue实现MVVMtemplate写法一写法二datamethod其他属性01-邂逅Vue3和Vue3开发体验 Vue3带来的变化 如何使用Vue 方式一&#xff1a;CDN引入 <d…

七分钟教会你如何编写一个合格的测试用例

1、测试用例编写依据 测试用例编写应严格根据PRD&#xff08;产品说明书&#xff09; 没有PRD应根据与客户的沟通和确认结果编写 开发的技术文档和流程图 2、测试用例的组成元素 【用例编号】测试用例的编号。 【用例等级】测试用例的重要级别&#xff0c;一般核心功能的…

HTML人物介绍、个人设计web前端大作业、贝聿铭人物介绍(带报告3000字)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

【Vue脚手架】总结笔记

脚手架文件结构 ├── node_modules ├── public │ ├── favicon.ico: 页签图标 │ └── index.html: 主页面 ├── src │ ├── assets: 存放静态资源 │ │ └── logo.png │ │── component: 存放组件 │ │ └── HelloWorld.vue │ │…