PyScriptでぷよぷよ

PyScriptが面白そうって話を聞いたので PyScriptでぷよぷよ作ってみる。
内容は [Elm](https://elm-lang.org/)で作ったものと同じものを目指す。

https://github.com/m-masataka/elm-puyo

# PyScript
AnacondaがリリースしたWebブラウザ上でpythonが実行できるフレームワークらしい。

公式からスクリプト読み込むだけで実行できるのでめちゃめちゃお手軽
```html
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
```

※ CORSエラーが出る時があったのでローカルにbuildしておくのもいいかも。

# 使い方
大体は↓に書いてある。

https://github.com/pyscript/pyscript/blob/main/GETTING-STARTED.md

pythonのコードを ``<py-script>`` タグに入れるだけでいいらしい。
```html
<py-script>
  実行したいコード
</py-script>
```

ここではゲーム作成の肝になる``テンプレートタグ``と``イベントリスナー``、``イベントループ``だけ紹介。
※ ドキュメントがあまり無いので、[サンプル](https://github.com/pyscript/pyscript/tree/main/pyscriptjs/examples)みて作ることになりそう。

## テンプレートタグ
まずはpython側からhtmlの要素を取得する話。
id指定の場合は↓で要素を取得できる
```python
...
<divid="grid-container">
</div>
...
<py-script>
grid_container = Element("grid-container")
</py-script>
```

そして、要素の操作や追加は↓のようにjavascriptみたいに可能
```python
# 追加
grid_container.element.appendChild(puyo_html.element)

# 変更 （イメージの変更）
puyo_html = Element("puyo-blue")
puyo_html_img =  puyo_html.select("img")
puyo_html_img.element.src = "hoge.img"
```

そして[templateタグ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/template)を使う場合はcloneを利用する。
```python
...
    <template id="puyo-template">
      <div class="view-cell" style="position: static">
        <img src="" class="puyo">
      </div>
    </template>
...
<py-script>
# スタイルとイメージを変更し、要素を追加
puyo_html = puyo_template.clone('puyo-template', to=grid_container)
puyo_html.element.style = "position: absolute;"
puyo_html_img =  puyo_html.select("img")
puyo_html_img.element.src = c.img
grid_container.element.appendChild(puyo_html.element)
</py-script>
```

これでViewの更新、変更はなんとかなりそう。

## イベントリスナー
ゲーム作るならキーボード入力がないとってことでイベントリスナー。
この辺は[pyodide](https://github.com/pyodide/pyodide)を見ないとダメッぽい。

https://github.com/pyodide/pyodide/blob/main/docs/usage/type-conversions.md#proxying

結論から、↓でキーイベントを取得できる。
```python
from pyodide import create_proxy

async def key_down(event):
    # ...なにかやりたい処理
    pass

game_panel = document.querySelector("body")
game_panel.addEventListener("keydown", create_proxy(key_down))
```

ほぼjavascriptの書き方だが、addEventListenerで特定のイベントを取得し、create_proxyでpythonの関数にproxyする。
↑の場合は``event.key``とかでキーコードが取得できる。

## イベントループ
``asyncio``のイベントループが使えるみたい。

```python
async def tick():
  while True:
    try:
        # ...なにかの処理
        await asyncio.sleep(1)
        print(tock)
    except Exception as e:
        break

pyscript.run_until_complete(tick())
```
``run_until_complete``以外は試してない。使えるのだろうか...


# ぷよぷよを作る

あとは作るだけ。
処理内容は以前elmで作ったものと変わらず

https://qiita.com/m-masataka/items/de44b117997ea2ab8ff8

テンプレートでぷよを定義して、それをひたすら動かしていく。
空のボード・操作するぷよ・次のぷよ・ゲームのステータスを初期状態として定義し、イベントループ/イベントリスナーで盤面を更新する。
更新毎に``update_view()``で画面に盤面を反映。


```python
    def __init__(self):
        self.board = [[Puyo.Empty for i in range(COL)] for i in range(ROW)]
        self.gripped_puyo = [
            { 'position': {'row': 0, 'col': 1}, 'color': Puyo.random_choice()},
            { 'position': {'row': 1, 'col': 1}, 'color': Puyo.random_choice()}
        ]
        for p in self.gripped_puyo:
            self.board[p['position']['row']][p['position']['col']] = p['color']
        self.next_puyo = [
            [Puyo.random_choice(), Puyo.random_choice()],
            [Puyo.random_choice(), Puyo.random_choice()],
        ]
        self._status = Status.Normal
        self.init_view()

    def update_view(self):
        count = 0
        for r in self.board[1:]:
            for c in r:
                puyo_html = Element("puyo-" + str(count))
                puyo_html_img =  puyo_html.select("img")
                puyo_html_img.element.src = c.img
                count += 1

    def init_view(self):
        pos = [0,0]
        count = 0
        for r in self.board:
            if pos[0] == 0:
                pos[0] = pos[0] + 1
                continue
            pos[1] = 0
            for c in r:
                puyo_html = puyo_template.clone('puyo-'+str(count), to=grid_container)
                puyo_html.element.style = "position: absolute; top: " + str(25 * (pos[0]-1)) + "px; left: " + str(25 * (pos[1]+1)) + "px;"
                puyo_html_img =  puyo_html.select("img")
                puyo_html_img.element.src = c.img
                grid_container.element.appendChild(puyo_html.element)
                pos[1] = pos[1] + 1
                count += 1
            pos[0] = pos[0] + 1
        self.update_next_view()

    def update_next_view(self):
        count = 0
        for pair in self.next_puyo:
            for c in pair:
                puyo_html = Element('next-puyo-'+str(count))
                puyo_html_img =  puyo_html.select("img")
                puyo_html_img.element.src = c.img
                count += 1
```

出来上がり。

https://github.com/m-masataka/pyscript-puyo

![puyo-demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/520924/c907deba-febc-614a-b4fa-8874ab592085.gif)


pyscript-gamesみたいなプロジェクトもできるのかな