import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; export interface SelectionData { label: string; numbers: (number | string)[]; value: number; total: number; isBoxed?: boolean; } @Injectable({ providedIn: 'root' }) export class SelectionService { private selections = new BehaviorSubject([]); selections$ = this.selections.asObservable(); private currentRow = new BehaviorSubject({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); currentRow$ = this.currentRow.asObservable(); updatePartial(update: Partial) { const current = this.currentRow.value; const updated: SelectionData = { ...current, ...update }; const label = updated.label; const value = updated.value; const numbers = updated.numbers || []; const isBoxed = updated.isBoxed ?? false; updated.total = 0; if ((numbers.length > 0 || numbers.includes('F')) && value > 0 && label) { switch (label) { case 'WIN': case 'SHP': case 'THP': case 'PLC': case 'SHW': { if (numbers.includes('F')) { updated.total = 12 * value * 10; } else { updated.total = numbers.filter(n => typeof n === 'number').length * value * 10; } break; } case 'FOR': { const dashIndex = numbers.indexOf('-'); const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex); const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1); const group1Count = group1.includes('F') ? 12 : group1.filter(n => typeof n === 'number').length; const group2Count = group2.includes('F') ? 12 : group2.filter(n => typeof n === 'number').length; let combinations = 0; if (isBoxed) { const allNums = [...group1, ...group2].filter(n => typeof n === 'number') as number[]; const uniqueCount = new Set(allNums).size + (group1.includes('F') || group2.includes('F') ? 12 : 0); combinations = uniqueCount >= 2 ? this.calculatePermutations(uniqueCount) : 0; updated.numbers = [...group1, '-', ...group2]; } else { if (group1.includes('F') && group2.includes('F')) { combinations = 12 * 11; // nP2 for 12 runners } else if (group1.includes('F') || group2.includes('F')) { const nonFieldGroup = group1.includes('F') ? group2 : group1; const nonFieldNumbers = nonFieldGroup.filter(n => typeof n === 'number') as number[]; combinations = 12 * nonFieldNumbers.length; if (nonFieldNumbers.length > 0) { combinations -= nonFieldNumbers.length; // Subtract overlapping runners } } else { const numGroup1 = group1.filter(n => typeof n === 'number') as number[]; const numGroup2 = group2.filter(n => typeof n === 'number') as number[]; for (const a of numGroup1) { for (const b of numGroup2) { if (a !== b) combinations++; } } } updated.numbers = [...group1, '-', ...group2]; } updated.total = combinations * value * 10; break; } case 'QUI': { const dashIndex = numbers.indexOf('-'); const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex); const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1); const group1_is_field = group1.includes('F'); const group2_is_field = group2.includes('F'); const group1Numbers = group1.filter(n => typeof n === 'number') as number[]; const group2Numbers = group2.filter(n => typeof n === 'number') as number[]; let combinations = 0; if (isBoxed) { const allNums = [...group1, ...group2].filter(n => typeof n === 'number') as number[]; const uniqueCount = new Set(allNums).size + (group1_is_field || group2_is_field ? 12 : 0); combinations = uniqueCount >= 2 ? (uniqueCount * (uniqueCount - 1)) / 2 : 0; } else if (group1_is_field && group2_is_field) { combinations = (12 * 11) / 2; // C(12,2) } else if (group1_is_field || group2_is_field) { const fieldSide = group1_is_field ? group1Numbers : group2Numbers; const nonFieldNumbers = (group1_is_field ? group2 : group1).filter(n => typeof n === 'number') as number[]; const pairSet = new Set(); const fieldNumbers = fieldSide.length > 0 ? fieldSide : Array.from({ length: 12 }, (_, i) => i + 1); for (let j of nonFieldNumbers) { for (let i of fieldNumbers) { if (i !== j) { let min = Math.min(i, j); let max = Math.max(i, j); pairSet.add(`${min},${max}`); } } } combinations = pairSet.size; } else { const pairSet = new Set(); for (let a of group1Numbers) { for (let b of group2Numbers) { if (a === b) continue; let key = a < b ? `${a},${b}` : `${b},${a}`; pairSet.add(key); } } combinations = pairSet.size; } updated.numbers = [...group1, '-', ...group2]; updated.total = combinations * value * 10; break; } case 'TAN': { const dashIndices = numbers .map((n, idx) => (n === '-' ? idx : -1)) .filter(idx => idx !== -1); if (dashIndices.length < 2 && !isBoxed) break; const group1 = dashIndices.length > 0 ? numbers.slice(0, dashIndices[0]) : numbers; const group2 = dashIndices.length > 0 ? numbers.slice(dashIndices[0] + 1, dashIndices[1] || numbers.length) : []; const group3 = dashIndices.length > 1 ? numbers.slice(dashIndices[1] + 1) : []; let combinations = 0; if (isBoxed) { if (numbers.includes('F')) { combinations = this.calculatePermutationsN(12, 3); } else { const allNums = [...group1, ...group2, ...group3].filter(n => typeof n === 'number') as number[]; combinations = allNums.length >= 3 ? this.calculatePermutationsN(allNums.length, 3) : 0; } } else { // Define possible values for each group: 'F' means all runners (1 to 12), else only selected numbers const runners = Array.from({ length: 12 }, (_, i) => i + 1); const group1Vals = group1.includes('F') ? runners : group1.filter(n => typeof n === 'number') as number[]; const group2Vals = group2.includes('F') ? runners : group2.filter(n => typeof n === 'number') as number[]; const group3Vals = group3.includes('F') ? runners : group3.filter(n => typeof n === 'number') as number[]; combinations = 0; for (const i of group1Vals) { for (const j of group2Vals) { if (i === j) continue; for (const k of group3Vals) { if (i === k || j === k) continue; combinations++; } } } } updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : []), ...(group3.length ? ['-', ...group3] : [])]; updated.total = combinations * value * 10; break; } case 'TRE': case 'MJP': case 'JKP': { const legs = this.splitToLegs(numbers, this.getLegCount(label)); const requiredLegs = this.getLegCount(label); const legCounts = legs.map(leg => leg.includes('F') ? 12 : leg.filter(n => typeof n === 'number').length); const filledLegs = legs.filter(leg => leg.length > 0).length; if (filledLegs >= requiredLegs - 1) { const combinations = legCounts.reduce((acc, count) => acc * (count || 1), 1); updated.total = combinations * value * 10; } break; } // ⚪ BOX logic default: { const combCount = isBoxed ? this.calculatePermutations(numbers.filter(n => typeof n === 'number').length) : this.calculateCombinations(numbers.filter(n => typeof n === 'number').length); updated.total = combCount * value * 10; break; } } } this.currentRow.next(updated); } finalizeCurrentRow() { const completed = this.currentRow.value; if (!completed.label || completed.numbers.length === 0 || completed.value <= 0) return; this.selections.next([...this.selections.value, { ...completed }]); this.currentRow.next({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); } clearSelections() { this.selections.next([]); this.currentRow.next({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); } private calculateCombinations(n: number): number { return n >= 2 ? (n * (n - 1)) / 2 : 0; } private calculatePermutations(n: number): number { return n >= 2 ? n * (n - 1) : 0; } private calculatePermutationsN(n: number, r: number): number { if (n < r) return 0; let result = 1; for (let i = 0; i < r; i++) { result *= (n - i); } return result; } private splitToLegs(numbers: (number | string)[], legCount: number): (number | string)[][] { const result: (number | string)[][] = []; let currentLeg: (number | string)[] = []; let separatorCount = 0; for (const item of numbers) { if (item === '/') { if (currentLeg.length > 0) { result.push([...currentLeg]); currentLeg = []; separatorCount++; } if (separatorCount >= legCount - 1) break; } else { currentLeg.push(item); } } // Push the last leg if it has numbers if (currentLeg.length > 0) { result.push([...currentLeg]); } // Fill remaining legs with empty arrays if needed while (result.length < legCount) { result.push([]); } return result.slice(0, legCount); } private getLegCount(label: string): number { switch (label) { case 'TRE': return 3; case 'MJP': return 4; case 'JKP': return 5; default: return 3; } } }