bcdice/BCDice

View on GitHub
lib/bcdice/game_system/StellarKnights.rb

Summary

Maintainability
B
4 hrs
Test Coverage
A
99%
# frozen_string_literal: true

require "bcdice/dice_table/table"
require "bcdice/dice_table/d66_grid_table"

module BCDice
  module GameSystem
    class StellarKnights < Base
      # ゲームシステムの識別子
      ID = 'StellarKnights'

      # ゲームシステム名
      NAME = '銀剣のステラナイツ'

      # ゲームシステム名の読みがな
      SORT_KEY = 'きんけんのすてらないつ'

      # ダイスボットの使い方
      HELP_MESSAGE = <<~MESSAGETEXT
        ・アタック判定 nSK[d][,k>l,...]
        []内は省略可能。
        n: ダイス数、d: アタック判定における対象の防御力、k, l: ダイスの出目がkならばlに変更(アマランサスのスキル「始まりの部屋」用)
        d省略時はダイスを振った結果のみ表示。(nSKはnB6と同じ)

        4SK: ダイスを4個振って、その結果を表示
        4+2SK: ダイスを4+2 (=6) 個振って、その結果を表示
        5/2SK: ダイスを5個の半分 (=2) 個振って、その結果を表示
        (5+3)/2SK: ダイスを(5+3)個の半分 (=4) 個振って、その結果を表示
        5SK3: 【アタック判定:5ダイス】、対象の防御力を3として成功数を表示
        3SK,1>6: ダイスを3個振り、出目が1のダイスを全て6に変更し、その結果を表示
        6SK4,1>6,2>6: 【アタック判定:6ダイス】、出目が1と2のダイスを全て6に変更、対象の防御力を4として成功数を表示

        ・基本
        TT:お題表
        STA    :シチュエーション表A:時間 (Situation Table A)
        STB    :シチュエーション表B:場所 (ST B)
        STB2[n]:シチュエーション表B その2:学園編 (ST B 2)
         n: 1(アーセルトレイ), 2(イデアグロリア), 3(シトラ), 4(フィロソフィア), 5(聖アージェティア), 6(SoA)
        STC    :シチュエーション表C:話題 (ST C)
        ALLS   :シチュエーション表全てを一括で(学園編除く)
        GAT:所属組織決定 (Gakuen Table)
        HOT:希望表 (Hope Table)
        DET:絶望表 (Despair Table)
        WIT:願い事表 (Wish Table)
        YST:あなたの物語表 (Your Story Table)
        YSTA:あなたの物語表:異世界 (YST Another World)
        PET:性格表 (Personality Table)
            性格表を2回振り、性格を決定する

        ・霧と桜のマルジナリア
        YSTM:あなたの物語表:マルジナリア世界 (YST Marginalia)
        STM:シチュエーション表:マルジナリア世界 (ST Marginalia)
        YSTL:あなたの物語表:手紙世界 (YST Letter)
        YSTR:あなたの物語表:リコレクト・ドール (YST Recollect-doll)
        STBR:シチュエーション表B:場所(リコレクト・ドール) (ST B Recollect-doll)
        STCR:シチュエーション表C:リコレクト (ST C Recollect)
        STBS:シチュエーション表B:シトラセッティング (ST B Sut Tu Real)
        STE:シチュエーション表:エクリプス専用 (ST Eclipse)

        ・紫弾のオルトリヴート
        FT:フラグメント表 (Fragment Table)
            フラグメント表を5回振る
        FTx:フラグメント表をx回振る
        YSTB:あなたの物語表:ブリンガー (YST Bringer)
        YSTF:あなたの物語表:フォージ (YST Forge)
        STAL:シチュエーション表:オルトリヴート (ST Alt-Levoot)
      MESSAGETEXT

      def initialize(command)
        super(command)

        @sort_barabara_dice = true # バラバラロール(Bコマンド)でソート有
        @d66_sort_type = D66SortType::NO_SORT
      end

      def eval_game_system_specific_command(command)
        command = command.upcase

        if (table = self.class::TABLES[command])
          table.roll(@randomizer)
        elsif (m = %r{([()+/\d]+)SK(\d)?((,\d>\d)+)?}.match(command))
          num_dices = Arithmetic.eval(m[1], RoundType::FLOOR)

          unless num_dices.nil?
            resolute_action(num_dices, m[2] && m[2].to_i, m[3])
          end
        elsif command == 'STB2'
          roll_all_situation_b2_tables
        elsif command == 'ALLS'
          roll_all_situation_tables
        elsif command == "PET"
          roll_personality_table
        elsif (m = /FT(\d+)?/.match(command))
          num = (m[1] || 5).to_i
          roll_fragment_table(num)
        end
      end

      private

      # @param [Integer] num_dices
      # @param [Integer | nil] defence
      # @param [String] dice_change_text
      # @return [Result, String]
      def resolute_action(num_dices, defence, dice_change_text)
        dices = @randomizer.roll_barabara(num_dices, 6).sort
        dice_text = dices.join(",")

        output = "(#{remake_command(num_dices, defence, dice_change_text)}) > #{dice_text}"
        if dices.empty?
          return output + translate("StellarKnights.SK.no_dice_error")
        end

        # FAQによると、ダイスの置き換えは宣言された順番に適用されていく
        dice_change_rules = parse_dice_change_rules(dice_change_text)
        dice_change_rules.each do |rule|
          dices.map! { |val| val == rule[:from] ? rule[:to] : val }
        end

        unless dice_change_rules.empty?
          dices.sort!
          output += " > [#{dices.join(',')}]"
        end

        if defence.nil?
          success = false
          failure = false
        else
          success_num = dices.count { |val| val >= defence }
          output += " > " + translate("StellarKnights.SK.success_num", success_num: success_num)
          success = success_num > 0
          failure = !success
        end

        Result.new(output).tap do |r|
          r.success = success
          r.failure = failure
        end
      end

      def remake_command(num_dices, defence, dice_change_text)
        command = "#{num_dices}SK"
        command += defence.to_s unless defence.nil?
        command += dice_change_text unless dice_change_text.nil?
        command
      end

      def parse_dice_change_rules(text)
        return [] if text.nil?

        # 正規表現の都合で先頭に ',' が残っているので取っておく
        text = text[1..-1]
        text.split(',').map do |rule|
          v = rule.split('>').map(&:to_i)
          {
            from: v[0],
            to: v[1],
          }
        end
      end

      def roll_all_situation_b2_tables
        (1..6).map { |num| self.class::TABLES["STB2#{num}"].roll(@randomizer) }.join("\n")
      end

      def roll_all_situation_tables
        ['STA', 'STB', 'STC'].map { |command| self.class::TABLES[command].roll(@randomizer) }.join("\n")
      end

      def roll_personality_table
        value1, index1 = get_table_by_d66(translate("StellarKnights.PET.items"))
        value2, index2 = get_table_by_d66(translate("StellarKnights.PET.items"))
        name = translate("StellarKnights.PET.name")
        result = translate("StellarKnights.PET.result", value1: value1, value2: value2)
        return "#{name}(#{index1},#{index2}) > #{result}"
      end

      def roll_fragment_table(num)
        if num <= 0
          return nil
        end

        results = Array.new(num) { get_table_by_d66(translate("StellarKnights.FT.items")) }
        values = results.map { |r| r[0] }
        indexes = results.map { |r| r[1] }
        name = translate("StellarKnights.FT.name")

        return "#{name}(#{indexes.join(',')}) > #{values.join(translate('StellarKnights.FT.sep'))}"
      end

      class << self
        private

        def translate_tables(locale)
          {
            "TT" => DiceTable::D66GridTable.from_i18n("StellarKnights.tables.TT", locale),
            "STA" => DiceTable::Table.from_i18n("StellarKnights.tables.STA", locale),
            "STB" => DiceTable::D66OneThirdTable.from_i18n("StellarKnights.tables.STB", locale),
            "STB21" => DiceTable::Table.from_i18n("StellarKnights.tables.STB21", locale),
            "STB22" => DiceTable::Table.from_i18n("StellarKnights.tables.STB22", locale),
            "STB23" => DiceTable::Table.from_i18n("StellarKnights.tables.STB23", locale),
            "STB24" => DiceTable::Table.from_i18n("StellarKnights.tables.STB24", locale),
            "STB25" => DiceTable::Table.from_i18n("StellarKnights.tables.STB25", locale),
            "STB26" => DiceTable::Table.from_i18n("StellarKnights.tables.STB26", locale),
            "STC" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STC", locale),
            "GAT" => DiceTable::Table.from_i18n("StellarKnights.tables.GAT", locale),
            "HOT" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.HOT", locale),
            "DET" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.DET", locale),
            "WIT" => DiceTable::D66OneThirdTable.from_i18n("StellarKnights.tables.WIT", locale),
            "YST" => DiceTable::D66OneThirdTable.from_i18n("StellarKnights.tables.YST", locale),
            "YSTA" => DiceTable::D66OneThirdTable.from_i18n("StellarKnights.tables.YSTA", locale),
            "YSTM" => DiceTable::D66OneThirdTable.from_i18n("StellarKnights.tables.YSTM", locale),
            "STM" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STM", locale),
            "YSTL" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.YSTL", locale),
            "YSTR" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.YSTR", locale),
            "STBR" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STBR", locale),
            "STCR" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STCR", locale),
            "STBS" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STBS", locale),
            "STE" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STE", locale),
            "YSTB" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.YSTB", locale),
            "YSTF" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.YSTF", locale),
            "STAL" => DiceTable::D66HalfGridTable.from_i18n("StellarKnights.tables.STAL", locale),
          }
        end
      end

      TABLES = translate_tables(:ja_jp)

      register_prefix('[()+\/\d]+SK', 'STB2', 'ALLS', 'PET', 'FT', TABLES.keys)
    end
  end
end