diff --git a/btc-UI/src/app/components/selection.service/selection.service.ts b/btc-UI/src/app/components/selection.service/selection.service.ts index 08a5ca8..92a71c2 100644 --- a/btc-UI/src/app/components/selection.service/selection.service.ts +++ b/btc-UI/src/app/components/selection.service/selection.service.ts @@ -29,41 +29,57 @@ export class SelectionService { const label = updated.label; const value = updated.value; - const numbers = (updated.numbers || []).filter(n => typeof n === 'number') as number[]; + const numbers = updated.numbers || []; const isBoxed = updated.isBoxed ?? false; updated.total = 0; - if ((numbers.length > 0 || updated.numbers.includes('F')) && value > 0 && label) { + if ((numbers.length > 0 || numbers.includes('F')) && value > 0 && label) { switch (label) { case 'WIN': case 'SHP': case 'THP': case 'PLC': case 'SHW': { - if (updated.numbers.includes('F')) { - updated.total = 12 * value * 10; // 'F' represents 12 runners + if (numbers.includes('F')) { + updated.total = 12 * value * 10; } else { - updated.total = numbers.length * value * 10; + updated.total = numbers.filter(n => typeof n === 'number').length * value * 10; } break; } case 'FOR': { - const dashIndex = updated.numbers.indexOf('-'); - const group1 = updated.numbers.slice(0, dashIndex).filter(n => typeof n === 'number') as number[]; - const group2 = updated.numbers.slice(dashIndex + 1).filter(n => typeof n === 'number') as number[]; + 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 uniq = Array.from(new Set([...group1, ...group2])); - combinations = this.calculatePermutations(uniq.length); // nP2 - updated.numbers = [...uniq, '-', ...uniq]; + 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 { - for (const a of group1) { - for (const b of group2) { - if (a !== b) combinations++; + 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]; @@ -74,33 +90,42 @@ export class SelectionService { } case 'QUI': { - const dashIndex = updated.numbers.indexOf('-'); - const group1 = updated.numbers.slice(0, dashIndex).filter(n => typeof n === 'number') as number[]; - const group2 = updated.numbers.slice(dashIndex + 1).filter(n => typeof n === 'number') as number[]; + 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; - const set1 = new Set(group1); - const set2 = new Set(group2); - const uniqueUnion = new Set([...group1, ...group2]); - function setsAreEqual(a: Set, b: Set): boolean { - if (a.size !== b.size) return false; - for (let v of a) if (!b.has(v)) return false; - return true; - } - - if (isBoxed || setsAreEqual(set1, set2)) { - combinations = uniqueUnion.size >= 2 ? (uniqueUnion.size * (uniqueUnion.size - 1)) / 2 : 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 ? (uniqueCount * (uniqueCount - 1)) / 2 : 0; } else { - const pairSet = new Set(); - for (let a of set1) { - for (let b of set2) { - if (a === b) continue; - let key = a < b ? `${a},${b}` : `${b},${a}`; - pairSet.add(key); + if (group1.includes('F') && group2.includes('F')) { + combinations = (12 * 11) / 2; // nC2 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) / 2; + if (nonFieldNumbers.length > 0) { + const unique = new Set(nonFieldNumbers); + combinations = unique.size * 11; // Each number pairs with 11 others } + } else { + const numGroup1 = group1.filter(n => typeof n === 'number') as number[]; + const numGroup2 = group2.filter(n => typeof n === 'number') as number[]; + const pairSet = new Set(); + for (let a of numGroup1) { + for (let b of numGroup2) { + if (a === b) continue; + let key = a < b ? `${a},${b}` : `${b},${a}`; + pairSet.add(key); + } + } + combinations = pairSet.size; } - combinations = pairSet.size; } updated.numbers = [...group1, '-', ...group2]; @@ -109,49 +134,100 @@ export class SelectionService { } case 'TAN': { - const dashIndices = updated.numbers + const dashIndices = numbers .map((n, idx) => (n === '-' ? idx : -1)) .filter(idx => idx !== -1); - if (dashIndices.length < 2) break; // not ready yet + if (dashIndices.length < 2 && !isBoxed) break; - const group1 = updated.numbers.slice(0, dashIndices[0]).filter(n => typeof n === 'number') as number[]; - const group2 = updated.numbers.slice(dashIndices[0] + 1, dashIndices[1]).filter(n => typeof n === 'number') as number[]; - const group3 = updated.numbers.slice(dashIndices[1] + 1).filter(n => typeof n === 'number') as number[]; + 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) : []; + 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; + const group3Count = group3.includes('F') ? 12 : group3.filter(n => typeof n === 'number').length; let combinations = 0; if (isBoxed) { - // Boxed: all unique numbers, nP3 - const allNums = Array.from(new Set([...group1, ...group2, ...group3])); - combinations = allNums.length >= 3 ? this.calculatePermutationsN(allNums.length, 3) : 0; + 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 { - // Unboxed: only combinations where all chosen numbers are different - for (const a of group1) { - for (const b of group2) { - if (b === a) continue; - for (const c of group3) { - if (c === a || c === b) continue; - combinations++; + if (group1.includes('F') && group2.includes('F') && group3.includes('F')) { + combinations = 12 * 11 * 10; // nP3 + } else if (group1.includes('F') || group2.includes('F') || group3.includes('F')) { + const counts = [group1Count, group2Count, group3Count]; + combinations = counts.reduce((acc, count) => acc * count, 1); + if (group1.includes('F') && !group2.includes('F') && !group3.includes('F')) { + const numGroup2 = group2.filter(n => typeof n === 'number') as number[]; + const numGroup3 = group3.filter(n => typeof n === 'number') as number[]; + combinations = 0; + for (let i = 1; i <= 12; i++) { + for (const j of numGroup2) { + for (const k of numGroup3) { + if (i !== j && i !== k && j !== k) combinations++; + } + } + } + } else if (group2.includes('F') && !group1.includes('F') && !group3.includes('F')) { + const numGroup1 = group1.filter(n => typeof n === 'number') as number[]; + const numGroup3 = group3.filter(n => typeof n === 'number') as number[]; + combinations = 0; + for (const i of numGroup1) { + for (let j = 1; j <= 12; j++) { + for (const k of numGroup3) { + if (i !== j && i !== k && j !== k) combinations++; + } + } + } + } else if (group3.includes('F') && !group1.includes('F') && !group2.includes('F')) { + const numGroup1 = group1.filter(n => typeof n === 'number') as number[]; + const numGroup2 = group2.filter(n => typeof n === 'number') as number[]; + combinations = 0; + for (const i of numGroup1) { + for (const j of numGroup2) { + for (let k = 1; k <= 12; k++) { + if (i !== j && i !== k && j !== k) combinations++; + } + } + } + } + } else { + const numGroup1 = group1.filter(n => typeof n === 'number') as number[]; + const numGroup2 = group2.filter(n => typeof n === 'number') as number[]; + const numGroup3 = group3.filter(n => typeof n === 'number') as number[]; + for (const a of numGroup1) { + for (const b of numGroup2) { + if (b === a) continue; + for (const c of numGroup3) { + if (c === a || c === b) continue; + combinations++; + } } } } } + updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : []), ...(group3.length ? ['-', ...group3] : [])]; updated.total = combinations * value * 10; - updated.numbers = [...group1, '-', ...group2, '-', ...group3]; break; } case 'TRE': case 'MJP': case 'JKP': { - const legs = this.splitToLegs(updated.numbers, this.getLegCount(label)); + 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 = legs.reduce((acc, leg) => acc * (leg.length || 1), 1); + const combinations = legCounts.reduce((acc, count) => acc * (count || 1), 1); updated.total = combinations * value * 10; } break; @@ -160,8 +236,8 @@ export class SelectionService { // ⚪ BOX logic default: { const combCount = isBoxed - ? this.calculatePermutations(numbers.length) - : this.calculateCombinations(numbers.length); + ? this.calculatePermutations(numbers.filter(n => typeof n === 'number').length) + : this.calculateCombinations(numbers.filter(n => typeof n === 'number').length); updated.total = combCount * value * 10; break; } @@ -200,9 +276,9 @@ export class SelectionService { return result; } - private splitToLegs(numbers: (number | string)[], legCount: number): number[][] { - const result: number[][] = []; - let currentLeg: number[] = []; + 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) { @@ -213,7 +289,7 @@ export class SelectionService { separatorCount++; } if (separatorCount >= legCount - 1) break; - } else if (typeof item === 'number') { + } else { currentLeg.push(item); } } diff --git a/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts b/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts index 0f26cc9..e3824c8 100755 --- a/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts +++ b/btc-UI/src/app/components/touch-pad-menu/touch-pad-menu.component.ts @@ -14,6 +14,7 @@ export class TouchPadMenuComponent implements OnInit { public twoGroupLabels = ['FOR', 'QUI']; public multiLegLabels = ['TRE', 'MJP', 'JKP']; + public threeGroupLabels = ['TAN']; public allowedFieldLabels = ['WIN', 'SHP', 'THP', 'PLC', 'SHW']; labels: string[] = [ @@ -38,16 +39,16 @@ export class TouchPadMenuComponent implements OnInit { // ✅ Original TAN logic tanGroupStage = 0; - tanGroups: number[][] = [[], [], []]; + tanGroups: (number | string)[][] = [[], [], []]; // ✅ FOR/QUI logic isFirstGroupComplete = false; - firstGroup: number[] = []; - secondGroup: number[] = []; + firstGroup: (number | string)[] = []; + secondGroup: (number | string)[] = []; // ✅ Multi-leg logic (TRE, MJP, JKP) multiLegStage = 0; - multiLegGroups: number[][] = [[], [], [], [], []]; + multiLegGroups: (number | string)[][] = [[], [], [], [], []]; isBoxed: boolean = false; @@ -80,8 +81,7 @@ export class TouchPadMenuComponent implements OnInit { get showShashEnter(): boolean { const label = this.selectedLabel || ''; - const isBoxed = this.isBoxed; - if (['FOR', 'QUI', 'TAN'].includes(label) && isBoxed) { + if (['FOR', 'QUI', 'TAN'].includes(label) && this.isBoxed) { return false; } const specialLabels = ['FOR', 'QUI', 'TAN', 'EXA', 'WSP', 'TRE', 'MJP', 'JKP', '.']; @@ -109,10 +109,25 @@ export class TouchPadMenuComponent implements OnInit { (this.selectedNumbers.length > 0 || this.selectedNumbers.includes('F')) && this.padValue.length === 0; } + get isBoxToggleDisabled(): boolean { - return this.selectedLabel !== null && this.allowedFieldLabels.includes(this.selectedLabel); + if (!this.selectedLabel) return true; + + // ❌ Disallow box mode for these labels + const disallowedBoxLabels = ['WIN', 'SHP', 'THP', 'PLC', 'SHW']; + if (disallowedBoxLabels.includes(this.selectedLabel)) { + return true; + } + + // ❌ Disallow box toggle if FIELD (F) is selected + if (this.selectedNumbers.includes('F')) { + return true; + } + + return false; } + private chunk(array: T[], size: number): T[][] { return Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size) @@ -461,11 +476,38 @@ export class TouchPadMenuComponent implements OnInit { } canUseField(): boolean { - return this.selectedLabel !== null && this.allowedFieldLabels.includes(this.selectedLabel) && this.selectedNumbers.length === 0; + if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') { + if (!this.isFirstGroupComplete && this.firstGroup.length === 0) { + return true; + } + if (this.isFirstGroupComplete && this.secondGroup.length === 0) { + return true; + } + return false; + } + if (this.selectedLabel === 'TAN') { + if (this.tanGroups[this.tanGroupStage].length === 0) { + return true; + } + return false; + } + return ( + this.selectedLabel !== null && + this.allowedFieldLabels.includes(this.selectedLabel) && + this.selectedNumbers.length === 0 + ); + } + + openFieldModal() { + this.fieldModalOpen = true; + this.fieldInput = ''; + this.fieldFEntered = false; + } + + closeFieldModal() { + this.fieldModalOpen = false; } - openFieldModal() { this.fieldModalOpen = true; this.fieldInput = ''; this.fieldFEntered = false; } - closeFieldModal() { this.fieldModalOpen = false; } handleFieldKey(key: string) { if (key === 'BACK') { this.fieldInput = this.fieldInput.slice(0, -1); @@ -483,15 +525,53 @@ export class TouchPadMenuComponent implements OnInit { } confirmFieldEntry() { - if (this.fieldFEntered) { + if (!this.fieldFEntered || !this.selectedLabel) return; + + if (this.selectedLabel === 'FOR' || this.selectedLabel === 'QUI') { + if (!this.isFirstGroupComplete) { + this.firstGroup = ['F']; + this.selectedNumbers = ['F']; + } else { + this.secondGroup = ['F']; + this.selectedNumbers = [...this.firstGroup, '-', 'F']; + } + this.selectionService.updatePartial({ + label: this.selectedLabel, + numbers: [...this.selectedNumbers], + isBoxed: false, + value: 1 + }); + } else if (this.selectedLabel === 'TAN') { + if (this.isBoxed) { + this.selectedNumbers = ['F']; + this.selectionService.updatePartial({ + label: this.selectedLabel, + numbers: ['F'], + isBoxed: true, + value: 1 + }); + } else { + this.tanGroups[this.tanGroupStage] = ['F']; + const combined: (number | string)[] = [...this.tanGroups[0]]; + if (this.tanGroupStage > 0) combined.push('-', ...this.tanGroups[1]); + if (this.tanGroupStage > 1) combined.push('-', ...this.tanGroups[2]); + this.selectedNumbers = combined; + this.selectionService.updatePartial({ + label: this.selectedLabel, + numbers: [...this.selectedNumbers], + isBoxed: false, + value: 1 + }); + } + } else { this.selectedNumbers = ['F']; this.selectionService.updatePartial({ - label: this.selectedLabel!, + label: this.selectedLabel, numbers: ['F'], isBoxed: false, value: 1 }); - this.closeFieldModal(); } + this.closeFieldModal(); } } \ No newline at end of file