- 项目介绍
本项目为2022年寒假一起练活动项目三——复古游戏移植。要求如下:设计或移植一款经典的游戏(硬禾学堂上已经实现的贪吃蛇等游戏除外),通过LCD屏显示,通过按键和四向摇杆控制游戏的动作;在游戏中要通过蜂鸣器播放背景音乐。我们选择移植的为经典游戏俄罗斯方块。
- 项目完成的功能和性能
首先我们设计了初始化的start面板,可以通过板卡上的A或B按键来启动游戏,初始化屏幕上有我们的涂鸦“theshy”(别问,问就是致敬doge),有micropython的字母和按A键开始的提示。然后,我们设计了7个“盒子”来模拟游戏中降落的方块,每个方块都能进行旋转,“田”字形方块使用22的矩阵;“一”字型方块使用5*5的矩阵;其余使用3*3的矩阵,以此来保证旋转时方块会沿中轴旋转,不会出现旋转时方块偏移的现象。然后是在游戏中有我们的计分面板,积分面板设计在左侧,从上到下分别是游戏名称、micropython的英文字母和所得分数。初始分数为零,每消掉一行得10分,分数的上限为1000分,超过1000分则显示999分。游戏实现了音乐功能,调用了已有的音乐函数,点击开始即可通过蜂鸣器持续播放音乐。当方块不能下落也就是方块叠到屏幕顶部时游戏结束,会弹出gameover的提示。同时,为了增加观赏性,我们定义了多种颜色并加以引用,所有的数字及英文字母都冠以不同的颜色。以上即是我们的项目完成情况。
- 实现的思路和过程
- 主要代码及说明
屏幕初始化、按键初始化及音乐序列代码
B = Pin(5,Pin.IN, Pin.PULL_UP)
A = Pin(6,Pin.IN, Pin.PULL_UP)
xAxis = ADC(Pin(28))
yAxis = ADC(Pin(29))
#音乐代码序列
song = '0 E3 1 0;2 E4 1 0;4 E3 1 0;6 E4 1 0;8 E3 1 0;10 E4 1 0;12 E3 1 0;14 E4 1 0;16 A3 1 0;18 A4 1 0;20 A3 1 0;22 A4 1 0;24 A3 1 0;26 A4 1 0;28 A3 1 0;30 A4 1 0;32 G#3 1 0;34 G#4 1 0;36 G#3 1 0;38 G#4 1 0;40 E3 1 0;42 E4 1 0;44 E3 1 0;46 E4 1 0;48 A3 1 0;50 A4 1 0;52 A3 1 0;54 A4 1 0;56 A3 1 0;58 B3 1 0;60 C4 1 0;62 D4 1 0;64 D3 1 0;66 D4 1 0;68 D3 1 0;70 D4 1 0;72 D3 1 0;74 D4 1 0;76 D3 1 0;78 D4 1 0;80 C3 1 0;82 C4 1 0;84 C3 1 0;86 C4 1 0;88 C3 1 0;90 C4 1 0;92 C3 1 0;94 C4 1 0;96 G2 1 0;98 G3 1 0;100 G2 1 0;102 G3 1 0;104 E3 1 0;106 E4 1 0;108 E3 1 0;110 E4 1 0;114 A4 1 0;112 A3 1 0;116 A3 1 0;118 A4 1 0;120 A3 1 0;122 A4 1 0;124 A3 1 0;0 E6 1 1;4 B5 1 1;6 C6 1 1;8 D6 1 1;10 E6 1 1;11 D6 1 1;12 C6 1 1;14 B5 1 1;0 E5 1 6;4 B4 1 6;6 C5 1 6;8 D5 1 6;10 E5 1 6;11 D5 1 6;12 C5 1 6;14 B4 1 6;16 A5 1 1;20 A5 1 1;22 C6 1 1;24 E6 1 1;28 D6 1 1;30 C6 1 1;32 B5 1 1;36 B5 1 1;36 B5 1 1;37 B5 1 1;38 C6 1 1;40 D6 1 1;44 E6 1 1;48 C6 1 1;52 A5 1 1;56 A5 1 1;20 A4 1 6;16 A4 1 6;22 C5 1 6;24 E5 1 6;28 D5 1 6;30 C5 1 6;32 B4 1 6;36 B4 1 6;37 B4 1 6;38 C5 1 6;40 D5 1 6;44 E5 1 6;48 C5 1 6;52 A4 1 6;56 A4 1 6;64 D5 1 6;64 D6 1 1;68 D6 1 1;70 F6 1 1;72 A6 1 1;76 G6 1 1;78 F6 1 1;80 E6 1 1;84 E6 1 1;86 C6 1 1;88 E6 1 1;92 D6 1 1;94 C6 1 1;96 B5 1 1;100 B5 1 1;101 B5 1 1;102 C6 1 1;104 D6 1 1;108 E6 1 1;112 C6 1 1;116 A5 1 1;120 A5 1 1;72 A5 1 6;80 E5 1 6;68 D5 1 7;70 F5 1 7;76 G5 1 7;84 E5 1 7;78 F5 1 7;86 C5 1 7;88 E5 1 6;96 B4 1 6;104 D5 1 6;112 C5 1 6;120 A4 1 6;92 D5 1 7;94 C5 1 7;100 B4 1 7;101 B4 1 7;102 C5 1 7;108 E5 1 7;116 A4 1 7'
#屏幕初始化
st7789_res = 0
st7789_dc = 1
disp_width = 240
disp_height = 240
game_sck = machine.Pin(2)
game_tx = machine.Pin(3)
spi = machine.SPI(0, baudrate=31250000, polarity=1, phase=1,
sck=game_sck,mosi=game_tx)
display = st7789.ST7789(spi,disp_width,disp_height,
reset=machine.Pin(st7789_res,machine.Pin.OUT),
dc=machine.Pin(st7789_dc,machine.Pin.OUT),
xstart=0,ystart=0,rotation=0)
此部分采用了贪吃蛇例程及音乐例程中所用到的屏幕初始化操作和音乐序列
2、俄罗斯方块的其中不同方块的设计,以及颜色的调用
设定屏幕为12*16的格子
g = []
for i in range(16):
g.append([0]*12)
block1 = [[1,1],[1,1]]
block2 = [[1,1,0],[0,1,1],[0,0,0]]
block3 = [[0,1,1],[1,1,0],[0,0,0]]
block4 = [[1,1,0],[0,1,0],[0,1,0]]
block5 = [[0,1,1],[0,1,0],[0,1,0]]
block6 = [[0,1,0],[1,1,1],[0,0,0]]
block7 = [[0,0,0,0,0],[0,0,0,0,0],[1,1,1,1,0],[0,0,0,0,0],[0,0,0,0,0]]
blocks = [block1,block2,block3,block4,block5,block6,block7]
color = [0xF800,0x0000,0x001F,0x07E0,0x07FF,0xF81F,0xFFE0,0xFFFF]
通过3*3的数组及5*5的数组成功让7种不同方块成功生成。
3、随机生成新的方块
def newblock():
block_id = random.randint(0,6)
block_org = blocks[block_id]
b = []
for i in range(len(block_org)):
b.append(block_org[i][:])
return b
4、消除上一秒留存的方块
def leave():
j=block_row
for r in block:
i = block_column
for d in r:
if d == 1:
g[j][i] = 0
i+= 1
j+= 1
5、向下加一个格子位移,使其下落,生成子方块
def enter():
global block_row
global block_column
j = block_row
for r in block:
i = block_column
for d in r:
if d == 1:
g[j][i] = 1
i+= 1
j+= 1
6、自动下落模块,首先删除上一秒的方块格子,向下加一,若果没有判断出碰到方块,则继续下落,如果碰到别的方块或者触底,或者凑够一行,则会执行生成新的方块。
def falldown():
global block_row,block_column,block,block_id
leave()
block_row+=1
if check() == False:
block_row-=1
enter()
clear()
block=newblock()
block_column = 4
block_row = 0
enter()
7、判断方块是否碰到其它方块或者是否碰到边界
def check():
global block_row
global block_column
j = block_row
for r in block:
i = block_column
for d in r:
if d == 1:
if i < 0 or i >= 12:
return False
if j < 0 or j >= 16:
return False
if g[j][i] == 1:
return False
i+= 1
j+= 1
return True
8、执行顺时针或者逆时针的旋转操作。
def clockwise():
N = len(block)
a = []
for i in range(N):
a.append(block[i][:])
for i in range(N):
for j in range(N):
block[N-j-1][i] = a[i][j]
def counterclockwise():
N = len(block)
a = []
for i in range(N):
a.append(block[i][:])
for i in range(N):
for j in range(N):
block[j][N-i-1] = a[i][j]
def spin():
leave()
clockwise()
if check() == False:
counterclockwise()
enter()
9、左右按键及下落按键操作
def left():
global block_column
leave()
block_column-= 1
if check() == False:
block_column+= 1
enter()
def right():
global block_column
leave()
block_column+= 1
if check() == False:
block_column-= 1
enter()
def down():
global block_row
leave()
block_row+= 1
if check() == False:
block_row-= 1
enter()
10、如果方块凑够一行,自动消除
def clear():
global score, gameover
i = 0
c = 0
while i<16:
if 0 in g[i]:
i+=1
else:
c+=1
del g[i]
g.insert(0,[0]*12)
if c>0:
score += 10*(2**(c-1))
if score>1000:
score=999
print(score,"a")
if 1 in g[0]:
gameover = True
11、在屏幕上画出之前所选的方块
def createblock():
global color
display.fill_rect(55,0,185,240,0x0000)
y = 0
for r in g:
x = 55
for d in r:
if d==1:
display.rect(x,y,15,15,0x10)
display.fill_rect(x+1,y+1,13,13,color[0])
x+=cell
y+=cell
12、主函数部分:调用之前的模块,判断按键是否触发,生成分数界面
def main():
global gameover,song
mySong = music(song,pins =[Pin(23)])
while True:
playmusic(mySong,400)
xValue = xAxis.read_u16()
yValue = yAxis.read_u16()
if B.value()==0:
spin()
print("spin")
elif xValue > 50000:
down()
print("down")
elif yValue < 10000:
left()
print("left")
elif yValue > 50000:
right()
print("right")
elif A.value()==0:
mySong.stop()
print("song is stop")
else:
print("fall down")
display.fill_rect(0,0,4,240,color[1])
display.fill_rect(50,0,4,240,color[7])
display.text(font1,"Score",1,200,color[6])
display.text(font2,str(score),1,150,color[6])
display.text(font1,"Micro",1,80,color[4])
display.text(font1,"Python",1,100,color[4])
display.text(font1,"Teris",1,20,color[3])
createblock()
falldown()
if gameover == True:
mySong.stop()
display.fill(0x0000)
display.text(font2,"GAME OVER",45,60)
display.text(font2,"try agian",45,130)
time.sleep(1)
start()
13、开始界面及按键开始触发步骤
def start():
display.fill(0x0000)
display.text(font2,"Micropython",30,50,color=0x001F)
display.text(font2,"By The Shy",10,10,color=0xFFFF)
display.text(font2,"Teris",70,95,color=0xF81F)
display.text(font2,"Start",70,140,color=0x07E0)
display.text(font2,"Press A",60,180,color=0x07E0)
while True:
buttonValueA = A.value()
if buttonValueA==0:
print("OK")
display.fill(0x0000)
enter()
main()
运行结果:
1、欢迎界面:
2、游戏运行界面:
3、游戏通关画面:
4、游戏结束画面:
- 遇到的难题及解决办法
在本次项目设计过程中,算法以及屏幕部分是最困难的,借鉴例程并通过贪吃蛇游戏获得启发,进行屏幕的刷新来生成新的方块,并将旧方块刷新掉,成功实现了自动下落功能。
- 未来的计划和建议
经过一个假期的学习与努力,我们最终交出了一份还算让我们满意的答卷,当然,还有一些内容由于时间关系和能力限制难以实现,例如游戏的暂停问题、游戏中的下一个方块提示显示问题。这也有待于我们的后续思考与学习。
这个假期是我们第一次接触到树莓派RP2040嵌入式系统开发板,也是为数不多的与硬件直接接触的编程实践。我们总的来说漂亮的完成了我们的目标,成功移植了俄罗斯方块游戏并且能够成功的展示,不仅在项目进程中学习到了新的编程知识,还找回了久违的童年的乐趣。同时,树莓派RP2040开发板也极具有探索价值,我们除了做成该项目外,还探索了项目一鼠标的模拟并取得一定的成效,在人人沉迷游戏的当下,能够静下心来,探索未曾接触的领域也给了我们一种成就感与充实感。非常感谢能有幸参加这次的寒假一起练活动,如果以后还有机会的话我们势必还会继续支持这类活动。
附板卡介绍:
基于树莓派RP2040的嵌入式系统学习平台,可以通过C/C++以及MicroPython编程来学习嵌入式系统的工作原理和应用。可以搭配传感器、模拟电路外设可以完成更多创意项目,并可以做为电赛的控制、显示接口平台。可提升的技能有:
- MicroPython或C/C++编程、ArmCortexM0+嵌入式系统
- 总线访问 - SPI、I2C
- 图形化信息显示 - 240 * 240 LCD
- 按键和模拟信号的输入控制
- 红外接收和控制
- 姿态传感器的使用
-
实物图和原理图展示:
-
学习平台的特点:
作为一个嵌入式系统的学习平台,首先要基于核心芯片的核心板的特点以及嵌入式系统的关键知识点来定义这款学习平台:
- 采用树莓派Pico核心芯片RP2040:
- 双核ArmCortexM0+内核,可以运行到133MHz
- 264KB内存
- 性能强大、高度灵活的可编程IO可用于高速数字接口
- 片内温度传感器、并支持外部4路模拟信号输入,内部ADC采样率高达500Ksps、12位精度
- 支持MicroPython、C、C++编程
板上功能:
- 240*240分辨率的彩色IPSLCD,SPI接口,控制器为ST7789
- 四向摇杆 + 2个轻触按键 + 一个三轴姿态传感器MMA7660用做输入控制
- 板上外扩2MBFlash,预刷MicroPython的UF2固件
- 一个红外接收管 + 一个红外发射管
- 一个三轴姿态传感器MMA7660
- 一个蜂鸣器
- 双排16Pin连接器,有SPI、I2C以及2路模拟信号输入
- 可以使用MicroPython、C、C++编程
- USBTypeC连接器用于供电、程序下载