【解決】JavaScriptの論理演算子が動かない理由(勘違い)

munokura

ユーザー
ローンチプラグインの SimpleMsgSideViewMZ を改変しています。

敵キャラの通常攻撃の動作を変化したいため、別スキルを作成しました。
この別スキルをバトルログに出さないようにするために改変しました。

しかし、ifが上手く使いこなせておらず…
なぜ動かないのか?
なぜこれで動くのか? this.push('wait'); を通過しなくても動作している?
本当は動いていない!(同じIDのアイテムだと非表示になるので、判定方法に抜けがある…先ほど発覚)(解決しました)
本当はどう書くべきなのか?

が分からず、困っています。

▼元のコード
JavaScript:
  Window_BattleLog.prototype.displayAction = function(subject, item) {
    if (displayAttack ||
       !(DataManager.isSkill(item) && item.id === subject.attackSkillId())
    ) {
      this.push('addItemNameText', item);
    } else {
      this.push('wait');
    }
  };

▼動くと思ったのに動かないコード
JavaScript:
  Window_BattleLog.prototype.displayAction = function (subject, item) {
    if (displayAttack ||
      !(DataManager.isSkill(item) && item.id === subject.attackSkillId()) ||
      !(DataManager.isSkill(item) && undisplaySkill.includes(String($dataSkills[item.id].id)))
    ) {
      this.push('addItemNameText', item);
    } else {
      this.push('wait');
    };

▼何故か動くコード
JavaScript:
  Window_BattleLog.prototype.displayAction = function (subject, item) {
    if (displayAttack ||
      !(DataManager.isSkill(item) && item.id === subject.attackSkillId())
    ) {
      if (!(DataManager.isSkill(item) && undisplaySkill.includes(String($dataSkills[item.id].id)))) {
        this.push('addItemNameText', item);
      }
      // } else {
      //   this.push('wait');
    }
  };
※なぜ this.push('wait'); を通過しなくても動作しているのかも分かりません…

追記
this.push('wait'); をコメントアウトしても動作するようですが…全体の仕組みが理解できていないので、不要なのかも分かりません…
メインの疑問はifの論理演算子の理解です。
 
最後に編集:

munokura

ユーザー
解決しました!?
JavaScript:
  Window_BattleLog.prototype.displayAction = function (subject, item) {
    if (displayAttack ||
      !((DataManager.isSkill(item) && item.id === subject.attackSkillId()) || (DataManager.isSkill(item) && undisplaySkill.includes(String($dataSkills[item.id].id))))
    ) {
      this.push('addItemNameText', item);
    } else {
      this.push('wait');
    }
  };

下記を何度も読み返したところ、「入れ子の括弧を削除する」辺りの解釈が足りないと感じました。

そこで、入れ子にして、事実上2つのorに変えたら、動作しました。
この解決方法に問題があるようでしたら、ご指摘いただければ幸いです。
 

fspace

ユーザー
JavaScriptの書き方云々というよりも単純なミスのような気がします。

記号か何かに置き換えてみるとわかりやすいのでは?

A: displayAttack
B: DataManager.isSkill(item)
C: item.id === subject.attackSkillId()
D: undisplaySkills.includes(String(item.id))

a. 動かない条件式:
 A || !(B && C) || !(B && D)
 A または ((B かつ C)でない) または ((B かつ D)でない)

b. 解決した条件式:
 A || !((B && C) || (B && D))
 A または (((B かつ C) または (B かつ D))でない)

(A, B, C, D) が (false, true, false, true) のとき
 a: false || !(true && false) || !(true && true)false || !false || !truefalse || true || falsetrue
 b: false || !((true && false) || (true && true))false || !(false || true)false || !truefalse || falsefalse

余談ですが、変形するともう少し簡単な形になります。

A || !((B && C) || (B && D))
= A || !((B || (B && D)) && (C || (B && D))) (分配則)
= A || !(B && (C || (B && D))) (吸収則)
= A || !(B && (C || B) && (C || D)) (分配則)
= A || !(B && (C || D)) (吸収則)
= A || !B || !(C || D) (ド・モルガンの法則)
= A || !B || (!C && !D) (ド・モルガンの法則)
 
これはムノクラさんにとっても勉強になったと思います。
!a && !b を括弧で括った場合!(a || b) という形になりますからね。
同様に !a || !b と、!(a && b) が等価なことだと覚えておけばいいと思います。

なお、this.push('wait')がなくても問題ない理由は、
これが「15フレーム何もせずに待つ」の意味なので、
消したとしても0.25秒速くなるだけなので。
 

munokura

ユーザー
おふた方とも、ご助言いただき、ありがとうございます。
fspace氏の助言につきましては、時間をかけて読み解きたいと思います。

ひとまず、これにて解決とさせていただきます。
 

ジェミニ

ユーザー
きた時には解決されておりましたが...
複雑な条件式を設定していたことで論理的なミスが発生するのであれば多少冗長であっても、複数のif文に分割するとか、変数に出すとかした方が後で見た時にわかりやすいのではないかなと思いました。
 
ド・モルガンの法則、懐かしいです。情報系だったもんで。

ジェミニさんの言うように、if分が横に長すぎるので中身の条件をまとめるための
関数を別に定義したほうが、見やすくなるのではと思いました。

例えば、Wikipediaの「プログラミング作法」より「数値が24時間制の範囲を満たしているか」を判定したければ

JavaScript:
const is24hour = function(hour,minute,second){
    if(! (0 <= hour && hour <= 23) ){
        return false;
    }
    if(! (0 <= minute && minute <= 59) ){
        return false;
    }
    if(! (0 <= second && second <= 59) ){
        return false;
    }
    return true;
}
のような関数を定義したほうが見やすく、修正しやすくなります。
 
トップ