基于树莓派RP2040实现复古游戏“五子棋”的移植项目
这是我参加“硬禾学堂2022寒假在家练”活动所完成的项目,项目使用的是树莓派RP2040开发板。 项目实现的总体功能是将复古游戏“五子棋”移植到树莓派RP2040开发板上。
标签
嵌入式系统
MicroPython
游戏移植
RP2040
2022寒假在家练
Yummy
更新2022-03-02
中央民族大学
798

一、项目介绍

这是我参加“硬禾学堂2022寒假在家练”活动所完成的项目,项目使用的是树莓派RP2040开发板。

项目实现的总体功能是将复古游戏“五子棋”移植到树莓派RP2040开发板上,具体的需求如下所示:

  • 能够在按下按钮A后开始播放背景音乐,并且再次按下停止播放。
  • 能够实现用摇杆控制光标的上下左右移动。
  • 能够在按下按钮B后在光标处下一颗黑色的棋子。
  • 能够根据你所下的黑棋,由程序自主下一颗白棋。
  • 能够在其中一方棋子连成五个以后结束游戏,并打出胜利提示。
  • 能够在按下按钮start后重新开始游戏。

二、设计框图

FsXIi6v3XoPNngB4IsQL8OUlDp5I

三、简单的软硬件介绍

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、游戏初始化测试

FlSDZsKKIlFLNPIvukOabGy7H2m0

2、光标移动测试

拨动摇杆,得以控制光标的上下左右移动。

FuYwzyWac8A95iZJ-oAHV62maXGo

3、音乐播放测试

按下按钮A以后,开始播放背景音乐,再次按下按钮A以后,音乐暂停。

4、落子测试

按下按钮B后在光标处下一颗黑色的棋子,并且立马根据你所下的黑棋,由程序自主下一颗白棋。

FnL_3x2l41YQtWh2Nbp2b0NyxdRs

5、游戏结束测试

在其中一方棋子连成五个以后结束游戏,并打出胜利提示。

FuTVitOqIS886xIw9b9vpYMHU_NC

6、游戏重启测试

按下按键start后,游戏重启。

FvUW_x-k5ZGM892tZLz5WI5G25fG

五、主要代码片段及说明

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寒假在家练”活动,我体会到了一个完整的项目是如何从一个点子,一步一步慢慢实现的。在实现这个复古游戏移植的过程中,我锻炼了自己的代码能力,也进一步掌握了嵌入式开发的一些技巧。

附件下载
code.rar
源码
团队介绍
杨明杰-中央民族大学
团队成员
Yummy
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号