Metadata-Version: 2.4
Name: luduscore
Version: 0.1.0
Summary: A rule-based, explainable console game recommendation engine.
Author-email: Divyansh Sharma <divyanshsharma1121@gmail.com>
License: MIT
Keywords: games,recommendation,recommender-system,rule-based,explainable-ai
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Games/Entertainment
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# luduscore

A rule-based, explainable console game recommendation engine — no AI/ML, no
web framework, no database required. Give it a player's preferences, get
back a ranked list of games with a numeric score and human-readable reasons
for each recommendation.

> **Not affiliated with, endorsed by, or sponsored by Sony Interactive
> Entertainment.** "PlayStation" and "PS5" are trademarks of Sony
> Interactive Entertainment Inc. This project is an independent,
> open-source tool compatible with console game preferences; it does not
> use any Sony trademark in its name.

## Why rule-based, not ML?

Every score is fully traceable: each matching dimension (difficulty, genre,
playstyle, multiplayer, story-vs-gameplay balance) contributes a fixed,
named weight. There's no model, no training data, no black box — the
`reasons` returned with each result are literally built from the rules that
fired. See [`src/luduscore/engine.py`](src/luduscore/engine.py) for the
full scoring logic.

## Install

```bash
pip install luduscore
```

## Quickstart

```python
from luduscore import ScoringEngine, UserProfile

engine = ScoringEngine()  # uses the bundled 100-game starter catalog

profile = UserProfile(
    skill_level="beginner",
    genres=["adventure", "rpg"],
    playstyle=["story-driven"],
    difficulty="easy",
    multiplayer=False,
    story_weight=0.9,  # 0.0 = pure gameplay, 1.0 = pure narrative
)

for result in engine.recommend(profile, top_n=5):
    print(f"{result.score:5.2f}  {result.title}")
    for reason in result.reasons:
        print(f"        - {reason}")
```

## The bundled catalog is a starter set, not a database

`luduscore` ships with 100 manually curated, real games as a default
catalog — enough to demo the engine and get useful results out of the box.
It is **not** meant to be a comprehensive game database. For anything
beyond demo/starter use, bring your own catalog:

```python
# Fully replace the bundled catalog
engine = ScoringEngine(games_path="my_games.json")
engine = ScoringEngine(games=my_list_of_game_dicts)

# Or extend the bundled catalog instead of replacing it
engine = ScoringEngine(extra_games=[{...}, {...}])
engine.add_game({...})
engine.add_games([{...}, {...}])
engine.remove_game("elden-ring")
```

`luduscore` doesn't care where your data comes from — JSON file, MongoDB,
SQL, an API call — as long as it's converted to a `list[dict]` matching the
required schema before you hand it to `ScoringEngine`. Fetching/storage is
your app's job; scoring is this package's job.

### Required game schema

Every game dict needs these keys (importable as `luduscore.REQUIRED_GAME_KEYS`):

| Key | Type | Notes |
|---|---|---|
| `id` | `str` | Unique identifier (slug) |
| `title` | `str` | Display name |
| `genres` | `list[str]` | e.g. `["action-rpg", "open-world"]` |
| `difficulty` | `str` | One of: `easy`, `easy-medium`, `medium`, `medium-hard`, `hard`, `very-hard` |
| `playstyle` | `list[str]` | e.g. `["combat-heavy", "story-driven"]` |
| `multiplayer` | `bool` | Has a meaningful multiplayer mode |
| `story_vs_gameplay` | `float` | `0.0` (pure gameplay) to `1.0` (pure narrative) |

`tags` (`list[str]`) is optional — it's descriptive metadata, not read by
the scoring logic.

`add_game`/`add_games` validate this schema and raise `ValueError` (naming
the exact missing keys) rather than silently scoring a malformed entry as
0 on that dimension.

## API

- `ScoringEngine(games=None, games_path=None, extra_games=None)`
- `engine.recommend(profile: UserProfile, top_n: int | None = 10) -> list[ScoredResult]`
- `engine.score_game(game: dict, profile: UserProfile) -> ScoredResult`
- `engine.add_game(game: dict)` / `engine.add_games(games: list[dict])`
- `engine.remove_game(game_id: str) -> bool`
- `UserProfile(skill_level, genres=[], playstyle=[], difficulty=None, multiplayer=None, story_weight=0.5)`
- `ScoredResult(game_id, title, score, reasons)`

## Development

```bash
pip install -e ".[dev]"
pytest
```

## License

MIT — see [LICENSE](LICENSE).
