Wio Terminal の CircuitPython に Stage ライブラリを追加する

# はじめに

Adafruit の PyBadge に関する[この記事](https://learn.adafruit.com/circuitpython-stage-game-library)を読んでいて、Wio Terminalでも動かしてみたいなと思ったところ、現状の Wio Terminal の CircuitPython には ugame や stage といったモジュールが組み込まれていませんでした。

ということで、Wio Terminal の CircuitPython 用に [Stage ライブラリ](https://github.com/python-ugame/circuitpython-stage)を組み込んでみました。

# ビルド手順

※ CircuitPython のビルド環境構築については省略します。 [BUILDING.md](https://github.com/adafruit/circuitpython/blob/main/BUILDING.md)を参考に構築してください。

ソース中の Stage ライブラリのあるディレクトリに移動して、Wio Terminal 用のディレクトリを作成します。

```bash
$ cd circuitpython/frozen/circuitpython-stage/
$ mkdir seeeduino_wio_terminal
$ cd seeeduino_wio_terminal
```

作成した Wio Terminal 用のディレクトリにファイル `ugame.py` を以下の内容で作成します。

```python
import board
import digitalio
import analogio
import gamepad
import stage


K_X = 0x01
K_DOWN = 0x02
K_LEFT = 0x04
K_RIGHT = 0x08
K_UP = 0x10
K_O = 0x20
K_START = 0x40
K_SELECT = 0x80


display = board.DISPLAY
buttons = gamepad.GamePad(
    digitalio.DigitalInOut(board.SWITCH_PRESS),
    digitalio.DigitalInOut(board.SWITCH_DOWN),
    digitalio.DigitalInOut(board.SWITCH_LEFT),
    digitalio.DigitalInOut(board.SWITCH_RIGHT),
    digitalio.DigitalInOut(board.SWITCH_UP),
    digitalio.DigitalInOut(board.BUTTON_1),
    digitalio.DigitalInOut(board.BUTTON_2),
    digitalio.DigitalInOut(board.BUTTON_3)
)

class DummyAudio:
    def play(self, f, loop=False):
        pass

    def stop(self):
        pass

    def mute(self, mute):
        pass

audio = DummyAudio()
```

`stage.py` のシンボリックリンクを作成します。

```sh
$ ln -s ../stage.py .
```

※ ここまでの修正は GitHub の[ここ](https://github.com/MinoruInachi/circuitpython-stage/tree/wio_terminal)にも置いておきました。

CircuitPython の Wio Terminal 用ボード定義ディレクトリに移動します。

```sh
$ cd ../../../ports/atmel-samd/boards/seeeduino_wio_terminal/
```

`board.c` を以下の内容で修正します。

```diff
@@ -47,7 +47,7 @@
     0xc1, 0x01, 0x10, // Power control SAP[2:0];BT[3:0]
     0xc5, 0x02, 0x3e, 0x28, // VCM control
     0xc7, 0x01, 0x86, // VCM control2
-    0x36, 0x01, 0x38, // Memory Access Control
+    0x36, 0x01, 0xe8, // Memory Access Control
     0x37, 0x01, 0x00, // Vertical scroll zero
     0x3a, 0x01, 0x55, // COLMOD: Pixel Format Set
     0xb1, 0x02, 0x00, 0x18, // Frame Rate Control (In Normal Mode/Full Colors)
@@ -84,7 +84,7 @@
         240, // Height
         0, // column start
         0, // row start
-        180, // rotation
+        0, // rotation
         16, // Color depth
         false, // Grayscale
         false, // pixels in a byte share a row. Only valid for depths < 8
```

これ、displayio モジュールがソフトウェアで 180° 回転させて表示しているのを、元からハードウェア設定的に 180° 回転させる修正です。なぜ、わざわざこんなことするかというと、Stage ライブラリが displayio の画面回転のことなんて無視しているので、180° 回転して（上下さかさまになったように）表示されてしまうためです。

`mpconfigboard.mk` を以下の内容で修正して Stage ライブラリの組込みを有効化します。

```diff
@@ -12,3 +12,8 @@
 LONGINT_IMPL = MPZ
 
 CIRCUITPY_VECTORIO = 1
+
+CIRCUITPY_GAMEPAD = 1
+CIRCUITPY_STAGE = 1
+
+FROZEN_MPY_DIRS += $(TOP)/frozen/circuitpython-stage/seeeduino_wio_terminal
```

CircuitPython をビルドします。

```sh
$ cd ../../../..
$ make -C mpy-cross
$ cd ports/atmel-samd
$ make BOARD=seeeduino_wio_terminal
```

ビルドが終わると、CircuitPython ファームウェアが `build-seeeduino_wio_terminal/firmware.uf2` にできるので、これを Wio Terminal のブートローダでインストールします。

以上でモジュール stage と ugame が使えるようになっています。

# おわりに

[きっかけとなった記事](https://learn.adafruit.com/circuitpython-stage-game-library)のデモスクリプトを Wio Terminal の画面の大きさに合わせて修正したものを以下に載せておきます。

```python
import ugame
import stage

class Ball(stage.Sprite):
    def __init__(self, x, y):
        super().__init__(bank, 1, x, y)
        self.dx = 2
        self.dy = 1
 
    def update(self):
        super().update()
        self.set_frame(self.frame % 4 + 1)
        self.move(self.x + self.dx, self.y + self.dy)
        if not 0 < self.x < 303:
            self.dx = -self.dx
        if not 0 < self.y < 224:
            self.dy = -self.dy

bank = stage.Bank.from_bmp16("ball.bmp")
background = stage.Grid(bank, 20, 15)
text = stage.Text(12, 1)
text.move(110, 110)
text.text("Hello world!")
ball1 = Ball(128, 0)
ball2 = Ball(0, 152)
ball3 = Ball(222, 128)
game = stage.Stage(ugame.display, 24)
sprites = [ball1, ball2, ball3]
game.layers = [text, ball1, ball2, ball3, background]
game.render_block()

while True:
    for sprite in sprites:
        sprite.update()
    game.render_sprites(sprites)
    game.tick()
```

![スクリーンショット 2020-09-20 11.58.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106817/c1f3c5ed-67fb-5064-bff1-9516e7c15bd1.png)