Files
Kevin L'Hereec 6a1c800253 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)
2026-08-21 14:53:58 +02:00

72 lines
2.6 KiB
JavaScript

const { test, describe } = require('node:test')
const assert = require('node:assert/strict')
const { CHARACTERS, TEAM_COLORS, getDeckForTeam, dealHands } = require('../../src/game/cards')
describe('cards.js', () => {
test('expose exactement 12 personnages', () => {
assert.equal(CHARACTERS.length, 12)
})
test('chaque personnage a un id unique', () => {
const ids = CHARACTERS.map(c => c.id)
assert.equal(new Set(ids).size, ids.length)
})
test('chaque personnage a les 4 attributs définis entre 1 et 6', () => {
for (const c of CHARACTERS) {
for (const attr of ['food', 'war', 'health', 'diplo']) {
assert.ok(Number.isInteger(c[attr]), `${c.id}.${attr} doit être un entier`)
assert.ok(c[attr] >= 1 && c[attr] <= 6, `${c.id}.${attr} doit être entre 1 et 6`)
}
}
})
test('getDeckForTeam retourne les 12 personnages avec la couleur d\'équipe', () => {
const deck = getDeckForTeam(2)
assert.equal(deck.length, 12)
for (const card of deck) {
assert.equal(card.teamIndex, 2)
assert.deepEqual(card.teamColor, TEAM_COLORS[2])
}
})
test('getDeckForTeam ne mute pas les CHARACTERS d\'origine', () => {
const before = JSON.parse(JSON.stringify(CHARACTERS))
getDeckForTeam(0)
assert.deepEqual(CHARACTERS, before)
})
describe('dealHands', () => {
test('distribue une main de taille handSize et le reste au deck, pour chaque joueur', () => {
const result = dealHands(3, 4)
assert.equal(result.length, 3)
for (const { hand, deck } of result) {
assert.equal(hand.length, 4)
assert.equal(deck.length, 12 - 4)
// Pas de doublons entre hand et deck (même deck de 12 cartes)
const allIds = [...hand, ...deck].map(c => c.id)
assert.equal(new Set(allIds).size, 12)
}
})
test('chaque joueur reçoit un deck mélangé indépendamment (decks non systématiquement identiques)', () => {
// Avec un shuffle aléatoire, la probabilité que 4 joueurs aient exactement
// la même main de 4 cartes dans le même ordre est infinitésimale.
const result = dealHands(4, 4)
const handsAsStrings = result.map(r => r.hand.map(c => c.id).join(','))
assert.ok(new Set(handsAsStrings).size > 1, 'les mains devraient différer entre joueurs')
})
test('gère handSize par défaut (4) si non fourni', () => {
const result = dealHands(2)
assert.equal(result[0].hand.length, 4)
})
test('gère playerCount = 0 sans erreur', () => {
const result = dealHands(0, 4)
assert.deepEqual(result, [])
})
})
})