Fires: everyone keeps their own pile
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a02a81dffd
commit
b4ecacf96c
4 changed files with 91 additions and 54 deletions
13
README.md
13
README.md
|
|
@ -124,11 +124,14 @@ Alpha. One season, bandit raids plus two friendly events, six buildings.
|
||||||
fire (cooking), the kiln (bricks) and the still (potions). Every craft at a
|
fire (cooking), the kiln (bricks) and the still (potions). Every craft at a
|
||||||
fire takes one log of its tier as fuel; a fire burning that tier's flame or
|
fire takes one log of its tier as fuel; a fire burning that tier's flame or
|
||||||
hotter pays it, and a hot forge takes a coal off every bar (never the last).
|
hotter pays it, and a hot forge takes a coal off every bar (never the last).
|
||||||
Anyone can tend any fire: each go feeds one log, for minutes of fire and XP
|
Anyone can tend any fire: each go feeds one log onto your own pile in it, for
|
||||||
that grow with level, and stops when the fire is full. Better logs burn
|
minutes of fire and XP that grow with level, until your pile reaches your
|
||||||
hotter colours (red, gold, white, blue, green, violet), burn first, and push
|
bank. The fire burns everyone's piles at once, sharing one minute a minute,
|
||||||
cheaper fuel out of a full fire. Whoever fed the fuel gets 10% of the XP of
|
so every log adds to how long it lasts and a full fire never shuts a
|
||||||
every craft it pays for. Because the fires are shared, the server runs
|
beginner out. Better logs burn hotter colours (red, gold, white, blue,
|
||||||
|
green, violet), burn first in their pile, and push your own cheaper fuel out
|
||||||
|
when your pile is full; the fire is as hot as its hottest pile. Whoever owns
|
||||||
|
that pile gets 10% of the XP of every craft the fire pays for. Because the fires are shared, the server runs
|
||||||
everyone's actions as time passes, not only when they next look.
|
everyone's actions as time passes, not only when they next look.
|
||||||
- **Events:** raids are announced a day or two ahead. Reconnaissance from
|
- **Events:** raids are announced a day or two ahead. Reconnaissance from
|
||||||
scouts, informants and the spymaster's guild narrows down when, how strong,
|
scouts, informants and the spymaster's guild narrows down when, how strong,
|
||||||
|
|
|
||||||
52
game.py
52
game.py
|
|
@ -2053,9 +2053,11 @@ def _use_charge(player: dict, skill: str, summary: dict) -> None:
|
||||||
|
|
||||||
|
|
||||||
# --- fires -----------------------------------------------------------------
|
# --- fires -----------------------------------------------------------------
|
||||||
# Each fire is a list of fuel: {tier, left (game seconds), by (who fed it)}. It burns on
|
# Each fire is a list of fuel: {tier, left (game seconds), by (who fed it)}. Everyone who
|
||||||
# game time, hottest fuel first and the oldest of that first, so the flame's colour is
|
# feeds a fire keeps their own pile in it, up to their own bank, so a full fire never
|
||||||
# the best fuel still in it.
|
# shuts a beginner out. The fire burns a game minute a minute, shared evenly between the
|
||||||
|
# piles, so every log adds to how long it lasts; each pile burns its hottest fuel first,
|
||||||
|
# and the fire is as hot as its hottest pile.
|
||||||
|
|
||||||
def new_fire(now: float) -> dict:
|
def new_fire(now: float) -> dict:
|
||||||
return {"fuel": [], "at": now, "saved": 0}
|
return {"fuel": [], "at": now, "saved": 0}
|
||||||
|
|
@ -2069,39 +2071,44 @@ def burn_fires(world: dict, now: float) -> None:
|
||||||
seconds = (now - fire["at"]) * speed(world)
|
seconds = (now - fire["at"]) * speed(world)
|
||||||
fire["at"] = now
|
fire["at"] = now
|
||||||
fuel = fire["fuel"]
|
fuel = fire["fuel"]
|
||||||
while seconds > 0 and fuel:
|
while seconds > 1e-9 and fuel:
|
||||||
chunk = burning(fire)
|
# Each pile burns its best fuel at 1/n of the pace, until the first of those runs out.
|
||||||
used = min(seconds, chunk["left"])
|
heads = [burning(fuel, by) for by in {chunk["by"] for chunk in fuel}]
|
||||||
chunk["left"] -= used
|
step = min(seconds, min(chunk["left"] for chunk in heads) * len(heads))
|
||||||
seconds -= used
|
for chunk in heads:
|
||||||
|
chunk["left"] -= step / len(heads)
|
||||||
if chunk["left"] <= 1e-6:
|
if chunk["left"] <= 1e-6:
|
||||||
fuel.remove(chunk)
|
fuel.remove(chunk)
|
||||||
|
seconds -= step
|
||||||
|
|
||||||
|
|
||||||
def burning(fire: dict) -> dict | None:
|
def burning(fuel: list, by: str | None = None) -> dict | None:
|
||||||
"""The fuel burning right now: the hottest, and the oldest of those."""
|
"""The fuel burning hottest (in one pile, with `by`): the hottest, and the oldest of those."""
|
||||||
return max(fire["fuel"], key=lambda chunk: chunk["tier"], default=None)
|
return max((chunk for chunk in fuel if by is None or chunk["by"] == by), key=lambda chunk: chunk["tier"], default=None)
|
||||||
|
|
||||||
|
|
||||||
def heat(world: dict, fire: str) -> int:
|
def heat(world: dict, fire: str) -> int:
|
||||||
"""The tier of a fire's flame, or -1 when it's out."""
|
"""The tier of a fire's flame, or -1 when it's out."""
|
||||||
chunk = burning(world["city"]["fires"][fire])
|
chunk = burning(world["city"]["fires"][fire]["fuel"])
|
||||||
return chunk["tier"] if chunk else -1
|
return chunk["tier"] if chunk else -1
|
||||||
|
|
||||||
|
|
||||||
def _room(fire: dict, tier: int, level: int) -> float:
|
def _room(fire: dict, player: dict, tier: int) -> float:
|
||||||
"""Game seconds of cheaper fuel a log of `tier` would push out, or -1 if it won't fit."""
|
"""Game seconds of the player's own cheaper fuel a log of `tier` would push out, or -1 if it won't fit."""
|
||||||
short = sum(chunk["left"] for chunk in fire["fuel"]) + fire_minutes(level) * 60 - fire_bank(level) * 60
|
level = skill_level(player, "firekeeping")
|
||||||
cheaper = sum(chunk["left"] for chunk in fire["fuel"] if chunk["tier"] < tier)
|
mine = [chunk for chunk in fire["fuel"] if chunk["by"] == player["id"]]
|
||||||
|
short = sum(chunk["left"] for chunk in mine) + fire_minutes(level) * 60 - fire_bank(level) * 60
|
||||||
|
cheaper = sum(chunk["left"] for chunk in mine if chunk["tier"] < tier)
|
||||||
return -1 if short > cheaper + 1e-6 else max(0.0, short)
|
return -1 if short > cheaper + 1e-6 else max(0.0, short)
|
||||||
|
|
||||||
|
|
||||||
def feed_fire(world: dict, player: dict, fire_id: str, tier: int) -> float:
|
def feed_fire(world: dict, player: dict, fire_id: str, tier: int) -> float:
|
||||||
"""Put one log in a fire, pushing out the cheapest fuel if it's full. Returns the minutes added."""
|
"""Put one log on your pile in a fire, pushing out your cheapest fuel if it's full. Returns the minutes added."""
|
||||||
fire, level = world["city"]["fires"][fire_id], skill_level(player, "firekeeping")
|
fire, level = world["city"]["fires"][fire_id], skill_level(player, "firekeeping")
|
||||||
short = _room(fire, tier, level)
|
short = _room(fire, player, tier)
|
||||||
while short > 1e-6:
|
while short > 1e-6:
|
||||||
cheapest = min((chunk for chunk in fire["fuel"] if chunk["tier"] < tier), key=lambda chunk: chunk["tier"])
|
cheapest = min((chunk for chunk in fire["fuel"] if chunk["by"] == player["id"] and chunk["tier"] < tier),
|
||||||
|
key=lambda chunk: chunk["tier"])
|
||||||
out = min(short, cheapest["left"])
|
out = min(short, cheapest["left"])
|
||||||
cheapest["left"] -= out
|
cheapest["left"] -= out
|
||||||
short -= out
|
short -= out
|
||||||
|
|
@ -2135,7 +2142,7 @@ def _warm(world: dict, crafter: dict, action: dict, xp: float, now: float) -> No
|
||||||
"""A fire paid for a craft: count it, and give its feeder a share of the XP."""
|
"""A fire paid for a craft: count it, and give its feeder a share of the XP."""
|
||||||
fire = world["city"]["fires"][action["fire"]]
|
fire = world["city"]["fires"][action["fire"]]
|
||||||
fire["saved"] = fire.get("saved", 0) + 1
|
fire["saved"] = fire.get("saved", 0) + 1
|
||||||
keeper = world["players"].get(burning(fire)["by"])
|
keeper = world["players"].get(burning(fire["fuel"])["by"])
|
||||||
if keeper and keeper is not crafter:
|
if keeper and keeper is not crafter:
|
||||||
gain_xp(world, keeper, "firekeeping", xp * WARMTH, now, keeper.setdefault("away", empty_summary()))
|
gain_xp(world, keeper, "firekeeping", xp * WARMTH, now, keeper.setdefault("away", empty_summary()))
|
||||||
|
|
||||||
|
|
@ -2184,9 +2191,8 @@ def can_start(world: dict, player: dict, action: dict) -> str | None:
|
||||||
return f"Out of {ITEMS[item]['name'].lower()}"
|
return f"Out of {ITEMS[item]['name'].lower()}"
|
||||||
elif player["bank"].get(item, 0) < qty:
|
elif player["bank"].get(item, 0) < qty:
|
||||||
return f"Out of {ITEMS[item]['name'].lower()}"
|
return f"Out of {ITEMS[item]['name'].lower()}"
|
||||||
if action.get("special") == "tend" and _room(world["city"]["fires"][action["tend"]], action["tier"],
|
if action.get("special") == "tend" and _room(world["city"]["fires"][action["tend"]], player, action["tier"]) < 0:
|
||||||
skill_level(player, "firekeeping")) < 0:
|
return f"Your pile in the {FIRES[action['tend']]['name'].lower()} is full"
|
||||||
return f"The {FIRES[action['tend']]['name'].lower()} is full"
|
|
||||||
if action.get("special") == "sabotage":
|
if action.get("special") == "sabotage":
|
||||||
raid = upcoming_raid(world)
|
raid = upcoming_raid(world)
|
||||||
if not raid or intel_level(world, raid) < 3:
|
if not raid or intel_level(world, raid) < 3:
|
||||||
|
|
|
||||||
|
|
@ -571,8 +571,9 @@ function skillGear(skill) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- fires ----------------------------------------------------------------
|
// --- fires ----------------------------------------------------------------
|
||||||
// The guild's shared fires. Between polls the client burns them down itself, hottest
|
// The guild's shared fires. Between polls the client burns them down itself, the same
|
||||||
// fuel first, the same way the server does.
|
// way the server does: the burn shared evenly between everyone's piles, each pile's
|
||||||
|
// hottest fuel first.
|
||||||
|
|
||||||
const ROMAN = ["I", "II", "III", "IV", "V", "VI"];
|
const ROMAN = ["I", "II", "III", "IV", "V", "VI"];
|
||||||
// Core to edge of each flame colour, for the animated fires.
|
// Core to edge of each flame colour, for the animated fires.
|
||||||
|
|
@ -590,16 +591,20 @@ const tendLevel = (tier) => C.actions.find((a) => a.id === `tend_forge_${C.firew
|
||||||
|
|
||||||
function fireNow(id) {
|
function fireNow(id) {
|
||||||
const f = S.city.fires[id], fuel = f.fuel.map((c) => ({ ...c }));
|
const f = S.city.fires[id], fuel = f.fuel.map((c) => ({ ...c }));
|
||||||
|
const hottest = (by) => fuel.filter((c) => c.by === by).reduce((best, c) => (c.tier > best.tier ? c : best));
|
||||||
let burn = Math.max(0, serverNow() - f.at) * f.speed;
|
let burn = Math.max(0, serverNow() - f.at) * f.speed;
|
||||||
while (burn > 0 && fuel.length) {
|
while (burn > 1e-9 && fuel.length) {
|
||||||
const top = fuel.reduce((best, c) => (c.tier > best.tier ? c : best));
|
const heads = [...new Set(fuel.map((c) => c.by))].map(hottest);
|
||||||
const used = Math.min(burn, top.left);
|
const step = Math.min(burn, Math.min(...heads.map((c) => c.left)) * heads.length);
|
||||||
top.left -= used;
|
for (const c of heads) {
|
||||||
burn -= used;
|
c.left -= step / heads.length;
|
||||||
if (top.left <= 1e-6) fuel.splice(fuel.indexOf(top), 1);
|
if (c.left <= 1e-6) fuel.splice(fuel.indexOf(c), 1);
|
||||||
|
}
|
||||||
|
burn -= step;
|
||||||
}
|
}
|
||||||
const heat = fuel.reduce((h, c) => Math.max(h, c.tier), -1), left = fuel.reduce((sum, c) => sum + c.left, 0);
|
const heat = fuel.reduce((h, c) => Math.max(h, c.tier), -1), left = fuel.reduce((sum, c) => sum + c.left, 0);
|
||||||
return { fuel, heat, left, low: heat >= 0 && left < C.fire_low, speed: f.speed };
|
const mine = fuel.filter((c) => c.by === S.uid);
|
||||||
|
return { fuel, mine, heat, left, low: heat >= 0 && left < C.fire_low, speed: f.speed };
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickFirewood() {
|
function pickFirewood() {
|
||||||
|
|
@ -628,6 +633,8 @@ function fireCard(id, pick) {
|
||||||
const start = `tend_${id}_${C.firewood[pick]}`, why = mine ? "" : me.blocked[start];
|
const start = `tend_${id}_${C.firewood[pick]}`, why = mine ? "" : me.blocked[start];
|
||||||
const crafting = f.users.filter((u) => !u.action.startsWith("tend_"));
|
const crafting = f.users.filter((u) => !u.action.startsWith("tend_"));
|
||||||
const doing = (u) => C.actions.find((a) => a.id === u.action);
|
const doing = (u) => C.actions.find((a) => a.id === u.action);
|
||||||
|
const piles = [...new Set(f.fuel.map((c) => c.by))];
|
||||||
|
const keepers = piles.length ? `<span title="${esc(piles.map(playerName).join("\n"))}">🔥 ${piles.length}</span>` : "";
|
||||||
const who = crafting.length
|
const who = crafting.length
|
||||||
? `<span title="${esc(crafting.map((u) => `${u.name}: ${doing(u).name}`).join("\n"))}">👤 ${crafting.length} ${crafting.map((u) => doing(u).icon).join("")}</span>`
|
? `<span title="${esc(crafting.map((u) => `${u.name}: ${doing(u).name}`).join("\n"))}">👤 ${crafting.length} ${crafting.map((u) => doing(u).icon).join("")}</span>`
|
||||||
: `<span class="muted">👤 0</span>`;
|
: `<span class="muted">👤 0</span>`;
|
||||||
|
|
@ -635,9 +642,10 @@ function fireCard(id, pick) {
|
||||||
<div class="fire-stage"><canvas data-flame="${id}"></canvas><span class="fire-state" data-fire-state="${id}"></span></div>
|
<div class="fire-stage"><canvas data-flame="${id}"></canvas><span class="fire-state" data-fire-state="${id}"></span></div>
|
||||||
<div class="fire-body">
|
<div class="fire-body">
|
||||||
<div class="fire-head">${glyph(spec.glyph, "#e7e9f0")}<b>${esc(spec.name)}</b><span class="muted small">${skill.icon} ${esc(skill.name)}</span></div>
|
<div class="fire-head">${glyph(spec.glyph, "#e7e9f0")}<b>${esc(spec.name)}</b><span class="muted small">${skill.icon} ${esc(skill.name)}</span></div>
|
||||||
<div class="fuel" data-fuel="${id}"></div>
|
<div class="fire-line"><span data-fire-left="${id}" title="Until it's out"></span><span data-fire-covers="${id}"></span></div>
|
||||||
<div class="fire-line"><span data-fire-left="${id}"></span><span data-fire-covers="${id}"></span></div>
|
<div class="fuel" data-fuel="${id}" title="Your pile"></div>
|
||||||
<div class="fire-line small">${who}<span title="Logs this fire has paid for">🪵 ${fmt(f.saved || 0)}</span></div>
|
<div class="fire-line small"><span data-fire-mine="${id}" title="Your pile / the most you can bank"></span><span title="Logs this fire has paid for">🪵 ${fmt(f.saved || 0)}</span></div>
|
||||||
|
<div class="fire-line small">${who}${keepers}</div>
|
||||||
${mine ? `<div class="bar thick"><span data-progress></span></div>` : ""}
|
${mine ? `<div class="bar thick"><span data-progress></span></div>` : ""}
|
||||||
<div class="foot"><span class="why">${esc(why || "")}</span>${mine
|
<div class="foot"><span class="why">${esc(why || "")}</span>${mine
|
||||||
? `<button class="btn danger" data-stop>Stop</button>`
|
? `<button class="btn danger" data-stop>Stop</button>`
|
||||||
|
|
@ -670,10 +678,11 @@ function animateFires() {
|
||||||
if (canvas) drawFlame(canvas, id, n);
|
if (canvas) drawFlame(canvas, id, n);
|
||||||
if (!text) continue;
|
if (!text) continue;
|
||||||
const set = (sel, html) => { for (const el of document.querySelectorAll(sel)) if (el.dataset.v !== html) { el.innerHTML = html; el.dataset.v = html; } };
|
const set = (sel, html) => { for (const el of document.querySelectorAll(sel)) if (el.dataset.v !== html) { el.innerHTML = html; el.dataset.v = html; } };
|
||||||
const bank = Math.max(S.me.firekeeping.bank * 60, n.left);
|
const bank = S.me.firekeeping.bank * 60, mine = n.mine.reduce((sum, c) => sum + c.left, 0);
|
||||||
const tiers = C.flames.map((_, t) => n.fuel.filter((c) => c.tier === t).reduce((sum, c) => sum + c.left, 0));
|
const tiers = C.flames.map((_, t) => n.mine.filter((c) => c.tier === t).reduce((sum, c) => sum + c.left, 0));
|
||||||
set(`[data-fuel="${id}"]`, tiers.map((left, t) => left ? `<i style="--c:${flameTint(t)};width:${(left / bank * 100).toFixed(2)}%"></i>` : "").join(""));
|
set(`[data-fuel="${id}"]`, tiers.map((left, t) => left ? `<i style="--c:${flameTint(t)};width:${(left / Math.max(bank, mine) * 100).toFixed(2)}%"></i>` : "").join(""));
|
||||||
set(`[data-fire-left="${id}"]`, `<b>${dur(n.left / n.speed)}</b> / ${dur(S.me.firekeeping.bank * 60 / n.speed)}`);
|
set(`[data-fire-left="${id}"]`, n.heat >= 0 ? `🔥 <b>${dur(n.left / n.speed)}</b>` : "");
|
||||||
|
set(`[data-fire-mine="${id}"]`, `You ${dur(mine / n.speed)} / ${dur(bank / n.speed)}`);
|
||||||
set(`[data-fire-time="${id}"]`, dur(n.left / n.speed));
|
set(`[data-fire-time="${id}"]`, dur(n.left / n.speed));
|
||||||
set(`[data-fire-covers="${id}"]`, n.heat >= 0 ? `<span class="tag flame" style="--c:${flameTint(n.heat)}" title="Pays the log for tier ${ROMAN[n.heat]} and below">🪵 ≤ ${ROMAN[n.heat]}</span>` : "");
|
set(`[data-fire-covers="${id}"]`, n.heat >= 0 ? `<span class="tag flame" style="--c:${flameTint(n.heat)}" title="Pays the log for tier ${ROMAN[n.heat]} and below">🪵 ≤ ${ROMAN[n.heat]}</span>` : "");
|
||||||
set(`[data-fire-state="${id}"]`, n.heat < 0 ? "Out" : n.low ? "Low" : C.flames[n.heat][0]);
|
set(`[data-fire-state="${id}"]`, n.heat < 0 ? "Out" : n.low ? "Low" : C.flames[n.heat][0]);
|
||||||
|
|
|
||||||
|
|
@ -1875,28 +1875,47 @@ class FirekeepingTest(unittest.TestCase):
|
||||||
def test_a_full_fire_stops_tending_and_pays_nothing(self):
|
def test_a_full_fire_stops_tending_and_pays_nothing(self):
|
||||||
self.kai["bank"] = {"logs": 100}
|
self.kai["bank"] = {"logs": 100}
|
||||||
summary = self.tend(self.kai, "forge", "logs", T0, 3000)
|
summary = self.tend(self.kai, "forge", "logs", T0, 3000)
|
||||||
self.assertEqual(summary["stopped"], "The forge is full")
|
self.assertEqual(summary["stopped"], "Your pile in the forge is full")
|
||||||
left = sum(chunk["left"] for chunk in self.fires["forge"]["fuel"])
|
left = sum(chunk["left"] for chunk in self.fires["forge"]["fuel"])
|
||||||
self.assertGreater(left + game.fire_minutes(1) * 60, game.fire_bank(1) * 60) # no room for another log
|
self.assertGreater(left + game.fire_minutes(1) * 60, game.fire_bank(1) * 60) # no room for another log
|
||||||
xp = self.kai["xp"]["firekeeping"]
|
xp = self.kai["xp"]["firekeeping"]
|
||||||
self.assertEqual(game.start_action(self.world, self.kai, "tend_forge_logs", T0 + 3000), "The forge is full")
|
self.assertEqual(game.start_action(self.world, self.kai, "tend_forge_logs", T0 + 3000), "Your pile in the forge is full")
|
||||||
self.assertEqual(self.kai["xp"]["firekeeping"], xp)
|
self.assertEqual(self.kai["xp"]["firekeeping"], xp)
|
||||||
|
|
||||||
def test_a_better_log_pushes_cheaper_fuel_out_but_not_the_reverse(self):
|
def test_a_better_log_pushes_your_cheaper_fuel_out_but_not_the_reverse(self):
|
||||||
bank = game.fire_bank(15) * 60
|
bank = game.fire_bank(15) * 60
|
||||||
self.fires["kiln"].update(fuel=[{"tier": 0, "left": bank, "by": "sam"}], at=T0) # full of plain logs
|
self.fires["kiln"].update(fuel=[{"tier": 0, "left": bank, "by": "kai"}], at=T0) # kai's pile: full of plain logs
|
||||||
self.kai["xp"]["firekeeping"] = game.XP_TABLE[15]
|
self.kai["xp"]["firekeeping"] = game.XP_TABLE[15]
|
||||||
self.kai["bank"] = {"oak_logs": 1, "logs": 1}
|
self.kai["bank"] = {"oak_logs": 1, "logs": 1}
|
||||||
self.assertEqual(game.start_action(self.world, self.kai, "tend_kiln_logs", T0), "The kiln is full")
|
self.assertEqual(game.start_action(self.world, self.kai, "tend_kiln_logs", T0), "Your pile in the kiln is full")
|
||||||
self.assertIsNone(game.start_action(self.world, self.kai, "tend_kiln_oak_logs", T0)) # a better log still fits
|
self.assertIsNone(game.start_action(self.world, self.kai, "tend_kiln_oak_logs", T0)) # a better log still fits
|
||||||
game.feed_fire(self.world, self.kai, "kiln", 1)
|
game.feed_fire(self.world, self.kai, "kiln", 1)
|
||||||
self.assertEqual(game.heat(self.world, "kiln"), 1)
|
self.assertEqual(game.heat(self.world, "kiln"), 1)
|
||||||
self.assertAlmostEqual(sum(chunk["left"] for chunk in self.fires["kiln"]["fuel"]), bank)
|
self.assertAlmostEqual(sum(chunk["left"] for chunk in self.fires["kiln"]["fuel"]), bank)
|
||||||
self.assertEqual(game.start_action(self.world, self.kai, "tend_kiln_logs", T0), "The kiln is full")
|
self.assertEqual(game.start_action(self.world, self.kai, "tend_kiln_logs", T0), "Your pile in the kiln is full")
|
||||||
|
|
||||||
def test_fuel_burns_hottest_first_on_game_time(self):
|
def test_a_beginner_can_always_feed_a_fire_a_veteran_filled(self):
|
||||||
|
self.fires["forge"].update(fuel=[{"tier": 5, "left": 100 * 3600, "by": "kai"}], at=T0)
|
||||||
|
self.sam["bank"] = {"logs": 5}
|
||||||
|
self.tend(self.sam, "forge", "logs", T0, 95)
|
||||||
|
self.assertEqual(self.sam["bank"]["logs"], 2)
|
||||||
|
self.assertGreater(self.sam["xp"]["firekeeping"], 0)
|
||||||
|
self.assertEqual(game.heat(self.world, "forge"), 5)
|
||||||
|
|
||||||
|
def test_piles_share_the_burn_and_the_time_adds_up(self):
|
||||||
|
fire = self.fires["forge"]
|
||||||
|
fire.update(fuel=[{"tier": 5, "left": 600, "by": "kai"}, {"tier": 0, "left": 600, "by": "sam"}], at=T0)
|
||||||
|
game.burn_fires(self.world, T0 + 600) # half each
|
||||||
|
self.assertEqual([round(chunk["left"]) for chunk in fire["fuel"]], [300, 300])
|
||||||
|
self.assertEqual(game.heat(self.world, "forge"), 5) # the beginner's logs keep the violet going longer
|
||||||
|
game.burn_fires(self.world, T0 + 1199)
|
||||||
|
self.assertEqual(game.heat(self.world, "forge"), 5)
|
||||||
|
game.burn_fires(self.world, T0 + 1200) # 20 minutes of fuel, 20 minutes of fire
|
||||||
|
self.assertEqual(fire["fuel"], [])
|
||||||
|
|
||||||
|
def test_a_pile_burns_its_hottest_fuel_first_on_game_time(self):
|
||||||
fire = self.fires["still"]
|
fire = self.fires["still"]
|
||||||
fire["fuel"] = [{"tier": 0, "left": 600, "by": "sam"}, {"tier": 2, "left": 300, "by": "kai"}]
|
fire["fuel"] = [{"tier": 0, "left": 600, "by": "kai"}, {"tier": 2, "left": 300, "by": "kai"}]
|
||||||
fire["at"] = T0
|
fire["at"] = T0
|
||||||
game.burn_fires(self.world, T0 + 200)
|
game.burn_fires(self.world, T0 + 200)
|
||||||
self.assertEqual(game.heat(self.world, "still"), 2)
|
self.assertEqual(game.heat(self.world, "still"), 2)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue