From 27f2934a62030eaf1bf6cc1f7f34802d3e1b86f3 Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Sun, 8 Jun 2025 20:34:11 -0500 Subject: add boss overview pages --- html/Makefile | 26 +++- html/boss/ai-companion.html | 184 +++++++++++++++++++++++++ html/boss/index.md | 10 ++ html/maps.md | 10 +- scripts/generate_boss_html.py | 303 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 526 insertions(+), 7 deletions(-) create mode 100644 html/boss/ai-companion.html create mode 100644 html/boss/index.md create mode 100755 scripts/generate_boss_html.py diff --git a/html/Makefile b/html/Makefile index 3519a80b..108a7fc6 100644 --- a/html/Makefile +++ b/html/Makefile @@ -45,8 +45,14 @@ EN_CARDS_LISTING := en/$(CARDS_LISTING) LANG_CARDS_LISTINGS := $(foreach card_listing, $(DE_CARDS_LISTING) $(EN_CARDS_LISTING), \ $(addprefix $(BUILDDIR)/, $(card_listing:.md=.html))) -SITES := index.md rules.md cards.md maps.md tools.html decks.md -HTML := $(addprefix $(BUILDDIR)/,$(SITES:.md=.html)) +GENERATE_BOSS := $(GAME_ROOT)/scripts/generate_boss_html.py + +BOSSES := kraken tyrant +BOSS_SITES := $(addprefix boss/,$(BOSSES:=.html)) +BOSS_SITES_HTML := $(addprefix $(BUILDDIR)/,$(BOSS_SITES)) + +SITES := index.md rules.md cards.md maps.md tools.html decks.md boss/ai-companion.html boss/index.html +HTML := $(addprefix $(BUILDDIR)/,$(SITES:.md=.html)) $(BOSS_SITES_HTML) BLOG_SOURCEDIR := $(HTML_ROOT)/blog BLOG_POSTS_MD := $(shell find $(BLOG_SOURCEDIR) -mindepth 1 -type f -name "*.md" -printf "%f\n") @@ -66,7 +72,7 @@ FILES_TO_COPY_TARGETS := $(addprefix $(BUILDDIR)/,$(FILES_TO_COPY)) all: $(HTML) $(RULES_HTML) maps misc $(BLOG_POSTS_HTML) misc: $(BUILDDIR)/latex-build $(BUILDDIR)/cards-data $(FILES_TO_COPY_TARGETS) \ - $(BUILDDIR)/webtools.wasm \ + $(BUILDDIR)/webtools.wasm $(BUILDDIR)/boss/ai-companion.wasm \ $(BUILDDIR)/feed.rss wasm_exec.js: $(shell go env GOROOT)/lib/wasm/wasm_exec.js @@ -138,9 +144,23 @@ $(eval $(call generateHtml, $(BUILDDIR),)) $(eval $(call generateRulesHtml, $(BUILDDIR)/rules, -N)) $(eval $(call generateHtml, $(BUILDDIR)/blog,)) +CARDS = $(notdir $(basename $(wildcard $(BUILDDIR)/cards-data/*/*.yml))) +GENCARDSDATALIST := echo $(CARDS) | python -c 'import sys; print("".join([f"" for card in sys.stdin.read().split()]))' +CARDSDATALIST := $(shell $(GENCARDSDATALIST)) +$(BUILDDIR)/boss/ai-companion.html: boss/ai-companion.html $(MAKEFILE_LIST) + @echo "building $@" + $(VERBOSE)if test \( ! \( -d $(@D) \) \); then mkdir -p $(@D); fi + $(VERBOSE) sed 's|%CARDS%|$(CARDSDATALIST)|' $< > $@ + FEEDSRC = feed GEN_FEED = $(FEEDSRC)/create_feed.py FEEDITEMS = $(wildcard $(FEEDSRC)/items/*) $(BUILDDIR)/feed.rss: $(FEEDITEMS) $(GEN_FEED) + @echo "building $$@" $(VERBOSE)python3 $(GEN_FEED) $(FEEDITEMS) > $@ + +$(BOSS_SITES_HTML): $(BUILDDIR)/boss/%.html: $(MAKEFILE_LIST) $(GENERATE_BOSS) $(GENERATE_HOVER_LINKS) + @echo "building $@" + @if test \( ! \( -d $(@D) \) \); then mkdir -p $(@D); fi + $(VERBOSE)$(GENERATE_BOSS) $(*F) $(DATA_ROOT) $(MAPS_ROOT) $(RULES_ROOT) > $@ diff --git a/html/boss/ai-companion.html b/html/boss/ai-companion.html new file mode 100644 index 00000000..65923543 --- /dev/null +++ b/html/boss/ai-companion.html @@ -0,0 +1,184 @@ + + + + + + AI Companion + + + + + + + + + + + + + + %CARDS% + + + + + + +

AI Companion

+ + +
+ + + + + +
+ + + + diff --git a/html/boss/index.md b/html/boss/index.md new file mode 100644 index 00000000..ec3c7776 --- /dev/null +++ b/html/boss/index.md @@ -0,0 +1,10 @@ +%Bosses + +For muhq's game there exist multiple [maps](../maps.html#coop) offering a cooperative game experience battling an AI opponent. + +## Available Bosses + +The following Bosses are available: + +* The Kraken - a simple AI opponent [[map](../maps.html#the-kraken)] [[overview](kraken.html)] [[ai-companion](ai-companion.html?mapInput=the-kraken)] +* The Tyrant - a sophisticated AI altering the map [[map](../maps.html#the-tyrant)] [[overview](tyrant.html)] [[ai-companion](ai-companion.html?mapInput=the-tyrant)] diff --git a/html/maps.md b/html/maps.md index 8eda748c..1a9b2c9d 100644 --- a/html/maps.md +++ b/html/maps.md @@ -4,10 +4,10 @@ Tile definitions can be found in the [rules](rules.html) ## Available Maps -### Coop +### Coop vs. an AI [*Boss*](boss/) -* The Kraken [[yaml](maps/the-kraken.yml)] [[png](maps/the-kraken.png)] -* The Tyrant [[yaml](maps/the-tyrant.yml)] [[png](maps/the-tyrant.png)] +* The Kraken [[yaml](maps/the-kraken.yml)] [[png](maps/the-kraken.png)] [[boss](boss/kraken.html)] +* The Tyrant [[yaml](maps/the-tyrant.yml)] [[png](maps/the-tyrant.png)] [[boss](boss/tyrant.html)] ### 2 Players @@ -22,4 +22,6 @@ Tile definitions can be found in the [rules](rules.html) * Islands [[yaml](maps/4P-islands.yml)] [[png](maps/4P-islands.png)] -"Dolle karte die leg ich mal da hin" +:::{lang=de} + "Dolle Karte, die leg ich mal da hin" +::: 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.
Since the kraken does not move and can not win it is the perfect boss to start.", + "tyrant": "" # TODO +} + +TEMPLATE = """ + + + + + ${boss_name} + + + + +
+
+

${boss_name}

+cards[pdf] +map +ai-companion +

${desc}

+
+ +
+

Win Condition

+

${win_condition}

+

Starting Deck

+

${start_deck}

+

Recommended Draft ?

+

${draft}

+

Instructions

+

${instructions}

+
+
+ + +""" + + +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'
  • {player.capitalize()}: {wc.capitalize()}
  • ' + return f'' + + +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
  • {cardFmt}
  • ' + return f'' + + +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() -- cgit v1.2.3