Backend Complete: Interface Refactoring & Service Container Enhancements
Repository Interface Optimization: - Created IBaseRepository.ts and IPaginatedRepository.ts - Refactored all 7 repository interfaces to extend base interfaces - Eliminated ~200 lines of redundant code (70% reduction) - Improved type safety and maintainability Dependency Injection Improvements: - Added EmailService and GameTokenService to DIContainer - Updated CreateUserCommandHandler constructor for DI - Updated RequestPasswordResetCommandHandler constructor for DI - Enhanced testability and service consistency Environment Configuration: - Created comprehensive .env.example with 40+ variables - Organized into 12 logical sections (Database, Security, Email, etc.) - Added security guidelines and best practices - Documented all backend environment requirements Documentation: - Added comprehensive codebase review - Created refactoring summary report - Added frontend implementation guide Impact: Improved code quality, reduced maintenance overhead, enhanced developer experience
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
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<BoardData> {
|
||||
// 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<number>();
|
||||
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);
|
||||
|
||||
// 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): 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 Math.random() < 0.5 ? 3 : -3; // Positions ending in 5
|
||||
} else if (position % 3 === 0) {
|
||||
return Math.random() < 0.5 ? 2 : -2; // Divisible by 3
|
||||
} else if (position % 2 === 1) {
|
||||
return Math.random() < 0.5 ? 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user