Implement Retire HoF (Gauntlet) rule enforcement for genlockes
When retireHoF is enabled, surviving HoF Pokemon and their evolutionary families are retired at leg advancement and treated as duplicates in all subsequent legs — both in the encounter modal and bulk randomize. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import random
|
||||
from collections import deque
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response
|
||||
from sqlalchemy import select
|
||||
@@ -9,6 +8,7 @@ from sqlalchemy.orm import joinedload, selectinload
|
||||
from app.core.database import get_session
|
||||
from app.models.encounter import Encounter
|
||||
from app.models.evolution import Evolution
|
||||
from app.models.genlocke import GenlockeLeg
|
||||
from app.models.nuzlocke_run import NuzlockeRun
|
||||
from app.models.pokemon import Pokemon
|
||||
from app.models.route import Route
|
||||
@@ -20,6 +20,7 @@ from app.schemas.encounter import (
|
||||
EncounterResponse,
|
||||
EncounterUpdate,
|
||||
)
|
||||
from app.services.families import build_families
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -159,34 +160,6 @@ async def delete_encounter(
|
||||
return Response(status_code=204)
|
||||
|
||||
|
||||
def _build_families(evolutions: list[Evolution]) -> dict[int, list[int]]:
|
||||
"""Build pokemon_id → family members mapping using BFS on evolution graph."""
|
||||
adj: dict[int, set[int]] = {}
|
||||
for evo in evolutions:
|
||||
adj.setdefault(evo.from_pokemon_id, set()).add(evo.to_pokemon_id)
|
||||
adj.setdefault(evo.to_pokemon_id, set()).add(evo.from_pokemon_id)
|
||||
|
||||
visited: set[int] = set()
|
||||
pokemon_to_family: dict[int, list[int]] = {}
|
||||
for node in adj:
|
||||
if node in visited:
|
||||
continue
|
||||
component: list[int] = []
|
||||
queue = deque([node])
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
if current in visited:
|
||||
continue
|
||||
visited.add(current)
|
||||
component.append(current)
|
||||
for neighbor in adj.get(current, set()):
|
||||
if neighbor not in visited:
|
||||
queue.append(neighbor)
|
||||
for member in component:
|
||||
pokemon_to_family[member] = component
|
||||
return pokemon_to_family
|
||||
|
||||
|
||||
@router.post(
|
||||
"/runs/{run_id}/encounters/bulk-randomize",
|
||||
response_model=BulkRandomizeResponse,
|
||||
@@ -247,7 +220,7 @@ async def bulk_randomize_encounters(
|
||||
if dupes_clause_on:
|
||||
evo_result = await session.execute(select(Evolution))
|
||||
evolutions = evo_result.scalars().all()
|
||||
pokemon_to_family = _build_families(evolutions)
|
||||
pokemon_to_family = build_families(evolutions)
|
||||
|
||||
# 7. Build initial duped set from existing caught encounters
|
||||
duped: set[int] = set()
|
||||
@@ -260,6 +233,23 @@ async def bulk_randomize_encounters(
|
||||
for member in family:
|
||||
duped.add(member)
|
||||
|
||||
# Seed duped set with retired Pokemon IDs from prior genlocke legs
|
||||
leg_result = await session.execute(
|
||||
select(GenlockeLeg).where(GenlockeLeg.run_id == run_id)
|
||||
)
|
||||
leg = leg_result.scalar_one_or_none()
|
||||
if leg:
|
||||
genlocke_result = await session.execute(
|
||||
select(GenlockeLeg.retired_pokemon_ids)
|
||||
.where(
|
||||
GenlockeLeg.genlocke_id == leg.genlocke_id,
|
||||
GenlockeLeg.leg_order < leg.leg_order,
|
||||
GenlockeLeg.retired_pokemon_ids.isnot(None),
|
||||
)
|
||||
)
|
||||
for (retired_ids,) in genlocke_result:
|
||||
duped.update(retired_ids)
|
||||
|
||||
# 8. Organize routes: identify top-level and children
|
||||
routes_by_id = {r.id: r for r in all_routes}
|
||||
top_level = [r for r in all_routes if r.parent_route_id is None]
|
||||
|
||||
Reference in New Issue
Block a user