bcdice/BCDice

View on GitHub
lib/bcdice/game_system/cthulhu7th/full_auto.rb

Summary

Maintainability
C
1 day
Test Coverage
# frozen_string_literal: true

module BCDice
  module GameSystem
    class Cthulhu7th < Base
      class FullAuto
        BONUS_DICE_RANGE = (-2..2).freeze

        # 連射処理を止める条件(難易度の閾値)
        # @return [Hash<String, Integer>]
        #
        # 成功の種類の小文字表記 => 難易度の閾値
        ROLL_FULL_AUTO_DIFFICULTY_THRESHOLD = {
          # レギュラー
          "r" => 0,
          # ハード
          "h" => 1,
          # イクストリーム
          "e" => 2
        }.freeze

        def self.eval(command, randomizer)
          new.eval(command, randomizer)
        end

        def eval(command, randomizer)
          @randomizer = randomizer
          getFullAutoResult(command)
        end

        private

        include Rollable

        def getFullAutoResult(command)
          m = /^FAR\((-?\d+),(-?\d+),(-?\d+)(?:,(-?\d+)?)?(?:,(-?\w+)?)?(?:,(-?\d+)?)?\)$/i.match(command)
          unless m
            return nil
          end

          bullet_count = m[1].to_i
          diff = m[2].to_i
          broken_number = m[3].to_i
          bonus_dice_count = m[4].to_i
          stop_count = m[5]&.downcase || ""
          bullet_set_count_cap = m[6]&.to_i || diff / 10

          output = ""

          # 最大で(8回*(PC技能値最大値/10))=72発しか撃てないはずなので上限
          bullet_count_limit = 100
          if bullet_count > bullet_count_limit
            output += "弾薬が多すぎます。装填された弾薬を#{bullet_count_limit}発に変更します。\n"
            bullet_count = bullet_count_limit
          end

          # ボレーの上限の設定がおかしい場合の注意表示
          if (bullet_set_count_cap > diff / 10) && (diff > 39) && !m[6].nil?
            bullet_set_count_cap = diff / 10
            output += "ボレーの弾丸の数の上限は\[技能値÷10(切り捨て)\]発なので、それより高い数を指定できません。ボレーの弾丸の数を#{bullet_set_count_cap}発に変更します。\n"
          elsif (diff <= 39) && (bullet_set_count_cap > 3) && !m[6].nil?
            bullet_set_count_cap = 3
            output += "技能値が39以下ではボレーの弾丸の数の上限および下限は3発です。ボレーの弾丸の数を#{bullet_set_count_cap}発に変更します。\n"
          end

          # ボレーの下限の設定がおかしい場合の注意表示およびエラー表示
          return "ボレーの弾丸の数は正の数です。" if (bullet_set_count_cap <= 0) && !m[6].nil?

          if (bullet_set_count_cap < 3) && !m[6].nil?
            bullet_set_count_cap = 3
            output += "ボレーの弾丸の数の下限は3発です。ボレーの弾丸の数を3発に変更します。\n"
          end

          return "弾薬は正の数です。" if bullet_count <= 0
          return "目標値は正の数です。" if diff <= 0

          if broken_number < 0
            output += "故障ナンバーは正の数です。マイナス記号を外します。\n"
            broken_number = broken_number.abs
          end

          unless BONUS_DICE_RANGE.include?(bonus_dice_count)
            return "エラー。ボーナス・ペナルティダイスの値は#{BONUS_DICE_RANGE.min}~#{BONUS_DICE_RANGE.max}です。"
          end

          output += "ボーナス・ペナルティダイス[#{bonus_dice_count}]"
          output += rollFullAuto(bullet_count, diff, broken_number, bonus_dice_count, stop_count, bullet_set_count_cap)

          return output
        end

        def rollFullAuto(bullet_count, diff, broken_number, dice_num, stop_count, bullet_set_count_cap)
          output = ""
          loopCount = 0

          counts = {
            hit_bullet: 0,
            impale_bullet: 0,
            bullet: bullet_count,
          }

          # 難易度変更用ループ
          4.times do |more_difficulty|
            output += getNextDifficultyMessage(more_difficulty)

            # ペナルティダイスを減らしながらロール用ループ
            while dice_num >= BONUS_DICE_RANGE.min

              loopCount += 1
              hit_result, total, total_list = getHitResultInfos(dice_num, diff, more_difficulty)
              output += "\n#{loopCount}回目: > #{total_list.join(', ')} > #{hit_result}"

              if total >= broken_number
                output += " ジャム"
                return getHitResultText(output, counts)
              end

              hit_type = getHitType(more_difficulty, hit_result)
              hit_bullet, impale_bullet, lost_bullet = getBulletResults(counts[:bullet], hit_type, diff, bullet_set_count_cap)

              output += " (#{hit_bullet}発が命中、#{impale_bullet}発が貫通)"

              counts[:hit_bullet] += hit_bullet
              counts[:impale_bullet] += impale_bullet
              counts[:bullet] -= lost_bullet

              return getHitResultText(output, counts) if counts[:bullet] <= 0

              dice_num -= 1
            end

            # 指定された難易度となった場合、連射処理を途中で止める
            if shouldStopRollFullAuto?(stop_count, more_difficulty)
              output += "\n【指定の難易度となったので、処理を終了します。】"
              break
            end

            dice_num += 1
          end

          return getHitResultText(output, counts)
        end

        # 連射処理を止めるべきかどうかを返す
        # @param [String] stop_count 成功の種類
        # @param [Integer] difficulty 難易度
        # @return [Boolean]
        def shouldStopRollFullAuto?(stop_count, difficulty)
          difficulty_threshold = ROLL_FULL_AUTO_DIFFICULTY_THRESHOLD[stop_count]
          return difficulty_threshold && difficulty >= difficulty_threshold
        end

        def getHitResultInfos(dice_num, diff, more_difficulty)
          total, total_list = roll_with_bonus(dice_num)

          fumbleable = getFumbleable(more_difficulty)
          hit_result = ResultLevel.from_values(total, diff, fumbleable).to_s

          return hit_result, total, total_list
        end

        def getHitResultText(output, counts)
          return "#{output}\n> #{counts[:hit_bullet]}発が通常命中、#{counts[:impale_bullet]}発が貫通、残弾#{counts[:bullet]}発"
        end

        def getHitType(more_difficulty, hit_result)
          successList, impaleBulletList = getSuccessListImpaleBulletList(more_difficulty)

          return :hit if successList.include?(hit_result)
          return :impale if impaleBulletList.include?(hit_result)

          return ""
        end

        def getBulletResults(bullet_count, hit_type, diff, bullet_set_count_cap)
          bullet_set_count = getSetOfBullet(diff, bullet_set_count_cap)
          hit_bullet_count_base = getHitBulletCountBase(diff, bullet_set_count)
          impale_bullet_count_base = (bullet_set_count / 2.to_f)

          lost_bullet_count = 0
          hit_bullet_count = 0
          impale_bullet_count = 0

          if !isLastBulletTurn(bullet_count, bullet_set_count)

            case hit_type
            when :hit
              hit_bullet_count = hit_bullet_count_base # 通常命中した弾数の計算

            when :impale
              impale_bullet_count = impale_bullet_count_base.floor # 貫通した弾数の計算
              hit_bullet_count = impale_bullet_count_base.ceil
            end

            lost_bullet_count = bullet_set_count

          else

            case hit_type
            when :hit
              hit_bullet_count = getLastHitBulletCount(bullet_count)

            when :impale
              impale_bullet_count = getLastHitBulletCount(bullet_count)
              hit_bullet_count = bullet_count - impale_bullet_count
            end

            lost_bullet_count = bullet_count
          end

          return hit_bullet_count, impale_bullet_count, lost_bullet_count
        end

        def getSuccessListImpaleBulletList(more_difficulty)
          successList = []
          impaleBulletList = []

          case more_difficulty
          when 0
            successList = ["ハード成功", "レギュラー成功"]
            impaleBulletList = ["クリティカル", "イクストリーム成功"]
          when 1
            successList = ["ハード成功"]
            impaleBulletList = ["クリティカル", "イクストリーム成功"]
          when 2
            successList = []
            impaleBulletList = ["クリティカル", "イクストリーム成功"]
          when 3
            successList = ["クリティカル"]
            impaleBulletList = []
          end

          return successList, impaleBulletList
        end

        def getNextDifficultyMessage(more_difficulty)
          case more_difficulty
          when 1
            return "\n【難易度がハードに変更】"
          when 2
            return "\n【難易度がイクストリームに変更】"
          when 3
            return "\n【難易度がクリティカルに変更】"
          end

          return ""
        end

        def getSetOfBullet(diff, bullet_set_count_cap)
          bullet_set_count = diff / 10

          if bullet_set_count_cap < bullet_set_count
            bullet_set_count = bullet_set_count_cap
          end

          if (diff >= 1) && (diff < 30)
            bullet_set_count = 3 # 技能値が29以下での最低値保障処理
          end

          return bullet_set_count
        end

        def getHitBulletCountBase(diff, bullet_set_count)
          hit_bullet_count_base = (bullet_set_count / 2)

          if (diff >= 1) && (diff < 30)
            hit_bullet_count_base = 1 # 技能値29以下での最低値保障
          end

          return hit_bullet_count_base
        end

        def isLastBulletTurn(bullet_count, bullet_set_count)
          ((bullet_count - bullet_set_count) < 0)
        end

        def getLastHitBulletCount(bullet_count)
          # 残弾1での最低値保障処理
          if bullet_count == 1
            return 1
          end

          count = (bullet_count / 2.to_f).floor
          return count
        end

        def getFumbleable(more_difficulty)
          # 成功が49以下の出目のみとなるため、ファンブル値は上昇
          return (more_difficulty >= 1)
        end
      end
    end
  end
end