2025年9月7日日曜日

LOMの全イベント管理シミュレーターを作った話

LOM全イベントの完全管理 

前回構想だけ話しましたが結局作りました
LOMでは全68イベントが存在します。

実の所イベントだけなら(固定の初期範囲と初期位置)だけならそこまで複雑になりません。
所がシミュレーションを好きなランドメイクをしつつイベントも好きな様に組むとなると爆発的に難しくなります。

個々の仕組みはそんなに難しくないのですがこれが組み合わさってそれをシステムに落とすとなると難易度がヤバい(語彙力低下)。
でももっと深く遊ぶのにランドメイクのシミュレーションだけではイベントを別途メモ帳とか別の場所で管理する必要があって面倒臭い。
そのためページ1つで完結させたくなった。

ファイル構成を考える

今回は処理とデータを分離しました。
とは言え、DBの使えるサーバーではないのでDB用JSを作ってそれをインポートで引っ張ります。
仕様が変更されることが無いのでシステム的な拡張性を考慮する必要がないので名前付きDBのような仕組みは不要で、欲しいのは配列の塊だけです。

今回は普通のjavascriptじゃなくてmjsで作成しました。
拡張子をmjsにしようと思ったら、サーバーが対応してないという根本的な問題があった(サーバー側がファイル形式をホワイトリストで処理してるようでアクセスが出来なくなる)のですがそもそもmjsは拡張子jsでも動くので結局拡張子はjsにする事になりました。

さておき、構成としては
・メイン処理JS
・DOM用オリジナルラッパーJS
・DB-JS
この3つ。
検索時に静的に埋め込まれていた方が良いかなという視点でAFとイベント名のリストはHTML側に記述しています。管理面を考えると動的に生成した方が楽なんだけどSEO的にはどっちが良いのか未だ分からない。最近のクローラーは動的な内容もある程度判定するようなんだけどベタ記述されてれば確実に読み込みはするはず。

操作部分を静的にするとデメリットもあって、描画を待たないといけない。
対処方法は3つ
・body下でjavascriptを読み込ませる
・window.onloadで発火させる
・DOMContentLoaded で発火させる

body下の場合htmlは上から順に処理されるのでボタンやリストのテーブルなどの描画が終わった後にjavascriptが実行されます(必要最低限の描画直後で最速だが、場合によっては描画が変な遅れ方するとエラーする可能性はある)。

onloadはスクリプト含めた描画に必要な全てを読み込んだ後に実行されます(一番確実だが一番遅い)

DOMContentLoaded はHTML の文書が完全に読み込まれ構文解析され、すべての遅延スクリプト(deferの遅延読み込み、module読み込み)がダウンロードされ、実行されたときに発火。
※画像、サブフレーム、非同期スクリプトは待たない。

DOM用オリジナルラッパーJSは過去に紹介しているbscラッパーです。
elementをラップして短縮記述出来ます。
例えば、document.getElementById()は$i()に圧縮出来ます。

処理を考える

ランドメイクのアンドゥをする際に前回のものは全工程を再処理してたので遅いのが少し不満だったため今回は各工程をスナップショットする様に変更。これによって今回はアンドゥがだいぶ高速化出来ています。

1.初期化

■初期値1(初回ロード)
・マップカラー、全マップ、初期マップインデックスを設定。
・イベントリスナーを設定。
・ログを初期化し、アーティファクト、6x6エリア、6x6マップをセットアップ。
・URLにクエリがある場合、保存状態を復元し、ない場合はデフォルト。

全体のマップの色や1度だけの処理部分。初期化2も呼ぶ。

■初期化2(随時リセット)
・イベントを初期化(非表示、無効、チェック解除)。
・イベント状態を0で初期化(68要素)。
・保存スナップショットを初期化(可変)。
・アーティファクトを初期化(26要素)。
・6x6エリアのマナを初期化し、初期マナ値を設定。
・ログに初期マップインデックスを記録。

エリアの変更等で呼ばれる
リセット用の初期化

2.操作

順序的には
マップ処理→配置処理→イベント処理
マップが無ければランドが配置できずランドが無ければイベントをする場所がない
大枠はこの3工程をループ

AFには基本イベントがある。
街以外はランドロックの解除が必要なので基本1つはイベントがある。
AF配置でも、イベント終了でもイベントが解除される

■初期操作

・25x25の全体マップから初期エリアの選択→6x6エリア更新
・ポスト取得
・ポストの配置(ポストだけは陸なら何処でも置けるため専用の配置)
・ドミナの取得(以降隣接配置に変わる)

■AF

・アーティファクトを選択し、配置可能なエリアを表示。
・指定されたAFを配置可能な6x6エリアを計算し、ハイライト。
・キャンセル時はハイライトとAFを戻す

・配置→位置の記憶→マナ計算(周囲4マスの変更)→イベント解放
・町の場合無条件でランドロック解除

■イベント

・イベントの開始
・まいごのプリンセス等は開始後にAFを取得
・イベントの終了
・ランドロック解除
・次イベント解放

■例外

・果樹園の60個採取は単独判定
・丙子椒林剣をサブイベント処理

■失敗・消滅
・失敗はその時点でゲーム内でも失敗が表示される
・消滅は条件が満たせなかった場合で結果的に発生しない

■ステップ

・ステップが変動するたびに処理
・各変数を全て保存
・リドゥ、アンドゥで変数を復元
・ステップ数上限を超える場合は変数保存、以内であれば復元
・状態をリアルタイムで復元用url生成

■その他描画関係

シミュレーションに関係ない部分の描画
・リストの表示切替等
・全体マップの表示切替等
・復元urlの𝕏でのポスト機能

3.処理の関数化

実処理はもう少し細分化。関数ベース。

関数名 処理内容
setdata ページロード時の初期化。マップカラー、全マップ、初期マップインデックス(gmi=97)を設定。リスナーを設定し、ログ、AF、6x6エリア/マップを初期化。URLクエリがあれば状態を復元、なければlms()を呼び出す。
setstartaf AF、イベント、マナ、ログの初期状態を設定。イベントを非表示/無効化、gevsを0で初期化、AF配列(gafo, gafc, gafs)を初期化、6x6エリアのマナ(l36m, l36a)を設定、ログに初期マップを記録。
set6x6area 6x6マップの表示位置を調整。マップインデックスから座標を取得し、box6x6のleft/topをピクセル単位で設定。
set6x6map 6x6マップを表示。マップカラーと6x6マップデータを取得し、各セル(box65)の背景色を設定。
get6x6map 6x6マップデータを生成。有効ポイントとフルマップから6x6領域を切り出し、山データを適用、座標ラベルを設定してデータを返す。
set6x6coordinates 6x6マップの座標ラベル(行:数値、列:アルファベット)を設定。インデックスを座標に変換して表示。
m6x6del 6x6エリアとマナ表示をクリア。area36とmana36のinnerHTMLを空にする。
get6x6mana 6x6マップのマナデータを取得。有効ポイントとマナマップから6x6領域のマナを抽出。
setfullmap 25x25の全体マップを表示。マップカラーとフルマップデータを用いて各セル(box25)の背景色を設定。
highlightmap 25x25マップにハイライトを適用。ハイライト用マップデータで背景色を更新。
boxclick 25x25マップのセルクリック処理。クリックされたインデックスが有効ポイントに含まれる場合、setmapを呼び出してマップを切り替え。
setmap 指定されたマップインデックスに切り替え。mapselectを更新し、6x6座標を設定後、lms()を呼び出す。
mapmove マップを上下左右に移動。方向(左/右/上/下)に応じてインデックスを変更し、lms()を呼び出す。
choiceaf AFを選択し、配置可能なエリアを表示。選択したAFのボタンを非表示、情報を表示、配置可能エリアをハイライト。
availableareas AFを配置可能な6x6エリアを計算。地形条件と隣接AFの状態を基に、配置可能なセルを透明度0.5でハイライト。
m6x6p100 6x6エリアの透明度をリセット(1.0に設定)。
afcancel AF選択をキャンセル。AF情報/ステータスを非表示、AFボタンを再表示、透明度をリセット。
setaf 6x6マップの指定位置にAFを配置。配置可能かチェックし、ログ、AF状態、エリア、マナを更新。必要に応じてAF解放や自動アンロックを実行し、lms()を呼び出す。
setafdirect 指定されたAFを指定位置に直接配置。UI操作を介さず、setafと同様の処理を行い、lms()を呼び出す。
getbuildingblocks AF「ビルディングブロック」を解放。gafs[1]=1を設定し、ボタンを表示。
checkmana AF配置時のマナを計算。隣接セルのマナを加算し、上限3に制限。隣接セルのマナも更新。
defaultunlock 特定ランド(街など)を自動アンロック。固定リストに基づき、gafs[n]=3を設定。
autounlock ランドのアンロックイベントを自動終了。アンロックリストからイベント番号を取得し、eventclearを呼び出す。
getaf 指定されたAFを解放。未解放の場合、gafs[n]=1を設定し、ボタンを表示。
manazero 特定ランド(Lucemia)のマナをゼロにリセット。l36mを更新し、UIに反映。
eventstart イベントを開始。gevs[n]=2を設定、UIを更新(チェックボックス有効、背景白)。競合イベントをチェックし、ログを記録、lms()を呼び出す。
eventclear イベントをクリア。開始状態を確認し、gevs[n]=3を設定、UIを更新(背景緑)。関連AFやアンロックイベントを実行、ログを記録、lms()を呼び出す。
subevent1 サブイベント(果実60個)を処理。チェックボックスを更新、AFを解放、ログを記録、lms()を呼び出す。
subevent2 サブイベント(武器「丙子椒林剣」)を処理。チェックボックスを更新、ログを記録、lms()を呼び出す。
eventcheck イベントの解放条件をチェック。AF、マナ、イベント状態を基に開始可能イベントを判定、UIを更新。進行不能イベントを失敗状態(gevs=4)に設定。
eventdisplay イベントリストの表示を制御。ラジオボタンの選択に応じて、開始済み/進行中/全イベントを表示/非表示。サブイベントの表示も制御。
relogw ログを圧縮(sX-eXをwXに変換)。glog[0]を圧縮し、glog[1]に保存。
compressLog ログ文字列を圧縮。sX-eXをwXに置換。
logconvert ログをHTML形式に変換。AF配置、イベント開始/終了、サブイベントをリスト形式で表示。最後のログをlaststepに表示。
log2txt ログエントリをテキストに変換。AF配置、イベント、サブイベントの内容を文字列化。
savestep 状態を保存し、URLを更新。ログと履歴を比較し、スナップショットを保存、URLに圧縮ログを付加、ステップ数を更新。
savesnapshot 現在のゲーム状態をスナップショットとして保存。グローバル変数とUI状態を保存。
loadset URLや履歴から状態を復元。ログをパースし、AF配置、イベント、サブイベントを再現、lms()を呼び出す。
undo 1ステップ前の状態に復元。ログの最後のエントリを削除し、スナップショットを復元。
redo 1ステップ後の状態に復元。履歴の次のエントリを適用。
undoall 初期状態(ステップ1)に復元。スナップショット1を復元。
redoall 最新の状態に復元。履歴の最終ステップを復元。
restoreSnapshot 指定されたスナップショットを復元。グローバル変数とUI状態を復元し、lms()を呼び出す。
add1step 1ステップ分のログを適用。AF配置、イベント、サブイベントを再現。
makecheck チェックリストを更新。特定ランドのマナや配置状況をチェックし、条件達成(OK)を表示。ランドレベルを計算。
convertlandlevel ランドのレベルを計算。基準点(AF0)とのマンハッタン距離と配置数を加算。
mana2tag マナ配列をHTML形式(色付きスパン)に変換。マナ値に応じて背景色を設定。
setcolor4 マップカラーの説明を表示。マップカラーを取得し、color4要素に背景色を設定。
openmyurl 保存URLを新しいタブで開く。saveurlの値をwindow.openで開く。
urlpost 進行状況をXに投稿。チェックリストを基にテキストを生成し、xpostで投稿。
setlistener イベントリスナーを設定。マップクリック、AF選択、イベント操作、Undo/Redo、表示切り替えなどのリスナーを登録。
iddisplayswitch 指定された要素の表示/非表示を切り替え。displayスタイルをトグル。
arrayaddition マナ配列を要素ごとに加算。
arrayaddition3max マナ配列を加算し、上限3に制限。
index2coordinates インデックスを座標(行, 列)に変換。

こんな感じ。

0 件のコメント:

コメントを投稿