import { GameField, BoardData } from '../../Domain/Game/GameAggregate'; import { logOther, logError } from '../Services/Logger'; interface SpecialFieldInfo { position: number; type: 'positive' | 'negative' | 'luck'; } export class BoardGenerationService { async generateBoard( positiveFieldCount: number, negativeFieldCount: number, luckFieldCount: number ): Promise { // Pattern-based approach has 100% success rate, no retry needed const result = this.generateSingleAttempt(positiveFieldCount, negativeFieldCount, luckFieldCount); logOther('Pattern-based board generation completed', { totalFields: result.fields.length, specialFields: result.fields.filter((f: GameField) => f.type !== 'regular').length, positiveFields: result.fields.filter((f: GameField) => f.type === 'positive').length, negativeFields: result.fields.filter((f: GameField) => f.type === 'negative').length, luckFields: result.fields.filter((f: GameField) => f.type === 'luck').length }); return result; } private generateSingleAttempt( positiveFieldCount: number, negativeFieldCount: number, luckFieldCount: number ): BoardData { // Step 1: Choose special field positions const specialFieldPositions = this.chooseSpecialFieldPositions( positiveFieldCount, negativeFieldCount, luckFieldCount ); // Step 2: Calculate step values using pattern-based approach const fields = this.calculatePatternBasedStepValues(specialFieldPositions); return { fields }; } private chooseSpecialFieldPositions( positiveFieldCount: number, negativeFieldCount: number, luckFieldCount: number ): SpecialFieldInfo[] { const totalSpecial = positiveFieldCount + negativeFieldCount + luckFieldCount; const specialFields: SpecialFieldInfo[] = []; // Generate unique random positions const positions = new Set(); while (positions.size < totalSpecial) { const position = Math.floor(Math.random() * 100) + 1; // 1-100 positions.add(position); } // Convert to sorted array const sortedPositions = Array.from(positions).sort((a, b) => a - b); // Distribute types randomly const types: ('positive' | 'negative' | 'luck')[] = [ ...Array(positiveFieldCount).fill('positive'), ...Array(negativeFieldCount).fill('negative'), ...Array(luckFieldCount).fill('luck') ]; // Shuffle types for (let i = types.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [types[i], types[j]] = [types[j], types[i]]; } sortedPositions.forEach((position, index) => { specialFields.push({ position, type: types[index] || 'positive' }); }); return specialFields; } private calculatePatternBasedStepValues(specialFields: SpecialFieldInfo[]): GameField[] { // Initialize all fields as regular const fields: GameField[] = Array.from({ length: 100 }, (_, i) => ({ position: i + 1, type: 'regular' as const })); // Update special fields with pattern-based step values specialFields.forEach(specialField => { const fieldIndex = specialField.position - 1; // Convert to 0-based index fields[fieldIndex].type = specialField.type; if (specialField.type === 'luck') { // Luck fields don't need step values return; } // Calculate step values based on position rules let maxStepValue: number; let minStepValue: number; if (specialField.position <= 80) { // Positions 1-80: step values can be ±20 maxStepValue = 20; minStepValue = -20; } else { // Positions 81-100: step values can be -30 to +10 maxStepValue = 10; minStepValue = -30; } // Generate appropriate step value for field type if (specialField.type === 'positive') { // Positive fields: use positive step values (3-8 range for good gameplay) const stepValue = Math.floor(Math.random() * 6) + 3; // 3-8 fields[fieldIndex].stepValue = Math.min(stepValue, maxStepValue); } else { // Negative fields: use negative step values (-3 to -8 range) const stepValue = -(Math.floor(Math.random() * 6) + 3); // -3 to -8 fields[fieldIndex].stepValue = Math.max(stepValue, minStepValue); } }); return fields; } // This method can be used by FieldEffectService for movement calculations public calculatePatternBasedMovement( currentPosition: number, stepValue: number, diceValue: number ): number { // Calculate pattern modifier based on current position const patternModifier = this.getPatternModifier(currentPosition, stepValue > 0); // Calculate final position: currentPosition + (stepValue × dice) + patternModifier const movement = stepValue * diceValue; let finalPosition = currentPosition + movement + patternModifier; // Ensure position stays within board bounds (1-100) if (finalPosition < 1) { finalPosition = 1; } else if (finalPosition > 100) { finalPosition = 100; } return finalPosition; } private getPatternModifier(position: number, positiveField: boolean): number { // Pattern modifiers for strategic complexity: // - Positions ending in 0 (10, 20, 30...): No modifier // - Positions ending in 5 (15, 25, 35...): ±3 modifier // - Positions divisible by 3 (9, 12, 21...): ±2 modifier // - Odd positions (1, 7, 11...): ±1 modifier // - Other even positions: No modifier if (position % 10 === 0) { return 0; // Positions ending in 0 } else if (position % 10 === 5) { return positiveField ? 3 : -3; // Positions ending in 5 } else if (position % 3 === 0) { return positiveField ? 2 : -2; // Divisible by 3 } else if (position % 2 === 1) { return positiveField ? 1 : -1; // Odd positions } else { return 0; // Other even positions } } private validate20_30Rule(currentPosition: number, targetPosition: number, distance: number): boolean { // Fields 1-85: max 20 fields in any direction if (currentPosition <= 85) { return distance <= 20; } // Fields 86-100: max 30 fields backward, max 20 fields forward if (currentPosition > 85) { if (targetPosition > currentPosition) { // Moving forward: max 20 fields return distance <= 20; } else { // Moving backward: max 30 fields return distance <= 30; } } return false; } }