Compare commits
6 Commits
f71db65642
...
dbddee4d92
| Author | SHA1 | Date | |
|---|---|---|---|
| dbddee4d92 | |||
| 734fb50f0a | |||
| 39d18c241e | |||
| 2ac6c23577 | |||
| 15283ede91 | |||
| e324559476 |
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-bi4e
|
# nuzlocke-tracker-bi4e
|
||||||
title: Integrate name suggestions into encounter registration UI
|
title: Integrate name suggestions into encounter registration UI
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
|
priority: normal
|
||||||
created_at: 2026-02-11T15:56:44Z
|
created_at: 2026-02-11T15:56:44Z
|
||||||
updated_at: 2026-02-11T15:56:44Z
|
updated_at: 2026-02-11T20:48:02Z
|
||||||
parent: nuzlocke-tracker-igl3
|
parent: nuzlocke-tracker-igl3
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -12,17 +13,23 @@ Show name suggestions in the encounter registration flow so users can pick a nic
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- When a user clicks an encounter slot and registers a new Pokemon, display 5-10 name suggestions below/near the nickname input
|
- When a user registers a new Pokemon encounter, display 5-10 name suggestions below/near the nickname input
|
||||||
- Each suggestion is a clickable chip/button that fills in the nickname field
|
- Each suggestion is a clickable chip/button that fills in the nickname field
|
||||||
- Include a "regenerate" button to get a fresh batch of suggestions
|
- Include a "regenerate" button to get a fresh batch of suggestions
|
||||||
- Only show suggestions if the run has a naming scheme selected
|
- Only show suggestions if the run has a naming scheme selected
|
||||||
- The nickname input should still be editable for manual entry
|
- The nickname input should still be editable for manual entry
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- **Data fetching**: Call `GET /api/v1/runs/{run_id}/name-suggestions?count=10` to get suggestions from the backend.
|
||||||
|
- **Regeneration**: Each call to the endpoint returns a fresh random batch (backend handles exclusion of used names).
|
||||||
|
- **No dictionary data in frontend**: All suggestion logic lives in the backend.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Add a name suggestions component (chips/buttons with regenerate)
|
- [x] Add a name suggestions component (chips/buttons with regenerate)
|
||||||
- [ ] Integrate the component into the encounter registration modal/form
|
- [x] Integrate the component into the encounter registration modal/form
|
||||||
- [ ] Wire up the name suggestion engine to the component
|
- [x] Wire up the backend API endpoint to the component via React Query
|
||||||
- [ ] Ensure clicking a suggestion populates the nickname field
|
- [x] Ensure clicking a suggestion populates the nickname field
|
||||||
- [ ] Ensure regenerate fetches a new batch without repeating prior suggestions
|
- [x] Ensure regenerate fetches a new batch from the API
|
||||||
- [ ] Hide suggestions gracefully if no naming scheme is set on the run
|
- [x] Hide suggestions gracefully if no naming scheme is set on the run
|
||||||
@@ -1,28 +1,36 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-c6ly
|
# nuzlocke-tracker-c6ly
|
||||||
title: Build name suggestion engine
|
title: Build name suggestion engine
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-11T15:56:44Z
|
created_at: 2026-02-11T15:56:44Z
|
||||||
updated_at: 2026-02-11T15:56:48Z
|
updated_at: 2026-02-11T20:44:27Z
|
||||||
parent: nuzlocke-tracker-igl3
|
parent: nuzlocke-tracker-igl3
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-bi4e
|
- nuzlocke-tracker-bi4e
|
||||||
---
|
---
|
||||||
|
|
||||||
Build the core logic that picks random name suggestions from the dictionary based on a selected naming scheme (category).
|
Build the backend service and API endpoint that picks random name suggestions from the dictionary based on a selected naming scheme.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Given a category and a list of already-used names in the run, return 5-10 unique suggestions
|
- Given a category and a run ID, return 5-10 unique suggestions
|
||||||
- Suggestions must not include names already assigned to other Pokemon in the same run
|
- The engine queries the run's existing encounter nicknames and excludes them from suggestions
|
||||||
- Support regeneration (return a fresh batch, avoiding previously shown suggestions where possible)
|
- Support regeneration (return a fresh batch, avoiding previously shown suggestions where possible)
|
||||||
- Handle edge case where category is nearly exhausted gracefully (return fewer suggestions)
|
- Handle edge case where category is nearly exhausted gracefully (return fewer suggestions)
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- **Backend service**: A Python module that loads the dictionary JSON, filters by category, excludes used names, and picks random suggestions.
|
||||||
|
- **API endpoint**: `GET /api/v1/runs/{run_id}/name-suggestions?count=10` — reads the run's `naming_scheme`, fetches encounter nicknames, returns suggestions.
|
||||||
|
- **No new DB tables needed**: Used names come from `Encounter.nickname` on the run's encounters.
|
||||||
|
- **Caching**: Load the dictionary once and cache in memory (it's static data).
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Create a service/utility module for name suggestion logic
|
- [x] Create a service module for name suggestion logic (e.g. `services/name_suggestions.py`)
|
||||||
- [ ] Implement random selection from a category with exclusion of used names
|
- [x] Implement dictionary loading with in-memory caching
|
||||||
- [ ] Implement regeneration that avoids repeating previous suggestions
|
- [x] Implement random selection from a category with exclusion of used names
|
||||||
- [ ] Add unit tests for the suggestion logic
|
- [x] Add API endpoint for fetching suggestions
|
||||||
|
- [x] Add unit tests for the suggestion logic
|
||||||
@@ -1,26 +1,34 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-igl3
|
# nuzlocke-tracker-igl3
|
||||||
title: Name Generation
|
title: Name Generation
|
||||||
status: todo
|
status: completed
|
||||||
type: epic
|
type: epic
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-05T13:45:15Z
|
created_at: 2026-02-05T13:45:15Z
|
||||||
updated_at: 2026-02-11T15:57:27Z
|
updated_at: 2026-02-11T20:48:02Z
|
||||||
---
|
---
|
||||||
|
|
||||||
Implement a dictionary-based nickname generation system for Nuzlocke runs. Instead of using an LLM API to generate names on the fly, provide a static dictionary of words categorised by theme. A word can belong to multiple categories, making it usable across different naming schemes.
|
Implement a dictionary-based nickname generation system for Nuzlocke runs. Instead of using an LLM API to generate names on the fly, provide a static dictionary of words categorised by theme. A word can belong to multiple categories, making it usable across different naming schemes.
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
- **Dictionary storage**: Static JSON file in `backend/src/app/seeds/data/`, alongside other seed data. Not exposed to frontend directly.
|
||||||
|
- **Dictionary format**: Category-keyed structure (`{ "mythology": ["Apollo", ...], "space": ["Nova", ...] }`) for fast lookup by naming scheme. Words may appear in multiple categories.
|
||||||
|
- **Suggestion logic**: Backend service with API endpoint. Frontend calls the backend to get suggestions.
|
||||||
|
- **Used-name tracking**: No new storage needed. The existing `Encounter.nickname` field already tracks assigned names. The suggestion engine queries encounter nicknames for the current run and excludes them.
|
||||||
|
- **Naming scheme per run**: Dedicated nullable `naming_scheme` column on `NuzlockeRun` (not in the `rules` JSONB).
|
||||||
|
|
||||||
## Approach
|
## Approach
|
||||||
|
|
||||||
- **Static dictionary**: A local data file (JSON) containing words tagged with categories (e.g. mythology, food, space, nature, warriors, music, etc.)
|
- **Static dictionary**: A local data file (JSON) containing words organised by category (e.g. mythology, food, space, nature, warriors, music, etc.)
|
||||||
- **~150-200 words per category**: A typical Nuzlocke has ~100 encounters, so this provides ample variety without repetition.
|
- **~150-200 words per category**: A typical Nuzlocke has ~100 encounters, so this provides ample variety without repetition.
|
||||||
- **Name suggestion UX**: When registering a new encounter, the user is shown 5-10 suggested names from their chosen naming scheme. They can click one to select it, or regenerate for a fresh batch.
|
- **Name suggestion UX**: When registering a new encounter, the user is shown 5-10 suggested names from their chosen naming scheme. They can click one to select it, or regenerate for a fresh batch.
|
||||||
- **Naming scheme selection**: Users pick a naming scheme (category) per run, either at run creation or in run settings.
|
- **Naming scheme selection**: Users pick a naming scheme (category) per run, either at run creation or in run settings.
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
- [ ] Word dictionary data file exists with multiple categories, each containing 150-200 words
|
- [x] Word dictionary data file exists with multiple categories, each containing 150-200 words
|
||||||
- [ ] Name suggestion engine picks random names from the selected category, avoiding duplicates already used in the run
|
- [x] Name suggestion engine picks random names from the selected category, avoiding duplicates already used in the run
|
||||||
- [ ] Encounter registration UI shows 5-10 clickable name suggestions
|
- [x] Encounter registration UI shows 5-10 clickable name suggestions
|
||||||
- [ ] User can regenerate suggestions if none fit
|
- [x] User can regenerate suggestions if none fit
|
||||||
- [ ] User can select a naming scheme per run
|
- [x] User can select a naming scheme per run
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
# nuzlocke-tracker-l272
|
||||||
|
title: Add boss data to seed files for all games
|
||||||
|
status: completed
|
||||||
|
type: feature
|
||||||
|
priority: normal
|
||||||
|
created_at: 2026-02-11T20:23:20Z
|
||||||
|
updated_at: 2026-02-11T20:31:27Z
|
||||||
|
---
|
||||||
|
|
||||||
|
Add gym leaders, Elite Four, champions and equivalents for all remaining games. Add kahuna and totem boss types for Alola games. Create 11 new seed files, complete 2 existing ones, and update frontend types/UI for new boss types.
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-m86o
|
# nuzlocke-tracker-m86o
|
||||||
title: Add naming scheme selection to run configuration
|
title: Add naming scheme selection to run configuration
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-11T15:56:44Z
|
created_at: 2026-02-11T15:56:44Z
|
||||||
updated_at: 2026-02-11T15:56:48Z
|
updated_at: 2026-02-11T20:37:00Z
|
||||||
parent: nuzlocke-tracker-igl3
|
parent: nuzlocke-tracker-igl3
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-bi4e
|
- nuzlocke-tracker-bi4e
|
||||||
@@ -15,14 +15,24 @@ Allow users to select a naming scheme (category) for their Nuzlocke run.
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Add a `namingScheme` field to the run model/settings (optional, nullable — user may not want auto-naming)
|
- Add a `naming_scheme` column to the NuzlockeRun model (nullable string — user may not want auto-naming)
|
||||||
- Provide a dropdown/selector in run creation and run settings where the user can pick a category
|
- Provide a dropdown/selector in run creation and run settings where the user can pick a category
|
||||||
- List available categories dynamically from the dictionary data
|
- List available categories dynamically by querying a backend endpoint that reads the dictionary file
|
||||||
- Allow changing the naming scheme mid-run
|
- Allow changing the naming scheme mid-run
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- **Storage**: Dedicated nullable `naming_scheme` column on `NuzlockeRun` (not in the `rules` JSONB). This is a first-class run setting.
|
||||||
|
- **Migration**: Add an Alembic migration for the new column.
|
||||||
|
- **API**: Add/update the run creation and update endpoints to accept `namingScheme`.
|
||||||
|
- **Categories endpoint**: Add a GET endpoint that returns the list of available category names from the dictionary file, so the frontend can populate the dropdown.
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Add `namingScheme` field to the NuzlockeRun type/model
|
- [x] Add `naming_scheme` nullable column to NuzlockeRun model
|
||||||
- [ ] Add naming scheme selector to run creation UI
|
- [x] Create Alembic migration for the new column
|
||||||
- [ ] Add naming scheme selector to run settings UI
|
- [x] Update run Pydantic schemas to include `namingScheme`
|
||||||
- [ ] Persist the selected naming scheme with the run data
|
- [x] Update run creation and update endpoints to persist the naming scheme
|
||||||
|
- [x] Add GET endpoint to list available naming categories from the dictionary
|
||||||
|
- [x] Add naming scheme selector to run creation UI
|
||||||
|
- [x] Add naming scheme selector to run settings UI
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-spx3
|
# nuzlocke-tracker-spx3
|
||||||
title: Evaluate separate seed/init container after PokeDB import
|
title: Evaluate separate seed/init container after PokeDB import
|
||||||
status: draft
|
status: scrapped
|
||||||
type: task
|
type: task
|
||||||
priority: low
|
priority: low
|
||||||
created_at: 2026-02-10T14:30:57Z
|
created_at: 2026-02-10T14:30:57Z
|
||||||
updated_at: 2026-02-10T14:30:57Z
|
updated_at: 2026-02-11T20:15:43Z
|
||||||
---
|
---
|
||||||
|
|
||||||
After the PokeDB.org data import (beans-bs05) is complete, evaluate whether the seed data has grown enough to justify splitting seeding into a separate init container.
|
After the PokeDB.org data import (beans-bs05) is complete, evaluate whether the seed data has grown enough to justify splitting seeding into a separate init container.
|
||||||
|
|||||||
@@ -1,29 +1,38 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-ueyy
|
# nuzlocke-tracker-ueyy
|
||||||
title: Create name dictionary data file
|
title: Create name dictionary data file
|
||||||
status: todo
|
status: completed
|
||||||
type: task
|
type: task
|
||||||
priority: normal
|
priority: normal
|
||||||
created_at: 2026-02-11T15:56:26Z
|
created_at: 2026-02-11T15:56:26Z
|
||||||
updated_at: 2026-02-11T15:56:48Z
|
updated_at: 2026-02-11T20:42:16Z
|
||||||
parent: nuzlocke-tracker-igl3
|
parent: nuzlocke-tracker-igl3
|
||||||
blocking:
|
blocking:
|
||||||
- nuzlocke-tracker-c6ly
|
- nuzlocke-tracker-c6ly
|
||||||
---
|
---
|
||||||
|
|
||||||
Create a JSON data file containing themed words for nickname generation.
|
Create a JSON data file containing themed words for nickname generation, stored in the backend alongside other seed data.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Each word entry has: `word` (string) and `categories` (string array)
|
- Store at `backend/src/app/seeds/data/name_dictionary.json`
|
||||||
|
- Category-keyed structure for fast lookup:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mythology": ["Apollo", "Athena", "Loki", ...],
|
||||||
|
"space": ["Apollo", "Nova", "Nebula", ...],
|
||||||
|
"food": ["Basil", "Sage", "Pepper", ...]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Words may appear in multiple categories
|
||||||
- Categories should include themes like: mythology, food, space, nature, warriors, music, literature, gems, ocean, weather, etc.
|
- Categories should include themes like: mythology, food, space, nature, warriors, music, literature, gems, ocean, weather, etc.
|
||||||
- Target 150-200 words per category
|
- Target 150-200 words per category
|
||||||
- Words can belong to multiple categories
|
|
||||||
- Words should be short, punchy, and suitable as Pokemon nicknames (ideally 1-2 words, max ~12 characters)
|
- Words should be short, punchy, and suitable as Pokemon nicknames (ideally 1-2 words, max ~12 characters)
|
||||||
|
- This file is NOT seeded into the database — it is read directly by the backend service at runtime
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
- [ ] Define the data schema / TypeScript type for dictionary entries
|
- [x] Create `backend/src/app/seeds/data/name_dictionary.json` with the category-keyed structure
|
||||||
- [ ] Create the JSON data file with initial categories
|
- [x] Populate each category with 150-200 words
|
||||||
- [ ] Populate each category with 150-200 words
|
- [x] Validate no duplicates exist within a single category
|
||||||
- [ ] Validate no duplicates exist within the file
|
- [x] Add a utility function to load the dictionary from disk (with caching)
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"""add naming_scheme to nuzlocke_runs
|
||||||
|
|
||||||
|
Revision ID: e5f6a7b8c9d1
|
||||||
|
Revises: d4e5f6a7b9c0
|
||||||
|
Create Date: 2026-02-11 12:00:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "e5f6a7b8c9d1"
|
||||||
|
down_revision: str | Sequence[str] | None = "d4e5f6a7b9c0"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
op.add_column(
|
||||||
|
"nuzlocke_runs",
|
||||||
|
sa.Column("naming_scheme", sa.String(50), nullable=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
op.drop_column("nuzlocke_runs", "naming_scheme")
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"""merge naming_scheme and genlocke_transfers
|
||||||
|
|
||||||
|
Revision ID: e5f70a1ca323
|
||||||
|
Revises: e5f6a7b8c9d1, e5f6a7b9c0d1
|
||||||
|
Create Date: 2026-02-11 21:49:29.942841
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'e5f70a1ca323'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = ('e5f6a7b8c9d1', 'e5f6a7b9c0d1')
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
pass
|
||||||
@@ -19,10 +19,41 @@ from app.schemas.run import (
|
|||||||
RunResponse,
|
RunResponse,
|
||||||
RunUpdate,
|
RunUpdate,
|
||||||
)
|
)
|
||||||
|
from app.services.naming import get_naming_categories, suggest_names
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/naming-categories", response_model=list[str])
|
||||||
|
async def list_naming_categories():
|
||||||
|
return get_naming_categories()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{run_id}/name-suggestions", response_model=list[str])
|
||||||
|
async def get_name_suggestions(
|
||||||
|
run_id: int,
|
||||||
|
count: int = 10,
|
||||||
|
session: AsyncSession = Depends(get_session),
|
||||||
|
):
|
||||||
|
run = await session.get(NuzlockeRun, run_id)
|
||||||
|
if run is None:
|
||||||
|
raise HTTPException(status_code=404, detail="Run not found")
|
||||||
|
|
||||||
|
if not run.naming_scheme:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Collect nicknames already used in this run
|
||||||
|
result = await session.execute(
|
||||||
|
select(Encounter.nickname).where(
|
||||||
|
Encounter.run_id == run_id,
|
||||||
|
Encounter.nickname.isnot(None),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
used_names = {row[0] for row in result}
|
||||||
|
|
||||||
|
return suggest_names(run.naming_scheme, used_names, count)
|
||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=RunResponse, status_code=201)
|
@router.post("", response_model=RunResponse, status_code=201)
|
||||||
async def create_run(data: RunCreate, session: AsyncSession = Depends(get_session)):
|
async def create_run(data: RunCreate, session: AsyncSession = Depends(get_session)):
|
||||||
# Validate game exists
|
# Validate game exists
|
||||||
@@ -35,6 +66,7 @@ async def create_run(data: RunCreate, session: AsyncSession = Depends(get_sessio
|
|||||||
name=data.name,
|
name=data.name,
|
||||||
status="active",
|
status="active",
|
||||||
rules=data.rules,
|
rules=data.rules,
|
||||||
|
naming_scheme=data.naming_scheme,
|
||||||
)
|
)
|
||||||
session.add(run)
|
session.add(run)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class NuzlockeRun(Base):
|
|||||||
)
|
)
|
||||||
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||||
hof_encounter_ids: Mapped[list[int] | None] = mapped_column(JSONB, default=None)
|
hof_encounter_ids: Mapped[list[int] | None] = mapped_column(JSONB, default=None)
|
||||||
|
naming_scheme: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||||
|
|
||||||
game: Mapped["Game"] = relationship(back_populates="runs")
|
game: Mapped["Game"] = relationship(back_populates="runs")
|
||||||
encounters: Mapped[list["Encounter"]] = relationship(back_populates="run")
|
encounters: Mapped[list["Encounter"]] = relationship(back_populates="run")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class RunCreate(CamelModel):
|
|||||||
game_id: int
|
game_id: int
|
||||||
name: str
|
name: str
|
||||||
rules: dict = {}
|
rules: dict = {}
|
||||||
|
naming_scheme: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class RunUpdate(CamelModel):
|
class RunUpdate(CamelModel):
|
||||||
@@ -16,6 +17,7 @@ class RunUpdate(CamelModel):
|
|||||||
status: str | None = None
|
status: str | None = None
|
||||||
rules: dict | None = None
|
rules: dict | None = None
|
||||||
hof_encounter_ids: list[int] | None = None
|
hof_encounter_ids: list[int] | None = None
|
||||||
|
naming_scheme: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class RunResponse(CamelModel):
|
class RunResponse(CamelModel):
|
||||||
@@ -25,6 +27,7 @@ class RunResponse(CamelModel):
|
|||||||
status: str
|
status: str
|
||||||
rules: dict
|
rules: dict
|
||||||
hof_encounter_ids: list[int] | None = None
|
hof_encounter_ids: list[int] | None = None
|
||||||
|
naming_scheme: str | None = None
|
||||||
started_at: datetime
|
started_at: datetime
|
||||||
completed_at: datetime | None
|
completed_at: datetime | None
|
||||||
|
|
||||||
|
|||||||
372
backend/src/app/seeds/data/name_dictionary.json
Normal file
372
backend/src/app/seeds/data/name_dictionary.json
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
{
|
||||||
|
"mythology": [
|
||||||
|
"Zeus", "Hera", "Apollo", "Athena", "Ares",
|
||||||
|
"Hermes", "Artemis", "Hades", "Poseidon", "Demeter",
|
||||||
|
"Hephaestus", "Aphrodite", "Dionysus", "Persephone", "Eros",
|
||||||
|
"Nike", "Helios", "Selene", "Nyx", "Chronos",
|
||||||
|
"Atlas", "Titan", "Hydra", "Cerberus", "Minotaur",
|
||||||
|
"Medusa", "Pegasus", "Phoenix", "Chimera", "Cyclops",
|
||||||
|
"Icarus", "Orpheus", "Perseus", "Achilles", "Odysseus",
|
||||||
|
"Ajax", "Hector", "Paris", "Helen", "Circe",
|
||||||
|
"Calypso", "Siren", "Muse", "Oracle", "Sphinx",
|
||||||
|
"Styx", "Elysium", "Olympus", "Tartarus", "Nemesis",
|
||||||
|
"Odin", "Thor", "Loki", "Freya", "Tyr",
|
||||||
|
"Baldur", "Fenrir", "Jormungandr", "Ragnarok", "Valkyrie",
|
||||||
|
"Mjolnir", "Yggdrasil", "Bifrost", "Valhalla", "Asgard",
|
||||||
|
"Midgard", "Norns", "Huginn", "Muninn", "Sleipnir",
|
||||||
|
"Ra", "Osiris", "Isis", "Anubis", "Thoth",
|
||||||
|
"Bastet", "Sekhmet", "Sobek", "Horus", "Seth",
|
||||||
|
"Nephthys", "Ammit", "Scarab", "Apophis", "Khepri",
|
||||||
|
"Amaterasu", "Susanoo", "Tsukuyomi", "Izanagi", "Izanami",
|
||||||
|
"Raijin", "Fujin", "Inari", "Kitsune", "Tengu",
|
||||||
|
"Oni", "Tanuki", "Yokai", "Kami", "Byakko",
|
||||||
|
"Vishnu", "Shiva", "Brahma", "Kali", "Ganesh",
|
||||||
|
"Lakshmi", "Hanuman", "Indra", "Agni", "Surya",
|
||||||
|
"Garuda", "Naga", "Deva", "Asura", "Dharma",
|
||||||
|
"Karma", "Mantra", "Lotus", "Chakra", "Maya",
|
||||||
|
"Brigid", "Morrigan", "Dagda", "Lugh", "Danu",
|
||||||
|
"Cernunnos", "Oberon", "Titania", "Merlin", "Avalon",
|
||||||
|
"Excalibur", "Druid", "Banshee", "Sidhe", "Fomorian",
|
||||||
|
"Leviathan", "Golem", "Djinn", "Ifrit", "Typhon",
|
||||||
|
"Griffin", "Kraken", "Wyrm", "Drake", "Wyvern",
|
||||||
|
"Basilisk", "Manticore", "Harpy", "Gorgon", "Triton",
|
||||||
|
"Prometheus", "Pandora", "Daphne", "Echo", "Narcissus",
|
||||||
|
"Midas", "Theseus", "Orion", "Castor", "Pollux",
|
||||||
|
"Zephyr", "Boreas", "Aether", "Chaos", "Gaia",
|
||||||
|
"Uranus", "Rhea", "Hyperion", "Themis", "Mnemosyne",
|
||||||
|
"Clio", "Fury", "Fate", "Moira", "Clotho"
|
||||||
|
],
|
||||||
|
"food": [
|
||||||
|
"Basil", "Sage", "Thyme", "Rosemary", "Saffron",
|
||||||
|
"Ginger", "Pepper", "Cinnamon", "Nutmeg", "Clove",
|
||||||
|
"Paprika", "Wasabi", "Cumin", "Fennel", "Dill",
|
||||||
|
"Anise", "Cardamom", "Turmeric", "Coriander", "Oregano",
|
||||||
|
"Tarragon", "Chive", "Parsley", "Mint", "Vanilla",
|
||||||
|
"Mango", "Kiwi", "Peach", "Cherry", "Plum",
|
||||||
|
"Fig", "Lemon", "Lime", "Melon", "Berry",
|
||||||
|
"Olive", "Cocoa", "Mocha", "Latte", "Chai",
|
||||||
|
"Matcha", "Espresso", "Cider", "Mead", "Porter",
|
||||||
|
"Stout", "Sake", "Roux", "Bisque", "Broth",
|
||||||
|
"Frappe", "Sorbet", "Gelato", "Truffle", "Praline",
|
||||||
|
"Nougat", "Toffee", "Caramel", "Fudge", "Brioche",
|
||||||
|
"Croissant", "Baguette", "Pretzel", "Dumpling", "Gyoza",
|
||||||
|
"Mochi", "Tempura", "Ramen", "Udon", "Soba",
|
||||||
|
"Pesto", "Salsa", "Kimchi", "Naan", "Tofu",
|
||||||
|
"Panko", "Miso", "Tahini", "Hummus", "Falafel",
|
||||||
|
"Burrito", "Taco", "Nacho", "Churro", "Crepe",
|
||||||
|
"Waffle", "Scone", "Crumpet", "Biscuit", "Cobbler",
|
||||||
|
"Crisp", "Tart", "Torte", "Mousse", "Souffle",
|
||||||
|
"Quiche", "Gratin", "Fondue", "Risotto", "Gnocchi",
|
||||||
|
"Ravioli", "Penne", "Orzo", "Pilaf", "Paella",
|
||||||
|
"Tagine", "Curry", "Satay", "Bulgogi", "Teriyaki",
|
||||||
|
"Ponzu", "Sriracha", "Harissa", "Chutney", "Relish",
|
||||||
|
"Pickle", "Caper", "Anchovy", "Prawn", "Scallop",
|
||||||
|
"Brisket", "Bison", "Quinoa", "Barley", "Millet",
|
||||||
|
"Almond", "Pecan", "Walnut", "Cashew", "Pistachio",
|
||||||
|
"Hazel", "Acai", "Guava", "Lychee", "Papaya",
|
||||||
|
"Tamarind", "Kumquat", "Clementine", "Nectar", "Honey",
|
||||||
|
"Maple", "Agave", "Molasses", "Butter", "Cream",
|
||||||
|
"Brie", "Gouda", "Cheddar", "Ricotta", "Paneer",
|
||||||
|
"Toasted", "Smoked", "Braised", "Seared", "Glazed",
|
||||||
|
"Roasted", "Grilled", "Poached", "Zest", "Tang",
|
||||||
|
"Crunch", "Morsel", "Nibble", "Drizzle", "Dash",
|
||||||
|
"Pinch", "Sprig", "Garnish", "Feast", "Brunch",
|
||||||
|
"Savory", "Umami", "Tangy", "Citrus", "Jambon"
|
||||||
|
],
|
||||||
|
"space": [
|
||||||
|
"Nova", "Nebula", "Quasar", "Pulsar", "Cosmos",
|
||||||
|
"Comet", "Meteor", "Astral", "Stellar", "Solar",
|
||||||
|
"Lunar", "Orbit", "Eclipse", "Galaxy", "Vortex",
|
||||||
|
"Zenith", "Nadir", "Apogee", "Perigee", "Epoch",
|
||||||
|
"Apollo", "Gemini", "Mercury", "Venus", "Mars",
|
||||||
|
"Jupiter", "Saturn", "Neptune", "Uranus", "Pluto",
|
||||||
|
"Titan", "Europa", "Io", "Callisto", "Ganymede",
|
||||||
|
"Triton", "Charon", "Phobos", "Deimos", "Ceres",
|
||||||
|
"Juno", "Vesta", "Pallas", "Eris", "Sedna",
|
||||||
|
"Sirius", "Rigel", "Vega", "Altair", "Deneb",
|
||||||
|
"Polaris", "Canopus", "Betelgeuse", "Antares", "Aldebaran",
|
||||||
|
"Spica", "Arcturus", "Capella", "Procyon", "Achernar",
|
||||||
|
"Castor", "Pollux", "Regulus", "Fomalhaut", "Mira",
|
||||||
|
"Bellatrix", "Mintaka", "Alnilam", "Alnitak", "Saiph",
|
||||||
|
"Orion", "Lyra", "Draco", "Hydra", "Corvus",
|
||||||
|
"Aquila", "Cygnus", "Phoenix", "Andromeda", "Cassiopeia",
|
||||||
|
"Perseus", "Centauri", "Pegasus", "Scorpius", "Leo",
|
||||||
|
"Aries", "Virgo", "Libra", "Pisces", "Taurus",
|
||||||
|
"Photon", "Neutron", "Proton", "Plasma", "Flux",
|
||||||
|
"Prism", "Spectrum", "Horizon", "Corona", "Halo",
|
||||||
|
"Aurora", "Solstice", "Equinox", "Parallax", "Parsec",
|
||||||
|
"Lightyear", "Warp", "Void", "Rift", "Abyss",
|
||||||
|
"Singularity", "Binary", "Cluster", "Remnant", "Dwarf",
|
||||||
|
"Giant", "Supernova", "Stardust", "Starfall", "Skyfire",
|
||||||
|
"Sunspot", "Flare", "Radiance", "Luminous", "Beacon",
|
||||||
|
"Voyager", "Pioneer", "Sputnik", "Hubble", "Kepler",
|
||||||
|
"Galileo", "Cassini", "Rosetta", "Artemis", "Chandra",
|
||||||
|
"Solaris", "Nebular", "Asteria", "Celestia", "Astra",
|
||||||
|
"Solis", "Lumen", "Ignis", "Ember", "Blaze",
|
||||||
|
"Spark", "Glimmer", "Shimmer", "Twilight", "Dusk",
|
||||||
|
"Dawn", "Crescent", "Waning", "Waxing", "Umbra",
|
||||||
|
"Penumbra", "Perihelion", "Aphelion", "Azimuth", "Meridian",
|
||||||
|
"Helix", "Spiral", "Ring", "Arc", "Bolt",
|
||||||
|
"Surge", "Pulse", "Wave", "Drift", "Phase",
|
||||||
|
"Xenon", "Neon", "Argon", "Helium", "Lithium"
|
||||||
|
],
|
||||||
|
"nature": [
|
||||||
|
"Willow", "Cedar", "Birch", "Aspen", "Maple",
|
||||||
|
"Rowan", "Alder", "Elm", "Oak", "Pine",
|
||||||
|
"Spruce", "Juniper", "Laurel", "Ivy", "Fern",
|
||||||
|
"Moss", "Lichen", "Thistle", "Clover", "Briar",
|
||||||
|
"Thorn", "Reed", "Sage", "Basil", "Lotus",
|
||||||
|
"Orchid", "Dahlia", "Iris", "Lily", "Violet",
|
||||||
|
"Jasmine", "Heather", "Daisy", "Poppy", "Aster",
|
||||||
|
"Azalea", "Magnolia", "Wisteria", "Camellia", "Marigold",
|
||||||
|
"Hawk", "Falcon", "Eagle", "Osprey", "Heron",
|
||||||
|
"Crane", "Wren", "Finch", "Lark", "Robin",
|
||||||
|
"Sparrow", "Raven", "Crow", "Jay", "Dove",
|
||||||
|
"Swift", "Owl", "Kestrel", "Harrier", "Condor",
|
||||||
|
"Wolf", "Fox", "Bear", "Lynx", "Puma",
|
||||||
|
"Jaguar", "Panther", "Stag", "Elk", "Moose",
|
||||||
|
"Bison", "Badger", "Otter", "Mink", "Ermine",
|
||||||
|
"Viper", "Cobra", "Adder", "Gecko", "Newt",
|
||||||
|
"Coral", "Pearl", "Amber", "Flint", "Slate",
|
||||||
|
"Granite", "Basalt", "Quartz", "Mica", "Clay",
|
||||||
|
"Dune", "Mesa", "Butte", "Gorge", "Canyon",
|
||||||
|
"Ridge", "Summit", "Crest", "Peak", "Cliff",
|
||||||
|
"Bluff", "Dale", "Glen", "Vale", "Hollow",
|
||||||
|
"Meadow", "Prairie", "Tundra", "Taiga", "Savanna",
|
||||||
|
"Grove", "Copse", "Thicket", "Canopy", "Glade",
|
||||||
|
"Brook", "Creek", "Rivulet", "Spring", "Rapids",
|
||||||
|
"Cascade", "Torrent", "Eddy", "Delta", "Marsh",
|
||||||
|
"Bog", "Fen", "Mire", "Lagoon", "Oasis",
|
||||||
|
"Frost", "Dew", "Mist", "Haze", "Fog",
|
||||||
|
"Breeze", "Gust", "Gale", "Squall", "Tempest",
|
||||||
|
"Aurora", "Solstice", "Equinox", "Eclipse", "Dawn",
|
||||||
|
"Dusk", "Twilight", "Ember", "Spark", "Flame",
|
||||||
|
"Petal", "Bloom", "Blossom", "Sprout", "Seedling",
|
||||||
|
"Sapling", "Root", "Bark", "Sap", "Nectar",
|
||||||
|
"Pollen", "Spore", "Frond", "Tendril", "Vine",
|
||||||
|
"Bramble", "Nettle", "Sorrel", "Yarrow", "Tansy",
|
||||||
|
"Mushroom", "Fungi", "Truffle", "Morel", "Chanterelle"
|
||||||
|
],
|
||||||
|
"warriors": [
|
||||||
|
"Spartan", "Samurai", "Viking", "Shogun", "Ronin",
|
||||||
|
"Gladiator", "Centurion", "Legionary", "Tribune", "Praetor",
|
||||||
|
"Knight", "Paladin", "Templar", "Crusader", "Sentinel",
|
||||||
|
"Warden", "Marshal", "Captain", "General", "Admiral",
|
||||||
|
"Vanguard", "Raider", "Berserker", "Valkyrie", "Einherjar",
|
||||||
|
"Ninja", "Shinobi", "Kenshi", "Bushido", "Daimyo",
|
||||||
|
"Mongol", "Khan", "Sultan", "Shah", "Pharaoh",
|
||||||
|
"Legatus", "Consul", "Imperator", "Caesar", "Augustus",
|
||||||
|
"Hussar", "Cossack", "Janissary", "Mamluk", "Saracen",
|
||||||
|
"Apache", "Comanche", "Mohawk", "Zulu", "Aztec",
|
||||||
|
"Mayan", "Incan", "Sparrow", "Hawk", "Falcon",
|
||||||
|
"Talon", "Fang", "Claw", "Tusk", "Horn",
|
||||||
|
"Blade", "Saber", "Rapier", "Cutlass", "Katana",
|
||||||
|
"Claymore", "Scimitar", "Halberd", "Glaive", "Pike",
|
||||||
|
"Lance", "Javelin", "Spear", "Trident", "Mace",
|
||||||
|
"Flail", "Hammer", "Maul", "Axe", "Tomahawk",
|
||||||
|
"Dagger", "Stiletto", "Dirk", "Kunai", "Shuriken",
|
||||||
|
"Bow", "Longbow", "Crossbow", "Bolt", "Arrow",
|
||||||
|
"Quiver", "Shield", "Buckler", "Aegis", "Bulwark",
|
||||||
|
"Rampart", "Bastion", "Citadel", "Fortress", "Garrison",
|
||||||
|
"Helm", "Visor", "Gauntlet", "Bracer", "Greave",
|
||||||
|
"Cuirass", "Brigand", "Chainmail", "Plate", "Scale",
|
||||||
|
"Banner", "Standard", "Crest", "Sigil", "Herald",
|
||||||
|
"Siege", "Assault", "Charge", "Rally", "Flanker",
|
||||||
|
"Scout", "Ranger", "Sniper", "Archer", "Lancer",
|
||||||
|
"Dragoon", "Cavalry", "Infantry", "Legion", "Phalanx",
|
||||||
|
"Cohort", "Regiment", "Battalion", "Brigade", "Platoon",
|
||||||
|
"Striker", "Brawler", "Duelist", "Champion", "Conqueror",
|
||||||
|
"Warlord", "Overlord", "Chieftain", "Thane", "Jarl",
|
||||||
|
"Reaver", "Marauder", "Pillager", "Ravager", "Slayer",
|
||||||
|
"Titan", "Colossus", "Juggernaut", "Goliath", "Ajax",
|
||||||
|
"Hector", "Achilles", "Leonidas", "Hannibal", "Attila",
|
||||||
|
"Genghis", "Alexander", "Boudicca", "Shaka", "Saladin",
|
||||||
|
"Agincourt", "Thermopylae", "Troy", "Carthage", "Masada",
|
||||||
|
"Valor", "Honor", "Glory", "Fury", "Wrath"
|
||||||
|
],
|
||||||
|
"music": [
|
||||||
|
"Tempo", "Rhythm", "Melody", "Harmony", "Chord",
|
||||||
|
"Riff", "Solo", "Verse", "Chorus", "Bridge",
|
||||||
|
"Cadence", "Crescendo", "Forte", "Piano", "Allegro",
|
||||||
|
"Adagio", "Vivace", "Presto", "Staccato", "Legato",
|
||||||
|
"Vibrato", "Tremolo", "Glissando", "Arpeggio", "Pizzicato",
|
||||||
|
"Fermata", "Sforzando", "Rubato", "Ostinato", "Coda",
|
||||||
|
"Treble", "Bass", "Alto", "Tenor", "Soprano",
|
||||||
|
"Baritone", "Falsetto", "Contralto", "Aria", "Duet",
|
||||||
|
"Sonata", "Fugue", "Prelude", "Nocturne", "Etude",
|
||||||
|
"Opus", "Requiem", "Ballad", "Anthem", "Hymn",
|
||||||
|
"Lyric", "Serenade", "Caprice", "Fantasia", "Overture",
|
||||||
|
"Minuet", "Waltz", "Bolero", "Tango", "Mambo",
|
||||||
|
"Samba", "Rumba", "Salsa", "Swing", "Bebop",
|
||||||
|
"Funk", "Soul", "Blues", "Jazz", "Gospel",
|
||||||
|
"Grunge", "Punk", "Metal", "Rock", "Indie",
|
||||||
|
"Techno", "Trance", "House", "Dubstep", "Ambient",
|
||||||
|
"Synth", "Drone", "Fuzz", "Wah", "Reverb",
|
||||||
|
"Echo", "Loop", "Beat", "Drop", "Groove",
|
||||||
|
"Fiddle", "Banjo", "Lute", "Harp", "Lyre",
|
||||||
|
"Viola", "Cello", "Oboe", "Flute", "Fife",
|
||||||
|
"Bugle", "Cornet", "Trumpet", "Trombone", "Tuba",
|
||||||
|
"Sax", "Clarinet", "Bassoon", "Piccolo", "Organ",
|
||||||
|
"Cymbal", "Gong", "Chime", "Bell", "Snare",
|
||||||
|
"Tambourine", "Bongo", "Conga", "Djembe", "Tabla",
|
||||||
|
"Sitar", "Shamisen", "Koto", "Guzheng", "Erhu",
|
||||||
|
"Raga", "Mantra", "Chant", "Dirge", "Elegy",
|
||||||
|
"Refrain", "Motif", "Theme", "Encore", "Finale",
|
||||||
|
"Pitch", "Tone", "Note", "Scale", "Octave",
|
||||||
|
"Sharp", "Flat", "Major", "Minor", "Modal",
|
||||||
|
"Acoustic", "Electric", "Muted", "Resonant", "Harmonic",
|
||||||
|
"Sonic", "Stereo", "Mono", "Vinyl", "Track",
|
||||||
|
"Album", "Mixtape", "Jam", "Gig", "Venue",
|
||||||
|
"Stage", "Amp", "Speaker", "Mic", "Reed",
|
||||||
|
"Bow", "Pick", "Slide", "Hammer", "Mallet",
|
||||||
|
"Timpani", "Marimba", "Celesta", "Dulcimer", "Zither"
|
||||||
|
],
|
||||||
|
"literature": [
|
||||||
|
"Prose", "Verse", "Stanza", "Sonnet", "Haiku",
|
||||||
|
"Limerick", "Ballad", "Ode", "Epic", "Saga",
|
||||||
|
"Fable", "Myth", "Legend", "Tale", "Lore",
|
||||||
|
"Tome", "Codex", "Scroll", "Grimoire", "Almanac",
|
||||||
|
"Quill", "Ink", "Vellum", "Parchment", "Folio",
|
||||||
|
"Preface", "Prologue", "Epilogue", "Chapter", "Canto",
|
||||||
|
"Epitaph", "Elegy", "Dirge", "Psalm", "Hymn",
|
||||||
|
"Allegory", "Parable", "Satire", "Parody", "Irony",
|
||||||
|
"Motif", "Theme", "Trope", "Genre", "Canon",
|
||||||
|
"Muse", "Oracle", "Bard", "Scribe", "Sage",
|
||||||
|
"Hamlet", "Othello", "Prospero", "Oberon", "Titania",
|
||||||
|
"Puck", "Ariel", "Caliban", "Portia", "Cordelia",
|
||||||
|
"Macbeth", "Banquo", "Lear", "Juliet", "Romeo",
|
||||||
|
"Brutus", "Cassius", "Shylock", "Mercutio", "Falstaff",
|
||||||
|
"Gatsby", "Atticus", "Scout", "Holden", "Pip",
|
||||||
|
"Huck", "Ishmael", "Ahab", "Queequeg", "Dorian",
|
||||||
|
"Darcy", "Heathcliff", "Rochester", "Eyre", "Estella",
|
||||||
|
"Cosette", "Valjean", "Quixote", "Sancho", "Dulcinea",
|
||||||
|
"Odysseus", "Aeneas", "Beowulf", "Gilgamesh", "Roland",
|
||||||
|
"Percival", "Galahad", "Lancelot", "Gawain", "Merlin",
|
||||||
|
"Scheherazade", "Sinbad", "Aladdin", "Mowgli", "Baloo",
|
||||||
|
"Nemo", "Moreau", "Jekyll", "Hyde", "Dracula",
|
||||||
|
"Renfield", "Harker", "Shelley", "Bronte", "Austen",
|
||||||
|
"Poe", "Frost", "Wilde", "Twain", "Dickens",
|
||||||
|
"Tolstoy", "Kafka", "Orwell", "Hemingway", "Faulkner",
|
||||||
|
"Woolf", "Plath", "Dumas", "Hugo", "Verne",
|
||||||
|
"Cipher", "Riddle", "Enigma", "Rune", "Glyph",
|
||||||
|
"Sigil", "Symbol", "Token", "Emblem", "Crest",
|
||||||
|
"Aria", "Lyric", "Rhyme", "Meter", "Cadence",
|
||||||
|
"Pathos", "Ethos", "Logos", "Hubris", "Nemesis",
|
||||||
|
"Catharsis", "Climax", "Zenith", "Nadir", "Pivot",
|
||||||
|
"Twist", "Reverie", "Whimsy", "Satyr", "Nymph",
|
||||||
|
"Sprite", "Wraith", "Phantom", "Specter", "Shade",
|
||||||
|
"Vestige", "Relic", "Echo", "Mirage", "Revenant",
|
||||||
|
"Requiem", "Opus", "Magnum", "Novella", "Lexicon"
|
||||||
|
],
|
||||||
|
"gems": [
|
||||||
|
"Ruby", "Sapphire", "Emerald", "Diamond", "Topaz",
|
||||||
|
"Amethyst", "Opal", "Pearl", "Garnet", "Peridot",
|
||||||
|
"Onyx", "Jade", "Agate", "Jasper", "Quartz",
|
||||||
|
"Citrine", "Zircon", "Beryl", "Spinel", "Tanzanite",
|
||||||
|
"Tourmaline", "Morganite", "Kunzite", "Iolite", "Larimar",
|
||||||
|
"Sunstone", "Moonstone", "Bloodstone", "Lodestone", "Heliodor",
|
||||||
|
"Chrysolite", "Alexandrite", "Andalusite", "Labradorite", "Amazonite",
|
||||||
|
"Rhodonite", "Sodalite", "Fluorite", "Calcite", "Pyrite",
|
||||||
|
"Galena", "Bauxite", "Magnetite", "Hematite", "Malachite",
|
||||||
|
"Azurite", "Turquoise", "Lapis", "Lazuli", "Carnelian",
|
||||||
|
"Sardonyx", "Chalcedony", "Aventurine", "Obsidian", "Granite",
|
||||||
|
"Marble", "Basalt", "Pumice", "Slate", "Flint",
|
||||||
|
"Amber", "Coral", "Ivory", "Jet", "Nacre",
|
||||||
|
"Gold", "Silver", "Platinum", "Copper", "Bronze",
|
||||||
|
"Iron", "Steel", "Titanium", "Cobalt", "Nickel",
|
||||||
|
"Chrome", "Zinc", "Tin", "Lead", "Brass",
|
||||||
|
"Pewter", "Electrum", "Palladium", "Rhodium", "Iridium",
|
||||||
|
"Osmium", "Ruthenium", "Tungsten", "Bismuth", "Lithium",
|
||||||
|
"Cesium", "Radium", "Gallium", "Indium", "Thallium",
|
||||||
|
"Strontium", "Barium", "Cadmium", "Antimony", "Tellurium",
|
||||||
|
"Selenite", "Celestite", "Sphalerite", "Kyanite", "Prehnite",
|
||||||
|
"Apatite", "Danburite", "Howlite", "Lepidolite", "Sugilite",
|
||||||
|
"Charoite", "Seraphinite", "Moldavite", "Tektite", "Shungite",
|
||||||
|
"Crystal", "Prism", "Facet", "Carat", "Luster",
|
||||||
|
"Brilliance", "Clarity", "Sparkle", "Shimmer", "Glimmer",
|
||||||
|
"Gleam", "Sheen", "Polish", "Ore", "Vein",
|
||||||
|
"Nugget", "Ingot", "Alloy", "Forge", "Smelt",
|
||||||
|
"Temper", "Anneal", "Refine", "Crucible", "Slag",
|
||||||
|
"Geode", "Druzy", "Cabochon", "Cameo", "Inlay",
|
||||||
|
"Filigree", "Gilt", "Patina", "Verdigris", "Tarnish",
|
||||||
|
"Shard", "Sliver", "Splinter", "Fragment", "Chip",
|
||||||
|
"Matrix", "Stratum", "Seam", "Deposit", "Quarry",
|
||||||
|
"Cavern", "Grotto", "Trove", "Cache", "Vault",
|
||||||
|
"Crown", "Diadem", "Tiara", "Circlet", "Coronet",
|
||||||
|
"Amulet", "Talisman", "Pendant", "Brooch", "Signet"
|
||||||
|
],
|
||||||
|
"ocean": [
|
||||||
|
"Tide", "Wave", "Surf", "Swell", "Crest",
|
||||||
|
"Trough", "Breaker", "Ripple", "Current", "Drift",
|
||||||
|
"Riptide", "Undertow", "Maelstrom", "Whirlpool", "Eddy",
|
||||||
|
"Tsunami", "Surge", "Deluge", "Torrent", "Cascade",
|
||||||
|
"Coral", "Reef", "Atoll", "Lagoon", "Shoal",
|
||||||
|
"Sandbar", "Islet", "Archipelago", "Fjord", "Inlet",
|
||||||
|
"Cove", "Bay", "Gulf", "Sound", "Strait",
|
||||||
|
"Channel", "Harbor", "Marina", "Pier", "Wharf",
|
||||||
|
"Dock", "Jetty", "Quay", "Beacon", "Lighthouse",
|
||||||
|
"Anchor", "Helm", "Rudder", "Keel", "Hull",
|
||||||
|
"Bow", "Stern", "Port", "Mast", "Sail",
|
||||||
|
"Rigging", "Galley", "Frigate", "Sloop", "Schooner",
|
||||||
|
"Clipper", "Brigantine", "Corsair", "Buccaneer", "Mariner",
|
||||||
|
"Skipper", "Bosun", "Navigator", "Helmsman", "Admiral",
|
||||||
|
"Commodore", "Captain", "Privateer", "Seafarer", "Voyager",
|
||||||
|
"Kraken", "Leviathan", "Siren", "Selkie", "Nereid",
|
||||||
|
"Triton", "Poseidon", "Neptune", "Calypso", "Charybdis",
|
||||||
|
"Scylla", "Davy", "Locker", "Nautilus", "Nemo",
|
||||||
|
"Whale", "Orca", "Dolphin", "Narwhal", "Porpoise",
|
||||||
|
"Shark", "Barracuda", "Marlin", "Sailfish", "Swordfish",
|
||||||
|
"Manta", "Stingray", "Moray", "Grouper", "Snapper",
|
||||||
|
"Tuna", "Mackerel", "Herring", "Sardine", "Anchovy",
|
||||||
|
"Squid", "Octopus", "Ammonite", "Cuttlefish", "Jellyfish",
|
||||||
|
"Anemone", "Urchin", "Starfish", "Seahorse", "Conch",
|
||||||
|
"Abalone", "Clam", "Oyster", "Mussel", "Scallop",
|
||||||
|
"Lobster", "Crab", "Shrimp", "Barnacle", "Kelp",
|
||||||
|
"Seaweed", "Plankton", "Algae", "Brine", "Foam",
|
||||||
|
"Spray", "Spume", "Mist", "Salt", "Pearl",
|
||||||
|
"Trench", "Abyss", "Fathom", "Depth", "Benthic",
|
||||||
|
"Pelagic", "Abyssal", "Hadal", "Tidal", "Littoral",
|
||||||
|
"Estuary", "Delta", "Mangrove", "Marsh", "Wetland",
|
||||||
|
"Driftwood", "Flotsam", "Jetsam", "Wreck", "Salvage",
|
||||||
|
"Bounty", "Plunder", "Treasure", "Doubloon", "Cutlass",
|
||||||
|
"Cannon", "Broadside", "Mainsail", "Spinnaker", "Jib",
|
||||||
|
"Bowsprit", "Gangway", "Porthole", "Bulkhead", "Bilge"
|
||||||
|
],
|
||||||
|
"weather": [
|
||||||
|
"Storm", "Tempest", "Squall", "Gale", "Gust",
|
||||||
|
"Breeze", "Zephyr", "Draft", "Whirlwind", "Tornado",
|
||||||
|
"Cyclone", "Typhoon", "Hurricane", "Monsoon", "Sirocco",
|
||||||
|
"Mistral", "Chinook", "Foehn", "Harmattan", "Tramontane",
|
||||||
|
"Thunder", "Lightning", "Bolt", "Flash", "Strike",
|
||||||
|
"Rumble", "Crack", "Boom", "Clap", "Roar",
|
||||||
|
"Rain", "Drizzle", "Shower", "Downpour", "Deluge",
|
||||||
|
"Torrent", "Cloudburst", "Sprinkle", "Mizzle", "Sleet",
|
||||||
|
"Snow", "Blizzard", "Flurry", "Frost", "Ice",
|
||||||
|
"Hail", "Rime", "Hoarfrost", "Glaze", "Verglas",
|
||||||
|
"Fog", "Mist", "Haze", "Smog", "Murk",
|
||||||
|
"Overcast", "Gloom", "Shadow", "Shade", "Dusk",
|
||||||
|
"Cloud", "Cumulus", "Stratus", "Cirrus", "Nimbus",
|
||||||
|
"Cumulonimbus", "Alto", "Wisp", "Veil", "Canopy",
|
||||||
|
"Anvil", "Billow", "Plume", "Column", "Front",
|
||||||
|
"Ridge", "Trough", "Vortex", "Eye", "Funnel",
|
||||||
|
"Twister", "Spout", "Surge", "Flood", "Flicker",
|
||||||
|
"Cascade", "Gush", "Swell", "Crest", "Tide",
|
||||||
|
"Drought", "Arid", "Parch", "Scorch", "Blaze",
|
||||||
|
"Ember", "Kindle", "Sear", "Wither", "Dustbowl",
|
||||||
|
"Heat", "Warmth", "Balmy", "Swelter", "Muggy",
|
||||||
|
"Humid", "Sultry", "Tropic", "Equator", "Solstice",
|
||||||
|
"Equinox", "Aurora", "Borealis", "Mirage", "Shimmer",
|
||||||
|
"Glint", "Gleam", "Radiance", "Glow", "Halo",
|
||||||
|
"Rainbow", "Prism", "Spectrum", "Arc", "Bow",
|
||||||
|
"Dew", "Droplet", "Puddle", "Rivulet", "Stream",
|
||||||
|
"Thaw", "Melt", "Freeze", "Chill", "Nip",
|
||||||
|
"Bite", "Brisk", "Crisp", "Cool", "Cold",
|
||||||
|
"Frigid", "Glacial", "Polar", "Arctic", "Tundra",
|
||||||
|
"Permafrost", "Icecap", "Avalanche", "Snowdrift", "Whiteout",
|
||||||
|
"Barometer", "Pressure", "Isobar", "Celsius", "Kelvin",
|
||||||
|
"Climate", "Season", "Forecast", "Outlook", "Pattern",
|
||||||
|
"Windchill", "Dewpoint", "Updraft", "Downdraft", "Jetstream",
|
||||||
|
"Tradewind", "Doldrums", "Lull", "Calm", "Serene",
|
||||||
|
"Clear", "Sunny", "Bright", "Radiant", "Blazing"
|
||||||
|
]
|
||||||
|
}
|
||||||
45
backend/src/app/services/naming.py
Normal file
45
backend/src/app/services/naming.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import json
|
||||||
|
import random
|
||||||
|
from functools import lru_cache
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DICTIONARY_PATH = Path(__file__).resolve().parents[1] / "seeds" / "data" / "name_dictionary.json"
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def _load_dictionary() -> dict[str, list[str]]:
|
||||||
|
if not DICTIONARY_PATH.exists():
|
||||||
|
return {}
|
||||||
|
with open(DICTIONARY_PATH) as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def get_naming_categories() -> list[str]:
|
||||||
|
"""Return sorted list of available naming category names."""
|
||||||
|
return sorted(_load_dictionary().keys())
|
||||||
|
|
||||||
|
|
||||||
|
def get_words_for_category(category: str) -> list[str]:
|
||||||
|
"""Return the word list for a category, or empty list if not found."""
|
||||||
|
return _load_dictionary().get(category, [])
|
||||||
|
|
||||||
|
|
||||||
|
def suggest_names(
|
||||||
|
category: str,
|
||||||
|
used_names: set[str],
|
||||||
|
count: int = 10,
|
||||||
|
) -> list[str]:
|
||||||
|
"""Pick random name suggestions from a category, excluding used names.
|
||||||
|
|
||||||
|
Returns up to `count` names. If the category is nearly exhausted,
|
||||||
|
returns fewer.
|
||||||
|
"""
|
||||||
|
words = get_words_for_category(category)
|
||||||
|
if not words:
|
||||||
|
return []
|
||||||
|
|
||||||
|
available = [w for w in words if w not in used_names]
|
||||||
|
if not available:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return random.sample(available, min(count, len(available)))
|
||||||
84
backend/tests/test_naming.py
Normal file
84
backend/tests/test_naming.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.services.naming import (
|
||||||
|
get_naming_categories,
|
||||||
|
get_words_for_category,
|
||||||
|
suggest_names,
|
||||||
|
)
|
||||||
|
|
||||||
|
MOCK_DICTIONARY = {
|
||||||
|
"mythology": ["Apollo", "Athena", "Loki", "Thor", "Zeus"],
|
||||||
|
"food": ["Basil", "Sage", "Pepper", "Saffron", "Mango"],
|
||||||
|
"space": ["Apollo", "Nova", "Nebula", "Comet", "Vega"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _mock_dictionary():
|
||||||
|
with patch("app.services.naming._load_dictionary", return_value=MOCK_DICTIONARY):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetNamingCategories:
|
||||||
|
def test_returns_sorted_categories(self):
|
||||||
|
result = get_naming_categories()
|
||||||
|
assert result == ["food", "mythology", "space"]
|
||||||
|
|
||||||
|
def test_returns_empty_for_empty_dictionary(self):
|
||||||
|
with patch("app.services.naming._load_dictionary", return_value={}):
|
||||||
|
assert get_naming_categories() == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetWordsForCategory:
|
||||||
|
def test_returns_words_for_valid_category(self):
|
||||||
|
result = get_words_for_category("mythology")
|
||||||
|
assert result == ["Apollo", "Athena", "Loki", "Thor", "Zeus"]
|
||||||
|
|
||||||
|
def test_returns_empty_for_unknown_category(self):
|
||||||
|
assert get_words_for_category("nonexistent") == []
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuggestNames:
|
||||||
|
def test_returns_requested_count(self):
|
||||||
|
result = suggest_names("mythology", set(), count=3)
|
||||||
|
assert len(result) == 3
|
||||||
|
assert all(name in MOCK_DICTIONARY["mythology"] for name in result)
|
||||||
|
|
||||||
|
def test_excludes_used_names(self):
|
||||||
|
used = {"Apollo", "Athena", "Loki"}
|
||||||
|
result = suggest_names("mythology", used, count=10)
|
||||||
|
assert set(result) <= {"Thor", "Zeus"}
|
||||||
|
assert not set(result) & used
|
||||||
|
|
||||||
|
def test_returns_fewer_when_category_nearly_exhausted(self):
|
||||||
|
used = {"Apollo", "Athena", "Loki", "Thor"}
|
||||||
|
result = suggest_names("mythology", used, count=10)
|
||||||
|
assert result == ["Zeus"]
|
||||||
|
|
||||||
|
def test_returns_empty_when_category_fully_exhausted(self):
|
||||||
|
used = {"Apollo", "Athena", "Loki", "Thor", "Zeus"}
|
||||||
|
result = suggest_names("mythology", used, count=10)
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
def test_returns_empty_for_unknown_category(self):
|
||||||
|
result = suggest_names("nonexistent", set(), count=10)
|
||||||
|
assert result == []
|
||||||
|
|
||||||
|
def test_no_duplicates_in_suggestions(self):
|
||||||
|
result = suggest_names("mythology", set(), count=5)
|
||||||
|
assert len(result) == len(set(result))
|
||||||
|
|
||||||
|
def test_default_count_is_ten(self):
|
||||||
|
# food has 5 words, so we should get all 5
|
||||||
|
result = suggest_names("food", set())
|
||||||
|
assert len(result) == 5
|
||||||
|
|
||||||
|
def test_cross_category_names_handled_independently(self):
|
||||||
|
# "Apollo" used in mythology shouldn't affect space
|
||||||
|
used = {"Apollo"}
|
||||||
|
mythology_result = suggest_names("mythology", used, count=10)
|
||||||
|
space_result = suggest_names("space", used, count=10)
|
||||||
|
assert "Apollo" not in mythology_result
|
||||||
|
assert "Apollo" not in space_result
|
||||||
@@ -28,3 +28,11 @@ export function updateRun(
|
|||||||
export function deleteRun(id: number): Promise<void> {
|
export function deleteRun(id: number): Promise<void> {
|
||||||
return api.del(`/runs/${id}`)
|
return api.del(`/runs/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNamingCategories(): Promise<string[]> {
|
||||||
|
return api.get('/runs/naming-categories')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNameSuggestions(runId: number, count = 10): Promise<string[]> {
|
||||||
|
return api.get(`/runs/${runId}/name-suggestions?count=${count}`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react'
|
import { useState, useEffect, useMemo } from 'react'
|
||||||
import { useRoutePokemon } from '../hooks/useGames'
|
import { useRoutePokemon } from '../hooks/useGames'
|
||||||
|
import { useNameSuggestions } from '../hooks/useRuns'
|
||||||
import {
|
import {
|
||||||
EncounterMethodBadge,
|
EncounterMethodBadge,
|
||||||
getMethodLabel,
|
getMethodLabel,
|
||||||
@@ -15,6 +16,8 @@ import type {
|
|||||||
interface EncounterModalProps {
|
interface EncounterModalProps {
|
||||||
route: Route
|
route: Route
|
||||||
gameId: number
|
gameId: number
|
||||||
|
runId: number
|
||||||
|
namingScheme?: string | null
|
||||||
existing?: EncounterDetail
|
existing?: EncounterDetail
|
||||||
dupedPokemonIds?: Set<number>
|
dupedPokemonIds?: Set<number>
|
||||||
retiredPokemonIds?: Set<number>
|
retiredPokemonIds?: Set<number>
|
||||||
@@ -92,6 +95,8 @@ function pickRandomPokemon(
|
|||||||
export function EncounterModal({
|
export function EncounterModal({
|
||||||
route,
|
route,
|
||||||
gameId,
|
gameId,
|
||||||
|
runId,
|
||||||
|
namingScheme,
|
||||||
existing,
|
existing,
|
||||||
dupedPokemonIds,
|
dupedPokemonIds,
|
||||||
retiredPokemonIds,
|
retiredPokemonIds,
|
||||||
@@ -120,6 +125,10 @@ export function EncounterModal({
|
|||||||
|
|
||||||
const isEditing = !!existing
|
const isEditing = !!existing
|
||||||
|
|
||||||
|
const showSuggestions = !!namingScheme && status === 'caught' && !isEditing
|
||||||
|
const { data: suggestions, refetch: regenerate, isFetching: loadingSuggestions } =
|
||||||
|
useNameSuggestions(showSuggestions ? runId : null)
|
||||||
|
|
||||||
// Pre-select pokemon when editing
|
// Pre-select pokemon when editing
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (existing && routePokemon) {
|
if (existing && routePokemon) {
|
||||||
@@ -380,6 +389,39 @@ export function EncounterModal({
|
|||||||
placeholder="Give it a name..."
|
placeholder="Give it a name..."
|
||||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
{showSuggestions && suggestions && suggestions.length > 0 && (
|
||||||
|
<div className="mt-2">
|
||||||
|
<div className="flex items-center justify-between mb-1.5">
|
||||||
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Suggestions ({namingScheme})
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => regenerate()}
|
||||||
|
disabled={loadingSuggestions}
|
||||||
|
className="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 disabled:opacity-50 transition-colors"
|
||||||
|
>
|
||||||
|
{loadingSuggestions ? 'Loading...' : 'Regenerate'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{suggestions.map((name) => (
|
||||||
|
<button
|
||||||
|
key={name}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setNickname(name)}
|
||||||
|
className={`px-2.5 py-1 text-xs rounded-full border transition-colors ${
|
||||||
|
nickname === name
|
||||||
|
? 'bg-blue-100 border-blue-300 text-blue-800 dark:bg-blue-900/40 dark:border-blue-600 dark:text-blue-300'
|
||||||
|
: 'border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:border-blue-300 dark:hover:border-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { getRuns, getRun, createRun, updateRun, deleteRun } from '../api/runs'
|
import { getRuns, getRun, createRun, updateRun, deleteRun, getNamingCategories, getNameSuggestions } from '../api/runs'
|
||||||
import type { CreateRunInput, UpdateRunInput } from '../types/game'
|
import type { CreateRunInput, UpdateRunInput } from '../types/game'
|
||||||
|
|
||||||
export function useRuns() {
|
export function useRuns() {
|
||||||
@@ -51,3 +51,19 @@ export function useDeleteRun() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useNamingCategories() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['naming-categories'],
|
||||||
|
queryFn: getNamingCategories,
|
||||||
|
staleTime: Infinity,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useNameSuggestions(runId: number | null) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['name-suggestions', runId],
|
||||||
|
queryFn: () => getNameSuggestions(runId!),
|
||||||
|
enabled: runId !== null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react'
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { GameGrid, RulesConfiguration, StepIndicator } from '../components'
|
import { GameGrid, RulesConfiguration, StepIndicator } from '../components'
|
||||||
import { useGames, useGameRoutes } from '../hooks/useGames'
|
import { useGames, useGameRoutes } from '../hooks/useGames'
|
||||||
import { useCreateRun, useRuns } from '../hooks/useRuns'
|
import { useCreateRun, useRuns, useNamingCategories } from '../hooks/useRuns'
|
||||||
import type { Game, NuzlockeRules } from '../types'
|
import type { Game, NuzlockeRules } from '../types'
|
||||||
import { DEFAULT_RULES } from '../types'
|
import { DEFAULT_RULES } from '../types'
|
||||||
import { RULE_DEFINITIONS } from '../types/rules'
|
import { RULE_DEFINITIONS } from '../types/rules'
|
||||||
@@ -14,11 +14,13 @@ export function NewRun() {
|
|||||||
const { data: games, isLoading, error } = useGames()
|
const { data: games, isLoading, error } = useGames()
|
||||||
const { data: runs } = useRuns()
|
const { data: runs } = useRuns()
|
||||||
const createRun = useCreateRun()
|
const createRun = useCreateRun()
|
||||||
|
const { data: namingCategories } = useNamingCategories()
|
||||||
|
|
||||||
const [step, setStep] = useState(1)
|
const [step, setStep] = useState(1)
|
||||||
const [selectedGame, setSelectedGame] = useState<Game | null>(null)
|
const [selectedGame, setSelectedGame] = useState<Game | null>(null)
|
||||||
const [rules, setRules] = useState<NuzlockeRules>(DEFAULT_RULES)
|
const [rules, setRules] = useState<NuzlockeRules>(DEFAULT_RULES)
|
||||||
const [runName, setRunName] = useState('')
|
const [runName, setRunName] = useState('')
|
||||||
|
const [namingScheme, setNamingScheme] = useState<string | null>(null)
|
||||||
const { data: routes } = useGameRoutes(selectedGame?.id ?? null)
|
const { data: routes } = useGameRoutes(selectedGame?.id ?? null)
|
||||||
|
|
||||||
const hiddenRules = useMemo(() => {
|
const hiddenRules = useMemo(() => {
|
||||||
@@ -44,7 +46,7 @@ export function NewRun() {
|
|||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
if (!selectedGame) return
|
if (!selectedGame) return
|
||||||
createRun.mutate(
|
createRun.mutate(
|
||||||
{ gameId: selectedGame.id, name: runName, rules },
|
{ gameId: selectedGame.id, name: runName, rules, namingScheme },
|
||||||
{ onSuccess: (data) => navigate(`/runs/${data.id}`) },
|
{ onSuccess: (data) => navigate(`/runs/${data.id}`) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -180,6 +182,33 @@ export function NewRun() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{namingCategories && namingCategories.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
htmlFor="naming-scheme"
|
||||||
|
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||||
|
>
|
||||||
|
Naming Scheme
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
id="naming-scheme"
|
||||||
|
value={namingScheme ?? ''}
|
||||||
|
onChange={(e) => setNamingScheme(e.target.value || null)}
|
||||||
|
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
>
|
||||||
|
<option value="">None (manual nicknames)</option>
|
||||||
|
{namingCategories.map((cat) => (
|
||||||
|
<option key={cat} value={cat}>
|
||||||
|
{cat.charAt(0).toUpperCase() + cat.slice(1)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
Get nickname suggestions from a themed word list when catching Pokemon.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
|
<div className="border-t border-gray-200 dark:border-gray-700 pt-4">
|
||||||
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
|
||||||
Summary
|
Summary
|
||||||
@@ -203,6 +232,14 @@ export function NewRun() {
|
|||||||
{enabledRuleCount} of {totalRuleCount} enabled
|
{enabledRuleCount} of {totalRuleCount} enabled
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<dt className="text-gray-600 dark:text-gray-400">Naming Scheme</dt>
|
||||||
|
<dd className="text-gray-900 dark:text-gray-100 font-medium">
|
||||||
|
{namingScheme
|
||||||
|
? namingScheme.charAt(0).toUpperCase() + namingScheme.slice(1)
|
||||||
|
: 'None'}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo, useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useParams, Link } from 'react-router-dom'
|
import { useParams, Link } from 'react-router-dom'
|
||||||
import { useRun, useUpdateRun } from '../hooks/useRuns'
|
import { useRun, useUpdateRun, useNamingCategories } from '../hooks/useRuns'
|
||||||
import { useGameRoutes } from '../hooks/useGames'
|
import { useGameRoutes } from '../hooks/useGames'
|
||||||
import { useCreateEncounter, useUpdateEncounter } from '../hooks/useEncounters'
|
import { useCreateEncounter, useUpdateEncounter } from '../hooks/useEncounters'
|
||||||
import { StatCard, PokemonCard, RuleBadges, StatusChangeModal, EndRunModal } from '../components'
|
import { StatCard, PokemonCard, RuleBadges, StatusChangeModal, EndRunModal } from '../components'
|
||||||
@@ -51,6 +51,7 @@ export function RunDashboard() {
|
|||||||
const createEncounter = useCreateEncounter(runIdNum)
|
const createEncounter = useCreateEncounter(runIdNum)
|
||||||
const updateEncounter = useUpdateEncounter(runIdNum)
|
const updateEncounter = useUpdateEncounter(runIdNum)
|
||||||
const updateRun = useUpdateRun(runIdNum)
|
const updateRun = useUpdateRun(runIdNum)
|
||||||
|
const { data: namingCategories } = useNamingCategories()
|
||||||
const [selectedEncounter, setSelectedEncounter] =
|
const [selectedEncounter, setSelectedEncounter] =
|
||||||
useState<EncounterDetail | null>(null)
|
useState<EncounterDetail | null>(null)
|
||||||
const [showEndRun, setShowEndRun] = useState(false)
|
const [showEndRun, setShowEndRun] = useState(false)
|
||||||
@@ -197,6 +198,37 @@ export function RunDashboard() {
|
|||||||
<RuleBadges rules={run.rules} />
|
<RuleBadges rules={run.rules} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Naming Scheme */}
|
||||||
|
{namingCategories && namingCategories.length > 0 && (
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
|
||||||
|
Naming Scheme
|
||||||
|
</h2>
|
||||||
|
{isActive ? (
|
||||||
|
<select
|
||||||
|
value={run.namingScheme ?? ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateRun.mutate({ namingScheme: e.target.value || null })
|
||||||
|
}
|
||||||
|
className="text-sm border border-gray-300 dark:border-gray-600 rounded-lg px-3 py-1.5 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
|
||||||
|
>
|
||||||
|
<option value="">None</option>
|
||||||
|
{namingCategories.map((cat) => (
|
||||||
|
<option key={cat} value={cat}>
|
||||||
|
{cat.charAt(0).toUpperCase() + cat.slice(1)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<span className="text-sm text-gray-900 dark:text-gray-100">
|
||||||
|
{run.namingScheme
|
||||||
|
? run.namingScheme.charAt(0).toUpperCase() + run.namingScheme.slice(1)
|
||||||
|
: 'None'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Active Team */}
|
{/* Active Team */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
|||||||
@@ -1435,6 +1435,8 @@ export function RunEncounters() {
|
|||||||
<EncounterModal
|
<EncounterModal
|
||||||
route={selectedRoute}
|
route={selectedRoute}
|
||||||
gameId={run!.gameId}
|
gameId={run!.gameId}
|
||||||
|
runId={runIdNum}
|
||||||
|
namingScheme={run!.namingScheme}
|
||||||
existing={editingEncounter ?? undefined}
|
existing={editingEncounter ?? undefined}
|
||||||
dupedPokemonIds={dupedPokemonIds}
|
dupedPokemonIds={dupedPokemonIds}
|
||||||
retiredPokemonIds={retiredPokemonIds}
|
retiredPokemonIds={retiredPokemonIds}
|
||||||
|
|||||||
Reference in New Issue
Block a user