ラベル javascript の投稿を表示しています。 すべての投稿を表示
ラベル javascript の投稿を表示しています。 すべての投稿を表示

2026年7月1日水曜日

LOMの手持ちから可能範囲の上限攻撃力レシピをだす方法とか

LOMレシピ自動化してみようか?ってはなし

タイトル通りですが今回は制限強化の自動化をする場合どういう工程を考えるかなって話。 

条件とか

(武器鋭さx主原料鋭さ+武器重さx主原料重さ+武器腕力x主原料腕力+武器技巧x主原料技巧)÷128x {(成長抑制値+属性Lv合計) ÷ 成長抑制値}= 攻撃力

表記が長くなるので以降は基本武器側を大文字アルファベット・主原料側を小文字アルファベットで表します。

(Axa + Bxb + Cxc + Dxd)÷128x {(S+L) ÷ S}= 攻撃力

レジェンドオブマナにおける攻撃力計算は上記通り。

所持アイテムによって所持内で最も攻撃力が上がるパターンを提出する場合どういう処理をすれば良いか

組合せは主原料55種類・副原料120種類・各副原料は99個まで所持できる・投入順序性が必要・攻撃力最大時のレシピは保存しておく

なお、最大チャートは特定の副原料を100個以上使う場合もあるがあまりにも組み合わせが増えるので上限は99とする。

つまり所持数は「120x99=11880」

個の上限を99に制限しても組み合わせは「11880!/(99!)^120」。

更に、攻撃力が途中で伸びない場合「0~11880」の範囲で投入をやめるパターンがある。

既に肉の重複を抜いている状態かつ100以上アイテムを使うという想定を抜いて絞ってもこれなので総当たりは100%現実的じゃない。

なのでもう少し踏み込んでもっと上限を絞る必要がある。

どうすれば良いか

一旦思考停止するならBFを除くアルゴリズム

枝刈り、動的計画、分枝限定、貪欲→ビーム、山登り、焼きなまし、タブー(除外)、遺伝、粒子、蟻コロ、A*→IDA*


ただ、基本的に相性が悪い。

精霊を入れて抜けるまでレベルアップ判定があるので1回置きに切り取れず、積む検索なら数手先までをみてから枝を切り捨てる必要がある。

主原料特性もあってタブーの特定も大変だし、厳密性のある探査も厳しい。

例えば枝を切り落とすにしても鏡なしかつ材料が豊富というパターンもこのゲームの特性上多く

その場合に1レベル上げるのに下段ループの数十手が必要になり枝を切り落とすまでの範囲が広すぎる。

ビームにしても範囲が広すぎる。

じゃあ範囲を絞ろう

根本的に範囲を絞るべきであるのは確実でじゃあ範囲ってどれくらい絞るのか。

  • ベストパターンは共通化しても上限は600ステップ前後で強化上限に到達するのでそれより長いのは無駄
  • 鏡なしのパターンにしても最大でも1200ステップ前後なのでそれより長いのは無駄
  • 少なからず2000ステップを越えるようなパターンは全て打ち切るべき

上限探査を600~1500くらいまで絞ればまぁまぁアルゴリズムで絞れる気はします。

いやまぁ根本的に論理的に解けるパズルを総当たりするのが効率が悪いと言ったらそれはまぁそうですが。

なので更に上限を絞りましょう。

ベストパターンではそもそも100個を超えるパターンがあるのでアイテム豊富という状態を余り考慮しないで絞りましょう。

そもそもアイテムが充実してるという前提なら最適化した固定パターンを作ってあるのでそちらのほうが効率が良いです。

現実的条件は副原料の基礎エネルギーの範囲Lvを上限に処理を絞るくらいが丁度良いかと思います。

また、基本的に吸収の考慮もしません。

小ステップパターンにおいては吸収すると素材が減るわけで必要エネルギーも効率が悪いので。

上限は「9-9-9-9_9-9-9-9」が現実的です。

ここまで条件を絞ればかなり現実的になります。

エネルギーの上限は288と256です。

全てをアルゴリズムで解決させる前半の探査説明は将棋のルールを教えずに将棋として進めるパターンを探すみたいな無謀さでしたがやはりある程度はルールは流し込まないと良い結果が出ない。

副原料エネルギーを上限とするなら考慮ステップ範囲は8ステップ範囲です。

具体的には「輝きx4>補助SPx2>精霊>輝き」「輝きx4>補助SPx3>精霊金貨」これを最大パターンとします。

これくらいになればビームサーチなどの枝の切り落としがかなり効果的に機能するかと思います。

未来8ステップ以内にレベルアップしないパターンは全て枝を切り落としてOKとなります。

何故かというと本来デメリットの切り落とした枝の先にある更に良い未来の可能性は存在しない為です。

探査範囲についてもう少し言えば、8ステップxLv9x8属性=576ぐらいが上限としてベターかなと思います。
まぁ、低Lvは直ぐ上がるので実際には更に少ないステップで打ち切って良い。

Lv9が上限の基礎強化は150ステップ前後で強化上限になるのでなんなら200ステップくらいで打ち切っても良いと思っています。

評価方法を考えよう

さて、ここまで上限切りの話で、次に評価方法です。

最初に示した通り式は「(Axa + Bxb + Cxc + Dxd)÷128x {(S+L) ÷ S}」です。

これをはつまり「性能積和x倍率」という式です。


この2つのうち最善仕上げも公開済み通り補正最大パターンは固定です。

そして性能積和補正は属性Lv上昇より上昇率が高い。


基本システムでも説明していますが、属性Lvを少し上げるくらいなら僅か5ステップで終わるこれだけで良いです。

なので先に持ってる副原料で最も情報補正が高いアイテムを先に確保します。


次に1レベル毎上昇率を確定。端数は消えるので出来れば55程度の平均を生成。

攻撃力は初期値と係数Kで決まるので単純に伸びが良いだけだけではダメ。

例えば初期値はアダマンタイトが大きいがディオールなどは初期値が低くても係数で最終攻撃力が上回る。

逆に言えば比較時にABで初期値が低く係数も低ければその時点で除外出来ます。


Kの基本値が決まればあとは何レベル上げられるだけが問題になる

アイテムが充実なら基礎パターンを当てれば良いが抵抗値の必要Eは2倍になる関係上「ただ攻撃力を上げる」だけならばどれか属性1つを上げるより平均的にレベルを分配した方が良い。

更に言えば、未来性=再強化を可能にするかという視点もある。

基本システムで説明している通り、属性ロックがある為、もし未来性を残すなら平均分配よりも対抗属性は強化しない方が良い。


ただ、今回の目的はただ手持ちのアイテムでどれくらい強くできるかなのでそれは無視。

使い捨て前提で持ってる副原料で極力最大になる様にするのが恐らくベスト。


属性Lvに関係するのは素材一覧にまとめてある通りだけど、単なるスライドとエネルギーとしある程度は総当たりしてよい。

まとめ

  • ステップ上限は600で打ち切り
  • 上限を256・288までにして吸収を考慮しない
  • 仕上げの上昇率・1Lv毎上昇率と初期値が評価値になる
  • 属性レベルは平均的に上がるパターンを優先する
  • 攻撃力を直接比較するのではなく係数とLv合計

評価でなんで攻撃力をみないかというと性能基準値が一時的に低下して攻撃力が下がっている可能性がある。なので初期値と上昇率での評価が必要。

補足

・「今あるものだけで強化する需要」限定とする

・基礎強化パターンを目的に副原料を少し集めた方が最終的にたぶん効率が良いが目を瞑る。

・強化結果の補足としては仕上げ最適解の副原料をすすめる。各強化パターンの上限攻撃力を補足。

・シミュレーターに実装してある、無属性・補助なしALL5・基礎E・クイック999の手順のアイテム集める方が最終効率は良くなる

何で今まで作らなかったの・・・?

構想自体はまぁあるにはあったのですがそもそも今まで作ってなかったのはまず手持ち全アイテムを入力が面倒くさいという所。
次に本文でも書いてますが効率が悪いからで、普通に作成済みの手順のアイテムを集めた方が作る時ものちのプレイ効率も良いから。
根本的な話をすれば仕組みはパズルみたいなものなので総当たりなんて事をせずとも仕組みさえ理解してれば手持ちから普通に順序だてて最良パターン作れるから。
そしてそれらの理由から需要は低そうだなって思っていたから。

じゃあなんで纏めたのかと言えばどうやら需要があるかもしれないみたいなのを感じたので自分ならどんな感じに仕様を落とし込むかなって一旦メモ書きみたいな?


2026年6月18日木曜日

ESLintを入れてみた話

 タイトル通りです。デフォルトのハイライト機能というか未使用変数判定が出なくなって対処方法を探ったけど結局見つからず、なんかもっとちゃんとしたものがあると言う事で入れる事に。
Microsoft製なので最低限怪しい拡張機能のバグを踏むこともないかなと。

ESLintを入れよう


最初はただの便利なハイライト機能なのかと思ったらそういうモノではない。
node.jsがなんのライブラリなのかと思うくらい違う話。

インストールだけなら簡単いつも通り左の検索から拡張機能を選択してインストールするだけ。
ただし、そのままでは使えない。設定ファイルなどを作らなければならない。
まずは「Ctrl+@」で画面下にconsoleを出す。
実行カレントは作ってるコードの親あたり。実際の作業場所は1つ深く掘ってた方が良いかも。

「npm init @eslint/config」を実行

『pm : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Program Files\nodejs\npm.ps1 を読み込むことができません。詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=135170
) を参照してください。
発生場所 行:1 文字:1
+ npm init @eslint/config
+ ~~~
    + CategoryInfo          : セキュリティ エラー: (: ) []、PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess』

はい。ダメです!
単純にセキュリティで弾いてくれていますね。
「PS>」と表示されてる通りパワーシェルは許可しないと実行権限がありません。
なので一時的に緩和します。

Set-ExecutionPolicy RemoteSigned -Scope Process


コマンド意味
Set-ExecutionPolicyPowerShellのスクリプト実行制限(ポリシー)を変更する
RemoteSignedローカルのファイルは許可、ネットからのファイルは署名を必須にする
-Scope変更する「有効範囲(スコープ)」を指定する宣言
Process「今開いているこのターミナルの画面(プロセス)だけ」を対象にする

こんな感じ。そして先ほどのコマンド「npm init @eslint/config」を実行。


ここから英語で質問が続きます。

「@eslint/create-config というツールをダウンロードして実行してもいいですか?」



『Node.js v22.14.0

npm error code 7

npm error path C:\alldata\website\bsc

npm error command failed

npm error command C:\WINDOWS\system32\cmd.exe /d /s /c create-config

npm error A complete log of this run can be found in: C:\Users\master\AppData\Local\npm-cache\_logs\2026-06-18T04_08_33_535Z-debug-0.log』

詰まる所「package.json ファイルがないため、ESLintを初期化できません」

であるならば「npm init -y」を実行


三度目のなんとやら「npm init @eslint/config」を実行

やっと質問に入れました。
↑↓キーで選択するだけです。スペースキーを押すと選択肢を追加可能。


「ESLintをどのように使いたいか?」
「To check syntax and find problems」(構文チェックと問題の検出を行う)

プロジェクトで使用しているJavaScriptの「モジュールシステム」
・「JavaScript modules (import/export)」
・「CommonJS (require/exports)」
記載されている通りですがjs内で(import/export)を使ってるなら上、古いjsしかなくrequireがあるなら下だと思いますが、基本的には上で良いと思います。
フレームワークの選択
素のjs(バニラjs)なら「None of these」
フレームワーク使ってるな2つから選択
TypeScript使ってるか?
使ってないならno
プログラムは何処で動かす?
・Browser: Webサイトを作っている場合は、これを選びます。
・Node: サーバーサイドのプログラムや、Node.jsでツールを作っている場合はこれを選びます。
どちらもやってる場合はスペースで両方チェックも出来ます
「依存パッケージ」を今すぐインストールするかどうか
まぁ普通にYesで。

どのパッケージマネージャーを使ってインストールするかを尋ねられています。
ここまで npm を使ってコマンドを実行してきたので、そのまま npm が選択された状態で Enterキー を押してください。

終わり

設定を変更したい時は最初に指定したカレントにある「eslint.config.mjs」を編集します。

なんと・・・入れたけどやっぱり該当ファイルのチェックがされない。
もう詰んだ。むりぃ・・・・


2026年5月22日金曜日

無名関数とラムダ式に違いはあるのか否か

知らない誰かに怒られそうだと思うけど。
【「無名関数=ラムダ式」違いはない。】で大丈夫です。

ラムダ式って一体何のか?

数学における【「ラムダ (λ)」計算】が関数の構造を持っていて利便性がある為プログラムのラムダ式の基礎になっています。
しかし、根本的に、数学のラムダ式とプログラミングにおけるラムダ式は違うものです。

数学の純粋な世界から、各プログラミング言語の実用的な仕様へと形を変える中で、元々のルールは大幅に変更されています。

そのため、「本来のラムダ式とは~」という学術的な話を持ち出すこと自体が、プログラミングの現場においてはあまり意味を成しません。

「無名関数=ラムダ式」違いなどない

それで最初に話したところに戻りますが「プログラミングにおけるラムダ式」とは結局のところ「変数=無名関数」この形です。
実際に変数に入れる必要はないですが「この形で処理できる式が記述できるかつ、その結果を受け取る事が出来る事」です。

この条件が定義です。
【「変数=無名関数」=無名関数=ラムダ式】という事です。
当然ながら「無名関数=ラムダ式」ではないという事を言う人もいるだろうしラムダ式は無名関数の一部であるといういう人もいると思います。

ですが先に述べた通り根本的に本来の数学のラムダ式は違って各言語のプログラム仕様に変更されてるため、無名関数が使えるならどれもラムダ式に違いはないです。

あとは、元のラムダ式にどれくらい近いか?くらいしか焦点をあてる所がないです。

仮にプログラムの内部仕様的にthisがとれるとかとれないとか、挙動の違いがあるとしても言語別のプログラム仕様の誤差でしかなく「変数=無名関数」が出来るのであればそれはラムダ式だし、逆に「変数=無名関数」が出来ないのならそれは無名関数でもないしラムダ式でもないです。

通称の差でしかない

さて、それを踏まえたうえで、もう少し無名関数を絞って一般的には・・・みたいな一般論の話をしましょう。
おおまかには「1行で書ける」「引数を渡せる」「名前が無い」「式の結果を直接受け取れる」「引数は1つ」「引数の渡し方が=>とか->である」
まぁだいたいこんな感じです。

言語の種類によっては仕様的に呼び方をラムダ式とそうじゃないものやPythonのようにそもそもlambda という記述が明示的なものもあります。
先ほども言いましたがプログラムのラムダ式は通称に過ぎません例えば、Pythonは一行でなければなりませんがjavascriptのアロー関数はそんな事はありません。
Pythonの様に制限されるものこそラムダ式だ!!というならjavascriptにラムダ式は存在しません。
別にそう考えてもよいですが、あくまでも最初に言った通り「変数=無名関数」なればよい程度で言語の仕様で結構内部的な仕様は異なりますし、それぞれラムダ式の実際の定義方法(記述方法)が違うので誤差です。
javascriptで

let a = e => e + 2; // ①
let a = (e) => { return e + 2 } // ②
let a = (e) => { let b = 2; return e + b } // ③
let a = function(e) { return e + 2 } // ④

とかで①はラムダ式だが②と③と④はラムダ式じゃない!!みたいなのは誤差ですよ。
言語によっても仕様がばらけるしラムダ式と無名関数に差なんてないです。
せいぜい無名関数っていうやつの1行版がだいたいラムダ式と呼ばれることが多い程度で結局通称に過ぎません。

なので、プログラミングでこれをラムダ式に・・・とかいう会話がでたら、あぁ無名関数の事なんだなって思えば十分です。

最後に

もし、ラムダ式を理解出来てるが無名関数ってなんだと思ってるならラムダ式の事なんだなって思えばいいし、無名関数を理解してる人がラムダ式ってなんだってなってる場合はなんだ無名関数の事なんだなって思えば十分です。
注意点としては手前で言っている通り、その言語のどの無名関数が通称ラムダ式って呼ばれているか程度は一応調べた方が会話に語弊が生まれないと思います。
とはいえ、どちらにしても「変数=無名関数」この事に過ぎないので難しい話ではないです。

例えばjavascriptとかだと無名関数な会話では「コールバック」とか「アロー関数」とか「IIFE」とか単純に「無名関数」が・・・とかあり、thisがとれるとか差はありますが無名関数としての本質的な違いはありません。

明確に「プログラミングにおいてラムダ式とはどの言語においてもこういう仕様でなければならい」というものが存在しないし、各言語の仕様もバラバラ。
なので仮に本当にそういうものがあったとしてもそれらは守られてないので「無名関数とラムダ式」は似て非なるものみたいな意見は無意味だなって思います。

2026年3月30日月曜日

コールバックって何?アロー関数って何?無名関数って何?

「コールバック」も「アロー関数」も「無名関数」も全部同じ関数でしかないという話

コールバックって何?アロー関数って何?無名関数って何?と思ってここに来たと思ます。

まず、所詮は全部関数です。
出来る事は全く同じで単に呼び方とか呼び出し方が違うだけ。

つまり、引数を渡してその結果を受け取る。
ぜーんぶ同じです。どれもfunctionです。

なので、じゃあ何が違うのかという話を今回は説明するだけ。
大前提としてまず普通のfunctionをどうやって使うのかを思いだす所から。

1.普通のfunction

function sample(n) {
  console.log(n);
}
sample(100);

だいたいこんな感じですよね。
『function 関数名(引数){処理}』
はい。合っています。

つまり、関数は特定の処理を何度も使うので独立させておいて、関数名を呼ぶことでその処理を引数を変えて何度も実行させます。
そういった場合に実は関数って呼んでるその機能って名前無くても成立しますよね?
関数の機能の部分って、『(引数){処理}』ここだけじゃないですか?

()「引数を渡す部分」』と『{処理}「実際に引数を使って処理する部分」
関数を関数たらしめる機能ってこれしかない

つまり、関数って実は『(){}』必要なのってこれだけだよねって言うのが全貌です。


関数=『(){}』このたった二つの括弧が関数って分かればもう「コールバック」も「アロー関数」も「無名関数」も9割理解出来たと思ってよいです。

1-2.関数は変数に代入出来る

もう一つ理解しておくべきことはこれです。
関数は変数にも代入出来る。
これは処理結果という話ではなくて、関数そのものを代入出来ます。
function abc(aaa){return aaa+1;}

という処理を作ります。
次にこの関数実行する場合

const a=abc(9);
console.log(a);//10

こうですね。『変数=関数()』
括弧を付けるとその関数が実行出来て、その結果が代入出来る。
次に関数を代入したい時は括弧を付けないで代入します。

const bbb = abc;
const b = bbb(9);
console.log(b);//10

関数本体を渡したいので引数などはいりません。
1つ手前で言った通り、括弧を付けると関数が実行されてしまう為です。
実際にはメモリの参照位置を渡してるんですが関数がそのまま代入されるって理解で一先ずOKです。
一旦理解したいのは関数は変数に代入出来るという事です。
ここまで理解できれば、無名関数もアロー関数も99%理解したと思ってよいです。

2.無名関数・アロー関数

無名関数とかアロー関数って他のサイトでも色々なサイトが説明してるけど分かり難い。
なぜなら、無名関数とかアロー関数の説明をしてるサイトって何処も
『const ccc = function(n) { console.log(n); };』
って、説明してるじゃないですか?
無名とか言ってるのに名前つけてるじゃん???? 『const ccc = function(n) { console.log(n); };』
ってするんだったら、そもそも
『function ccc(n) { console.log(n); };』
でいいよね????
なんか違うの????

さすが!天才!そこまで理解できれ完璧です。
そう、全く違いは無いです。

じつは、関数として処理内容が同じならどっちで書いても全く同じ処理です。
なので、基本的には

// 普通の関数

function ccc(n) { console.log(n); }

// 無名関数

const ccc = function(n) { console.log(n); };

// アロー関数

const ccc = (n) => { console.log(n); };

最初に説明したとおり、この三つ全部が関数の機能としては完全に全く同じです。
そうなんですよ。
名前を付けてしまえばどれも同じです。
この3つのパターンどれで定義しても実行方法も結果も同じです

じゃあなんで呼び方が違うのか????
はい。今回の話はそこです。

まず、名称に関してですが1つ前のセクションで説明した通り、関数は変数に代入出来ます。
前のセクションでは普通に定義した関数を別の名前で定義しなおしました
function abc(){処理}
const bbb=abc();

ですが無名関数はそもそも、名前付きの関数を定義していません。
const ccc=function(){処理}

はい。みたまんまです。「なまえを付けてない関数」を代入しています。
説明上必要だから一旦代入してるだけで無名関数っていうのがさしている部分は
『function(){処理}』
この部分です。
ここだけ見れば確かに関数名…つまり名前はない無名関数という名称には納得がいく。

でも・・・確かに名前は付いてないけど・・・それって名前がないと呼べなくね?
そう、その通り。

ついさっき説明した通り、何かに代入したらほぼほぼ普通の関数とかわりませんし、そうなったら利点も薄い。(巻き上げは後述)
つまり、名前無しで呼べるタイミングがあるって事です。
それがコールバックです!

3.「コールバック」も同じ関数である

さて、話の続きです。
関数という部分「(){}」は必要だけど、名前が無くても別にいいタイミングある。ってマ?って話でしたね。
実は、この名前を使わないで関数部分だけを処理する・・・これが実はコールバックなんですよ(大事なので二回目)。

簡単なものからいくと例えば、『forEach』とかですね。

array.forEach(コールバック文);

名前を書かないつまり、この「コールバック」ってかっこよく言ってるけどこれって実はただの関数の事なんですよ。
最初にも言ってる通り、「コールバック」も「アロー関数」も「無名関数」も全部同じ関数です。
関数という機能は同じです。つまり・・・

array.forEach(関数);

実際にはこれだけです。
初学者はコールバックが良く分からなくて詰まってしまう人が割と多いのですがただの関数と違いは無いんですよ。
なーのーで、必ずしもアロー関数を書く必要は全然ありません。
例えば、forEachなどは何処のサイトに行っても間違いなく

array.forEach((value, index) => {
  // 処理内容
});

って書いてあって、初学者はアロー関数の記述になじみが無いから一瞬でパンクして頭が「???」になるわけです。
ですが、先ほど言った通り、コールバックっていうのはただの関数なので普通の関数でも良いんですよ。
つまり、一旦このコールバックって言うところに普通に関数を記述してみれば初学者も理解しやすい・・・と思うのにどのサイトもアロー構文しかないんですよね。
「コールバックってアロー構文じゃなくてもかけるの?????」
当然問題ありません。
無名関数とかアロー関数っていうのは元々の関数を記述際に別に名前いらないよね?っていう関数の進化でしかないので別に関数を普通に記述しても問題ないです。
という訳で、forEach文を普通に名前付き関数で書くと・・・

// 2. forEachの引数の中で、そのまま「function 名前()」を定義する
const ar = [1, 2, 3];            
ar.forEach(
  function eee(v) {
    console.log(v);
  }
);

見ての通りこの様に記述出来ます。一般的な普通の関数です。
『function eee(v) {console.log(v);}』
関数eee(引数){処理}これだけ。
今回最初に書いた普通の関数と同じです。引数を取得してそれをそのまま表示するだけ。
この時に、第一引数として取得出来るのが配列の中身です。
なーのーでこの「v」には配列が順番に渡されるので1・2・3と表示されます。

たったそれだけです。
mapとかfilterとか他にもコールバックっが使えるメソッドがありますが。
引数にその中身が連続で渡されるだけです。

これでまた、話が最初に戻ります。
「コールバック」も「アロー関数」も「無名関数」も全部同じ関数
なーのーでー・・・次に無名関数にしてみましょう。 だって、ここで必要なのって引数を受け取る『 () 』この括弧と処理を入力する『 {} 』ここしか必要ないじゃないですか?

// 1. 普通の関数を無名関数にする=関数名が無くなるだけ
const ar = [1, 2, 3];            
ar.forEach(
  function(v) {
    console.log(v);
  }
);

勿論これでも動きます。
関数名があろうがなかろうが処理に必要なのは『(){}』これだけですもんね。
名前が無くなった所で処理に必要なものは足りています。

そしてここで更に「function」って記述いらないよね・・・?ってなって出来たのがアロー関数です。
アローで必要なのは『 () 』と『 {} 』それとそれをつなぐアロー『 => 』だけです。

// 3. 無名関数をアロー関数にする
const ar = [1, 2, 3];            
ar.forEach(
  (v) => {
    console.log(v);
  }
);

はい。全く同じ処理がこれだけ短くなりました。
殆ど関数としての原型が無い・・・。
そう、初学者はこれだけで良く分からなくなっちゃう。
なので関数って実質『(){}』たったこれだけなんだぁって理解出来ればOK!

ここまできたらもう一押し理解しましょう。
javascriptを作ってきた人たちはもっと概念的理解にたどり着きます

関数が「(){}」だけでいいなら・・・『引数』『処理結果』だけあればそもそも『 () 』も『 {} 』も要ら居ないんじゃない?
つまり・・・

// 3. 更に省略したアロー関数
const ar = [1, 2, 3];            
ar.forEach(v => console.log(v));

使用頻度が高い引数が1つで処理が1つ(returnが1つ)だけならこれでいいよね?
っていうのがjavascriptのアロー関数って事です。
初学者がこれを関数だと理解出来ないのはもうしょうがないと思います。
もう概念なんですよ。『引数と結果』っていうこういう処理概念です。

とは言え、コールバックっていうのが関数だという所だけ分かっていれば理解も出来るし、慣れます。
そして、慣れると無駄な文字を書かなくて良いのでやはり利便性が勝ります。

最後に、でも複雑な処理がコールバック部分に入ったら見辛いのでは・・・?
はい。見辛いです。
ですが、コールバック部分に入れるのが関数つまりは関数を代入すれば自動でそこに引数を渡してくれるんです。 つまり

// コールバック部分に関数を直接代入
function eee(v){ console.log(v) };
const arr = [1, 2, 3];
arr.forEach(eee);

このように記述する事も出来ます。
特に多いパターンはDOMのイベントリスナーとかは処理が大きくなったらこの様に関数を分離してコールバックで関数を呼ぶというパターンも増えます。
ちなみに、このイベントリスナーとかで関数に間違えて括弧を付けてしまい関数が実行されるっていうのが良くある話なので「何を代入したいか」というのを忘れずに。

4.唯一の大きな違い「巻き上げ」と「再代入」

「機能は全部同じ」と言いましたが、JavaScriptの挙動として一つだけ決定的な違いがあります。
それは、「いつ呼び出せるか」「上書きできるか」というルールです。

① 巻き上げ(Hoisting)

普通の function で宣言した関数は、コードのどこに書いても「一番上にあるもの」として扱われます。
つまり、定義する前に行をまたいで呼び出すことができます。

test(); // エラーにならない!実行できる

function test() {
  console.log("動くよ");
}

対して、変数に代入する「無名関数」や「アロー関数」は、代入する前に呼ぶとエラーになります。
変数は宣言されるまで存在しない扱いだからです。

② 再代入の禁止(constによる保護)

普通の function は、後から同じ名前で関数を作ると上書きされてしまいます。
しかし、const を使ってアロー関数を定義すれば、間違えて同じ名前の関数を作ろうとした時にエラーで教えてくれます。

const myFunc = () => { console.log("絶対守るマン") };

// 同じ名前で作り直そうとするとエラーが出る(安全!)
const myFunc = () => { ... }; 

③暗黙return

軽く触れたけれどアロー関数はとにかく短く記述する事が目的に出来ている。
なので『引数』『結果』が最短記述出来る様に出来ておりreturnが1つだけの場合、暗黙的に結果をreturnしてくれる
『v=>v+1』とした場合、これの実行結果はvが引数として処理されv+1が自動的にreturnで返って来る。
これら「暗黙return」と「 {} 不要」の必要条件は、式が1つである事。
もっと簡単い言えば、ブロックが1つである事。更に言えば「 ; 」が無い事です。
そのため式が一つであった場合でも「 ; 」が付くと暗黙returnと括弧なしが利用出来ません。
最短記述をしたい場合は「 ; 」は消す必要があります。

また、引数側は引数が一つの時のみ「 () 」が省略できます。
引数が複数ある場合『 (a,b)=>a+b 』みたいにする必要があります。

④アロー関数はthisを拘束しない(Lexical this)

アロー関数の最大の武器は、「自分自身の this を持たない」という性質です。
JavaScriptの this とは、簡単に言えば「その処理を動かしている実行主(オーナー)」を指します。しかし、従来の function で書かれた関数(無名関数含む)は、呼び出し方によってこの this がコロコロと変わってしまう「浮気性」な性質がありました。

アロー関数はこの問題を解決し、「関数が定義された場所の this を一生使い続ける」という固定の挙動をします。

【無名関数として使う場合の違い】

イベントリスナーや setTimeout などで「使い捨ての関数(無名関数)」を渡す際、その差が顕著に出ます。
従来の無名関数: 実行時に this が「ボタン要素」や「Window」に勝手に書き換わります。
アロー関数の無名関数: 外側のクラスやオブジェクトの this をそのまま保持します。
これにより、const self = this; といった「thisの退避」という古いテクニックが不要になりました。

【メリットとデメリット】

メリット デメリット(注意点)
this の混乱がなくなり、コードが直感的になる。 オブジェクトのメソッドとして使うと、そのオブジェクト自身を this で参照できない。
記述が短くなり、コールバック関数がスッキリ書ける。 arguments(引数の集合)や new(コンストラクタ)が使えない。


「その場限りの処理(コールバック)」や「外側の this を使いたい時」はアロー関数。「オブジェクトのメソッド」や「this を動的に切り替えたい時」は function、という使い分けが現代のスタンダードです。

まとめると、「どこでも呼べる自由な functionか、「ルールが厳格で安全な const + アロー関数か、という使い分けになります。
最近の開発でアロー関数が好まれるのは、この「勝手に書き換わらない安全さ」があるからでもあります。

2026年3月20日金曜日

svgを動的に生成してみた話

ドット絵SVGを動的生成

今回はsvgを小さくデータで持ちたいなっていう超局地的な内容です。

HTMLに直接埋め込む

svgファイルはテキストデータで作る画像なので、以下のように直接htmlに埋め込む事も可能です。

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
  <!-- 画像の座標情報 -->
</svg>

また、cssに直接書いて参照させる事も可能です。

javascript化

今回のコードは以下の通り。

<script>
    (function renderGolemIcon() {
        const container = document.getElementById('golem-icon-container');
        if (!container) return;

        // カラーパレット
        const colors = {
            "#ffa500": 0,
            "#8b4513": 1,
            "#808080": 2,
            "#000000": 3,
            "#ffff00": 4
        };
        const colorList = ["#ffa500", "#8b4513", "#808080", "#000000", "#ffff00"];

        // SVGから1pxずつ抽出した完全なデータ
        const dots = [
            [7,0,0],[8,0,0],[9,0,0],[10,0,1],[11,0,0],
            [7,1,0],[8,1,0],[9,1,0],[10,1,0],[11,1,1],[12,1,0],
            [6,2,0],[7,2,0],[8,2,0],[9,2,0],[10,2,1],[11,2,0],[12,2,1],
            [3,3,2],[6,3,0],[7,3,0],[8,3,0],[9,3,1],[10,3,0],[11,3,1],[12,3,0],
            [3,4,2],[4,4,2],[6,4,0],[7,4,0],[8,4,1],[9,4,0],[10,4,1],[11,4,1],[12,4,1],[13,4,0],[15,4,2],
            [4,5,2],[5,5,0],[6,5,0],[7,5,1],[8,5,0],[9,5,1],[10,5,0],[11,5,1],[12,5,1],[13,5,1],[15,5,2],
            [3,6,2],[4,6,0],[5,6,0],[6,6,0],[7,6,0],[8,6,0],[9,6,0],[10,6,0],[11,6,1],[12,6,1],[13,6,2],[14,6,2],[15,6,2],
            [2,7,2],[3,7,2],[4,7,0],[5,7,1],[6,7,3],[7,7,3],[8,7,3],[9,7,3],[10,7,0],[11,7,0],[12,7,2],[13,7,2],[14,7,2],[15,7,2],
            [2,8,2],[3,8,2],[4,8,2],[5,8,3],[6,8,4],[7,8,3],[8,8,3],[9,8,4],[10,8,3],[11,8,3],[12,8,2],[13,8,2],[14,8,2],
            [3,9,2],[4,9,0],[5,9,1],[6,9,4],[7,9,3],[8,9,3],[9,9,4],[10,9,3],[11,9,3],[12,9,0],[13,9,1],[14,9,0],
            [4,10,0],[5,10,0],[6,10,3],[7,10,3],[8,10,3],[9,10,3],[10,10,3],[11,10,0],[12,10,1],[13,10,0],[14,10,1],[15,10,0],
            [1,11,0],[2,11,0],[3,11,0],[4,11,1],[5,11,0],[6,11,0],[7,11,0],[8,11,0],[9,11,0],[10,11,0],[11,11,1],[12,11,1],[13,11,0],[14,11,1],[15,11,0],
            [0,12,0],[1,12,0],[2,12,0],[3,12,1],[4,12,1],[5,12,1],[6,12,0],[7,12,1],[8,12,1],[9,12,1],[10,12,1],[11,12,1],[12,12,1],[13,12,1],[14,12,0],[15,12,1],
            [0,13,0],[1,13,0],[2,13,1],[3,13,1],[4,13,1],[5,13,0],[6,13,0],[7,13,0],[8,13,1],[9,13,1],[10,13,1],[11,13,1],[12,13,0],[13,13,1],[14,13,0],[15,13,0],
            [0,14,0],[1,14,1],[2,14,1],[3,14,0],[4,14,0],[5,14,0],[6,14,0],[7,14,0],[8,14,0],[9,14,0],[10,14,0],[11,14,0],[12,14,0],[13,14,1],[14,14,1],[15,14,0],
            [0,15,0],[1,15,1],[2,15,1],[3,15,0],[4,15,0],[5,15,0],[6,15,0],[7,15,0],[8,15,0],[9,15,0],[10,15,0],[11,15,0],[12,15,0],[13,15,0],[14,15,1],[15,15,1]
        ];

        let rectsHtml = '';
        dots.forEach(([x, y, colorIdx]) => {
            rectsHtml += `<rect x="${x}" y="${y}" width="1" height="1" fill="${colorList[colorIdx]}" />`;
        });

        container.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
                ${rectsHtml}
            </svg>
        `;
    })();
</script>

実際に描画されたSVG

ちなみに、この通り実際に復元可能です。
仕組み的には前に作ったsvgドット絵エディタで作ったsvgのデータをforを回して復元するだけです。なのでwidthもheightも1固定。
カラーパレットは配列化してindex値で取り出し。
16x16ですが、空白の位置もあるので配列横軸は16個ない場所もあります。
逆に縦軸は全部色があるので16個あります。

関数化してみる

もう一度使うかと言われると使わないかもしれないけど関数化してみるテスト。
idは唯一キーなのでidだと1か所にしか復元できないものの一旦はこれで良いかな。
そして、もう一度と言ったものの…ここまで作ったけど結局使わなかったっていう・・・。
でもまぁパレット値を弄るだけで全体の指定色を変えれるので使う機会はあるのかもしれない。

<script type="module">
    /**
     * ピクセルアイコンを描画する汎用関数
     * @param {string} id - 描画先の要素ID
     * @param {Array} pixels - [x, y, colorIdx] の配列
     * @param {Array} colors - カラーコードの配列
     */
    function renderPixelIcon(id, pixels, colors) {
        const container = document.getElementById(id);
        if (!container) return;

        let rectsHtml = '';
        pixels.forEach(([x, y, colorIdx]) => {
            const fill = colors[colorIdx] || '#000';
            rectsHtml += `<rect x="${x}" y="${y}" width="1" height="1" fill="${fill}" />`;
        });

        container.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" shape-rendering="crispEdges">
                ${rectsHtml}
            </svg>
        `;
    }

    // 初回実行処理
    const init = () => {
        // ゴーレムのカラーパレット
        const golemColors = ["#ffa500", "#8b4513", "#808080", "#000000", "#ffff00"];

        // ゴーレムのドットデータ (All dots included)
        const golemPixels = [
            [7,0,0],[8,0,0],[9,0,0],[10,0,1],[11,0,0],
            [7,1,0],[8,1,0],[9,1,0],[10,1,0],[11,1,1],[12,1,0],
            [6,2,0],[7,2,0],[8,2,0],[9,2,0],[10,2,1],[11,2,0],[12,2,1],
            [3,3,2],[6,3,0],[7,3,0],[8,3,0],[9,3,1],[10,3,0],[11,3,1],[12,3,0],
            [3,4,2],[4,4,2],[6,4,0],[7,4,0],[8,4,1],[9,4,0],[10,4,1],[11,4,1],[12,4,1],[13,4,0],[15,4,2],
            [4,5,2],[5,5,0],[6,5,0],[7,5,1],[8,5,0],[9,5,1],[10,5,0],[11,5,1],[12,5,1],[13,5,1],[15,5,2],
            [3,6,2],[4,6,0],[5,6,0],[6,6,0],[7,6,0],[8,6,0],[9,6,0],[10,6,0],[11,6,1],[12,6,1],[13,6,2],[14,6,2],[15,6,2],
            [2,7,2],[3,7,2],[4,7,0],[5,7,1],[6,7,3],[7,7,3],[8,7,3],[9,7,3],[10,7,0],[11,7,0],[12,7,2],[13,7,2],[14,7,2],[15,7,2],
            [2,8,2],[3,8,2],[4,8,2],[5,8,3],[6,8,4],[7,8,3],[8,8,3],[9,8,4],[10,8,3],[11,8,3],[12,8,2],[13,8,2],[14,8,2],
            [3,9,2],[4,9,0],[5,9,1],[6,9,4],[7,9,3],[8,9,3],[9,9,4],[10,9,3],[11,9,3],[12,9,0],[13,9,1],[14,9,0],
            [4,10,0],[5,10,0],[6,10,3],[7,10,3],[8,10,3],[9,10,3],[10,10,3],[11,10,0],[12,10,1],[13,10,0],[14,10,1],[15,10,0],
            [1,11,0],[2,11,0],[3,11,0],[4,11,1],[5,11,0],[6,11,0],[7,11,0],[8,11,0],[9,11,0],[10,11,0],[11,11,1],[12,11,1],[13,11,0],[14,11,1],[15,11,0],
            [0,12,0],[1,12,0],[2,12,0],[3,12,1],[4,12,1],[5,12,1],[6,12,0],[7,12,1],[8,12,1],[9,12,1],[10,12,1],[11,12,1],[12,12,1],[13,12,1],[14,12,0],[15,12,1],
            [0,13,0],[1,13,0],[2,13,1],[3,13,1],[4,13,1],[5,13,0],[6,13,0],[7,13,0],[8,13,1],[9,13,1],[10,13,1],[11,13,1],[12,13,0],[13,13,1],[14,13,0],[15,13,0],
            [0,14,0],[1,14,1],[2,14,1],[3,14,0],[4,14,0],[5,14,0],[6,14,0],[7,14,0],[8,14,0],[9,14,0],[10,14,0],[11,14,0],[12,14,0],[13,14,1],[14,14,1],[15,14,0],
            [0,15,0],[1,15,1],[2,15,1],[3,15,0],[4,15,0],[5,15,0],[6,15,0],[7,15,0],[8,15,0],[9,15,0],[10,15,0],[11,15,0],[12,15,0],[13,15,0],[14,15,1],[15,15,1]
        ];

        // アイコンを描画
        renderPixelIcon('golem-icon-container', golemPixels, golemColors);
    };

    // 実行
    init();
</script>

最後に

技術的に面白いかとやってみましたが、根本的にはそもそも画像と同様に使えるのでsvgファイルとしてimgで参照させましょう。
そうすればcssも汚されないし、htmlも汚れません。

2026年1月20日火曜日

サイトデザインを更新するぞ

サイトデザイン更新

時間があるうちにサイトのデザインを更新する事にしました。
前の状態はこんな感じ。

■Twitterウィジェット

画面中央左の空きはTwitterのツイートウィジェットです。
仕様が変わった事で見てるブラウザでログインしてない限りは表示されなくなりました
逆に言えば、仕様が変わっても𝕏にログインさえしてればツイートがちゃんと表示されるんですがブラウザにログインセッションがないとガラ空きになってしまう。
スマホアクセス時はバッサリツイート表示を消してたのでまぁそもそも別にいらないかな。

■更新履歴

内部スクロールに詰めてたけど数個だけ表示してあとはmoreで履歴ページにまとめよう。

■リンク周り

一旦整理するのに大半を除外。

■ヘッダーとフッター

遥か昔に更新が面倒なのでもともとヘッダーとフッターはjavascriptで生成して埋め込んでいました。
ですが昔のクローラーはjavascriptを全く読まないのでサイトの奥へリンクを読みにいけないためSEO的にダメという事でhtml側に書くようにした経緯があります。
メニューを更新したくなかったからメインメニューを減らしたんですよ。

現在のサイトデザイン

まぁ暫定ですが一旦更新したのがこちら


モダンとは何かが良く分からないけどまぁ色々モダンに寄り添うようには頑張ったはず。

■サイトのトップ

最近の流行りというかなんというかサイトを開いたら「ヒーローイメージ」という画像をバァンと出して、キャッチコピーなどをドカンと入れるのが主流(たぶん)。
勿論サイトの種類に寄りますが折角絵を描いてるしそれに倣う事にしました。
とは言え最近絵を描いてなかったから幅2000~3000pxの絵があまりなくて、今回はオリキャラのロロさんじゃなくて、闇玉藻をバァンと配置しました。
超いにしえのサイトが中央に「入口」みたいなリンクがあるのを彷彿とさせるというのはデザインが一周回ったという可能性がある。

■サイトの横幅

前回の横幅サイズは1000pxにしていました。
今回の横幅は1280pxです。
2025年前後で現在PCディスプレイの解像度は1920px、1536pxの幅が多いです。
マックスサイズで開く事はあまりないのでそれより狭い幅が良いかと。
指標的なものは余りありませんがTailwind CSSのコンテナ幅で2xlが1536px、xlが1280pxという感じなので広げ過ぎても情報が横に伸びるため今回は1280pxにしました。

■基本メニュー

TOPは100vh-数十ピクセルで上下を黒線で映画画面な形にする事で引き締めてあります。
なので必ずスクロールが必要でヒーローイメージで1ページを占有させたので基本メニューはその下に全部あります。
機能面で言えば機能性が悪いです。
まぁでも自分のサイトの構成上メイントップよりサイト内サイト構成になってるのでそちらに直接アクセスする方が多いだろうから別にいいかなって割り切った。

■モダンって何だろうね・・・

grokにモダンっぽいを聞いたらメニューをガラス表現のトップ追従とかがモダンっぽいと言ってたんだけどエフェクトでわざわざ重くすることはないなと。
appleが良くやってます見栄えが良くなりますってメッチャ推してきたけど保留。
いやまぁモダン的な動きを付けるとどれも重くなるだけなんだけどさ。
個人的には情報を見る時に注視してる外で物が動くのが嫌なのでサイト内で動的な所は少ないです。
メニューが少し遅く表示されるのはReactの読み込みをラグを体感させないためにそうしています。遅れてぱっと出るとガク付きを感じますが、元を0%表示からゆっくり出す事でで読み込みラグがあってもメニューの表示がそういう演出に見える様にしました。

■ダークモード

現在サイトを見てる人の大半はダークモードで見てる割合が5割を超える
トグルで切り替え付けた方がベターだとは思ったけど面倒になったのでサイト全体を強制ダークベースにしました。

■ヘッダーとフッター

最近はベタhtmlの方が確実とはいえ生成コンテンツ部分も読むみたいなので今回はReact18でヘッダーとフッターを作る様に変更。
React19だとsrcでjsxが変換できないため18です。割とお試し実装なのでCDN経由。使えなくなりそう時はちゃんと実装します。
現在はバベル経由でwebで直ビルド。
一応jsが無効な時用に最低限のヘッダーはindexへのリンク、footerはコピーライトだけが残る様にベタcssで入れてあります。

■cssフレームワーク

今回はメインのcssはスカスカにしてあります。
こちらも結構お試しの感覚でCDNでTailwind を入れています。
今でもコード見るとやはりごちゃごちゃして汚く感じるものの確かにsrcのcssと往復しなくていいのはデザインいじりが捗ります。
まぁただ前回の記事でも話した通りTailwind の真価がでるのはReactなどのコンテンツに押し込んで使った時なのでベタhtmlに書いている今回の場合はそりゃあそう感じるのは仕方ない。

■レスポンシブの対応方法

TWのmdでちょこちょこメディアクエリを入れてる所はありますが基本的には最低と最大幅のコンテンツdivを作って自動折り返し。
今回は幅広めなので指定数で幅が狭くなったら1列にするという形ではなくて、幅があるだけ横にアイテムを詰めて、狭くなるだけアイテム列が減っていく形にしてあります。

2026年1月16日金曜日

js-mjs-jsx-ts-tsxの違い

js、mjs、jsx、ts、tsxって何なの?

さて今回はjavascriptの話です。

超簡単に言えば結局どれもjs=Javascriptであり、Javascriptに出来ない事は他のjs以外の拡張にでも処理出来ません。それに加え、末尾xがついてるものはjs+タグの構造です。
個々に説明しますね。

.js

一般的にバニラjsなどと呼ばれる無加工天然のjavascriptです。
全ての基盤であり、ブラウザが進化して例えば直接ts実行できるみたいな未来がない限り先ほど出した一覧のどの形式であってもこの純粋jsにトランスパイルする必要があります。

一般的なスクリプト体系と同様に変数の型が自動で判定されます。
また、変数や関数はグローバル領域「window.」に定義される為どこからでも参照もでき、定数やfreezeでなければどこからでも変更もできます。
つまり、スコープが超広い。

.mjs

端的に言えばモジュールJavascriptです。ES Modulesと言われます。
拡張子は区分けの為にmjsとする場合もありますが「.js」でも問題ありません。
呼ぶ時に<script type="module" src="script.js"></script>このtype部分で判定されます。
基本的にはほぼほぼバニラjavascriptで、トランスパイルなども不要です。
モジュールとなった事でスコープの範囲がそのmjs内でしか参照出来ません。
そうなると複数のmjsファイルの場合、他のファイルの変数や関数にアクセス出来ない…となりますが、mjs内でexportを指定した上で他のmjsはimportすれば参照出来る様になります。

スコープが排他された事でhtml側からのonclick等での関数実行も出来ません。
mjsを使う場合イベントリスナーが必要になります。
js側からhtml側をみてクリック等を見張るという形で実行します。

jsかmjsは読み込み方で変わるので以降のものもこのどちらかに属します。
つまり、mjs(jsx)みたいな事もあり得るわけです。

また、mjsは個々同士でexportとimport出来ますがpackage.jsonにまとめて管理する事が多いかと思います。

.jsx

簡単に言えば「HTML+javascript」の融合体です。
JavaScript XMLと言われます。

関心の分離はどうした?と初見ではかなり抵抗がありますがjs側で全てを管理するという事で現在は主流の仕組みです。
ブラウザ上で直接は利用出来ません。つまり必ずJavascriptに変換する必要があります。
一応CDNでバベルなどを経由すればjsxの直接リンクしてもリアルタイムでビルド出来ます。

.ts

雑に言えばjavascriptの強化版です。typeScriptと言われます。

Javascriptに型定義の概念を追加する事で開発の安定性を向上させる事に成功しました。
ブラウザ上で直接利用する事は出来ません。トランスパイルが必須です。

.tsx

typeScriptのJSX版です。typeScriptの型定義を使える上にhtmlタグも一緒にまとめて記述出来るという訳ですね。

当然ながらブラウザ上で直接は利用出来ません。トランスパイル必須です。

jsxの仕組みが主流という事はそうじてtsxも主流です。
どちらかと言えば強固であるtsxの方を主流というのが正しいかも。

現在のjavascriptフレームワークはReactが主流であり、大半はtsxなんじゃないかな。

その他

他にcjsやコーヒースクリプトなどもあるがもう古い技術として基本覚える必要はない。cjsは必要な時に調べれば十分。

2026年1月14日水曜日

React-TailwindでCDN経由で使ってみる

Reactを使ってみよう!

今回はいたって単純React使ってみようという話です。
CDN経由で使えるのでどんな感じかを直ぐに試せるのは本当に良い時代です。
一応順を追って説明しますね

CDN (Contents Delivery Network) とは

まずコンテンツ・デリバリー・ネットワークで使えるというのがどういうことかというと、通常ライブラリとかフレームワークはダウンロードしてきてサーバーアップロードして参照したりしないといけない。
正直面倒臭いし、ブログなどではそもそもサーバにファイルが置けない。
そんな時にCDNです。
CDNは超簡単に言えば直リンクです
JSとかcssとかに直リンクして必要な結果だけを貰う。
ただし、昨今のCDNはデータサイズが大きいので重いです。例えば、Tailwindcssとかはcssって名前ではあるものの実際にはjsを経由してcssのデータを貰う形です。
全乗せのcss情報が来るのでサイズが大き目でリンク時のコンソールにもCDNでの本番運用だと遅いから推奨しないって内容の文字が裏で表示されます
他にも少し問題がありますが一旦後で説明します。
取り合えず超簡単にまとめると「便利機能がリンクするだけで使える様になる」です。

Reactとは

さて、本題のリアクトとはjavascriptのフレームワークです。
jsxで記述する事でタグとjavascriptを一つにまとめる事が出来ます。
初見では相当気持ち悪さを感じますがみんながみんな慣れるというので慣れなんだと思います。
また、Tailwindとの相性がよくcssを別途使わなくても短い記述でstyleも含めて記述する事が出来ます。クラス名を考えなくて良いという事もあり開発速度が上がります。

CDNでReactで使う場合

先ほど「他にも少し問題が」って言いましたがCDN経由ビルドとなるのでmjsでは利用できません。
私は自作のDOMラッパーをインポートしたりするので最近はmjsが多かったのですが今回は使えません。
何故使えないのかというとまずjsxのアクセス方法が<script type="text/babel" src="jsx.js"></script>こんな感じですね。
babelに投げてビルドしてもらうという事ですね。投げたjsxはjavascriptとして返ってきて始めて動作するのですがbabel君は外部接続できないのでimport文が利用出来ません。
スクリプトのアクセス方法をmjsにするとそもそもbabelにデータが渡せません。

つまり変数がグローバル化してしまうという事です。
まぁ、それだけといえばそれだけです。

Reactのおおまかな流れ

①ReactDOMから必要な機能を取り出す

const { createRoot } = ReactDOM;

分割代入で取り出すのが一番楽かと思います。
一応、ReactDOMから取り出してるだけなので
const a=ReactDOM.createRootでも
const a=ReactDOM['createRoot']でもアクセスは可能です。

②コンポーネントを作る

例えば以下の様にfooterを作ってみます

const Footer = () => (
    <footer className="py-8 border-t border-[#1a1a1a] bg-[#050505]">
        <div className="max-w-[1280px] mx-auto px-5 text-center text-xs text-[#555]">
            <p>©black stray cat</p>
        </div>
    </footer>
);

定数にFooterを定義無名関数でそのまま内容を書きます。
ほぼほぼタグをそのまま記述可能ですがclassはバニラJavascriptで定義されているためJSXではclassNameにする必要があります。
ちなみに「ケースセンシティブ (case-sensitive)」なのでFooterの頭をFにする事でタグのfooterと別の判定になります。Reactは 「小文字で始まるものは通常のHTMLタグ」「大文字で始まるものはカスタムコンポーネント」 と自動で判別します。そのため、自作コンポーネントは必ず大文字で始める必要があります。

③レンダリングする

const footerEl = document.getElementById('footer-root');
if (footerEl) createRoot(footerEl).render(<Footer />);

処理的には先ずDOMを定義して次に存在確認存在したらレンダリング
先ほどReactDOMから取り出した機能を使います。
renderに指定する中身は定数に定義したコンポーネント名です。
今回で言えば「<Footer />」こうですね。

④html側

<div id="footer-root"></div>

こんな感じです
先ほどgetElementById指定した所に内容を生成します。
このdivの内側に予定と違う内容あれば全て書き換えます。

挙動だけを見るとinnerHTMLに近いものだと思ってもあまり問題ないです。
ただし内部的には仮想DOMで極力再利用する様に処理されます。
innerHTMLの場合は全て破棄された後に書き変わる処理である為Reactの方がメモリ効率が良いし高速です。

つまり、事前に使うhtmlを用意しておくと読み込みが早くなります。
基本的にはNext.jsを使う事になると思いますがhtmlが無くても生成はされますので試す分には問題ないです。

この様にしてcss-in-jsがコンポーネントとして分離サイト内でパーツとして利用できます。
新たにサイト等を作る時にも同様の機能が欲しい時全て込み込みで移植出来ます。

CDNでReactでモジュール

さて、React19では可能というか19以降はインポートでしか行けない。
しかもインラインでしかjsxをビルド出来ない。
パーツをインラインで書くなら意味がない。

さらに色々な問題が発生します。
相対パスが外のurlと相対になり参照が上手くいかないとか、CDNのTailwindが先に処理されてReact19内で使うstyleが用意されないといった問題が起きる。
一応TWの対応はできる
  <script>
    tailwind.config = {
      safelist: [
        'hidden',
        'md:block',
        'md:hidden',
        'block',
        'w-8',
        'h-8',
        'py-4',
        'bg-[#050505]',
        // 例えば事前に読み込む必要がでる
      ]
    }
  </script>
のように先に使うものを指定しておく・・・ただこれを手動でやるのはCDNの手軽さが損なわれる。TWの参照が前後するならもう普通のスタイルを埋め込んだ方が確実。

React19の一応使い方としてはまず呼び出しは
  <!-- importmap:React 19 -->
  <script type="importmap">
  {
    "imports": {
      "react": "https://esm.sh/react@19.2.0",
      "react-dom/client": "https://esm.sh/react-dom@19.2.0/client"
    }
  }
  </script>

  <!-- tsxでJSX変換 -->
  <script type="module" src="https://esm.sh/tsx"></script>
この様に変更します
コード呼びは
<script type="text/babel">ここにインラインで記述
そしてReact呼び出しは今回インポートに変わります
    import React, { useState } from "react";
    import { createRoot } from "react-dom/client";
JSXに基本的な記述変更ありません
CDNでReactをモジュールにするとかなり色々な相性が悪くなる
ただ、公式推奨はもう新しいReact19なんですよね・・・。

まとめ

cdnで使うという前提において
コードを外部参照できるのはReact18まで
この場合はTailwindもとても相性が良い

React19はコードを外置き出来ないインラインでしかjsxをビルド出来ない
コードを別ファイルで管理したい時の解決策は2つ
1.素直にReact18を使う(既にサポート終了ずみ余命のみ)
2.React19でjsxを記述せず純粋なjsモジュールとして使う

ただまぁReactをバニラjsのmjsで利用すると変化癖がつく可能性があるにはある
たぶん現在の一般的な使い方はjsxで記述するのが普通だろうし・・・。
やはりCDNは個人サイトとかで使う場合であってもお試しが限界かなぁ

2026年1月12日月曜日

Next.js-React-TailwindCSSなどたわいもない話

最近思ってる事を整理するために一旦書き出して整理してみるという話

今回は最近思ってる事を 整理するために一旦書き出して整理してみるという話。
昨今のWebページは基礎となるhtmlやcssやjsを素のままは書かない。 大半はフレームワークだよりになっている。 一応WebComponentsという選択肢もあるがこれもまたフレームワークの影響によるもの。

この世には「関心の分離」という言葉あって、Webページで言うなら画面のデザインと動的処理の分離をするべきだとずっと言われてきた。 だが最近の流れはそうではない。データをコンポーネント単位で「デザイン+処理」としてカプセル化して再利用しようというのが主流である。

まず、大半はフレームワークという話。

主に流行ってるのはReact.jsとVue.jsほぼ2強…いやたぶん割合的にReact(next.js)1強。
htmlタグをjs側に取り込んでしまおうというのがこれらのフレームワークである。 通常のhtmlはhtmlからjsとcssを呼び出してhtmlを主体にそれを装飾する形だった。 フレームワークは逆にjsを主体にhtmlを吐き出すという思想の元に出来ている。

そうすると空白のページにjsが動いて始めて生成される。 これにはちょっとした問題がありページが大きいほど描画が遅れる。 そこで更にサーバー側のフレームワークNext.jsである。 サーバ側で一旦描画処理をして静的htmlを生成し描画を先にする。

フロントフレームワークは「html+処理」だがこれらフレームワークは既にあるhtmlを生成せず差分のみを生成する様に出来ている。 今は「Svelte、Solid、Astro」みたいな軽量かつ強いものも育ってきてるが先に出来たものに対して「安定」を優先させると新規フレームワークを導入・選択肢にするのはまだ難しい気がする。

WebComponentsの話

Microsoftなどの大きいサイトの場合はページが大きすぎて使ってるフレームワークの分裂が起きている。 そして統合が難しいが同じメニューなどを付けたくなった時にフレームワークを統合するのではなくもっと原始的なWebComponentsでパーツを配る事にしたそうである。 つまり、WebComponents+各フレームワークを使って共存させる事で落ち着いている。

TailwindCSSの話

cssやめよう。極端に言えば思想はこれである。

webページでの関心の分離はデザインとhtmlの分離ではないし、分離しない方が良いという方向で作られている。
デザイン面においてはhtmlよりもcss主体の方が再利用性があるという考え。
前回、使用感を記事にしたがhtml上にstyleでごりごり書くような地獄のような可読性になる。

ただ、Tailwindの思想としては「html+Tailwind」ではなくあくまでもcss-in-jsのパーツとして出来ている。
そのため開発者もフレームワークを使わない=コンポーネントにしないTailwindCSS単品の運用はあまり本意でない様である。
複数回登場するようなパーツならなおさらコンポーネント化した方が良い。

また、クラス名を考えるのは時間の無駄でさっさとデザインを反映させた方が良いという考え方。
じゃあなぜstyleではなくTailwindを使うのかというとstyle記述では出来ないメディアクエリなどのcssstyleが複数あるため。
他にも、フレームワーク全般に言える事だが記述の統一性の向上がある。

フレームワークの食い合わせの話

Reactは仮想DOM、WebComponentsはシャドーDOMを使っている。 先に述べた通りWebComponentsは他のフレームワークと共存できるが、シャドーDOMはTailwindと食い合わせが悪い。
これはcssが内部に閉じてしまいTailwindが使えなくなってしまう。
(いくつか対応方法はあるが現状あまり美しい実装ではない感じ)
逆に言えばReactとTailwindは良い組合せと言える。

現状の組み合わせで良さそうなものは?

Next.js-React-Tailwind現状ではこの組み合わせがかなり良さそうに感じる。

ちなみに今はかなり下火に見えるphpも再考した時に悪くないと思っている。 phpが凄く下げられているが現行のシェアもphpは低いわけでは無く特にWordPressは根強いシェアがある(40%以上ある)。

■変換が多すぎ問題

上記をもう少し加えるならTailwind-in-Next.js(React(TypeScript))という構造だろうか。 TypeScriptは型なしjavascriptに変換する必要があり、Next.jsはhtmlを生成する必要がある。 リアクトはビルドする必要があり、Tailwindもビルドする必要がある。 こう考えると本当に変換しすぎていて正直面倒臭い。

先ほど再考にphpが上がったのは面倒臭いからhtmlなどを直接インクルードすればいいじゃんという考えである。
この変換祭りがなんで起きるのかというと開発環境の負債をユーザに渡さない為である。

正直面倒臭いからCDN (Contents Delivery Network) =リンクのみで済ませたい。 ReactとTailwindはCDN使えるし・・・。
ただベタデータは重い為変換して軽量化しないと描画が遅れる為、モダン開発環境を享受しつつユーザー体験を損なわない為には変換祭りをしなければない。

2025年10月10日金曜日

Automatic Semicolon Insertion; ASIで沼った話

Automatic Semicolon Insertion; ASIで沼った話

久々に原因特定に時間が掛かったので備忘録。
今回問題になったのが「Automatic Semicolon Insertion」、通称ASIによる影響です。
直訳すれば「自動セミコロン挿入」ですが、簡単に言うと「セミコロン(;)で処理を自動的に終了位置として判定してくれるよ」という仕組み。
コード自体が勝手に書き換わるわけではなく、JavaScriptエンジンがセミコロンを「挿入したつもり」で文の境界を解釈します。
便利な機能ではあるのですがちょっとした「落とし穴」になりえるのでJavaScript弄っている人は暇つぶしにでも読んでみてください。

ASIって何?(=処理末端解釈)

ASIの最大の目的は、開発者の利便性向上です。JavaScriptはインタープリタ系言語の代表格で、型指定が不要、変数の自動型変換が可能という「ゆるさ」が売り。
数千行のコードを書くときに、毎文の終わりにセミコロンを手動で付けるなんて面倒くさいですよね? ASIがあれば、エンジンが自動でセミコロンを「仮想挿入」して文を区切ってくれるので、セミコロン漏れの心配が減ります。

JavaScriptのようなスクリプト言語は、C++やJavaのようなコンパイル言語と比べて、型の指定が不要であったり、変数の型が自動変換されたりと、開発の自由度やスピードが重視される傾向にあります。ASIもまた、開発者の負担を減らし、柔軟な記述を可能にする自然な流れと言えます。

ASIの落とし穴

ASIの問題は、文の境界が曖昧になる点にあります。
簡単に言えば「自動判定」が誤作動する…いやまぁ機械的に処理してるわけだから正確にはユーザーがASIが意識の外にある場合があるってだけですが。
最も有名なのがreturn文の例です。

return // ここで改行
a + 1
本来は a + 1 の結果を返したい意図で書いたとしても、JavaScriptは改行された時点で「ここで処理が終わったな」と判断し、内部的に以下のように解釈します。
return; //  ASIによってセミコロンが挿入
a + 1;  //  別の処理として解釈

結果、returnは何も値を返さず処理が終了し、期待した動作になりません。JavaScriptは変数名やメソッド名の途中ではない限り、基本的にどこでも改行可能なので、この自由さがASIと衝突することで、予期せぬ挙動を生み出すのです。

今回、ASIで沼った話

さて、本題の「ASIで沼った話」。これは単純なイベントリスナー登録の話です。
と、その前に、通常はこの様に書きますが
document.getElementById(id).addEventListener(type, listener);
個人コーディングの時はBSCオリジナルラッパーを使っているので
$i('aaa').ael('change', bbb);
こんな感じに圧縮記述出来ます。

普段、可読性を重視して、リスナーは次のように列挙して記述しています。
$i('aaa1').ael('change', bbb)
$i('aaa2').ael('change', bbb)
$i('aaa3').ael('change', bbb)
これは何の問題もありません。ASIによって、各行の末尾にセミコロンが補完され、それぞれ独立した処理として実行されます。まさに「ASI様様」という状態です。

リスナーをまとめてみた

今回は同じ関数を呼ぶ処理があったので可読性と効率を考えて、forEachでまとめて登録することにしました。
// リスナーまとめエリアでの記述
['aaaAbc','aaaDef','aaaGhi'].forEach(id=>{$i(id).ael('change', aaaAAA);});
このコード自体はテストして問題もありません。
DOMも読み込み済みでこれといったエラーはありません。

ところが、このコードを「リスナーまとめエリア」に移動した瞬間、aaaGhiの要素でエラーが発生! 原因不明で頭を抱えました。DOMはOK、個別記述ならOK、隔離してもOK、上部移動してもOK。でも、このエリアに置くとダメ…。

沼の原因:配列の誤解釈

forEachをばらせば正常になるのでバラで登録しても良かったんですが今後の為にも一応調査した事で一応特定は出来ました。
今回の具体的な配置は以下の通りです。
$i('aaa1').ael('change', bbb) // 1行目
$i('aaa2').ael('change', bbb) // 2行目
$i('aaa3').ael('change', bbb) // 3行目
['aaaAbc','aaaDef','aaaGhi'].forEach(id=>{$i(id).ael('change', aaaAAA);}); // 4行目
実は、内部的に発生していたのは、3行目と4行目の結合でした。
$i('aaa3').ael('change', bbb)['aaaAbc','aaaDef','aaaGhi'].forEach(id=>{$i(id).ael('change', aaaAAA);});
JavaScriptのインタプリタはこの様に解釈しました。

具体的には:
  1. $i('aaa3').ael('change', bbb) の実行結果(addEventListenerは通常 undefined などの値を返します)
  2. その結果に対して、次の行の配列をインデックスとしてアクセスしようとする
  3. undefinedなどの値に配列アクセス([ ])を試みたためエラーが発生
  4. 結果的に「undefined.addEventListener」のような状態になり、処理全体が破綻した

改行によって処理の継続と判断される箇所で、たまたま次の行が(や[など、前の行の処理の続きと解釈できる記号で始まっていたため、ASIが発動せず、予期せぬ文法解釈(配列アクセス)が行われてしまったという事ですね。

利便性 vs リスク:JavaScriptのジレンマ

結局、ASIでセミコロンから解放されたはずなのに、「セミコロンを明示的に付けた方が安全」
という話になりますが、そうなると「ASIの存在意義は?」という根本的な問いに行き着きます

①自由さと厳密さのトレードオフ
JavaScriptの設計思想は元々「柔軟な記述」を重視していますが、現在のコーディングの潮流はより厳密な方向に向かっています。

②TypeScriptという名のJavaScript
TypeScriptという言葉を聞いたことがある人も居るかもしれませんが、JavaScriptに静的型付けの概念を持ち込み、コンパイル時に厳密なチェックを可能することでコーディングを厳格堅牢にできます。
ですが・・・結局のTypeScriptはJavaScriptに過ぎません。コンパイルという表現を使いますが一般的なプログラムのように機械語にするわけでは無い為、処理が高速化するわけでもありません。
JavaScriptは結局ブラウザ依存です。つまり、折角型を付けたコードを削除して低級のJavaScriptに戻すというここだけ聞くと馬鹿らしい処理をしています。

個人的に最近はモジュール形式(.mjs)で書くことが増えましたが、これはローカルスコープやstrict modeの適用など、より厳格なコーディングを促しますがASIは有効です。

JavaScriptの未来:柔軟性と厳格性の狭間

正直、現状のJavaScriptがどこを目指しているのか、掴みどころがありません。
柔軟性を保ち続けたいのか、それともTypeScriptのような厳格な方向へ完全にシフトしてしまうのか。言語には通常、何らかの明確な「信念」がありますよね。
例えば、C言語系は高速実行を、Javaはプラットフォーム非依存を、Pythonは読みやすさと生産性を、PerlやRubyは簡潔さを、それぞれの強みとして進化させてきました。一方、JavaScriptは「どっちつかず」の状態が長すぎて、信念のなさが逆にその独自の魅力——いや、ブレブレの個性——を生んでいる気がします。
もちろん、見守るしかない部分もありますが、心から願うのは、この「ゆるさ」が失われないことくらいでしょうか・・・。


まとめ
  • セミコロンを積極的に付ける:ASIに頼りすぎず、明示的に。
  • 改行前に一文完結を確認:returnやメソッドチェーンの後、特に注意。
  • ツール活用:PrettierやESLintで自動整形・警告を。
  • テストを忘れず:個別動作OKでも、文脈で壊れる可能性あり。

2025年9月28日日曜日

liタグをドラッグアンドドロップで入れ替える話

 タグの入替方法

交換自体はそれほど難しくないが、アイテム交換にアクションを付けるとちょっと面倒臭い。
交換処理はいかの手順を踏む

  1. 入替対象タグ内に「draggable="true"」を追加してドラッグ可能にする
  2. ドラッグを開始時の対象idを取得
  3. 交換対象をドラッグオーバーで特定し交換対象がわかる表示
  4. ドロップして実際に交換

処理的にはこうなります。

親タグの<ul>にはidを付与します。「<ul id="draggable-list">」
子タグの<li>にdraggableとidを付与「<li id="item-xxx" draggable="true" >」
親を取得してその中で入れ替えます
リスナーは「item.addEventListener(event, handler)」イベント別に分けます
イベントは「dragstart、dragover、dragend」この三つが必須

  • dragstart(ドラッグ中)
  • dragover(交換可能位置上)
  • dragend(マウスボタンを放した)

挙動抑止として「drop、dragenter、dragleave、dragend」

  • dragenter(ドラッグ対象先判定)
  • dragleave(対象外選択範囲)

抑止処理

「e.preventDefault();」でブラウザ上のデフォルト挙動表示を抑止します。
なので処理の頭に指定します。over中とenterの最初に記述します。
dragleaveは何もしないので処理を空で用意。

処理開始

dragstartにやる事は現在のidが何か?を取得します。
「e.dataTransfer.setData('text/plain', e.target.id);」を使えばdataTransferに掴んでいる対象を保存できます。
変数を使わないのでコード的には綺麗ですが問題はover中は中身を取得出来ません。

なので「draggedItem = e.target;」で対象別途保存します

over中

つまりは交換可能対象の上でドラッグ中の状態です
overされてる対象と現在掴んでる対象を比較交換するかの状態を表示させます
ただ、そのためには現在掴んでいる対象を知る必要があります。
const draggedId = e.dataTransfer.getData('text/plain');if (!draggedId) return;」
const dragitem=document.getElementById(draggedId )
とすれば、ドラッグ対象を得られるのですが、over中はdataTransferから空文字しか返ってきません。
つまり、放した瞬間しか取得出来ません。「if (!draggedId) return;」でその間無視する事ができるものの、それは交換できる状態の視覚効果の処理に到達出来ません。
一応放した瞬間に取得出来るのでAとBの対象交換自体は出来ます。

先に言った通り「交換可能視覚効果」を得るには「ドラッグ先の対象」と「ドラッグ中の対象」両方を知ってる必要があり、ドラッグ中の対象を事前に得るならば処理開始時に「draggedItem = e.target;」をしておいてdraggedItem を参照するようにすればOK
ただ、draggedItem は処理開始時とover中どちらからもアクセスできる必要があります
それはつまりdraggedItem をグローバル変数にせざる得ないという事です。

「 if (targetItem && targetItem !== draggedItem) {」空の場合と交換対象がドロップ中のアイテム自身ではいけないのでこんな感じの分岐になります。

ドロップ

対象にマウスボタンを放した時
ただ、over中に処理が完結する。

動的にリストを追加

function addlist(n){
    const smna = getSecondaryMaterialNames();
    const list = $i('draggable-list');
    const newItem = Object.assign(document.createElement('li'), {id: `item-${crypto.randomUUID()}`,draggable: true,innerHTML: `<span>${smna[n]}</span><span class="delete-btn">×</span>`});
    newItem.dataset.smc = n;
    list.appendChild(newItem); // add tag
    setlistael(newItem);       // set ael(drag)
}

リストに×ボタンを付けてリストを消す

✕ボタンのイベントリスナーは一度だけ定義して親のイベントから継承させる
なのでリスナー登録するのは✕ボタンではない

 $i('draggable-list').ael('click', (e) => {
if (e.target.classList.contains('delete-btn')) {const itemToRemove = e.target.closest('li');if (itemToRemove) {itemToRemove.remove();getUpdatedOrder();}}
});

 親のidにクリックイベントで登録。
クラスのdelete-btnに継承してliの現ターゲットを削除させる。

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 インデックスを座標(行, 列)に変換。

こんな感じ。