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

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

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. 小蛇的移动速度是固定的,可以改进为随着身体越来越长,移动速度也逐步加快;

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值