一、项目介绍
这是我参加“硬禾学堂2022寒假在家练”活动所完成的项目,项目使用的是树莓派RP2040开发板。
项目实现的总体功能是将复古游戏“五子棋”移植到树莓派RP2040开发板上,具体的需求如下所示:
- 能够在按下按钮A后开始播放背景音乐,并且再次按下停止播放。
- 能够实现用摇杆控制光标的上下左右移动。
- 能够在按下按钮B后在光标处下一颗黑色的棋子。
- 能够根据你所下的黑棋,由程序自主下一颗白棋。
- 能够在其中一方棋子连成五个以后结束游戏,并打出胜利提示。
- 能够在按下按钮start后重新开始游戏。
二、设计框图
三、简单的软硬件介绍
1、元器件
项目所使用的元器件为树莓派RP2040开发板,该开发板是由树莓派基金会推出的双核Arm Cortex M0+微控制器,有133MHz的时钟速率以及264KB的SRAM,支持C/C++、MicroPython编程。
2、软件
项目所使用的开发语言为MicroPython,MicroPython是一款编程语言兼容Python3的软件,用C写成的,能够运行在微控制器的硬件上并进行了相应的优化。
3、工具
项目所使用的开发工具为Thonny。Thonny是一款针对初学者的Python IDE工具。
Thonny的官方网站:https://thonny.org/
四、实现的功能及图片展示
1、游戏初始化测试
2、光标移动测试
拨动摇杆,得以控制光标的上下左右移动。
3、音乐播放测试
按下按钮A以后,开始播放背景音乐,再次按下按钮A以后,音乐暂停。
4、落子测试
按下按钮B后在光标处下一颗黑色的棋子,并且立马根据你所下的黑棋,由程序自主下一颗白棋。
5、游戏结束测试
在其中一方棋子连成五个以后结束游戏,并打出胜利提示。
6、游戏重启测试
按下按键start后,游戏重启。
五、主要代码片段及说明
1、硬件接口模块类(class Hardware)
主要实现一些涉及硬件接口的初始化。
- RP2040对于屏幕显示控制的初始化。(SPI、st7789、Pin)
- RP2040对于摇杆坐标读取的初始化。(ADC、Pin)
- RP2040对于A、B、start三个按钮状态读取的初始化。(Pin)
- RP2040对于音乐模块的初始化。(class PlayMusic)
class Hardware():
def __init__(self):
# screen
spi = SPI(0, baudrate=31250000, polarity=1, phase=0,
sck=Pin(game_kit.lcd_sck, Pin.OUT),
mosi=Pin(game_kit.lcd_sda, Pin.OUT))
screen = st7789.ST7789(
spi,
240,
240,
reset=Pin(game_kit.lcd_rst, Pin.OUT),
dc=Pin(game_kit.lcd_dc, Pin.OUT),
rotation=0)
# control
self.screen = screen
self.xAxis = ADC(Pin(28))
self.yAxis = ADC(Pin(29))
self.buttonB = Pin(5,Pin.IN, Pin.PULL_UP) #B
self.buttonA = Pin(6,Pin.IN, Pin.PULL_UP) #A
self.buttonSTART = Pin(7,Pin.IN, Pin.PULL_UP) #A
# bgm
song1 = '0 A5 2 26 0.6299212574958801;2 B5 2 26 0.6299212574958801;4 C6 6 26 0.6299212574958801;10 B5 2 26 0.6299212574958801;12 C6 4 26 0.6299212574958801;16 E6 4 26 0.6299212574958801;20 B5 8 26 0.6299212574958801;32 E5 4 26 0.6299212574958801;36 A5 6 26 0.6299212574958801;42 G5 2 26 0.6299212574958801;44 A5 4 26 0.6299212574958801;48 C6 4 26 0.6299212574958801;52 G5 8 26 0.6299212574958801;64 F5 2 26 0.6299212574958801;66 E5 2 26 0.6299212574958801;68 F5 6 26 0.6299212574958801;74 E5 2 26 0.6299212574958801;76 F5 4 26 0.6299212574958801;80 C6 4 26 0.6299212574958801;84 E5 8 26 0.6299212574958801;94 C6 2 26 0.6299212574958801;96 C6 2 26 0.6299212574958801;98 C6 2 26 0.6299212574958801;100 B5 6 26 0.6299212574958801;106 F#5 2 26 0.6299212574958801;108 F#5 4 26 0.6299212574958801;112 B5 4 26 0.6299212574958801;116 B5 8 26 0.6299212574958801;128 A5 2 26 0.6299212574958801;130 B5 2 26 0.6299212574958801;132 C6 6 26 0.6299212574958801;138 B5 2 26 0.6299212574958801;140 C6 4 26 0.6299212574958801;144 E6 4 26 0.6299212574958801;148 B5 8 26 0.6299212574958801;160 E5 2 26 0.6299212574958801;162 E5 2 26 0.6299212574958801;164 A5 6 26 0.6299212574958801;170 G5 2 26 0.6299212574958801;172 A5 4 26 0.6299212574958801;176 C6 4 26 0.6299212574958801;180 G5 8 26 0.6299212574958801;192 E5 4 26 0.6299212574958801;196 F5 4 26 0.6299212574958801;200 C6 2 26 0.6299212574958801;202 B5 2 26 0.6299212574958801;204 B5 4 26 0.6299212574958801;208 C6 4 26 0.6299212574958801;212 D6 4 26 0.6299212574958801;216 E6 2 26 0.6299212574958801;218 C6 2 26 0.6299212574958801;220 C6 8 26 0.6299212574958801;228 C6 2 26 0.6299212574958801;230 B5 2 26 0.6299212574958801;232 A5 4 26 0.6299212574958801;236 B5 4 26 0.6299212574958801;240 G#5 4 26 0.6299212574958801;244 A5 11 26 0.6299212574958801;256 C6 2 26 0.6299212574958801;258 D6 2 26 0.6299212574958801;260 E6 6 26 0.6299212574958801;266 D6 2 26 0.6299212574958801;268 E6 4 26 0.6299212574958801;272 G6 4 26 0.6299212574958801;276 D6 8 26 0.6299212574958801;288 G5 2 26 0.6299212574958801;290 G5 2 26 0.6299212574958801;292 C6 6 26 0.6299212574958801;298 B5 2 26 0.6299212574958801;300 C6 4 26 0.6299212574958801;304 E6 4 26 0.6299212574958801;308 E6 11 26 0.6299212574958801;324 A5 2 26 0.6299212574958801;326 B5 2 26 0.6299212574958801;328 C6 4 26 0.6299212574958801;332 B5 2 26 0.6299212574958801;334 C6 2 26 0.6299212574958801;336 D6 4 26 0.6299212574958801;340 C6 6 26 0.6299212574958801;346 G5 2 26 0.6299212574958801;348 G5 8 26 0.6299212574958801;356 F6 4 26 0.6299212574958801;360 E6 4 26 0.6299212574958801;364 D6 4 26 0.6299212574958801;368 C6 4 26 0.6299212574958801;372 E6 15 26 0.6299212574958801;388 E6 11 26 0.6299212574958801;400 E6 4 26 0.6299212574958801;404 A6 8 26 0.6299212574958801;412 G6 8 26 0.6299212574958801;420 E6 4 26 0.6299212574958801;424 D6 2 26 0.6299212574958801;426 C6 2 26 0.6299212574958801;428 C6 8 26 0.6299212574958801;436 D6 4 26 0.6299212574958801;440 C6 2 26 0.6299212574958801;442 D6 2 26 0.6299212574958801;444 D6 4 26 0.6299212574958801;448 G6 4 26 0.6299212574958801;452 E6 11 26 0.6299212574958801;464 E6 4 26 0.6299212574958801;468 A6 8 26 0.6299212574958801;476 G6 8 26 0.6299212574958801;484 E6 4 26 0.6299212574958801;488 D6 2 26 0.6299212574958801;490 C6 2 26 0.6299212574958801;492 C6 8 26 0.6299212574958801;500 D6 4 26 0.6299212574958801;504 C6 2 26 0.6299212574958801;506 D6 2 26 0.6299212574958801;508 D6 4 26 0.6299212574958801;512 B5 4 26 0.6299212574958801;516 A5 11 26 0.6299212574958801;528 A5 2 26 0.6299212574958801;530 B5 2 26 0.6299212574958801;532 C6 6 26 0.6299212574958801;538 B5 2 26 0.6299212574958801;540 C6 4 26 0.6299212574958801;544 E6 4 26 0.6299212574958801;548 B5 8 26 0.6299212574958801;560 E5 4 26 0.6299212574958801;564 A5 6 26 0.6299212574958801;570 G5 2 26 0.6299212574958801;572 A5 4 26 0.6299212574958801;576 C6 4 26 0.6299212574958801;580 G5 8 26 0.6299212574958801;592 F5 2 26 0.6299212574958801;594 E5 2 26 0.6299212574958801;596 F5 6 26 0.6299212574958801;602 E5 2 26 0.6299212574958801;604 F5 4 26 0.6299212574958801;608 C6 4 26 0.6299212574958801;612 E5 8 26 0.6299212574958801;622 C6 2 26 0.6299212574958801;624 C6 2 26 0.6299212574958801;626 C6 2 26 0.6299212574958801;628 B5 6 26 0.6299212574958801;634 F#5 2 26 0.6299212574958801;636 F#5 4 26 0.6299212574958801;640 B5 4 26 0.6299212574958801;644 B5 8 26 0.6299212574958801;656 A5 2 26 0.6299212574958801;658 B5 2 26 0.6299212574958801;660 C6 6 26 0.6299212574958801;666 B5 2 26 0.6299212574958801;668 C6 4 26 0.6299212574958801;672 E6 4 26 0.6299212574958801;676 B5 8 26 0.6299212574958801;688 E5 2 26 0.6299212574958801;690 E5 2 26 0.6299212574958801;692 A5 6 26 0.6299212574958801;698 G5 2 26 0.6299212574958801;700 A5 4 26 0.6299212574958801;704 C6 4 26 0.6299212574958801;708 G5 8 26 0.6299212574958801;720 E5 4 26 0.6299212574958801;724 F5 4 26 0.6299212574958801;728 C6 2 26 0.6299212574958801;730 B5 2 26 0.6299212574958801;732 B5 4 26 0.6299212574958801;736 C6 4 26 0.6299212574958801;740 D6 4 26 0.6299212574958801;744 E6 2 26 0.6299212574958801;746 C6 2 26 0.6299212574958801;748 C6 8 26 0.6299212574958801;756 C6 2 26 0.6299212574958801;758 B5 2 26 0.6299212574958801;760 A5 4 26 0.6299212574958801;764 B5 4 26 0.6299212574958801;768 G#5 4 26 0.6299212574958801;772 A5 11 26 0.6299212574958801'
song0 = '0 E5 2 14;4 B4 2 14;6 C5 2 14;8 D5 2 14;10 E5 1 14;11 D5 1 14;12 C5 2 14;14 B4 2 14;16 A4 2 14;20 A4 2 14;22 C5 2 14;24 E5 2 14;28 D5 2 14;30 C5 2 14;32 B4 2 14;38 C5 2 14;40 D5 2 14;44 E5 2 14;48 C5 2 14;52 A4 2 14;56 A4 2 14;64 D5 2 14;68 F5 2 14;70 A5 2 14;74 G5 2 14;76 F5 2 14;78 E5 2 14;84 C5 2 14;86 E5 2 14;90 D5 2 14;92 C5 2 14;94 B4 2 14;98 B4 2 14;100 C5 2 14;102 D5 2 14;106 E5 2 14;110 C5 2 14;114 A4 2 14;118 A4 2 14;248 E5 8 14;256 C5 8 14;264 D5 8 14;272 B4 8 14;280 C5 8 14;288 A4 8 14;296 G#4 8 14;304 B4 8 14;313 E5 8 14;321 C5 8 14;329 D5 8 14;337 B4 4 14;341 B4 4 14;345 C5 4 14;349 E5 4 14;353 A5 8 14;361 G#5 8 14;248 C5 8 14;256 A4 8 14;264 B4 8 14;272 G#4 8 14;280 A4 8 14;288 E4 8 14;0 B4 2 14;4 G#4 2 14;6 A4 2 14;8 B4 2 14;12 A4 2 14;14 G#4 2 14;28 B4 2 14;30 A4 2 14;32 G#4 4 14;40 B4 2 14;38 A4 2 14;44 C5 2 14;52 E4 2 14;64 F4 2 14;94 G#4 2 14;100 A4 2 14;102 B4 2 14;106 C5 2 14;110 A4 2 14;114 E4 2 14;114 E4 2 14;118 E4 2 14;98 G#4 2 14;296 E4 8 14;304 G#4 8 14;313 C5 8 14;321 A4 8 14;329 B4 8 14;337 G#4 4 14;341 G#4 4 14;345 A4 4 14;349 C5 4 14;353 E5 8 14;353 E5 8 14;361 E5 8 14;48 A4 2 14;56 E4 2 14;68 D5 2 14;70 F5 2 14;74 E5 2 14;76 D5 2 14;80 E5 2 14;78 C5 2 14;80 C5 2 14;22 A4 2 14;24 C5 2 14;84 A4 2 14;86 C5 2 14;92 A4 2 14;90 B4 2 14;0 E5 2 14;4 B4 2 14;6 C5 2 14;8 D5 2 14;10 E5 1 14;11 D5 1 14;12 C5 2 14;14 B4 2 14;16 A4 2 14;20 A4 2 14;22 C5 2 14;24 E5 2 14;28 D5 2 14;30 C5 2 14;32 B4 2 14;38 C5 2 14;40 D5 2 14;44 E5 2 14;48 C5 2 14;52 A4 2 14;56 A4 2 14;64 D5 2 14;68 F5 2 14;70 A5 2 14;74 G5 2 14;76 F5 2 14;78 E5 2 14;84 C5 2 14;86 E5 2 14;90 D5 2 14;92 C5 2 14;94 B4 2 14;98 B4 2 14;100 C5 2 14;102 D5 2 14;106 E5 2 14;110 C5 2 14;114 A4 2 14;118 A4 2 14;0 B4 2 14;4 G#4 2 14;6 A4 2 14;8 B4 2 14;12 A4 2 14;14 G#4 2 14;28 B4 2 14;30 A4 2 14;32 G#4 4 14;40 B4 2 14;38 A4 2 14;44 C5 2 14;52 E4 2 14;64 F4 2 14;94 G#4 2 14;100 A4 2 14;102 B4 2 14;106 C5 2 14;110 A4 2 14;114 E4 2 14;114 E4 2 14;118 E4 2 14;98 G#4 2 14;48 A4 2 14;56 E4 2 14;68 D5 2 14;70 F5 2 14;74 E5 2 14;76 D5 2 14;80 E5 2 14;78 C5 2 14;80 C5 2 14;22 A4 2 14;24 C5 2 14;84 A4 2 14;86 C5 2 14;92 A4 2 14;90 B4 2 14;124 E5 2 14;128 B4 2 14;130 C5 2 14;132 D5 2 14;134 E5 1 14;135 D5 1 14;136 C5 2 14;138 B4 2 14;140 A4 2 14;144 A4 2 14;146 C5 2 14;148 E5 2 14;152 D5 2 14;154 C5 2 14;156 B4 2 14;162 C5 2 14;164 D5 2 14;168 E5 2 14;172 C5 2 14;176 A4 2 14;180 A4 2 14;188 D5 2 14;192 F5 2 14;194 A5 2 14;198 G5 2 14;200 F5 2 14;202 E5 2 14;208 C5 2 14;210 E5 2 14;214 D5 2 14;216 C5 2 14;218 B4 2 14;222 B4 2 14;224 C5 2 14;226 D5 2 14;230 E5 2 14;234 C5 2 14;238 A4 2 14;242 A4 2 14;124 B4 2 14;128 G#4 2 14;130 A4 2 14;132 B4 2 14;136 A4 2 14;138 G#4 2 14;152 B4 2 14;154 A4 2 14;156 G#4 4 14;164 B4 2 14;162 A4 2 14;168 C5 2 14;176 E4 2 14;188 F4 2 14;218 G#4 2 14;224 A4 2 14;226 B4 2 14;230 C5 2 14;234 A4 2 14;238 E4 2 14;238 E4 2 14;242 E4 2 14;222 G#4 2 14;172 A4 2 14;180 E4 2 14;192 D5 2 14;194 F5 2 14;198 E5 2 14;200 D5 2 14;204 E5 2 14;202 C5 2 14;204 C5 2 14;146 A4 2 14;148 C5 2 14;208 A4 2 14;210 C5 2 14;216 A4 2 14;214 B4 2 14;124 E5 2 14;128 B4 2 14;130 C5 2 14;132 D5 2 14;134 E5 1 14;135 D5 1 14;136 C5 2 14;138 B4 2 14;140 A4 2 14;144 A4 2 14;146 C5 2 14;148 E5 2 14;152 D5 2 14;154 C5 2 14;156 B4 2 14;162 C5 2 14;164 D5 2 14;168 E5 2 14;172 C5 2 14;176 A4 2 14;180 A4 2 14;188 D5 2 14;192 F5 2 14;194 A5 2 14;198 G5 2 14;200 F5 2 14;202 E5 2 14;208 C5 2 14;210 E5 2 14;214 D5 2 14;216 C5 2 14;218 B4 2 14;222 B4 2 14;224 C5 2 14;226 D5 2 14;230 E5 2 14;234 C5 2 14;238 A4 2 14;242 A4 2 14;124 B4 2 14;128 G#4 2 14;130 A4 2 14;132 B4 2 14;136 A4 2 14;138 G#4 2 14;152 B4 2 14;154 A4 2 14;156 G#4 4 14;164 B4 2 14;162 A4 2 14;168 C5 2 14;176 E4 2 14;188 F4 2 14;218 G#4 2 14;224 A4 2 14;226 B4 2 14;230 C5 2 14;234 A4 2 14;238 E4 2 14;238 E4 2 14;242 E4 2 14;222 G#4 2 14;172 A4 2 14;180 E4 2 14;192 D5 2 14;194 F5 2 14;198 E5 2 14;200 D5 2 14;204 E5 2 14;202 C5 2 14;204 C5 2 14;146 A4 2 14;148 C5 2 14;208 A4 2 14;210 C5 2 14;216 A4 2 14;214 B4 2 14;248 C5 2 13;252 C5 2 13;254 E5 2 13;250 E5 2 13;256 C5 2 13;260 C5 2 13;258 A4 2 13;262 A4 2 13;264 D5 2 13;268 D5 2 13;266 B4 2 13;270 B4 2 13;272 G#4 2 13;276 G#4 2 13;274 B4 2 13;278 B4 2 13;280 A4 2 13;284 A4 2 13;282 C5 2 13;286 C5 2 13;347 C5 2 13;351 C5 2 13;313 C5 2 13;317 C5 2 13;319 E5 2 13;315 E5 2 13;321 C5 2 13;325 C5 2 13;323 A4 2 13;327 A4 2 13;329 D5 2 13;333 D5 2 13;331 B4 2 13;335 B4 2 13;337 G#4 2 13;341 G#4 2 13;339 B4 2 13;343 B4 2 13;345 A4 2 13;349 A4 2 13;349 E5 2 13;353 E5 2 13;355 A5 2 13;359 A5 2 13;357 E5 2 13;361 E5 2 13;363 G#5 2 13;367 G#5 2 13;365 E5 2 14;292 A4 2 13;288 A4 2 13;294 E4 2 13;290 E4 2 13;300 G#4 2 13;296 G#4 2 13;302 E4 2 13;298 E4 2 13;308 G#4 2 13;304 G#4 2 13;310 B4 2 13;306 B4 2 13'
self.bgm = PlayMusic(song0, tempo=1, duty=500, pins=[
Pin(game_kit.buzzer, Pin.OUT)])
2、播放音乐类(class PlayMusic)
用于音乐播放的类。引用了buzzer_music模块。
以music为父类创建了PlayMusic类。
该类包含以下几个函数。
(1)__init__:初始化
def __init__(self, songString='0 D4 8 0', looping=True, tempo=3, duty=2512, pin=None, pins=[Pin(0)]):
super().__init__(songString, looping, tempo, duty, pin, pins)
self.pause = True
(2)toggle_pause:反转当前播放状态
def toggle_pause(self):
self.pause = not self.pause
if self.pause:
print("mute\n")
self.mute()
else:
print("unmute\n")
self.unmute()
(3)mute:静音状态设置
def mute(self):
for pwm in self.pwms:
pwm.duty_u16(0)
(4)unmute:结束静音状态设置(即正常播放)
def unmute(self):
for pwm in self.pwms:
pwm.duty_u16(self.duty)
(5)tick:每一时刻播放
def tick(self):
if self.pause:
pass
else:
super().tick()
3、棋子类(class Chessman)
定义了棋子的名字、权值、颜色信息。
class Chessman():
def __init__(self, name,val, color):
self.name = name
self.val = val
self.color = color
blackchessman=Chessman('Black', 1, st7789.color565(45, 45, 45))
whitechessman=Chessman('White', 2, st7789.color565(219, 219, 219))
4、棋盘类(class Checkerboard)
包含的变量有line_points、all_chessman[][]。
- line_points:一个常数,表示棋盘的边长。
- all_chessman[][]:一个二维数组,其中all_chessman[i][j]表示(j,i)这个位置所落棋子的颜色。(黑色为1,白色为2,没有棋子为0)
该类包含以下几个函数。
(1)__init__:初始化
def __init__(self, line_points):
self.line_points = line_points
self.all_chessman = [[0] * line_points for _ in range(line_points)]
(2)can_drop:判断是否可落子
# 判断是否可落子
def can_drop(self, point):
return self.all_chessman[int(point[1])][int(point[0])] == 0
(3)drop:落子,若该子落下之后即可获胜,则返回获胜方,否则返回 None
def drop(self, chessman, point):
"""
落子
:param chessman:
:param point:落子位置
:return:若该子落下之后即可获胜,则返回获胜方,否则返回 None
"""
print(str(chessman.name)+":("+str(int(point[0]))+ ","+str(int(point[1]))+")")
self.all_chessman[int(point[1])][int(point[0])] = chessman.val
if self.win(point):
print(str(chessman.name)+" win!")
return chessman
(4)win:判断是否赢了
# 判断是否赢了
def win(self, point):
cur_value = self.all_chessman[int(point[1])][int(point[0])]
for os in offset:
if self.get_count_on_direction(point, cur_value, os[0], os[1]):
return True
其中,offset的定义如下所示:
offset = [(1, 0), (0, 1), (1, 1), (1, -1)]
(5)get_count_on_direction:判断从某一点开始往某一方向延展的同色棋子是否连成五颗。
参数:
- point:选定起始的棋子位置。
- value:选定颜色
- x_offset:延伸的下一棋子的x变化量
- y_offset:延伸的下一棋子的y变化量
def get_count_on_direction(self, point, value, x_offset, y_offset):
count = 1
for step in range(1, 5):
x = int(point[0]) + step * x_offset
y = int(point[1]) + step * y_offset
if 0 <= x < self.line_points and 0 <= y < self.line_points and self.all_chessman[y][x] == value:
count += 1
else:
break
for step in range(1, 5):
x = int(point[0]) - step * x_offset
y = int(point[1]) - step * y_offset
if 0 <= x < self.line_points and 0 <= y < self.line_points and self.all_chessman[y][x] == value:
count += 1
else:
break
return count >= 5
5、光标类(class Cursor)
包含的变量为pos,pos是一个二维向量,表示光标所在的位置坐标。
要注意该坐标为屏幕像素的坐标,因而横纵坐标范围为0-239。
包含了以下函数。
(1)__init__:初始化,光标初始位置为屏幕正中心
def __init__(self):
self.pos = [120,120]
(2)get_clickpoint:根据光标所在位置,返回游戏区坐标,所谓的游戏区坐标为棋盘坐标,因而横纵坐标范围为0-18
# 根据光标所在位置,返回游戏区坐标
def get_clickpoint(self):
pos_x = self.pos[0] - Start_X
pos_y = self.pos[1] - Start_Y
if pos_x < -Inside_Width or pos_y < -Inside_Width:
return None
x = pos_x // SIZE
y = pos_y // SIZE
if pos_x % SIZE > Stone_Radius:
x += 1
if pos_y % SIZE > Stone_Radius:
y += 1
if x >= Line_Points or y >= Line_Points:
return None
return x,y
6、游戏运行类(class GameRunning)
游戏运行最主要的类,包含了以下一些变量。
- draw:class Draw,用于绘制游戏界面。
- cur_runner:class Chessman,表示当前落子的一方。
- winner:class Chessman,表示胜利的一方,若没有决出胜负则为None
- computer:class AI,用于调用电脑方的策略
包含以下一些函数。
(1)__init__:初始化
def __init__(self):
self.draw=None
self.cur_runner=None
self.winner=None
self.computer=None
(2)game_init:游戏参数初始化,用于开始游戏或者重启游戏
def game_init(self,screen):
self.draw = Draw(screen,Checkerboard(Line_Points),Cursor())
self.cur_runner = blackchessman
self.winner = None
self.computer = AI(Line_Points, whitechessman)
# 画棋盘
self.draw.draw_checkerboard()
self.draw.draw_cursor(BLACK_COLOR)
(3)get_next:返回下一步的落子方
def get_next(self):
if self.cur_runner == blackchessman:
return whitechessman
else:
return blackchessman
(4)game_move:光标的移动和绘制
def game_move(self,xValue,yValue):
self.draw.draw_cursor(Checkerboard_Color)
v=0.0001
if xValue<15000 or xValue>45000:
self.draw.cursor.pos[0]=self.draw.cursor.pos[0]+(xValue-30000)*v
elif yValue<15000 or yValue>45000:
self.draw.cursor.pos[1]=self.draw.cursor.pos[1]+(yValue-30000)*v
if self.draw.cursor.pos[0]<0:
self.draw.cursor.pos[0]=0
if self.draw.cursor.pos[0]>239:
self.draw.cursor.pos[0]=239
if self.draw.cursor.pos[1]<0:
self.draw.cursor.pos[1]=0
if self.draw.cursor.pos[1]>239:
self.draw.cursor.pos[1]=239
#print(self.draw.cursor.pos)
self.draw.draw_cursor(BLACK_COLOR)
(5)game_drop:在光标处落下一颗黑子,并且电脑根据你所下的黑子,自主下一颗白子
def game_drop(self):
if self.winner is None:
click_point = self.draw.cursor.get_clickpoint()
if click_point is not None:
if self.draw.checkerboard.can_drop(click_point):
self.winner = self.draw.checkerboard.drop(self.cur_runner, click_point)
if self.winner is None:
self.cur_runner = self.get_next()
self.computer.get_opponent_drop(click_point)
AI_point = self.computer.AI_drop()
self.winner = self.draw.checkerboard.drop(self.cur_runner, AI_point)
self.cur_runner = self.get_next()
else:
print('超出棋盘区域')
# 画棋盘上已有的棋子
self.draw.draw_all_chessman()
7、绘图类(class Draw)
主要用于绘制整个游戏界面。
包含以下一些变量。
- screen:class ST7789,传入ST7789类,用于调用st7789模块中的函数。
- checkerboard:class Checkerboard,传入棋盘类,用于调用棋盘类中的成员变量和成员函数。
- cursor:class Cursor,传入光标类,用于调用光标类中的成员变量和成员函数。
包含以下一些函数。
(1)__init__:初始化
def __init__(self,screen,checkerboard, cursor):
self.screen = screen
self.checkerboard = checkerboard
self.cursor = cursor
(2)fill_circle:绘制一个实心圆
参数:
- x:圆心x坐标(0-239)
- y:圆心y坐标(0-239)
- radius:圆的半径
- color:绘制的颜色(st7789的565编码)
def fill_circle(self,x,y,radius,color):
for i in range(x-radius-1,x+radius+1):
for j in range(y-radius-1,y+radius+1):
if (x-i)*(x-i)+(y-j)*(y-j) <= radius*radius:
self.screen.pixel(i,j,color)
(3)draw_checkerboard:绘制棋盘
# 画棋盘
def draw_checkerboard(self):
# 填充棋盘背景色
self.screen.fill(Checkerboard_Color)
# 画棋盘网格线外的边框
self.screen.fill_rect(Outer_Width, Outer_Width, Border_Length, Border_Length ,BLACK_COLOR)
self.screen.fill_rect(Outer_Width+Border_Width,
Outer_Width+Border_Width,
Border_Length-2*Border_Width,
Border_Length-2*Border_Width,Checkerboard_Color)
# 画网格线
for i in range(Line_Points):
self.screen.line(Start_Y, Start_Y + SIZE * i,
Start_Y + SIZE * (Line_Points - 1), Start_Y + SIZE * i,
BLACK_COLOR)
for j in range(Line_Points):
self.screen.line(Start_X + SIZE * j, Start_X,
Start_X + SIZE * j, Start_X + SIZE * (Line_Points - 1),
BLACK_COLOR)
# 画星位和天元
for i in (3, 9, 15):
for j in (3, 9, 15):
if i == j == 9:
radius = 4
else:
radius = 3
self.fill_circle(Start_X + SIZE * i, Start_Y + SIZE * j, radius, BLACK_COLOR)
(4)draw_chessman:绘制一颗棋子
# 画棋子
def draw_chessman(self, point, stone_color):
self.fill_circle(Start_X + SIZE * point[0], Start_Y + SIZE * point[1], Stone_Radius, stone_color)
(5)draw_all_chessman:绘制所有棋盘上已经有的棋子
# 画棋盘上已有的棋子
def draw_all_chessman(self):
for i, row in enumerate(self.checkerboard.all_chessman):
for j, cell in enumerate(row):
if cell == blackchessman.val:
self.draw_chessman( (j, i), blackchessman.color)
elif cell == whitechessman.val:
self.draw_chessman( (j, i), whitechessman.color)
(6)draw_cursor:绘制光标
def draw_cursor(self, color):
x = self.cursor.pos[0]
y = self.cursor.pos[1]
pos = self.cursor.get_clickpoint()
if pos is not None:
pos_x, pos_y = pos
x0 = Start_X + SIZE * pos_x
y0 = Start_Y + SIZE * pos_y
self.screen.line(int(x0 - SIZE //2) ,int( y0 - SIZE //2),
int(x0 + SIZE //2), int(y0 + SIZE //2),
color)
self.screen.line(int(x0 - SIZE //2) , int(y0 + SIZE //2),
int(x0 + SIZE //2), int(y0 - SIZE //2),
color)
8、显示文本类(class TextResult)
用于最终决出胜利者后,在屏幕中打印胜利信息。
class TextResult():
def __init__(self,screen):
self.screen = screen
def text_int_xy(self,x, y, content, color):
self.screen.text(font, content, x*8, y*8, color, Checkerboard_Color)
9、对手类(class AI)
该类实现了让程序根据当前战局,选择一个合适的位置落子,和玩家进行五子棋对抗的目的。
包含了以下一些变量:
- line_points:int,表示棋盘边长大小
- my:class Chessman,表示程序所使用的棋子
- opponent:class Chessman,表示程序的对手所使用的棋子,即玩家所用棋子
- all_chessman[][]:一个二维数组,其中all_chessman[i][j]表示(j,i)这个位置所落棋子的颜色。(黑色为1,白色为2,没有棋子为0)
包含了以下一些函数。
(1)__init__:初始化
def __init__(self, line_points, chessman):
self.line_points = line_points
self.my = chessman
self.opponent = blackchessman if chessman == whitechessman else whitechessman
self.all_chessman = [[0] * line_points for _ in range(line_points)]
(2)get_opponent_drop:在自己的all_chessman,标记上对手所落的子,以便后来分析战局
def get_opponent_drop(self, point):
self.all_chessman[int(point[1])][int(point[0])] = self.opponent.val
(3)AI_drop:根据战局选择一个合适的位置落子
def AI_drop(self):
point = None
score = 0
for i in range(self.line_points):
for j in range(self.line_points):
if self.all_chessman[j][i] == 0:
_score = self.get_point_score((i, j))
if _score > score:
score = _score
point = (i, j)
elif _score == score and _score > 0:
r = random.randint(0, 100)
if r % 2 == 0:
point = (i, j)
self.all_chessman[int(point[1])][int(point[0])] = self.my.val
return point
(4)get_point_score:得分函数,返回落子在某一坐标的收益
def get_point_score(self, point):
score = 0
for os in offset:
score += self.get_direction_score(point, os[0], os[1])
(5)get_direction_score:部分得分函数,返回落子在某一个坐标,对于某一方向上延伸的棋子的收益
def get_direction_score(self, point, x_offset, y_offset):
count = 0 # 落子处我方连续子数
_count = 0 # 落子处对方连续子数
space = None # 我方连续子中有无空格
_space = None # 对方连续子中有无空格
both = 0 # 我方连续子两端有无阻挡
_both = 0 # 对方连续子两端有无阻挡
# 如果是 1 表示是边上是我方子,2 表示敌方子
flag = self.get_stone_color(point, x_offset, y_offset, True)
if flag != 0:
for step in range(1, 6):
x = int(point[0]) + step * x_offset
y = int(point[1]) + step * y_offset
if 0 <= x < self.line_points and 0 <= y < self.line_points:
if flag == 1:
if self.all_chessman[y][x] == self.my.val:
count += 1
if space is False:
space = True
elif self.all_chessman[y][x] == self.opponent.val:
_both += 1
break
else:
if space is None:
space = False
else:
break # 遇到第二个空格退出
elif flag == 2:
if self.all_chessman[y][x] == self.my.val:
_both += 1
break
elif self.all_chessman[y][x] == self.opponent.val:
_count += 1
if _space is False:
_space = True
else:
if _space is None:
_space = False
else:
break
else:
# 遇到边也就是阻挡
if flag == 1:
both += 1
elif flag == 2:
_both += 1
if space is False:
space = None
if _space is False:
_space = None
_flag = self.get_stone_color(point, -x_offset, -y_offset, True)
if _flag != 0:
for step in range(1, 6):
x = int(point[0]) - step * x_offset
y = int(point[1]) - step * y_offset
if 0 <= x < self.line_points and 0 <= y < self.line_points:
if _flag == 1:
if self.all_chessman[y][x] == self.my.val:
count += 1
if space is False:
space = True
elif self.all_chessman[y][x] == self.opponent.val:
_both += 1
break
else:
if space is None:
space = False
else:
break # 遇到第二个空格退出
elif _flag == 2:
if self.all_chessman[y][x] == self.my.val:
_both += 1
break
elif self.all_chessman[y][x] == self.opponent.val:
_count += 1
if _space is False:
_space = True
else:
if _space is None:
_space = False
else:
break
else:
# 遇到边也就是阻挡
if _flag == 1:
both += 1
elif _flag == 2:
_both += 1
score = 0
if count == 4:
score = 10000
elif _count == 4:
score = 9000
elif count == 3:
if both == 0:
score = 1000
elif both == 1:
score = 100
else:
score = 0
elif _count == 3:
if _both == 0:
score = 900
elif _both == 1:
score = 90
else:
score = 0
elif count == 2:
if both == 0:
score = 100
elif both == 1:
score = 10
else:
score = 0
elif _count == 2:
if _both == 0:
score = 90
elif _both == 1:
score = 9
else:
score = 0
elif count == 1:
score = 10
elif _count == 1:
score = 9
else:
score = 0
if space or _space:
score /= 2
return score
(6)get_stone_color:判断指定位置处在指定方向上棋子的颜色, 若为我方棋子则返回1,若为对方棋子则返回2,否则返回0
# 判断指定位置处在指定方向上是我方子、对方子、空
def get_stone_color(self, point, x_offset, y_offset, next):
x = int(point[0]) + x_offset
y = int(point[1]) + y_offset
if 0 <= x < self.line_points and 0 <= y < self.line_points:
if self.all_chessman[y][x] == self.my.val:
return 1
elif self.all_chessman[y][x] == self.opponent.val:
return 2
else:
if next:
return self.get_stone_color((x, y), x_offset, y_offset, False)
else:
return 0
else:
return 0
10、主函数(def main)
实现了对于各个类的初始化,并实现了游戏主循环,具体的流程可以看设计框图。
def main():
hardware=Hardware()
gamerunning=GameRunning()
textresult=TextResult(hardware.screen)
gamerunning.game_init(hardware.screen)
while True:
xValue = hardware.yAxis.read_u16()
yValue = hardware.xAxis.read_u16()
if hardware.buttonA.value() == 0:
hardware.bgm.toggle_pause()
hardware.bgm.tick()
if hardware.buttonSTART.value() == 0:
gamerunning.game_init(hardware.screen)
elif gamerunning.winner:
textresult.text_int_xy((SCREEN_WIDTH - 64)//32, (SCREEN_HEIGHT - 8)//16,
gamerunning.winner.name + ' win!', RED_COLOR)
else:
if hardware.buttonB.value() == 0:
gamerunning.game_drop()
gamerunning.game_move(xValue,yValue)
time.sleep(0.1)
六、项目总结
通过参加“硬禾学堂2022寒假在家练”活动,我体会到了一个完整的项目是如何从一个点子,一步一步慢慢实现的。在实现这个复古游戏移植的过程中,我锻炼了自己的代码能力,也进一步掌握了嵌入式开发的一些技巧。