fix: cartes Événement illisibles + objets/bâtiments adversaires au-dessus de leur carte
Deux problèmes de lisibilité/placement signalés : 1. Le texte des cartes Événement (nom + description) restait illisible malgré le passage précédent de l'échelle 0.55 à 0.8 — le composant dédié EventCardObject affichait une grande carte réduite, écrasant systématiquement le texte quelle que soit l'échelle choisie. Fix : suppression complète d'EventCardObject, remplacé par le même pattern que les objets/bâtiments (ItemBoardToken, nouveau kind='event') : un petit jeton compact (46x46) toujours visible sur le plateau, avec la carte détaillée affichée en TAILLE RÉELLE (police 13-16px, jamais réduite) au survol de la souris, et un bouton "JOUER" (au lieu d'"ACHETER") pour confirmer. ItemBoardToken généralisé pour supporter ce 3e kind (pas de badge de coût affiché — déjà payé à la pioche — mais badge "!" repris pour les événements à effet négatif type Épidémie). Extraction de client/src/game/objects/tokenLayout.js (ICON_SIZE) dans un module SANS dépendance à Phaser, pour que eventHandLayout.js (et ses tests Vitest) puisse l'importer sans jamais charger Phaser — qui échoue à l'import en environnement jsdom (canvas natif requis, absent de jsdom). 2. Les mini-cartes objets/bâtiments déjà achetés par un ADVERSAIRE étaient affichées SOUS son slot de jeu, visuellement en dessous/après sa carte jouée. Déplacées AU-DESSUS de son slot pour les mettre davantage en évidence, comme posées sur sa carte plutôt que cachées dessous. - 43 tests unitaires client mis à jour (eventHandLayout.test.js migré vers ICON_SIZE au lieu de CARD_W/CARD_H/scale, désormais indépendant de Phaser). - e2e/tests/helpers.js : playEventCard adapté au nouveau flux survol->bouton JOUER (au lieu d'un simple clic direct sur la carte) ; drawEventUntil corrigé pour ne jamais défausser une carte 'royal_accord' sans fournir ses 2 cibles de ciblage séquentiel obligatoire (le jeu sans cible bloquait silencieusement la séquence, sans jamais émettre 'play-event', rendant la boucle de pioche très peu fiable). - Vérification ciblée via un test E2E existant (pas de nouvelle suite E2E lourde, conformément à la demande de privilégier les tests unitaires) : le nouveau flux jeton->survol->JOUER->résolution fonctionne bout en bout (Récolte exceptionnelle +2 Nourriture confirmé après résolution).
This commit is contained in:
1 parent
45eb9be60d
commit
1a91a0f876
7 files changed
+316
-309
No files matched your search
+62
-15
@@ -325,6 +325,13 @@ export async function clickEventDrawButton(page) {
|
||||
* puisse faire progresser les tours pendant l'accumulation.
|
||||
*/
|
||||
export async function drawEventUntil(page, otherPages, eventId, maxDraws = 20) {
|
||||
// Événements sans ciblage requis (au pire un ciblage optionnel côté
|
||||
// Épidémie, déclenché seulement avec la Trousse — jamais possédée dans un
|
||||
// scénario de test frais). Utilisés pour vider un slot de la main sans
|
||||
// jamais déclencher une séquence de ciblage qui resterait bloquée faute
|
||||
// de clic sur un slot adverse (playEventCard sans targetTeamIndexes).
|
||||
const SAFE_TO_DISCARD = new Set(['bountiful_harvest', 'troop_levy', 'plot_uncovered', 'castle_feast', 'epidemic'])
|
||||
|
||||
for (let attempt = 0; attempt < maxDraws; attempt++) {
|
||||
const hand = await getMyEventHand(page)
|
||||
if (hand.includes(eventId)) return true
|
||||
@@ -334,11 +341,30 @@ export async function drawEventUntil(page, otherPages, eventId, maxDraws = 20) {
|
||||
// rendait ce helper très peu fiable — avec seulement 6 types
|
||||
// d'événements et une main de 2, l'échec dès les 2 premières pioches
|
||||
// est fréquent, pas rare), jouer l'une des 2 cartes en main pour
|
||||
// libérer un slot et pouvoir continuer à piocher. On joue la première
|
||||
// carte sans ciblage particulier ; si elle nécessite un ciblage
|
||||
// (royal_accord/epidemic), on tente sans cible plutôt que de bloquer
|
||||
// le nettoyage de la main.
|
||||
await playEventCard(page, hand[0])
|
||||
// libérer un slot et pouvoir continuer à piocher.
|
||||
// IMPORTANT : ne JAMAIS choisir 'royal_accord' pour cette défausse —
|
||||
// il exige un ciblage séquentiel à 2 étapes (gain + perte) ; le jouer
|
||||
// sans fournir targetTeamIndexes laisserait la séquence de ciblage
|
||||
// bloquée côté client (aucun 'play-event' jamais émis), et donc la
|
||||
// main jamais libérée, causant l'échec systématique observé.
|
||||
const discardable = hand.find(id => SAFE_TO_DISCARD.has(id)) ?? hand[0]
|
||||
if (discardable === 'royal_accord') {
|
||||
// Les 2 cartes en main sont 'royal_accord' (cas rare) : impossible
|
||||
// de la défausser sans ciblage séquentiel complet. On cible le
|
||||
// premier adversaire réel (n'importe quel autre teamIndex que le
|
||||
// mien) pour les deux étapes — le ciblage doit obligatoirement
|
||||
// pointer sur un adversaire pour que le clic sur le slot déclenche
|
||||
// réellement _onTargetSlotClicked (les highlights ne sont posés que
|
||||
// sur les slots adverses, jamais sur le mien).
|
||||
const myTeamIndex = await page.evaluate(() => {
|
||||
const game = window.__ALL_GAMES__?.[window.__ALL_GAMES__.length - 1]
|
||||
return game?.scene?.getScene('GameScene')?.store?.myTeamIndex ?? 0
|
||||
})
|
||||
const opponentTeamIndex = myTeamIndex === 0 ? 1 : 0
|
||||
await playEventCard(page, discardable, [opponentTeamIndex, opponentTeamIndex])
|
||||
} else {
|
||||
await playEventCard(page, discardable)
|
||||
}
|
||||
await playFullTurn([page, ...otherPages])
|
||||
continue
|
||||
}
|
||||
@@ -360,28 +386,49 @@ export async function drawEventUntil(page, otherPages, eventId, maxDraws = 20) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne (clique) une carte de ma main d'événements pour la jouer ce
|
||||
* tour. Si l'événement nécessite un ciblage, fournir `targetTeamIndexes`
|
||||
* Joue une carte de ma main d'événements ce tour. Les cartes événement
|
||||
* utilisent désormais le même composant que les objets/bâtiments
|
||||
* (ItemBoardToken, kind='event') : un jeton compact toujours visible, dont
|
||||
* il faut survoler pour révéler la carte détaillée, puis cliquer sur son
|
||||
* bouton "JOUER" — plus un simple clic direct sur une grande carte comme
|
||||
* l'ancien EventCardObject.
|
||||
*
|
||||
* Si l'événement nécessite un ciblage, fournir `targetTeamIndexes`
|
||||
* (tableau, dans l'ordre attendu par GameScene._startTargeting : pour
|
||||
* royal_accord [gainTargetTeamIndex, loseTargetTeamIndex], pour epidemic
|
||||
* [targetTeamIndex]) — les clics correspondants sur les slots adversaires
|
||||
* sont effectués automatiquement.
|
||||
* sont effectués automatiquement après le clic sur "JOUER".
|
||||
*/
|
||||
export async function playEventCard(page, eventId, targetTeamIndexes = []) {
|
||||
const canvas = page.locator('#phaser-container canvas')
|
||||
const box = await canvas.boundingBox()
|
||||
|
||||
const cardPos = await page.evaluate((eventId) => {
|
||||
const tokenPos = await page.evaluate((eventId) => {
|
||||
const game = window.__ALL_GAMES__?.[window.__ALL_GAMES__.length - 1]
|
||||
const scene = game?.scene?.getScene('GameScene')
|
||||
const card = scene?.eventCards?.find(c => c.eventData.id === eventId)
|
||||
if (!card) return null
|
||||
const b = card.getBounds()
|
||||
return { x: b.centerX, y: b.centerY }
|
||||
const token = scene?.eventCards?.find(c => c.itemData.id === eventId)
|
||||
return token ? { x: token.x, y: token.y } : null
|
||||
}, eventId)
|
||||
if (!cardPos) return false
|
||||
if (!tokenPos) return false
|
||||
|
||||
await page.mouse.click(box.x + cardPos.x, box.y + cardPos.y)
|
||||
await page.mouse.move(box.x + tokenPos.x, box.y + tokenPos.y)
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
const btnCenter = await page.evaluate((eventId) => {
|
||||
const game = window.__ALL_GAMES__?.[window.__ALL_GAMES__.length - 1]
|
||||
const scene = game?.scene?.getScene('GameScene')
|
||||
const token = scene?.eventCards?.find(c => c.itemData.id === eventId)
|
||||
if (!token || !token._expanded) return null
|
||||
const btnBg = token._expanded.list.find(o => o.type === 'Graphics' && o.input)
|
||||
if (!btnBg) return null // bouton non interactif (une autre carte déjà jouée ce tour)
|
||||
const worldPoint = btnBg.getWorldTransformMatrix().transformPoint(
|
||||
btnBg.input.hitArea.centerX, btnBg.input.hitArea.centerY
|
||||
)
|
||||
return { x: worldPoint.x, y: worldPoint.y }
|
||||
}, eventId)
|
||||
if (!btnCenter) return false
|
||||
|
||||
await page.mouse.click(box.x + btnCenter.x, box.y + btnCenter.y)
|
||||
await page.waitForTimeout(250)
|
||||
|
||||
for (const teamIndex of targetTeamIndexes) {
|
||||
|
||||
Reference in new issue
Block a user