本帖最后由 sky007 于 2023-6-8 22:44 编辑
贪食蛇大作战
很高兴参加 Beetle ESP32-C3试用活动,这个小项目用到了 DF的C3模组以及自己打了 2812灯板PCB,与 小欧机器人平台(http://www.ovorobot.com)结合,利用 MQTT平台(www.emqx.com),实现了贪食蛇蛇对战的场景。之前,是考虑做激光对战小车的平台,但后来想,还是先做这个2812灯板的小项目来验证一下,一方面可以利用一下之前让ChartGPT写的贪食蛇程序,另一方面,也积累一下MQTT通信的学习。
这个项目的总体思路是:两个控制器的C3模运行并订阅MQTT主题,小欧机器人的APP作为裁判员,向MQTT主题发起游戏开启指令,控制器收到指令后分别进行各自的贪食蛇游戏,当控制器吃到果实后,会告诉小欧机器人APP,它会播报实况信息。如果有一方碰到边沿后,游戏失败,控制器会发消息到MQTT主题,小欧机器人APP会宣布比赛结果,播放图片和音效。
可以参考以下流程图,进行初步了解。
如果以上描述还是不够清晰,可以看看B站的这个视频——贪食蛇大作战
(https://www.bilibili.com/video/BV1Ks4y1v71o?buvid=XX25F532A443BEBBE5990166D0CFF4DA69DA3&is_story_h5=false&mid=lJ9bKaN2CiRRy3cz%2BxrEKg%3D%3D&plat_id=240&share_from=ugc&share_medium=android&share_plat=android&share_source=WEIXIN&share_tag=s_i×tamp=1686188472&unique_k=xJq7RHr&up_id=142708284)
下面,我来介绍一下,这个项目是如何实现的,我将分Beetle ESP32-C3简介、小欧机器人平台简介、MQTT平台简介,PCB平台简介,代码解析这5个部分来说明,希望可以把我的想法描述清楚。
一、Beetle ESP32-C3简介 啥也不用多说,官方资料都在这里https://wiki.dfrobot.com.cn/_SKU_DFR0868_Beetle_ESP32_C3,特别小巧的一个模组,可以做特别小巧的作品。很不幸,由于操作不慎,被我烧了一块,怪心疼的。
二、小欧机器人平台简介
小欧是个很强大的在线编程平台http://www.ovorobot.com/index.html,我的理解,简单点来说,就是把手机作为单片机,利用手机的摄像头、散光灯、扬声器、显示屏、触摸屏、光线传感器、加速度传感器等等作为编程用的传感器,也可以调用网络图片、声音,也可以调用各种API,直接让小欧来使用,他甚至还是一台服务器,在线编程后可以实现各种你想要的功能。关键是开发者叶老师还对所有相关的内容,建立了知识树(小欧知识树:http://atom.ovorobot.com/index.php)可以方便的检索到要学习的内容和示例代码,而且在不断丰富,非常强大,非常方便,有兴趣可以试一下。
三、MQTT平台简介
这个平台https://www.emqx.com/用起来还是很方便的。不用关注服务器端的情况,公共账号也能用。只要在程序里嵌入代码就可以。只要有网络,无论在哪里,都可以方便的控制。
micropython这么设置:
小欧图形化这么设置
四、PCB平台简介
嘉立创EDAhttps://lceda.cn/,每月2张免费包邮优惠券,只要你会画简单的电路图,这个羊毛可以薅。
五、代码解析
1、控制器A
- '''
- 实验名称:小欧MQTT贪食蛇大作战
- 版本:v1.0
- 日期:2023.6
- 改编:sky
- 说明:编程实现MQTT通信,小欧宣布贪食蛇大作战开始后,2个控制器分别开始自己的游戏,吃到果实,小欧就播报,若一方超出边界,即为失败,小欧宣布比赛结果
- 注意:上传程序时应对应相应的CLIENT_ID,控制器A是sky,控制器B是lucy
- 小欧程序地址:http://www.ovorobot.com/blockly/blockly.html#-7La73tLfUyNsZpZcgJJYFnRVDNPWIxYzE3K
- '''
- import network,time #导入网络和时间
- from simple import MQTTClient #导入MQTT板块
- from machine import Pin #导入引脚
- import random #导入随机数
- from neopixel import NeoPixel #导入2812彩灯
- import _thread
-
- # 定义输出管脚
- pin = Pin(3, Pin.OUT)
-
- # 定义灯带参数
- pixels = NeoPixel(pin, 45)
-
- #定义输入管脚
- UP= Pin(1, Pin.IN, Pin.PULL_UP)
- DOWN= Pin(2, Pin.IN, Pin.PULL_UP)
- LEFT= Pin(21, Pin.IN, Pin.PULL_UP)
- RIGHT= Pin(20, Pin.IN, Pin.PULL_UP)
-
- # 定义蛇的初始位置、长度、方向和颜色
- snake_pos = [(2,3), (2,2), (2,1)] #初始化蛇的位置,头向右【坐标点(y,x),坐标x=0~8,y=0~4】
- snake_len = 3 #初始化蛇的长度,3
- snake_direction = 'right' #初始化蛇的方向,向右
- snake_color = (10,0,0) #设定蛇的颜色
-
- # 定义食物的初始位置和颜色
- food_pos = (random.randint(0, 4), random.randint(0, 8)) #设定果实的位置
- food_color = (0,10,0) #设定果实的颜色
-
- # 初始清除灯带
- pixels.fill((0,0,0))
- pixels.write()
-
- #WIFI连接函数
- def WIFI_Connect():
-
- WIFI_LED=Pin(10, Pin.OUT) #初始化WIFI指示灯
-
- wlan = network.WLAN(network.STA_IF) #STA模式
- wlan.active(True) #激活接口
- start_time=time.time() #记录时间做超时判断
-
- if not wlan.isconnected():
- print('connecting to network...')
- wlan.connect('XXXXXX', 'YYYYYY') #输入WIFI账号密码
-
- while not wlan.isconnected():
-
- #LED闪烁提示
- WIFI_LED.value(1)
- time.sleep_ms(300)
- WIFI_LED.value(0)
- time.sleep_ms(300)
-
- #超时判断,15秒没连接成功判定为超时
- if time.time()-start_time > 15 :
- print('WIFI Connected Timeout!')
- wlan.active(False) #反激活WiFi
- break
-
- if wlan.isconnected():
- #LED点亮
- WIFI_LED.value(1)
-
- #串口打印信息
- print('network information:', wlan.ifconfig())
- return True
-
- else:
- return False
-
- #MQTT发布数据任务
- def MQTT_Send(TOPIC2,msg):
- client.publish(TOPIC2, CLIENT_ID+msg)
- print(CLIENT_ID+msg)
-
-
- #接收数据任务
- def MQTT_Rev(TOPIC1, msg):
- global START
- print(TOPIC1, msg)
- if msg==b'lucylose': #收到胜利的消息
- win()
- if msg==b'start': #收到开始比赛的消息
- START=True
-
-
- #执行WIFI连接函数并判断是否已经连接成功
- if WIFI_Connect():
- SERVER = 'broker.emqx.io' # MQTT服务器地址
- PORT = 1883 # MQTT服务器端口
- CLIENT_ID = 'sky' # 客户端ID
- TOPIC1 = 'ovosend' # 小欧发送消息TOPIC名称
- TOPIC2 = 'ovoreceive' # 小欧接收消息TOPIC名称
- client = MQTTClient(CLIENT_ID, SERVER, PORT) # 实例化客户端
- client.set_callback(MQTT_Rev) #配置回调函数,将反馈
- client.connect() #链接网络
- client.subscribe(TOPIC1) #订阅主题
-
-
-
- #定义失败灯效,并发送失败消息
- def lose():
- pixels.fill((10,10,10))
- pixels.write()
- MQTT_Send(TOPIC2,'lose')
-
- #定义胜利灯效
- def win():
- pixels.fill((10,0,0))
- pixels.write()
-
- START=False
-
- #贪食蛇主程序
- while True:
-
- client.check_msg() #调用MQTT_Send()函数,接收信息,并返回msg的值
-
- while START:
- # 绘制蛇和食物
- pixels.fill((0,0,0))
- pixels.write()
- for pos in snake_pos: #snake_pos蛇的位置
- pixels[pos[0]*9+pos[1]] = snake_color
- pixels[food_pos[0]*9+food_pos[1]] = food_color #food_pos食物的位置
- pixels.write()
- # 检测按键操作
- if snake_direction == 'right':
- next_pos = (snake_pos[0][0], snake_pos[0][1]+1) #最右边的点,y不变,x+1
- elif snake_direction == 'left':
- next_pos = (snake_pos[0][0], snake_pos[0][1]-1) #最右边的点,y不变,x-1
- elif snake_direction == 'up':
- next_pos = (snake_pos[0][0]-1, snake_pos[0][1]) #最右边的点,y-1,x不变
- elif snake_direction == 'down':
- next_pos = (snake_pos[0][0]+1, snake_pos[0][1]) #最右边的点,y+1,x不变
-
- #按键操作后,碰到边沿
- if next_pos[0]<0 or next_pos[0]>4 or next_pos[1]<0 or next_pos[1]>8 or next_pos in snake_pos:
- #添加生成音效
- lose()
- START=False
- continue
-
- #按键操作后,未碰到边沿
- snake_pos.insert(0, next_pos) #插入蛇的新坐标next_pos,位置插在index=0的前面
-
- if next_pos == food_pos:
- MQTT_Send(TOPIC2,'ate') #发送控制器CLIENT_ID吃到果实的消息
-
- snake_len += 1 #吃到了食物,蛇长度加一,重新生成食物
- food_pos = (random.randint(0, 4), random.randint(0, 8)) #随机生成新的果实
- else:
- snake_pos.pop() #没有吃到果实,蛇的身体就把最后一个元素删除。刚才在头部增加一个元素,现在减少一个元素,这样就实现了移动的效果。
- time.sleep(0.5)
-
- # 检测按键操作
- if LEFT.value()==0:
- snake_direction = 'left'
- time.sleep(0.3)
- elif RIGHT.value()==0:
- snake_direction = 'right'
- time.sleep(0.3)
- elif UP.value()==0:
- snake_direction = 'up'
- time.sleep(0.3)
- elif DOWN.value()==0:
- snake_direction = 'down'
- time.sleep(0.3)
- print(snake_direction)
-
复制代码
2、控制器B
- '''
- 实验名称:小欧MQTT贪食蛇大作战
- 版本:v1.0
- 日期:2023.6
- 改编:sky
- 说明:编程实现MQTT通信,小欧宣布贪食蛇大作战开始后,2个控制器分别开始自己的游戏,吃到果实,小欧就播报,若一方超出边界,即为失败,小欧宣布比赛结果
- 注意:上传程序时应对应相应的CLIENT_ID,控制器A是sky,控制器B是lucy
- 小欧程序地址:http://www.ovorobot.com/blockly/blockly.html#-7La73tLfUyNsZpZcgJJYFnRVDNPWIxYzE3K
- '''
- import network,time #导入网络和时间
- from simple import MQTTClient #导入MQTT板块
- from machine import Pin #导入引脚
- import random #导入随机数
- from neopixel import NeoPixel #导入2812彩灯
- import _thread
-
- # 定义输出管脚
- pin = Pin(3, Pin.OUT)
-
- # 定义灯带参数
- pixels = NeoPixel(pin, 45)
-
- #定义输入管脚
- UP= Pin(1, Pin.IN, Pin.PULL_UP)
- DOWN= Pin(2, Pin.IN, Pin.PULL_UP)
- LEFT= Pin(21, Pin.IN, Pin.PULL_UP)
- RIGHT= Pin(20, Pin.IN, Pin.PULL_UP)
-
- # 定义蛇的初始位置、长度、方向和颜色
- snake_pos = [(2,3), (2,2), (2,1)] #初始化蛇的位置,头向右【坐标点(y,x),坐标x=0~8,y=0~4】
- snake_len = 3 #初始化蛇的长度,3
- snake_direction = 'right' #初始化蛇的方向,向右
- snake_color = (10,0,0) #设定蛇的颜色
-
- # 定义食物的初始位置和颜色
- food_pos = (random.randint(0, 4), random.randint(0, 8)) #设定果实的位置
- food_color = (0,10,0) #设定果实的颜色
-
- # 初始清除灯带
- pixels.fill((0,0,0))
- pixels.write()
-
- #WIFI连接函数
- def WIFI_Connect():
-
- WIFI_LED=Pin(10, Pin.OUT) #初始化WIFI指示灯
-
- wlan = network.WLAN(network.STA_IF) #STA模式
- wlan.active(True) #激活接口
- start_time=time.time() #记录时间做超时判断
-
- if not wlan.isconnected():
- print('connecting to network...')
- wlan.connect('XXXXXX', 'YYYYYY') #输入WIFI账号密码
-
- while not wlan.isconnected():
-
- #LED闪烁提示
- WIFI_LED.value(1)
- time.sleep_ms(300)
- WIFI_LED.value(0)
- time.sleep_ms(300)
-
- #超时判断,15秒没连接成功判定为超时
- if time.time()-start_time > 15 :
- print('WIFI Connected Timeout!')
- wlan.active(False) #反激活WiFi
- break
-
- if wlan.isconnected():
- #LED点亮
- WIFI_LED.value(1)
-
- #串口打印信息
- print('network information:', wlan.ifconfig())
- return True
-
- else:
- return False
-
- #MQTT发布数据任务
- def MQTT_Send(TOPIC2,msg):
- client.publish(TOPIC2, CLIENT_ID+msg)
- print(CLIENT_ID+msg)
-
-
- #接收数据任务
- def MQTT_Rev(TOPIC1, msg):
- global START
- print(TOPIC1, msg)
- if msg==b'skylose': #收到胜利的消息
- win()
- if msg==b'start': #收到开始比赛的消息
- START=True
-
-
- #执行WIFI连接函数并判断是否已经连接成功
- if WIFI_Connect():
- # 设置播放音效
- SERVER = 'broker.emqx.io' # MQTT服务器地址
- PORT = 1883 # MQTT服务器端口
- CLIENT_ID = 'lucy' # 客户端ID
- TOPIC1 = 'ovosend' # 小欧发送消息TOPIC名称
- TOPIC2 = 'ovoreceive' # 小欧接收消息TOPIC名称
- client = MQTTClient(CLIENT_ID, SERVER, PORT) # 实例化客户端
- client.set_callback(MQTT_Rev) #配置回调函数,将反馈
- client.connect()
- client.subscribe(TOPIC1) #订阅主题
-
-
-
- #定义失败灯效,并发送失败消息
- def lose():
- pixels.fill((10,10,10))
- pixels.write()
- MQTT_Send(TOPIC2,'lose')
-
- #定义胜利灯效
- def win():
- pixels.fill((10,0,0))
- pixels.write()
-
- START=False
-
- #贪食蛇主程序
- while True:
-
- client.check_msg() #调用MQTT_Send()函数,接收信息,并返回msg的值
-
- while START:
- # 绘制蛇和食物
- pixels.fill((0,0,0))
- pixels.write()
- for pos in snake_pos: #snake_pos蛇的位置
- pixels[pos[0]*9+pos[1]] = snake_color
- pixels[food_pos[0]*9+food_pos[1]] = food_color #food_pos食物的位置
- pixels.write()
- # 检测按键操作
- if snake_direction == 'right':
- next_pos = (snake_pos[0][0], snake_pos[0][1]+1) #最右边的点,y不变,x+1
- elif snake_direction == 'left':
- next_pos = (snake_pos[0][0], snake_pos[0][1]-1) #最右边的点,y不变,x-1
- elif snake_direction == 'up':
- next_pos = (snake_pos[0][0]-1, snake_pos[0][1]) #最右边的点,y-1,x不变
- elif snake_direction == 'down':
- next_pos = (snake_pos[0][0]+1, snake_pos[0][1]) #最右边的点,y+1,x不变
-
- #按键操作后,碰到边沿
- if next_pos[0]<0 or next_pos[0]>4 or next_pos[1]<0 or next_pos[1]>8 or next_pos in snake_pos:
- #添加生成音效
- lose()
- START=False
- continue
-
- #按键操作后,未碰到边沿
- snake_pos.insert(0, next_pos) #插入蛇的新坐标next_pos,位置插在index=0的前面
-
- if next_pos == food_pos:
- MQTT_Send(TOPIC2,'ate') #发送控制器CLIENT_ID吃到果实的消息
-
- snake_len += 1 #吃到了食物,蛇长度加一,重新生成食物
- food_pos = (random.randint(0, 4), random.randint(0, 8)) #随机生成新的果实
- else:
- snake_pos.pop() #没有吃到果实,蛇的身体就把最后一个元素删除。刚才在头部增加一个元素,现在减少一个元素,这样就实现了移动的效果。
- time.sleep(0.5)
-
- # 检测按键操作
- if LEFT.value()==0:
- snake_direction = 'left'
- time.sleep(0.3)
- elif RIGHT.value()==0:
- snake_direction = 'right'
- time.sleep(0.3)
- elif UP.value()==0:
- snake_direction = 'up'
- time.sleep(0.3)
- elif DOWN.value()==0:
- snake_direction = 'down'
- time.sleep(0.3)
- print(snake_direction)
-
复制代码
3、小欧机器人
- http://www.ovorobot.com/blockly/blockly.html#-lWuq8T4AVcrqW50Mtycgp2jFnvrgUEmJk8g
复制代码
目前,这个项目还没有很完善,PCB还没最终完成,无源蜂鸣器需要加上去,那样联网成功、吃到果实都可以在本地发出提示音,后面再完善。另外,PCB是裸露的,需要打印个外壳,把板子包裹起来,手感会好很多。还有,利用这个控制器,还可以做其他的小项目,比如小欧MIDI,来作为乐器也是不错的,还可以做一个猫抓老鼠的互动游戏,好像也挺好玩……
但是,先告一段落,以后再折腾吧,先得把MQTT激光对战小车捣腾起来。
|