Point a dead copier at its fix: name the best target one reorder away
JCA.best_copy_target scans the board for copyable jokers (same truth test as copy_source: blueprint_compat must be true, debuffed excluded, other copiers excluded) and prefers the ones the database marks copy_target. When a Blueprint or Brainstorm reports anything but 'ok', the tooltip now adds the reorder that revives it: 'Fix: slot this just left of Baron' / 'Fix: make Baron your leftmost joker'. Explanation, not verdict, so learning mode keeps it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -421,6 +421,30 @@ local function tag_labels(tag_set)
|
|||||||
return labels
|
return labels
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- When a copier is copying nothing (or something it cannot use), the fix is a
|
||||||
|
-- reorder -- so name the best target actually on the board. Copyable means the
|
||||||
|
-- same thing it means in copy_source: blueprint_compat tested for TRUTH, not
|
||||||
|
-- debuffed, and not another copier (suggesting a copier would re-ask the same
|
||||||
|
-- question one slot over). Jokers the database marks copy_target are preferred;
|
||||||
|
-- the leftmost candidate wins a tie so the suggestion is stable across hovers.
|
||||||
|
-- Returns the card and whether it carried the copy_target tag.
|
||||||
|
function JCA.best_copy_target(copier)
|
||||||
|
if not (G.jokers and G.jokers.cards) then return nil end
|
||||||
|
local best, best_prime
|
||||||
|
for _, c in ipairs(G.jokers.cards) do
|
||||||
|
if c ~= copier and is_joker(c) and not c.debuff
|
||||||
|
and not COPIERS[c.config.center.key]
|
||||||
|
and c.config.center.blueprint_compat then
|
||||||
|
local entry = JCA.db[c.config.center.key]
|
||||||
|
local prime = not not (entry and entry.gives.copy_target)
|
||||||
|
if not best or (prime and not best_prime) then
|
||||||
|
best, best_prime = c, prime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return best, best_prime
|
||||||
|
end
|
||||||
|
|
||||||
-- Build gaps: what the current board is shopping for. A tag is a gap when
|
-- Build gaps: what the current board is shopping for. A tag is a gap when
|
||||||
-- some owned joker wants it and no OTHER owned joker gives it (same rule as
|
-- some owned joker wants it and no OTHER owned joker gives it (same rule as
|
||||||
-- scoring and theme discovery: a joker cannot feed itself). The reverse -- a
|
-- scoring and theme discovery: a joker cannot feed itself). The reverse -- a
|
||||||
@@ -574,6 +598,18 @@ local function tooltip_rows(card)
|
|||||||
and 'Copying nothing - it IS the leftmost joker'
|
and 'Copying nothing - it IS the leftmost joker'
|
||||||
or 'Copying nothing - no joker to its right', G.C.RED)
|
or 'Copying nothing - no joker to its right', G.C.RED)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- A dead copier usually has a live fix one reorder away. This is
|
||||||
|
-- an explanation of the mechanic, so learning mode keeps it.
|
||||||
|
if status ~= 'ok' then
|
||||||
|
local best = JCA.best_copy_target(card)
|
||||||
|
if best then
|
||||||
|
local bname = name_of(best.config.center.key)
|
||||||
|
text_row(key == 'j_brainstorm'
|
||||||
|
and ('Fix: make ' .. bname .. ' your leftmost joker')
|
||||||
|
or ('Fix: slot this just left of ' .. bname), G.C.GREEN)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Ceremonial Dagger. This one is about to eat a joker, permanently, at the
|
-- Ceremonial Dagger. This one is about to eat a joker, permanently, at the
|
||||||
|
|||||||
@@ -339,6 +339,30 @@ local ring = field('j_brainstorm', 'j_blueprint')
|
|||||||
for _, c in ipairs(ring) do c.config.center.blueprint_compat = true end
|
for _, c in ipairs(ring) do c.config.center.blueprint_compat = true end
|
||||||
eq(JCA.copy_source(ring[2]), nil, 'a Blueprint/Brainstorm ring resolves to nothing')
|
eq(JCA.copy_source(ring[2]), nil, 'a Blueprint/Brainstorm ring resolves to nothing')
|
||||||
|
|
||||||
|
-- A dead copier's fix is a reorder, so the advisor names the best target on
|
||||||
|
-- the board: copyable (blueprint_compat for TRUTH, not debuffed, not another
|
||||||
|
-- copier), preferring jokers the db marks as prime copy targets.
|
||||||
|
field('j_hiker', 'j_baron', 'j_blueprint')
|
||||||
|
for _, c in ipairs(G.jokers.cards) do c.config.center.blueprint_compat = true end
|
||||||
|
local best, prime = JCA.best_copy_target(G.jokers.cards[3])
|
||||||
|
eq(best, G.jokers.cards[2], 'the prime copy target (Baron) beats a plain joker')
|
||||||
|
eq(prime, true, 'and is reported as prime')
|
||||||
|
|
||||||
|
-- With no prime target, the leftmost copyable joker wins; copiers and
|
||||||
|
-- uncopyable jokers never get suggested.
|
||||||
|
field('j_blueprint', 'j_hiker', 'j_splash')
|
||||||
|
G.jokers.cards[2].config.center.blueprint_compat = true
|
||||||
|
best, prime = JCA.best_copy_target(G.jokers.cards[1])
|
||||||
|
eq(best, G.jokers.cards[2], 'falls back to the leftmost copyable joker')
|
||||||
|
eq(prime, false, 'which is not prime')
|
||||||
|
|
||||||
|
-- A debuffed joker is never the suggestion: it copies as nothing.
|
||||||
|
field('j_blueprint', 'j_baron')
|
||||||
|
G.jokers.cards[2].config.center.blueprint_compat = true
|
||||||
|
G.jokers.cards[2].debuff = true
|
||||||
|
eq(JCA.best_copy_target(G.jokers.cards[1]), nil,
|
||||||
|
'a debuffed joker is not suggested as a copy target')
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
section('Engine: the sell advisor respects what you can actually sell')
|
section('Engine: the sell advisor respects what you can actually sell')
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user