feat: système d'objets achetables (boutique + effets permanents) et refonte visuelle

- Nouveaux personnages illustrés (12 PNG remplacés dans client/public/cards)
- 4 objets achetables (Trébuchet, Charrue, Trousse du Médecin, Sceau Royal)
  avec effets permanents (double_gain, guaranteed_gain, copy_best_in_game),
  stock unique par partie, priorité d'achat tournante entre joueurs
- Boutique en colonne à gauche du plateau : icônes compactes, carte détaillée
  agrandie au survol (taille découplée du displayScale du jeton), bouton
  ACHETER actif/grisé selon la jauge du joueur, retrait immédiat de la
  colonne une fois l'objet acheté (les autres remontent pour combler)
- Objets achetés affichés "posés sur la table" : à côté de la main pour soi,
  sous le slot de jeu pour les adversaires
- Icône du premier joueur (priorité d'achat) dans le HUD Vue
- Refonte des cartes personnage (CardObject) : portrait plein cadre épuré,
  valeurs d'attribut discrètes aux 4 coins
- Fix removePlayer : nettoyage de pendingBuys/pendingPlays et recalage de
  firstPlayerIndex à la déconnexion d'un joueur
- 77 tests unitaires serveur (node:test) : cards, items, Room, RoomManager
- README mis à jour avec les règles complètes du jeu
- .gitignore : exclusion de .idea/ et .serena/ (artefacts d'outils locaux)
This commit is contained in:
Kevin L'Hereec committed 2026-08-21 14:53:58 +02:00
1 parent da06ecaf2d
commit 6a1c800253
37 files changed
+3558 -188

No files matched your search

+24 -7
View File
@@ -58,6 +58,24 @@ io.on('connection', (socket) => {
socket.emit('game-state', room.getStateFor(socket.id))
})
// ─── Acheter un objet ────────────────────────────────────────────────────────
socket.on('buy-item', ({ roomId, itemId }) => {
const room = rooms.get(roomId)
if (!room) return
const result = room.buyItem(socket.id, itemId)
if (!result.ok) {
socket.emit('error', { message: result.error })
return
}
// L'achat est différé (résolu avec le tour) : on renvoie juste l'état à
// jour à chaque joueur pour refléter la tentative en attente.
room.players.forEach(player => {
io.to(player.socketId).emit('game-state', room.getStateFor(player.socketId))
})
})
// ─── Jouer une carte ─────────────────────────────────────────────────────────
socket.on('play-card', ({ roomId, cardId }) => {
const room = rooms.get(roomId)
@@ -87,19 +105,18 @@ io.on('connection', (socket) => {
const roundResult = room.resolveRound()
console.log(`[${roomId}] Tour ${room.currentTurn} résolu`)
// Révéler les cartes jouées et les gains par attribut
// Révéler les cartes jouées, les gains par attribut, et les objets achetés
io.to(roomId).emit('round-result', {
playedCards: roundResult.playedCards,
gains: roundResult.gains,
boughtItems: roundResult.boughtItems,
message: roundResult.message
})
// Mise à jour scores
io.to(roomId).emit('game-state', {
players: room.getPlayersInfo(),
currentTurn: room.currentTurn,
maxTurns: room.maxTurns,
phase: 'resolve'
// Mise à jour scores (état personnalisé par joueur : items, achat en
// attente, objets encore disponibles, etc.)
room.players.forEach(player => {
io.to(player.socketId).emit('game-state', room.getStateFor(player.socketId))
})
// 3 secondes d'affichage puis tour suivant / fin