2025年5月5日月曜日

JSON.parse(JSON.stringify())で深いコピーをしないでという話

javascriptで深いコピーをする方法について

前回は浅いコピーの話をしたので今回は深いコピーの話

■JSON.parse(JSON.stringify(配列))

let originalarr= Array(36).fill().map(() => Array(8).fill(0));
let copiedarr= JSON.parse(JSON.stringify(originalarr));
copiedarr[0][0] = 1; console.log(originalarr[0][0]); // 出力: 0 (影響を受けない)

 この方法は有名で簡潔に深いコピーを実現できます
ただし、JSON.parse(JSON.stringify(配列)) は、手軽に深いコピーを実現できる反面、undefined、関数、Date オブジェクト、RegExp オブジェクト、Map、Set、循環参照などの特定のデータ型を正しく扱えないという大きな制約があります。


この方法でコピーするのがおすすめのパターン

以下のような比較的単純な構造のオブジェクトや配列を深いコピーする場合には手軽で有効です。
・プリミティブな値 (number, string, boolean, null) のみを含む配列やオブジェクト
・プレーンな JavaScript オブジェクト (POJO - Plain Old JavaScript Object) で、上記のプリミティブな値と基本的なオブジェクト、配列のみで構成されている場合


そもそもこれは何をやっているのか

使い方と使う場面を知ったら次は仕組みの理解。

■そもそもJSONってなに?
JSON 「JavaScript Object Notation」の略です。
JavaScript のオブジェクトを軽量なテキスト形式で表現するための標準的なデータ形式です。その名前が示す通り、JavaScript のオブジェクトの記法をベースにしていますが、言語に依存しないため、様々なプログラミング言語やシステム間でデータを交換する際に広く利用されています。


JSON.stringify() メソッド

まずは内部から理解しましょう。
構文は以下パターン。
JSON.stringify(value)
JSON.stringify(value, replacer)
JSON.stringify(value, replacer, space)

「JSON 文字列に変換する」というのが根本的な機能です。
例外のTypeErrorが発生するのは「value が循環参照を含む場合・長整数値に遭遇した場合」です。
上記に記載している通り色々な言語間のデータをやり取りなどが可能な文字列形式にするという事です。ですので、JSON.stringify(配列)を行うとstringを出力します。 


JSON.parse() 静的メソッド

JSON 文字列を JavaScript のオブジェクトや配列に変換します。
つまり、逆の事を行います。JSON 文字列を解析して、元の配列やオブジェクトにします。


■つまり?

つまり、「JSON.parse(JSON.stringify(配列))」というのは配列を一旦文字列にしてそれを再度配列に戻すという事を行っています。
一旦文字列になる事で配列ではなくなるので新たに再変換されて出力される時に新しい配列になるので多次元配列がディープコピーできるのです。
また、この処理を行う時に文字列に上手く変換できない値達は欠損するという事です。


■素直に配列でコピーする

関数化する

function deepCopyMultiDimensionalArray(arr) {
if (!Array.isArray(arr)) {
return arr; // 配列でなければそのまま返す (プリミティブな値やオブジェクトなど)
} const newArray = [];
for (let i = 0; i < arr.length; i++) {
newArray[i] = deepCopyMultiDimensionalArray(arr[i]); // 要素が配列なら再帰的にコピー
}
return newArray;
} //ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー // 使用例 let originalMulti = [1, [2, [3, 4]], 5, { a: 6 }];
let copiedMulti = deepCopyMultiDimensionalArray(originalMulti); copiedMulti[1][1][0] = 99;
copiedMulti[3].a = 100; console.log("オリジナル:", originalMulti);
// 出力: オリジナル: [ 1, [ 2, [ 3, 4 ] ], 5, { a: 6 } ]
console.log("コピー:", copiedMulti);
// 出力: コピー: [ 1, [ 2, [ 99, 4 ] ], 5, { a: 100 } ] let originalWithObject = [1, [2, { b: 7 }]];
let copiedWithObject = deepCopyMultiDimensionalArray(originalWithObject); copiedWithObject[1][1].b = 101;
console.log("オリジナル (オブジェクトあり):", originalWithObject);
// 出力: オリジナル (オブジェクトあり): [ 1, [ 2, { b: 7 } ] ]
console.log("コピー (オブジェクトあり):", copiedWithObject);
// 出力: コピー (オブジェクトあり): [ 1, [ 2, { b: 101 } ] ]

こんな感じ


■二次元配列のコピーでいい場合

まぁでも多次元配列なんてあんまり使う物でもないと思いますので(多次元で深くネストすると人間も何処に何があるか分かり難くなるので)、大半は二次元配列とかが多い(セルや座標)と思います。

その程度であれば1次配列を[...配列]で展開して回すだけでも十分です。
function deepcopy2d(oa) {return oa.map(ar=>[...ar]);}


■structuredClone(obj)

実はもっと簡単な方法があります。

let original = {
  date: new Date(),
  regex: /abc/g,
  map: new Map([['key', 'value']]),
  set: new Set([1, 2, 3]),
  arrayBuffer: new ArrayBuffer(8),
  circular: {}
}; original.circular.self = original;
let cloned = structuredClone(original);
console.log(cloned.date); // 元の Date オブジェクトとは異なる新しい Date オブジェクト console.log(cloned.regex); // 元の RegExp オブジェクトとは異なる新しい RegExp オブジェクト
console.log(cloned.map);  // 元の Map オブジェクトとは異なる新しい Map オブジェクト
console.log(cloned.set);  // 元の Set オブジェクトとは異なる新しい Set オブジェクト
console.log(cloned.arrayBuffer); // 元の ArrayBuffer とは異なる新しい ArrayBuffer
console.log(cloned.circular.self === cloned); // true (循環参照も正しくコピー)
cloned.date.setTime(0);
console.log(original.date.getTime() === cloned.date.getTime()); // false (深いコピー)

こんな感じです。
見ての通り、より多くのデータ型をサポート: Date オブジェクト、RegExp オブジェクト、Map、Set、ArrayBuffer、DataView、ImageBitmap、ImageData など、JSON.stringify() では正しく扱えなかった多くの型を適切にコピーできます。

循環参照を処理: structuredClone() は循環参照のあるオブジェクトグラフも正しくコピーできます。内部的にコピー済みのオブジェクトを追跡し、無限ループを防ぎます。

Transferable オブジェクトの転送: ArrayBuffer などの Transferable オブジェクトは、コピーではなく所有権が移動するため、より効率的なデータの受け渡しが可能です(元のオブジェクトでは使用できなくなります)。

より自然なコピー: JSON.stringify() のように一旦文字列にシリアライズするステップがないため、より直接的で自然なコピー処理が行われます。


しかもwindow.のグローバル関数なのでこれでオールOK!
実装されたのは2020年頃でnodeも2021年頃実装しています。

2025年4月28日月曜日

ランドメイク&全イベント統合シミュレーターを作ろうかなって話

「 Hey!Scripting Cat!全イベントのシミュレーションもしたいよ!」

HAHAHA!なかなか無理をいいますね。この令和の時代に需要が何処にあるんですか。
ひとまずは「ランドメイクシミュレーター」は作ってあるからそれを使ってね
現状でもAFイベントはセットになってるからプレイ時に可能な配置をシミュレーションできるよ。
とは言え確かにあそこまでやるならもう全部のイベントの消滅条件とかまで管理しちゃえばいいんじゃない?という気もする。あと連続(アンドゥ・リドゥ)描画をした時にちょっと遅いのでそこを解消したいですね。

ランドメイク&全イベント管理を考える

まずは現在のランドメイクシミュレーターの機能と追加したい機能

■現在のランドメイクシミュレーターの機能
・全体マップを選択する
・ランドメイクマップを生成6x6(地形と土地マナ)
・ポスト入手→配置→積み木入手→配置
積み木まではイベントがない為配置と同時にAF取得
以下ループ
┏┓
┃┃
┃■イベント開始
┃┃
┃■イベント終了
┃┃
┃■AF入手
┃┃
┗┛
・常に工程を復元用ログに保存

■現在サブ機能
・全体マップの表示非表示
・全体マップ選択範囲クリック選択
・全体マップ選択範囲矢印で操作
・推奨エリアの直接選択
・配置とイベント両方のログ
・配置とイベント両方のアンドゥリドゥ

ーーーーーーーーーーー

■初期化
・Fマップ生成
・Lマップ生成
・リスナー設置
 ┗Fマップ 25x25
 ┗Lマップ 6x6
 ┗ボタン類 12個前後
 ┗AF 26 
 ┗イベント 約68x2
 ┗チェック 2程度

■操作
Fマップ選択>インデックス変更判定>変更時再初期化
繰り返し
 ┗イベント開始or終了をチェック
 ┗AFを選択
 ┗AFを設置
 ┗イベントの取得

■イベント
・開始または終了チェック時にAF取得判定
・終了チェックでランドロック解除
・履歴加算

■AF設置時
・AF選択時 他の操作を停止、AF情報表示、配置可能エリア光らせる
・(キャンセル時:各操作停止解除、AF情報を閉じる、配置エリアを戻す)
・AF配置 AFがランドになる際に周囲4つのマナ合計÷2を加算して配置、周囲4つのランドはAFのマナを直加算
・配置終了時(各操作停止解除、AF情報を閉じる、配置エリアを戻す)
・設置終了時イベントを解放
・設置終了時履歴加算
 
■履歴操作
・各操作でイベント操作を記憶
・アンドゥ、肯定を1つ戻す
・リドゥ、履歴範囲内で履歴を一つ進める
・履歴操作中に履歴加算がある場合現在の位置を履歴の末端に変更

■状態の保存復元
SaveURLに現在の状態をパラメータで保存
URLにアクセスするだけで復元可能

鶏が先か卵が先か

・イベントが終了するとAF取得(ない時もある)
・AFを設置してランド生成でイベント生成

ランドが無いとイベントがそもそも発生しないのでAFが先
ポストを配置→ホーム生成→ナンバリング0イベント草人→AF入手

2025年4月27日日曜日

onclickを廃止した話

「Hey!Scripting Cat!モジュール読み込みにしたらボタンのonclickで関数が実行できなった!どうすればいい?」

なるほど良くある話ですね。HTML の onclick 属性は、イベント発生時にグローバルスコープで評価されるため、モジュールスコープ内で定義された関数を直接参照しようとすると、その関数が見つからず実行できません。

じゃあonclickが使えなくなるのかと言えばそうではありません。

モジュール読み込みでボタンから関数を実行する方法

web上のjavascriptにはグローバル領域に直接スクリプトを読み込む方法とモジュール(module)で読み込む方法の主に二種類があります。
今回の目標はボタンを押して関数を実行する事が目的です。
工程は2つあります「ボタンを押す」「関数実行」。これを実現する方法は2つ存在します。


1.window.領域に関数を持ってくる(非推奨)

今までの感覚を変更しないのであれば一番簡単です。
変更するのは関数側です。

■js
function moduleFunction() {console.log('関数実行');}
window.moduleFunction = moduleFunction;

これだけです。htmlも変更不要で簡単ですね。
現在onclickが動かないというのはつまりグローバル領域に関数が居ない為です。
じゃあ何処にいるのかというとモジュールスコープ領域に居ます。

つまり、window.関数名に現在の関数名を代入すればモジュール内の関数をhtml側からonclickで実行できます。折角モジュール読み込みするのであれば(スコープを気にし始めたのなら)一応非推奨ではありますが他の関数等はモジュールにいるので今までの通りの方法「<script src="script.js"></script>」でただjsを読み込むだけより全然良いと思います。
onclickが出来ないならレガシーな通常読み込みに戻そうと考えてしまうより一旦はこの方法で良いと思います。モダンへの入り口は広い方がいい。


モジュールに移行する理由

個人や小規模なサイトであれば必ずしもモジュール読み込みの形式にする必要はありません。ただ、そもそもモジュールで読み込む方法が生まれたという事は理由が、つまり利点があるから実装されました。

最大の理由はおそらく名前の衝突です。グローバルスコープに全ての変数や関数があると衝突して正しく動かなくなる可能性高くなります。

<script type="module" ></script>で直接モジュール領域を作ったり
<script type="module" src="script.js"></script>モジュール読み込みを行うとグローバルスコープを侵さずに衝突しない領域を作ることができます。

次に個々に領域を分断した事でそれをつなぐインポートとエクスポートが便利だからです。
これも結局はスコープの制御がしやすいという話に帰結します。


2.addEventListenerでボタン待ちをする

さて、次はよりモダンな方法です。
まずはボタンにidを付けます(他にも方法はある後述)。
■html
<button id="mybutton">ボタン</button>
■js
function moduleFunction() {console.log('関数実行');}
document.getElementById("mybutton").addEventListener('click', moduleFunction)

これだけです。それほど難しくはなりません。


モジュールのトップレベルのコードは、モジュールが最初に読み込まれ、評価される際に一度だけ実行されます。このタイミングで addEventListener を呼び出すことで、ボタン要素にクリックイベントのリスナーが登録され、ボタンがクリックされるのを待機する状態になります。

これによってボタンが押されるとidで対象のボタンが特定されクリックが実行されたことでmoduleFunctionを実行できます。
少し注意点としては「addEventListener('click', moduleFunction)」の関数名部分(この場合「moduleFunction」)には「()」をつけてはいけません。
関数登録に括弧が付くとその位置で即時関数が実行されてしまいます。

※ボタンを特定する他の方法
id 以外に、nameやclassやタグ名で要素を取得できます。
・class(例: document.querySelector('.mybutton'))
・タグ(例: document.querySelectorAll('button'))


引数を渡したい場合

括弧が無ければじゃあ引数渡せないのかというとそうではなく
document.getElementById("mybutton").addEventListener('click', ()=>{moduleFunction(1)})
とすれば引数を渡す事が出来ます。
これでonclickとはさよならできますね。

関数に飛ばさず直接処理する場合

document.getElementById("mybutton").addEventListener('click', ()=>{ここに処理を記述する})

こうするだけでOK。
さらっと流してるアロー関数部分は前の記事を参照してください。(https://blackstraycatreboot.blogspot.com/2025/04/blog-post.html)
要するに先ほどアロー関数に関数名を記述してるのは無名関数にする事で処理領域を確保して、この領域で改めて関数にキックしてたわけです。
ですから、ここに関数を記述せずに処理を記述すればOK!



余談

ちなみに「addEventListener」は実行時にイベントオブジェクトを渡します。
この場合、'click' イベントに対するリスナーなので、event は クリックイベント に関する情報を保持します。
今回のclick イベントの場合はeventの型はMouseEvent オブジェクト。
MouseEvent は Event クラスの派生型で、クリック固有のプロパティ(例: マウス座標)を含みます。


function moduleFunction(arg, event) {
  console.log(`引数: ${arg}`);
  // イベントオブジェクトの詳細を確認
  console.log(`クリックされた要素:`, event.target);
  console.log('イベントオブジェクト:', event);
  console.log('type:', event.type); // "click"
  console.log('target:', event.target); // <button id="mybutton">...
  console.log('clientX/Y:', event.clientX, event.clientY); // 例: 150, 200
  console.log('ctrlKey:', event.ctrlKey); // 例: false
}
document.getElementById('mybutton').addEventListener('click', (event) => {
  moduleFunction(1, event); // event オブジェクトを渡す
});

こういった情報取得も可能です。
ただまぁ今回は「ボタンを押す」「関数実行」が主題なので今回はこれで終了。

2025年4月24日木曜日

location.search/href/hash:urlから複数のデータを取得する方法

「Hey,Scripting Cat!URLパラメータの取得の仕方を教えて!」


はい、urlからパラメータを取得する方法はいくつかあります
location.href フル URL(プロトコル、ホスト、パス、クエリ、ハッシュ
location.search 「?」以降の全てクエリ名を取得
location.hash 「#」以降の値を取得

URLの指定方法と取り出し方

http://url/a.htm?q=z3z7z9z9z9z9z0z9z8z34z0z9z9z14z1#1z2z3
上記の場合取得できるのは

【href】http://url/a.htm?q=z3z7z9z9z9z9z0z9z8z34z0z9z9z14z1#1z2z3
【search】?q=z3z7z9z9z9z9z0z9z8z34z0z9z9z14z1
【hash】#1z2z3

ハッシュは必ず最後にする必要があります。ハッシュの後にクエリ―パラメータを入れるとハッシュの内容として処理される為クエリを取得出来ません
ちなみに、処理でもハッシュ(hash)ですが、フラグメントと呼んだりもします。

クエリとハッシュの扱い方の違い

さて、取り出し方の前にそもそものurlパラメータに指定方法について
既に上に書いてはいますが2種類あります。

■クエリ(?クエリ名=aaa&クエリ名=bbb)

基本的にはクエリ指定は別のページとして扱います
つまりサーバーや検索エンジンは別々のURL(コンテンツが違うものとして)判定しようとします。
もっと言うとクエリが指定されているのにページの内容が全く同じか少ししか変化しない場合グーグル等検索エンジンなどからは複数のページで同じ内容のコンテンツが提供されていると判断されてランキング低下やインデックス優先度低下のリスクが上がります。
phpなのでページ内容が直接変更されるものは良いですが、javascriptのみで内容を変更するような場合はjavascriptが無い状態で基本ページを判定されるので注意が必要です(同じページなのにurlパラメータ違いで全てが別ページ扱いになり、重複ページのペナルティの可能性が高まる)
他には、次に説明するハッシュと違いリアルタイムでシームレスなURL変更は出来ません。
「location.search = "?q=ppp"」みたいにするとページをリロードをしてパラメータが更新されます。

■ハッシュ(#aaa)

クエリとは逆に同じページ内のリンクとして扱います
アンカーなどでページを上下させる方法などが一般的です。
ハッシュを使ってコンテンツをタブなどで切り替えるなどもありです。
ブラウザの履歴などには一応urlの履歴が残りますが、クライアント側(ブラウザ側)で処理している為、サーバー側からはハッシュの遷移が取得出来ません。
そのためページ遷移の判定を得るためにクエリを指定するパターンも結構あります。
しかし先に説明した通りグーグルなどはクエリ違いの同じページを嫌います。
また、ハッシュはページをリロードせずにリアルタイムでURLハッシュを変更可能です。

■URLの正規化(canonical)

canonicalタグ(カノニカルタグ)を使う事で(基本的にはパラメータなしを指定)パラメータの有無についてこのページが(コンテンツが重複してるページの)正規のページです。と、知らせるタグがあります。
ただし、完全な指定をするというものではないのでurl違いの同じ内容のページはない方が良いです。検索エンジンに知らせるだけで検索エンジンが申告通り判定するかは別問題)。


データの取り出し方

まぁなんも考えず上記のurlをの指定方法をみれば想像つくと思いますが「str.split(”z”)」すれば良いです。もちろん取り出したいデータが数値じゃなくアルファベットなら固定数値でsplitすれば良いです。
ちなみにhrefからパラメータを取り出す事も出来ますが、基本的にはsearch/hashで取り出しましょう。
そうしないとクエリを取り出したかったのにハッシュが取り込まれたりする可能性があります。

■パラメータの頭に区切りを入れる手法
ちなみに上記で区切りの頭にzを入れてますがこれは「q=z3z7z9z9z9z9z0」この「クエリ名=」が邪魔だからです。パラメータの一つ目[0]を必ず捨てる事でパラメータを直接splitして1番目から配列を回すだけで良い。必要なら0番目の要素を破棄も可能。
普通は「q=3z7z9z9z9z9z0」ですが、こうした場合は「=」以降で切り出しした後にsplitをするか配列後0番目の要素から要素[0].split("=")[1]とかでもこの要素0が空指定だった場合、要素[0].split("=")[1]ではエラーするので有無を判定してから切り出す必要がでたりします。
下記で推奨する正しいパラメータの抜き出し方もありますがクエリ名を知ってる必要があります(普通は知ってる)。今回の方法であればクエリ名が何であっても0番目を捨てるので雑にデータを抜き出せます。


正しいURLパラメータと正しい値の取り出し方

正直手間なのですが正しい指定と取り出し方もあります。
ちゃんするならこうした方が良いです。
http://url/a.htm?q1=aaa&q2=bbb&q3=10&q4=30&q5=200#abcd

データの取り出し方

// 現在の URL のクエリ文字列を取得
const url = new URL(location.href);
// 例: http://url/a.htm?q1=aaa&q2=bbb&q3=10&q4=30&q5=200#abcd
// クエリパラメータを取得
const params = new URLSearchParams(url.search);
// 各パラメータの値を取得
const q1 = params.get("q1"); // "aaa"
const q2 = params.get("q2"); // "bbb"
const q3 = params.get("q3"); // 10 (数値)
const q4 = params.get("q4"); // 30 (数値)
const q5 = params.get("q5"); // 200 (数値)
// フラグメントを取得
const fragment = url.hash.slice(1); // "abcd" (# を除去)
// 確認
console.log({ q1, q2, q3, q4, q5, fragment });

厳密な指定や取得が必要な場合はこうしてください。SEO的にもこちらが正しいです。
ただ、やはり少し手間なんですよね。20個以上パラメータが必要だったりするときに全てクエリ名を入れる必要があるし。

ちなみにクエリは重複指定も可能です
http://url/a.htm?q1=aaa&q1=bbb
みたいなパターンです。
const params = new URLSearchParams(url.search);
const q1 = params.get("q1"); // "aaa"
これだと実は1つ目しか取得出来ません。二つ目以降は無視されます。
勿論対処方法もあります。

const q1 = params.getAll("q1"); // ["aaa","bbb"]
手動で切り出す方法はいくらでもありますが基本的にはこの取り出し方が一番きれいだと思います。
なお、getAll は常に配列を返します。1つの中身でも配列です["要素値"]、無ければ[](空配列)です。

非推奨な話

一番最初に説明している通り実は「location.search」は「「?」以降の全てクエリ名を取得します。なので実は「?クエリ名=」って記述しなくてもパラメータを指定出来るし、urlからパラメータを取り出す事も可能です。
例えば上部ではでは「z」で区切っていたけど数字もアルファベットも使い切りたい時に区切り文字がない。そんな時に実はURLに「a.html?aaa?BBB?ddd?123?saf?777777?ss」みたいなURLを作っても別にページにアクセス出来なくなったりはしない。
さらに「location.search.split("?")」みたいな形でデータを取り出す事も可能。

まぁでも「やろうと思えば出来る」だけであって勿論非推奨です。
当然ながら一般的なURLパラメータ以外は検索エンジンにも嫌がられます。

2025年4月23日水曜日

bsc_wrapper.jsを作ってコードを圧縮した話

今回はコードのラップについてのお話です。
少し前の「document.getElementByIdを毎回書くのを止めようかなって話」のほぼ続きです。

オリジナルラッパーを作るyo!

後でモジュールにしようとは思いますがいったんグローバルで。
はい、こちら「ビスクラッパー.js」です。

//bsc_wrapper.js

function wrapElement(element) { const hasProp = (prop) => prop in element; const wrapped = { get si() { return hasProp('selectedIndex') ? element.selectedIndex : undefined; }, set si(value) { if (hasProp('selectedIndex')) element.selectedIndex = value; }, get sl() { return hasProp('selected') ? element.selected : undefined; }, set sl(value) { if (hasProp('selected')) { element.selected = value; // <option> の selected 変更時に親 <select> の selectedIndex を更新 if (element.parentElement && element.parentElement.tagName.toLowerCase() === 'select') { const index = Array.from(element.parentElement.options).indexOf(element); if (value && index !== -1) { element.parentElement.selectedIndex = index; // 選択 } else if (!value && index === element.parentElement.selectedIndex) { element.parentElement.selectedIndex = -1; // 選択解除 } } } }, get ds() { return hasProp('disabled') ? element.disabled : undefined; }, set ds(value) { if (hasProp('disabled')) element.disabled = value; }, get ck() { return hasProp('checked') ? element.checked : undefined; }, set ck(value) { if (hasProp('checked')) element.checked = value; }, get vl() { return hasProp('value') ? element.value : undefined; }, set vl(value) { if (hasProp('value')) element.value = value; }, get ih() { return hasProp('innerHTML') ? element.innerHTML : undefined; }, set ih(value) { if (hasProp('innerHTML')) element.innerHTML = value; }, get cn() { return hasProp('className') ? element.className : undefined; }, set cn(value) { if (hasProp('className')) element.className = value; }, get tc() { return hasProp('textContent') ? element.textContent : undefined; }, set tc(value) { if (hasProp('textContent')) element.textContent = value; }, get hd() { return hasProp('hidden') ? element.hidden : undefined; }, set hd(value) { if (hasProp('hidden')) element.hidden = value; }, get ro() { return hasProp('readOnly') ? element.readOnly : undefined; }, set ro(value) { if (hasProp('readOnly')) element.readOnly = value; }, get rq() { return hasProp('required') ? element.required : undefined; }, set rq(value) { if (hasProp('required')) element.required = value; }, get ty() { return hasProp('type') ? element.type : undefined; }, set ty(value) { if (hasProp('type')) element.type = value; }, get nm() { return hasProp('name') ? element.name : undefined; }, set nm(value) { if (hasProp('name')) element.name = value; }, get id() { return hasProp('id') ? element.id : undefined; }, set id(value) { if (hasProp('id')) element.id = value; }, get st() { return { get bc() { return element.style.backgroundColor; }, set bc(value) { element.style.backgroundColor = value; }, get dp() { return element.style.display; }, set dp(value) { element.style.display = value; }, get co() { return element.style.color; }, set co(value) { element.style.color = value; }, get fs() { return element.style.fontSize; }, set fs(value) { element.style.fontSize = value; }, get wd() { return element.style.width; }, set wd(value) { element.style.width = value; }, get ht() { return element.style.height; }, set ht(value) { element.style.height = value; }, get mg() { return element.style.margin; }, set mg(value) { element.style.margin = value; }, get pd() { return element.style.padding; }, set pd(value) { element.style.padding = value; } }; }, element, getElement() { return element; }, on(type, listener) { element.addEventListener(type, listener); }, off(type, listener) { element.removeEventListener(type, listener); } }; return new Proxy(wrapped, { get(target, prop) { if (/^\d+$/.test(prop) && hasProp('options')) { const index = parseInt(prop); const option = element.options[index]; if (!option) { console.warn(`wrapElement: options[${index}] は存在しません`); return undefined; } return wrapElement(option); } if (prop in target) return target[prop]; const value = element[prop]; return typeof value === 'function' ? value.bind(element) : (hasProp(prop) ? value : undefined); }, set(target, prop, value) { if (prop in target) { target[prop] = value; return true; } if (hasProp(prop)) { element[prop] = value; } return true; } }); } function $i(id) { const element = document.getElementById(id); if (!element) return null; return wrapElement(element); } function $n(name) { const elements = document.getElementsByName(name); return Array.from(elements).map(element => wrapElement(element)); } function $$(query, { preferId = false } = {}) { const idElement = document.getElementById(query); const nameElements = Array.from(document.getElementsByName(query)); if (idElement && nameElements.length > 0) { console.warn(`idとnameに同じ名称あり: "${query}"`); if (preferId) return wrapElement(idElement); return null; } if (idElement) return wrapElement(idElement); if (nameElements.length > 0) return nameElements.map(element => wrapElement(element)); return null; } const $c = console.log.bind(console);

/* // グローバル互換性(オプション) window.$i = $i; window.$n = $n; window.$$ = $$; window.$c = $c; window.apc = apc; window.finishset0 = finishset0; //モジュールの場合(エクスポート) export { $i, $n, $$, $c }; */

こんな感じです。
前回は「const $ = (id) => document.getElementById(id);」で圧縮!!って話をしました。
これだけでもだいぶ平和になるんですが今回はそれを更に便利にした感じです。

例えば

  function apc(){
	var ea=getmaxessence()
	var ma=document.getElementById("material"); var msi=ma.selectedIndex
	var eq=document.getElementById("equipment");var esi=eq.selectedIndex
	var s1=document.getElementById("sp1");      var s1i=s1.selectedIndex
	var s2=document.getElementById("sp2");      var s2i=s2.selectedIndex
	var s3=document.getElementById("sp3");      var s3i=s3.selectedIndex
	var fi=document.getElementById("finish");   var fii=fi.selectedIndex
	
	var pmap=document.getElementsByName("pmap")
	var equp=document.getElementsByName("equp")
	var sp1p=document.getElementsByName("sp1p")
	var sp2p=document.getElementsByName("sp2p")
	var sp3p=document.getElementsByName("sp3p")
	var finp=document.getElementsByName("finp")
	var perp=document.getElementsByName("performance")
	var per2=document.getElementsByName("performance2")
    上記を
    …
    function apc(){
	var ea=getmaxessence()
	var ma=$i("material"); var msi=ma.si
	var eq=$i("equipment");var esi=eq.si
	var s1=$i("sp1");      var s1i=s1.si
	var s2=$i("sp2");      var s2i=s2.si
	var s3=$i("sp3");      var s3i=s3.si
	var fi=$i("finish");   var fii=fi.si
	
	var pmap=$n("pmap")
	var equp=$n("equp")
	var sp1p=$n("sp1p")
	var sp2p=$n("sp2p")
	var sp3p=$n("sp3p")
	var finp=$n("finp")
	var perp=$n("performance")
	var per2=$n("performance2")
    

$i ← document.getElementById
$n ← document.getElementsByName
どちらも二文字でアクセスできるようにラップしました。
varなのはDWが古くてletとか構文エラー入るので…直近のコードはだいたいletかconstです。

今回の目的「無くてもいいけどあったら楽」

jquery使ったことは無いんですがそちらより短くアクセス出来ると思います。
勿論様々なラップがされてるjqueryには機能面では劣ります。

今回の目的としては通常のjavascriptの記述方式の流れを汲む事と短縮しても何のプロパティにアクセスしてるかプロパティ名を知ってれば分かる事。
このラッパーを使うのを止めた場合でもjavascriptの記述に戻れる(と思う)様に考えてます。

jqueryとかはメソッド方式らしく「obj.method()」みたいな感じで動かすらしいです。
ただその記述方法は独特なものになるので今回はオリジナルのプロパティを設定する形にしたかった事もあり、ゲッターとセッターを使ったラップにしてあります。拡張も容易です。

ちなみに、jqueryでメソッド方式の優位点はチェーンを作れるところ
「obj.method().プロパティ1().プロパティ2().プロパティ3()」みたいにする事で一度の記述で複数のプロパティを書き換えたり出来るそうです。

そういうオリジナルの形に進んじゃうと便利だけど普通の記述の構成忘れちゃいそうなので今回のラッパーはjqueryは違う方向性って事です。

ゲッターとセッター

今回実は初めて使いました。
オブジェクトを作って
・引数無しならそのプロパティの値を取得=ゲッター
・引数を代入したらその値をセット=セッター
ざっくり言えばそんな感じです。

他にも一応方法はあって、生エレメントDOMを設定を直接書き換える方法もあるにはあります「Object.defineProperties(HTMLElement.prototype…」みたいな感じです。
直接的な挙動に短縮プロパティを設定する方法なのでこちらなら色々面倒なことを考えなくても良くなりますが危険度の方が高いと思います。まぁ使わなかった方法の話はいいでしょう。閑話休題。

前回の記事の方法であれば「const $i = (id) => document.getElementById(id);」
関数名を合わせるとこの様になり「$i("idname")」でアクセスできます。
今回の方法でも同様に「$i("idname")」でアクセスできますが戻ってくるものが違います。
let a=$i("idname")
こうした場合、前回と今回ではaに入るものが違います。
前回の方法は生エレメントを直接取得していますが今回はオブジェクトを取得しています。

つまり、前回の方法なら全てのメソッドやプロパティにアクセス出来ますが、オブジェクト側は設定しないと使えないのです。
最初にゲッターセッターを作ったあとに通常プロパティ名でアクセスしたら何も取得出来ませんでした
let a=$i("idname")
console.log(a.selectedIndex)
こう記述した時にaの中身は
前回:a=生エレメント
今回:a=オブジェクト
という事です。セッターゲッターに通常のプロパティと同じ名前のプロパティ名を設定すればどちらでも使えるように出来ますが全て列挙するのは現実的ではありません。

ではどうするかというとゲッターセッターに短縮プロパティを登録して、登録外のものは生エレメントに転送する方法です。
となると、生エレメントは常に持っておかなければならないという結論に。
さらに言えばオブジェクトから生エレメントを取り出せるようにしました。
「let b=$i(id).element」といった感じですね。

命名規則の話

今回の目的としてはとにかく頻度が高く、単語が長いものを短くするという目的があるので
基本的に二文字に圧縮しようと決めていました
・単語が二つあるものはその頭をとる(innerHTMLならih)
・母音頭2文字(di sa bledみたいに区切りってds)
・母音が無ければ先頭2文字(styleとかならst)
基本的にこのルールによってプロパティ名を決めました。


結局ラッパーって何?

簡単に言えばオリジナルオブジェクトです$i(id)は生エレメントをオリジナルオブジェクトで包み込んでそれにプロパティを生やしてるだけです。
指定のプロパティでアクセスがきたらそれを内部のエレメントに投げて結果貰ったりDOMを書き換えたりするって事。

2025年4月22日火曜日

script type="module"に変更して外部ファイルを読み込む

今回のお題はメインとなるjsの可読性を上げようかなって話
まぁそういう意味では前回の「document.getElementById」を毎回書くのを止めようかなって話と同じですね。

javascriptで長いコードを外部化する方法

まず、古来からのjavascriptの読み込み方法というのは

<script type="text/JavaScript" src="./main.js"></script>

こんな感じです。
ですがこの読み込み方法では外部ファイルjsを参照出来ません。
jsのファイルの読み込み方法から変更する必要があります。

<script type="module" src="main.js"></script>

この様にタイプの指定をmoduleという指定方法でjavascriptを読み込みます。

①従来の読み込み方法

違いとしては古来の方法は「window.」というブラウザのグローバル空間で定義されていています。例えば
window.a=10
var a=10
と、どちらで定義しても同じです。「window.」のプロパティの形で登録されていて暗黙的に省略してるだけです。
通常の関数もそうです。

function aaa() { console.log("こんにちは");}
console.log(window.aaa); // 関数 aaa
aaa(); 


じゃあ、node.jsの場合は?となる人も居るかと思いますがnodeでは「global.」オブジェクトに定義されます。ですので、

global.myVar = 10;
console.log(global.myVar); // 10
console.log(myVar); // 10

nodeではこんな感じになります。


話しを戻して、例えば、
<script type="text/JavaScript" src="./main1.js"></script>
<script type="text/JavaScript" src="./main2.js"></script>
と、二つ読み込んだ場合はどちらにコードを記載してもどちらかでも変数にも関数にもアクセスできます。
main1に「function a(){ console.log("test");}」と記述しているならば、main2で「a()」と書くだけでアクセスできます。

ファイル自体は分かれていても、コードの処理領域・スコープが同じなのです。

更に言えば読み込んでいるhtml側もそうです。
例えば、
<button onclick="a()">ぼたん</button>
はボタンを押すだけで「a()」を実行できます。

②モジュールの読み込み方法

次にmoduleですが、こちらは処理領域がjsのファイル内に閉じられています。
<script type="module" src="main1.js"></script>
<script type="module" src="main2.js"></script>

古来の方法通り普通にhtmlに読み込んでるように見えますがhtml側とモジュール側では領域が断絶されています。jsのトップレベルにあるコードは実行されますがそれだけです。

例えばmain1.jsに「function a(){ console.log("test");}」記述されている時に
html側から「<button onclick="a()">ぼたん</button>」を押しても実行出来ません。
「window.」領域にモジュールの内容がいない為html側からアクセスできません。
対応については順番に説明します。

②-1:モジュールのスコープ

例えば、main1.jsに「var b=10」を定義した場合、この変数bを使えるのはmain1.jsだけです。html側からも他のjsファイルもアクセス出来ません。
関数を定義した場合も同様で、「const c = (x) => x+1」のように定義した場合、html側からも他のjsファイルからもアクセス出来ません。


②-2:他のjsファイルへのアクセス

htmlとのやり取りの前に先に他のjsとのやり取りを先に説明します
現状ではmain1.jsは完全に独立していて他のjsに影響されないしmain1.jsも他のjsへは影響を与える事は出来ません。
どうするかというと、他のjsへアクセスするにはエクスポートとインポートが必要になります。
インポートはエクスポートされているものしか参照出来ません。
つまりjsのファイル間で合意が必要になります。「使って良い処理を出す側」と「使いたい処理を受ける側」これが一致して初めて外部のファイルを参照して利用できます。
インポート側が一方的に好きな変数や関数を参照出来ないという事です。
この方式はコーディング的にスコープ周りは強固ですが面倒臭いのは否めない。


②-2-1:エクスポートする側

まず、エクスポートして貰わないとそもそもインポートも出来ない形式なので先にエクスポートの方法について。

1.外部参照させたい変数や関数の頭全てに「export 」を記述する方法
export const aa=1
export const bb=2
const cc=3
export function dd() {}
export function ee() {}
function ff() {}
「export 」が付いていないものは外部からは参照出来ません。
コードを流してみた時に頭に「export 」があるかどうかで外部から見れるかが直ぐに分かります。

2.一番最後に「export 」対象を列挙する方法①
const aa=1
const bb=2
const cc=3
function dd() {}
function ee() {}
function ff() {}
export { aa, bb, ee,ff};//まとめて指定
こちらは最後に対象の変数や関数を指定する方法です。各定義の頭に「export 」を付ける必要はありません。既存のコードがある場合は全ての対象にexport を付けなおす必要はないので楽かもしれません。
逆に言えば流し見した時にどれがエクスポート対象かは分かりません。最後のリストから名前で検索して内容を確認する必要があるかも。
また、ある程度量がある場合列挙するのがそれはそれで面倒かもしれない。

3.一番最後に「export 」対象を列挙する方法②「デフォルトエクスポート」
const aa=1
const bb=2
const cc=3
function dd() {}
function ee() {}
function ff() {}
export { aa, bb, ee,ff};//まとめて指定
export default { bb, dd, ee,ff}; // デフォルトエクスポート
モジュールにつき1回だけ可能な指定方法、手間的には対象を列挙①と変わりません。
インポート時に楽が出来るかどうかの違いです。詳細はインポート側で説明。

②-2-2:インポートする側

さて、エクスポート対象が決まったらやっとインポート側で参照できるようになります。
エクスポート側では対象全てをエクスポートするみたいな事は出来ませんがインポート側は多少は柔軟でエクスポートを一括で取り込むことも可能

1.エクスポートされた変数関数をそのまま使えるようにする
import { aa, bb, dd } from './db.js';
console.log(aa);
console.log(dd());
エクスポートされた変数や関数の名前を指定して名前を変えずそのままそれを使えるようにします

2.一括取り込み①「export default」読み込み
import datas from './db.js'; 
console.log(datas.aa);
console.log(datas.dd());
オブジェクトの様に一括で取り込み、プロパティの様に対象全てを利用する事が出来ます。
オブジェクト名は好きな名前を付ける事が出来ます。
記述時に「{}」が不要な所は注意
モジュール1つにつき1回だけ可能。

3.一括取り込み②名前空間で「*」全指定する
import * as datas2 from './db.js';
console.log(datas2.aa);
console.log(datas2.dd());
エクスポート対象を*で全て対象にしてasで名前を付けて纏めています
こちらもオブジェクトとプロパティの様に対象対象全てを利用できます

4.エクスポートされた変数関数の名前を変更して取り込む
import { aa as zz, dd as yy} from './db.js';
console.log(zz);
console.log(yy());
エクスポートされた変数や関数の名前を指定した後にasで変数関数の名前を変更して取り込みます。

5.一旦全て取り込んで個別に取り出す(分割代入)
import * as datas2 from './db.js';
const { aa, dd } = datas2 ;
console.log(aa);
console.log(dd());
一旦全て取り込むのは既に紹介しましたがオブジェクトとプロパティのようなアクセスでした。分割代入を使えば元の名前で変数関数を取り出して使う事ができます。

html側からアクションを起こす方法

コード側のやり取りの説明が終わったので今度はhtmlからのアクションはどうするのかについてです。
「window.」空間外にjavascriptがいる為アクセス出来ません。しかもjsと違ってhtml側からではインポートの構文が使えません。解決方法は主に2つ。

①「window.」空間に来てもらう

html側からアクセス出来ないのは「window.」空間に対象の変数も関数も居ない為アクセスできないのですから、js側で「window.」に定義してhtml側の空間に下りてきて貰えばいいのです。
■js(module)
function dd() {}
window.dd=dd;
■html
<button onclick="dd()">ボタンをクリック</button>
こうする事でモジュール空間にあった変数や関数を「window.」空間に定義してhtml側からでもアクセスできるようになります

②イベントリスナーでjs側からhtml側アクション待ちで待機する

■js(module)
function dd() {}
document.getElementById("pp").addEventListener("click", dd);
■html
<button id="pp">ボタンをクリック</button>
onclickと違い一方方向にアクションが進んでいくのではなくjs側で待ち状態にしてhtml側のアクションの後にjs側でアクションされた対象を特定して実行する
どのボタンかの特定が必要に足る為idなどの識別子は必ず必要になる。

③「window.」空間の上にオブジェクトの空間を挟む

■js(module)
function dd() {}
window.obj1= window.App || {};
window.obj1.dd= dd;
■html
<button onclick="obj1.dd()">ボタンをクリック</button>
これは①のちょっと安全版みたいなもので完全なグローバル空間の「window.」の上にオブジェクトを作成して、一階層浮かせたグローバル空間で処理をする方法です。
一階層浮かせているのでonclickで実行する時にオブジェクト名も必要になります。

━━━━━━━━━━━━━
■終わりに
現状のjavascriptの一部を分離するために色々と調べたんだけど今から分割するのは手間が掛かり過ぎるかも・・・うーん困った。
調べる前は外部ファイルに定義して必要なものだけ引っ張ってこればいいのかなと思っていたけどエクスポートとインポートが相互で処理が必要で思ったより簡単ではなかった。
修正するかもしれないけど一旦保留にして次何か作る時はちょっとモジュール思考で考えてみよう。

2025年4月21日月曜日

document.getElementByIdを毎回書くのを止めようかなって話

document.なんちゃら長すぎもう無理・・・

document.getElementById("idname")、document.getElementsByName("name")
・・・長いよ!!こんなの毎回書くの面倒すぎる。
こんなもの・・・これをこうしてこう!!

const $ = (id) => document.getElementById(id);

はい。今回は最終的にこういうことが出来るというお話です。
タイトル通り、 document.getElement・・・をいくつも書くのが面倒なので関数にすればいいかぁとは思ってたけど何か短く書くかーとなって最終形態が上記通りです。

javascriptの「$」はどんな存在なの?

「$は変数定義出来る数少ない記号なだけでアルファベットと同様にただの識別子に使用可能なただの文字扱い」です。
なので、
const $ = (id) => document.getElementById(id);
const d = (id) => document.getElementById(id);
ただの変数名ですのでこの二つに差はないです。記号なせいで特別に見えますがアルファベットと同じです。一旦$にしたのは変数名が重なる事がないからです。別にdでもconst idgetとかでもなんでも良いです。

シングルクォート囲みの$には干渉しないの?

しません。
「`文字と${aaa}を一緒に記述出来る`」使い方はこんな感じですね。
シングルクォート囲みの「${変数や処理}」は全く干渉しませんletやvarとはまぁちょっと違いますが特定の条件下の決められた記述なので変数定義とは全く関係ないです。

変数の識別子に使える文字列ってそもそも何?

$以外に変数に使える記号とかってあるの?って話ですよね。
一応あります。折角なのでそもそもの変数の定義可能な文字列について。

■JavaScript の変数名
【a-z, A-Z】アルファベット
【0-9】数字(0-9)※ただし、最初の文字には使えない。
【_】アンダースコア
【$】ドル記号
【Unicode 文字】(例: 日本語のひらがな、カタカナ、漢字なども可)。

■ルール:
①数字以外は変数名の1文字目に使える(そのためこの中ではある意味数字は特殊)
②予約語(例: function, let, const など)は変数名に出来ない
③大文字小文字の区別される(例:nekoとneKoは別の変数として定義される)

という事は
const _ = (id) => document.getElementById(id);

って記述も可能って事ですね。
さらには日本語も使えるので実は
const 取得 = (id) => document.getElementById(id);

って記述も可能です。なんなら絵文字でも可能。
ただまぁ、可読性がどうかはさておき。
変数に日本語が使える=Unicode 文字が使えるっていうのはjavascriptは最初からそういう仕様だったそうで…、だいぶjavascript使ってるのに先ほど初めて知りました。

逆にそれ以外の記号関係は変数名に使用できない

@, #, %, &, *, +, -, /, !, ?, =, <, > などがありますが
「@」はデコレーター
(デコレーターは、クラスやメソッドなどの宣言の前に @ を付けて、その宣言に機能を追加したり装飾したりするための特別な宣言。クラスやメソッドに、ある機能を付与するような処理を記述し、その処理をデコレーターでアタッチすることで、再利用可能な形で機能を追加できる機能)
「#」はプライベートなプロパティやメソッドの宣言
「%」は余り「+」は加算「‐」は減算「!」はnotなどなど比較や計算の演算子となっていて他の記号は変数に使えません。

そうしなければならないという事ではないが記号文字の使い道

つまるところ「$」と「_」の二つですね。

■アンダースコア(_):
プライベート変数や一時変数を表す。
例:_private, _temp, _(ループで使わない変数)。

■ドル記号($):
DOM 操作やユーティリティ関数のショートカット。
例:$, $element。

だいたいそんな感じで使われていますが別にそう決められているわけでは無いです。
またアンダーが最初に来るときは意味がある場合が多いですが「best_cat」みたいな変数の単純な単語区切りにも使われてます。こういう時は特別な意味はないです。

jQuery には気を付ける

有名なjqueryは既に$が定義されています。なのでjqueryを使う予定があるなら「$」単品で変数名を定義をするのはやらない方が無難です。特にグローバル変数なので混在するとグローバル範囲が汚染される可能性があります。
まぁ上記で説明した通り「$」自体は別に特別な機能って事は無くただの変数名である以上別の変数名で上書きして書き換える事も出来るにはできると思いますがそれはそれで大変だと思うのでよしなに。

2025年4月11日金曜日

アロー関数(無名関数)&三項演算子を学んだ話

 結構雑に無名関数は使ってたんだけどまだ知識がレガシーだったので備忘

一旦コード書きますね。

const isInput = bo.tagName.toLowerCase() === 'input';
const getText = () => isInput ? bo.value : bo.textContent;
const setText = (text) => isInput ? (bo.value = text) : (bo.textContent = text);

最近のコードがこれです。

そもそも無名関数って何?アロー関数って何?

「そもそも無名関数って何?」という人もいると思いますが、これはなんてことはない、名前を付けない関数のことです。
アロー関数は無名関数の一部です。ほぼニアリーイコールです。

通常の関数は 「function 関数名(引数) { 処理; return 戻り値; }」 という形です。そして、定義した関数を 「変数 = 関数名(引数)」 のように呼び出して使います。

たとえば:

function aaa(x) {
    let b = x + 1;
    return b;
}
console.log(aaa(10)); // 11


さて、無名関数の説明をする前に1つだけ補足すると、「関数は変数に代入することができます」:

変数 = function 関数名(引数) { 処理; return 戻り値; };

たとえば、こんな風に関数を定義して変数に代入できます:

const bbb = function aaa(x) {
    let b = x + 1;
    return b;
};
console.log(bbb(10)); // 11


変数 bbb に代入したことで、bbb は関数として呼び出すことができます。つまり:

変数 = function 関数名(引数) { 処理; return 戻り値; };

というのは、変数に「関数」が入ってるということです。以降、代入先を「関数」と書きますが、実際は const 変数名 = ... の形で使います。

そして、関数を代入するとき、定義した関数の名前(ここでは aaa)が不要なら、いっそ名前を省略しようというのが無名関数の第一段階目です:

関数 = function(引数) { 処理; return 戻り値; };

たとえば:
const bbb = function(x) {
    let b = x + 1;
    return b;
};
console.log(bbb(10)); // 11


関数名を省略して、直接 function(x) と書く形です。これで、名前をつけない関数、つまり無名関数が作れます。

ちなみに、このように関数内で別の関数を定義することもできます。

たとえば:

function outer() {
    const inner = function(x) {
        let b = x + 1;
        return b;
    };
    return inner(10);
}
console.log(outer()); // 11

ここでは inner が outer の中で定義されていて、外からは見えません。

第二段階として、今度は function を省略します。代わりにアロー(=>)を使って、アロー右側が関数の処理であることを明示します:

関数 = (引数) => { 処理; return 戻り値; };

function がなくなるだけで、かなりスッキリしますね。

たとえば:

const bbb = (x) => {
    let b = x + 1;
    return b;
};
console.log(bbb(10)); // 11


ここからは、1行で書く場合に限りますが、さらに短く記述することが可能です。第三段階は:

関数 = (引数) => 処理

たとえば:

const aaa = (x) => x + 1;
console.log(aaa(10)); // 11


通常、処理結果をいったん変数に入れて、それを戻り値として返します。でも、1行の場合は『処理結果がそのまま戻り値になる』と分かるので、return を省略できます。また、1行だけなら『=> の後が戻り値の式』と確定するので、{} も外せます。

{} を外すと、1つの式しか書けなくなるため、return を書くとエラーになります。つまり、『処理結果が直接戻り値になる』ので、return は不要となります。

無名関数の話をまとめると・・・

⓪function aaa(x) {let b = x + 1; return b;}
①const bbb = function aaa(x) { let b = x + 1; return b; };
②const bbb = function (x)  { let b = x + 1; return b; };
③const bbb = (x) => { let b = x + 1; return b; };
④const aaa = (x) => x + 1;

要点としては
①関数は変数に代入できる
②関数名が不要になる
③アロー関数で functionも省略 できる
④1行限定で括弧とreturnも省略できる

三項演算子

【条件 ? 真の場合 : 偽の場合】
簡単に言えば「if、else」文の短縮記述ですね。
感覚としてはエクセル関数のifの記述に似ています。

最初に記述した「isInput ? (bo.value = text) : (bo.textContent = text);」はisInput がtrueかfalseになります。
これによって真ならコロンの左側が、偽なら右側の代入処理が行われます。

もう一つ「const getText = () => isInput ? bo.value : bo.textContent;」
こちらはreturnがあります。アロー関数を使って三項演算子を使った場合、上記なら真の場合、インプットボタンのバリューが返り、偽ならボタンのテキストが返ります。

アロー関数の一行記述の場合、直接returnが返ってしまいますが簡単な計算や簡単な分岐returnぐらいなら可能という事です。

2025年4月7日月曜日

node.jsのmjs(esm)とcjsってなーに?

mjs(esm)とcjsの違い

処理速度が爆速なので重たい処理をスクリプトで作るならnode.jsだなって事をこの前記事にしたんですけど、自分のコードを変換して貰った時に「{"type": "commonjs"}」って内容の「package.json」を作ってねって言われたんですよ。

色々調べた結果node.jsは現在二つに分岐していて、esmとcjsの二つの記述方法があるという事が分かりました。

cjs

まずcjsは「CommonJS」の略で、要するに一般的なjavascriptの記述方法です。

mjs

そして、esmはECMAScriptModules(エクマスクリプトモジュール)の略で「Ecmaインターナショナル」といものがあり、それによって標準化されたJavaScriptの国際規格という事らしい。

このECMAは大元をたどると「欧州電子計算機工業会(英語: European Computer Manufacturers Association)」の略だったが今は関係ないただの単語としてだけ文字が残って「Ecma」みたいな記述で良いらしい。

要するにEcmaという国際規格にしたjavascriptって事です

2024年今現在のnodeの仕様

v12以前「.jsのデフォルトはCommonJSだった時代」
v14以降「.cjsやpackage.jsonでの明示が必要になった」
v22以降「.jsのデフォルトがESMになった」←今ここ

というわけで、一旦最初の疑問「json」って何?って所はおおよそ理解しました。
そしてnodeが規格を判定する時に最初に判定するのは拡張子らしいので仮にどっちがデフォルトであっても拡張子をcjsにするか、mjsとすれば確実のようです。
一括でファイルのタイプを管理するときは「json」ファイルを作ればOK!

javascriptって今どうなってるの?

cjsは古い記述方法と言ってしまえばそれまでですが、過去のjavascriptと現在のjavascriptを結ぶ規格。
一言で言えば、web記述ベースのnode特化コード。
esmはwebとnodeを共通化しようという未来規格です。nodeがこちらをデフォルトにしたのはその意識が強いという事でしょう。
今の所cjsの記述されたパッケージなども多いですが今後はesmが主流になるのかもしれないです。
つまり、esm「ブラウザネイティブ」かcjs「Node.js特化」かの違い

まとめ
ESM: ブラウザ+Node.jsの未来標準、非同期・静的解析が強み。
CJS: Node.jsの歴史的規格、同期的でサーバーサイドに強い。
ESMがモダン、CJSがレガシー。

今からjavascriptを勉強するならesmベースで勉強しつつ、cjsに対応できると良いという感じ。

2025年4月6日日曜日

LEFT ALIVE(レフトアライブ)を遊んでみた

 まず、デフォルト難易度が難しすぎる
「STANDARD:スタンダード」でとにかく死にまくる
購入前にどんなゲームかPS公式の『LEFT ALIVE』 ストーリートレーラーも見て

確認した上で面白そうと思ったんですが、想像以上の難しさ。
フロントミッションやった事ないけど面白そうだなと思って購入しましたが開始1時間くらいでかなりそういうゲームじゃないという洗礼を受けます。
ぱっと見ステルスゲームに見えますしジャンルとしてはまぁ間違いでもないんでしょうけどサバイバルゲーム・・・いや、死にゲー」です!!

ちなみに人によっては気にすると思うので言語対応は「日本語は字幕のみ」です。
言語パックとかはありません。ちょっとよそ見をすると内容が抜けちゃうのでムービーもちゃんと見ないといけません。

■デフォルトの難易度が高すぎる

難易度は5種類
・CASUAL:カジュアル:お手軽
・LIGHT:ライト:簡単
・STANDARD:スタンダード:標準
・ADVANCED:アドバンスト:上級
・HOPELESS:ホープレス:絶望

もしこれからやるのであれば絶対にカジュアルで遊ぶのをオススメします
このゲーム標準という意味は「地獄を見る覚悟がある」です。
普通のプレイヤーならスタンダードを選ぶと思いますが敵は全員超固いです。
ヘッドショットですら4・5発必要になります。
敵の射撃精度もかなり高いです。発見されてる状態なら少し横切った僅かな時間でも射貫いて来ます。
殆どの場合は敵同士が複数でカバーしており単体の敵を削って道を開けるのも難しい。
しかも全体的に敵が多く、ヴァンツァーというロボの射程に入ると地獄です。
敵の感知能力が高くこちらを視認していなくても近くにいると探索モードに入ります。しかも大抵の場合プレイヤー側にほぼ真っすぐ来ます。
敵が軍人で耐久が高いは分かりますが、じゃあ主人公も軍人や警察だったりするのに、耐久力ぺらっぺらなのは・・・。仮に敵のスーツにそこまで耐久があるなら敵から奪えてもいいでしょ。

後から知ったのですがこれでも敵は弱体化してこの難易度なんです。
初回プレイした人たちの阿鼻叫喚が聞こえてきそうです。

このアプデ情報の通りカジュアルは難しすぎて後から追加された難易度です。

難易度 内容 備考
CASUAL ■シューティングライクなプレイを楽しみたい方向け
・プレイヤーの攻撃力が増加し、受けるダメージが大幅に低減されます
・所持できる銃弾の最大数が増加します。
・ヘッドショットおよび背後からの打撃攻撃の威力が強化され、一撃で大きなダメージを与えられます。
・搭乗するヴァンツァーの耐久値が強化されています。
アプデ実装された
LIGHT ■ほどよい緊張感を楽しみたい方向け
・プレイヤーの受けるダメージが低減されます。
・所持できる銃弾の最大数が増加します。
・敵兵の体力が低下しています。
・ヘッドショット及び背後からの打撃攻撃を行った場合でも致命的なダメージを与える事は出来ません
-
STANDARD ■緊張感と達成感を楽しみたい方向け
・敵兵の体力と攻撃力が、標準的な値に設定される
・ヘッドショット及び背後からの打撃攻撃を行った場合でも致命的なダメージを与える事は出来ません
デフォルト難易度
ADVANCED ・敵兵の体力と攻撃力が、高めに設定されています。
・ヘッドショット及び背後からの打撃攻撃を行った場合でも致命的なダメージを与える事は出来ません
・所持できる銃弾の最大数が制限されます。
-
HOPELESS ・敵兵の体力と攻撃力が、非常に高めに設定されています。
・ヘッドショット及び背後からの打撃攻撃を行った場合でも致命的なダメージを与える事は出来ません
・所持できる銃弾の最大数が大幅に制限されます。
-

カジュアル以外には以下文言があります

「敵の行動の特性には、いくつかのバリエーションがあります。
よく観察すれば、安全にその場を切り抜ける方法が見つかるかもしれません。
敵と戦い、倒すことは、ひとつの手段でしかありません。
可能な限り、戦うことを避ける方法を考えましょう」

自分がプレイしていての敵の行動の気づきとしては敵は「索敵をする敵」と「持ち場に滞在し続けようとする敵」に分かれていて、かつドローンは反応次第一気に集まります。

スタンダードプレイでもドローンはをつって狩る索敵をつって倒す持ち場から動かないならヒットアンドアウェイで大抵あんとかなりますが弾も減るし何よりも時間が掛かりすぎる。
観察すれば「まぁまぁ打開できますが本当にただただ無駄に時間が掛かりすぎる。」

一応打開行動としては「ショットガン超近距離」なら一発で倒せます。
スライディングで敵に触れれば敵が吹っ飛びダウン状態となり、パイプ等打撃武器で基本的には一撃で倒せます。

ただ、スライディング&パイプで一撃で倒せない敵が登場したところで「スタンダード」プレイを私は諦めました。
本当に時間が掛かり過ぎてやりがいとかよりも苦痛やストレスの方がはるかに上回る。
一発撃ちアラートが消えるまで(警戒解除まで)待つなど地味な方法などで命からがら進行しても達成感や面白さよりも、やっと終わった疲労感の方が強い。


■操作性や仕様が良くない

■スライディングが敵方向を勝手に避ける
しかもスライディングの仕様も本当に良く無くて敵に真っすぐ向かってダッシュしてる状態からスライディングしているのに敵をよけてスライディングが発生する(なんなの本当に…)。
初期ボタンがL3なので押し込み時に横にぶれてるのかと思って屈むを×ボタンにしましたがやはり単純にスライディング発生時に方向が調整されるらしく上手く当たりませんでしたので仕様みたいです。

■壁端のカバーアクションがガバガバすぎる
壁に張り付いてる状態で移動してるのに壁端でキャラがはみ出している
敵もそれに気づくし隠れているのに銃弾が当たります
じゃあちょっと奥めに隠れるとカバーから銃を構える動作が出来ません。
壁端で銃を構えようとしてるんだからどうにかして欲しい。

■壁張り付きと前転
壁張り付きが「×ボタン」、前転が「×ボタン+方向キー」緊迫してプレイしてるため移動から直ぐに物陰に隠れたい。移動して物陰に隠れ・・・前転!敵の目の前でハチの巣などがぽんぽん起こります。
吸着も微妙な判定があり、勝手に壁張り付きが解除されたり、隠れてのが見つかり撃たれてる時に移動キーを押しても壁張り付きが解除されずハチの巣にされたり・・・
小さい障害物と建物が隣接してる時に、小さい障害物に壁張り付きをしてる状態で建物側に移動キーが入るとなんと立ち上がってしまう。当然見つかるし撃たれる。

■打撃武器の耐久性
唯一の救済にも見えるスライディング&殴打ですが耐久値が無くなると使えなくなります。
キツイ・・・。

■配置変更で周囲に敵がいきなり復活する
救出するためにどうにかやっと敵を排除していったのに「敵の配置が変更されました」という無慈悲な通知とともに敵が周囲に現れる。えぐい。

■残骸などを拾う時にどれを拾うかメニューが邪魔
いいから全部拾え!あんまり立ち止まってる時間はないんだって!!という場面がちらほらあります。実際問題として一部だけ拾うをいう事もなかったのでこのメニュー表示要らなすぎる。仮に重量オーバーなら重い方を残して拾って欲しい。

■射線が通らない
無駄なリアルの追及というか照準がどう考えても射線が通っているのに障害物に邪魔させる。「赤×」がでてるのは辛うじてわかるけど出ない時もあるので一方的に的に敵の銃弾ばかり当たる

■リソースを残そうとした場合の行動
スタンダードの難易度で回復アイテムや銃弾など止血パッチすら使うのが勿体ない
リソースを節約したい場合プレイヤーがどうするかというと死ぬ事です
デフォルトの難易度が高すぎて、ゾンビアタックをして一番リソース消費が少なかったのを採用するだけです
生きようと思いません。次の自分が何とかしてくれる。
出血したら止血パッチなんて使わずに今回はしょうがなかったなぁと思うだけでした。
ポーズメニューにロードが無く、タイトル画面に戻るしかなくそれも面倒なのでじゃあ死ぬか…テンポ悪いなぁという感じ。

■オートセーブは基本意味が無い
たまにセーブポイントじゃない所から再開されますが死んだらほぼほぼセーブポイントのセーブがロードされます。
また、逆にへんな場所でされたオートセーブは死亡時にリトライとexitなのですがロードが無いのが結構ストレス。なんで何かあったらタイトル画面に戻されるんだ・・・。

■敵の感知範囲が広い
ちょっと視界に入ったり、視界に居なくても走ったり、歩いても(たぶん音に反応している?)気付かれる。
また兵士が周囲に居ない戦車の真後ろなのに反応したりする

■索敵センサーの微妙さ

敵をマークする索敵センサーがありますが点滅形式で見づらいのはまあ良しとしても、時間がたつとマークは消失します。作れる索敵センサーに限りがあるのにえぐすぎる。しかも先に述べた通り敵の行動を観察に時間がかかります。
どうするか。セーブポイントからの極力しなない様に偵察と特攻です。
アイテムの場所、敵の数と場所を探るのに一旦捨て身で確認して死に戻り対応です。

索敵センサーを作るのは材料と時間の無駄」です。この時点でゲーム的にはこれでいいのかという感じはします。

■救出が困難すぎる

更にゲーム中には救出対象が登場します。
プレイヤーを生かすのだけでも何時間も掛けて移動してるのにかなり難しいです
更に更に救出対象の場所に到達するまでに時間制限があるパターンがあって、上で述べた通り無駄に時間がかかるのに時間制限とかたどり着けません。

先に述べた通り、敵の配置変更が30分とか1時間かけて周囲をクリアリングしたのに敵が復活して萎えてしまう。

■スタンダードからカジュアルに難易度変更すると

別ゲーです!スタンダードからやっていれば超人になった気分になります。
敵の銃弾を受けてもハチの巣にされてもこんなに喰らわないのかよってくらいプレイヤーが固くなります。さらにヘッドショットでちゃんと敵が一発で死にます。

クリエーターがこれくらいの難易度が普通と作った難易度で遊べないのは心苦しい所はあるけども、チャプター4まではスタンダードで行ったので許して…。
あとアップデートで追加されたカジュアルですが英断だと思います。
遊びやすいし、探索がちゃんと出来るようになって落ちてるアーカイブなどもちゃんと拾えるし、市民の救出なども出来るようになりました。

■ゲームとしてフォロー出来る所はあるけれど…

このゲームはステルスゲームではなくサバイバルゲームというジャンルになっています。
不利な状況からどうやって打開するのか、自分がピンチの時に誰かを助ける事が出来るのか、持ってる銃弾やアイテムのリソースをどう振り分けてプレイするか
と、言ったテーマがあるようです。
これだけ聞くと確かにと思う事もあるし面白そうです。
トレーラーを見た時にフロントミッション知らなくても面白そうと思ったので方向が完全におかしいわけじゃないはず。

ただ、蓋を開けてみればデフォルトの難易度スタンダードがあまりにも不利すぎて圧倒的死にゲー、圧倒的理不尽。
敵も同じ人間なのに(ボスでもない一般兵が)プレイヤーより圧倒的に強すぎる。
ヘッドショット4発も耐えられるとか人間じゃない。

元からファンの人たちはこういうのに慣れているのかと思ったら結構散々な事を言われていて可哀想。

キャラデザやモデリング、シナリオや落ちてるアーカイブとかストーリーも良いと思います。
問題があるとすれば難易度。しいてはゲームデザインでしょうか。難易度部分設計が厳しすぎた。
カジュアルを遊ぶと分かりますが難易度が緩和されるだけでかなり評価が良くなると思います。
折角索敵センサーでマーキングした敵が分からなくなる所だったり流血要素、エリアをクリアしたのに逆に配置変更で敵がリセットされるなど結構難易度爆上げです。
難易度を上げる調整をするにしても操作性をわざと悪くして調整する方法はただの理不尽調整です。カバーアクションで身を乗り出してないのにはみ出てて敵に見つかる・カバーアクション中なのに銃弾があたる。そして画面に対してこちらからの射線が非常に分かり難く射線が通らない。
「プレイヤー側に隠れる意思があって隠れる動作もしているのにも関わらず敵が反応する」「プレイヤーの画面では確かに敵にポインタがあるにも関わらず射線が通っていない、しかもその射線がどうなっているのかも非常に分かり難い」プレイヤーが努力しているのにシステムが阻害するのはゲーム的に不満の方が大きい。
逆にこれと相対するようにスライディングが雑に強すぎるよくわからない調整。

難しさに対する回答が返ってこない感じで達成感よりも「ただ理不尽」からくる疲労感の方が強くなっていてうーん。
ゲーム作るのって難しいですね。

■カジュアルは普通に面白かった

操作性・仕様の問題などはまぁありますがカジュアルなら許せる範囲かなと思います。
スタンダードだと進行条件が厳しすぎて諦めるような所もどうにか出来ます。

先に述べた通りキャラデザやモデリング、シナリオや落ちてるアーカイブとかストーリーも良かったです。最後のヴァンツァーにいっぱい乗れる所も良かったですし、最後の方でシナリオがちゃんと畳まれ行くのも良かった。
マルチエンディングみたいなのですが既にスタンダードをやった時のあの苦痛感を思うと1周で十分かなぁという感想。

前評判とか知らずに単にトレーラーをみて面白そうと思ったゲームでしたが「カジュアルなら楽しめる」と思います。普通におすすめ出来ます。
難易度がピーキーすぎるのでスタンダード以上で初プレイするならかなり覚悟が必要です。

2025年4月4日金曜日

C#プログラムが突然起動時に終了「sfc /scannow」した話

 まぁタイトル通り。
過去に作成済みのLOMの能力値組み合わせを探すだけのプログラムですがなんか突然起動した瞬間に動かなくなりました。
過去に起動実績があるのでプログラムソースの中身は問題ない事は確定
最初はウィルススキャンで隔離されているのかと思ったけれど弾かれた形跡はなく色々探した所イベントビューアのWindowsログのapplicationログにエラーを発見。

ーーー

障害が発生しているアプリケーション名: wp_put11.exe、バージョン: 0.0.0.0、タイム スタンプ: 0x67e8c34c

障害が発生しているモジュール名: KERNELBASE.dll、バージョン: 10.0.19041.5607、タイム スタンプ: 0x18768d24

例外コード: 0xc06d007e

障害オフセット: 0x000000000003b699

障害が発生しているプロセス ID: 0x28e8

障害が発生しているアプリケーションの開始時刻: 0x01dba54932b292ea

障害が発生しているアプリケーション パス: C:\Users\master\Desktop\C♯\v11\wp_put11.exe

障害が発生しているモジュール パス: C:\WINDOWS\System32\KERNELBASE.dll

レポート ID: cac20f27-c118-49d7-9450-b52568853519

障害が発生しているパッケージの完全な名前: 

障害が発生しているパッケージに関連するアプリケーション ID: 

ーーー

「ははーんなるほどね!(全く分からない)」

System32に居るからシステムファイルのKERNELBASE.dllが悪いのかなぁとも思って少し調べたところこれ自体は実行したプログラムとOS間をかけ橋しているだけで問題はなく

流れ的には「プログラム実行」→KERNELBASE.dll「実行されたやつなんかエラーっすね」
と、返してるだけらしい。

問題は「0xc06d007e(モジュールが見つからない)」こっち
今回はWindowsデフォの「C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe」で直接コンパイルしていて呼び出しも「using System; using System.IO; using System.Threading.Tasks; using System.Collections.Generic; using System.Threading;」だけ。

すべて.NET Frameworkの標準ライブラリに含まれるもので、外部DLL不要。そもそも動作確認済みのプログラムで開始直後受け付け待ちにすら到達できない(処理中エラーじゃない)のであとはー・・・「NET Framework 4.0のランタイムが逝ったか・・・?」

こんな時は\sfc コマンド!/
いつも通り名前から実行で出したcmdに「sfc /scannow」実行したら
このコマンド管理者しか実行しちゃダメなんですよって丁寧に怒られたので
「C:\Windows\System32」に移動してcmd.exeを右クリックで管理者実行

ーーー

C:\WINDOWS\system32>sfc /scannow

システム スキャンを開始しています。これにはしばらく時間がかかります。
システム スキャンの検証フェーズを開始しています。
検証 100% が完了しました。
Windows リソース保護により、破損したファイルが見つかりましたが、それらは正常に修復されました。
オンライン修復の場合、詳細は次の場所にある CBS ログ ファイルに含まれています
windir\ Logs\CBS\CBS.log (たとえば C:\Windows\Logs\CBS\CBS.log)。オフライン修復の場合、詳細は /OFFLOGFILE フラグによって指定したログ ファイルに含まれています。

ーーー

再実行!即強制終了!「例外コード: 0xc06d007e」!
(´・ω・`)ソンナー
ーーー

C:\WINDOWS\system32>DISM /Online /Cleanup-Image /RestoreHealth
展開イメージのサービスと管理ツール
バージョン: 10.0.19041.3636
イメージのバージョン: 10.0.19045.5608

[===========================84.9%=================         ]

[===========================87.3%==================        ]

[===========================89.9%====================      ]

[==========================100.0%==========================] 復元操作は正常に完了しました。

操作は正常に完了しました。

ーーー


再実行!即強制終了!「例外コード: 0xc06d007e」!
(´・ω・`)ドウシテ・・・


2025年3月24日月曜日

LOMのステータスを総当たり探査した話(技術者向け)

 たまには技術ノートでも書こうかなという話
今回のお題はlomのベストパターン探査です。

VBS:
Function CalcStats(w)
s=array(5,5,5,5,5,5,5)
s(0)=s(0)+0.75*w(0)+1 *w(1)+1 *w(2)+1.25*w(3)+1.25*w(4)+1.75*w(5)+0.75*w(6)+0.5 *w(7)+1.25*w(8)+0.75*w(9)+0.5 *w(10)
s(1)=s(1)+1.25*w(0)+1 *w(1)+0.75*w(2)+1 *w(3)+0.75*w(4)+0.75*w(5)+1.25*w(6)+0.75*w(7)+0.75*w(8)+1.25*w(9)+1.75*w(10)
s(2)=s(2)+0.75*w(0)+1 *w(1)+1 *w(2)+0.75*w(3)+1.25*w(4)+0.5 *w(5)+1 *w(6)+0.75*w(7)+0.75*w(8)+0.5 *w(9)+0.75*w(10)
s(3)=s(3)+0.75*w(0)+0.75*w(1)+0.75*w(2)+0.75*w(3)+0.75*w(4)+0.75*w(5)+0.75*w(6)+1.25*w(7)+0.5 *w(8)+0.75*w(9)+0.75*w(10)
s(4)=s(4)+0.5 *w(0)+0.75*w(1)+0.75*w(2)+0.75*w(3)+0.5 *w(4)+0.75*w(5)+0.5 *w(6)+0.5 *w(7)+1 *w(8)+0.5 *w(9)+0.5 *w(10)
s(5)=s(5)+1 *w(0)+0.75*w(1)+1 *w(2)+0.5 *w(3)+0.75*w(4)+0.75*w(5)+0.75*w(6)+0.75*w(7)+0.75*w(8)+0.75*w(9)+0.75*w(10)
s(6)=s(6)+1 *w(0)+0.75*w(1)+0.5 *w(2)+0.75*w(3)+0.5 *w(4)+0.75*w(5)+1 *w(6)+1.25*w(7)+0.75*w(8)+1.25*w(9)+1 *w(10)
for i=0 to ubound(s) s(i)=fix(s(i) if s(i)>99 then s(i)=99 next CalcStats=s end function function t619(s) t=0 for i=0 to ubound(s):t=t+s(i):next if t>=619 then t619=true else t619=false end function function s79t615(s) f=true t=0 for i=0 to ubound(s) if s(i)<79 then f=false t=t+s(i) next if t<615 then f=false s79t615=f end function msgbox s79t615(CalcStats(array(1,81,1,0,0,0,0,6,9,0,0)))
msgbox t619(CalcStats(array(1,81,1,0,0,0,0,6,9,0,0)))

考えなくちゃいけないのは計算と探査判定
探査方法の考え方としては、武器別にステータス配列を作って配列を合算しても良いんだけど配列同士の合算が面倒くさい。
考え方を変えて関数で各武器の回数だけを貰ってその分だけ能力値を掛ける。
これをCscriptで多重forに放り投げるだけ。

Jscript:

function CalcStats(w) {
  var s = [5, 5, 5, 5, 5, 5, 5];
  s[0]=s[0]+0.75*w[0]+1*w[1]+1*w[2]+1.25*w[3]+1.25*w[4]+1.75*w[5]+0.75*w[6]+0.5*w[7]+1.25*w[8]+0.75*w[9]+0.5*w[10];
  s[1]=s[1]+1.25*w[0]+1*w[1]+0.75*w[2]+1*w[3]+0.75*w[4]+0.75*w[5]+1.25*w[6]+0.75*w[7]+0.75*w[8]+1.25*w[9]+1.75*w[10];
  s[2]=s[2]+0.75*w[0]+1*w[1]+1*w[2]+0.75*w[3]+1.25*w[4]+0.5*w[5]+1*w[6]+0.75*w[7]+0.75*w[8]+0.5*w[9]+0.75*w[10];
  s[3]=s[3]+0.75*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+1.25*w[7]+0.5*w[8]+0.75*w[9]+0.75*w[10];
  s[4]=s[4]+0.5*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+0.5*w[6]+0.5*w[7]+1*w[8]+0.5*w[9]+0.5*w[10];
  s[5]=s[5]+1*w[0]+0.75*w[1]+1*w[2]+0.5*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+0.75*w[7]+0.75*w[8]+0.75*w[9]+0.75*w[10];
  s[6]=s[6]+1*w[0]+0.75*w[1]+0.5*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+1*w[6]+1.25*w[7]+0.75*w[8]+1.25*w[9]+1*w[10];
  for (var i = 0; i < s.length; i++) {
      s[i] = Math.floor(s[i]);
      if (s[i] > 99) s[i] = 99;
  }
  return s;
}

function t619(s) {
  var t = 0;
  for (var i = 0; i < s.length; i++) {t += s[i];}
  return t >= 619;
}
function s79t615(s) {
  var f = true;
  var t = 0;
  for (var i = 0; i < s.length; i++) {
     if (s[i] < 79) f = false;
     t += s[i];
  }
  if (t < 615) f = false;
  return f;
}
function s80t607(s) {
  var f = true;
  var t = 0;
  for (var i = 0; i < s.length; i++) {
      if (s[i] < 80) f = false;
      t += s[i];
  }
  if (t < 607) f = false;
  return f;
}

速度比較用にJscriptにそのまま書き直したものがこれ。同じくCscriptで実行したけど速度的に大きな差は無かった。
別にいらなかったんだけど全ステ80以上パターンも探査に追加この時点では607が最低値で分かっていたのでそれを指定。
ちなみにあとで総当たりさせると合計607は三千パターン以上ある為後で合計範囲を610以上に変更した。
619のif elseはそもそも比較演算すればboolなので修正他はそのまま。

javascript(node.js実行):

function CalcStats(w) {
  let s = [5, 5, 5, 5, 5, 5, 5];
  s[0]=s[0]+0.75*w[0]+1*w[1]+1*w[2]+1.25*w[3]+1.25*w[4]+1.75*w[5]+0.75*w[6]+0.5*w[7]+1.25*w[8]+0.75*w[9]+0.5*w[10];
  s[1]=s[1]+1.25*w[0]+1*w[1]+0.75*w[2]+1*w[3]+0.75*w[4]+0.75*w[5]+1.25*w[6]+0.75*w[7]+0.75*w[8]+1.25*w[9]+1.75*w[10];
  s[2]=s[2]+0.75*w[0]+1*w[1]+1*w[2]+0.75*w[3]+1.25*w[4]+0.5*w[5]+1*w[6]+0.75*w[7]+0.75*w[8]+0.5*w[9]+0.75*w[10];
  s[3]=s[3]+0.75*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+1.25*w[7]+0.5*w[8]+0.75*w[9]+0.75*w[10];
  s[4]=s[4]+0.5*w[0]+0.75*w[1]+0.75*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+0.5*w[6]+0.5*w[7]+1*w[8]+0.5*w[9]+0.5*w[10];
  s[5]=s[5]+1*w[0]+0.75*w[1]+1*w[2]+0.5*w[3]+0.75*w[4]+0.75*w[5]+0.75*w[6]+0.75*w[7]+0.75*w[8]+0.75*w[9]+0.75*w[10];
  s[6]=s[6]+1*w[0]+0.75*w[1]+0.5*w[2]+0.75*w[3]+0.5*w[4]+0.75*w[5]+1*w[6]+1.25*w[7]+0.75*w[8]+1.25*w[9]+1*w[10];
  for (let i = 0; i < s.length; i++) {
  s[i] = Math.floor(s[i]);
  if (s[i] > 99) s[i] = 99;
  }
  return s;
  }
function t619(s) {
  return s.reduce((t, val) => t + val, 0) >= 619;
}
function s79t615(s) {
  const t = s.reduce((sum, val) => sum + val, 0);
  return s.every(val => val >= 79) && t >= 615;
}
  function s80t607(s) {
  const t = s.reduce((sum, val) => sum + val, 0);
  return s.every(val => val >= 80) && t >= 607;
}

前回の記事で初めてnode.jsをインストールしたんだけどjavascriptがコーディング出来てローカル処理をしたいなら絶対インストールした方がいい。
処理速度は100倍以上速くなる。
Jscriptでは変数定義にvarしか使えなかったけどjavascriptなのでletやconstに修正して、判定関数も処理を整理。

この後さらにこれをC#に書き換えて並列処理で高速化するんだけど、現状で片手剣30回前後まで絞っても目新しいパターン結果は出なかったので省略。

発見パターンのうち有益なパターンは「LOMリマスター:レベルアップ能力値シミュレーター」の方を参照してください。
※Lvシステムは変わってないのでリマスターじゃない場合も同じです

結局の所、能力調整的に微妙な…いわゆる最大パターン619は6パターンしかありません。
逆に一番いいパターン、79以上615は27パターン程あります。

(オマケとして探査した80以上の合計上限はどう頑張っても611が最大で、79より合計が下がるので微妙。ちなみにこの611パターンは1つしか見つかってないです。80以上610なら18パターンほど見つかってます。)

■大まかな探査時間
片手剣の回数をベースに杖とグラブは無制限、他は10回という制限で
片手剣の使用回数を62回にした場合はだいたい以下の通り。

個別の時間差
 0% →   9%: 13:07:43 - 12:28:35 = 39分 8秒 = 2,348秒
 9% →  18%: 13:40:55 - 13:07:43 = 33分12秒 = 1,992秒
18% →  27%: 14:09:01 - 13:40:55 = 28分 6秒 = 1,686秒
27% →  36%: 14:34:30 - 14:09:01 = 25分29秒 = 1,529秒
36% →  45%: 14:54:26 - 14:34:30 = 19分56秒 = 1,196秒
45% →  54%: 15:10:44 - 14:54:26 = 16分18秒 =   978秒
54% →  63%: 15:24:24 - 15:10:44 = 13分40秒 =   820秒
63% →  72%: 15:35:43 - 15:24:24 = 11分19秒 =   679秒
72% →  81%: 15:45:03 - 15:35:43 =  9分20秒 =   560秒
81% →  90%: 15:52:29 - 15:45:03 =  7分26秒 =   446秒
90% → 100%: 15:58:26 - 15:52:29 =  5分57秒 =   357秒 

ループの外側の数値が大きくなるほど使える回数は減るので内側に向かってどんどん処理時間が早まっていく。
だいたい50前後くらいなら少し放置しておけばリストアップ出来る。
これより片手剣の回数が減ると一日放置とかになるかも

■C#その後:
・並列でも全てをなげちゃうとCPU90%くらいで数日帰ってこないことになるので片手剣の回数を指定出来るように修正、関数として分離(全体を回したい時はこれにfor)
全体に投げてた並列処理をfor内部を並列化して内部のforを高速化

・条件を変更するたびにコンパイルが面倒になったので設置textから判定条件を読み込めるように修正
File.ReadAllLines(conditionsFile);で読み込んで改行でsplitして
読み込むテキストは改行区切りで「80,80,80,80,80,80,80,610」「81,81,81,81,81,81,81,」「,,,,,,,619」こんな感じ。空部分は条件指定なし

・全部デスクトップに吐いて、読み込みもデスクトップだったのを全部実行exeから相対パスに出力と読み込みするように変更「Environment.GetFolderPath(Environment.SpecialFolder.Desktop)→System.Reflection.Assembly.GetExecutingAssembly().Location」とか

・各ループ上限を柔軟に調整したくなったのでリミットテキストを外部において武器の回数上限を変更出来るように修正

・引数で回数を指定して実行する事で入力待ちするのを省略するように変更
片手剣回数を関数分離して回したいときはforにしようと思ってたけど適当にスクリプトで引数付きで実行出来るようにした

などの修正をしたけど検証終わったらもう使わないよなぁという気持ち。
いやまぁ初見言語のC#の勉強にはなったけども

C#(for抜粋):

for (int w0 = 0; w0 <= w0Limit; w0++)
{
  int w2Limit = Math.Min(remaining - w0, loopLimits[2]);
  Console.WriteLine("\n片手剣 " + actualOneHandSword + "回 - 進捗: " + (w0 * 100 / w0Max) + "% (" + w0 + "/" + w0Max + ") - " + DateTime.Now);
  for (int w2 = 0; w2 <= w2Limit; w2++)
  {
    int w3Limit = Math.Min(remaining - w0 - w2, loopLimits[3]);
    for (int w3 = 0; w3 <= w3Limit; w3++)
    {
      int w4Limit = Math.Min(remaining - w0 - w2 - w3, loopLimits[4]);
      for (int w4 = 0; w4 <= w4Limit; w4++)
      {
        Console.Write(".");
        Console.Out.Flush();
        int w5Limit = Math.Min(remaining - w0 - w2 - w3 - w4, loopLimits[5]);
        for (int w5 = 0; w5 <= w5Limit; w5++)
        {
          int w6Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5, loopLimits[6]);
          for (int w6 = 0; w6 <= w6Limit; w6++)
          {
            int w7Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5 - w6, loopLimits[7]);
            Parallel.For(0, w7Limit + 1, w7 =>
            {
              int w8Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5 - w6 - w7, loopLimits[8]);
              Parallel.For(0, w8Limit + 1, w8 =>
              {
                int w9Limit = Math.Min(remaining - w0 - w2 - w3 - w4 - w5 - w6 - w7 - w8, loopLimits[9]);
                for (int w9 = 0; w9 <= w9Limit; w9++)
                {
                  int w10 = remaining - w0 - w2 - w3 - w4 - w5 - w6 - w7 - w8 - w9;
                  if (w10 >= 0 && w10 <= loopLimits[10])
                  {
                    int[] localW = new int[11];
                    localW[0] = w0; localW[1] = actualOneHandSword; localW[2] = w2; localW[3] = w3; localW[4] = w4;
                    localW[5] = w5; localW[6] = w6; localW[7] = w7; localW[8] = w8; localW[9] = w9; localW[10] = w10;
                    bool localHasMatch = false;
                    StatsWrite(localW, actualOneHandSword, limitPrefix, ref localHasMatch);
                    if (localHasMatch) hasMatch = true;
                  }
                }
              });
            });
          }
        }
      }
    }
  }
}

C#のコードだけ記載しないのもあれだったから。総当たり処理部分はこんな感じ。
ループは最初forではなく再帰にしてたんだけどforの方が早いらしい事が分かったのでforに戻した。1秒でも早くというかミリ秒でも早く動いて欲しかった故に・・・。

判定処理をforに入れるとfor内が厚くるのでStatsWrite側に判定処理を入れています。Parallel.Forで並列化してるのは杖とグラブ部分。

2025年3月21日金曜日

node.jsを使ってみた話。

 そもそもnode.jsって何?

今回自分が知りたかった要点ですが「node.js」とは
・node.js=javascriptのローカル実行環境
・実行環境=インストールが必要
・javascriptの記述でローカル処理できる
・WSH(Jscriptやvbs)より
百倍以上速い!!

まぁ他にもあるんですが、今回押さえたい要点はこの4つです。

「node.js」名前だけ聞いたことあったんですが「jQuery」のようなjavascriptライブラリだと思ってたんですよ。
・・・ですが、全然そうではなくて実行環境だったんですよ。

「javascript」と「Jscript」と「node.js」の関係

■javascript
web上で動的に処理する時に使うスクリプト言語です
ファイルの拡張子は「.js」です

■Jscript
Windows上で(ローカル環境で)処理を自動化する時に使うjavascriptベースのスクリプト言語です。限りなくjavascriptに近い言語ですがjavascriptとは別物です
ファイル拡張子は「.js」つまり拡張子までもjavascriptと同じです。

古いjavascriptがベースで、例えば変数定義はvarしかなくlet等は使えません。
また、今後新規にサポートされる予定もありません。むしろ消えかかっている泥船言語です。Microsoftは今後Windowsの自動化はパワーシェルを推奨してる感じです。

根本的な話をするとWSH(Windows Script Host)というWindows上で動かすスクリプト言語二つの内の一つがJscriptです(もう一つはvbs)。
バッチファイル(.bat)などより柔軟な自動化が出来る言語で、当然ながらWindows上でしか動作しません。
ただ逆に言えばWindowsであれば何かをインストールなど不要で直ぐにスクリプトが使えWindowsのこまごました自動化が出来ます。普通に便利です。

■node.js
「node.js」なんて名前だからてっきりファイルかと思うじゃないですか?
そうじゃないんです。
javascriptをローカルで使えるようにする環境」なんです

そうです、記述する言語は「javascript」です。
当然ファイルの拡張子も「.js」です。
しかも、ローカルファイルにアクセスなども出来るようになるんです。
「じゃあ、Jscript完全上位互換になるって事?」と思うでしょうがJscriptで使えるものが使えません。

例えば:
・WScript.Echo: Windows Scripting Host(WSH)特有のメソッドで、Node.jsには存在しません。
・WScript.CreateObject("WScript.Shell"): WSHでシステムオブジェクトにアクセスするための機能。Node.jsでは代替手段が必要。
・new ActiveXObject("Scripting.FileSystemObject"): WindowsのCOMオブジェクトで、Node.jsではサポートされていません。

あと上には書きませんでしたがnode.jsはマルチプラットフォームです。
Windows以外の環境でもjavascriptが使えるって事になります。

node.jsはjavascriptで記述するのに何でそんなに早いの?

そもそもなんでscriptは遅いのかって話に直結します。
一般的にプログラムとスクリプトには壁があり、その大きな違いはコンパイル(やビルド)をするかしないかが大きな境界になります。
インタプリタとコンパイラという区分です。

・インタプリタではソースコードを一行ずつ変換し命令を処理・実行
・コンパイラは全ての命令をまとめて変換後に一括で処理・実行

処理速度だけで言えば機械が処理しやすいプログラム(コンパイラ)の方が圧倒的に早いです。
じゃあなんでスクリプトなんてものがあるかというと、確かにプログラムよりはかなり遅いですが、直ぐに実行できて直ぐに修正出来るという利点があります

小中規模であればスクリプトの方が対処する内容に対して即効性があるのです。
逆にプログラムはコンパイルを通さないと処理が実行出来ませんが大規模な処理は圧倒的に処理速度が重要になります。

node.jsはjavascriptをコンパイルしている

前段の通りコンパイルすると処理速度が爆発的に早くなります。
実はnode.jsはjavascriptをコンパイルする事で高速化しています。

更に詳しく言えば「Just-In-Time (JIT) コンパイル」をしています。
インタプリタで初期実行した後、ホットコードをマシンコードに変換という形です。
手動でコンパイルする必要がないがないが重たい処理だけ知らないうちにコンパイルしてくれるって事ですね。
また、イベント駆動型の非同期I/Oモデル故に大量処理に向いています。


凄い雑にまとめると「javascript」を超高速化出来るのがnode.jsです。
さて、ここから下は駄文です。


そもそもなんでのnode.js使ってみたの?

LOMのレベルアップのステータスを手動で調整してたけど面倒になったから総当たりで他にパターンが無いかの確認と上限を確定させるため。
まぁ一旦雑に総当たりで試してみたが当然遅い。そこで高速化する手立てを考えている時にnode.jsに行きついたわけです。

どれくらい早くなった?

総当たりをぶつけた訳ですが途中探査にJscriptで3時間かかった部分がnode.jsで実行したら1分で探査されました。
Jscript特有部分を少し直した以外は処理方法などは手を加えずに180倍速で処理できました(はっや・・・)。

ただあくまでも今回のしかも一部分を切り取った時間比較なのであれですが基本的には重い処理を作ったなら100倍くらいは早くなると見積もっていいと思います。


追記:
一日node.jsで回して片手剣56回までの探査で行ったので強制終了

「そういえばWindowsってデフォルト環境でC#のコンパイル出来るようになったんだよなぁ」って事を思い出してC#用にコードを変更
さらにC#なら並列で処理できるからもっと早くなるんじゃないか?
フルでぶん投げるけど元より全探査しようとは思ってない。
24時間回してみてどれくらい結果取れるかなぁって感じ←今ここ

余談:早いスクリプト言語って何?

「javascriptをnode.jsで実行(以下node)、javascript、Jscript、VBS、Python」の処理速度を比較した場合単純処理なら

node>>超えられない壁>>Jscript・VBS>javascript>Python

コンパイルがされる関係上百倍くらいは差がでるのでnodeを越える事は出来ません。
重たい処理をしたい場合、悪い事は言いませんのでjavascriptが出来るならnode.jsをインストールした方が良いです。
Pythonインストールするぐらいならnode.jsインストールしたほうがたぶん平和。

もうプログラムで良くない?

まぁ、そもそもWindowsデフォルト環境だけで何もインストールしたくない、もしくは環境的に新たに何かをインストールできない場合、基本的には「Jscript・VBS・javascript」しか選択肢がありません。

ただし、裏技的解決方法としてはC#で書き直す方法があります。
なんとWindowsはデフォルト環境でC#がコンパイルできます!

もう少し丁寧に言うとWindowsはデフォルトで「.NET Framework 」が入っておりC#のコンパイル環境が整っています。
つまり普通にプログラム組んじゃおうって事ですね。当然ながらめちゃ早いです。しかもC#の場合は「Parallel.For」でマルチプロセスで平行処理できる為、今回のような物量探査問題だけならばnodeより確実に早いです。

※一応注意点としてはC#のデフォルトコンパイル環境は「.NET Framework」のバージョンが古いので最新のC#コードが使えない事は留意してください。
例えば「$とか=>」が使えません。
■ラムダ式形式: =>を使った簡潔なメソッド定義はC# 6.0以降の機能。
■文字列補間($"...")がC# 6.0以降の機能


2025年3月13日木曜日

奇数か(偶数か)を判定する

前置き:

今回のお話は 𝕏のおすすめ欄にたまたま流れてきたポスト「1~100までの整数が奇数かどうかを判定するプログラムを書きました!」に起因します。

一般の人には面白さが伝わるか分からないけど『「もし奇数なtrueそうでなければfalse」つまり1なら奇数2なら偶数3なら偶s...って100個書けば良いんだ!』

HAHAHAそうだけどそうじゃないだろっていう面白さ…やっぱり伝わらないかも。

確かに定義が「1~100固定で奇数ならtrueを返し偶数ならfalse」なら間違いないくこれで答えが得られるけど汎用性が無さすぎる事が1つ、もう一つはそもそも処理本質はそこじゃない。っていう所。

「1なら奇数」...は既に答えを知っている場合の条件分岐で本来定義を考えるべきなのは奇数はどの条件で奇数になるのかって話。

このポストにはさらに元ネタがあって「コーディングのヒント」っていうこのベタif文をエクセル等のオートフィル(処理を繰り返す)によって脳筋コードを生成するってネタポストの再ネタポストって事ですね。

そして「【ゆる募】1~100までの整数が奇数かどうかを判定するプログラム大喜利を開催します☺ 言語不問✨」更にネタにした二番煎じ、三番煎じのネタポスト『無駄に洗練された無駄の無い無駄な動き』を考える会みたいな話です


「プログラムをする」=「ロジカルな思考になる事」

自身はプログラマーではないく精々スクリプターなので思考が雑だけれど基本的には同じで「コーディング」する事よりも「その本質はなんなのか」を考える思考ロジックがたぶん一般人とプログラマーの境界な気がします。

この記事を書くきっかけになったのはネタポストもあるけれど実は大喜利のリプにあった「入力が数値じゃないかもしれない」みたいなポストがあってそうじゃないんよなぁと思ってなんとなく(今はそういう話をしてるんではなくロジック部分のみの話なんよ)。

さておき、そもそも通常はどのようにコードを書くか


n=99;print(bool(n%2))


最初に考えたのはこんな感じです。ポスト元が1をtrueにしてるのでそれに合わせます。

奇数なら余りは1になり、偶数なら余りが0になります。

これをbool値「真(True)偽(False)」に変換します。

まぁでもよくよく考えたら「n%2==1」で直接bool値になりますね。

元のコードの通り関数にするなら


num=99
def is_odd(num:int)->bool:return num%2==1
print(is_odd(n))


こんな感じでしょうか。

この通り本来は奇数をだすならたったこれだけなのです。


大喜利の話

特に記載はないですがこの手の大喜利の要点は

結果が正しい事は前提で、より壮大に無駄にリソースを使い条件を満たそうという話です。

かつ限定条件が1~100なのでこの範囲じゃないと上手く動かないとかの方が面白い。

更に言えば単に意味のない処理を入れる訳ではなく必要な処理で構成されており、処理が抜けると動かない形であった方が良いですね。


まぁなのである意味一番元のベタif文100個並べるが完成されてるかもしれません

(判定を100回行い、なおかつ1行でも抜けると正しく動作しない)


もう一つの要点

それは定義を考えるという事です。

通常の奇数の定義は「数値に対して2で割って余りが1なら奇数」これが普通です。

このような大喜利はこの定義を別の条件に言い換え処理を考える遊びです。


例えば入力は1~100の整数という条件ですので「奇数は偶数と必ず交互に存在する」見たいな事を考えた場合「奇数から始まり、対象の数値まで真と偽を交互に代入」すれば奇数か偶数かを判定できる。

𝕏では花占いと表現してポストしたやつで


n=100;b=false;for(i=0;i<n;i++){if(b){b=false}else{b=true}}


こんな感じです。

Jscriptですがまぁjavascriptでも同じです。


更に別の奇数の定義を考えてみます。

交互に存在するなら「真偽交互に100個分回答を用意して、入力数値番目」にアクセスすれば直接真偽を得られる


n=99;WScript.StdOut.WriteLine(Boolean("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010".split("")[n]*1))


定義を元に少し変更します。

画像でポストするのが面倒だしコピペ出来ないし面倒。という事でコードを「全角140文字=半角280字で考える」

なので真偽配列を10二進数配列を作ります。

さらに配列は0から始まるけど入力値は1~100なので要素-1のアクセスが面倒なので配列の0番目の要素を追加し101個の配列を用意してやれば入力値で直接0か1が返ります。これをbool変換すれば奇数か判定できる。 コードはJscriptのCscriptです。


他にも大喜利の例題は「1,3,5,....99なら奇数」という50個分の判定ですがそもそも偶数奇数判定は「入力値の末尾1桁が1,3,5,7,9なら奇数」でいいじゃんと考える事も出来ます。

であれば


n=100;print([False,True,False,True,False,True,False,True,False,True][int(list(str(n))[-1])])


こんな感じで真偽100個配列を作るより奇数5つの配列でいい 今度はPythonで組んでみましたが先ほどやってる事は同じです。


あと他には「2で割って余りが1なら奇数」という表現を別の表現で考える方法もあります

「2で割った結果が小数になるならば奇数」みたいな感じです


n=100:msgbox cbool(ubound(split(n/2,".")))


例えば2で割ってそれを小数点で配列化、割り切れてれば配列数は0、割り切れてなければ1。

0・1をbool化すれば真偽判定できます


言語特有の考え方ですが先ほどの100個全ての真偽を配列にする方法を

n=100;print(bool(int(list("01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010")[n])))

Python版で考えて、Pythonは進数変換が容易なのでこの二進数を16進数に圧縮しようかと思ったんですがそもそも


n=100;print(bool(int(list("01"*50+"0")[n])))


の圧縮率が高すぎてもうこれで良くない?みたいな事もあります。

「01」というパターンを50回繰り返すというPython記述便利。


さて、交互に繰り返される真偽・二進数・末尾一桁と来ましたが入力値が0かnot0になれば良いわけです。だとするとそもそも「十進数を二進数に変換して末尾一桁を真偽変換」すれば良いのではとも気づきます。


n=100;print(bin(n)[-1]=="1")


おっと、また短くなってしまった。Python久々に書いたけど便利だなぁ。

もっと無駄な形にしないとダメですね


割り算と余りの概念を脳筋で解決する方法も考えてみます

2個をワンセットとして対消滅させていき、数字君が生き残れれば奇数

分かりやすく言うと「数値を2ずつ引いていき、余れば奇数」


n=99:do while n>1:n=n-2:loop:msgbox n=1

vba・vbsならこんな感じです。これもやっぱり短いですね。
やはりベタifが無駄であり無駄という意味では酷いコードの完成度が高い。
でも𝕏の文字数制限内でそれ書くのは面倒臭い。
じゃあ、いっそ実行可能な形式でそれを生成すればいいよね

a=split("false/true","/"):c="n=数値"&vbcrlf:for i=1 to 100:c=c&"if n="&i&" then b="&a(i mod 2)& vbcrlf:next:c=c&"msgbox b"&vbcrlf
createobject("scripting.filesystemobject").createtextfile("n.vbs", 1).write c


𝕏の文字数制限内に収まりました
「数値」の所を1~100に置き換えればそのまま実行可能です。

if n=1 then b=true
if n=2 then b=false
...

をベタ入力したvbsファイルを生成するvbsです。
コードを生成するコードの時点で二度手間で、コード生成時にi mod 2で既に処理内で余りを出してる所にも二度手間感があります。
「でも何かを抜くと動かなくなる」というコードです。
まぁ一旦こんなところで終わりにしましょう。


意味はないけど意味がある事

今回の「奇数の真偽を出す」は別に意味のある事ではありません
ただ、こう言った処理定義の考え方変換や柔軟な発想、手段の引き出しを増やす事はあるほど良いです。

また、0と1で百個用意して配列要素で値をえる方法などは既に結果が変わらない場合などであれば配列をメモリに突っ込んで引き出した方がforなどを回すより高速に処理できるし、呼び出しが外にあればメインは要素1つ与えるだけで取り出せるメイン処理の簡素化が出来たりもします。

こういったものは別に大喜利である必要はありませんんが内容が簡単で初心者でも上級者でも一緒に頭の体操が出来るのが面白い。
簡単な問題なので「処理すべき事」と「結果がこうなる」が分かっているうえで他の人のコードを見れるから初心者からでも上級者がどんな工程を踏むのかが見れるのも良いです。

他にも「与えられた数値が素数かどうか判定する」「うるう年の判定」など条件を知っているが考え方が試されるものは色々あります。

余談:
上記コードは自分で考えたもので、記述出来るのがwscript、VBA、Pythonなどスクリプト系なのでコードがごった煮でごめんね:;(∩´﹏`∩);:

2025年2月12日水曜日

addEventListener()の使い方

まず、一言説明
Q.「addEventListener」って何?
A.「クリック等のマウス操作や、キーボード操作、ブラウザリサイズ等操作で処理を発火できるようになる」


例えば、何かをクリックしたときに関数を呼ぶ場合
「<div onclick="setaf()">」とすれば対象の関数を発火できます。
1つ2つ程度なら全然これで良いと思います。

ですが…、数が多くなった場合はどうでしょうか100個200個divがあった時に全てonclickを入れたり操作するのは大変。
またはクリック以外でもブラウザ上で何か「こういう操作」が発生したらチェックするために関数を発火したい時に関数を発火できます。

経緯

とあるwebシミュレーターを作る時に、gridマップから何処をクリックしたか知りたいなぁという事がありました。
24x24マスのgrid=項目行列も含め625マスのdiv操作
その盤面から何処がクリックされたのか知りたい

操作領域は24x24。の領域を選択範囲19x19をセットで操作=361ヵ所
実際に操作処理するのが165ヵ所。

・・・面倒臭い。

使い方

対象要素.addEventListener( 種類, 関数, false )

一旦形だけ理解しましょう。
これをグローバルエリアに置きます。

第1引数:
固有の種類があります別途調べてください
(つまり、任意の名称設定できません決まったコマンド的なものです)

第2引数:
関数は自分で作った関数名を指定します

第3引数:
イベント伝搬方式のfalseは一旦そのままでいいです。

記述1

対象要素.addEventListener('click', abcdef, false);
function abcdef(){処理内容}

addEventListenerで指定する関数名には「()」は付けないで下さい
付けるとただの関数呼び出しになります
具体的にはリロードされただけで実行されます。

記述2:アロー関数

対象要素.addEventListener('click', () => {処理内容})

関数を呼ばずにすぐ処理したい場合この様に書きます
更に、記述1では括弧をつけないので引数が渡せませんが渡したい時は単にこの処理内容部分から普通に関数を呼べば良いです。

対象要素.addEventListener('click', () => {abcdef(引数)})
function abcdef(aaa){処理内容}

アロー関数の様な方法として無名関数というのもあって
対象要素.addEventListener('click', function() {処理内容}, false)

こんな感じでも記述できます
閑話休題

何処がクリックされたのか

話しを戻して、今回のやりたい事って何かというとつまるところ
「クリックしたgridにあるdivのどれが押されたか?」

ググってみたら「grid div 要素数える」とか色々検索したけどgrid.jsがとかライブラリjsでindexでとかArray.fromで要素を配列に変換したあとにArray.prototype.indexOf.call("集合の要素", "目的の要素")で数えるとか

・・・いやそんな面倒臭いはずは…だってまずforで要素探して数えてるiさえ関数に渡せればいいだけなのになぁとちょっと迷走

今回の実装方法

const target = document.getElementsByClassName('mybox'); 
for(let i = 0; i < target.length; i++) {
    target[i].addEventListener('click', ()=>{boxclick(i)});
}

クリックイベントを検知してそのカウンタiをただの関数に投げるだけ。
自分が求めていたのはこれだけなのに全然すっきりした答えが出て来なくて無駄な遠回りをした。
今回対象にしたのはClassNameです。
同じClass名を対象に丸ごと持ってそれを対象にクリックされたかを調べ発火
アロー関数先の関数に引数を渡してただ呼び出すだけ

記述位置

記述は操作する対象が存在してなければ実行できません
例えば、ヘッダーにいれたjsに書くと対象がロード前で実行失敗になります

つまりhtmlの一番下に書けばOKって事です
・・・ですが面倒臭いですよね

その場合はロードが完了後発火する所に記述すればいいわけです
一番手っ取り早いのは

window.onload=function(){ページロード後に実行したい関数()}

飛ばした関数の中に記述すればOK


2025年1月11日土曜日

javascript配列の値渡しと参照渡し

 今回は簡単な備忘録


超簡単に説明するとデータが複数の情報を内包するもの「オブジェクト型」のもの、更に言えば「配列」「連想配列」等も含むものは参照渡し。

逆に単一データを保持する変数は値渡し。


厳密な言い回しは他に行ってもらうとして、ここにたどり着く人は配列の挙動がおかしいって人の為の対応について。

ーーーーーーーーーーーーーー変数代入

var a=[1,2,3,4]
var b=a.slice()

ーーーーーーーーーーーーーー
以上、終わり。


ーーーーーーーーーーーーーー関数引数

var a=[1,2,3,4]
var b=aaa(a.slice())
function aaa(a){}

ーーーーーーーーーーーーーー
以上、終わり。


■簡単な説明説明
今回、知りたいだろうことは配列を参照渡しではなくて値渡ししたい。
もっと言えば、参照先じゃなくて

・配列を別の変数にコピーをしたい
・関数に配列のコピーを渡したい

この二つだと思うので、処理後に新規配列で生成されるメソッド系を使えばOK
sliceは文字や配列を切り出す処理なので、引数に何も渡さない場合は配列を変更せずにコピーを渡すだけの処理になります。

変換の仕方がスマートかと言われるとまぁ微妙な所ですが、まあ一番手っ取り早い方法は確実にこれ。


■すっごい雑な補足

javascriptの場合はもうちょっと具体的に言うと「参照の参照渡し」を行っている
とはえ、おそらく他でも参照渡しで「概ねあってます」という形だと思う。

結局の所、代入先で値を変更すると代入元も値がかわるので・・・



もっと楽な方法

ーーーーーーーーーーーーーー変数代入

var a=[1,2,3,4]
var b=[...a]

ーーーーーーーーーーーーーー
以上、終わり。


ーーーーーーーーーーーーーー関数引数

var a=[1,2,3,4]
var b=aaa([...a])
function aaa(a){}

ーーーーーーーーーーーーーー


■すっごい雑な補足
この「...」って何?これはスプレッド構文といいます。
配列やオブジェクトの一番外側の括りを展開します。
データとしては上記aなら「...a」と記述した場合「1,2,3,4」ベタ要素が展開されます。
この展開されたベタ要素を受け取ることが出来るのは今言った通り配列かオブジェクト、あとは関数の引数です(ジェネレーター関数周りで使う事もありますが覚えなくていいです)。

■つまり?
「[...a]」と記述すると「[1,2,3,4]」この様に配列内で要素が展開されて名前のない配列が新しく作成されることで結果的に新しい配列を変数に代入したり関数の引数に渡す事が出来ます。



■あと注意点として、どちらも「浅いコピー」です。ネストされた配列やオブジェクトは結局参照が渡されます。ですので1次配列やオブジェクトの階層が1つしかない場合のみという事は留意してください。