Files
Tirsvad 375cc0b5f3 Buy one product per store visit and upgrades, visit the store every second
The game updates the price and owned text at once but the affordable
flag only at its next redraw, so a second purchase in the same visit
used a stale flag. Buying at most one product per visit cut the failed
purchases from about 40 percent to 1 of 49 and raised the CPS from 16.6
to 29 in the 300 second Docker run. README, screenshots and the wiki in
en, da and ms show the new result.

Task: MIL-006#1
Task: MIL-006#2
Task: MIL-006#3
Task: MIL-006#4
Task: MIL-006#5
2026-10-28 17:30:09 +08:00

82 lines
2.8 KiB
Python

"""@file
@brief Fakes of the ports for tests that must not start a browser or sleep.
"""
from collections.abc import Callable
class FakeGame:
"""@brief Records what the bot does and answers reads from dictionaries."""
def __init__(
self,
texts: dict[str, str] | None = None,
attributes: dict[tuple[str, str], str] | None = None,
on_click: Callable[[str], None] | None = None,
text_sequences: dict[str, list[str]] | None = None,
attribute_sequences: dict[tuple[str, str], list[str]] | None = None,
) -> None:
self.texts: dict[str, str] = texts or {}
self.attributes: dict[tuple[str, str], str] = attributes or {}
self.on_click = on_click
## Texts returned one per read, before the plain text applies.
self.text_sequences: dict[str, list[str]] = text_sequences or {}
## Attribute values returned one per read, before the plain value applies.
self.attribute_sequences: dict[tuple[str, str], list[str]] = (
attribute_sequences or {}
)
self.reads: dict[str, int] = {}
self.attribute_reads: dict[tuple[str, str], int] = {}
self.opened: list[str] = []
self.waited: list[str] = []
self.clicked: list[str] = []
self.close_calls = 0
def open_page(self, url: str) -> None:
self.opened.append(url)
def wait_for_element(self, element_id: str) -> None:
self.waited.append(element_id)
def has_element(self, element_id: str) -> bool:
"""An element exists when a text or an attribute is defined for it."""
return element_id in self.texts or any(
known == element_id for known, _ in self.attributes
)
def click_element(self, element_id: str) -> None:
self.clicked.append(element_id)
if self.on_click is not None:
self.on_click(element_id)
def read_text(self, element_id: str) -> str:
self.reads[element_id] = self.reads.get(element_id, 0) + 1
queued = self.text_sequences.get(element_id)
if queued:
return queued.pop(0)
return self.texts[element_id]
def read_attribute(self, element_id: str, attribute: str) -> str:
key = (element_id, attribute)
self.attribute_reads[key] = self.attribute_reads.get(key, 0) + 1
queued = self.attribute_sequences.get(key)
if queued:
return queued.pop(0)
return self.attributes.get(key, "")
def close(self) -> None:
self.close_calls += 1
class FakeClock:
"""@brief A clock that moves forward by a fixed step on every reading."""
def __init__(self, step: float = 1.0) -> None:
self.now = 0.0
self.step = step
def monotonic(self) -> float:
reading = self.now
self.now += self.step
return reading