10787浏览
查看: 10787|回复: 7

[ESP8266/ESP32] 基于Beetle ESP32-C3的贪吃蛇游戏

[复制链接]
本帖最后由 豆爸 于 2023-6-7 10:48 编辑

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








Amos Young  中级技师

发表于 2023-6-28 10:49:00

超级厉害,学习了,研究一下。
回复

使用道具 举报

腿毛利小五郎  高级技师

发表于 2023-6-30 17:20:53

怀旧,超赞
回复

使用道具 举报

花生编程  中级技匠

发表于 2023-7-29 22:18:35

厉害厉害
回复

使用道具 举报

花生编程  中级技匠

发表于 2023-7-30 19:41:34

好赞啊!!!
回复

使用道具 举报

派大星ym  初级技匠

发表于 2023-8-20 11:29:37

   
回复

使用道具 举报

派大星ym  初级技匠

发表于 2023-8-20 11:32:38

666666666666
回复

使用道具 举报

Amos Young  中级技师

发表于 2023-10-26 11:21:32

奇思妙想,收藏学习
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail