cw_card_utils 0.1.11 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Public: New preferred namespace entrypoint.
4
+ # Usage:
5
+ # require "crackling_wit/card_utils"
6
+ # CracklingWit::CardUtils::DeckComparator.new(...)
7
+ #
8
+ # This file aliases the existing implementation under the new namespace
9
+ # to provide a zero-churn migration path. All classes/modules remain
10
+ # implemented in CwCardUtils and are re-exported here.
11
+
12
+ require_relative "../cw_card_utils"
13
+
14
+ module CracklingWit
15
+ module CardUtils
16
+ # Delegate configuration to the legacy module
17
+ class << self
18
+ def card_data_source=(source)
19
+ ::CwCardUtils.card_data_source = source
20
+ end
21
+
22
+ def card_data_source
23
+ ::CwCardUtils.card_data_source
24
+ end
25
+
26
+ def configure(&block)
27
+ ::CwCardUtils.configure(&block)
28
+ end
29
+ end
30
+
31
+ # Re-export legacy constants under the new namespace
32
+ Error = ::CwCardUtils::Error
33
+ CardDataSource = ::CwCardUtils::CardDataSource
34
+ CurveCalculator = ::CwCardUtils::CurveCalculator
35
+ DecklistParser = ::CwCardUtils::DecklistParser
36
+ ScryfallCmcData = ::CwCardUtils::ScryfallCmcData
37
+ SynergyProbability = ::CwCardUtils::SynergyProbability
38
+ DeckComparator = ::CwCardUtils::DeckComparator
39
+ VERSION = ::CwCardUtils::VERSION
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Top-level namespace for CracklingWit gems
4
+ module CracklingWit
5
+ end
@@ -1,7 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CwCardUtils
4
+ # Public: Computes mana curve distributions from a deck.
5
+ # 日本語: デッキからマナカーブ分布を計算します。
4
6
  class CurveCalculator
7
+ # Public: Initialize with a deck-like object.
8
+ # 日本語: デッキ相当のオブジェクトで初期化します。
9
+ #
10
+ # @param deck [CwCardUtils::DecklistParser::Deck]
5
11
  def initialize(deck)
6
12
  @deck = deck
7
13
  @deck_size = @deck.count_without_lands
@@ -11,6 +17,18 @@ module CwCardUtils
11
17
  @collapsed_curve = {}
12
18
  end
13
19
 
20
+ # Public: Collapsed curve counts (0-1,2,3,4,5,6+).
21
+ # 日本語: まとめられたカーブ分布(0-1,2,3,4,5,6+)。
22
+ #
23
+ # How it works (EN): Builds upon the raw curve, bucketing CMCs into
24
+ # coarse buckets for quick, human-readable summaries. Lands are
25
+ # excluded, and nil/zero CMC non-lands are treated as 0.
26
+ #
27
+ # 仕組み (JA): 素のカーブを基に、CMC を粗い区分へまとめて
28
+ # 人が読みやすい集計を生成します。土地は除外し、nil/0 の CMC
29
+ # (土地でない) は 0 として扱います。
30
+ #
31
+ # @return [Hash{String=>Integer}]
14
32
  def collapsed_curve
15
33
  return @collapsed_curve if @collapsed_curve.values.any?
16
34
 
@@ -37,16 +55,48 @@ module CwCardUtils
37
55
  @collapsed_curve
38
56
  end
39
57
 
58
+ # Public: Raw curve counts keyed by CMC bucket.
59
+ # 日本語: CMCバケットごとの素のカウント。
60
+ #
61
+ # How it works (EN): Iterates over non-land cards and increments a
62
+ # bucket equal to ceil(CMC). Cards with nil/0 CMC and non-land type
63
+ # are assigned to bucket 0; lands are skipped entirely.
64
+ #
65
+ # 仕組み (JA): 土地以外のカードを走査し、ceil(CMC) をバケットに
66
+ # 加算します。CMC が nil/0 で土地でない場合は 0 バケットへ。
67
+ # 土地はスキップします。
68
+ #
69
+ # @return [Hash{Integer=>Integer}]
40
70
  def curve
41
71
  calculate_curve if @raw_curve.empty?
42
72
  @raw_curve
43
73
  end
44
74
 
75
+ # Public: Normalized curve (fractions of non-land count).
76
+ # 日本語: 正規化されたカーブ (土地を除く総数に対する割合)。
77
+ #
78
+ # How it works (EN): Sorts buckets by CMC and divides each count by
79
+ # the number of non-land cards, rounding to 4 decimals.
80
+ #
81
+ # 仕組み (JA): CMC 順にソートし、各バケットを土地以外の総枚数で
82
+ # 割って 4 桁に丸めます。
83
+ #
84
+ # @return [Hash{Integer=>Float}]
45
85
  def normalized_curve
46
86
  normalize_curve if @normalized_curve.values.empty?
47
87
  @normalized_curve
48
88
  end
49
89
 
90
+ # Public: Collapsed and normalized curve distribution.
91
+ # 日本語: まとめかつ正規化したカーブ分布。
92
+ #
93
+ # How it works (EN): Applies the same normalization as above to the
94
+ # collapsed buckets, providing an at-a-glance shape of the deck.
95
+ #
96
+ # 仕組み (JA): まとめたバケットに対して同様の正規化を行い、
97
+ # デッキの形状を直感的に把握できるようにします。
98
+ #
99
+ # @return [Hash{String=>Float}]
50
100
  def collapsed_normalized_curve
51
101
  normalize_collapsed_curve if @collapsed_normalized_curve.values.empty?
52
102
  @collapsed_normalized_curve
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ ##
4
+ # Public: Compares two decks and produces matchup insights for play/draw.
5
+ # 日本語: 2つのデッキを比較し、先手/後手のマッチアップ分析を生成します。
3
6
  module CwCardUtils
4
7
  class DeckComparator
5
8
  attr_reader :deck_a, :deck_b, :analysis
@@ -11,12 +14,32 @@ module CwCardUtils
11
14
  interaction: 0.20,
12
15
  }.freeze
13
16
 
17
+ # Public: Create a comparator for two decks.
18
+ # 日本語: 2つのデッキを対象にコンパレータを作成します。
19
+ #
20
+ # @param deck_a [CwCardUtils::DecklistParser::Deck] 比較対象Aのデッキ
21
+ # @param deck_b [CwCardUtils::DecklistParser::Deck] 比較対象Bのデッキ
22
+ # @return [void]
14
23
  def initialize(deck_a, deck_b)
15
24
  @deck_a = deck_a
16
25
  @deck_b = deck_b
17
26
  @analysis = {}
18
27
  end
19
28
 
29
+ # Public: Compare decks for on-play and on-draw scenarios.
30
+ # 日本語: 先手/後手の両シナリオで比較します。
31
+ #
32
+ # Intent (EN): Produces two analyses (on the play / on the draw)
33
+ # combining archetype, curve segmentation, synergy hit-rate, and
34
+ # interaction density. A weighted heuristic yields Deck A's win
35
+ # rate and a favored label, plus explanatory notes.
36
+ #
37
+ # 意図 (JA): アーキタイプ、カーブ分割、シナジー成立率、
38
+ # インタラクション密度を統合し、先手/後手の 2 通りの分析を
39
+ # 生成します。重み付きヒューリスティックによりデッキAの勝率と
40
+ # 有利側のラベルを算出し、説明的なノートを付与します。
41
+ #
42
+ # @return [Hash] { on_play: Hash, on_draw: Hash }
20
43
  def compare
21
44
  results = {
22
45
  on_play: matchup_scenario(on_play: true),
@@ -27,6 +50,8 @@ module CwCardUtils
27
50
 
28
51
  private
29
52
 
53
+ # Internal: Build a single scenario result.
54
+ # 日本語: 単一シナリオの結果を作成します。
30
55
  def matchup_scenario(on_play:)
31
56
  a_info = analyze_deck(deck_a, on_play: on_play)
32
57
  b_info = analyze_deck(deck_b, on_play: !on_play)
@@ -51,6 +76,10 @@ module CwCardUtils
51
76
  }
52
77
  end
53
78
 
79
+ # Internal: Extract features used by the comparator from a deck.
80
+ # 日本語: デッキから比較用の特徴量を抽出します。
81
+ #
82
+ # @return [Hash]
54
83
  def analyze_deck(deck, on_play:)
55
84
  detector = CwCardUtils::DecklistParser::ArchetypeDetector.new(deck)
56
85
  archetype = detector.detect
@@ -76,6 +105,8 @@ module CwCardUtils
76
105
  }
77
106
  end
78
107
 
108
+ # Internal: Average combo hit-rate by turn 5 given play/draw.
109
+ # 日本語: 先手/後手を考慮した5ターン目までのシナジー成立率の平均。
79
110
  def calc_synergy_hit_rate(deck, on_play:)
80
111
  synergy_pairs = extract_synergy_pairs(deck)
81
112
  return 0 if synergy_pairs.empty?
@@ -88,6 +119,8 @@ module CwCardUtils
88
119
  (probs.sum / probs.size).round(2)
89
120
  end
90
121
 
122
+ # Internal: Return unordered pairs of synergy-tagged cards.
123
+ # 日本語: シナジー系タグのカード名ペアを返します。
91
124
  def extract_synergy_pairs(deck)
92
125
  synergy_cards = deck.main.select do |card|
93
126
  card.tags.intersect?([:synergistic_finisher, :tribal_synergy, :scaling_threat])
@@ -96,10 +129,14 @@ module CwCardUtils
96
129
  synergy_cards.map(&:name).combination(2).to_a
97
130
  end
98
131
 
132
+ # Internal: Cards seen by specified turn.
133
+ # 日本語: 指定ターンまでに見られるカード枚数。
99
134
  def draws_by_turn(turn, on_play:)
100
135
  on_play ? (7 + (turn - 1)) : (7 + turn)
101
136
  end
102
137
 
138
+ # Internal: Combine sub-scores into a single win-rate for Deck A.
139
+ # 日本語: 複数の部分スコアを統合し、デッキAの勝率を返します。
103
140
  def weighted_score(a, b)
104
141
  # Archetype advantage
105
142
  archetype_score = case predict_matchup(a, b)
@@ -143,6 +180,8 @@ module CwCardUtils
143
180
  ).round(3)
144
181
  end
145
182
 
183
+ # Internal: Heuristic label for who is favored and why.
184
+ # 日本語: どちらが有利かのヒューリスティックな説明を返します。
146
185
  def predict_matchup(a, b)
147
186
  return "Deck A (Aggro)" if is_aggro?(a) && is_control?(b)
148
187
  return "Deck B (Aggro)" if is_aggro?(b) && is_control?(a)
@@ -153,14 +192,20 @@ module CwCardUtils
153
192
  "Even Matchup"
154
193
  end
155
194
 
195
+ # Internal: Aggro heuristic.
196
+ # 日本語: アグロ判定のヒューリスティック。
156
197
  def is_aggro?(info)
157
198
  info[:archetype].downcase.include?("aggro") || info[:early_curve] > 0.5
158
199
  end
159
200
 
201
+ # Internal: Control heuristic.
202
+ # 日本語: コントロール判定のヒューリスティック。
160
203
  def is_control?(info)
161
204
  info[:archetype].downcase.include?("control") || info[:late_curve] > 0.4
162
205
  end
163
206
 
207
+ # Internal: Compare interaction tag density.
208
+ # 日本語: インタラクションの密度を比較します。
164
209
  def compare_interaction_density(a, b)
165
210
  a_interact = a[:tag_ratios][:interaction].to_f.round(2)
166
211
  b_interact = b[:tag_ratios][:interaction].to_f.round(2)
@@ -174,6 +219,8 @@ module CwCardUtils
174
219
  end
175
220
  end
176
221
 
222
+ # Internal: Produce human-readable notes for a scenario.
223
+ # 日本語: シナリオごとの人間に読みやすい注釈を生成します。
177
224
  def generate_notes(a, b, on_play:)
178
225
  notes = []
179
226
  notes << (on_play ? "Deck A is on the play" : "Deck B is on the play")
@@ -2,9 +2,16 @@
2
2
 
3
3
  module CwCardUtils
4
4
  module DecklistParser
5
+ # Public: Detects deck archetypes and color/tribe labels.
6
+ # 日本語: デッキのアーキタイプと色/部族ラベルを検出します。
5
7
  class ArchetypeDetector
6
8
  attr_reader :deck, :format, :tribe, :colors, :tag_counts, :tag_ratios
7
9
 
10
+ # Public: Initialize with a deck.
11
+ # 日本語: デッキで初期化します。
12
+ #
13
+ # @param deck [CwCardUtils::DecklistParser::Deck]
14
+ # @return [void]
8
15
  def initialize(deck)
9
16
  @deck = deck
10
17
  @format = deck.format.to_s.downcase # :standard, :edh, etc.
@@ -16,6 +23,21 @@ module CwCardUtils
16
23
  calculate_ratios
17
24
  end
18
25
 
26
+ # Public: Full label, e.g., "Azorius Cat Tribal Midrange".
27
+ # 日本語: 例 "Azorius Cat Tribal Midrange" のような完全ラベルを返します。
28
+ #
29
+ # Intent (EN): Converts detected color identity, dominant tribe, and
30
+ # archetype into a concise human-readable label. Color comes from
31
+ # identity resolution, tribe from majority subtype, and archetype
32
+ # from heuristics over tag counts/ratios and average CMC.
33
+ #
34
+ # 意図 (JA): 検出した色アイデンティティ、優勢トライブ、
35
+ # アーキタイプを人が読みやすい短いラベルへ変換します。色は
36
+ # アイデンティティ解決、トライブは多数派サブタイプ、
37
+ # アーキタイプはタグ頻度/比率と平均 CMC のヒューリスティックから
38
+ # 決定します。
39
+ #
40
+ # @return [String]
19
41
  def detect
20
42
  archetype = detect_archetype
21
43
  color_label = resolve_color_label(colors)
@@ -30,6 +52,8 @@ module CwCardUtils
30
52
 
31
53
  # === TAG COUNTING ===
32
54
 
55
+ # Internal: Count tag frequencies across the deck.
56
+ # 日本語: デッキ全体のタグ出現数をカウントします。
33
57
  def count_tags
34
58
  total_tags = 0
35
59
 
@@ -45,6 +69,8 @@ module CwCardUtils
45
69
  @tag_counts[:_total] = total_tags
46
70
  end
47
71
 
72
+ # Internal: Convert counts to ratios.
73
+ # 日本語: カウントを比率へ変換します。
48
74
  def calculate_ratios
49
75
  total = @tag_counts[:_total].to_f
50
76
  return if total.zero?
@@ -57,6 +83,8 @@ module CwCardUtils
57
83
 
58
84
  # === CMC UTILITY ===
59
85
 
86
+ # Internal: Average CMC across mainboard (lands excluded by caller logic).
87
+ # 日本語: メインボードの平均 CMC。
60
88
  def average_cmc
61
89
  @average_cmc ||= begin
62
90
  cmcs = deck.main.map { |card| card.respond_to?(:cmc) ? card.cmc.to_f : nil }.compact
@@ -66,6 +94,8 @@ module CwCardUtils
66
94
  end
67
95
 
68
96
  # === FORMAT-AWARE DETECTION ===
97
+ # Internal: Heuristic archetype classification.
98
+ # 日本語: アーキタイプのヒューリスティック分類。
69
99
  def detect_archetype
70
100
  if format == "edh"
71
101
  return :combo if tag_ratios[:combo_piece].to_f > 0.10 && tag_ratios[:draw].to_f > 0.08
@@ -105,10 +135,14 @@ module CwCardUtils
105
135
 
106
136
  # === COLOR IDENTITY ===
107
137
 
138
+ # Internal: Compute unique color identity from deck.
139
+ # 日本語: デッキから色アイデンティティを抽出します。
108
140
  def resolve_color_identity(deck)
109
141
  deck.main.flat_map { |c| c.respond_to?(:color_identity) ? c.color_identity : [] }.uniq.sort
110
142
  end
111
143
 
144
+ # Internal: Render color identity into label.
145
+ # 日本語: 色アイデンティティをラベルへ変換します。
112
146
  def resolve_color_label(identity)
113
147
  ColorIdentityResolver.resolve(identity)
114
148
  end
@@ -2,8 +2,16 @@
2
2
 
3
3
  module CwCardUtils
4
4
  module DecklistParser
5
- # A Card is a single card in a deck.
5
+ # Public: A single card entry in a deck.
6
+ # 日本語: デッキ内の1枚のカードを表します。
6
7
  class Card
8
+ # Public: Create a card entry.
9
+ # 日本語: カードエントリを作成します。
10
+ #
11
+ # @param name [String]
12
+ # @param count [Integer]
13
+ # @param cmc_data_source [CwCardUtils::CardDataSource, nil]
14
+ # @return [void]
7
15
  def initialize(name, count, cmc_data_source = nil)
8
16
  @name = name
9
17
  @count = count
@@ -11,45 +19,102 @@ module CwCardUtils
11
19
  @cmc_data_source = cmc_data_source || CwCardUtils.card_data_source
12
20
  end
13
21
 
14
- attr_reader :name, :count, :cmc_data_source
22
+ # Public: Card name.
23
+ # 日本語: カード名。
24
+ # @return [String]
25
+ attr_reader :name
26
+
27
+ # Public: Number of copies in the deck.
28
+ # 日本語: デッキ内の枚数。
29
+ # @return [Integer]
30
+ attr_reader :count
31
+
32
+ # Public: Data source used for lookup.
33
+ # 日本語: 参照に用いるデータソース。
34
+ # @return [CwCardUtils::CardDataSource, nil]
35
+ attr_reader :cmc_data_source
36
+
37
+ # Public: Tag symbols inferred for the card.
38
+ # 日本語: そのカードに付与されたタグ一覧。
39
+ # @return [Array<Symbol>]
15
40
  attr_accessor :tags
16
41
 
42
+ # Public: Converted mana cost from the data source.
43
+ # 日本語: データソースから取得した点数で見たマナ・コスト。
44
+ #
45
+ # @return [Numeric, nil]
17
46
  def cmc
18
47
  @cmc ||= @cmc_data_source&.cmc_for_card(@name)
19
48
  end
20
49
 
50
+ # Public: Type line from the data source.
51
+ # 日本語: データソースから取得したタイプ行。
52
+ #
53
+ # @return [String]
21
54
  def type
22
55
  @type ||= @cmc_data_source&.type_for_card(@name) || "Land"
23
56
  end
24
57
 
58
+ # Public: Keywords from the data source.
59
+ # 日本語: データソースから取得したキーワード。
60
+ #
61
+ # @return [Array<String>]
25
62
  def keywords
26
63
  @keywords ||= @cmc_data_source&.keywords_for_card(@name) || []
27
64
  end
28
65
 
66
+ # Public: Oracle text from the data source.
67
+ # 日本語: データソースから取得したオラクルテキスト。
68
+ #
69
+ # @return [String, nil]
29
70
  def oracle_text
30
71
  @oracle_text ||= @cmc_data_source&.oracle_text_for_card(@name)
31
72
  end
32
73
 
74
+ # Public: Power from the data source.
75
+ # 日本語: データソースから取得したパワー。
76
+ #
77
+ # @return [String, Integer, nil]
33
78
  def power
34
79
  @power ||= @cmc_data_source&.power_for_card(@name)
35
80
  end
36
81
 
82
+ # Public: Toughness from the data source.
83
+ # 日本語: データソースから取得したタフネス。
84
+ #
85
+ # @return [String, Integer, nil]
37
86
  def toughness
38
87
  @toughness ||= @cmc_data_source&.toughness_for_card(@name)
39
88
  end
40
89
 
90
+ # Public: Color identity array from the data source.
91
+ # 日本語: データソースから取得した色アイデンティティ配列。
92
+ #
93
+ # @return [Array<String>]
41
94
  def color_identity
42
95
  @color_identity ||= @cmc_data_source&.color_identity_for_card(@name) || []
43
96
  end
44
97
 
98
+ # Public: Human-readable summary.
99
+ # 日本語: 人間が読みやすい概要。
100
+ #
101
+ # @return [String]
45
102
  def inspect
46
103
  "<Card: #{@name} (#{@count}) #{cmc}>"
47
104
  end
48
105
 
106
+ # Public: Serialize to a Hash.
107
+ # 日本語: Hash へシリアライズします。
108
+ #
109
+ # @return [Hash]
49
110
  def to_h
50
111
  { name: @name, count: @count, cmc: cmc, type: type, keywords: keywords, power: power, toughness: toughness, oracle_text: oracle_text }
51
112
  end
52
113
 
114
+ # Public: JSON representation.
115
+ # 日本語: JSON 文字列を返します。
116
+ #
117
+ # @return [String]
53
118
  def to_json(*_args)
54
119
  to_h.to_json
55
120
  end
@@ -3,7 +3,15 @@
3
3
  module CwCardUtils
4
4
  module DecklistParser
5
5
 
6
+ # Public: Derives semantic tags from card text/type/keywords.
7
+ # 日本語: カードのテキスト/タイプ/キーワードから意味的なタグを導出します。
6
8
  class CardTagger
9
+ # Public: Initialize a tagger for a card within a deck context.
10
+ # 日本語: デッキ文脈でのカード用タグ付け器を初期化します。
11
+ #
12
+ # @param card [CwCardUtils::DecklistParser::Card]
13
+ # @param deck [CwCardUtils::DecklistParser::Deck]
14
+ # @return [void]
7
15
  def initialize(card, deck)
8
16
  @card = card
9
17
  @text = card.oracle_text.to_s.downcase
@@ -24,6 +32,22 @@ module CwCardUtils
24
32
  @tribe = deck.tribe&.to_s&.downcase
25
33
  end
26
34
 
35
+ # Public: Compute tags for a card in the context of a deck.
36
+ # 日本語: デッキ文脈でカードのタグを算出します。
37
+ #
38
+ # How it works (EN): Applies lightweight NLP-style regex heuristics
39
+ # over oracle text, type line, and keywords. Recognizes role tags
40
+ # such as :threat, :interaction, :ramp, and synergy/tribal patterns
41
+ # (e.g., token/ETB triggers, scaling effects, chosen type). Uses
42
+ # deck context (tribe) to infer tribal roles and finishers.
43
+ #
44
+ # 仕組み (JA): オラクルテキスト、タイプ行、キーワードへ簡易な
45
+ # 正規表現ベースのヒューリスティックを適用します。:threat、
46
+ # :interaction、:ramp などの役割タグや、トークン/ETB、スケール系、
47
+ # 選択タイプなどのシナジー・部族パターンを検出します。デッキの
48
+ # 文脈 (トライブ) を用いて部族ロールやフィニッシャーも推論します。
49
+ #
50
+ # @return [Array<Symbol>]
27
51
  def tags
28
52
  t = []
29
53
 
@@ -116,20 +140,28 @@ module CwCardUtils
116
140
 
117
141
  private
118
142
 
143
+ # Internal: Whether the card is a creature.
144
+ # 日本語: そのカードがクリーチャーかどうか。
119
145
  def creature?
120
146
  @type_line.include?("creature")
121
147
  end
122
148
 
149
+ # Internal: Whether the card is a planeswalker.
150
+ # 日本語: そのカードがプレインズウォーカーかどうか。
123
151
  def planeswalker?
124
152
  @type_line.include?("planeswalker")
125
153
  end
126
154
 
155
+ # Internal: Extract creature subtypes as array.
156
+ # 日本語: クリーチャーのサブタイプを配列として抽出します。
127
157
  def creature_subtypes
128
158
  return [] unless creature?
129
159
  raw = @type_line.split(/[—-]/).last.to_s.strip
130
160
  raw.split.map(&:downcase)
131
161
  end
132
162
 
163
+ # Internal: Heuristic for finisher classification.
164
+ # 日本語: フィニッシャー判定のヒューリスティック。
133
165
  def likely_finisher?
134
166
  return true if @cmc >= 6 && @text.match?(/flying|trample|indestructible|each opponent|you win|extra turn|double/i)
135
167
  return true if planeswalker? && @text.match?(/creatures.*get.*flying|you get an emblem|each opponent/i)
@@ -2,6 +2,8 @@
2
2
 
3
3
  module CwCardUtils
4
4
  module DecklistParser
5
+ # Public: Convert color identity arrays to human-readable labels.
6
+ # 日本語: 色アイデンティティの配列をわかりやすい名称へ変換します。
5
7
  class ColorIdentityResolver
6
8
  COLOR_NAMES = {
7
9
  %w[W] => "White",
@@ -42,6 +44,11 @@ module CwCardUtils
42
44
  [] => "Colorless",
43
45
  }.freeze
44
46
 
47
+ # Public: Resolve a color identity array to a label.
48
+ # 日本語: 色アイデンティティ配列をラベルへ変換します。
49
+ #
50
+ # @param identity [Array<String>] like ["U", "W"]
51
+ # @return [String]
45
52
  def self.resolve(identity)
46
53
  key = identity.sort
47
54
  COLOR_NAMES[key] || key.join("/")