#atgm_2020 でゆうしゃくんを作り上げた話

こんにちは。Naka(@Naka_musicgamE)と申します。


普段からnoteなんて使わないんですが思い立ったのでちょっとメモをしようかと思います。

はじめに

話題は「2020GM運営のあんたがたに挑戦します」というtwitter上で展開される謎解きゲームのようなものについてです。(以下、atgm_2020と呼称します)(ツイッター上では #atgm_2020 というハッシュタグでツイートがなされていました。)

このゲームには元ネタがあります。それは「twitterのあんたがたに挑戦します2020GW」(以下atgt2020gwと呼称します)というもの。こちらは2ちゃんねる発祥の謎解きゲーム。めちゃくちゃ難しい問題がたくさん降ってくることに定評があります。このゲームには私もプレイヤーとして参加しました。といっても基本的には外野から騒ぐだけですが…。
気になる人は #atgt2020gw というハッシュタグで調べてみましょう。いろいろ出てくると思います。尤も、これを読んでる人はプレイしているか存在を知っている人が多いような気もしますが…。

さて、ではこのatgm_2020は何なのかといいますと、picaさんが主軸となって企画された「atgt2020gwの運営の人たちにお礼をしよう!」というもの。atgt2020gwで出題された問題などをパロディして出題しながら、とんでもない問題を生み出した運営の人たちにお礼をする企画でした。(私達のほうが時間かかる問題を生んだパートもちらほら有りましたが……。)

なんなら、atgt2020gwのメインGMのましーさんからは「お礼はお礼でもお礼参りでは?」なんて言われたりもしていましたが…。

メインのお話

さて、ここからが本題。atgt2020gwでは、というかあんたがた全般では「突撃」なる概念が存在するんですよね。詳細は割愛しますがざっくりいうと、謎の答えになる場所に実際に赴くこと。ものによっては海外に飛ばされることもあるとかないとか。去年は韓国とかベトナムとかにも行ったらしいですよ。

それはさておき、そんなあんたがた名物の突撃ですが、atgt2020gwやatgm_2020が行われていた当時は、というかこれを書いている今もですが、コロナ騒ぎの真っ只中。そもそも、外出を前提としたこんなゲームが開催されるのかも私自身疑わしく思っていたのですが…。

なんと、メインGMのましーさんがatgt2020gwのためだけに突撃をネット上で再現するシステムを構築していたのです。それによって、我々は家から一歩も出ること無くatgtをプレイすることが出来ました。

さて、なんでこの話をしたのかというと我々atgm_2020運営も同様のシステムを作る必要があったからなんです。ゲーム開催時は7月。まだコロナ騒ぎが収まってないであろう時期。我々がatgtのパロディを作るからにはせめてシステムもパロディ、あわよくば完全再現したい!ということで私が自分の勉強のためにLINEを利用したBOTを作成することになりました。

そうは言っても

そうは言っても、私はLINEbotなんて作ったことは有りません。応答メッセージだけの簡単なやつは作ったことがありますがそんなものでは再現はできません。そもそも、今回要求されていた機能はだいたいこんな感じ。

・現地(答え)をwhat3wordsの形式で登録する
・登録された答えの地点にプレイヤーが仮想的に突撃する。
・突撃が終了したら、その地点が正解だったら次の問題を手に入れるためのパスワードを発行する。

今回実際にプレイしてくださった方はお気づきかもしれませんが、この機能を完璧に実装することを今回は放棄しました。その話はまた後で。

そもそものお話

そもそものお話、LINEのbotを作るためにはサーバーを用意することが必要です。が、それに関する知識が私にはない…!プログラミング初心者で、開発の事もよく知らない私はサーバーを借りたり、それを実際に運用する方法なんて知りません。それに関する記事を読んでみたりもしたのですが何を書いてあるのかさっぱり。

そこで利用してみたのは GoogleAppsScript というGoogle のサービス。実は、スプレッドシートを使ってLINEのBotを作ることが可能で、(参照したツイートを引用しようとしたら、元ツイが削除されていました。)それを参考にしながらBotを作成しようとしました。

実装の壁

さて、ここまでまとめたところで問題が生じます。それは「位置情報をどう扱えばいいのかがわからない」ことでした。

本家では「プレイヤーの位置情報」と「登録された現地の位置情報」を取得して「その2つの位置を比較して移動所要時間を算出する」という流れの処理が行われていました。が、これらの処理を実際に実現する方法が全くわからないのです。

本家では、What3WordsとGoogle マップのAPIを利用したり、位置情報を取得したりしていたようていたようですが、そこらへんのシステムがよくわかりません。調べはしたのですが、いまいち何をすればいいのかよくわかってませんでした。そしてなにより、セキュリティもよくわかってないような私が位置情報を扱いたくなかったです。

そこで、メインGMのpicaさんと相談してシステムの方針を変えることにしました。

そこで提案されたのが

そこで提案されたのが、「ゆうしゃくん」という概念でした。あんたがた恒例の「突撃」という行動ですが、通常この行動を行う人のことを「勇者」と呼ぶのとが多いんです。本家ではその勇者役をプレイヤーが担うのですが、今回はそうではなく勇者役をある架空の存在に担ってもらいプレイヤーに応援してもらう、という仕組みを採用しました。

あんたがたの最大の特徴の一つである「突撃」と「勇者」を破壊するのはかなり勇気が要りましたが、実装の壁の都合もありこのような形に落ち着きました。

ゆうしゃくんとおうえん

そんなこんなで出来上がっていった「ゆうしゃくん」と言うシステム。プレイヤーご現地を登録したり応援したりといった情報を、googleスプレッドシートを利用して管理することで現在の突撃状況や答えの現地の登録などをやりやすくしていました。

応援の実装方法はギリギリまで悩みましたが、最終的には「応援の数に応じて残り時間が減少する」方式を採用しました。「応援速度に応じて移動速度が上昇」なども考えはしましたが、処理が面倒なのと応援の時期で効果に差が出てしまうのが良くないということでこの形に落ち着きました。

人が多い時間帯に問題が進行することで色んな人が問題の回答に参加しやすくなったので、結果的には良い判断だったかなと思っています。

問題だらけのシステム

そんなこんなで完成したゆうしゃくん。前日までに他のGMのみなさんを含めてシステムが正常に動くのかどうかのテストを行っていました。

そして迎えたatgm_2020スタートの日。1匁が回答された直後に事件が起きました。本来であれば突撃から1時間以上の待ち時間の後にパスワードが発行されるはずなのに、突撃直後にパスワードが発行されてしまいました。

ちゃんと動くかどうか心配で張り付いていた私は、これを見て死ぬかと思いました。テストでは動いていたのもあって、尚の事びっくりしてしまいました。

プレイヤーが二匁を解いている間に他のプログラミングに明るいGMの方と一緒にコードを見ながら原因を究明していました。

その結果、複数の不備が重なってうまく行っていなかったことがわかりました。詳しい原因はこのノートの最後で軽く触れることにします。

原因究明の結果、2匁以降はどうにかうまく回すことができました。

懸念されてはいた事態

そんなこんなで、無事(?)動き出した勇者くんですがシステムの考案当初から問題視されていた点がありました。それは「一度に一箇所にしか突撃でいない点」です。本家atgt2020gwでもたまにあった、w3wの一文字違いだとか、一マス違いなどが生じたときにゲームのテンポを著しく悪くするのではないか。そして、悪意の第三者が介入した結果ゲームが成立しなくなるのではないか。ゲームが始まるギリギリまで検討されてる事項の一つでした。

前者については、ゲームの仕様であり、たまにはゆっくりした時間を過ごすチャンスにしてほしいということでそのままにすることにしました。突撃時間はあんたがたの数少ない休息の時間です。

しかし、後者はそうも言っていられません。本当にゲームが進行できなくなる可能性すら有りました。かといって、うまく悪意のある行動を振り分けるような方法は有りません。私は、atgm_2020スタートの前日にこっそりブラックリストの実装をして、それが使われないことを祈ることにしました。

深刻な問題

しかし、こういう祈りは大抵の場合通じないものです。第三者によるゲームの進行を妨げようとするかのような突撃が実行されました。

お恥ずかしながら、このようなときの対応を事前に準備しきれていなかった我々は、「突撃のキャンセルの対応」と「荒らし発生時の対応の決定」を同時にこなす必要に迫られました。

前者はシステム的にちょっと操作すればすぐできる話なのですが、後者は非常に大変でした。話題が話題なので何があったかは省かせていただきますが、想像はされてもいざ直面すると非常に大変でした。

他にも、荒らしを回避するために「突撃をキャンセルする仕組み」を実装するべきでは。というアイデアも出てきましたが、新たな実装がバグを招く可能性やさらなる荒らしの要因になりうる可能性を鑑みて追加実装はしないことになりました。

プレイヤーの皆さんの温かい反応のおかげもあって、荒らしのような事態こそありましたが、無事ゲームは進行させることができました。

思いつきによるトラブル

トラブルはまだまだ続きます。今回のatgm_2020では、一回だけ「分岐」という概念が登場します。一つの問題で2つの答えが同時に現れるケースです。このとき、勇者くんの「一度に一箇所だけしか突撃できず、そこそこ長時間待ちが発生する」仕様が、ゲームのテンポを著しく損なう可能性が強く懸念されました。そこで、その分岐の2回目の突撃は突撃時間を短縮しようということにこの問題をプレイヤーが解いている間あたりで決定されました。

当時、このようなツイートも流されました。これは1つ目の現地がカフェだったので、それとからめて次の突撃が加速されることを宣言するツイートです。ちなみに、もし最初の突撃が三国郵便局付近になっていた場合は、ノエルちゃんに出会ったことで元気が出た!みたいな文言になる可能性が高かったです。

が、しかしここで私がやらかしました。内部処理の書き換えを間違えて「残り時間の表示が半分になっているが、残り時間が減るスピードも半分になる」つまり、実質的には全く変化していないと言う事態を招いてしまいました。その場の思いつきで実装を考えて、その実装がただしいかどうかの検証を一切しなかったのが敗因です。

ちょっとだけ頑張ったこと

そんなこんなで、トラブル続きだった勇者くんですが、ちょっとだけ頑張ったこともあります。それは、表記ゆれへの対応でした。

今回の勇者くんでは、コマンドを送信してもらう形でいろいろな処理を実行していく様になっていました。この手のシステムは同じ意味でも指定されたコマンドでないと動かない、なんてことになりがちですがうまいこと頑張って表記ゆれにも対応できるようにしました。

例えば、「おうえん」というコマンドならば「応援」でも同じ処理が実行されるようにしました。

無駄にこだわったポイントとしては、「とつげき」というコマンドなら「突撃」「凸撃」、はたまた「凸」であってもちゃんと動くようになっていました。遊んでいるときにこのことに気がついた人はどれぐらいいたでしょうか。

後日、「いつも打ち間違えに気がついていちいち消していた」というコメントを頂いたのはすごく申し訳なく思いました。少しぐらいの表記ゆれなら対応している、ということはきちんと伝えるべきでした。

まとめると

とまあ、こんな感じで様々なトラブルに見舞われつつもどうにかこうにかゲーム終了までこぎつける事ができました。現在はゆうしゃくんのシステムは停止していて、ごく一部のワードを除いて特殊な反応は返していませんが、もしよろしければその「ごく一部のワード」を探してみてください。どうやら、atgm_2020の振り返り生放送にそのヒントがあるようですよ。そんなのはいいから、答えが知りたい!と言う人はこちらを探してみてください。

でですね、まあ今回の感想をまとめてみると。初めての開発がこれなのは頭おかしいの一点に尽きます。人によってはそうでもない、と思うかも知れませんが、個人的には実装難易度も構成難易度も責任もめちゃくちゃハードで途中で気が狂うかとおもいました。それでも、どうにかなったのはプレイしてくださった皆様のおかげです。本当にありがとうございました。

おわりに

こんな感じで私のメモ書きは終わりにしたいと思います。初めてづくしのatgm_2020でしたが、どうにかなってよかったです。この経験はしっかり次に生かしていきたいですね。

え、次のあんたがたはGMとして参加するのか、ですか?それはまあ始まってからのお楽しみとしましょう。どうせいつかは分かることなので。

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

ちょっと細かいお話

途中で飛ばした、一匁の突撃エラー事件の原因のメモ書きです。細かい上にわかりにくい記述で大変恐縮です。こんなこともあるんだな、ぐらいの気持ちで読んでいただけると幸いです。

原因の1つ目は「if文の場合分けを適切に行っていなかったこと」です。

原因になっていた当時のコードがざっくりとこんな感じです。

    //終了しているか確認
   if (assultCheck == 0){//突撃中
     
     //終了
     return
   }else{//完了
     
     //突撃していない状態にステータスを更新
     systemSheet.getRange(2,7).setValue(0)
     
     //正誤判定処理を呼び出し
     assultFinish(assultNum)

     return
   }

assultCheckというフラグを見ながら完了したかどうかを判定する仕組みになっています。想定では、このフラグは1か0の二値しかとらず、1になったときだけelse文が実行されて、突撃結果が出力されるようになっていました。

しかし、後述の仕様と重なった結果、このコードでは致命的な問題が生じることになりました。

原因の2つ目は、GASで定義した関数は計算に時間がかかること。でした。何を当然のことを、と思うかも知れませんがこれが本当に重要なポイントでした。というのも、先程のassultCheckというフラグはスプレッドシート上で計算される値になっています。スプレッドシートでは「=SUM(A1:B4)」みたいな感じの関数を設定することでそれに応じた計算を自動でやってくれるのですが、GASを利用すると好きな関数を自分で作ることができるんです。

今回はそれを利用してフラグ判定の関数を作っていたのですが、ここに罠がありました。GASを利用した計算は時間がかかり、再計算の度に計算が完了するまでの間「#loading」というエラー値を返す、という仕様がありました。

以上の2点を組み合わせると何が起きるか。そう、assultCheckというフラグに「#loading」というエラー値が格納された場合、if文の処理の結果突撃終了時の処理が実行されてしまうのです。

このことを明らかにするために、システムのコピーをして手元でいろいろ操作して調べていましたが、うまく見つかって本当に良かったです。

修正として、if文の条件式の書き換えと、スプレッドシートの組み込み関数の利用の2点を行うことでどうにか解決させることができました。

「2値のいずれかを取ることを期待される状況でも、きっちり条件分けはしないといけない」ということを痛感しました。



この記事が気に入ったらサポートをしてみませんか?