【ブラウザゲーム】Fishing Days

操作方法

(PC・スマホ両対応)

クリック or タップ:浮きを投げる。浮きを引っ張る。

遊び方

浮きを投げて、魚を待ちます。浮きをよーく観察して、魚が来ていそうなら浮きを引っ張ります。魚が引っかかったら連打で岸まで寄せます。

【javascript】async、awaitの正確な仕様を理解する

前提知識

Microtask queueMacrotask queueー非同期処理が格納されるキュー。setTimeoutはMacrotask、Promiseのコールバック関数はMicrotask queueである。

・処理の優先順位は、コールスタックに入れられた関数>Microtask queue>Macrotask queue。コールスタックが空になったとき、Microtask queueが呼ばれ、その両方が空になったとき、Macrotask queueが呼ばれる。

・要するに、大まかに言うと、処理の順番は、「非同期じゃないコード」→「Microtask queueに入れられた関数(キュー入れられた順)」→「Macrotask queueに入れられた関数(キューに入れられた順)」である。(途中で優先度の高い処理が追加されると前に戻る)

仕様① awaitが読み込まれたときの挙動

・awaitの行に差し掛かった時、awaitの右に書かれた処理を実行した後、async関数を中断し、async関数を「Microtask queue」に入れる。

以下はコード例。

async function hello()
{
    console.log("hello")
    var b = await 1
    console.log("hello"+b)
}

console.log("start")
hello()
console.log("end")

上を実行すると、start→hello→end→hello1の順でログが出力される。

仕様② async関数の返り値

・async関数の返り値Promiseオブジェクト。

・async内の処理が終わっていない場合は、stateが「Pending」であるPromiseを返す。

・async内の処理が終わっている場合は、stateが「Fulfilled」であるPromiseを返す。

returnは関数の返り値とはならない。返り値は常にPromiseオブジェクトで、その中にreturnの結果が保存される。

以下はコード例。

async function hello()
{
    var b = await 1
    console.log("resume!")
    return b
}

a = hello()
console.log(a)

window.setTimeout(() => {
    console.log(a)
}, 1000)

上を実行すると、stateが「Pending」であるPromiseオブジェクトが出力された後、「resume!」が出力され、1秒後にstateが「Fulfilled」であるPromiseオブジェクトが出力される。

仕様③ 実行順序に気を付ける

・実は、仕様②で書いたコードのsetTimeoutの秒数を0にしても、結果は同じです。

・何故なら、「Microtask queue」→「Macrotask queue」の順に実行されるからです。

【ブラウザゲーム】ローグサバイバー

操作方法

A/W/D/Sー移動

R/Spaceーリセット(ゲームオーバー時)

・落ちている剣に近づくと武器を交換できます。

・右上のアイコンは剣に付与された追加効果を表します。

ゲームの概要

ヴァンサバライク+ローグ要素。

ただ生き残るゲームです。ゴール的なものはありません。

【javascript】厄介なビット演算の仕様を完全に理解する

導入

かなり厄介なので、正確な動きをかいておきます。

前提知識

・javascriptでは、数の型はNumberのみ。整数も小数も関係なくNumber型。

・Number型は、倍精度浮動点小数点数型であり、IEEE 754という規格が使われている。

・C#でいえばdouble。

・Number型の整数の精度は53bit。つまり、-2**53+1以上2**53-1以下の整数は正確に表記できる。(例えば、2*53+1のbit表記は2**53と同じ。)

・int32のbit表記には、補数表現が使われている。

javascriptのビット演算

ビット演算を行うときのみ、int32として扱われる。(>>>を含む計算は、uint32として計算されます。)

ビット演算が終了すると、int32(またはuint32)から通常のNumber型に戻る

Number→intへの変換

①小数の場合は、近い整数に変換される。

②整数x を ((x % 2**32)+2**32) % 2**32に変換する。簡単に言えば、mod 2**32において等しい、0以上2**32-1の整数に変換する。

③その整数の2進数表記をint32とみなす。(演算が>>>の場合はuint32としてみなす。)

int32→Numberへの変換

・int32における整数nの値は、そのまま、Number型におけるnに変換される。

(bit表記は変わるが、数の世界では変化なし。)

整理

・>>>が含まれない場合、-2**31以上2**31-1以下の整数は、ビット演算の前と後では変化しない。

・-2**31-1以下の場合や2**31以上の整数の場合、値は変化する。

整数の精度に注意

-2**53以下の整数と2**53以上の整数を扱う場合、意図していない値になるので注意。

例えば、(2**53+1)^0は、上の変換通りに計算すると、変換後は1になりそうですが、実際は0になります。これは、2**53+1のbit表記が2**53である為です。

・-2*31^0は、-2**31 mod 2**32 = 2**31より2**31に変換され、これを2進数を表すと、100…00(32bit)になる。これをint32で解釈すると-2**31となるので、計算終了後はNumber型の-2**31になる。

・2**31^0は2進数で表すと、100…00(32bit)になる。これをint32で解釈すると、-2**31となるので、計算終了後はNumber型の-2**31になる。

・-2**31^0は、-2**31-1 mod 2**32=2**31-1より2**31-1に変換され、これを2進数で表すと、01111…1111(32bit)になる。これをint32で解釈すると、2**31-1となるので、計算終了後はNumber型の2**31-1になる。

・-1^0は、2**32-1に変換され、これを2進数で表すと、111…111(32bit)になる。これをint32で解釈すると、-1となるので、計算終了後はNumber型の-1になる。

・(2**31^0) + (2**31^0)は、-2**32になる。

参考文献

↓ かなり分かりやすいjavascriptの数値型に関する記事です。

https://qiita.com/uhyo/items/f9abb94bcc0374d7ed23

【javascript】サイト上で音を鳴らす方法【2024年】

導入

2018年、Googleのポリシーが変更になり、javascriptでただAudioオブジェクトを作って、play()を呼ぶだけでは、音を鳴らすことができなくなりました。

様々なサイトで、すでに無効になった上記の方法が紹介されていて、2018年以降の方法を紹介しているサイトがあまりなかったので、ここに方法を書いておきます。

方法

①ユーザーにサイトをクリックをさせる。

②AudioContextオブジェクトのresume()を呼ぶ。

③その後に音を鳴らす。

(①と②は逆でも良いです。)

コード例

const atx = new AudioContext();
atx.resume();
const snd = new Audio("オーディオファイルへのパス.mp3");
document.addEventListener("click",() => {
     snd.currentTime = 0;
     snd.play();
})

上のコードは、クリックすると音が鳴るコードです。

currentTime = 0は、再生場所を設定しています。再生場所を元に戻さないと、連続再生しても、前の音が終わるまで次の音が鳴りません。

また、AudioContextのstateというプロパティで「音声が許可されたかどうか」が確認できます。stateが「running」の場合は許可あり、「suspended」の場合は許可なし、です。

ブラウザゲームを作る場合は、ミュートをデフォルトの設定するか、クリックするとゲームが始まるように設計すると良さそうです。

参考文献

https://developer.chrome.com/blog/autoplay

【ゲーム制作】敵AIを数学的理論を基に作成する① ~UCB1方策~

導入

スマブラなどの格闘ゲームからオセロなどの戦略ゲームに至るまで、敵のAIが必要になります。今回は、数学的に性能が保証された戦略AIを作成する方法を紹介します。

今回は、「UCB1方策」というものを紹介します。

UCB1方策の概要

UCB1方策というのは、多腕バンディット問題に対する戦略の一つです。

多腕バンディット問題とは、複数の「報酬の期待値が分かっていない」選択肢があるときに、報酬を最大化する選択肢の選び方を考える問題です。

多腕バンディット問題の例は、「勝率が不明な複数のスロットがあって、金貨をどういう風に入れたら報酬を最大化できるか」という問題です。

UCB1方策の具体的な手順

①まず、全ての選択肢を1回ずつ試します。

②その後、UCBスコアが最大になる選択肢を試します。(UCBスコア=μ+c√(ln(N)/n))

(μ:その選択肢の現時点の勝率。c:定数。理論上は√2だが問題によって調整する必要がある。小さくするほど現時点の勝率を重要視し、大きくするほど探索を重要視する。N:全ての選択師の探索回数の合計、n:その選択肢の探索回数。)

(※lnは自然対数で、logの底がネイピア数eであるものです。)

③手順②を繰り返します。

UCB1方策が戦略ゲームのAIに使える理由

戦略ゲームはモンテカルロ法を使うことにより「多腕バンディット問題」とみなすことができるからです。

モンテカルロ法とは、乱数を使って特定の値を推定する方法です。ランダムに点を打って、円の面積を推定するのもモンテカルロ法です。

ある選択肢を選んだとき、「それ以降の選択肢はどちらかが勝つまで完全にランダムに選び、勝敗を決める」とします。

1つの選択を選ぶと、ある特定の確率で「勝利」という報酬を得られるので、これは多腕バンディット問題になっています。

「選択肢を試す=1$失う」、「勝利=10$貰う」などと考えると分かりやすいです。

UCB1方策の実現例

RPGゲームの戦闘で、ドラゴンAIに最適な行動を選ばせる場合を考えます。

選択肢は、「A:殴る」「B:火を吐く」「C:力を溜める」です。

まずは手順①。Aを選ぶと結果は「敗北」。B、Cは「勝利」でした。

現在のUCBスコアは、Aは0、B、Cは1。

続いて手順②。定数c=√2とします。Bを選ぶと結果は「敗北」でした。

現在のUCBスコアは、Aは√(2ln2)≒1.18、Bは1/2+√(ln2)≒1.33、Cは1+√(2ln2)≒2.18

再度②。Cを選ぶと結果は「勝利」でした。

現在のUCBスコアは、Aは√(2ln3)≒1.48、Bは1/2+√(ln3)≒1.55、Cは1+√(ln3)≒2.05.

再度②。Cを選ぶと結果は「敗北」でした。

現在のUCBスコアは、Aは√(2ln4)≒1.67、Bは1/2+√(ln4)≒1.68、Cは2/3+√(2ln4/3)≒1.63

再度②。Bを選ぶと結果は「敗北」でした。

(本来は何十回、何百回とする必要がありますが、今回はここで終了します。)

現在、最も勝率が高い選択肢はCなので、ドラゴンはCを選択する。

問題点

UCB1方策は、原始モンテカルロ法を少し改良した程度の戦略です。なので、まだまだ全然弱いです。

※今回は、このUCB1方策に探索木を組み合わせたUCTという強力な方策を紹介する準備として紹介しました。

数学的理解

UCB1方策の手順②の式を導出したい人向け。

↓ 研究者の方が書かれている記事です

多腕バンディット問題におけるUCB方策を理解する · THINKING MEGANE (monochromegane.com)

↓ 多腕バンディットに使われる統計学の定理の紹介

参考文献

https://www.kurims.kyoto-u.ac.jp/~kyodo/kokyuroku/contents/pdf/1894-14.pdf

https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%B3%E3%83%86%E3%82%AB%E3%83%AB%E3%83%AD%E6%9C%A8%E6%8E%A2%E7%B4%A2

https://ja.wikipedia.org/wiki/%E5%A4%9A%E8%85%95%E3%83%90%E3%83%B3%E3%83%87%E3%82%A3%E3%83%83%E3%83%88%E5%95%8F%E9%A1%8C

【永久保存版】正規表現ライブラリ 全関数 まとめ

導入

pythonの正規表現ライブラリreの関数を簡略化してまとめたものです。

オブジェクト

正規表現オブジェクト:正規表現が格納されたオブジェクト。

マッチオブジェクト:一部の関数により返されるオブジェクト。マッチした際の情報が格納されている。

関数

patternは「正規表現」、textは「対象となる文章」が入ります。

replacedは「マッチした部分の置き換え後の文字列」が入ります。

関数名(引数)説明
re.match(pattern, text)文頭がマッチするか判定する。マッチした場合、マッチオブジェクトを返す。それ以外の場合、Noneを返す。
re.fullmatch(pattern, text)文全体がマッチするか判定する。マッチした場合、マッチオブジェクトを返す。それ以外の場合、Noneを返す。
re.search(pattern, text)一番最初にマッチした部分の情報をマッチオブジェクトとして返す。それ以外の場合、Noneを返す。
re.findall(pattern, text)マッチした文字列をリストとして返す。丸括弧がある場合、囲われた部分のみを返す。丸括弧が複数ある場合、囲われた部分を分けて、タプルのリストとして返す。
re.finditer(pattern, text)マッチした部分の情報をマッチオブジェクトのイテレータとして返す。
re.sub(pattern, replaced, text)マッチした部分をreplacedで置き換え、「置き換え後のtext」を返す。
re.subn(pattern, replaced, text)マッチした部分をreplacedで置き換え、「置き換え後のtext」と「置き換えた回数」をタプルとして返す。

【Godot 4.0】マルチプレイヤー実装に引っかかったら

導入

自分で色々試した上で、バグの原因となる場所を全てまとめました。

マルチプレイヤーの実装

そもそもマルチプレイヤーの実装の仕方が分からないという場合は、こちらの3分半の動画を見てみて下さい。コードを真似れば実装自体はできます。

基礎知識

公式ドキュメントで説明が不足している3つの概念について解説します。

MultiplayerSpawner:指定したPackedScene(AutoSpawnListに含まれるPackedScene)がサーバー側の指定した階層上(SpawnPath上)にあるとき、それを全てクライアント側に複製し、サーバー側で削除されたら、クライアント側でも削除する、というノードです。簡単にいえば、サーバー側の特定の階層をクライアント側にコピーするノードです。MultiplayerSynchronizerが変数をシンクロさせるなら、こっちはPackedSceneのインスタンスの存在をシンクロさせるノードと言えます。ただし、複製されるのはname(階層名)のみで、中身までコピーしているわけではありません。単に、サーバー側の動向に従い、クライアント側で自動でシーンをインスタンス化して階層に入れてくれるだけです。

MultiplayerSynchronizer:指定した変数のみ、他の全てのPeerの変数の値を、自分側の変数の値で上書きします。MultiplayerSynchronizerのauthorityが自分のPeerのidと一致している人だけが、他のPeerにある変数を上書きできます。つまり、MultiplayerSynchronizerのauthorityに設定されたPeerのみが複製権限を持ちます。

Authority:ノードが持つ変数。MultiplayerSynchronizerによって使用されます。set_multiplayer_authority()、get_multiplayer_authority()によって、設定、取得ができます。デフォルトは1です(Serverに割り振られるPeerのidと同じ)。authorityを変更すると、子ノードのauthorityも変更されます。上の動画でPlayerのauthorityを変更するのは、子ノードにあるMultiplayerSynchronizerのauthorityを変更する為です。

原因① 階層名が同じ

階層名が同じであるとバグがおきます。GodotはUnityと違い、階層名を一意にする必要があります。正確にいえば、通信機能を使わない場合は、階層名を一意にしなくても問題ないですが、Godotの通信機能は「同階層名が存在しない」という前提で設計されているので、同階層名が2つ以上あると、MultiplayerSpawnerが正しく機能せず、クライアント側でエラーが起きてしまいます。

例えば、nameが「Player」であるPackedSceneをnameを変更しないまま2つ作成して階層に入れると、ホスト側では、「on_spawn_receive: Method/function failed. Returning: ERR_INVALID_DATA」、クライアント側では、「Unable to auto-spawn node with reserved name. Make sure to add your replicated scenes via ‘add_child(node, true)’ to produce valid name.」などのエラーが発生します。

対処方法は、同じ名前の階層を2つ作らないことです。PackedSceneを追加する際には階層名(name)を変更してあげましょう。

また、「add_childの第二変数をtrueにして!」というエラーが表示されますが、それをしたところで変わらずエラーが起きます。

原因② Authorityの問題

MultiplayerSynchronizerのAuthorityが自分のPeerのidに設定されていない場合、値の複製権限が別のPeerにあるので、自分側で値を変更しても、何も変わりません。

対処方法は、複製されるインスタンスの_enter_tree()で、set_multiplayer_authority(<自分のpeerのid>)をすることです。別のPeerでも呼ばれる場所なら、どこに書いても問題ありません。

自分のPeer側でしかset_multiplayer_authority()が呼ばれない場合、原因③のように、別のPeerでは正しくauthorityが設定されません。

原因③ 相手のPeer側で値の設定が行われていない

「サーバー側でPackedSceneをインスタンス化し、様々な変数を設定して階層に入れる。」というコードがあるとします。これでは意味がありません。クライアント側では、MultiplayerSpawnerによって、変数の変更されていない新しいインスタンスが作られるだけです。

MultiplayerSpawnerは、指定したPackedSceneがサーバー側で作成されたときに、クライアント側で、指定したPackedSceneをインスタンス化して階層に追加するノードです。このとき引き継がれるのはname(階層名)だけなので、中身は別途追加してあげる必要があります。

対処方法は、①nameに必要な情報を書き込み、_enter_tree()の際にnameから必要な情報を読み取る。②MultiplayerSpawnerが複製を行う際に出されるspawned(node: Node)というシグナルと変数設定用の関数を繋げる。③全てのMultiplayerSynchronizerの権限をサーバー側に持たせ、変数がシンクロした後に権限をクライアント側に渡す。

【Godot 4.0】関数を次フレームに呼び出す方法

導入

関数を次フレームに呼び出す方法として、call_deferredがよく挙げられますが、誤った方法です。海外のコミュニティでも、「call_deferredを使えば良いよ!」という声しか無かったので、その誤解を解く為にこの記事を書きました。

方法

await get_tree().create_timer(0).timeout

を使う。このコードの意味は「シーンツリー上に0秒のタイマーを作成して、そのタイマーが切れるまで待つ」です。次フレームまで処理が中断されるので、このコードの後ろに実行したい関数をおけば良いです。

「0秒タイマー」によって丁度次フレームまで処理を中断させることができる理由は、タイマーの処理が各フレームの最初(自作スクリプトが実行される前)に行われるからです。

call_deferredのよくある誤解

call_deferredは「後で関数を呼ぶ」関数です。しかし、call_deferredによって指定された関数は次フレームに行く前に呼ばれてしまいます。例えば、あるフレームでcall_deferredを使って関数を呼んだ場合、同じフレームの_processの前に関数が呼ばれてしまうことがあります。

海外コミュニティにcall_deferredについての正確な動作を解説したポストがあります。

ここからかいつまんで説明すると、以下のようになります。

call_deferredはスタックに関数を入れますが、そのスタックの関数が実行されるタイミングは1フレームで複数回あります。コードの終わりなどにもそのタイミングがあるので、指定した関数が現フレームで呼ばれてしまいます。

Comment
byu/belzecue from discussion
ingodot