基于Beetle ESP32-C3的贪吃蛇游戏

2023-05-291.9万
精华项目
AI 快速预览详细 收起
本项目使用Beetle ESP32-C3、0.96英寸OLED和4个轻触开关制作了一款迷你贪吃蛇游戏掌机。通过详细的硬件清单和线路连接说明,以及MicroPython固件的下载与刷入步骤,用户可以轻松复刻这一经典游戏。项目不仅适合初学者学习编程和硬件连接,还能激发孩子的兴趣。


1 项目概述

基于Beetle ESP32-C3的贪吃蛇游戏图5


贪吃蛇曾是一款经典的手机像素游戏。玩家使用方向键操控一条长长的蛇不断吞下水果,同时蛇身随着吞下的水果不断变长,当蛇头撞到蛇身或障壁时游戏结束。





本项目尝试使用Beetle ESP32-C3、OLED、轻触开关等来制作一款迷你游戏掌机,并实现贪吃蛇游戏的复刻。

2 功能简介
显示系统:使用0.96英寸OLED显示贪吃蛇的位置、显示水果的位置、显示成绩
控制系统:使用4个轻触按钮控制贪吃蛇的上下左右移动
积分系统:统计游戏过程中获得的积分

3 硬件清单

Beetle ESP32-C3 *1
0.96 OLED  *1
轻触开关 *4
面包板   *1
面板板跳线  若干


4 线路连接

ESP32-C3引脚               连接元件引脚

      IO0                          OLED   SCL
      IO1                          OLED   SDA
      IO7                          轻触开关(上)
      IO2                          轻触开关(下)
      IO6                          轻触开关(左)
      IO5                          轻触开关(右)


基于Beetle ESP32-C3的贪吃蛇游戏图7




基于Beetle ESP32-C3的贪吃蛇游戏图6






5 MicroPython固件的下载与刷入

5.1 下载micropython固件
访问https://micropython.org/resources/firmware/esp32c3-usb-20230426-v1.20.0.bin 连接,下载固件,如下图所示。

基于Beetle ESP32-C3的贪吃蛇游戏图1



5.2 使用杜邦线连接ESP32-C3引脚9与GND,将ESP32-C3通过USB连接至电脑。





5.3 使用flash download tool工具刷入micropython固件

(1)打开flash download tool工具,选择ESP32-C3,Load Mode:USB,如下图所示。

基于Beetle ESP32-C3的贪吃蛇游戏图2



(2)选择下载好的固件文件,按如下图所示设置,选择好正确的COM口。



基于Beetle ESP32-C3的贪吃蛇游戏图3



(3)点击“EARSE”擦除。下载完成,点击“START”刷入固件。下载完毕,断开USB连接,拔掉连接9号引脚与GND的杜邦线。使用USB线重新将ESP32-C3连接至电脑


6 Thonny的下载与安装
Thonny是一个面向初学者的 Python IDE。Thonny 由爱沙尼亚的 Tartu 大学开发,它采用了不同的方法,因为它的调试器是专为学习和教学编程而设计的。

Thonny安装完毕后,打开运行。点击“运行”-“选择解释器”。选择“MicroPython(ESP32)”,并选择正确的串口。

基于Beetle ESP32-C3的贪吃蛇游戏图4


7、编程(MicroPython)

(1)SSD1306库的导入

这里我们使用了一个OLED驱动库文件ssd1306.py,这个文件可以从github下载,也可以新建一个ssd1306.py的文件,将下面代码复制粘贴进去。

将ssd1306.py驱动文件通过Thonny上传到ESP32-C3的根目录。


"""
显示在ssd1306上的贪吃蛇游戏
运行后,可以通过按任意按钮启动游戏。
默认情况下,蛇初始时从左到右移动。游戏的目的是收集尽可能多的水果,水果将随机放置。
随着每吃一个水果,蛇就会变得更长。
当蛇撞到墙上或它自己时,游戏结束,显示Gameover。
此时可通过按任意按钮,将游戏还原为起始值,然后触摸按钮即可再次启动游戏。
"""
import random
import time


from machine import Pin, I2C


import ssd1306


SCREEN_WIDTH = 128
SCREEN_HEIGHT = 64


#正
UP_PIN = Pin(7, Pin.IN, Pin.PULL_UP)
DOWN_PIN = Pin(2, Pin.IN, Pin.PULL_UP)
LEFT_PIN = Pin(6, Pin.IN, Pin.PULL_UP)
RIGHT_PIN = Pin(5, Pin.IN, Pin.PULL_UP)




# snake config
SNAKE_PIECE_SIZE = 3  # 蛇的每一格占用3*3个像素
MAX_SNAKE_LENGTH = 150  # 蛇的最长长度
MAP_SIZE_X = 41  # 活动范围
MAP_SIZE_Y = 20
START_SNAKE_SIZE = 5  # 初始长度
SNAKE_MOVE_DELAY = 10  # 移动延时




# game config
class State(object):
    START = 0
    RUNNING = 1
    GAMEOVER = 2


    @classmethod
    def setter(cls, state):
        if state == cls.START:
            return cls.START
        elif state == cls.RUNNING:
            return cls.RUNNING
        elif state == cls.GAMEOVER:
            return cls.GAMEOVER




class Direction(object):
    # 注意顺序
    UP = 0
    LEFT = 1
    DOWN = 2
    RIGHT = 3


    @classmethod
    def setter(cls, dirc):
        if dirc == cls.UP:
            return cls.UP
        elif dirc == cls.DOWN:
            return cls.DOWN
        elif dirc == cls.LEFT:
            return cls.LEFT
        elif dirc == cls.RIGHT:
            return cls.RIGHT


i2c = I2C(0, scl=Pin(0), sda=Pin(1), freq=400000)
#i2c = I2C(0)
screen = ssd1306.SSD1306_I2C(SCREEN_WIDTH, SCREEN_HEIGHT, I2C(0))
screen.rotate(2)


################ Snake 功能实现 ###################
class Snake(object):
    def __init__(self):
        self.snake = []  # 初始位置[(x1,y1),(x2,y2),...]一个元组列表
        self.fruit = []  # 水果,[x,y]
        self.snake_length = START_SNAKE_SIZE
        self.direction = Direction.RIGHT  # 当前前进方向
        self.new_direction = Direction.RIGHT  # 用户按键后的前进方向
        self.game_state = None
        self.display = screen
        self.setup_game()


    def setup_game(self):
        """初始化游戏"""
        self.game_state = State.START
        direction = Direction.RIGHT
        new_direction = Direction.RIGHT
        self.reset_snake()
        self.generate_fruit()
        self.display.fill(0)
        self.draw_map()
        #self.show_score()
        #self.show_press_to_start()
        self.display.show()


    def reset_snake(self):
        """重设蛇的位置"""
        self.snake = []  # 重置
        self.snake_length = START_SNAKE_SIZE
        for i in range(self.snake_length):
            self.snake.append((MAP_SIZE_X // 2 - i, MAP_SIZE_Y // 2))


    def check_fruit(self):
        """检测蛇是否吃到水果,能否继续吃水果"""
        if self.snake[0][0] == self.fruit[0] and self.snake[0][1] == self.fruit[1]:
            if self.snake_length + 1 < MAX_SNAKE_LENGTH:
                self.snake_length += 1
                # 吃到水果后,将蛇增加一格
                self.snake.insert(0, (self.fruit[0], self.fruit[1]))
            self.generate_fruit()


    def generate_fruit(self):
        """随机生成水果位置,注意不能生成在蛇身上"""
        while True:
            self.fruit = [random.randint(1, MAP_SIZE_X - 1), random.randint(1, MAP_SIZE_Y - 1)]
            fruit = tuple(self.fruit)
            if fruit in self.snake:
                # 生成在蛇身上
                continue
            else:
                print('fruit: ', self.fruit)
                break


    @staticmethod
    def button_press():
        """是否有按键按下"""
        for pin in UP_PIN, DOWN_PIN, LEFT_PIN, RIGHT_PIN:
            if pin.value() == 0:  # 低电平表示按下
                return True
        return False


    def read_direction(self):
        """读取新的按键方向,不能与当前方向相反"""
        for direction, pin in enumerate((UP_PIN, LEFT_PIN, DOWN_PIN, RIGHT_PIN)):
            if pin.value() == 0 and not (direction == (self.direction + 2) % 4):
                self.new_direction = Direction.setter(direction)
                return


    def collection_check(self, x, y):
        """检查蛇社否撞到墙或者(x,y)位置"""
        for i in self.snake:
            if x == i[0] and y == i[1]:
                return True
        if x < 0 or y < 0 or x >= MAP_SIZE_X or y >= MAP_SIZE_Y:
            return True
        return False


    def move_snake(self):
        """按照方向键移动蛇,返回能否继续移动的布尔值"""
        x, y = self.snake[0]
        new_x, new_y = x, y


        if self.direction == Direction.UP:
            new_y -= 1
        elif self.direction == Direction.DOWN:
            new_y += 1
        elif self.direction == Direction.LEFT:
            new_x -= 1
        elif self.direction == Direction.RIGHT:
            new_x += 1


        if self.collection_check(new_x, new_y):  # 不能继续移动
            return False


        self.snake.pop()  # 去除最后一个位置
        self.snake.insert(0, (new_x, new_y))  # 在开头添加新位置
        return True  # 能继续移动


    def draw_map(self):
        """绘制地图区域: 蛇、水果、边界"""
        offset_map_x = SCREEN_WIDTH - SNAKE_PIECE_SIZE * MAP_SIZE_X - 2
        offset_map_y = 2


        # 绘制水果
        self.display.rect(self.fruit[0] * SNAKE_PIECE_SIZE + offset_map_x,
                          self.fruit[1] * SNAKE_PIECE_SIZE + offset_map_y,
                          SNAKE_PIECE_SIZE, SNAKE_PIECE_SIZE, 1)
        # 绘制地图边界, 边界占一个像素,但是绘制时在内侧留一个像素,当蛇头部到达内部一个像素时,即判定为碰撞
        self.display.rect(offset_map_x - 2,
                          0,
                          SNAKE_PIECE_SIZE * MAP_SIZE_X + 4,
                          SNAKE_PIECE_SIZE * MAP_SIZE_Y + 4, 1)
        # 绘制蛇
        for x, y in self.snake:
            self.display.fill_rect(x * SNAKE_PIECE_SIZE + offset_map_x,
                                   y * SNAKE_PIECE_SIZE + offset_map_y,
                                   SNAKE_PIECE_SIZE,
                                   SNAKE_PIECE_SIZE, 1)


    def show_score(self):
        """显示得分"""
        score = self.snake_length - START_SNAKE_SIZE
        self.display.text('Score:%d' % score, 4, 2, 1)


    def show_press_to_start(self):
        """提示按任意键开始游戏"""
        self.display.text('Press', 4, 40, 1)
        self.display.text('any', 60, 40, 1)
        self.display.text('Key!', 100, 40, 1)


    def show_game_over(self):
        """显示游戏结束"""
        self.display.text('Game', 50, 25, 1)
        self.display.text('Over!', 50, 35, 1)




#################  循环运行程序  ##################
if __name__ == '__main__':
    # print('******** Start ********')
    snake = Snake()
    move_time = 0
    while True:
        if snake.game_state == State.START:
            if Snake.button_press():
                snake.game_state = State.RUNNING


        elif snake.game_state == State.RUNNING:
            move_time += 1
            snake.read_direction()
            if move_time >= SNAKE_MOVE_DELAY:
                snake.direction = snake.new_direction
                snake.display.fill(0)
                if not snake.move_snake():
                    snake.game_state = State.GAMEOVER
                    snake.show_game_over()
                    snake.show_score()
                    #self.show_press_to_start()
                    time.sleep(1)
                snake.draw_map()
                #snake.show_score()
                snake.display.show()
                snake.check_fruit()
                move_time = 0


        elif snake.game_state == State.GAMEOVER:
            if Snake.button_press():
                time.sleep_ms(500)
                snake.setup_game()
                print('******** new game ********')
                snake.game_state = State.START


        time.sleep_ms(20)



8 效果演示





9 总结

本项目实现了贪吃蛇的基本功能,总体上比较简陋。后期可以通过完善和美化UI,使人机界面更友好;可以增加无源蜂鸣器,增加游戏音效;增加锂电池脱机使用。


10 项目资源下载附件GreedySnake.rar








创作许可协议

本项目采用 None(不开放任何权利,保留所有权利) 进行许可。

评论(9)
Amos Young
Amos Young
奇思妙想,收藏学习
派大星ym
派大星ym
666666666666
派大星ym
派大星ym
评论内容
花生编程
花生编程
好赞啊!!!
花生编程
花生编程
厉害厉害
腿毛利小五郎
腿毛利小五郎
怀旧,超赞
Amos Young
Amos Young
超级厉害,学习了,研究一下。
耘想
耘想
hihihihihhihihiih
耘想
耘想
666666666666666
- 没有更多了 -