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
82 lines
2.8 KiB
Python
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
|