"""Stub BGG XML fixtures for the 27 titles extracted from the real shelf
photos, aligned with the objectids in the real collection snapshots.
Like write_stub_fixtures.py these are hand-written approximations (BGG's
API is locked until the registration is approved). They cover: the plain
search per title, empty results for the long Avalon Hill/SPI box titles
(what real search does with them), the truncated-head retry searches,
tie-break stats (Cosmic Encounter, Wiz-War, Sorcerer), and version lists
for every title with publisher/edition/year cues. Once BGG_API_TOKEN
exists: delete the cache dirs and re-run resolve/record_fixtures to
replace all of this with real recordings.
Usage: uv run python scripts/write_photo_fixtures.py
"""
from __future__ import annotations
from pathlib import Path
from bggpipe.bgg_client import cache_key
TARGETS = (Path("tests/fixtures/bgg_cache"), Path("data/bgg_cache"))
SEARCH_TYPES = "boardgame,boardgameexpansion"
BG, EXP = "boardgame", "boardgameexpansion"
# (bgg_id, name, year, type, name_type)
SEARCHES: dict[str, list[tuple]] = {
"ROYALS": [(165986, "Royals", 2014, BG, "primary")],
"CAT CRIMES": [(235096, "Cat Crimes", 2017, BG, "primary")],
"COSMIC ENCOUNTER": [
(39, "Cosmic Encounter", 1977, BG, "primary"),
(40529, "Cosmic Encounter", 2008, BG, "primary"),
],
"SHERIFF OF NOTTINGHAM": [
(157969, "Sheriff of Nottingham", 2014, BG, "primary"),
(289411, "Sheriff of Nottingham (2nd Edition)", 2020, BG, "primary"),
],
"5 MINUTE DUNGEON": [(207830, "5-Minute Dungeon", 2017, BG, "primary")],
"Britannia": [(240, "Britannia", 1986, BG, "primary")],
# BGG search finds nothing for the full transcribed box titles
"CIVILIZATION Game of the Heroic Age - The Dawn of History 8000 BC to 250 BC": [],
"CIVILIZATION Game of the Heroic Age": [],
"CIVILIZATION": [
(71, "Civilization", 1980, BG, "primary"),
(170416, "Sid Meier's Civilization: The Board Game", 2002, BG, "primary"),
(233247, "Civilization: A New Dawn", 2017, BG, "primary"),
],
"ADVANCED CIVILIZATION Game Expansion of the Heroic Age - "
"Featuring New Civilization, Commodity, and Calamity Cards": [],
"ADVANCED CIVILIZATION Game Expansion of the Heroic Age": [],
"ADVANCED CIVILIZATION": [(177, "Advanced Civilization", 1991, EXP, "primary")],
"DOCTOR WHO The Game of Time & Space": [
(3090, "Doctor Who: The Game of Time & Space", 1980, BG, "primary"),
(125675, "Doctor Who: The Card Game", 2012, BG, "primary"),
],
"SORCERER The Game of Magical Conflict": [],
"SORCERER": [
(3585, "Sorcerer", 1975, BG, "primary"),
(244115, "Sorcerer", 2019, BG, "primary"),
],
"STARFORCE ALPHA CENTAURI Interstellar Conflict in the 25th Century": [],
"STARFORCE ALPHA": [
(
2524,
"StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century",
1974,
BG,
"primary",
),
],
"SKIP-BO": [(1078, "Skip-Bo", 1967, BG, "primary")],
"a Gentle Rain": [(268163, "A Gentle Rain", 2019, BG, "primary")],
"PATCH WORK": [(163412, "Patchwork", 2014, BG, "primary")],
"SANTORINI": [
(194655, "Santorini", 2016, BG, "primary"),
(255563, "Santorini: New York", 2020, BG, "primary"),
],
"Utter Nonsense!": [(181254, "Utter Nonsense", 2015, BG, "primary")],
"Cards Against Humanity": [(50381, "Cards Against Humanity", 2009, BG, "primary")],
"Joking Hazard": [(193621, "Joking Hazard", 2016, BG, "primary")],
"EXPLODING KITTENS": [
(172225, "Exploding Kittens", 2015, BG, "primary"),
(172242, "Exploding Kittens: NSFW Edition", 2015, BG, "primary"),
],
"Herbaceous": [(195314, "Herbaceous", 2017, BG, "primary")],
"Wiz-War": [
(1218, "Wiz-War", 1983, BG, "primary"),
(104710, "Wiz-War", 2012, BG, "alternate"), # primary: Wiz-War (Eighth Edition)
],
"SCRAWL": [(218866, "Scrawl", 2017, BG, "primary")],
"FLAT TOP": [(2529, "Flat Top", 1977, BG, "primary")],
"Diplomacy": [(483, "Diplomacy", 1959, BG, "primary")],
"Age of Renaissance": [(26, "Age of Renaissance", 1996, BG, "primary")],
"History of the World": [(224, "History of the World", 1991, BG, "primary")],
"Dungeon!": [(1339, "Dungeon!", 1975, BG, "primary")],
}
# ids-param -> [(bgg_id, name, year, type, owned, rank, [publishers])]
THING_STATS: dict[str, list[tuple]] = {
"39,40529": [
(39, "Cosmic Encounter", 1977, BG, 7000, 800, ["Eon"]),
(40529, "Cosmic Encounter", 2008, BG, 75000, 150, ["Fantasy Flight Games"]),
],
"1218,104710": [
(1218, "Wiz-War", 1983, BG, 4000, 2500, ["Chessex"]),
(
104710,
"Wiz-War (Eighth Edition)",
2012,
BG,
8000,
1200,
["Fantasy Flight Games"],
),
],
"3585,244115": [
(
3585,
"Sorcerer",
1975,
BG,
900,
5000,
["Simulations Publications, Inc. (SPI)"],
),
(244115, "Sorcerer", 2019, BG, 6000, 1500, ["White Wizard Games"]),
],
}
# bgg_id -> [(version_id, name, year, [publishers], [languages])]
VERSIONS: dict[int, list[tuple]] = {
235096: [(360982, "English edition", 2017, ["ThinkFun"], ["English"])],
240: [
(
25668,
"Avalon Hill English edition",
1986,
["The Avalon Hill Game Co"],
["English"],
),
(90000, "Fantasy Flight edition", 2006, ["Fantasy Flight Games"], ["English"]),
(
90001,
"Welt der Spiele German edition",
1986,
["Welt der Spiele"],
["German"],
),
],
71: [
(
71001,
"Avalon Hill English edition",
1981,
["The Avalon Hill Game Co"],
["English"],
),
(
71002,
"Hartland Trefoil first edition",
1980,
["Hartland Trefoil Ltd."],
["English"],
),
(71003, "Gibsons Games edition", 1988, ["Gibsons Games"], ["English"]),
],
177: [
(
177001,
"Avalon Hill English edition",
1991,
["The Avalon Hill Game Co"],
["English"],
)
],
3090: [
(
309001,
"Games Workshop English edition",
1980,
["Games Workshop Ltd."],
["English"],
),
],
3585: [
(
358501,
"SPI Designer's Edition",
1975,
["Simulations Publications, Inc. (SPI)"],
["English"],
),
(
358502,
"SPI folio edition",
1975,
["Simulations Publications, Inc. (SPI)"],
["English"],
),
],
2524: [
(
252401,
"SPI Designer's Edition",
1974,
["Simulations Publications, Inc. (SPI)"],
["English"],
),
(
252402,
"SPI folio edition",
1974,
["Simulations Publications, Inc. (SPI)"],
["English"],
),
],
268163: [
(268164, "Bloom Edition", 2021, ["Mondo Games"], ["English"]),
(268165, "First edition", 2019, ["Mondo Games"], ["English"]),
],
193621: [(19362101, "First edition", 2016, ["Cyanide & Happiness"], ["English"])],
2529: [
(252901, "Avalon Hill edition", 1981, ["The Avalon Hill Game Co"], ["English"]),
(252902, "Battleline first edition", 1977, ["Battleline"], ["English"]),
],
483: [
(
48301,
"Avalon Hill 1976 edition",
1976,
["The Avalon Hill Game Co"],
["English"],
),
(
48302,
"Avalon Hill 1999 edition",
1999,
["The Avalon Hill Game Co"],
["English"],
),
(48303, "Gibsons Games edition", 1963, ["Gibsons Games"], ["English"]),
],
26: [
(
2601,
"Avalon Hill English edition",
1996,
["The Avalon Hill Game Co"],
["English"],
)
],
224: [
(
22401,
"Avalon Hill English edition",
1993,
["The Avalon Hill Game Co"],
["English"],
),
(
22402,
"Ragnar Brothers first edition",
1991,
["Ragnar Brothers"],
["English"],
),
],
1339: [
(133901, "TSR first edition", 1975, ["TSR"], ["English"]),
(133902, "TSR second edition", 1980, ["TSR"], ["English"]),
(133903, "TSR New Dungeon edition", 1989, ["TSR"], ["English"]),
],
}
def search_xml(results: list[tuple]) -> str:
items = "".join(
f'- '
f''
f'
'
for bgg_id, name, year, type_, name_type in results
)
return f'{items}'
def stats_xml(things: list[tuple]) -> str:
items = ""
for bgg_id, name, year, type_, owned, rank, publishers in things:
links = "".join(
f''
for p in publishers
)
items += (
f'- '
f''
f'{links}'
f""
f''
f''
f"
"
)
return f"{items}"
def versions_xml(bgg_id: int, versions: list[tuple]) -> str:
version_items = ""
for vid, name, year, publishers, languages in versions:
links = "".join(
f''
for p in publishers
) + "".join(
f'' for lang in languages
)
version_items += (
f'- '
f''
f'{links}
'
)
return (
f'- '
f''
f"{version_items}
"
)
def _esc(text: str) -> str:
return text.replace("&", "&").replace("<", "<").replace('"', """)
def main() -> None:
files: dict[str, str] = {}
for query, results in SEARCHES.items():
key = cache_key("search", {"query": query, "type": SEARCH_TYPES})
files[key] = search_xml(results)
for ids, things in THING_STATS.items():
files[cache_key("thing", {"id": ids, "stats": "1"})] = stats_xml(things)
for bgg_id, versions in VERSIONS.items():
key = cache_key("thing", {"id": str(bgg_id), "versions": "1"})
files[key] = versions_xml(bgg_id, versions)
for target in TARGETS:
target.mkdir(parents=True, exist_ok=True)
for name, xml in files.items():
(target / name).write_text(xml)
print(f"Wrote {len(files)} fixture file(s) to {' and '.join(map(str, TARGETS))}")
if __name__ == "__main__":
main()