class_name Currency extends RefCounted ## Money. Everything is stored in COPPER — one integer, on GameState.purse_copper. ## The denominations exist so a bounded §6 move can name a unit (give_item(gold) ## is +10,000c); they are NOT stored buckets, so change-making never arises. ## ## The ratios are rules and live here in code. PRICES are content and live in ## /content (roadmap: "the economy is two systems"). Formatting happens only at ## the display edge — this is the one place that knows what "12g 40s 8c" means. const COPPER := 1 const SILVER := 100 const GOLD := 10000 const VALUES := {"copper": COPPER, "silver": SILVER, "gold": GOLD} static func is_currency(item_id: String) -> bool: return VALUES.has(item_id) static func value(item_id: String) -> int: return int(VALUES.get(item_id, 0)) static func format(copper: int) -> String: # Empty denominations are suppressed and at least one unit always shows: # "0c", "47c", "1g 8c", "12g 40s". "0g 0s 8c" reads as a spreadsheet, not a # purse — and being broke should read as poverty (§3). if copper <= 0: return "0c" var parts: Array[String] = [] var left := copper @warning_ignore("integer_division") # denomination math — the remainder is carried, not lost var gold := left / GOLD if gold > 0: parts.append("%dg" % gold) left -= gold * GOLD @warning_ignore("integer_division") var silver := left / SILVER if silver > 0: parts.append("%ds" % silver) left -= silver * SILVER if left > 0: parts.append("%dc" % left) return " ".join(parts)