Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 82e22eab25 |
6 changed files with 309 additions and 27 deletions
10
README.md
10
README.md
|
|
@ -32,8 +32,10 @@ is saved after each edit or result, so you can stop and resume later.
|
||||||
|
|
||||||
The main menu also has a fully randomized one-off show (choose a game first) and
|
The main menu also has a fully randomized one-off show (choose a game first) and
|
||||||
Season Mode. Seasons save separately under `seasons/` and book Raw → Thursday
|
Season Mode. Seasons save separately under `seasons/` and book Raw → Thursday
|
||||||
Night SmackDown! → WWF PPV repeatedly. New season champions can be selected, randomized, or left
|
Night SmackDown! → WWF PPV repeatedly. New season champions can be selected,
|
||||||
vacant. Semi-random cards use division rankings and repeat-match rivalries;
|
randomized, or left vacant. Built-in seasons ask which wrestlers are eligible
|
||||||
|
for the Light-Heavyweight division; that selection is saved with the season.
|
||||||
|
Semi-random cards use division rankings and repeat-match rivalries;
|
||||||
title defenses are possible on any show and are weighted more heavily on PPVs.
|
title defenses are possible on any show and are weighted more heavily on PPVs.
|
||||||
PPV cards may feature up to two title defenses, while weekly shows may feature
|
PPV cards may feature up to two title defenses, while weekly shows may feature
|
||||||
one. Mark each season match with a ranking division, and separately mark title
|
one. Mark each season match with a ranking division, and separately mark title
|
||||||
|
|
@ -69,8 +71,8 @@ federation, not just the built-in WWF games:
|
||||||
- **Create a new game and federation** — name the game, pick the federation and
|
- **Create a new game and federation** — name the game, pick the federation and
|
||||||
platform (PlayStation 1, PlayStation 2, or other), name the federation's
|
platform (PlayStation 1, PlayStation 2, or other), name the federation's
|
||||||
three shows, list the roster, edit the suggested match types, and add any
|
three shows, list the roster, edit the suggested match types, and add any
|
||||||
championship/ranking divisions (singles, tag team, or women's). A women's
|
championship/ranking divisions (singles, tag team, women's, or
|
||||||
division asks which roster members are eligible.
|
Light-Heavyweight). Restricted divisions let you choose eligible wrestlers.
|
||||||
- **Copy a built-in WWF game to edit** — clones any built-in game into your own
|
- **Copy a built-in WWF game to edit** — clones any built-in game into your own
|
||||||
editable profile; the built-in games themselves stay unchanged.
|
editable profile; the built-in games themselves stay unchanged.
|
||||||
- **Edit a custom game** — reopen any saved profile.
|
- **Edit a custom game** — reopen any saved profile.
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,10 @@ def edit_custom_roster(config: dict[str, Any]) -> None:
|
||||||
removed = config["roster"].pop(index)
|
removed = config["roster"].pop(index)
|
||||||
config["women_roster"] = [name for name in config["women_roster"]
|
config["women_roster"] = [name for name in config["women_roster"]
|
||||||
if name.casefold() != removed.casefold()]
|
if name.casefold() != removed.casefold()]
|
||||||
|
config["light_heavyweight_roster"] = [
|
||||||
|
name for name in config.get("light_heavyweight_roster", [])
|
||||||
|
if name.casefold() != removed.casefold()
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def edit_custom_match_types(config: dict[str, Any]) -> None:
|
def edit_custom_match_types(config: dict[str, Any]) -> None:
|
||||||
|
|
@ -117,19 +121,24 @@ def edit_custom_titles(config: dict[str, Any]) -> None:
|
||||||
if not name or any(item["name"].casefold() == name.casefold() for item in existing):
|
if not name or any(item["name"].casefold() == name.casefold() for item in existing):
|
||||||
print("Enter a non-empty championship name not already listed.")
|
print("Enter a non-empty championship name not already listed.")
|
||||||
continue
|
continue
|
||||||
kind_options = ["singles", "tag", "women"]
|
kind_options = ["singles", "tag", "women", "light_heavyweight"]
|
||||||
kind_labels = ["Singles / open", "Tag team", "Women's division"]
|
kind_labels = ["Singles / open", "Tag team", "Women's division",
|
||||||
|
"Light-Heavyweight division"]
|
||||||
kind = kind_options[app.ask_choice("Championship type", kind_options, labels=kind_labels)]
|
kind = kind_options[app.ask_choice("Championship type", kind_options, labels=kind_labels)]
|
||||||
existing.append({"name": name, "kind": kind})
|
existing.append({"name": name, "kind": kind})
|
||||||
|
|
||||||
|
|
||||||
def edit_women_roster(config: dict[str, Any]) -> None:
|
def edit_eligible_roster(config: dict[str, Any], field: str, division: str,
|
||||||
|
require_eligible: bool = False) -> None:
|
||||||
|
eligible = config.setdefault(field, [])
|
||||||
while True:
|
while True:
|
||||||
choices = ["add", "remove", "done"]
|
choices = ["add", "remove", "done"]
|
||||||
labels = ["Add eligible wrestler", "Remove eligible wrestler", "Done"]
|
labels = ["Add eligible wrestler", "Remove eligible wrestler", "Done"]
|
||||||
action = app.ask_choice("Women's division eligibility", choices, labels=labels)
|
action = app.ask_choice(f"{division} eligibility", choices, labels=labels)
|
||||||
eligible = config["women_roster"]
|
|
||||||
if action == 2:
|
if action == 2:
|
||||||
|
if require_eligible and not eligible:
|
||||||
|
print(f"Choose at least one eligible wrestler for the {division} division.")
|
||||||
|
continue
|
||||||
return
|
return
|
||||||
if action == 0:
|
if action == 0:
|
||||||
available = [name for name in config["roster"]
|
available = [name for name in config["roster"]
|
||||||
|
|
@ -146,6 +155,16 @@ def edit_women_roster(config: dict[str, Any]) -> None:
|
||||||
eligible.pop(remove_index)
|
eligible.pop(remove_index)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_women_roster(config: dict[str, Any]) -> None:
|
||||||
|
edit_eligible_roster(config, "women_roster", "Women's division")
|
||||||
|
|
||||||
|
|
||||||
|
def edit_light_heavyweight_roster(config: dict[str, Any]) -> None:
|
||||||
|
edit_eligible_roster(
|
||||||
|
config, "light_heavyweight_roster", "Light-Heavyweight", require_eligible=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def edit_custom_shows(config: dict[str, Any]) -> None:
|
def edit_custom_shows(config: dict[str, Any]) -> None:
|
||||||
while True:
|
while True:
|
||||||
shows = config["shows"]
|
shows = config["shows"]
|
||||||
|
|
@ -169,13 +188,24 @@ def edit_custom_shows(config: dict[str, Any]) -> None:
|
||||||
|
|
||||||
def edit_custom_profile(config: dict[str, Any]) -> dict[str, Any]:
|
def edit_custom_profile(config: dict[str, Any]) -> dict[str, Any]:
|
||||||
while True:
|
while True:
|
||||||
options = ["identity", "shows", "roster", "match_types", "titles", "women", "save"]
|
options = ["identity", "shows", "roster", "match_types", "titles", "women"]
|
||||||
labels = ["Game/federation, platform, screenshot directory",
|
labels = ["Game/federation, platform, screenshot directory",
|
||||||
f"Show names (Raw: {config['shows']['Raw']})",
|
f"Show names (Raw: {config['shows']['Raw']})",
|
||||||
f"Edit roster ({len(config['roster'])} wrestlers)",
|
f"Edit roster ({len(config['roster'])} wrestlers)",
|
||||||
f"Edit match types ({len(config['match_types'])})",
|
f"Edit match types ({len(config['match_types'])})",
|
||||||
f"Edit championships ({len(config['championships'])})",
|
f"Edit championships ({len(config['championships'])})",
|
||||||
f"Edit Women's eligibility ({len(config['women_roster'])})", "Validate and save"]
|
f"Edit Women's eligibility ({len(config['women_roster'])})"]
|
||||||
|
has_light_heavyweight = any(
|
||||||
|
title.get("kind") == "light_heavyweight" for title in config["championships"]
|
||||||
|
)
|
||||||
|
if has_light_heavyweight:
|
||||||
|
options.append("light_heavyweight")
|
||||||
|
labels.append(
|
||||||
|
f"Edit Light-Heavyweight eligibility "
|
||||||
|
f"({len(config.get('light_heavyweight_roster', []))})"
|
||||||
|
)
|
||||||
|
options.append("save")
|
||||||
|
labels.append("Validate and save")
|
||||||
action = app.ask_choice("BYOG editor", options, labels=labels)
|
action = app.ask_choice("BYOG editor", options, labels=labels)
|
||||||
if action == 0:
|
if action == 0:
|
||||||
config["game_title"] = app.ask("Game title", config["game_title"]).strip()
|
config["game_title"] = app.ask("Game title", config["game_title"]).strip()
|
||||||
|
|
@ -201,7 +231,12 @@ def edit_custom_profile(config: dict[str, Any]) -> dict[str, Any]:
|
||||||
app.edit_custom_titles(config)
|
app.edit_custom_titles(config)
|
||||||
elif action == 5:
|
elif action == 5:
|
||||||
app.edit_women_roster(config)
|
app.edit_women_roster(config)
|
||||||
|
elif has_light_heavyweight and action == 6:
|
||||||
|
app.edit_light_heavyweight_roster(config)
|
||||||
else:
|
else:
|
||||||
|
if has_light_heavyweight and not config.get("light_heavyweight_roster"):
|
||||||
|
print("Choose eligible Light-Heavyweight wrestlers before saving this profile.")
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
return app.save_custom_game(config)
|
return app.save_custom_game(config)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
|
|
@ -225,6 +260,7 @@ def builtin_profile_for_copy(game_id: str, custom_id: str) -> dict[str, Any]:
|
||||||
"match_types": definitions,
|
"match_types": definitions,
|
||||||
"championships": app.title_definitions_for_game(game_id),
|
"championships": app.title_definitions_for_game(game_id),
|
||||||
"women_roster": sorted(app.WOMEN_ROSTERS.get(game_id, set())),
|
"women_roster": sorted(app.WOMEN_ROSTERS.get(game_id, set())),
|
||||||
|
"light_heavyweight_roster": app.load_roster(game_id),
|
||||||
"shows": {"Raw": "Monday Night Raw", "SmackDown": "Thursday Night SmackDown!",
|
"shows": {"Raw": "Monday Night Raw", "SmackDown": "Thursday Night SmackDown!",
|
||||||
"PPV": "WWF PPV"},
|
"PPV": "WWF PPV"},
|
||||||
})
|
})
|
||||||
|
|
@ -261,6 +297,7 @@ def create_custom_game() -> dict[str, Any]:
|
||||||
"match_types": [],
|
"match_types": [],
|
||||||
"championships": [],
|
"championships": [],
|
||||||
"women_roster": [],
|
"women_roster": [],
|
||||||
|
"light_heavyweight_roster": [],
|
||||||
"shows": shows,
|
"shows": shows,
|
||||||
}
|
}
|
||||||
app.edit_custom_roster(config)
|
app.edit_custom_roster(config)
|
||||||
|
|
@ -273,6 +310,8 @@ def create_custom_game() -> dict[str, Any]:
|
||||||
app.edit_custom_titles(config)
|
app.edit_custom_titles(config)
|
||||||
if any(title["kind"] == "women" for title in config["championships"]):
|
if any(title["kind"] == "women" for title in config["championships"]):
|
||||||
app.edit_women_roster(config)
|
app.edit_women_roster(config)
|
||||||
|
if any(title["kind"] == "light_heavyweight" for title in config["championships"]):
|
||||||
|
app.edit_light_heavyweight_roster(config)
|
||||||
return app.edit_custom_profile(config)
|
return app.edit_custom_profile(config)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -290,6 +329,9 @@ def clone_builtin_game() -> dict[str, Any]:
|
||||||
config = app.builtin_profile_for_copy(source_id, app.new_custom_game_id(federation, title))
|
config = app.builtin_profile_for_copy(source_id, app.new_custom_game_id(federation, title))
|
||||||
config["game_title"] = title or config["game_title"]
|
config["game_title"] = title or config["game_title"]
|
||||||
config["federation_name"] = federation or config["federation_name"]
|
config["federation_name"] = federation or config["federation_name"]
|
||||||
|
if any(item["kind"] == "light_heavyweight" for item in config["championships"]):
|
||||||
|
config["light_heavyweight_roster"] = []
|
||||||
|
app.edit_light_heavyweight_roster(config)
|
||||||
print(f"Copied {app.game_title(source_id)} into the editable BYOG directory.")
|
print(f"Copied {app.game_title(source_id)} into the editable BYOG directory.")
|
||||||
return app.edit_custom_profile(config)
|
return app.edit_custom_profile(config)
|
||||||
|
|
||||||
|
|
@ -326,4 +368,4 @@ def manage_games() -> None:
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
__all__ = ['builtin_profile_for_copy', 'choose_builtin_game', 'clone_builtin_game', 'create_custom_game', 'edit_custom_match_types', 'edit_custom_profile', 'edit_custom_roster', 'edit_custom_shows', 'edit_custom_titles', 'edit_existing_custom_game', 'edit_women_roster', 'manage_games', 'show_roster']
|
__all__ = ['builtin_profile_for_copy', 'choose_builtin_game', 'clone_builtin_game', 'create_custom_game', 'edit_custom_match_types', 'edit_custom_profile', 'edit_custom_roster', 'edit_custom_shows', 'edit_custom_titles', 'edit_eligible_roster', 'edit_existing_custom_game', 'edit_light_heavyweight_roster', 'edit_women_roster', 'manage_games', 'show_roster']
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,15 @@ def title_definitions_for_game(game_id: str,
|
||||||
if game_id in app.CUSTOM_GAME_CONFIGS:
|
if game_id in app.CUSTOM_GAME_CONFIGS:
|
||||||
return [dict(title) for title in app.CUSTOM_GAME_CONFIGS[game_id]["championships"]]
|
return [dict(title) for title in app.CUSTOM_GAME_CONFIGS[game_id]["championships"]]
|
||||||
return [
|
return [
|
||||||
{"name": name, "kind": "tag" if name == "Tag Team" else "women" if name == "Women's" else "singles"}
|
{
|
||||||
|
"name": name,
|
||||||
|
"kind": (
|
||||||
|
"tag" if name == "Tag Team"
|
||||||
|
else "women" if name == "Women's"
|
||||||
|
else "light_heavyweight" if name == "Light-Heavyweight"
|
||||||
|
else "singles"
|
||||||
|
),
|
||||||
|
}
|
||||||
for name in app.CHAMPIONSHIPS
|
for name in app.CHAMPIONSHIPS
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -243,16 +251,27 @@ def validate_custom_game(data: Any) -> dict[str, Any]:
|
||||||
for item in raw_titles:
|
for item in raw_titles:
|
||||||
if not isinstance(item, dict):
|
if not isinstance(item, dict):
|
||||||
raise ValueError("Each championship must be a JSON object with name and kind.")
|
raise ValueError("Each championship must be a JSON object with name and kind.")
|
||||||
name, kind = item.get("name"), str(item.get("kind", "singles")).casefold()
|
name = item.get("name")
|
||||||
if not isinstance(name, str) or not name.strip() or name.casefold() in seen_titles:
|
if not isinstance(name, str) or not name.strip() or name.casefold() in seen_titles:
|
||||||
raise ValueError("Championship names must be non-empty and unique.")
|
raise ValueError("Championship names must be non-empty and unique.")
|
||||||
if kind not in {"singles", "tag", "women"}:
|
default_kind = "light_heavyweight" if name.casefold() == "light-heavyweight" else "singles"
|
||||||
raise ValueError(f"Championship '{name}' kind must be singles, tag, or women.")
|
kind = str(item.get("kind", default_kind)).casefold()
|
||||||
|
if kind not in {"singles", "tag", "women", "light_heavyweight"}:
|
||||||
|
raise ValueError(
|
||||||
|
f"Championship '{name}' kind must be singles, tag, women, or light_heavyweight."
|
||||||
|
)
|
||||||
championships.append({"name": name.strip(), "kind": kind})
|
championships.append({"name": name.strip(), "kind": kind})
|
||||||
seen_titles.add(name.casefold())
|
seen_titles.add(name.casefold())
|
||||||
women = data.get("women_roster", [])
|
women = data.get("women_roster", [])
|
||||||
if not isinstance(women, list) or any(name not in roster for name in women):
|
if not isinstance(women, list) or any(name not in roster for name in women):
|
||||||
raise ValueError("women_roster must be a subset of the roster.")
|
raise ValueError("women_roster must be a subset of the roster.")
|
||||||
|
light_heavyweight = data.get("light_heavyweight_roster", [])
|
||||||
|
if (not isinstance(light_heavyweight, list)
|
||||||
|
or any(name not in roster for name in light_heavyweight)):
|
||||||
|
raise ValueError("light_heavyweight_roster must be a subset of the roster.")
|
||||||
|
if (any(title["kind"] == "light_heavyweight" for title in championships)
|
||||||
|
and not light_heavyweight):
|
||||||
|
raise ValueError("light_heavyweight_roster must contain eligible wrestlers.")
|
||||||
raw_shows = data.get("shows")
|
raw_shows = data.get("shows")
|
||||||
if raw_shows is not None and not isinstance(raw_shows, dict):
|
if raw_shows is not None and not isinstance(raw_shows, dict):
|
||||||
raise ValueError("shows must be an object with Raw, SmackDown, and PPV names.")
|
raise ValueError("shows must be an object with Raw, SmackDown, and PPV names.")
|
||||||
|
|
@ -278,6 +297,7 @@ def validate_custom_game(data: Any) -> dict[str, Any]:
|
||||||
"match_types": match_types,
|
"match_types": match_types,
|
||||||
"championships": championships,
|
"championships": championships,
|
||||||
"women_roster": list(dict.fromkeys(women)),
|
"women_roster": list(dict.fromkeys(women)),
|
||||||
|
"light_heavyweight_roster": list(dict.fromkeys(light_heavyweight)),
|
||||||
"shows": shows,
|
"shows": shows,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,6 +326,7 @@ def builtin_game_profile(game_id: str) -> dict[str, Any]:
|
||||||
"match_types": app.match_type_definitions(game_id),
|
"match_types": app.match_type_definitions(game_id),
|
||||||
"championships": app.title_definitions_for_game(game_id),
|
"championships": app.title_definitions_for_game(game_id),
|
||||||
"women_roster": sorted(app.WOMEN_ROSTERS.get(game_id, set())),
|
"women_roster": sorted(app.WOMEN_ROSTERS.get(game_id, set())),
|
||||||
|
"light_heavyweight_roster": None,
|
||||||
"shows": {"Raw": "Monday Night Raw", "SmackDown": "Thursday Night SmackDown!",
|
"shows": {"Raw": "Monday Night Raw", "SmackDown": "Thursday Night SmackDown!",
|
||||||
"PPV": "WWF PPV"},
|
"PPV": "WWF PPV"},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,14 @@ def record_match_result(card: dict[str, Any], index: int, auto_simulate: bool =
|
||||||
if not is_player_match and not auto_simulate:
|
if not is_player_match and not auto_simulate:
|
||||||
print(f"Only {wrestler}'s matches can be recorded manually in Wrestler control.")
|
print(f"Only {wrestler}'s matches can be recorded manually in Wrestler control.")
|
||||||
return False
|
return False
|
||||||
if match.get("title_defense") and season and not app.validate_title_defense(season, match):
|
if season:
|
||||||
|
valid_division = (
|
||||||
|
app.validate_title_defense(season, match)
|
||||||
|
if match.get("title_defense")
|
||||||
|
else app.validate_match_division_eligibility(season, match)
|
||||||
|
)
|
||||||
|
if not valid_division:
|
||||||
|
if match.get("title_defense"):
|
||||||
print("Edit this match's participants or title status before recording its result.")
|
print("Edit this match's participants or title status before recording its result.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ def season_path(season_id: str) -> Path:
|
||||||
|
|
||||||
def new_season_state(name: str, game_id: str,
|
def new_season_state(name: str, game_id: str,
|
||||||
champions: dict[str, list[str] | None] | None = None,
|
champions: dict[str, list[str] | None] | None = None,
|
||||||
season_id: str | None = None) -> dict[str, Any]:
|
season_id: str | None = None,
|
||||||
|
profile: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||||
champions = champions or {}
|
champions = champions or {}
|
||||||
profile = app.game_profile(game_id)
|
profile = profile or app.game_profile(game_id)
|
||||||
title_definitions = [dict(item) for item in profile["championships"]]
|
title_definitions = [dict(item) for item in profile["championships"]]
|
||||||
title_state: dict[str, Any] = {}
|
title_state: dict[str, Any] = {}
|
||||||
for definition in title_definitions:
|
for definition in title_definitions:
|
||||||
|
|
@ -69,6 +70,14 @@ def load_season(path_or_id: Path | str) -> dict[str, Any]:
|
||||||
if not payload.get("season_id") or not payload.get("name"):
|
if not payload.get("season_id") or not payload.get("name"):
|
||||||
raise ValueError(f"Season save is missing its identity: {path}")
|
raise ValueError(f"Season save is missing its identity: {path}")
|
||||||
payload.setdefault("game_config", app.game_profile(payload["game_id"]))
|
payload.setdefault("game_config", app.game_profile(payload["game_id"]))
|
||||||
|
if payload["game_id"] in app.BUILTIN_GAME_IDS:
|
||||||
|
for definitions in (payload.get("title_definitions"),
|
||||||
|
payload["game_config"].get("championships")):
|
||||||
|
if isinstance(definitions, list):
|
||||||
|
for definition in definitions:
|
||||||
|
if (definition.get("name") == "Light-Heavyweight"
|
||||||
|
and definition.get("kind") == "singles"):
|
||||||
|
definition["kind"] = "light_heavyweight"
|
||||||
payload.setdefault("game_title", payload["game_config"].get("game_title", app.game_title(payload["game_id"])))
|
payload.setdefault("game_title", payload["game_config"].get("game_title", app.game_title(payload["game_id"])))
|
||||||
payload.setdefault("federation_name", payload["game_config"].get(
|
payload.setdefault("federation_name", payload["game_config"].get(
|
||||||
"federation_name", app.federation_name(payload["game_id"])
|
"federation_name", app.federation_name(payload["game_id"])
|
||||||
|
|
@ -174,13 +183,45 @@ def eligible_roster_for_division(game_id: str, division: str,
|
||||||
profile: dict[str, Any] | None = None) -> list[str]:
|
profile: dict[str, Any] | None = None) -> list[str]:
|
||||||
profile = profile or app.game_profile(game_id)
|
profile = profile or app.game_profile(game_id)
|
||||||
roster = list(profile.get("roster", [])) if roster is None else roster
|
roster = list(profile.get("roster", [])) if roster is None else roster
|
||||||
if app.championship_kind(game_id, division, profile) == "women":
|
eligibility_field = {
|
||||||
eligible_names = profile.get("women_roster", [])
|
"women": "women_roster",
|
||||||
|
"light_heavyweight": "light_heavyweight_roster",
|
||||||
|
}.get(app.championship_kind(game_id, division, profile))
|
||||||
|
if eligibility_field:
|
||||||
|
eligible_names = profile.get(eligibility_field)
|
||||||
|
# Older seasons do not have a Light-Heavyweight eligibility snapshot.
|
||||||
|
if eligible_names is None:
|
||||||
|
return list(roster)
|
||||||
eligible = {name.casefold() for name in eligible_names}
|
eligible = {name.casefold() for name in eligible_names}
|
||||||
return [name for name in roster if name.casefold() in eligible]
|
return [name for name in roster if name.casefold() in eligible]
|
||||||
return list(roster)
|
return list(roster)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_match_division_eligibility(season: dict[str, Any],
|
||||||
|
match: dict[str, Any]) -> bool:
|
||||||
|
division = match.get("ranking_division")
|
||||||
|
if division not in app.season_title_names(season):
|
||||||
|
return True
|
||||||
|
if app.season_championship_kind(season, division) not in {"women", "light_heavyweight"}:
|
||||||
|
return True
|
||||||
|
profile = app.season_game_profile(season)
|
||||||
|
eligible = {
|
||||||
|
name.casefold()
|
||||||
|
for name in app.eligible_roster_for_division(
|
||||||
|
season["game_id"], division, profile=profile
|
||||||
|
)
|
||||||
|
}
|
||||||
|
excluded = [
|
||||||
|
name for side in match.get("sides", []) for name in side
|
||||||
|
if name.casefold() not in eligible
|
||||||
|
]
|
||||||
|
if excluded:
|
||||||
|
names = ", ".join(dict.fromkeys(excluded))
|
||||||
|
print(f"{names} is not eligible for the {division} division.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def choose_roster_member(names: list[str], prompt: str) -> str:
|
def choose_roster_member(names: list[str], prompt: str) -> str:
|
||||||
if not names:
|
if not names:
|
||||||
raise ValueError("No eligible wrestlers are available for that championship.")
|
raise ValueError("No eligible wrestlers are available for that championship.")
|
||||||
|
|
@ -217,6 +258,13 @@ def create_season() -> dict[str, Any]:
|
||||||
game_id = app.choose_game()
|
game_id = app.choose_game()
|
||||||
profile = app.game_profile(game_id)
|
profile = app.game_profile(game_id)
|
||||||
name = app.ask("Season name", f"{app.game_display_name(game_id)} Season")
|
name = app.ask("Season name", f"{app.game_display_name(game_id)} Season")
|
||||||
|
has_light_heavyweight = any(
|
||||||
|
definition.get("kind") == "light_heavyweight"
|
||||||
|
for definition in profile["championships"]
|
||||||
|
)
|
||||||
|
if has_light_heavyweight and game_id in app.BUILTIN_GAME_IDS:
|
||||||
|
profile["light_heavyweight_roster"] = []
|
||||||
|
app.edit_light_heavyweight_roster(profile)
|
||||||
champions: dict[str, list[str] | None] = {}
|
champions: dict[str, list[str] | None] = {}
|
||||||
used: set[str] = set()
|
used: set[str] = set()
|
||||||
for definition in profile["championships"]:
|
for definition in profile["championships"]:
|
||||||
|
|
@ -225,7 +273,7 @@ def create_season() -> dict[str, Any]:
|
||||||
champions[division] = holder
|
champions[division] = holder
|
||||||
if holder:
|
if holder:
|
||||||
used.update(name.casefold() for name in holder)
|
used.update(name.casefold() for name in holder)
|
||||||
season = app.new_season_state(name, game_id, champions)
|
season = app.new_season_state(name, game_id, champions, profile=profile)
|
||||||
app.save_season(season)
|
app.save_season(season)
|
||||||
print(f"Season saved: {app.season_path(season['season_id'])}")
|
print(f"Season saved: {app.season_path(season['season_id'])}")
|
||||||
return season
|
return season
|
||||||
|
|
@ -302,7 +350,9 @@ def season_rivalry_id(sides: list[list[str]]) -> str | None:
|
||||||
|
|
||||||
|
|
||||||
def record_season_match(season: dict[str, Any], card: dict[str, Any],
|
def record_season_match(season: dict[str, Any], card: dict[str, Any],
|
||||||
match: dict[str, Any], match_index: int) -> None:
|
match: dict[str, Any], match_index: int) -> bool:
|
||||||
|
if not app.validate_match_division_eligibility(season, match):
|
||||||
|
return False
|
||||||
division = match.get("ranking_division")
|
division = match.get("ranking_division")
|
||||||
winner = match.get("winner_side")
|
winner = match.get("winner_side")
|
||||||
if division in app.season_title_names(season) and winner is not None:
|
if division in app.season_title_names(season) and winner is not None:
|
||||||
|
|
@ -351,6 +401,7 @@ def record_season_match(season: dict[str, Any], card: dict[str, Any],
|
||||||
if match.get("rivalry_payoff"):
|
if match.get("rivalry_payoff"):
|
||||||
rivalry["resolved"] = True
|
rivalry["resolved"] = True
|
||||||
rivalry["resolved_at"] = card.get("season_show", "")
|
rivalry["resolved_at"] = card.get("season_show", "")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def validate_title_defense(season: dict[str, Any], match: dict[str, Any]) -> bool:
|
def validate_title_defense(season: dict[str, Any], match: dict[str, Any]) -> bool:
|
||||||
|
|
@ -360,6 +411,8 @@ def validate_title_defense(season: dict[str, Any], match: dict[str, Any]) -> boo
|
||||||
if division not in app.season_title_names(season):
|
if division not in app.season_title_names(season):
|
||||||
print("A title defense must be assigned to a championship division.")
|
print("A title defense must be assigned to a championship division.")
|
||||||
return False
|
return False
|
||||||
|
if not app.validate_match_division_eligibility(season, match):
|
||||||
|
return False
|
||||||
holder = season["champions"][division].get("holder")
|
holder = season["champions"][division].get("holder")
|
||||||
if holder and not any(app.side_identity(side) == app.side_identity(holder)
|
if holder and not any(app.side_identity(side) == app.side_identity(holder)
|
||||||
for side in match.get("sides", [])):
|
for side in match.get("sides", [])):
|
||||||
|
|
@ -384,9 +437,12 @@ def sync_season_card_results(card: dict[str, Any]) -> None:
|
||||||
result_id = f"{season_id}:{card.get('season_show_number', 0)}:{index + 1}"
|
result_id = f"{season_id}:{card.get('season_show_number', 0)}:{index + 1}"
|
||||||
if result_id in processed:
|
if result_id in processed:
|
||||||
continue
|
continue
|
||||||
|
if not app.validate_match_division_eligibility(season, match):
|
||||||
|
continue
|
||||||
if match.get("title_defense") and not app.validate_title_defense(season, match):
|
if match.get("title_defense") and not app.validate_title_defense(season, match):
|
||||||
continue
|
continue
|
||||||
app.record_season_match(season, card, match, index)
|
if not app.record_season_match(season, card, match, index):
|
||||||
|
continue
|
||||||
processed.add(result_id)
|
processed.add(result_id)
|
||||||
season["processed_results"] = sorted(processed)
|
season["processed_results"] = sorted(processed)
|
||||||
complete = all(match.get("winner_side") is not None for match in card["matches"])
|
complete = all(match.get("winner_side") is not None for match in card["matches"])
|
||||||
|
|
@ -399,4 +455,4 @@ def sync_season_card_results(card: dict[str, Any]) -> None:
|
||||||
season["show_history"].append(card["path"])
|
season["show_history"].append(card["path"])
|
||||||
app.save_season(season)
|
app.save_season(season)
|
||||||
|
|
||||||
__all__ = ['choose_initial_champion', 'choose_roster_member', 'choose_season_control_mode', 'create_season', 'eligible_roster_for_division', 'load_season', 'new_season_state', 'print_power_rankings', 'record_season_match', 'save_season', 'season_control_label', 'season_other_pool', 'season_path', 'season_ranking_entries', 'season_rivalry_id', 'season_show_brand', 'side_display', 'side_identity', 'sync_season_card_results', 'toggle_season_control_mode', 'validate_title_defense']
|
__all__ = ['choose_initial_champion', 'choose_roster_member', 'choose_season_control_mode', 'create_season', 'eligible_roster_for_division', 'load_season', 'new_season_state', 'print_power_rankings', 'record_season_match', 'save_season', 'season_control_label', 'season_other_pool', 'season_path', 'season_ranking_entries', 'season_rivalry_id', 'season_show_brand', 'side_display', 'side_identity', 'sync_season_card_results', 'toggle_season_control_mode', 'validate_match_division_eligibility', 'validate_title_defense']
|
||||||
|
|
|
||||||
154
test_lvpwb.py
154
test_lvpwb.py
|
|
@ -916,6 +916,126 @@ class SeasonControlTests(unittest.TestCase):
|
||||||
self.assertEqual(updated["next_show_number"], 2)
|
self.assertEqual(updated["next_show_number"], 2)
|
||||||
|
|
||||||
|
|
||||||
|
class LightHeavyweightTests(unittest.TestCase):
|
||||||
|
def test_builtin_season_selects_and_snapshots_eligible_wrestlers(self):
|
||||||
|
def select_eligible(profile):
|
||||||
|
profile["light_heavyweight_roster"] = ["X-Pac", "Jeff Hardy"]
|
||||||
|
|
||||||
|
def initial_champion(division, _game_id, _used, _profile):
|
||||||
|
return ["X-Pac"] if division == "Light-Heavyweight" else None
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as folder, \
|
||||||
|
patch.object(app, "SEASON_DIR", Path(folder)), \
|
||||||
|
patch.object(app, "choose_game", return_value="sd1"), \
|
||||||
|
patch.object(app, "ask", return_value="Lightweight Season"), \
|
||||||
|
patch.object(app, "edit_light_heavyweight_roster", side_effect=select_eligible), \
|
||||||
|
patch.object(app, "choose_initial_champion", side_effect=initial_champion), \
|
||||||
|
contextlib.redirect_stdout(io.StringIO()):
|
||||||
|
season = app.create_season()
|
||||||
|
loaded = app.load_season(season["season_id"])
|
||||||
|
|
||||||
|
self.assertEqual(season["game_config"]["light_heavyweight_roster"],
|
||||||
|
["X-Pac", "Jeff Hardy"])
|
||||||
|
self.assertEqual(
|
||||||
|
app.eligible_roster_for_division(
|
||||||
|
"sd1", "Light-Heavyweight", profile=loaded["game_config"]
|
||||||
|
),
|
||||||
|
["X-Pac", "Jeff Hardy"],
|
||||||
|
)
|
||||||
|
self.assertEqual(loaded["champions"]["Light-Heavyweight"]["holder"], ["X-Pac"])
|
||||||
|
defense = {
|
||||||
|
"ranking_division": "Light-Heavyweight", "title_defense": True,
|
||||||
|
"sides": [["X-Pac"], ["Jeff Hardy"]], "winner_side": 1,
|
||||||
|
}
|
||||||
|
self.assertTrue(app.validate_title_defense(loaded, defense))
|
||||||
|
app.record_season_match(loaded, {"season_show": "Raw"}, defense, 0)
|
||||||
|
self.assertEqual(loaded["champions"]["Light-Heavyweight"]["holder"], ["Jeff Hardy"])
|
||||||
|
|
||||||
|
def test_initial_champion_random_choice_uses_only_eligible_wrestlers(self):
|
||||||
|
profile = app.game_profile("sd1")
|
||||||
|
profile["light_heavyweight_roster"] = ["X-Pac"]
|
||||||
|
choices_seen = []
|
||||||
|
|
||||||
|
def first_choice(options):
|
||||||
|
choices_seen.append(list(options))
|
||||||
|
return options[0]
|
||||||
|
|
||||||
|
with patch.object(app, "ask_choice", return_value=1), \
|
||||||
|
patch.object(app.random, "choice", side_effect=first_choice), \
|
||||||
|
contextlib.redirect_stdout(io.StringIO()):
|
||||||
|
champion = app.choose_initial_champion(
|
||||||
|
"Light-Heavyweight", "sd1", set(), profile
|
||||||
|
)
|
||||||
|
self.assertEqual(champion, ["X-Pac"])
|
||||||
|
self.assertEqual(choices_seen, [["X-Pac"]])
|
||||||
|
|
||||||
|
def test_ineligible_wrestlers_cannot_record_or_sync_division_results(self):
|
||||||
|
with tempfile.TemporaryDirectory() as folder, \
|
||||||
|
patch.object(app, "SEASON_DIR", Path(folder)):
|
||||||
|
season = app.new_season_state(
|
||||||
|
"Lightweight Season", "sd1",
|
||||||
|
champions={"Light-Heavyweight": ["X-Pac"]},
|
||||||
|
season_id="lightweight-results",
|
||||||
|
)
|
||||||
|
season["game_config"]["light_heavyweight_roster"] = ["X-Pac"]
|
||||||
|
card_path = Path(folder) / "card.md"
|
||||||
|
season["current_card"] = str(card_path)
|
||||||
|
app.save_season(season)
|
||||||
|
match = {
|
||||||
|
"type": "Singles", "sides": [["X-Pac"], ["The Rock"]],
|
||||||
|
"winner_side": 1, "ranking_division": "Light-Heavyweight",
|
||||||
|
"title_defense": True,
|
||||||
|
}
|
||||||
|
card = {
|
||||||
|
"season_id": season["season_id"], "season_show_number": 1,
|
||||||
|
"season_show": "Raw", "path": str(card_path),
|
||||||
|
"matches": [match],
|
||||||
|
}
|
||||||
|
|
||||||
|
with contextlib.redirect_stdout(io.StringIO()):
|
||||||
|
self.assertFalse(app.validate_title_defense(season, match))
|
||||||
|
self.assertFalse(app.record_match_result(card, 0, auto_simulate=True))
|
||||||
|
app.sync_season_card_results(card)
|
||||||
|
|
||||||
|
updated = app.load_season(season["season_id"])
|
||||||
|
self.assertEqual(updated["champions"]["Light-Heavyweight"]["holder"], ["X-Pac"])
|
||||||
|
self.assertEqual(updated["rankings"]["Light-Heavyweight"], {})
|
||||||
|
self.assertEqual(updated["processed_results"], [])
|
||||||
|
|
||||||
|
def test_legacy_season_without_eligibility_snapshot_remains_unrestricted(self):
|
||||||
|
season = app.new_season_state("Legacy Season", "sd1", season_id="legacy-lightweight")
|
||||||
|
season["game_config"].pop("light_heavyweight_roster", None)
|
||||||
|
self.assertEqual(
|
||||||
|
app.eligible_roster_for_division(
|
||||||
|
"sd1", "Light-Heavyweight", profile=season["game_config"]
|
||||||
|
),
|
||||||
|
season["game_config"]["roster"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_legacy_builtin_light_heavyweight_kind_migrates_without_restricting_roster(self):
|
||||||
|
season = app.new_season_state(
|
||||||
|
"Legacy Lightweight", "sd1", season_id="legacy-lightweight-kind"
|
||||||
|
)
|
||||||
|
for definitions in (season["title_definitions"],
|
||||||
|
season["game_config"]["championships"]):
|
||||||
|
next(item for item in definitions if item["name"] == "Light-Heavyweight")["kind"] = "singles"
|
||||||
|
season["game_config"].pop("light_heavyweight_roster", None)
|
||||||
|
with tempfile.TemporaryDirectory() as folder, \
|
||||||
|
patch.object(app, "SEASON_DIR", Path(folder)):
|
||||||
|
app.save_season(season)
|
||||||
|
loaded = app.load_season(season["season_id"])
|
||||||
|
self.assertEqual(
|
||||||
|
app.season_championship_kind(loaded, "Light-Heavyweight"),
|
||||||
|
"light_heavyweight",
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
app.eligible_roster_for_division(
|
||||||
|
"sd1", "Light-Heavyweight", profile=loaded["game_config"]
|
||||||
|
),
|
||||||
|
loaded["game_config"]["roster"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BYOGTests(unittest.TestCase):
|
class BYOGTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self._configs = app.CUSTOM_GAME_CONFIGS
|
self._configs = app.CUSTOM_GAME_CONFIGS
|
||||||
|
|
@ -954,6 +1074,7 @@ class BYOGTests(unittest.TestCase):
|
||||||
{"name": "Fantasy Women", "kind": "women"},
|
{"name": "Fantasy Women", "kind": "women"},
|
||||||
],
|
],
|
||||||
"women_roster": ["Cara Storm"],
|
"women_roster": ["Cara Storm"],
|
||||||
|
"light_heavyweight_roster": [],
|
||||||
}
|
}
|
||||||
data.update(overrides)
|
data.update(overrides)
|
||||||
return data
|
return data
|
||||||
|
|
@ -1007,6 +1128,7 @@ class BYOGTests(unittest.TestCase):
|
||||||
"invalid side sizes"),
|
"invalid side sizes"),
|
||||||
({"match_types": []}, "at least one match type"),
|
({"match_types": []}, "at least one match type"),
|
||||||
({"championships": [{"name": "Belt", "kind": "hardcore"}]}, "kind"),
|
({"championships": [{"name": "Belt", "kind": "hardcore"}]}, "kind"),
|
||||||
|
({"light_heavyweight_roster": ["Not Listed"]}, "subset of the roster"),
|
||||||
({"platform": "dreamcast"}, "platform must be ps1"),
|
({"platform": "dreamcast"}, "platform must be ps1"),
|
||||||
({"screenshot_directory": 3}, "screenshot_directory must be a path string"),
|
({"screenshot_directory": 3}, "screenshot_directory must be a path string"),
|
||||||
]
|
]
|
||||||
|
|
@ -1053,6 +1175,38 @@ class BYOGTests(unittest.TestCase):
|
||||||
app.ROOT / "custom-screens")
|
app.ROOT / "custom-screens")
|
||||||
self.assertTrue(app.game_has_screenshots(overridden["game_id"]))
|
self.assertTrue(app.game_has_screenshots(overridden["game_id"]))
|
||||||
|
|
||||||
|
def test_custom_light_heavyweight_eligibility_is_validated_and_applied(self):
|
||||||
|
config = self.config(
|
||||||
|
championships=[
|
||||||
|
{"name": "Fantasy Lightweight", "kind": "light_heavyweight"},
|
||||||
|
],
|
||||||
|
light_heavyweight_roster=["Ava Crusher", "Cara Storm"],
|
||||||
|
)
|
||||||
|
self.register(config)
|
||||||
|
profile = app.game_profile(config["game_id"])
|
||||||
|
self.assertEqual(
|
||||||
|
app.eligible_roster_for_division(
|
||||||
|
config["game_id"], "Fantasy Lightweight", profile=profile
|
||||||
|
),
|
||||||
|
["Ava Crusher", "Cara Storm"],
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
app.championship_kind(config["game_id"], "Fantasy Lightweight", profile),
|
||||||
|
"light_heavyweight",
|
||||||
|
)
|
||||||
|
with self.assertRaisesRegex(ValueError, "must contain eligible wrestlers"):
|
||||||
|
app.validate_custom_game(self.config(championships=[
|
||||||
|
{"name": "Fantasy Lightweight", "kind": "light_heavyweight"},
|
||||||
|
]))
|
||||||
|
|
||||||
|
def test_light_heavyweight_eligibility_editor_selects_roster_members(self):
|
||||||
|
config = {"roster": ["Ava Crusher", "Ben Brawler"],
|
||||||
|
"light_heavyweight_roster": []}
|
||||||
|
with patch.object(app, "ask_choice", side_effect=[0, 0, 2]), \
|
||||||
|
contextlib.redirect_stdout(io.StringIO()):
|
||||||
|
app.edit_light_heavyweight_roster(config)
|
||||||
|
self.assertEqual(config["light_heavyweight_roster"], ["Ava Crusher"])
|
||||||
|
|
||||||
def test_custom_game_screenshot_override_can_be_set_in_editor(self):
|
def test_custom_game_screenshot_override_can_be_set_in_editor(self):
|
||||||
config = app.validate_custom_game(self.config())
|
config = app.validate_custom_game(self.config())
|
||||||
with patch.object(app, "ask_choice", side_effect=[0, 0, 6]), \
|
with patch.object(app, "ask_choice", side_effect=[0, 0, 6]), \
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue