Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions contrat/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3187,6 +3187,23 @@
},
"additionalProperties": false
},
"Essai": {
"type": "object",
"description": "Une paire essayée sans succès (échec ou « presque ») : ni issue, ni date.",
"required": [
"gauche",
"droite"
],
"properties": {
"droite": {
"$ref": "#/components/schemas/IdCatalogue"
},
"gauche": {
"$ref": "#/components/schemas/IdCatalogue"
}
},
"additionalProperties": false
},
"Etat": {
"type": "object",
"description": "Projection de l'état confirmé du joueur : des identifiants et des nombres,\njamais de texte du catalogue (ADR 0027).",
Expand Down Expand Up @@ -3257,6 +3274,13 @@
"$ref": "#/components/schemas/EnergieEtat",
"description": "Charges de pli, à l'horloge du serveur (ADR 0020)."
},
"essais": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Essai"
},
"description": "Paires déjà essayées sans succès depuis la dernière parution (App#89),\ndans l'ordre des cases, triées. Vide avant ce champ ou après une parution."
},
"fascicules_debloques": {
"type": "array",
"items": {
Expand Down
1 change: 1 addition & 0 deletions crates/api/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub async fn exporter(base: &PgPool, courriel: &Courriel, adresse: &str) -> anyh
'ricochets', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM ricochet t WHERE t.joueur_id = j.id),
'ricochets_du_jour', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM ricochet_jour t WHERE t.joueur_id = j.id),
'paliers_recuperes', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM palier_recupere t WHERE t.joueur_id = j.id),
'essais', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM essai t WHERE t.joueur_id = j.id),
'quetes_du_jour', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM quete_jour t WHERE t.joueur_id = j.id),
'tampons', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM tampon t WHERE t.joueur_id = j.id),
'cartes_lecteur_recuperees', (SELECT coalesce(jsonb_agg(to_jsonb(t) - 'joueur_id'), '[]') FROM carte_lecteur_recuperee t WHERE t.joueur_id = j.id),
Expand Down
80 changes: 78 additions & 2 deletions crates/api/src/progression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use std::collections::{HashMap, HashSet};
use chrono::{DateTime, TimeDelta, Utc};
use etymo_catalogue::Jeu;
use etymo_contrat::{
CadeauEtat, CompteEtat, CompteurPli, Decouverte, EnergieEtat, Etat, Exemplaires,
FasciculeDebloque, IndiceObtenu, OuverturePli, ProgressionQuete, SabliersEtat,
CadeauEtat, CompteEtat, CompteurPli, Decouverte, EnergieEtat, Essai, Etat, Exemplaires,
FasciculeDebloque, IndiceObtenu, IssueFusion, OuverturePli, ProgressionQuete, SabliersEtat,
};
use etymo_domaine::IdCatalogue;
use etymo_domaine::carte_de_lecteur;
Expand Down Expand Up @@ -144,9 +144,82 @@ pub async fn etat(
quetes_initiales: quetes_initiales::etat(&mut *tx, joueur, jeu, maintenant).await?,
fascicules_debloques: lire_fascicules_debloques(&mut *tx, joueur).await?,
compte: compte_etat(&mut *tx, joueur).await?,
essais: lire_essais(&mut *tx, joueur, jeu).await?,
})
}

/// Plafond de la mémoire des essais, par joueur (App#89) : au-delà, un essai n'est plus noté.
pub const PLAFOND_ESSAIS: i64 = 3000;

/// Dernier fascicule paru : la mémoire des essais ne vaut que jusqu'à la parution suivante.
fn fascicule_juge(jeu: &Jeu) -> Option<&IdCatalogue> {
jeu.chaine.last().map(|m| &m.fascicule_id)
}

/// Paires essayées depuis la dernière parution ; les plus anciennes ne se lisent plus.
async fn lire_essais(
tx: &mut PgConnection,
joueur: Uuid,
jeu: Option<&Jeu>,
) -> Result<Vec<Essai>, ErreurApi> {
let Some(fascicule) = jeu.and_then(fascicule_juge) else {
return Ok(Vec::new());
};
sqlx::query!(
"SELECT gauche, droite FROM essai WHERE joueur_id = $1 AND fascicule_juge = $2 ORDER BY gauche, droite",
joueur,
fascicule.as_str()
)
.fetch_all(tx)
.await?
.into_iter()
.map(|l| Ok(Essai { gauche: id(&l.gauche)?, droite: id(&l.droite)? }))
.collect()
}

/// Note un essai sans succès (App#89) : un échec ou un « presque », si le
/// joueur connaît les deux briques. Renvoie vrai pour un essai à noter.
/// Une parution oublie en bloc les essais d'avant : de vieilles paires peuvent
/// devenir des mots, et une purge paire par paire désignerait les familles du
/// nouveau fascicule. Sans ligne de commande ni changement de version : la
/// mémoire est une aide, pas un état de jeu.
pub async fn noter_essai(
tx: &mut PgConnection,
joueur: Uuid,
jeu: &Jeu,
briques: &[IdCatalogue],
issue: &IssueFusion,
) -> Result<bool, ErreurApi> {
if !matches!(issue, IssueFusion::Echec | IssueFusion::Presque { .. }) {
return Ok(false);
}
let ([gauche, droite], Some(fascicule)) = (briques, fascicule_juge(jeu)) else {
return Ok(true);
};
sqlx::query!(
"DELETE FROM essai WHERE joueur_id = $1 AND fascicule_juge <> $2",
joueur,
fascicule.as_str()
)
.execute(&mut *tx)
.await?;
sqlx::query!(
"INSERT INTO essai (joueur_id, gauche, droite, fascicule_juge)
SELECT $1, $2, $3, $4
WHERE (SELECT count(*) FROM exemplaire WHERE joueur_id = $1 AND brique_id IN ($2, $3)) = (CASE WHEN $2 = $3 THEN 1 ELSE 2 END)
AND (SELECT count(*) FROM essai WHERE joueur_id = $1) < $5
ON CONFLICT DO NOTHING",
joueur,
gauche.as_str(),
droite.as_str(),
fascicule.as_str(),
PLAFOND_ESSAIS
)
.execute(&mut *tx)
.await?;
Ok(true)
}

/// Compte du joueur, absent pour une partie invitée (ADR 0070).
pub(crate) async fn compte_etat(
tx: &mut PgConnection,
Expand Down Expand Up @@ -1578,6 +1651,9 @@ pub async fn reinitialiser(tx: &mut PgConnection, joueur: Uuid) -> Result<(), Er
sqlx::query!("DELETE FROM palier_recupere WHERE joueur_id = $1", joueur)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM essai WHERE joueur_id = $1", joueur)
.execute(&mut *tx)
.await?;
sqlx::query!("DELETE FROM commande WHERE joueur_id = $1", joueur)
.execute(&mut *tx)
.await?;
Expand Down
7 changes: 6 additions & 1 deletion crates/api/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ pub async fn fusion(
Issue::Presque { motif } => IssueFusion::Presque { motif },
Issue::Echec => IssueFusion::Echec,
};
// Mémoire des essais (App#89) : un échec ou un « presque » se note, avant l'état renvoyé.
let essai = progression::noter_essai(&mut tx, joueur, jeu, &demande.briques, &issue).await?;
let reponse = ReponseFusion {
etat: etat_courant(&app, &mut tx, joueur).await?,
issue,
Expand All @@ -362,8 +364,11 @@ pub async fn fusion(
.execute(&mut *tx)
.await?;
tx.commit().await?;
} else if essai {
// Ni commande ni version : l'essai noté se rejoue sans effet (ON CONFLICT DO NOTHING).
tx.commit().await?;
} else {
// Une proposition qui ne modifie rien ne laisse aucune ligne (modèle de données §7).
// Une autre proposition qui ne modifie rien ne laisse aucune ligne (modèle de données §7).
tx.rollback().await?;
}
Ok(Json(reponse))
Expand Down
Loading
Loading