Add configurable bench size based on RulesConfig
Extends the RulesConfig support in the frontend game board to also honor the bench.max_size setting: - Add benchSize option to LayoutOptions interface - Update calculateLandscapeLayout to use configurable bench count - Update calculatePortraitLayout to use configurable bench count - Update StateRenderer to pass benchSize from rules_config.bench.max_size - Add benchSize computed property to game store - Add 7 tests for configurable bench size behavior The layout now generates the correct number of bench slots based on the rules config (defaults to 5 for backwards compatibility). Bench slots remain horizontally centered regardless of count. https://claude.ai/code/session_01AAxKmpq2AGde327eX1nzUC
This commit is contained in:
parent
7885b272a4
commit
c430a43e19
@ -698,3 +698,127 @@ describe('calculateLayout - prize zone options', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// =============================================================================
|
||||
// Conditional Bench Size Tests (RulesConfig support)
|
||||
// =============================================================================
|
||||
|
||||
describe('calculateLayout - bench size options', () => {
|
||||
it('returns default 5 bench slots when no options provided', () => {
|
||||
/**
|
||||
* Test backwards compatibility with existing code.
|
||||
*
|
||||
* When called without options, the layout should generate 5 bench slots
|
||||
* for each player (standard Pokemon TCG bench size).
|
||||
*/
|
||||
const layout = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT)
|
||||
|
||||
expect(layout.myBench).toHaveLength(BENCH_SIZE)
|
||||
expect(layout.oppBench).toHaveLength(BENCH_SIZE)
|
||||
})
|
||||
|
||||
it('returns correct bench count with custom benchSize option', () => {
|
||||
/**
|
||||
* Test that bench zones can have a custom count.
|
||||
*
|
||||
* Some rule variants may use fewer bench slots (e.g., 3 for a faster game).
|
||||
* The layout should generate exactly the requested number of slots.
|
||||
*/
|
||||
const layout = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, {
|
||||
benchSize: 3,
|
||||
})
|
||||
|
||||
expect(layout.myBench).toHaveLength(3)
|
||||
expect(layout.oppBench).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('works with larger bench sizes', () => {
|
||||
/**
|
||||
* Test that larger bench sizes are supported.
|
||||
*
|
||||
* Some custom variants might use more than 5 bench slots.
|
||||
*/
|
||||
const layout = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, {
|
||||
benchSize: 8,
|
||||
})
|
||||
|
||||
expect(layout.myBench).toHaveLength(8)
|
||||
expect(layout.oppBench).toHaveLength(8)
|
||||
})
|
||||
|
||||
it('works correctly in portrait mode with custom bench size', () => {
|
||||
/**
|
||||
* Test that portrait mode also respects bench size settings.
|
||||
*
|
||||
* The portrait layout should also generate the correct number
|
||||
* of bench positions based on the benchSize option.
|
||||
*/
|
||||
const layout = calculateLayout(PORTRAIT_WIDTH, PORTRAIT_HEIGHT, {
|
||||
benchSize: 3,
|
||||
})
|
||||
|
||||
expect(layout.myBench).toHaveLength(3)
|
||||
expect(layout.oppBench).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('bench positions are valid when using custom size', () => {
|
||||
/**
|
||||
* Test that generated bench positions are all valid.
|
||||
*
|
||||
* Each bench slot should have positive dimensions and valid coordinates.
|
||||
*/
|
||||
const layout = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, {
|
||||
benchSize: 4,
|
||||
})
|
||||
|
||||
for (const bench of layout.myBench) {
|
||||
expect(isValidZone(bench), 'Player bench zone should be valid').toBe(true)
|
||||
}
|
||||
for (const bench of layout.oppBench) {
|
||||
expect(isValidZone(bench), 'Opponent bench zone should be valid').toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it('bench slots are centered properly with different sizes', () => {
|
||||
/**
|
||||
* Test that bench zones remain centered regardless of count.
|
||||
*
|
||||
* Whether using 3, 5, or 8 bench slots, they should all be
|
||||
* horizontally centered on the screen.
|
||||
*/
|
||||
const layout3 = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, { benchSize: 3 })
|
||||
const layout5 = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, { benchSize: 5 })
|
||||
const layout7 = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, { benchSize: 7 })
|
||||
|
||||
const centerX = LANDSCAPE_WIDTH / 2
|
||||
|
||||
// Calculate average X position of each player's bench
|
||||
const avg3 = layout3.myBench.reduce((sum, b) => sum + b.x, 0) / 3
|
||||
const avg5 = layout5.myBench.reduce((sum, b) => sum + b.x, 0) / 5
|
||||
const avg7 = layout7.myBench.reduce((sum, b) => sum + b.x, 0) / 7
|
||||
|
||||
// All should be centered
|
||||
expect(avg3).toBeCloseTo(centerX, 0)
|
||||
expect(avg5).toBeCloseTo(centerX, 0)
|
||||
expect(avg7).toBeCloseTo(centerX, 0)
|
||||
})
|
||||
|
||||
it('can combine benchSize with prize options', () => {
|
||||
/**
|
||||
* Test that bench and prize options work together.
|
||||
*
|
||||
* Custom rules might have both custom bench size and prize count,
|
||||
* and both should be applied correctly.
|
||||
*/
|
||||
const layout = calculateLayout(LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT, {
|
||||
benchSize: 3,
|
||||
usePrizeCards: true,
|
||||
prizeCount: 4,
|
||||
})
|
||||
|
||||
expect(layout.myBench).toHaveLength(3)
|
||||
expect(layout.oppBench).toHaveLength(3)
|
||||
expect(layout.myPrizes).toHaveLength(4)
|
||||
expect(layout.oppPrizes).toHaveLength(4)
|
||||
})
|
||||
})
|
||||
|
||||
@ -71,6 +71,8 @@ export interface LayoutOptions {
|
||||
usePrizeCards?: boolean
|
||||
/** Number of prize cards when using classic mode. Defaults to 6. */
|
||||
prizeCount?: number
|
||||
/** Number of bench slots. Defaults to 5. */
|
||||
benchSize?: number
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
@ -162,8 +164,9 @@ function calculateLandscapeLayout(
|
||||
): BoardLayout {
|
||||
const usePrizeCards = options?.usePrizeCards ?? true
|
||||
const prizeCount = options?.prizeCount ?? PRIZE_SIZE
|
||||
const benchSize = options?.benchSize ?? BENCH_SIZE
|
||||
const scale = getScaleFactor(width, height)
|
||||
|
||||
|
||||
// Scale card dimensions
|
||||
const cardW = Math.round(CARD_WIDTH_MEDIUM * scale)
|
||||
const cardH = Math.round(CARD_HEIGHT_MEDIUM * scale)
|
||||
@ -171,7 +174,7 @@ function calculateLandscapeLayout(
|
||||
const smallCardH = Math.round(CARD_HEIGHT_SMALL * scale)
|
||||
const padding = Math.round(ZONE_PADDING * scale)
|
||||
const edgePad = Math.round(EDGE_PADDING * scale)
|
||||
|
||||
|
||||
// Vertical layout divisions
|
||||
const centerY = height / 2
|
||||
const playerY = centerY + (height * 0.18) // Player active zone
|
||||
@ -179,11 +182,11 @@ function calculateLandscapeLayout(
|
||||
const benchOffsetY = cardH + padding // Bench below/above active (full card height + padding)
|
||||
const handY = height - edgePad - cardH / 2 // Hand at bottom
|
||||
const oppHandY = edgePad + cardH / 2 // Opponent hand at top
|
||||
|
||||
|
||||
// Horizontal layout - center of screen
|
||||
const centerX = width / 2
|
||||
const benchSpacing = cardW + padding
|
||||
const totalBenchWidth = (BENCH_SIZE - 1) * benchSpacing
|
||||
const totalBenchWidth = (benchSize - 1) * benchSpacing
|
||||
|
||||
// Deck/discard positions (right side)
|
||||
const deckX = width - edgePad - cardW / 2 - padding
|
||||
@ -208,7 +211,7 @@ function calculateLandscapeLayout(
|
||||
rotation: 0,
|
||||
},
|
||||
|
||||
myBench: Array.from({ length: BENCH_SIZE }, (_, i) => ({
|
||||
myBench: Array.from({ length: benchSize }, (_, i) => ({
|
||||
x: centerX - totalBenchWidth / 2 + i * benchSpacing,
|
||||
y: playerY + benchOffsetY,
|
||||
width: cardW,
|
||||
@ -270,7 +273,7 @@ function calculateLandscapeLayout(
|
||||
rotation: Math.PI, // Upside down
|
||||
},
|
||||
|
||||
oppBench: Array.from({ length: BENCH_SIZE }, (_, i) => ({
|
||||
oppBench: Array.from({ length: benchSize }, (_, i) => ({
|
||||
x: centerX + totalBenchWidth / 2 - i * benchSpacing, // Reversed order
|
||||
y: oppY - benchOffsetY, // Above active for opponent
|
||||
width: cardW,
|
||||
@ -348,8 +351,9 @@ function calculatePortraitLayout(
|
||||
): BoardLayout {
|
||||
const usePrizeCards = options?.usePrizeCards ?? true
|
||||
const prizeCount = options?.prizeCount ?? PRIZE_SIZE
|
||||
const benchSize = options?.benchSize ?? BENCH_SIZE
|
||||
const scale = getScaleFactor(width, height) * 0.85 // Slightly smaller for portrait
|
||||
|
||||
|
||||
// Scale card dimensions - use smaller cards in portrait
|
||||
const cardW = Math.round(CARD_WIDTH_SMALL * scale * 1.2)
|
||||
const cardH = Math.round(CARD_HEIGHT_SMALL * scale * 1.2)
|
||||
@ -357,7 +361,7 @@ function calculatePortraitLayout(
|
||||
const smallCardH = Math.round(CARD_HEIGHT_SMALL * scale * 0.9)
|
||||
const padding = Math.round(ZONE_PADDING * scale * 0.8)
|
||||
const edgePad = Math.round(EDGE_PADDING * scale * 0.8)
|
||||
|
||||
|
||||
// Vertical layout divisions
|
||||
const centerY = height / 2
|
||||
const playerY = centerY + (height * 0.15)
|
||||
@ -365,11 +369,11 @@ function calculatePortraitLayout(
|
||||
const benchOffsetY = cardH + padding // Full card height + padding
|
||||
const handY = height - edgePad - cardH / 2
|
||||
const oppHandY = edgePad + cardH / 2
|
||||
|
||||
|
||||
// Horizontal layout - tighter spacing for portrait
|
||||
const centerX = width / 2
|
||||
const benchSpacing = Math.min(cardW + padding, (width - edgePad * 2) / BENCH_SIZE)
|
||||
const totalBenchWidth = (BENCH_SIZE - 1) * benchSpacing
|
||||
const benchSpacing = Math.min(cardW + padding, (width - edgePad * 2) / benchSize)
|
||||
const totalBenchWidth = (benchSize - 1) * benchSpacing
|
||||
|
||||
// Deck/discard positions (right side, more compact)
|
||||
// Use reduced size (0.8) for these zones
|
||||
@ -395,7 +399,7 @@ function calculatePortraitLayout(
|
||||
rotation: 0,
|
||||
},
|
||||
|
||||
myBench: Array.from({ length: BENCH_SIZE }, (_, i) => ({
|
||||
myBench: Array.from({ length: benchSize }, (_, i) => ({
|
||||
x: centerX - totalBenchWidth / 2 + i * benchSpacing,
|
||||
y: playerY + benchOffsetY,
|
||||
width: cardW * 0.9, // Slightly smaller bench cards
|
||||
@ -456,7 +460,7 @@ function calculatePortraitLayout(
|
||||
rotation: Math.PI,
|
||||
},
|
||||
|
||||
oppBench: Array.from({ length: BENCH_SIZE }, (_, i) => ({
|
||||
oppBench: Array.from({ length: benchSize }, (_, i) => ({
|
||||
x: centerX + totalBenchWidth / 2 - i * benchSpacing,
|
||||
y: oppY - benchOffsetY,
|
||||
width: cardW * 0.9,
|
||||
|
||||
@ -132,6 +132,7 @@ export class StateRenderer {
|
||||
const layoutOptions: LayoutOptions = {
|
||||
usePrizeCards: state.rules_config?.prizes.use_prize_cards ?? false,
|
||||
prizeCount: state.rules_config?.prizes.count ?? 4,
|
||||
benchSize: state.rules_config?.bench.max_size ?? 5,
|
||||
}
|
||||
|
||||
// Calculate layout
|
||||
|
||||
@ -236,6 +236,11 @@ export const useGameStore = defineStore('game', () => {
|
||||
rulesConfig.value?.prizes.count ?? 4
|
||||
)
|
||||
|
||||
/** Maximum number of Pokemon on the bench */
|
||||
const benchSize = computed<number>(() =>
|
||||
rulesConfig.value?.bench.max_size ?? 5
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Computed - Card Lookup
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -361,6 +366,7 @@ export const useGameStore = defineStore('game', () => {
|
||||
rulesConfig,
|
||||
usePrizeCards,
|
||||
prizeCount,
|
||||
benchSize,
|
||||
|
||||
// Card lookup
|
||||
lookupCard,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user