ぼすきーに公開したPlayについて語る

2023年12月7日

今回はぼすきーに公開したPlayのコンセプトや裏話について話していこうと思います。
ぼすきー Advent Calender 2023の参加記事です。

とはいえ作った全てはここに書いていません。コンセプトが明確にあったりするやつだけ抜粋して書いています。
それでも普通に語っていたらいつの間にか文字数が10000文字を超えていましたが…

ぼすきー特有ネタも怪文書もなんでもあります、それでもいいよって人はよろしくお願いします。

タカハシ折リッカー

原点。登録から5日でリリースしたものです。
薄いことで有名なタカハシがどこまで折れるか挑戦するゲームです。ゲームか?
折れなくなったら、そこで終わりです。アイデアがなくなったか、祈りが通じなかったか、単純に折れなかったかに関わらず。

https://voskey.icalo.net/notes/9hdcp26aw9
ここから3時間ほどで完成していたらしい

コンセプト

:そんなもの コレにはないよ…: とはいえ、「口調の突き落とし」はこの頃から存在しています。

「タカハシを折れ!!!!」と突如言われて、いざ折れなくなると「タカハシを折れませんでした。」と口調が変化して、
そのセッションでできることは「投稿する」だけになるところとか。

また、リスクとリターンのバランスもこの頃から考えていました。
リスキーだけど大量に折れる「いい感じにタカハシを折る」と、
中間のリスクと少しだけ折りボーナスをくれる「祈りながらタカハシを折る」と、
リスクは少なめだけどボーナスがない「普通通りにタカハシを折る」の3つがある点。

さすがにタワー系と違って「完成」の概念を付けてリスクとリターンを作るのは無理がありました。
折られたタカハシをどうやって誇るんですか…月まで届いたとか?

なので、折り回数のブーストと失敗リスクでバランスを取っています。取れてない気もしますが。

名前は単純にクリックするほど増えていくのでタカハシ + 折り + クリッカー で タカハシ折リッカーです。小春の方ではない。

アドバイス

  • 日頃の行いを良くする
  • 折れるように祈る

キャラ合わせポーカー

自分が作った中で本当に「ゲーム」といえるPlayだと思います。実はタカハシ折りより前に制作開始したものです。
5枚のキャラクターが書かれたカードが配られるので、不要なカードを引き直してより強い役を目指します。
CPUより強い役だったら勝ち、弱い役だったら負けです。
勝てそうだと思ったらBETすることができます。勝敗によって増減するスター数に応じて、BETの最低数が上がります。

コンセプト

NewマリオDSの「ルイージの絵合わせポーカー」が元ネタです。

ぼすきーのyay絵文字を活かして絵の代わりにキャラを合わせるポーカーを作ろうと遊んでいる時に思いつき、制作したものです。
てぇてぇだったら役の強さが2倍になるのもこの時に考えています。

このPlayには継続性が十分にありそうなので、5ラウンドゲームにしています。
ゲームが終わったら、「次のゲームへ」をクリックすることでいくらでも遊ぶことができます。

カードには2つのキャラクターカードとてぇてぇ役を持つスペシャルキャラカードが一つあります。
この「7種類あるカードが1種類別カードと組み役を持ち、1種類だけ2種類のカードと組み役を持つ」仕様は、キャラ選において非常に悩むことになります…

ちなみに、スペシャルカードは非常に低い日替わり確率でタカハシカードに変化します。タカハシカードはどのキャラクターとも組み役を持ちませんが、
その代わり5カードを作ると1BETでも926枚のコインを貰えるというメリットがあります。むしろ役が作りにくくなって逆効果かもしれない。

ショップ

コインはデバイスにセーブされていて、次のセッションにも持ち越すことができます。
その結果、「勝てば勝つほどコインが増えていく」という問題があったので、アップデートでショップ機能を作って消費を増やすことにしました。
所有しているコインを消費することで、カードの柄を別のセットに変えたり、
よりハードな設定にしたり、タカハシ化の確率を増やす事ができるようになっています。

この「ハードな設定」についても、少しだけ説明しようと思います。

ハードギャンブラー

ショップに登場したハードギャンブラーは、5BETが上限だったのを35BETまで増やすことができるようにするアイテムです。
購入にはまず1000コイン持っている必要があり、購入すると現在の所持コインの半分を消費します。

そして、負けると通常はBETしたコインを失いますが、
ハードギャンブラーが有効の場合「相手の役ポイント × BETしたコイン」を取られます。
そのため、自分が多くBETしていて、相手が強い役を持っていると、その分コインを失うというリスクがあります。

このあたりは結構よくできたと思いますが、
前述したタカハシ化の状態で35BETしてCPUにタカハシ5カードを作られて負けた場合
926 × 35 = 32410枚のコインを失う恐怖の可能性を生み出しました。
…記念日に無条件タカハシ化イベントをした時に恐れていた事態を引き起こした人が出ましたが。https://voskey.icalo.net/notes/9isxz7p1bi

アドバイス

  • マイナスをなるべく減らしたい場合はその状況に合ったBETをする
  • 弦巻マキ/ノイズフィーちゃん は2つの組み役を持つので、可能なら活用しましょう
  • ハードギャンブラーは累計で見るとプラスがマイナスを上回ることが多いので、買えそうな時に早めに買いましょう
  • タカハシの5カードは破滅をもたらします

ランダム系

これに関してはコンセプトとか特にないので、ランダム系について書こうと思います。

引き直し可能か、日替わりか?

自分が作ったランダム系Playは、一つを除いて全て日替わりで作成されています。
なぜ日替わりを採用するか、なぜ一つだけ採用しなかったのかについて、自分の考えを説明します。

そもそもMisskeyのPlayって「なんとなくタイムラインに流れてきたものをサクッと遊ぶ」ものだと思っていて、
文字シャッフルのPlayが流行るのもこれが理由だと思っています。
ランダム系のPlayは、日替わりの場合実行したらもうその日にできることは「投稿する」くらいしかないわけです。

それを引き直し可能にすると、その日にできることが増え「根気強く引き直して揃える」事ができるようになります。

それでも揃うかどうかは非常に低い確率の運ゲーで、そうなるともはや「作業」になります。
しかしやっていれば自然と揃う時が来るので、適当に引いて投稿することが目標の日替わりと違い「揃える」ことが目標になってしまいます。

その結果、やっていくにつれ面倒になって引くことをやめてしまう可能性が出てきます。
継続性を得るには時にやることが少ないことも重要です。なので自分のランダム系Playは引き直しがありません。

ただし、例外として #LTLがrandすぎて泣いちゃった があります。「投稿を目的とした引き直し」があるからですね。
自慢話のためではなく、今使えそうな感じになるまで引き直して投稿する需要があるので、このPlayには引き直しがついています。
バリエーションが少なく、狙ったものに近いものが出やすいのも理由です。

ということで、引き直しについての話は終わりです。

真・タカハシ折リッカー

タカハシを折る作業は、まだ続く。

タカハシを折り、施設を購入し、より大量の折りタカハシを生産するPlayです。
今もなおベータ段階です。

コンセプト

クッキークリッカーパロディです。Playでクリッカー作った人そんなにいないと思う。なぜいないのか作って思い知りました。
タカハシ折リッカーは一つのタカハシを折っていき、折れなくなったら終わりですが、
このPlayは15回折られたタカハシを何個も、何十個も、何百個も、何千個も、何万個も、何億個も、何兆個も…生産していくゲームです。
セーブデータが端末に保存されるので、1セッションで終わるPlayではありません。いつまでも遊べます。

タカハシを折るボタンは無印の時は三つありましたが、一つになっています。(あなたが来世を迎えたら、元の三つに戻せるかもしれませんね…)
15回折ると「1折りタカハシ」になります。一個折りタカハシを作ったら次のタカハシを折って、また折りタカハシを生産します。

折りタカハシはこのPlayでは取引に使えるようです。どうして折らないと使えないんでしょうね?私にもわかりません。
持っている折りタカハシを消費して、あなたが折らなくても自動的に折ってくれる施設を購入することができます。

クリッカーゲームは数字が増えていくからこそ面白いと思っています。
進めていくうちに今までたどり着けなかった単位に簡単にたどり着けるようになるとか。

なので常にぼすきーに張り付いているぼ廃と相性ピッタリ。
そのせいか人気セクションにも入っていて、3ヶ月経った今も遊ばれています。

後はやっぱりギャグ性ですね。
元ネタであるクッキークリッカーの「フレーバーテキスト」は本当に面白くて、ここだけは勝てる気がしません。
タカハシ折りを一通り終えたら、元ネタのクッキークリッカーもやってみてください。
Cookie clicker Steam版もあります。C418の最高の音楽とともにクッキーを生産できるのでおすすめです。

タカ折りの今後

今後実装したい要素についても色々考えてます。とりあえず0.5.0はぼちぼち開発中。
とりあえずクリック強化のアップグレードは力不足なのでOpS基準で増加するようにしたり、昇天を使った周回要素の実装もしようと思ってます。

アポカリプス要素はまだ導入しません。今のところどういうシナリオでそうなるかまったく思いつかないからですね。
タカハシを折ってるだけで世界がめちゃくちゃになるシナリオってどういうこと??????

…あとあんまりひどいことになるとCeVIOプロジェクトの人からお達しが来そうです。今のままでも来そうなのに。

初期のタカハシ折り

最初の頃は施設価格の所持率に応じた倍率調整をミスっていてすごく簡単に施設を増やせるようになっていたので、
簡単に100万タカハシなどを達成する人が続出しました。

0.2.0アップデートでセーブデータリセット&所持数に応じたより適切な倍率調整をしたので、
今は適正なプレイ時間でゲームを進められるようになったと思います。

そんなアプデ後の今、最近1億折りタカハシとか行ってる人をよく見ます。怖いわ…。

パフォーマンス

このリアルタイムゲームではパフォーマンスについても話しておきます。

このゲームは45TPSで動作しています。ティックでは自動的な折り処理をやっているのですが、
初期の0.2.0辺りではそれぞれが折り処理の関数を呼んでいたのでかなり重くなっていました。
なので、それぞれが折り関数を呼ぶ代わりに「このティック内で何回折りが行われるか」の変数に加算して、
ティックの最後で折り関数にその変数の値を投げる、という処理にするようアップデートしました。

ついでに、ユーザーのインタラクションがないと動かないテキストについても折り処理で更新しないようにしました。
ショップとかの価格表記を購入した時だけ更新する、みたいな感じですね。

この結果パフォーマンスはまあまあ改善したと思います。
それでも結構な処理を45TPSでしているのでかなり重いPlayになっていますが。

ちなみにこの規模になるとコードをMisskey内で書くのは地獄なので、スクラッチパッドは実行専用になります。VSCodeは必須。
0.5.0の開発段階で文字数カウントにかけてみましたが55995文字/1004行ありました。この記事の文字数より多い。
リファクタリングをする時が来たかな…。

とりあえずショップとかの動的テキストをファーストレンダーとテキスト更新で二回記述してるのは非効率なのでやめようと思っています。
関数にすると書きにくくなるし、Object化すると多分strに変わってしまって結局何も変わらないし、
render時は空にしてテキスト更新に任せるのが一番手っ取り早い解決策ですね。

モジュール化ほしいです…。

クソデカ数値問題

今後の問題はもう一つあって、クリッカー系は当然非常に大きい数を扱いますが、
AiScriptは当然JSの上で動くのでNumber.MAX_SAFE_INTEGERの9007199254740991(9007兆)を超えると丸め誤差で下桁が少しづつおかしくなっていきます。

また、指数表記の問題もあります。9垓を超えればそのままの状態でできる表示は1e+21のような指数表記になってしまい、
プレイヤーが必要な折りタカハシ量や現在の折りタカハシ量を直感的に理解することが難しくなります。

そもそも指数表記に達するくらい大きい数の場合、先述した丸め誤差で正確に変換することもできないので、
この数をどう扱うかはアップデートを行う上で必ず考えなければなりません。

AiScriptはJSのBigintを使うこともできないので、自力でArrayでも使って数を保存するくらいしかなさそう。
しかしArrayになるとfor処理を回す必要も出てくるため、1ティックの処理を45TPSの場合は22.2ms、60TPSの場合は16.6msの間に
必ず処理を完了させないと繰り上がり処理などが正しく行えない可能性もあります。

他にできそうな解決策は1兆を超えたら1折りタカハシスタックのような変数に加算して表示することですかね。
それでもその場しのぎにしかならないかもですが…。

迂闊なアップデートができない理由になっているこの問題、どうしようかな…。
一旦考えてる間にQoL機能だけ引っ張り出して0.4にバックポートしてもいいかもしれません。

アドバイス

  • 買いたての施設はアップグレードと新しい施設の購入の判断を適切に行いましょう
    施設数が1なら施設を買い足すよりもアップグレードした方が安い場合もあります。逆もあります。
  • 時間に身を任せましょう
  • 折っている間に創作物の進捗を進めましょう
  • 折っている間に日常のタスクを消化しましょう

タカハシストッパー

いつも回っている回転タカハシ。そんなタカハシを、止められるとしたら…?

コンセプト

:そんなもの コレにはないよ…:
回ってるタカハシの絵文字見てたら「これ止めるPlay作ったら面白いんじゃない?」と思っただけですね。

ただ止められたか止められなかったかじゃ物足りなさそうなので、「指定された角度にいかに近く止められるか」を競うようにしました。
ボタン一回でピタッと止まったら目押しゲーになってしまうため、慣性も付けています。速度に応じて完全に止まるまで時間がかかります。

無印タカ折りにあったテキストの突き落としもあります。今回はかなり強め。

それでもまだ足りないので、壊れたり止まらなかったり気を利かせてくれたり    したりするランダムイベントも作りました。
気を利かせてゆっくり動いていく虚無の時間はお気に入りです。

このPlayには「リトライ」があります。この理由として、まず成功するために技術が必要であること、
そしてユーザーの操作によってゲームの進行スピードが完全に左右されず、タカハシが止まるのを待ったりするなどのゲームサイドでの時間確保があるためです。

一度感覚を覚えてしまえば狙った結果を出しやすくなりますし、
イベント自体もそこまで運が絡まないので、このPlayにはリトライ機能があります。

アドバイス

  • スピードが早い時は慣性を考慮してかなり早めに、遅い時は少しだけ早めに止めましょう
    このあたりの感覚は自然と身についていくはずです
  • 通常よりも速度減少が遅い時はランダムイベントが起こる証です
  • タカハシを  せないように気をつけましょう
    後悔するぞ

VOSKRIS

ただの落ち物パズルゲームです。これ以上は言いません。

コンセプト

某パズルゲームのクローンです。MisskeyのPlayに移植してみたかった。
ニコテトのノウハウがあった上、AiScriptもJavascriptに近い記法だったので開発にそこまで時間はかかりませんでした。

このPlayでは「キーボードコントロール」を実装しています。これについて少し話しておきます。

キーボードコントロール

Playには操作性の問題がありました。ゲーム的なPlayを操作するには実質的にボタンしか無いので、
瞬間的な操作にはカーソル移動コストがかかってしまいます。
なので前からPlayにキーボードコントロールを実装したいと考えていました。

それに使ったのがtextInputです。
これにフォーカスしてもらって、入力を元に判定すれば操作できるようになるのではないかと考え、少しつまづきましたが成功しました。

以下がサンプルコードです。適当に持って行ってください。

/// @ 0.16.0
var kbtext = ""
var x = 0
var y = 0
Ui:render([
    Ui:C:container({
        align: "center"
        children: [
            Ui:C:mfm({ text: `:blank:{Str:lf}:blank:{Str:lf}:blank:{Str:lf}$[position.x={x},y={y} $[bg.color=fff :blank:]]{Str:lf}:blank:{Str:lf}:blank:{Str:lf}:blank:{Str:lf}` }, "disp")
        ]
    })
    Ui:C:textInput({
        onInput: @(text){
            if (kbtext != text) {
                kbtext = text
                // 入力判定
                if ( kbtext.index_of("w") != -1 ) {
                    y = y - 0.5
                }
                if ( kbtext.index_of("s") != -1 ) {
                    y = y + 0.5
                }
                if ( kbtext.index_of("a") != -1 ) {
                    x = x - 0.5
                }
                if ( kbtext.index_of("d") != -1 ) {
                    x = x + 0.5
                }
                Ui:get("disp").update({ text: `:blank:{Str:lf}:blank:{Str:lf}:blank:{Str:lf}$[position.x={x},y={y} $[bg.color=fff :blank:]]{Str:lf}:blank:{Str:lf}:blank:{Str:lf}:blank:{Str:lf}` })
                // リセット関数を呼ぶ
                onInput()
            }
            
        },
        default: ""
        label: "キーボードインプット"
        caption: "WASD=移動"
    }, "kbinput")
])
@onInput() {
    // 一度" "に置き換えてから、""に戻す(そうしないと何故か書き換えできなかった)
    if ( kbtext != "" ) {
        Ui:get("kbinput").update({
            default: " "
        })
        Ui:get("kbinput").update({
            default: ""
        })
        kbtext = ""
    }
}

パフォーマンス

このゲームのパフォーマンスは最初の頃に比べて改善しましたが、それでもまだ良いとは言えないと思っています。

こういうタイプのゲームをAiScriptで作るとなるとfor処理を使うことになります。
このfor処理が結構重くて、使い所と長さに気をつけないとパフォーマンスが落ちる原因になってしまいます。

VOSKRISの初期では全ての行に対してforを回してましたが、アップデートでこれを必要な場所(ミノが置かれた高さ辺り)のみに絞ってforを回すようにしたり、
ミノの情報を二次元配列ではなく相対位置のみ書いた配列に最小化したり、forの回数を頑張って抑えています。その結果判定が緩くなったりもしてますが…。

また、置き換え可能な場所は.map()や.filter()などのネイティブ関数に置き換えてしまうことも重要です。
ただし、mapは順番の保証性がないことに注意して使わなければなりません。

ちなみに最高速は結構抑えていますが、これはプレイヤー操作でできる限り不一致を起こさないためのものです。
ティックスピードを上げてタイマーを開放すると初代TGMの230くらいまで行きますが、移動操作が結構狂います。

のびのびさーよさよ

小夜と和解せよ。小夜を伸縮せよ。よろしくお願いします。
小夜と和解しながら小夜を伸ばしましょう。納得行く高さまで行って完成にするか、もっと高みを目指すかはあなた次第です…。

コンセプト

「つむつむきりんたん」のパロディです。見た目も動作も似てますが、一応自分で書き上げてます。
伸びる小夜の絵文字を見て「これで積み上げ系を作れ」と言わんばかりの適合性を感じたので作りました。

追加要素として「和解」があります。通常積み上げ系の成功率は積み上げていくと下がっていくものですが、
「和解する」をクリックして成功率を回復することができます。

しかしリスクも有り、和解は連続で行うごとに和解成功率が下がっていきますし、
和解に失敗した場合に少し伸び進捗を失う可能性もあります。

積み上げ系のPlayには「完成」の要素が大きくゲーム性を作っています。
今の進捗で完成にすればこのままの姿で投稿することができますが、より積み上げていくなら失敗して全てを台無しにするリスクがあります。

特にのびさよは「崩れる」のではなく「縮む」ので、失敗すれば残るものが数字のみになってしまいます。
このあたりが積み上げ系の面白い所ですね。

実はこのPlayは普通に動作するリトライ機能があったのですが、これはランダム系と同じく「作業」になりかねないのでコメントアウトされています。

アドバイス

  • 和解は連続で行うごとに成功率が下がります
    伸ばすと和解回数の累積が少し減ります
  • さよはかしこいです
  • さよはいます。よろしくお願いします
  • さよはいます。よろしくお願いします
  • さよはいます。よろしくお願いします
  • さよはいます。よろしくお願いします
  • さよはいます。よろしくお願いします

ぼ廃スコアチェッカー

お客様の 「ぼ民寿命」は あと 7日… 6日… 5 days… 4 days… 3 days… 2 days… 1 day…
The time limit of your vomin life is the… 3 minutes… 2 minutes… 1 minute… 5 4 3 2 1 0… The start of your vohai life.

コンセプト

Misskeyの統計APIを元にした診断Play。

データの収集が100日前まで、そして累計値ではなく平均を見てスコアを計算していますが、
これは最近どれくらいぼすきーに浸っているかの指標として機能したかったことが理由です。実際自分は二段階ほど降格しています。

また、ファイル付きノートやリノート率も同時に計測し、それらを元に最終的な「肩書き」を出力します。
累計のリアクション数やノート数も確認できます。それでも360日前までです。

今後やりたいことは…新規詐欺でスコアに+5やるとか?このアドカレ書き終えたらやろうかな…。

ぼすきーのPlayは興味が引かれやすい物の場合、瞬間速が非常に速いのでPlayがAPIを呼ぶ場合は注意が必要ですね。
繰り返して呼ぶ場合は呼び出しごとに遅延を設けるとか。
このPlayは現状4回なのでそこまで問題ないだろうと思って設けていませんが。

新規詐欺の検知はnotes/searchでユーザー限定して3回ほど呼ぶ形でやろうと思ってます。ユーザー限定検索なら通常のノート検索より重くないはず…。
ただし、OR検索ができなさそうな点には注意。5ワードでORしたいなら5回分呼ぶ必要があります。

アドバイス

  • ぼ廃になりたくないなら毎日のノートやリアクション頻度を下げましょう
  • ぼ廃になりたいなら毎日いっぱいノートしましょう

これからのPlay

ネタ切れ気味と言おうとしたが作りたいものはまだ3個くらいある。
ぼすきー内に限らず色々な場所に展開してみたいですが、これ以上Fediverseに休眠アカウントを増やすわけにもいきません…。

これからPlayを作る人へ

最後のセクションに行く前に、これからPlayを作ろうと考えている人へのアドバイスを添えておきます。

まずはプログラミングの概念を学びましょう。動作をアルゴリズム化する能力は絶対に身につけておくと良いです。
変数や条件分岐などの基本知識を理解することも重要ですが…

AiScriptは基本的によほど変なことをしない限り思った動作を形にしやすく、
書き出す中で考えることがあまり多くないので初心者にも優しい言語になっています。

ゲーム系のPlayはもちろん「ゲーム性」が重要です。「桜井政博のゲーム作るには」とか見たらいいと思います。
リスクとリターンや、難しさと一般性の関係などをわかりやすく説明してくれています。

おわりに

PlayのAiScriptは作りたいものをすぐ形にできる良いシステムです。開発に興味があるなら、以下のドキュメントや記事が参考になります。
自分の思い通りのPlayやプラグインを作りたいなら、ぜひ読んでみてください。

ちなみにぼすきーのPlay農家集計では一位(17個)だったようです。どうしてこんなに作ってしまったんだろう…
https://voskey.icalo.net/notes/9kzfm5a8li

MisskeyとAiScriptを開発したしゅいろさん、そしてぼすきーを運営しているイカロさん、
そしてここまで作ったPlayを遊んでくださっているぼすきーユーザーのみなさんに感謝します。

それでは、ここまで読んでいただき本当にありがとうございました!明日はnvsoftsさんの記事です。