Shark:基于树莓派PICO移植游戏-贪吃蛇
使用MicroPython进行移植贪吃蛇游戏,游戏控制为MMA7660三轴姿态传感器,光电旋转编码器旋转控制贪吃蛇前进速度
标签
树莓派
嵌入式系统
PICO
贪吃蛇
ST7789
mma7660
shark_xin
更新2021-09-06
1122

一、模块介绍

   1. PICO模块的主要功能如下图所示

Fg6gesESvY0_ip3V2CN9ZDl4avY7

  • PICO模块拥有264KB的SRAM(RP2040片内自带)和2MB的Flash(模块上外扩),存储空间比较大,适合于MicroPython编程学习
  • GPIO方面的优势,能够灵活配置,支持实时性比较强的应用
  • 内部ADC支持两路模拟信号的输入,采样率为500Kbps,并可以提供外部参考电压
  • 23数字GPIO + 3个模拟/数字复用的输入管脚,这3个管脚可以做为ADC的输入
  • 通过USB供电和程序配置

2.拓展板外设介绍

  • 控制输入 - 2个按键 + 1个光电旋转编码器,用以对PICO进行控制和参数设置
  • LCD显示 - 通过SPI连接,1.54寸、分辨率为240 * 240的真彩色LCD模块
  • 模拟Mic音频输入 - 麦克风 + 运放 + 低通滤波器利用内部的ADC制作电压表或简易示波器
  • 蜂鸣器音频输出
  • 双声道音频输出,通过耳机插座输出
  • 三轴姿态传感器(MMA7660)- 通过I2C连接姿态传感器,在姿态传感器上还将中断管脚INT连接到GPIO上,用以对姿态变化的快速中断响应
  • SD卡
  • 红外发射/接收(本次开发板没有)
  • UART接口 - 1个UART插座(TX、RX、3.3V、GND),可以同其它板卡通过UART进行通信
  • I2C接口 - 在连接了板上姿态传感器的同时还可以连接其它I2C的传感器或I2C外设(每个I2C外设地址不同)

    FjJ96kZOTdFpEpBgggjLgHG7NNYV

二、项目介绍

  项目3 - 设计/移植一款游戏

  1. 设计或移植一款经典的游戏,通过LCD屏显示,通过按键和旋转编码器控制
  2. 在游戏中要通过蜂鸣器播放背景音乐

    本次设计的是贪吃蛇,通过蜂鸣器播放背景音乐,显示将贪吃蛇显示到240X240的屏幕上,使用mma7660三轴姿态传感器给贪吃蛇切换方向,使用光电旋转编码器给贪吃蛇改变速度,顺时针为速度+2,逆时针为速度-2,贪吃蛇撞墙则游戏结束,并显示分数。贪吃蛇长度达到30X30-1的长度时则显示胜利,并显示分数。

三、代码解释

    1.初始化LCD显示器 

st7789_res = 0
st7789_dc  = 1
disp_width = 240
disp_height = 240
spi_sck=machine.Pin(2)
spi_tx=machine.Pin(3)
spi0=machine.SPI(0,baudrate=4000000, phase=0, polarity=1, sck=spi_sck, mosi=spi_tx)
display = st7789.ST7789(spi0, disp_width, disp_width,
                    reset=machine.Pin(st7789_res, machine.Pin.OUT),
                        dc=machine.Pin(st7789_dc, machine.Pin.OUT),
                            xstart=0, ystart=0, rotation=0)

   2.初始化mma7660三轴姿态传感器

SCL = Pin(11)   #mma7660 11号引脚
SDA = Pin(10)   #mma7660 10号引脚
BUS = 1 
pin = Pin(9, Pin.IN, Pin.PULL_DOWN)   #mma7660 9号引脚
mma7660 = I2C(BUS,scl=SCL, sda=SDA, freq=40000000)
address = mma7660.scan()  #获取装置位置
print("激活 MMA7660")
mma7660.writeto_mem(76,7,b'1')   #向0x07寄存器写入01,执行主动模式
display.fill(st7789.WHITE)

   3.初始化旋转编码器

key = machine.Pin(6,machine.Pin.IN)
keyleft=machine.Pin(4,machine.Pin.IN)
keyright=machine.Pin(5,machine.Pin.IN)

   4.构建一个Snake类,在其中实现贪吃蛇绘制,出现随机事物,绘制实物,贪吃蛇尾部刷新,贪吃蛇吃东西成长,移动蛇身,播放音乐的功能。

class Snake:
    # 构建snake
    def __init__(self,cube=8):
        #默认网格大小4
        self.cube_width = cube
        self.cube_color = 0xc618  # 50%灰色0x7bef 20%灰色 0xc618
        self.snake_color= 0x0000  #黑色
        self.disp_width_num, self.disp_height_num = disp_width // self.cube_width, disp_height // self.cube_width
        self.snake_body = []
        self.snake_body.append((int(self.disp_width_num // 2 * self.cube_width),
                                int(self.disp_height_num //2 * self.cube_width)))   #贪吃蛇的头
        self.food_position = [20,20]  #  产生食物,第一个位置,不定义create_food会定义为一个元祖,导致报错
        self.direction = Direction.LEFT
        self.last_pos = None

     ①绘制网格:没多大实际作用,可以不画出来,比较适合纠错。

def draw_grads(self):   #绘制网格
        for i in range(self.disp_width_num + 1):
            display.vline(self.cube_width*i,0,disp_height,self.cube_color)  
        for i in range(self.disp_height_num + 1):
            display.hline(0,self.cube_width*i,disp_width,self.cube_color)

      ②绘制贪吃蛇

def draw_snake(self): #绘制贪吃蛇
        print('贪吃蛇初始位置:{}'.format(self.snake_body)) 
        for i in self.snake_body:
            display.fill_rect(i[0],i[1],self.cube_width,self.cube_width,0x0000)

      ③出现随机食物,使用random函数,将食物的随机坐标显示出来。

def create_food(self): #随机食物
        position = (random.randint(0, self.disp_width_num - 1), random.randint(0,self.disp_height_num - 1))
        self.food_position = position

      ④绘制食物。

def draw_food(self): #绘制食物
        display.fill_rect(self.food_position[0]*self.cube_width,self.food_position[1]*self.cube_width,self.cube_width,self.cube_width,st7789.RED)
    

        ⑤吃东西变长,蛇头的坐标跟食物的坐标一样,则表示吃到了食物。

def grow_up(self):  #吃东西成长
        if self.snake_body[0][0] == self.food_position[0] * self.cube_width and \
           self.snake_body[0][1] == self.food_position[1] * self.cube_width :
            return True
        else:
            return False

      ⑥更新蛇的位置,包括屁股后的位置,主要功能为跟上蛇头,并将最后一个点设置为空白色。

def refresh(self):  #更新蛇的位置
        for i in range(len(self.snake_body) - 1, 0, -1):
            self.snake_body[i] = self.snake_body[i-1]
#         for i in self.snake_body:
        display.fill_rect(self.last_pos[0],self.last_pos[1],self.cube_width,self.cube_width,0xffff)

      ⑦移动蛇身,上则是蛇头的纵坐标-1,下则是纵坐标+1,其他方向同理,只需要改变蛇头的位置。后面跟着接上就好了。

def move(self): # 移动蛇身
        if self.direction == Direction.UP:
            self.snake_body[0] = (self.snake_body[0][0],self.snake_body[0][1] - self.cube_width)
        elif self.direction == Direction.DOWN:
            self.snake_body[0] = (self.snake_body[0][0],self.snake_body[0][1] + self.cube_width)
        elif self.direction == Direction.LEFT:
            self.snake_body[0] = (self.snake_body[0][0] - self.cube_width,self.snake_body[0][1])
        elif self.direction == Direction.RIGHT:
            self.snake_body[0] = (self.snake_body[0][0] + self.cube_width,self.snake_body[0][1])

      ⑧播放音乐

    def song(self):  #播放一声音乐
        mySong = music(song2)
        mySong.tick()

   5.创建一个游戏控制函数class Game,判断输赢,判断方向,判断旋转编码器,游戏分数,运行函数,游戏的初始速度为4 。

      ①判断输赢,如果贪吃蛇的长度=格子数的乘积-1,就判断赢,如果0<=蛇的坐标>=边长,就输。

def is_win(self):   # 判断是否赢
        return len(self.get_body) == self.snake.disp_width_num * self.snake.disp_height_num - 1
    
    def is_fail(self):      # 判断是否输
        if not 0 <= self.get_body[0][0] < disp_width+1 or not 0 <= self.get_body[0][1] < disp_height+1:
            return True
        return False

      ②判断方向,如果是前进则不能直接倒退,其他方向同理,不能屁股变为头。

def get_mma7660(self):  
        TILTS = mma7660.readfrom_mem(76,3,8) # 获取方向数据
        TILT=TILTS[0:1:1] # 取8位字符的第一个位
        if TILT== b'\x19':
            if self.snake.direction != Direction.UP:    # 不能直接调转,屁股变尾巴。
                self.snake.direction = Direction.DOWN
        elif TILT == b'\x15':
            if self.snake.direction != Direction.DOWN:
                self.snake.direction = Direction.UP
        elif TILT == b'\x05':
            if self.snake.direction != Direction.RIGHT:
                self.snake.direction = Direction.LEFT
        elif TILT == b'\t':
            if self.snake.direction != Direction.LEFT:
                self.snake.direction = Direction.RIGHT
        else:
            pass
#             self.snake.direction = None

      ③旋转编码器,逆时针速度-2,顺时针速度+2

def int_handler(self,pin):  # 旋转编码器控制贪吃蛇速度,中断
        keyleft.irq(handler=None)
        if keyleft.value() == 0:
            if keyright.value() == 1:
                self.fps +=2
            if keyright.value() == 0:
                self.fps -=2
        if keyleft.value() == 1:
            if keyright.value() == 0:
               self.fps +=2       
            if keyright.value() == 1:
               self.fps -=2
        keyleft.irq(handler=self.int_handler)

      ④统计游戏分数:贪吃蛇的长度-1

@property
    def score(self):  # 游戏分数
        return len(self.get_body) - 1

      ⑤运行函数

def run(self):
        self.state = GameState.PLAYING
        update_time = time.ticks_ms()
        self.snake.draw_grads()   #画出网格
        self.snake.song()
        display.fill(st7789.WHITE)
        keyleft.irq(trigger=machine.Pin.IRQ_FALLING|machine.Pin.IRQ_RISING, handler=self.int_handler)  #中断实现
        
        while self.state == GameState.PLAYING:
            self.get_mma7660()  #方向检测
#             self.snake_speed()  #贪吃蛇速度

            self.snake.last_pos = self.get_body[-1]  #保存尾部的位置,小蛇吃了食物,需要在尾部增长 
            if time.ticks_diff(time.ticks_ms(), update_time) > (1000 // self.fps):
                self.snake.refresh()  #更新身体位置
                self.snake.move()     #改变头部的位置        
                if self.snake.grow_up():  # 是否吃到食物,吃到就再生随机成一个食物,并身体加1
                    self.snake.song()     # 播放音乐
                    self.snake.create_food()
                    self.get_body.append(self.snake.last_pos)
                    print(self.get_body)
    #                   self.snake.Area_refresh()   # 刷新蛇屁股后面
    #                   display.fill(st7789.WHITE)
                self.snake.draw_food()      #画食物
                self.snake.draw_snake()     # 画蛇   
                update_time = time.ticks_ms()  # 刷新帧时间
                
            if self.is_fail():  # 判断if输
                self.state = GameState.FAIL
                break
            if self.is_win():  # 判断if赢
                self.state = GameState.WIN
                break
    
        if self.state == GameState.FAIL:
            display.fill(st7789.BLACK)
            display.text(font2,'Game over!',50,80,st7789.WHITE)
            display.text(font2,"Score:%d" % self.score,50,130,st7789.WHITE)
        if self.state == GameState.WIN:
            display.fill(st7789.BLACK)
            display.text(font2,'You WIN!',50,100,st7789.WHITE)
        if self.state == GameState.PAUSE:
            display.fill(st7789.BLACK)
            display.text(font2,'PAUSE',50,80,st7789.WHITE)
            display.text(font2,'Press 2 to continue',20,100,st7789.WHITE)

四、总结

      显示内容时需要吧st7789.py放置到pico板内部;如果需要显示文字,则需在pico板内创建fonts文件夹,并将vga1_16x32.py  vga2_8x8.py  两个文件放置到fonts文件夹内;如果需要播放音乐,则需要将buzzer_music.py放置到pico内部。如果需要开机运行贪吃蛇,则需要将主文件更名为main.py并放置到pico内部。

      pico板的thonny软件开发时不能使用一些python的模块,并且模块有的还需要放置到pico内才能使用,这略显不足,并且该thonny没有自动纠错的功能,有的时候很多错误很简单就是找不到。

      总的来说本次项目收获良多,学习到了pico的使用,thonny的使用,并且得到了一些编程的思路,希望电子森林越做越红火吧。谢谢

 

 

附件下载
main.py
主程序
Snake_Class.zip
主程序需要调用的类
团队介绍
重庆理工大学计算机科学与工程学院
团队成员
shark_xin
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号