Files
Skori/docs/creation-jeu.md
T
Zed 1f400b78cb
Build and Publish Docker Image / build-and-push-image (push) Successful in 59s
Refonte UI : DA Strava + Kurzgesagt
- Nouveau logo planète flat (PlanetMark) + icônes PWA régénérées + splash navy cosmique
- Tokens de thème unifiés : accent teal de marque, rayons plus doux, dark mode cosmique (surfaces élevées)
- Fond CosmicBackground raffiné (halos aurora teal/indigo)
- Navigation par barre d'onglets en bas (BottomNav) ; menu ☰ réduit au secondaire
- Composants partagés PageHeader + SessionCard
- Accueil = dashboard : tuiles de stats + démarrage rapide + feed d'activité
- Stats : classement en barres horizontales (teinte unique, méthode dataviz)
- Restyle de tous les écrans liste + boucle de jeu immersive conservée
- Doc du format de création de jeu (docs/creation-jeu.md)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 11:42:15 +02:00

373 lines
11 KiB
Markdown

# Créer un jeu personnalisé pour Skori
Ce document décrit **tous les paramètres** d'un fichier de définition de jeu (`.json`)
importable dans Skori via **Paramètres → Jeux → Gérer les jeux importés → Importer**.
Un jeu importé ajoute une nouvelle tuile à l'écran d'accueil, avec ses propres
règles de score, options, icône et couleurs — sans recompiler l'application.
> Les règles de calcul sont **déclaratives** (décrites par des données, pas du
> code). C'est volontaire : un fichier importé ne peut jamais exécuter de code
> arbitraire sur votre appareil.
---
## 1. Structure minimale
Le plus petit fichier valide :
```json
{
"schemaVersion": 1,
"id": "mon-jeu",
"name": "Mon Jeu",
"minPlayers": 2,
"maxPlayers": 6,
"scoreType": "positive",
"scoreFormula": { "strategy": "sum_rounds" },
"colors": { "primary": "#3b82f6" }
}
```
Tout le reste est optionnel.
---
## 2. Champs racine
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| `schemaVersion` | `1` | ✅ | Toujours `1` (version du format). |
| `id` | texte | ✅ | Identifiant unique. Lettres, chiffres, `_` et `-` uniquement. **Ne peut pas** être un id natif (voir §9). Réimporter avec le même `id` **met à jour** le jeu. |
| `name` | texte | ✅ | Nom affiché (max 60 caractères). |
| `minPlayers` | entier | ✅ | Nombre minimum de joueurs (1 à 20). |
| `maxPlayers` | entier | ✅ | Nombre maximum de joueurs (1 à 20). |
| `scoreType` | énum | ✅ | Nature du score — voir §3. |
| `scoreFormula` | objet | ✅ | Comment additionner les manches — voir §4. |
| `colors` | objet | ✅ | Couleur(s) du thème — voir §7. |
| `description` | texte | ❌ | Phrase affichée sur l'écran de la partie (max 400 caractères). |
| `targetScore` | objet | ❌ | Score de fin de partie — voir §5. |
| `targetScoreCondition` | `"reaches"` \| `"exceeds"` | ❌ | `reaches` = fin si score **≥** cible (défaut logique) ; `exceeds` = fin si score **>** cible. |
| `fixedRounds` | entier | ❌ | La partie se termine après ce nombre de manches — voir §6. |
| `scoringCategories` | tableau | ❌ | Catégories de score saisies une par une — voir §8. |
| `options` | tableau | ❌ | Options configurables avant la partie — voir §6/§8. |
| `iconImage` | texte | ❌ | Icône du jeu (image en base64 « data URI ») — voir §7. |
| `bannerImage` | texte | ❌ | Bannière de l'écran de fin (base64 data URI) — voir §7. |
---
## 3. `scoreType` — nature du score
Détermine **qui gagne** et la façon dont le score est interprété.
| Valeur | Le gagnant est… | Exemple |
|---|---|---|
| `positive` | le **plus haut** score | Azul, Harmonies |
| `negative` | le **plus bas** score | Skyjo, Cabo, Odin |
| `target` | le plus haut (course vers un objectif) | jeux à objectif de points |
| `fixed_rounds` | le plus haut, sur un nombre fixe de manches | — |
| `sudden_death` | le plus haut (mort subite) | — |
> En pratique, le classement de fin de partie utilise `negative` → plus petit
> gagne, **tous les autres** → plus grand gagne. Choisissez `negative` pour un
> jeu « le moins de points possible », sinon `positive`.
---
## 4. `scoreFormula` — calcul du total
Comment le total d'un joueur est calculé à partir de ses scores de manche.
### `sum_rounds` (par défaut)
Simple addition de toutes les manches.
```json
"scoreFormula": { "strategy": "sum_rounds" }
```
### `sum_rounds_with_reset`
Addition, avec **remise à un palier** quand le total atteint *exactement* un seuil
(règle « Cabo » : tomber pile sur 100 redescend à 50).
```json
"scoreFormula": {
"strategy": "sum_rounds_with_reset",
"resetThreshold": 100,
"resetTo": 50,
"oncePerPlayer": true
}
```
| Paramètre | Type | Description |
|---|---|---|
| `resetThreshold` | nombre | Si le total **égale exactement** cette valeur… |
| `resetTo` | nombre | …il est ramené à cette valeur. |
| `oncePerPlayer` | booléen | `true` = la remise ne peut avoir lieu qu'**une seule fois** par joueur. |
---
## 5. `targetScore` — fin de partie sur un score
Déclenche la fin de la partie dès qu'un joueur atteint (ou dépasse, selon
`targetScoreCondition`) une valeur cible. Combinable avec `fixedRounds`
(la première condition atteinte l'emporte).
### Cible fixe
```json
"targetScore": { "type": "fixed", "value": 100 }
```
### Cible dépendant d'une option
Permet à l'utilisateur de choisir la cible avant la partie (comme Odin :
« 15 pts / 20 pts / personnalisé »).
```json
"targetScore": {
"type": "fromOption",
"optionId": "target_score",
"customSentinel": "custom",
"customOptionId": "custom_target_score"
}
```
| Paramètre | Description |
|---|---|
| `optionId` | id de l'option (`select` ou `number`) qui contient la cible. |
| `customSentinel` | *(optionnel)* valeur de l'option signifiant « personnalisé ». |
| `customOptionId` | *(optionnel)* id de l'option `number` lue si la valeur = `customSentinel`. |
---
## 6. `fixedRounds` — nombre de manches fixe
```json
"fixedRounds": 5
```
- La partie se termine automatiquement après ce nombre de manches.
- `fixedRounds: 1` active un mode spécial **« Décompte final »** (une seule
saisie de scores, idéal pour un jeu où l'on ne compte qu'à la fin).
---
## 7. Apparence : `colors`, `iconImage`, `bannerImage`
### Couleurs
```json
"colors": { "primary": "#6e1d5a", "secondary": "#c2410c" }
```
- `primary` (obligatoire) : couleur principale du thème du jeu (format hex).
- `secondary` (optionnel) : couleur d'appoint.
### Images (base64)
`iconImage` et `bannerImage` sont des **data URI** : l'image encodée directement
dans le fichier (aucun chemin externe). Formats : PNG, SVG, WebP, JPEG.
```json
"iconImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA…"
```
Pour convertir une image en data URI :
- en ligne : chercher « image to base64 data uri » ;
- ou dans un terminal :
- macOS/Linux : `echo "data:image/png;base64,$(base64 -w0 icone.png)"`
- Windows PowerShell : `"data:image/png;base64," + [Convert]::ToBase64String([IO.File]::ReadAllBytes("icone.png"))`
> Gardez les images légères (idéalement < 200 Ko) : elles sont stockées telles
> quelles dans l'appareil. Si `iconImage` est absent, une icône générique est
> utilisée.
---
## 8. Options et catégories
### `options` — réglages avant la partie
Chaque option apparaît sur l'écran de préparation de la partie.
```json
"options": [
{
"id": "kamikaze",
"label": "Règle Kamikaze",
"type": "boolean",
"defaultValue": true
},
{
"id": "target_score",
"label": "Score de fin",
"type": "select",
"defaultValue": "15",
"options": [
{ "label": "Courte (15)", "value": "15" },
{ "label": "Classique (20)", "value": "20" },
{ "label": "Personnalisé", "value": "custom" }
]
},
{
"id": "custom_target_score",
"label": "Score personnalisé",
"type": "number",
"defaultValue": 25
}
]
```
| Champ | Description |
|---|---|
| `id` | Identifiant de l'option (référencé par `targetScore.fromOption`). |
| `label` | Libellé affiché. |
| `type` | `boolean` (case à cocher), `number` (champ numérique), `select` (liste). |
| `defaultValue` | Valeur par défaut. |
| `options` | *(select uniquement)* liste de `{ label, value }`. |
### `scoringCategories` — saisie par catégories
Active une saisie **étape par étape** (une catégorie à la fois, comme Harmonies).
Le score de la manche = **somme des catégories**.
```json
"scoringCategories": [
{ "id": "arbres", "label": "Arbres" },
{ "id": "montagnes", "label": "Montagnes" },
{ "id": "eau", "label": "Champs d'eau" }
]
```
> Généralement associé à `fixedRounds: 1` (un seul décompte final réparti en
> catégories).
---
## 9. Contraintes & validation
À l'import, le fichier est validé. En cas d'erreur, un message précis indique le
champ fautif.
- `schemaVersion` doit valoir `1`.
- `id` : lettres/chiffres/`_`/`-`, et **pas** un id réservé :
`azul`, `cabo`, `skyjo`, `harmonie`, `odin_cards`.
- `name` ≤ 60 caractères, `description` ≤ 400 caractères.
- `minPlayers` / `maxPlayers` entre 1 et 20.
- `scoreType` parmi les valeurs du §3.
- `scoreFormula.strategy` parmi `sum_rounds`, `sum_rounds_with_reset`.
- Les jeux importés sont **propres à chaque profil** (pas encore synchronisés
entre appareils).
> **Non pris en charge en v1** : les formules 100 % personnalisées par expression
> (`custom_expression`). Les deux stratégies ci-dessus couvrent tous les jeux
> fournis en standard.
---
## 10. Exemples complets
### A. Skyjo (le moins de points, fin à 100)
```json
{
"schemaVersion": 1,
"id": "mon-skyjo",
"name": "Skyjo maison",
"minPlayers": 2,
"maxPlayers": 8,
"scoreType": "negative",
"scoreFormula": { "strategy": "sum_rounds" },
"targetScore": { "type": "fixed", "value": 100 },
"targetScoreCondition": "reaches",
"description": "Le moins de points possible. Fin dès qu'un joueur atteint 100.",
"colors": { "primary": "#0f766e" }
}
```
### B. Cabo (remise à 50 sur 100 pile)
```json
{
"schemaVersion": 1,
"id": "mon-cabo",
"name": "Cabo maison",
"minPlayers": 2,
"maxPlayers": 5,
"scoreType": "negative",
"scoreFormula": {
"strategy": "sum_rounds_with_reset",
"resetThreshold": 100,
"resetTo": 50,
"oncePerPlayer": true
},
"targetScore": { "type": "fixed", "value": 100 },
"targetScoreCondition": "reaches",
"colors": { "primary": "#6e1d5a" }
}
```
### C. Odin (cible choisie par option)
```json
{
"schemaVersion": 1,
"id": "mon-odin",
"name": "Odin maison",
"minPlayers": 2,
"maxPlayers": 6,
"scoreType": "negative",
"scoreFormula": { "strategy": "sum_rounds" },
"targetScore": {
"type": "fromOption",
"optionId": "target_score",
"customSentinel": "custom",
"customOptionId": "custom_target_score"
},
"targetScoreCondition": "reaches",
"options": [
{
"id": "target_score",
"label": "Score de fin de partie",
"type": "select",
"defaultValue": "15",
"options": [
{ "label": "Partie courte (15 pts)", "value": "15" },
{ "label": "Partie classique (20 pts)", "value": "20" },
{ "label": "Personnalisé", "value": "custom" }
]
},
{
"id": "custom_target_score",
"label": "Score personnalisé",
"type": "number",
"defaultValue": 25
}
],
"colors": { "primary": "#c2410c" }
}
```
### D. Jeu par catégories (le plus de points, décompte final)
```json
{
"schemaVersion": 1,
"id": "mon-jeu-categories",
"name": "Jeu à catégories",
"minPlayers": 1,
"maxPlayers": 4,
"scoreType": "positive",
"scoreFormula": { "strategy": "sum_rounds" },
"fixedRounds": 1,
"scoringCategories": [
{ "id": "cat1", "label": "Objectifs" },
{ "id": "cat2", "label": "Bonus" },
{ "id": "cat3", "label": "Pénalités" }
],
"colors": { "primary": "#16a34a" }
}
```