diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-06-08 20:34:11 -0500 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-07-03 22:01:22 -0400 |
| commit | 27f2934a62030eaf1bf6cc1f7f34802d3e1b86f3 (patch) | |
| tree | 66ec0ade0d9aa4b63a697d20f3eee327759ebfdd /scripts | |
| parent | b9cb10d93c802280f4bc7f9cb42bb6b596edcf10 (diff) | |
| download | muhqs-game-27f2934a62030eaf1bf6cc1f7f34802d3e1b86f3.tar.gz muhqs-game-27f2934a62030eaf1bf6cc1f7f34802d3e1b86f3.zip | |
add boss overview pages
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/generate_boss_html.py | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/scripts/generate_boss_html.py b/scripts/generate_boss_html.py new file mode 100755 index 00000000..46ebd15f --- /dev/null +++ b/scripts/generate_boss_html.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 +"""Generate html page of an AI boss opponent""" + +# TODO: support multiple languages + +import argparse +from pathlib import Path +from string import Template +import re +import yaml + +from data import name2file +import generate_card_hover_links + +SETS = {"kraken": "kraken", "tyrant": "tyrant"} +NAMES = {"kraken": "The Kraken", "tyrant": "The Tyrant"} +DESCS = { + "kraken": + "Face the evil of the sea. Overcome the kraken, the fierce epicenter of the ozean, which employs creatures of the depth to stop your offense.<br>Since the kraken does not move and can not win it is the perfect boss to start.", + "tyrant": "" # TODO +} + +TEMPLATE = """<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="$lang" xml:lang="$lang"> +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> + <title>${boss_name}</title> + <style> +:root { + /* define applied colors */ + --bg: #fdfdfd; + --fg: #1a1a1a; + --link-color: #1a1a1a; + --pre-bg: #fdfdfd; +} +@media (prefers-color-scheme: dark) { + /* change apllied colors to the dark gruvbox pallet + * https://github.com/morhetz/gruvbox + */ + :root { + --bg: #282828; /* dark bg */ + --fg: #ebdbb2; /* dark fg */ + --link-color: #d79921; /* yellow */ + --pre-bg: #1d2021; /* dark bg0_h */ + } +} +html { + line-height: 1.5; + font-family: Georgia, serif; + font-size: 20px; + color: var(--fg); + background-color: var(--bg); +} +body { + margin: 0 auto; + max-width: 36em; + padding: 50px; + hyphens: auto; + word-wrap: break-word; + text-rendering: optimizeLegibility; + font-kerning: normal; +} +.main { + //display: flex; + //flex-flow: row wrap; + display: grid; + grid-template-columns: 3fr 2fr; + grid-template-rows: 200px auto + + max-width: 100%; + overflow:hidden; +} +.intro { + grid-column-start: 1; + grid-row-start: 1; +} +.card { + margin-top: 2em; + grid-column-start: 2; + grid-row-start: 1; + grid-row-end: 3; +} +.content { + grid-column-start: 1; + grid-row-start: 2; +} +@media (max-width: 600px) { + body { + font-size: 0.9em; + padding: 1em; + } + .main { + grid-template-columns: 1fr + } + .card { + margin-top: 0; + grid-column-start: 1; + grid-row-start: 2; + } + .content { + grid-row-start: 3; + } +} +@media print { + body { + background-color: transparent; + color: black; + font-size: 12pt; + } + p, h2, h3 { + orphans: 3; + widows: 3; + } + h2, h3 { + page-break-after: avoid; + } +} +.content { + overflow:hidden; +} +p { + margin: 1em 0; +} +a { + color: var(--link-color); +} +a:visited { + color: var(--link-color); +} +img { + max-width: 100%; +} +h1, h2, h3 { + margin-top: 1.4em; +} +ol, ul { + padding-left: 1.7em; + margin-top: 1em; +} +li > ol, li > ul { + margin-top: 0; +} +code { + font-family: Menlo, Monaco, 'Lucida Console', Consolas, monospace; + padding: .2em .4em; + font-size: 85%; + margin: 0; + white-space: pre-wrap; +} +pre { + margin: 0; + background-color: var(--pre-bg); + //padding: 1em; + display: block; + overflow: auto; +} +pre > code { + white-space: pre; +} +pre code { display: inline-block; } +header { + margin-bottom: 4em; + text-align: center; +} +#draft-notation-hint { + font-size: 20px; + margin-left: 5px; + padding: 1px; + border: 0.5px solid var(--link-color); + border-radius: 100%; +} + </style> + +</head> +<body> +<div id="main" class="main"> +<div class="intro"> +<h2 class="title">${boss_name}</h2> +<a href="../$lang/cards_listing.html#${set_name}">cards</a>[<a href="../latex-build/$lang/${set_name}.pdf">pdf</a>] +<a href="../maps/${map_name}.png">map</a> +<a href="ai-companion.html?mapInput=${map_name}">ai-companion</a> +<p id="desc">${desc}</p> +</div> +<img class="card" src="../latex-build/$lang/${boss_card}.png"/> +<div class="content"> +<h3 id="wincon">Win Condition</h3> +<p>${win_condition}</p> +<h3 id="start-deck">Starting Deck</h3> +<p>${start_deck}</p> +<h3 id="draft">Recommended Draft <a id="draft-notation-hint" href="../rules/$lang/rules.html#notation">?</a></h3> +<p><code>${draft}</code></p> +<h3 id="ai">Instructions</h3> +<p><pre><code>${instructions}</code></pre></p> +</div> +</div> <!-- main --> +</body> +</html> +""" + + +def name_to_map(name: str) -> str: + """Return the map's file name of a boss""" + return name.lower().replace(" ", "-") + + +def wincon_desc(map_def: dict) -> str: + """Format a map's win condition""" + wincon = map_def['win_condition'] + if isinstance(wincon, str): + return wincon.capitalize() + + s = "" + for player, wc in wincon.items(): + s += f'<li><b>{player.capitalize()}</b>: {wc.capitalize()}</li>' + return f'<ul>{s}</ul>' + + +def recommended_draft(map_def: dict) -> str: + """Format the map's recommended draft""" + # TODO: improve default draft + # TODO: format multiple recommendations + return map_def.get('draft', '3x[2;8]') + + +def ai_instruction(name: str, rules, lang: str) -> str: + """Extract the boss' instructions from the rules""" + rules_path = Path(rules) / lang / 'ai.md' + with open(rules_path, 'r', encoding='utf-8') as rf: + rules = rf.read() + + p = f'#.*{name}.*\n' + ai_start = re.findall(p, rules) + ai_start = ai_start[0] + + rules = rules[rules.find(ai_start) + len(ai_start):] + # rule taken from mdextractor + instructions = re.findall(r"```(?:\w+\s+)?(.*?)```", rules, re.DOTALL) + return instructions[0].strip() + + +def gen_startdeck_ul(map_def: dict, lang: str) -> str: + if 'start_deck_list' in map_def: + dl = map_def["start_deck_list"].splitlines() + else: + dl = ['3 misc/farmer'] + + s = "" + for card in dl: + cardFmt = "" + parts = card.split() + if len(parts) > 1: + cardFmt = f'{parts[0]} ' + c = parts[1] + else: + c = card + + def gen_link_target(c, lang): + return generate_card_hover_links.gen_link_to_cardlisting( + c, lang, path_prefix='../') + + hlink = generate_card_hover_links.gen_hoverable_link( + c, lambda _: c, gen_link_target, '../latex-build/', lang) + cardFmt += hlink + s += f'\n<li>{cardFmt}</li>' + return f'<ul id="start-decklist">{s}\n</ul>' + + +def main(): + # pylint: disable=W0641 + """Generate a boss page""" + + parser = argparse.ArgumentParser(description='generate a boss html page') + parser.add_argument('name', + help='the name of the boss', + choices=['kraken', 'tyrant']) + parser.add_argument('data', help='directory containing the card data') + parser.add_argument('maps', help='directoey containing the map data') + parser.add_argument('rules', help='directoey containing the rules') + + args = parser.parse_args() + + lang = 'en' + boss_name = NAMES[args.name] + desc = DESCS[args.name] + set_name = SETS[args.name] + boss_card = f'{set_name}/{name2file(boss_name)}' + + map_name = name_to_map(boss_name) + map_path = Path(args.maps) / f'{map_name}.yml' + with open(map_path, 'r', encoding='utf-8') as map_file: + map_def = yaml.safe_load(map_file) + + win_condition = wincon_desc(map_def) + start_deck = gen_startdeck_ul(map_def, lang) + draft = recommended_draft(map_def) + instructions = ai_instruction(boss_name, args.rules, lang) + + print(Template(TEMPLATE).substitute(locals())) + + +if __name__ == '__main__': + main() |
