translate

「たたかう」のロジックをコードで考える
2021年 2月 9日

勇者と逆さまの塔も、バトルマシンも、デモンズダンジョンも、RPGです。

そして、RPGといえば「たたかう」です。その「たたかう」のロジックを考えてみましょう。

ただのなぐりあいのロジック

例えば、こんな勇者とゴブリンがいます。

const hero = { // 勇者
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30, // 攻撃力
  def: 10 // 防御力
}

const goblin = { // ゴブリン
  maxHp: 100,
  hp: 100,
  maxMp: 0,
  mp: 0,
  atk: 20, // 攻撃力
  def: 5 // 防御力
}

この2人に殴り合いをしてもらいましょう。 ダメージ計算式は、偉大なアルテリオス計算式を採用します。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
}

const goblin = {
  maxHp: 100,
  hp: 100,
  maxMp: 0,
  mp: 0,
  atk: 20,
  def: 5,
}

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtkDamage = hero.atk - goblin.def
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  if (goblin.hp > 0) {
    const goblinAtkDamage = goblin.atk - hero.def
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
勇者のHP: 100  ゴブリンのHP: 100
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 90  ゴブリンのHP: 75
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 80  ゴブリンのHP: 50
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 70  ゴブリンのHP: 25
勇者の攻撃、ゴブリンに25のダメージ!
勇者の勝ち!

勇者が hp を 70 残した状態で勝ちますね。でも、これでは色々と考慮がたりません。もっと考えてみましょう。

両者ともカッチカチの場合の殴り合いは永久に

例えば、勇者とゴブリンがお互いに防御力に特化していたら、どうなるでしょうか。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 999, // カッチカチ
}

const goblin = {
  maxHp: 100,
  hp: 100,
  maxMp: 0,
  mp: 0,
  atk: 20,
  def: 999, // カッチカチ
}

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtkDamage = hero.atk - goblin.def
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  if (goblin.hp > 0) {
    const goblinAtkDamage = goblin.atk - hero.def
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}

答えは簡単、無限ループします。お互いにダメージがマイナスになる為、HPが0以下にならないからです。

このままでは、勇者の攻撃力より高い防御力を持った敵が現れた場合、バグと化してしまいます。対応策は色々とありますが、ここでは最低保障ダメージシステムを採用しましょう。意味は読んでそのままです。

const hero = { ...略 }

const goblin = { ...略 }

const damageCalculator = (offense, defense) => {
  const damage = offense - defense // 攻撃力 - 防御力
  const least = Math.round(offense / 10) // 最低保証ダメージ(攻撃力の1/10)
  if (damage < least) {
    return least
  }
  return damage
}

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtkDamage = damageCalculator(hero.atk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  if (goblin.hp > 0) {
    const goblinAtkDamage = damageCalculator(goblin.atk, hero.def)
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
勇者のHP: 100  ゴブリンのHP: 100
勇者の攻撃、ゴブリンに3のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 98  ゴブリンのHP: 97
勇者の攻撃、ゴブリンに3のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
:
:
中略
:
:
勇者のHP: 36  ゴブリンのHP: 4
勇者の攻撃、ゴブリンに3のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 34  ゴブリンのHP: 1
勇者の攻撃、ゴブリンに3のダメージ!
勇者の勝ち!

これで攻撃力の1/10のダメージは最低でも通るようになりましたので、いずれ決着がつくようになりました。

しかしこれでは「はぐれメタル」系の敵がいる場合、圧倒的にモンスターに不利になりますので、その場合は最低保証ダメージを 1 にするといいでしょう。

これで単純に殴り合う最低限のロジックができました。次はスキルについて考えてみましょう。これだけだと、ただただ脳筋が勝つゲームになってしまいます。

主人公のスキルよりモンスターのスキルの方が多彩説

まずはスキルについて、色々と考えてみましょう。スキルと一言に言ってもたくさんあります。

  • 体力を回復する回復系スキル(アクティブ)
  • 強い力で敵を攻撃する強撃系スキル(アクティブ)
  • 毒などの状態以上にする特殊スキル(アクティブ)
  • 敵の攻撃を一定確率で避けるスキル(パッシブ)
  • 味方をかばうスキル(パッシブ)
  • パーティにいるだけで味方or敵のステータスが増減するスキル(パッシブ)
  • 敵味方全員のスキルを無効にするスキル(パッシブ)

などなど、スキルは無限にあります。これをどうロジックに落としていくか考えてみましょう。問題は、スキルの内容や発動タイミングが多岐に渡るということです。

もしあなたがスキルを実装するとしたらどうやりますか?

さすが遊戯王

自分の場合、色々と考えた結果、「たたかう」に、下記のようなフェイズを作ることにしました。

  • スタートフェイズ(一番最初にスキルが発動する)
  • セットフェイズ(戦闘開始時にスキルが発動する)
  • アタックフェイズ(攻撃側のスキルが発動する)
  • ディフェンスフェイズ(防御力側のスキルが発動する)
  • ダメージ計算フェイズ(実際のダメージが計算される)
  • アタックアフターフェイズ(ダメージ計算後に攻撃側のスキルが発動する)
  • ディフェンスアフターフェイズ(ダメージ計算後に防御側のスキルが発動する)
  • ダメージ発生フェイズ(上記の流れを元に計算されたダメージがHPから引かれる)

これぐらいのフェイズを設定してあげると、そこそこ複雑なスキルでもロジックを実装することが出来ました。ちなみに後から気がついたんですが、これほぼ遊戯王のターン進行と同じなんですよね。自分なりに知恵を絞った結果だったんですが、さすが遊戯王。

ではこれを、実際のロジックに落としてみましょう。

強撃系スキルの実装

例えば、よくある強撃系のスキルの mpを5消費して、自分の攻撃力を1回だけ250%上げる を実装してみます。なおスキルは自動発動にします。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
  skill: { // skill追加
    attack: {
      name: '強撃',
      info: 'mpを5消費して、自分の攻撃力を1回だけ250%上げる',
      cost: 5 // 消費mp
    }
  }
}

const goblin = {
  maxHp: 300, // <- 少しタフに
  hp: 300,
  maxMp: 0,
  mp: 0,
  atk: 20,
  def: 5,
}

const damageCalculator = (offense, defense) => { ...略 }

const executeAttackPhase = (hero) => {
  if (hero.skill.attack) { // アタックスキルを持っていたら
    if (hero.mp >= hero.skill.attack.cost) { // mpが足りていれば
      hero.mp -= hero.skill.attack.cost // mpを消費して
      return hero.atk * 3.5 // スキル使用時の勇者の攻撃力を返す
    }
  }
  return hero.atk // 通常の勇者の攻撃力を返す
}

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtk = executeAttackPhase(hero) // アタックフェーズの実行
  const heroAtkDamage = damageCalculator(heroAtk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  if (goblin.hp > 0) {
    const goblinAtkDamage = damageCalculator(goblin.atk, hero.def)
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
勇者のHP: 100  ゴブリンのHP: 300
勇者の攻撃、ゴブリンに100のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 90  ゴブリンのHP: 200
勇者の攻撃、ゴブリンに100のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 80  ゴブリンのHP: 100
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 70  ゴブリンのHP: 75
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 60  ゴブリンのHP: 50
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 50  ゴブリンのHP: 25
勇者の攻撃、ゴブリンに25のダメージ!
勇者の勝ち!

ちゃんと強撃スキルが2回発動して倒せました。強撃系スキルは純粋に強いので、上昇幅や使用可能回数の調整が大切になります。頑張って調整しましょう。

次々考えていきましょう。

大防御

mpを1消費して、自分の防御力を1回だけ200%上げる について考えてみます。先程と似ていますが、発動タイミングが違いますね。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
  skill: {
    attack: {
      name: '強撃',
      info: 'mpを5消費して、自分の攻撃力を1回だけ250%上げる',
      cost: 5
    },
    defense: { // skill追加
      name: '大防御',
      info: 'mpを1消費して、自分の防御力を1回だけ200%上げる',
      cost: 1 // 消費mp
    }
  }
}

const goblin = { ...略 }

const damageCalculator = (offense, defense) => { ...略 }

const executeAttackPhase = (hero) => { ...略 }

const executeDefensePhase = (hero) => {
  if (hero.skill.defense) { // ディフェンススキルを持っていたら
    if (hero.mp >= hero.skill.defense.cost) { // mpが足りていれば
      hero.mp -= hero.skill.defense.cost // mpを消費して
      return hero.def * 2 // スキル使用時の勇者の防御力を返す
    }
  }
  return hero.def // 通常の勇者の防御力を返す
}

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtk = executeAttackPhase(hero)
  const heroAtkDamage = damageCalculator(heroAtk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  if (goblin.hp > 0) {
    const heroDefense = executeDefensePhase(hero) // ディフェンスフェーズの実行
    const goblinAtkDamage = damageCalculator(goblin.atk, heroDefense)
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
勇者のHP: 100  ゴブリンのHP: 300
勇者の攻撃、ゴブリンに100のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 98  ゴブリンのHP: 200
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 96  ゴブリンのHP: 175
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 94  ゴブリンのHP: 150
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 92  ゴブリンのHP: 125
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 90  ゴブリンのHP: 100
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 80  ゴブリンのHP: 75
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 70  ゴブリンのHP: 50
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 60  ゴブリンのHP: 25
勇者の攻撃、ゴブリンに25のダメージ!
勇者の勝ち!

これで、敵からの攻撃時も大防御でダメージを軽減できました。アクティブスキルにすれば、敵の大技に合わせて繰り出すとユーザに「気持ちいい」を与えれますね。さぁ、どんどんいきましょう。

ステータス強化

戦闘開始時にパーティの攻撃力1.2倍 について考えてみます。ノーコストで、いるだけでバフ付与の、つよつよキャラですね。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
  skill: {
    attack: {
      name: '強撃',
      info: 'mpを5消費して、自分の攻撃力を1回だけ250%上げる',
      cost: 5
    },
    defense: {
      name: '大防御',
      info: 'mpを1消費して、自分の防御力を1回だけ200%上げる',
      cost: 1
    },
    set: { // skill追加
      name: 'ステータス強化',
      info: '戦闘開始時にパーティの攻撃力1.2倍',
      cost: 0
    },
  }
}

const goblin = { ...略 }

const damageCalculator = (offense, defense) => { ...略 }

const executeAttackPhase = (hero) => { ...略 }

const executeDefensePhase = (hero) => { ...略 }

const executeSetPhase = (hero) => {
  if (hero.skill.set) { // セットスキルを持っていたら
    hero.atk = hero.atk * 1.2 // 攻撃力を1.2倍に
  }
}

executeSetPhase(hero) // セットフェーズの実行

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtk = executeAttackPhase(hero)
  const heroAtkDamage = damageCalculator(heroAtk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  if (goblin.hp > 0) {
    const heroDefense = executeDefensePhase(hero)
    const goblinAtkDamage = damageCalculator(goblin.atk, heroDefense)
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
勇者のHP: 100  ゴブリンのHP: 300
勇者の攻撃、ゴブリンに121のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 98  ゴブリンのHP: 179
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 96  ゴブリンのHP: 148
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 94  ゴブリンのHP: 117
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 92  ゴブリンのHP: 86
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
-------------------
勇者のHP: 90  ゴブリンのHP: 55
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 80  ゴブリンのHP: 24
勇者の攻撃、ゴブリンに31のダメージ!
勇者の勝ち!

やっぱりステータス強化スキルは強いですね。ステータス強化と強撃の組み合わせはバランス崩壊に直結するので、気をつけましょう。さぁさぁどんどんいきましょう。

状態異常

攻撃時に相手を毒にする についても考えてみます。今回は確定で毒にできるとします。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
  skill: {
    attack: {
      name: '強撃',
      info: 'mpを5消費して、自分の攻撃力を1回だけ250%上げる',
      cost: 5
    },
    defense: {
      name: '大防御',
      info: 'mpを1消費して、自分の防御力を1回だけ200%上げる',
      cost: 1
    },
    set: { // skill追加
      name: 'ステータス強化',
      info: '戦闘開始時にパーティの攻撃力1.2倍',
      cost: 0
    },
    attackAfter: {
      name: '毒撃',
      info: '攻撃時に相手を毒にする',
      cost: 0
    },
  }
}

const goblin = { ...略 }

const damageCalculator = (offense, defense) => { ...略 }

const executeAttackPhase = (hero) => { ...略 }

const executeDefensePhase = (hero) => { ...略 }

const executeSetPhase = (hero) => { ...略 }

const executeAttackAfterPhase = (hero) => {
  if (hero.skill.attackAfter) { // アタックアフタースキルを持っていたら
    goblin.status = 'poison' // 毒。毎ターンHPの1/10のダメージ
  }
}

executeSetPhase(hero)

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtk = executeAttackPhase(hero)
  const heroAtkDamage = damageCalculator(heroAtk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  executeAttackAfterPhase(hero) // アタックアフターフェーズの実行
  if (goblin.hp > 0) {
    const heroDefense = executeDefensePhase(hero)
    const goblinAtkDamage = damageCalculator(goblin.atk, heroDefense)
    hero.hp -= goblinAtkDamage
    console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    // 状態異常発生フェイズ
    if (goblin.status === 'poison') {
      const poisonDamage = Math.round(goblin.hp / 10)
      goblin.hp -= poisonDamage
      console.log(`ゴブリンに${poisonDamage}の毒ダメージ!`)
    }
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
勇者のHP: 100  ゴブリンのHP: 300
勇者の攻撃、ゴブリンに121のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
ゴブリンに18の毒ダメージ!
-------------------
勇者のHP: 98  ゴブリンのHP: 161
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
ゴブリンに13の毒ダメージ!
-------------------
勇者のHP: 96  ゴブリンのHP: 117
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
ゴブリンに9の毒ダメージ!
-------------------
勇者のHP: 94  ゴブリンのHP: 77
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
ゴブリンに5の毒ダメージ!
-------------------
勇者のHP: 92  ゴブリンのHP: 41
勇者の攻撃、ゴブリンに31のダメージ!
ゴブリンの攻撃、勇者に2のダメージ
ゴブリンに1の毒ダメージ!
-------------------
勇者のHP: 90  ゴブリンのHP: 9
勇者の攻撃、ゴブリンに31のダメージ!
勇者の勝ち!

アタックアフターフェイズで毒を発生させました。確定で毒だと強すぎるので、実際には確率でにしたほうがいいと思います。勇者がつよつよになってきましたね!さぁまだまだ。

回避

次は 50%で攻撃を避ける スキルを実装してみましょう。忍者や盗賊がよくもってるスキルですね。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
  skill: {
    attack: {
      name: '強撃',
      info: 'mpを5消費して、自分の攻撃力を1回だけ250%上げる',
      cost: 5
    },
    defense: {
      name: '大防御',
      info: 'mpを1消費して、自分の防御力を1回だけ200%上げる',
      cost: 1
    },
    set: { // skill追加
      name: 'ステータス強化',
      info: '戦闘開始時にパーティの攻撃力1.2倍',
      cost: 0
    },
    attackAfter: {
      name: '毒撃',
      info: '攻撃時に相手を毒にする',
      cost: 0
    },
    defenseAfter: {
      name: '回避',
      info: '50%で攻撃を避ける',
      cost: 0
    }
  }
}

const goblin = { ...略 }

const damageCalculator = (offense, defense) => { ...略 }

const executeAttackPhase = (hero) => { ...略 }

const executeDefensePhase = (hero) => { ...略 }

const executeSetPhase = (hero) => { ...略 }

const executeAttackAfterPhase = (hero) => { ...略 }

const executeDefenseAfterPhase = (hero) => {
  if (hero.skill.defenseAfter) { // ディフェンスアフタースキルを持っていたら
    if (Math.random() > 0.5) {
      return true // 回避判定
    }
  }
  return false // 回避判定
}

executeSetPhase(hero)

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtk = executeAttackPhase(hero)
  const heroAtkDamage = damageCalculator(heroAtk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  executeAttackAfterPhase(hero)
  if (goblin.hp > 0) {
    const heroDefense = executeDefensePhase(hero)
    const goblinAtkDamage = damageCalculator(goblin.atk, heroDefense)
    const avoidanceFlg = executeDefenseAfterPhase(hero) // ディフェンスアフターフェーズの実行
    if (!avoidanceFlg) {
      hero.hp -= goblinAtkDamage
      console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    } else {
      console.log(`勇者はゴブリンの攻撃を回避した!`)
    }

    if (goblin.status === 'poison') {
      const poisonDamage = Math.round(goblin.hp / 10)
      goblin.hp -= poisonDamage
      console.log(`ゴブリンに${poisonDamage}の毒ダメージ!`)
    }
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}

これで敵の攻撃を50%で避けることができるようになりました。更につよつよ勇者になってきましたね。次で最後にしましょう。

スキル無効化スキル

敵味方全員がスキル使用不可 というスキルを実装してみましょう。カタストロフ的なスキルですね。モンスターにスキルを設定してないから、デメリットしかありません。

const hero = {
  maxHp: 100,
  hp: 100,
  maxMp: 10,
  mp: 10,
  atk: 30,
  def: 10,
  skill: {
    attack: {
      name: '強撃',
      info: 'mpを5消費して、自分の攻撃力を1回だけ250%上げる',
      cost: 5
    },
    defense: {
      name: '大防御',
      info: 'mpを1消費して、自分の防御力を1回だけ200%上げる',
      cost: 1
    },
    set: { // skill追加
      name: 'ステータス強化',
      info: '戦闘開始時にパーティの攻撃力1.2倍',
      cost: 0
    },
    attackAfter: {
      name: '毒撃',
      info: '攻撃時に相手を毒にする',
      cost: 0
    },
    defenseAfter: {
      name: '回避',
      info: '50%で攻撃を避ける',
      cost: 0
    },
    start: {
      name: 'スキル無効化',
      info: '敵味方全員がスキル使用不可',
      cost: 0
    },
  }
}

const goblin = { ...略 }

const damageCalculator = (offense, defense) => { ...略 }

const executeAttackPhase = (hero) => { ...略 }

const executeDefensePhase = (hero) => { ...略 }

const executeSetPhase = (hero) => { ...略 }

const executeAttackAfterPhase = (hero) => { ...略 }

const executeDefenseAfterPhase = (hero) => { ...略 }

const executeStartPhase = (hero) => {
  if (hero.skill.start) { // スタートスキルを持っていたら
    hero.skill = {}
    console.log('全員のスキルを無効化!')
  }
}

executeStartPhase(hero) // スタートフェーズの実行

executeSetPhase(hero)

while (hero.hp > 0 && goblin.hp > 0) {
  console.log(`勇者のHP: ${hero.hp}  ゴブリンのHP: ${goblin.hp}`)
  const heroAtk = executeAttackPhase(hero)
  const heroAtkDamage = damageCalculator(heroAtk, goblin.def)
  goblin.hp -= heroAtkDamage
  console.log(`勇者の攻撃、ゴブリンに${heroAtkDamage}のダメージ!`)
  executeAttackAfterPhase(hero)
  if (goblin.hp > 0) {
    const heroDefense = executeDefensePhase(hero)
    const goblinAtkDamage = damageCalculator(goblin.atk, heroDefense)
    const avoidanceFlg = executeDefenseAfterPhase(hero)
    if (!avoidanceFlg) {
      hero.hp -= goblinAtkDamage
      console.log(`ゴブリンの攻撃、勇者に${goblinAtkDamage}のダメージ`)
    } else {
      console.log(`勇者はゴブリンの攻撃を回避した!`)
    }

    if (goblin.status === 'poison') {
      const poisonDamage = Math.round(goblin.hp / 10)
      goblin.hp -= poisonDamage
      console.log(`ゴブリンに${poisonDamage}の毒ダメージ!`)
    }
    console.log('-------------------')
  }
}

if (hero.hp) {
  console.log('勇者の勝ち!')
} else {
  console.log('ゴブリンの勝ち!')
}
全員のスキルを無効化!
勇者のHP: 100  ゴブリンのHP: 300
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 90  ゴブリンのHP: 275
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 80  ゴブリンのHP: 250
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 70  ゴブリンのHP: 225
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 60  ゴブリンのHP: 200
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 50  ゴブリンのHP: 175
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 40  ゴブリンのHP: 150
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 30  ゴブリンのHP: 125
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 20  ゴブリンのHP: 100
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
勇者のHP: 10  ゴブリンのHP: 75
勇者の攻撃、ゴブリンに25のダメージ!
ゴブリンの攻撃、勇者に10のダメージ
-------------------
ゴブリンの勝ち!

こういう時の為のスタートフェイズです。無事に勇者は全スキルが封印されて、非合法強化勇者はゴブリンに負けました。

めでたしめでたし。

おわりに

普段何気なく遊んでいるゲームの「たたかう」ですが、こうやって自分でロジックを考えてソースコードに落としてみると、いろんな気付きがあるものです。面白そうだなぁと思ったら、ぜひやってみてください。ロジカルシンキングのトレーニングにもなります。