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で並列化してるのは杖とグラブ部分。