ChatGPTを使ったゲームを面白くするための試行錯誤

この記事は「空想召喚AIバトル」の制作を振り返りつつ、ChatGPTを用いたゲーム制作に関する知見をまとめたものだ。
いま話題のChatGPTゲーム、まだ遊んだことのない人はぜひ遊んでみよう!

https://hothukurou.com/game/Sengoku/index.html

記事の構成としては「制作までの経緯」「実際に制作してぶち当たった面白さの壁を乗り越えるまで」「ChatGPTのAPIを用いたデータベース制作法」の3つからなっている。
特に最後のデータベース制作は実際のコードも載せているので、同じようなゲームを作りたい人の参考になれば幸いである。

ChatGPTの大開拓時代

2023年、ChatGPTのようなAIチャットボットの驚異的な性能が世間を震撼させ、これから巻き起こる新時代にある者は不安を抱き、またある者は胸をときめかせていた。

ChatGPTは既存の仕組みでは考えられなかった新しい形で作業の効率化を提供してくれる、いわゆるゲームチェンジャーというべき存在だ。そんな大層なものが月額数千円程度の費用で誰でも使うことができるのだからすごいことである。

こういう話があると「とりあえず使ってみるか」と思うのがエンジニアのサガである。そして、実際に業務の中で取り入れてみたり、全く新しいアプリケーションを試行錯誤する中で「それっぽい嘘つくから意外とポンコツやね」と問題点を発見していく。そして「プログラム書かせてもあんまり信用ならないから、時間かければ自分でも実装できる程度のアルゴリズムの実装を頼もう」やら「パッと思いつかない変数名の提案を頼もう」などと現実的な形に着地させていく。

今回制作したゲームはこういった試行錯誤の過渡期だからこそ輝くゲームである。ただ、こういった注目されている技術は関心が高くてウケがいいし、何よりも時代が注目しているコンテンツを作れる位置にいること自体が人生において貴重なタイミングである。作品公開活動を続けていてつくづく思うのだが、自分の作品が高く評価されるためには良いものを作るだけでは足りず、その作品がその時代で愛されている必要がある。「時代に愛される」というワードは商業クリエイターならば運で掴むものではなく、市場調査を入念に行った上で、既に形成された市場のパイをいただくマーケットイン思考でつかみ取るものだろうが、そんな思考で動けるほど自分は理論的ではない。

 全くもって理論的な思考ではないが、市場にはないけど自分が楽しいと思うものが市場にウケることが一番面白くて良いことだと思っている。で、今回偶然にもChatGPTを使ったゲームに自分は関心があった。またブラウザゲーム制作者なのでChatGPTサーバーとAPI通信する処理は簡単に実装可能である。

こういった時代と噛み合った創作タイミングはそうそうあるものではない。「今作るのが一番盛り上がるはず!」という一心でゲーム制作をはじめた。その時の試行錯誤の記録をここに残す。

実はこの記事も「今一番注目されるネタ」だからこそ一生懸命に書いている。なんとまあメタ構造的な記事であろうか。

前座はこれぐらいにして、この後はきちんと制作記録と試行錯誤の上で陥った課題、その解決策を書いていく。そして、後半にはChatGPTにデータベースを制作させた時のPythonコードを置いておくので実際に似たようなゲームを作ってみたい人はぜひ活用してほしい。

ChatGPTゲームの先駆者AIバトラーが面白かった

ChatGPTのAPIを用いたゲームの先駆者として有名なのは、だらさんの制作したAIバトラー(https://ai-battle.alphabrend.com/)だろう。

攻撃力・防御力などのパラメータを振り分けて、最後に能力を自由記入で設定すると対戦相手との勝敗を判定してくれるWebゲームである。判定リクエストをChatGPTに投げて、その結果を表示することで勝敗とバトル状況を教えてくれる。

これがめちゃくちゃ面白かった。

自分で能力を自由に考えてよいので「ブラックホールを相手の足元に発生させる」というめちゃくちゃ強い技を書いてもOK。変わり種では「相手の合計ポイントよりも+1ポイント高い能力を得た後に、全ての能力が発動しなくなる」といったロック系戦略も提案できて「自分の書いた最強の能力」を友達と遊ぶ創造的な楽しみがあった。実際自分も会社で同僚と遊びまくっていた。

こういった「高度な知識を前提とした判定を求める仕組み」はChatGPTだからこそ実現できることである。

元々このゲームは無制限で遊べていたのだが、公開初日に膨大なアクセスが集中して公開一時中止となってしまった。個人使用プランの最大リクエスト数を超える膨大なアクセスが来てしまったためである。

また、ChatGPT のAPIリクエストは従量課金制となっており、アクセスが集中すると費用がかさむことになる。Webコンテンツの主な収益源は広告収入であるが、どうしても費用の方がかさんでしまう。

現在は有料会員の制度を設けてAIバトラーは遊べるようになっている。無料でも月一回は遊べるので一度遊んでみるとよいだろう。

ともかく、このAIバトラーの盛り上がりを見ると「ChatGPTにゲームマスター(GM)を頼むと面白そうだ」という発想が生まれてくる。ゲームマスターというのはボードゲームなどでゲームの進行役を務める人物である。特にTRPGやマーダーミステリーなどの「参加者がキャラクターを演じで、架空の物語を冒険するゲーム」では、GMは状況の提示を行なったり、行動結果の判定を行ったりする。この役割をChatGPTに行わせようということである。


また、「ChatGPTに毎回リクエストを送っていると、バズった時にすぐ上限に達する」という問題が発生するので、何らかの対策が必要となる。そこで今回は「ユーザーに文章を入力させるのではなく、限られた組み合わせで対戦する仕組みを作り、ChatGPTとの通信結果を全てデータベース化する」ことを考える。データベース作成のためにChatGPTを使おうという考え方だ。これならば通信量もデータベース作成分の費用で済む。

と、方針が決まったので早速ゲーム制作に入った。

ChatGPTの性能調査

まずはChatGPTの性能調査からである。2023年4月時点ではChatGPT-3.5までしかAPI通信ができなかったので、ChatGPT-3.5に対してWebから質問を投げて意図通りの回答を得られるか試行錯誤を行った。
2023年4月段階ではChatGPT4の性能が凄まじく、これに比べるとChatGPT3.5は幼稚園児並と言われているのだが、APIが使えないのだからしょうがない。
前の項では「ChatGPTを使おうGMとして使う」という方針を立てていたが、これ以外にも「ChatGPTにブラフゲームの相手をしてもらう」ことも検討した。

 

ブラフゲームを検討してみる

ブラフゲームというのはシンプルなゲームで相手の裏をかく楽しさを主題にしたゲームで、限定じゃんけんとかEカードみたいなカイジが良く遊んでいるゲームである。
一般的にCPU対戦のブラフゲームはあまり面白くない。ブラフゲームは相手の人間性を読み取って裏を書いて出し抜く気持ちよさが面白いからだ。CPU相手だとどうしてもこの面白さは表現できない。
だが、ChatGPT相手であれば、もしかしたら人間性を感じて面白くなるかもしれない。
今回は「お互いに1~10のカードを手札に持ち、一枚ずつ公開して大きい方が勝ち」というゲームを覚えさせることにした。
ChatGPTには「どのカードを出すのか」という指示と「その時の感想」を書いてもらう。さて、うまくいくか・・・!

結論として意図通りの回答をしてくれなかったため、諦めた。
ゲームのルールを覚えこませて操作指示を行うのは高度すぎたようだ。また、ルールを学習するためのルール説明文も必要になるため、APIで送信したときの通信量も増えてしまう。
ということでブラフゲーム作成は諦め、当初の方針であるGMとしてのChatGPT実装に入ることにした。

モンスターと戦うRPGを検討

ここから具体的にAPIを用いてモンスターと戦うRPGを作ってみた。

武器を選んで、モンスターと戦うというシンプルなルール。その結果はChatGPTに判断してもらうという。例えばゴーレムが出現したときに、たいまつと木の盾を選んで戦うを選ぶと、「ゴーレムとたいまつと木の盾を持った冒険者、戦ったらどちらが勝ちますか?冒険者が勝つなら@YES,ゴーレムが勝つなら@NOと書いてその後に理由を書いてください」という質問をChatGPTに投げる。

ChatGPTはその解答をくれるので、その解答文字列に@YESが含まれていれば勝ち、なければ負けと判定するようにした。

こんな感じで、現れたモンスターに対して所持アイテムで対処するというゲームだ。TRPGっぽい雰囲気がある。テーブルゲームにCat&Chocolateというカードゲームがあるのだが、ルールはそれに近い。

プレイヤーに文章を入力させず、決められたアイテムを使用することで「データベースによる勝敗判定」ができるようになる。

結果はこんな感じで文章で表示される。

以下の図は負けた時の解説だ。一応それっぽい解説が書かれている。

このゲームも一通り遊べるようにしていたのだが、結論からいえば面白くなかったので公開を諦めた。ChatGPTによる判断がランダムのように思えてしまい、運ゲーと感じてしまったことが原因だ。このゲームは@NOと答えられた時点でゲームオーバーとなるので、運ゲーとの相性がとてつもなく悪かった。

また大問題として、解説が思った以上に面白くならなかったことが一番の敗因である。ChatGPTは真面目に勝敗を考えてくれるのだが、どうも真面目過ぎて飽きてしまう文章だった。

ChatGPTにはユーモアがない。

これは実際にゲームを制作して嫌という程痛感したことである。何度でもいうが、ChatGPTの文章解説は真面目すぎてユーモアがなく、面白くならないのだ。

よく考えてみると、人の話を聞いて面白いと感じるためには、その相手に好意を持っている必要がある。人間的に魅力のない人の話を聞いてもそこまで面白くない。朝の朝礼で校長先生の話を聞くのは苦痛だが、好きな配信者やVtuberのトークならばいくらでも聞けるのと同じ理屈である。

人の話にはその人の視点がふんだんに含まれており、話を聞くことでその人の人間性をより理解できることになる。好意を持つ人の人間性をより理解できる点も面白さに一役買っているのかもしれない。人間は集団生活によって生き残っていたので、相互理解による快感は原始的な本能である。ChatGPTはこの相互理解から生まれる魅力が圧倒的にないため、面白く感じないのだ。実存する人間ではないから。

ただ、この問題は解決策がある。解説にユーモアがなければ、何言っても面白くなるような舞台設定をこしらえればいい。ゲームの仕組みや演出を行うことで、真面目なことを言っても面白くなるお膳立てを行って、面白さを表現すればよい。

外堀を埋めて面白くするために、今回は将棋バトルというテーマを思いついたのだ。

面白くなるお膳立てを行う

ということで現在のような将棋で戦うゲームが生まれた。ルール的には軍人将棋やガイスターに近いもので、勝敗がわからないままコマを進めていくタイプのゲームだ。

青いコマを進めて画面上のGOALに到達すれば勝ち。コマをぶつけると対戦となり、その結果はChatGPTが判断する。
ゲーム的に面白いので、ChatGPTが面白いことを言わなくてもゲーム的に成立する公算が高い。ゲームを作っているのだから、まずはゲームのロジックが面白いことが大切である。

上記は完成形だが、まずは本当にゲームとして面白いか検証するため、余計な演出を入れずに大まか仕組みを組んでいく。以下の図は作り始めたばかりの頃であったが、調整の結果現在の4×4のマスに落ち着いた。


ちなみにこの時点では「戦国武将をドラフトして戦わせるゲーム」であった。歴史の偉人は義務教育でみんな教わるだろうから知名度もあるし、戦わせるといったら戦国武将だろうと思ったからだ。


ゲームとして成立することがわかったので、演出もどんどん増していく。ゲームにおいて演出は多ければ多いほど面白そうに見える速攻魔法的な役割がある。この演出で盛り上げた結果のChatGPT回答はさらに面白くなるはずである。

昔、ラスタとんねるず94’という番組で武道家同士を将棋で戦わせるジャイアント将棋というコーナーがあったのだが、コマがぶつかってから種目が決定するまでの演出がとても魅力的で、これが大いに番組を面白くしていた。自分が4歳の頃にやっていた番組なので覚えている人誰もいないだろうなと思っていたのだが、意外にも公開初日でジャイアント将棋との類似性を指摘している人がいてビックリした。
ともかく、このゲーム性で面白さを担保した上で演出で面白さを持っていく行為こそが「面白くなるお膳立て」の一つである。

ただ、一つ大きな誤算があった。この状況になってもなお、面白くならなかったのである。
なんか面白くないなと思って、どうしようもなくなったのでもう「おしまいだ!万策尽きた!」と陰鬱な週末を過ごしていたのだが、ふとしたアイデアでこの状況を乗り切ることができた。

それは「戦国武将ドラフトではなく、とにかく強そうで人気のある者同士を戦わせよう!」というアイデアだ。
シンプルなアイデアだが、これが以外と盲点だった。


織田信長と豊臣秀吉が100m走や格闘対決しても面白くなかったのだが、織田信長と恐竜が100m走や格闘対決したらめちゃくちゃ面白くなった。
戦国武将は有名で誰でも知っているのだが、そこまで愛着を持っている人は多くないことが原因だろうと推測している。
戦国武将同士が対戦することよりも、パンダや口裂け女、宮本武蔵など、絶対に接点のない者同士が可愛さ対決したり、賢さ対決したほうが面白いフリになる。実際、他の人に「宮本武蔵と八尺様が大喜利対決したらどちらが勝つと思います?」したら、この質問自体がボケ質問となるだろう。そしてこの質問はどう回答してもボケ回答になる。
面白いフリというのは、回答がどんなに真面目でユーモアがなくても答えるだけで面白くみえてしまうものだ。実際、この発想の転換で登場キャラクタを総取っ替えしたところ、ChatGPTの回答が急に面白くなった。
これが面白い回答をChatGPTが行ったように見せる最後のお膳立て「人間だったら悩むような突飛なお題を出して回答させる」である。
これにより、ようやくこのゲームは公開できるクオリティになった。

データベース作成とバリデーション方法

ちなみに、データベース化も非常に容易であった。具体的なプログラムはページ下に書いておくが、参加者23人が5つの種目で勝負すると、その組み合わせは1265通りである。
人力でこの組み合わせを埋めるのは絶対に勘弁だが、ChatGPTが処理するのならば2~3時間放置で作成完了する。
ちなみに1265通りのデータベース作成にかかった費用は0.7ドルほどである。めちゃくちゃ安い。これ以外に通信は行わないので、費用もこれ以上かからない。

注意すべき点として、ChatGPTは一度に出せる文字数が決まっているから、一度に1265通り全てのデータを出力することはできない。そのため、普通にChatGPTアプリでjson出力させることはできない。

今回はChatGPTのAPIに合計1265通りの勝負内容を一つずつ質問して、その回答を記録していくことにした。
早速Pythonでコードを書いた。めちゃくちゃシンプルだ。Pythonは自動化処理を簡単に書くことができるからオススメの言語だ。
その分複雑なコードを書こうとすると地獄をみるので、あくまで100行以内で収まるコードで使うことをおすすめする。
実行したコードは以下である。めちゃくちゃシンプルだ。(以下に注意点などを書いておくので、使う時は必ず読んでから自己責任で実行すること。)

pip installでopenaiをインストールすれば動く。

import openai
import itertools
import json

LIMIT_ERROR_COUNT = 10


def main():

    outputs = []
    # APIキーの設定
    openai.api_key = "ここにAPI Keyを書く"

    name_list = sorted(["織田信長", "ドラゴン", "ぬりかべ", "大虎", "恐竜", "化け猫", "花子さん", "八尺様", "吸血鬼", "赤鬼", "魔王",
                       "ライオン",  "パンダ",  "戦車", "桃太郎", "ヒュドラ", "キマイラ", "トロール", "宮本武蔵", "口裂け女", "九尾の狐", "魔女", "ゾンビ"])
    battle_list = ["格闘", "100m走", "賢さ", "大喜利", "可愛さ"]
    progress_number = 0
    all_number = (len(name_list)*(len(name_list)-1)/2)*5
    for pair in itertools.combinations(name_list, 2):
        for battle in battle_list:
            progress_number += 1
            print("progress:"+str(progress_number)+"/"+str(int(all_number)))
            error_count = 0
            while (True):
                if (send_gpt(pair, battle, progress_number, outputs)):
                    break
                error_count += 1
                if (error_count >= LIMIT_ERROR_COUNT):
                    print("レスポンスが不正だったり、通信が一定回数失敗したので諦めました", pair, battle)
                    return
    with open("output.json", "w", encoding="utf-8") as outputFile:
        json.dump(outputs, outputFile, indent=2, ensure_ascii=False)


def send_gpt(pair, battle, progress_number, outputs):
    try:
        response = openai.ChatCompletion.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "user",
                    "content": pair[0]+"と"+pair[1]+"が"+battle+"で勝負したらどちらが勝つ?返信はjson形式で{win:'勝者の名前',reason:'理由'}と書いてください。"},
            ],
        )
        print(response.choices[0]["message"]["content"].strip())
        outputs.append({"id": progress_number, "player1": pair[0], "player2": pair[1], "battle": battle,
                        "result": json.loads(response.choices[0]["message"]["content"].strip())})
        return True
    except:
        print("エラー", progress_number, pair[0], pair[1], battle)
        return False


main()

python読めない!という方へ説明すると、このプログラムでは以下の文章をChatGPTに問いかけている。

[対戦者1]と[対戦者2]が[対決内容]で勝負したらどちらが勝つ?返信はjson形式で{win:’勝者の名前’,reason:’理由’}と書いてください。

きちんとChatGPTが回答すれば、連想配列の形式でデータを返信してくれるので、それを配列に追加していき、最終的な結果をjsonデータで出力すれば、JavaScriptでも使えるデータベースが完成する。

ちなみにプログラム先頭に書いてあるAPI KeyはOpenAIに登録して、クレジット登録すれば配布される。
間違ってもこのAPI Keyは他の人に見せてはいけない。あなたのクレジットから課金されてしまうからだ。
よくある失敗談として、プログラムごとgithubにアップロードした時にAPI Keyも含めてアップしてしまったという例がある。絶対に起こさないように気をつけよう。自分もこの記事の公開前に何度かチェックした。

初期登録時に18ドル相当のクレジットが3ヶ月限定で付与されるので、ありがたく使うといい。おそらく今回やりたいことはこの18ドルで賄うことができるだろう。
これからこのコードを実行する上で注意点を書いておくので、必ず読んでから自己責任で実行してほしい。誤ってChatGPTに120ドル全額溶かしました!となったら目も当てられない。

ページ中段のエラー処理であるtry~catch文はChatGPTサーバーがビジー状態となって通信できなくなった時か、ChatGPTが正しくないフォーマットを返信した時に発生する。
通信不通はまだ仕方がないとして、正しくないフォーマットが送られてくると問題である。
本来は{win:”名前”,reason:”理由”}と連想配列が送られてくるべきところだが、ChatGPTはたまに文字列で返信することがある。上記プログラムではレスポンスをコンソールに表示するようにしているのだが、以下のように回答が拒否されてしまうことがある。

progress:1/1265
{
  "win": "キマイラ",
  "reason": "キマイラの方が体力・攻撃力が高く、また炎や毒のブレスを放つことができるため、ぬりかべに対して優位に立てる。"
}
progress:2/1265
申し訳ありませんが、ぬりかべやキマイラは架空の存在であり、100m走で勝負することはできません。そのため、回答することはできません。
エラー 2 ぬりかべ キマイラ 100m走

上記は判定できないという返信だったが、これ以外にも倫理的に回答できないという返信が行われることもある。
ChatGPTは倫理フィルターがあるので、むやみに戦わせると反社会的とみなして回答を拒否することがある
この場合も再度同じ質問を投げればいつかは回答がもらえるのだが、同じ質問だと回答拒否の再現性が高いため、一度エラーが起きると解決しない傾向がある。
想定外の回答に対してエラーで再試行する今回の仕組みは、下手するとお金が吹き飛ぶ行動でもあるため、エラー回数の上限はしっかり設定しておこう。(今回の例だとLIMIT_ERROR_COUNTにあたる)
また、ChatGPTのAPIにはハードリミットという項目から費用上限を設定できるので、ここに上限金額を書いておくと安心だ。ソフトリミットという設定欄もあるが、これはメール通知のみなので、あまり信用できない。

ともあれ、上記のプログラムが最後までうまくいくと、全勝敗データベースが配列で出力される。

これで完了かといえば、まだ課題はある。

たまにwinの欄に名前ではなく「不明です」とか「わかりません」と書かれるケースがあるからだ。
つまり、データ構造が意図した形になっていないということで、これは非常に困ったことになる。

これをJavaScriptのようなコンパイラがない言語で実行すると、この条件で判定するときだけ実行時エラーを起こすやっかいなバグの原因となる。
1265通りのうち、1通りだけ実行時エラーになる!」となったら、この対処はかなり面倒である。

これをどうやって意図した形に有効化(バリデーション)するか。今回は型定義系の言語によるバリデーションを行った。

回答データ構造が適切か調べるためにTypeScriptの型バリデーションを行う。
Webブラウザ上で動作する言語といえばJavaScriptだが、TypeScriptはこのJavaScriptに型という概念を追加した言語だ。
型という概念は、めちゃくちゃ大雑把に説明すると「その変数は何者ですか?」を定義するものである。変数が何者かわかると、不用意なバグが格段に減るメリットがある。


JavaScriptだと変数に型をつけることができないので、以下のように変数に異なる形式のデータを入れることができてしまう。

let winResult = "織田信長";  // 文字列の変数に
winResult = 123; // 数値も入っちゃう
winResult = { // 連想配列も入っちゃう
    win: "織田信長",
    reason: "強かったので勝ちです"
};

これを型定義できるTypeScriptで書くと、変数に異なる形式のデータを入れるとエラー扱いすることができる。
変数のデータ構造を一種類に制限することで、データの形式が同じであることを保証することできる。

この型によるエラーの仕組みは機械的に判断ができるので、TypeScriptを使っている人はLinterと呼ばれるコードの間違い指摘機能を入れている。上記画像で赤線がついているのはLinterの機能だ。

Linterはコードを自動で走査して、エラー箇所を自動で発見して報告してくれる機能だ。

これを今回使用して意図していないデータをエラーとしてコード内に表示してもらうことを考える。
ここで役立つ型定義として、ユニオン型を用いた型定義を作る。

export type Name = "織田信長" | "ドラゴン" | "ぬりかべ" | "大虎" | "恐竜" | "化け猫" | "花子さん" | "八尺様" | "吸血鬼" | "赤鬼" | "魔王" |
    "ライオン" | "パンダ" | "戦車" | "桃太郎" | "ヒュドラ" | "キマイラ" | "トロール" | "宮本武蔵" | "口裂け女" | "九尾の狐" | "魔女" | "ゾンビ";
export type Battle = "格闘" | "100m走" | "賢さ" | "大喜利" | "可愛さ";

ここで定義したName型はパイプ記号(|)を用いて書かれた文字列しか持つことができず、他の値が入るとエラーとなる。
これを用いると先ほどのデータ構造を型として定義でき、これを適用することで型にあわないデータを容易に見つけることができる。

こんな感じでエラー箇所を発見できるので、不正なデータは検知できるようになる。

今回生成したデータ列は以下の型で定義することができる。

export type JudgeData = {
    id: number,
    player1: Name,
    player2: Name,
    battle: Battle,
    result: {
        win: Name,
        reason: string
    }
};

この型の配列型になる変数を定義して、生成した配列を貼りつける。もし問題があれば赤字でエラー表示してくれるはずだ。

今回は686個目のデータに型定義とは異なるデータが発見された。TypeScriptのトランスパイラも同様のエラーを検出して報告してくれている。

typescript/judge_data.ts:6887:13 - error TS2322: Type '"不明"' is not assignable to type 'Name'.

6887             "win": "不明",
                 ~~~~~


ただ、このユニオン型には一つ問題があり、型の組み合わせ数が多すぎると「複雑な型です」とエラーを返す問題がある。

error TS2590: Expression produces a union type that is too complex to represent.

Name型に23種類の型候補を入れているのだが、連想配列内に3つもName型を入れると、型全体の組み合わせが23^3=12167通りとなってしまい、これがエラーの原因になるらしい。
仕方がないので出力がName型で確定しているplayer1,player2をstringに戻した。このplayer1,player2はプログラム的にName型であることは確定しており、返信結果であるresultの型だけ確認できれば問題ないからだ。

export type JudgeData = {
    id: number,
    player1: string,
    player2: string,
    battle: Battle,
    result: {
        win: Name,
        reason: string
    }
};

型定義を上の型にすると、問題なくチェックできるはずだ。この型チェックで問題なければ型チェックデータベースの完成となる。

まとめ

以上でChatGPTを用いたゲーム作成の振り返りと、データベース作成方法、データ構造を正しくバリデーションする方法について書いた。
この記事読む人の想定スキルは高いものと考えているので、ChatGPTの使い方や型定義の説明は釈迦に説法かと思うのだが、一応書き残しておいた。自分が踏み抜いてしまった地雷とその回避方法も解説しているので、参考になれば幸いである。
何か間違いや、疑問点があればtwitterに質問を投げてほしい。また、良い記事だと思ったらSNSなどで紹介してくれるとありがたい。