discord botであみだくじを作ってみたい

# 初めに
- WSL, Ubuntu22.04.1
- Python 3.10.6
- discord.py             2.1.0

という環境で行っています．

# 作る
## あみだくじ
まずは，あみだくじをしてくれるプログラムを作りましょう．以下をちょっと見て作ってみました．
- [CodeIQ本[Q57:あみだくじの問題]についてのメモ](https://kanatoko.wordpress.com/2017/12/24/codeiq%E6%9C%ACq57%E3%81%82%E3%81%BF%E3%81%A0%E3%81%8F%E3%81%98%E3%81%AE%E5%95%8F%E9%A1%8C%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E3%81%AE%E3%83%A1%E3%83%A2/)
- [Pythonプログラミング（ステップ８・関数・あみだくじ）](https://wagtail.cds.tohoku.ac.jp/coda/python/p-8-function-part1-amida.html)
- [pythonであみだくじもどきを作ってみた。](https://qiita.com/ryo_110/items/bb426fded5e9471bd58e)

とりあえず動作はしましたが，すごく効率悪そうなことしてるので畳んで表示します．．．ご自身で頑張ってください．．．
ちなみに空白での調整は，botに内容を送信させて地道にやってました．


<details><summary>あみだくじ</summary>

答えをまず生成し，それに対応したあみだくじを作ります．つまり，元の配列のある要素を順番に答えの位置に移動させます．今回は答えの後ろから順番に移動を確定させています．具体的には[1,2,3,4]から[4,2,3,1]という結果にしたい場合を考えてみると，
1. [1,2,3,4] -> [2,3,4,1]
1. [2,3,4,1] -> [2,4,3,1]
1. [2,4,3,1] -> [4,2,3,1]

という感じで要素を移動させて答えにします．この移動の過程をあみだくじの横線とすることで，あみだくじを作っています．また，単に移動したところに横線をいれると横線がすべてつながるため，横線が入る場合，一つの行では一つの横線しかないように改行してあります．

```python: amida.py
import random


def list_move(items, frm, to):
    for j in range(frm, to):
        tmp = items[j]
        items[j] = items[j + 1]
        items[j + 1] = tmp
    return items


def get_blank(n):
    text = ""
    for _ in range(n):
        text += "|    "
    return text


def get_amida(original, ans):
    items = [item for item in original]

    amida_text = ""
    for i in range(len(ans) - 1, -1, -1):
        text = " "
        index = items.index(ans[i])
        if i == index:
            continue

        items = list_move(items, index, i)

        text += get_blank(index)

        for j in range(index, i):
            text += "| - "
            text += f"{get_blank(len(ans)-j-1)}\n"
            text += f" {get_blank(j+1)}"

        text += get_blank(len(ans) - i)
        amida_text += f"{text}\n"

    return amida_text[:-1]


def get_amida_result(original: list[str]) -> list[str]:
    ans = [item for item in original]
    random.shuffle(ans)

    before = "   ".join(original)
    after = "   ".join(ans)
    result = f"{before}\n{get_amida(original, ans)}\n{after}"
    return result.split("\n")


if __name__ == "__main__":
    result = get_amida_result(original=["1", "2", "3", "4"])
    print(result)
```



</details>


## botにおけるあみだくじのコマンド

コマンドの引数として数値を受け取り，その分の要素であみだくじを作ります．ボタンを押されたらあみだくじの結果を出力するようにします．これは汎用性のためです．具体的には，スタンプを押した人数や数であみだくじの本数を変更させたりもできるなと思ったため，ボタンを押してからあみだくじをするようにしています．
また，先ほどつくったあみだくじを作ってくれる`amida.py`は，完成したあみだくじを返しています．そのため，そのまま取得すると味気ないなと感じました．そのため，1行ずつ出力するようにします．

そして，ボタンがあったメッセージを編集してあみだくじを表示するようにしています．これはボタンが複数回押されることを防ぐついでであることが一つ．そして，responseへの返信は一度しかできないため，インタラクションへの追加の反応は[followup](https://discordpy.readthedocs.io/ja/latest/interactions/api.html#discord.Interaction.followup)を使います．しかし，こちらの[edit_messageメソッド](https://discordpy.readthedocs.io/ja/latest/api.html#discord.Webhook.edit_message)では，新しく返信をしてしまうようで，既存のメッセージを編集をするわけではないようです．つまり，段階的に編集するとメッセージがたくさん送られることになります．これが理由でボタンのあったメッセージを編集しています．

あみだくじの中身は配列を受け取っているため[join](https://docs.python.org/ja/3/library/stdtypes.html?highlight=join#str.join)を使っているのですが，あみだくじ側のプログラムで`yield`を使ってもよいと思います．元のメッセージから内容を追記する形でループさせればいいと思います．


```python: bot.py
import os
from discord import Client, Intents, Interaction, ui
from discord.app_commands import CommandTree
from discord.ui import View, Button
from amida import get_amida_result
import asyncio


class AmidaView(View):
    def __init__(self, number: int):
        super().__init__()
        self.number = number

    @ui.button(label="begin")
    async def begin_amida(self, interaction: Interaction, button: Button):
        result = get_amida_result([f"{i+1}" for i in range(self.number)])
        self.clear_items()
        await interaction.response.edit_message(view=self)

        for i in range(1, len(result)):
            await asyncio.sleep(0.25)
            await interaction.edit_original_response(content="\n".join(result[: i + 1]))


class MyClient(Client):
    def __init__(self, *, intents: Intents):
        super().__init__(intents=intents)
        self.tree = CommandTree(self)

    async def setup_hook(self) -> None:
        commands = await self.tree.sync()
        print(commands)

    async def on_ready(self):
        print(f"Logged in as {client.user} (ID: {client.user.id})")
        print("------")


intents = Intents.default()
client = MyClient(intents=intents)


@client.tree.command()
async def amida(interaction: Interaction, number: int):
    view = AmidaView(number)
    await interaction.response.send_message(f"{number}'s amida kuji", view=view)


client.run(os.getenv("TOKEN"))
```

要素数5のあみだくじを作ってみると，

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953650/4e1a60b6-98fc-5645-89e6-46a706d9fb30.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953650/844bcaa2-4f65-9514-6c5f-474642639c42.png)

こんな感じになります．実際に1行ずつ表示されているのはご自身で確認いただければと思います．~~いい感じにgifにする方法がわからなかったので~~

9個でやってみるとこんな感じです．多くすればするほどどうやって実装しているのかがわかりやすいですね．．．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2953650/c33f792a-e006-1e0e-2c4f-0592849e8f14.png)

というわけでdiscord botであみだくじを作ってみたい記事でした．
お疲れ様でした．