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

22 篇文章 1 订阅
19 篇文章 1 订阅

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/创建下个食物 撞墙/自杀? 结束 yes no yes no

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 getrandbits


class 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 True
        return False

    def 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_press


class 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 = 2
        self.foodx, self.foody = 0, 0
        self.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]-1
        elif self.direct=='down':
            self.y[0] = self.y[0]+1
        elif self.direct=='left':
            self.x[0] = self.x[0]-1
        else:
            self.x[0] = self.x[0]+1
        # 吃到食物
        if self.food_eat():
            self.x.append(tmp_x)
            self.y.append(tmp_y)
            self.long += 1
            self.food_create()

    def is_dead(self):
        '判断是否撞墙或自杀'
        if self.x[0]<0 or self.x[0]>7:
            return True
        if self.y[0]<0 or self.y[0]>7:
            return True
        for i in range(1, self.long):
            if self.x[0] == self.x[i] and self.y[0] == self.y[i]:
                return True
        return False
    
    def food_create(self):
        '创建食物'
        while True:
            self.foodx = getrandbits(3)
            self.foody = getrandbits(3)
            bad_food = False
            for i in range(self.long):
                if self.x[i] == self.foodx and self.y[i]==self.foody:
                    bad_food = True
                    break
            if not bad_food:
                break

    def food_eat(self):
        '判断吃到食物'
        return self.x[0]==self.foodx and self.y[0]==self.foody

    def 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+= 1
    last_press_direct = btn.press()

    if step > 50:
        if last_press_direct != snake.direct:
            snake.direct = last_press_direct  # 方向有变化时才转向
        snake.move()

        if snake.is_dead():
            break
        else:
            led.show(snake.drawdata())

        step = 0

5. 实验效果

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

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

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

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

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我知道了。针对你的问题,我能够为你提供如下的解答: 在使用 MicroPythonESP8266 控制 LED 的过程中,可以通过 PWM 技术实现呼吸灯效果。PWM(Pulse Width Modulation)脉宽调制技术是一种通过改变信号的占空比来控制电路输出功率的技术。 具体实现步骤如下: 1. 首先需要在 ESP8266 上安装 MicroPython,然后连接 ESP8266LED。 2. 在 MicroPython 中,可以使用 machine 模块中的 Pin 和 PWM 类来控制 LED。 3. 先使用 Pin 类将 LED 连接到 ESP8266 上的一个 GPIO 引脚。 4. 再使用 PWM 类来初始化一个 PWM 对象,并设置 PWM 对象的频率和占空比。 5. 最后使用 PWM 对象的 duty() 方法来改变占空比,从而控制 LED 显示的亮度。 下面是一个简单的 MicroPython 代码示例,用于控制 LED 呼吸灯效果: ```python from machine import Pin, PWM import time led_pin = Pin(2, Pin.OUT) # 连接到GPIO2 pwm = PWM(led_pin, freq=5000) # 初始化PWM对象,设置频率为5000Hz while True: for duty_cycle in range(0, 1024, 8): # 从0到1024,每次增加8 pwm.duty(duty_cycle) # 设置占空比 time.sleep_ms(5) # 暂停5ms for duty_cycle in range(1023, -1, -8): # 从1023到0,每次减少8 pwm.duty(duty_cycle) # 设置占空比 time.sleep_ms(5) # 暂停5ms ``` 上面的代码中,使用了一个无限循环来让 LED 一直显示呼吸灯效果。在每个循环中,通过循环改变占空比,从而让 LED 呈现出呼吸灯效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值