diff --git a/main.lua b/main.lua index d776fd0..5f3dfa3 100644 --- a/main.lua +++ b/main.lua @@ -421,6 +421,46 @@ local function tag_labels(tag_set) return labels end +-- 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 +-- scoring and theme discovery: a joker cannot feed itself). The reverse -- a +-- give no other owned joker wants -- is an untapped hook a future purchase +-- could pay off. Engine roles (mult/chips/xmult) are skipped for the same +-- reason they have no TAG_LABEL: they hint nothing specific. Returns two +-- tag arrays in TAG_ORDER, so the display is stable across hovers. +function JCA.build_gaps() + local looking, untapped = {}, {} + if not (G.jokers and G.jokers.cards) then return looking, untapped end + local entries = {} + for _, c in ipairs(G.jokers.cards) do + if is_joker(c) then + local e = JCA.db[c.config.center.key] + if e then entries[#entries + 1] = e end + end + end + -- Compare by index, not table identity: two copies of the same joker + -- share one db entry, and they DO feed each other's cluster tags. + local function others_have(side, tag, self_idx) + for i, e in ipairs(entries) do + if i ~= self_idx and e[side][tag] then return true end + end + end + local want_gap, give_hook = {}, {} + for i, e in ipairs(entries) do + for t in pairs(e.wants) do + if TAG_LABEL[t] and not others_have('gives', t, i) then want_gap[t] = true end + end + for t in pairs(e.gives) do + if TAG_LABEL[t] and not others_have('wants', t, i) then give_hook[t] = true end + end + end + for _, t in ipairs(data.tag_order) do + if want_gap[t] then looking[#looking + 1] = t end + if give_hook[t] then untapped[#untapped + 1] = t end + end + return looking, untapped +end + local function tooltip_rows(card) -- Touch mode enlarges every advice row for phone screens (see config.lua). local S = JCA.config.touch_mode and 1.4 or 1 @@ -1250,18 +1290,43 @@ end -- in an overlay. The overlay reuses catalog_tabs(); the page cyclers work -- unchanged because vanilla create_tabs also names its body 'tab_contents'. +-- Shopping list shown at the top of the in-run overlay: the tags the board +-- wants but nothing else on it gives, and the hooks it gives that nothing +-- pays off yet. Teaching content, so learning mode keeps it. +local function build_gaps_rows() + local looking, untapped = JCA.build_gaps() + local nodes = {} + local function line(prefix, tags, colour) + if #tags == 0 then return end + local labels = {} + for _, t in ipairs(tags) do labels[#labels + 1] = {name = TAG_LABEL[t]} end + for i, l in ipairs(wrap_names(labels, 58, 2)) do + nodes[#nodes + 1] = {n = G.UIT.R, config = {align = 'cm', padding = 0.02}, nodes = { + {n = G.UIT.T, config = {text = (i == 1 and prefix or ' ') .. l, + colour = colour, scale = 0.3, shadow = true}}, + }} + end + end + line('Your build is looking for: ', looking, G.C.GOLD) + line('Untapped on your board: ', untapped, G.C.UI.TEXT_LIGHT) + if #nodes == 0 then return nil end + return {n = G.UIT.R, config = {align = 'cm', padding = 0.04}, nodes = nodes} +end + G.FUNCS.jca_open_catalog = function() + local contents = {} + local ok, gaps = pcall(build_gaps_rows) + if ok and gaps then contents[#contents + 1] = gaps end + contents[#contents + 1] = {n = G.UIT.R, config = {align = 'tm', padding = 0}, nodes = { + create_tabs({ + tabs = catalog_tabs(), + snap_to_nav = true, + colour = G.C.BOOSTER, + }), + }} G.FUNCS.overlay_menu{definition = create_UIBox_generic_options({ back_func = 'exit_overlay_menu', - contents = { - {n = G.UIT.R, config = {align = 'tm', padding = 0}, nodes = { - create_tabs({ - tabs = catalog_tabs(), - snap_to_nav = true, - colour = G.C.BOOSTER, - }), - }}, - }, + contents = contents, })} end diff --git a/test.lua b/test.lua index 1bc0464..a0bddee 100644 --- a/test.lua +++ b/test.lua @@ -400,6 +400,35 @@ ok(cut == board[1] or cut == board[2], 'the sell advisor flags one of the two clashing jokers', cut and cut.config.center.key or 'nil') +-------------------------------------------------------------------------------- +section('Engine: build gaps name what the board is shopping for') +-------------------------------------------------------------------------------- + +-- Baron alone: wants held-card retriggers and hand size, and nothing on the +-- board provides either; its copy_target give has no copier to pay it off. +field('j_baron') +local looking, untapped = JCA.build_gaps() +eq(table.concat(looking, ','), 'retrig_held,hand_size', + 'a lone Baron is shopping for its enablers, in TAG_ORDER') +eq(table.concat(untapped, ','), 'copy_target', + 'its copy_target hook is untapped until a copier arrives') + +-- Mime fills the retrig_held gap (and only that gap). +field('j_baron', 'j_mime') +looking = JCA.build_gaps() +eq(table.concat(looking, ','), 'hand_size', 'Mime closes the retrig_held gap') + +-- Two copies of the same cluster joker feed each other: the entries share one +-- db table, so the check must compare board slots, not table identity. +field('j_jolly', 'j_jolly') +looking = JCA.build_gaps() +eq(table.concat(looking, ','), '', 'two Jolly Jokers are not shopping for Pairs') + +-- One alone is: a cluster member cannot feed itself. +field('j_jolly') +looking = JCA.build_gaps() +eq(table.concat(looking, ','), 'pair', 'a lone cluster member still wants its cluster') + -------------------------------------------------------------------------------- section('Data: themes are reachable') --------------------------------------------------------------------------------