Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions client/src/viewmodel/__tests__/dominantColor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,20 @@ describe("getDominantManaColor", () => {
});

it("counts mana cost shards of non-land permanents", () => {
// The engine serializes shards as Rust variant names (e.g. "White", "Red"),
// not MTG symbols ("W", "R") — mirror the real payload here.
const objects: Record<string, GameObject> = {
"1": makeGameObject({
id: 1,
name: "Serra Angel",
card_types: { supertypes: [], core_types: ["Creature"], subtypes: ["Angel"] },
mana_cost: { type: "Cost", shards: ["W", "W"], generic: 3 },
mana_cost: { type: "Cost", shards: ["White", "White"], generic: 3 },
}),
"2": makeGameObject({
id: 2,
name: "Lightning Bolt",
card_types: { supertypes: [], core_types: ["Creature"], subtypes: [] },
mana_cost: { type: "Cost", shards: ["R"], generic: 0 },
mana_cost: { type: "Cost", shards: ["Red"], generic: 0 },
}),
};

Expand All @@ -118,21 +120,46 @@ describe("getDominantManaColor", () => {
expect(result).toBe("White");
});

it("counts both halves of a hybrid shard variant", () => {
// "WhiteBlue" (the {W/U} hybrid variant name) must contribute to both
// White and Blue — SHARD_ABBREVIATION maps it to "W/U", split on "/".
const objects: Record<string, GameObject> = {
"1": makeGameObject({
id: 1,
name: "Hybrid Creature",
card_types: { supertypes: [], core_types: ["Creature"], subtypes: [] },
mana_cost: { type: "Cost", shards: ["WhiteBlue"], generic: 0 },
}),
"2": makeGameObject({
id: 2,
name: "Blue Creature",
card_types: { supertypes: [], core_types: ["Creature"], subtypes: [] },
mana_cost: { type: "Cost", shards: ["Blue"], generic: 0 },
}),
};

// White=1 (from the hybrid), Blue=2 (hybrid + mono-blue) → Blue wins.
const result = getDominantManaColor([1, 2], objects, 0);

expect(result).toBe("Blue");
});

it("combines land subtypes and spell mana costs", () => {
const objects: Record<string, GameObject> = {
"1": makeGameObject({ id: 1, card_types: { supertypes: ["Basic"], core_types: ["Land"], subtypes: ["Island"] } }),
"2": makeGameObject({ id: 2, card_types: { supertypes: ["Basic"], core_types: ["Land"], subtypes: ["Island"] } }),
"3": makeGameObject({
id: 3,
"2": makeGameObject({
id: 2,
name: "Red Creature",
card_types: { supertypes: [], core_types: ["Creature"], subtypes: [] },
mana_cost: { type: "Cost", shards: ["R"], generic: 1 },
mana_cost: { type: "Cost", shards: ["Red", "Red"], generic: 1 },
}),
};

const result = getDominantManaColor([1, 2, 3], objects, 0);
// Island=Blue 1, creature=Red 2 → Red wins (would be Blue if spell shards
// were silently dropped, as with the pre-fix symbol-keyed lookup).
const result = getDominantManaColor([1, 2], objects, 0);

expect(result).toBe("Blue");
expect(result).toBe("Red");
});
});

Expand Down
18 changes: 13 additions & 5 deletions client/src/viewmodel/dominantColor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { GameObject, ManaColor, PlayerId } from "../adapter/types";
import { SHARD_ABBREVIATION } from "./costLabel";

const LAND_SUBTYPE_TO_COLOR: Record<string, ManaColor> = {
Plains: "White",
Expand All @@ -8,7 +9,10 @@ const LAND_SUBTYPE_TO_COLOR: Record<string, ManaColor> = {
Forest: "Green",
};

const SHARD_TO_COLOR: Record<string, ManaColor> = {
// Keyed by MTG symbol halves ("W", "U", …) — the components produced by
// splitting a shard's `SHARD_ABBREVIATION` on "/". Colorless/Snow/X/Phyrexian
// halves have no entry and are ignored, so only true color pips are counted.
const SYMBOL_TO_COLOR: Record<string, ManaColor> = {
W: "White",
U: "Blue",
B: "Black",
Expand All @@ -35,11 +39,15 @@ function countColors(
if (color) colorCounts.set(color, (colorCounts.get(color) ?? 0) + 1);
}
} else if (obj.mana_cost.type === "Cost") {
// Non-land permanents: count colored mana shards
// Non-land permanents: count colored mana shards. The engine serializes
// shards as Rust variant names ("White", "WhiteBlue", "PhyrexianRed"),
// not MTG symbols — SHARD_ABBREVIATION is the canonical bridge to symbols
// ("W", "W/U", "R/P"). Split hybrid/Phyrexian shards on "/" and count each
// colored half.
for (const shard of obj.mana_cost.shards) {
// Handle hybrid shards like "W/U" — count both halves
for (const part of shard.split("/")) {
const color = SHARD_TO_COLOR[part];
const abbreviation = SHARD_ABBREVIATION[shard] ?? shard;
for (const part of abbreviation.split("/")) {
const color = SYMBOL_TO_COLOR[part];
if (color) colorCounts.set(color, (colorCounts.get(color) ?? 0) + 1);
}
}
Expand Down
Loading