btc_horse/btc-UI/src/app/components/selection.service/selection.service.ts

247 lines
8.8 KiB
TypeScript

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { SharedStateService } from '../../service/shared-state.service';
export interface SelectionData {
label: string;
numbers: (number | string)[];
value: number;
total: number;
isBoxed?: boolean;
}
@Injectable({ providedIn: 'root' })
export class SelectionService {
private selections = new BehaviorSubject<SelectionData[]>([]);
selections$ = this.selections.asObservable();
private currentRow = new BehaviorSubject<SelectionData>({
label: '',
numbers: [],
value: 0,
total: 0,
isBoxed: false
});
currentRow$ = this.currentRow.asObservable();
private runnerCount: number = 12; // Fallback
constructor(private sharedStateService: SharedStateService) {
this.sharedStateService.runnerCount$.subscribe(count => {
this.runnerCount = count || 12; // Update runner count dynamically
});
}
updatePartial(update: Partial<SelectionData>) {
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;
// Enforce ticket number limit (1-100)
if (value > 100) {
updated.value = 100;
}
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':
case 'WSP': {
if (numbers.includes('F')) {
updated.total = this.runnerCount * value * 10;
} else {
updated.total = numbers.filter(n => typeof n === 'number').length * value * 10;
}
break;
}
case 'FOR':
case 'EXA': {
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') ? this.runnerCount : group1.filter(n => typeof n === 'number').length;
const group2Count = group2.includes('F') ? this.runnerCount : 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 = group1.includes('F') || group2.includes('F') ? this.runnerCount : new Set(allNums).size;
combinations = uniqueCount >= 2 ? this.calculatePermutations(uniqueCount) : 0;
updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : [])];
} else {
combinations = group1Count * group2Count;
updated.numbers = [...group1, ...(group2.length ? ['-', ...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 group1Count = group1.includes('F') ? this.runnerCount : group1.filter(n => typeof n === 'number').length;
const group2Count = group2.includes('F') ? this.runnerCount : 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 = group1.includes('F') || group2.includes('F') ? this.runnerCount : new Set(allNums).size;
combinations = uniqueCount >= 2 ? this.calculateCombinations(uniqueCount) : 0;
updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : [])];
} else {
combinations = (group1Count * group2Count) ;
updated.numbers = [...group1, ...(group2.length ? ['-', ...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(this.runnerCount, 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 {
const group1Count = group1.includes('F') ? this.runnerCount : group1.filter(n => typeof n === 'number').length;
const group2Count = group2.includes('F') ? this.runnerCount : group2.filter(n => typeof n === 'number').length;
const group3Count = group3.includes('F') ? this.runnerCount : group3.filter(n => typeof n === 'number').length;
combinations = group1Count * group2Count * group3Count;
}
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') ? this.runnerCount : 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;
const currentSelections = this.selections.value;
const totalAmount = currentSelections.reduce((sum, sel) => sum + sel.total, 0);
if (totalAmount + completed.total > 5000) return;
this.selections.next([...currentSelections, { ...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;
}
}
}