LVPWB/lvpwb/results.py

238 lines
10 KiB
Python

from __future__ import annotations
import json
from typing import Any
import lvpwb as app
def choose_match_outcome(match_type: str | dict[str, Any]) -> str:
outcomes = app.allowed_match_outcomes(match_type)
if len(outcomes) == 1:
print(f"Match outcome: {outcomes[0]}")
return outcomes[0]
return outcomes[app.ask_choice("Winning method", list(outcomes))]
def prompt_match_minutes() -> float:
while True:
raw = app.ask("Match time in minutes")
value = app.parse_match_minutes(raw)
if value is not None:
return value
print("Enter a non-negative number of minutes (decimals are okay).")
def prompt_action_count(label: str) -> int:
while True:
raw = app.ask(label, "0")
value = app.parse_action_count(raw)
if value is not None:
return value
print("Enter a whole number zero or greater.")
def season_simulation_weights(season: dict[str, Any] | None,
match: dict[str, Any]) -> list[float]:
"""Higher-ranked division entrants get an edge, but lower ranks can upset."""
sides = match.get("sides", [])
division = match.get("ranking_division")
if not season or not division or division not in app.season_title_names(season):
return [1.0] * len(sides)
entries = app.season_ranking_entries(season, division)
rank_positions = {app.side_identity(entry.get("members", [])): position
for position, entry in enumerate(entries)}
total = len(entries)
weights: list[float] = []
holder = season.get("champions", {}).get(division, {}).get("holder")
for side in sides:
key = app.side_identity(side)
position = rank_positions.get(key)
if position is None or total <= 1:
weight = 1.0
else:
normalized_rank = (total - 1 - position) / (total - 1)
weight = 1.0 + 1.5 * normalized_rank
record = next((entry for entry in entries
if app.side_identity(entry.get("members", [])) == key), None)
if record:
wins, losses = record.get("wins", 0), record.get("losses", 0)
total_results = wins + losses
if total_results:
form = wins / total_results
weight += (form - 0.5) * 0.3
if match.get("title_defense") and holder and app.side_identity(holder) == key:
weight *= 1.15
weights.append(max(0.5, weight))
return weights
def simulate_match_result(card: dict[str, Any], index: int,
season: dict[str, Any] | None = None) -> dict[str, Any]:
"""Apply an auto-simulated winner, a legal finish, and plausible rating inputs."""
match = card["matches"][index]
sides = match.get("sides", [])
if not sides:
raise ValueError("Cannot simulate a match before its participants are configured.")
weights = app.season_simulation_weights(season, match) if season else [1.0] * len(sides)
match["winner_side"] = app.random.choices(range(len(sides)), weights=weights, k=1)[0]
match["finish"] = app.random.choice(app.allowed_match_outcomes(match["type"]))
gimmick = match["type"] in {"Cage", "Ladder", "Hardcore", "Table", "TLC", "Hell in a Cell"}
low, high = (8, 28) if gimmick or match.get("title_defense") else (5, 20)
if index == 2:
high += 5
match["match_time_minutes"] = app.random.randint(low, high)
match["near_falls"] = app.random.randint(0, 5)
match["finishers"] = app.random.randint(1, 3)
match["rating"] = app.calculate_match_rating(match)
return match
def match_features_wrestler(match: dict[str, Any], wrestler: str) -> bool:
selected = wrestler.casefold()
return any(
isinstance(name, str) and name.casefold() == selected
for side in match.get("sides", [])
for name in side
)
def simulate_non_player_matches(
card: dict[str, Any], season: dict[str, Any] | None = None
) -> None:
"""Auto-sim pending matches that do not feature the season's player wrestler."""
if season is None:
try:
season = app.load_season(str(card["season_id"]))
except (KeyError, OSError, ValueError, json.JSONDecodeError) as exc:
print(f"Cannot verify season control mode: {exc}")
return
if season.get("control_mode") != "wrestler":
return
wrestler = season.get("player_wrestler")
if not isinstance(wrestler, str) or not wrestler:
print("Wrestler control has no selected wrestler; change control mode in the season menu.")
return
pending = app.pending_indices(card)
for index in pending:
match = card["matches"][index]
if not match_features_wrestler(match, wrestler):
if not app.record_match_result(card, index, auto_simulate=True):
return
def record_match_result(card: dict[str, Any], index: int, auto_simulate: bool = False) -> bool:
match = card["matches"][index]
season: dict[str, Any] | None = None
if card.get("season_id"):
try:
season = app.load_season(str(card["season_id"]))
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"Cannot verify season data: {exc}")
return False
if season and season.get("control_mode") == "wrestler":
wrestler = season.get("player_wrestler")
if not isinstance(wrestler, str) or not wrestler:
print("Wrestler control has no selected wrestler; change control mode in the season menu.")
return False
is_player_match = match_features_wrestler(match, wrestler)
if is_player_match and auto_simulate:
print(f"{wrestler}'s match must be recorded manually in Wrestler control.")
return False
if not is_player_match and not auto_simulate:
print(f"Only {wrestler}'s matches can be recorded manually in Wrestler control.")
return False
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.")
return False
if auto_simulate:
app.simulate_match_result(card, index, season)
print(f"Auto-simulated Match {index + 1}: {' & '.join(match['sides'][match['winner_side']])} "
f"wins by {match['finish'].lower()} in {match['match_time_minutes']} minutes.")
else:
sides = match["sides"]
side_labels = [f"Side {side_number}: {' & '.join(side)}"
for side_number, side in enumerate(sides, 1)]
match["winner_side"] = app.ask_choice(
"Winning side", list(range(len(sides))), labels=side_labels
)
match["finish"] = app.choose_match_outcome(match)
match["match_time_minutes"] = app.prompt_match_minutes()
match["near_falls"] = app.prompt_action_count("Near falls (two-count kickouts)")
match["finishers"] = app.prompt_action_count("Finishers executed")
match["rating"] = app.calculate_match_rating(match)
app.save_card(card)
if card.get("season_id"):
app.sync_season_card_results(card)
print(f"Result saved to {card['path']} · Match rating: {match['rating']:.1f}%.")
ppv_rating = app.calculate_ppv_rating(card)
if ppv_rating is not None:
print(f"PPV rating (20/30/50 match weighting): {ppv_rating:.1f}%")
return True
def record_result(card: dict[str, Any], auto_simulate: bool | None = None,
match_index: int | None = None) -> None:
pending = app.pending_indices(card)
if not pending:
print("All three matches are complete.")
return
season = None
if card.get("season_id"):
try:
season = app.load_season(str(card["season_id"]))
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"Cannot verify season data: {exc}")
return
if season and season.get("control_mode") == "wrestler":
app.simulate_non_player_matches(card, season)
wrestler = season.get("player_wrestler")
if not isinstance(wrestler, str) or not wrestler:
print("Wrestler control has no selected wrestler; change control mode in the season menu.")
return
pending = [
index for index in app.pending_indices(card)
if app.match_features_wrestler(card["matches"][index], wrestler)
]
if not pending:
print(f"No pending matches featuring {wrestler}.")
return
index = match_index if match_index in pending else app.choose_match_index(
card, pending, "Choose a match to finish"
)
if season and season.get("control_mode") == "wrestler":
auto_simulate = False
elif auto_simulate is None:
auto_simulate = app.ask_choice(
"Result mode", ["Enter result manually", "Auto-simulate result"]
) == 1
app.record_match_result(card, index, auto_simulate=auto_simulate)
def simulate_pending_matches(card: dict[str, Any]) -> None:
if card.get("season_id"):
try:
season = app.load_season(str(card["season_id"]))
except (OSError, ValueError, json.JSONDecodeError) as exc:
print(f"Cannot verify season data: {exc}")
return
if season.get("control_mode") == "wrestler":
app.simulate_non_player_matches(card, season)
print("Pending matches not featuring your wrestler were simulated.")
return
pending = app.pending_indices(card)
if not pending:
print("All three matches are complete.")
return
for index in pending:
if not app.record_match_result(card, index, auto_simulate=True):
break
__all__ = ['choose_match_outcome', 'match_features_wrestler', 'prompt_action_count', 'prompt_match_minutes', 'record_match_result', 'record_result', 'season_simulation_weights', 'simulate_match_result', 'simulate_non_player_matches', 'simulate_pending_matches']