feat(lottery): 扩展传统马来赔率、玩法结算与开注商短码支持
This commit is contained in:
@@ -24,9 +24,19 @@ final class PlayRuleEngine
|
||||
$playCode = (string) $line['play_code'];
|
||||
$dimension = $line['dimension'] ?? null;
|
||||
$digitSlot = $line['digit_slot'] ?? null;
|
||||
$selectionType = (string) ($line['selection_type'] ?? 'straight');
|
||||
$amount = (int) $line['amount'];
|
||||
$number = $this->normalizer->normalize($playCode, (string) $line['number'], is_string($dimension) ? $dimension : null);
|
||||
|
||||
$allowedSelectionTypes = match ((int) ($playConfig->dimension ?? 4)) {
|
||||
4 => ['straight', 'reverse', 'full_cover', 'full_play', 'half_play'],
|
||||
3 => ['straight', 'reverse', 'full_cover', 'full_play'],
|
||||
default => ['straight'],
|
||||
};
|
||||
if (! in_array($selectionType, $allowedSelectionTypes, true)) {
|
||||
throw new TicketOperationException('selection_type_not_allowed', ErrorCode::BetInvalidPlayInput->value);
|
||||
}
|
||||
|
||||
if ($amount < (int) $playConfig->min_bet_amount || $amount > (int) $playConfig->max_bet_amount) {
|
||||
throw new TicketOperationException('bet_amount_out_of_range', ErrorCode::WalletAmountExceedsLimit->value);
|
||||
}
|
||||
@@ -40,14 +50,17 @@ final class PlayRuleEngine
|
||||
}
|
||||
|
||||
$digitSlotInt = $digitSlot === null ? null : (int) $digitSlot;
|
||||
$combos = $this->expandToCombinations($playCode, $number, is_string($dimension) ? $dimension : null, $digitSlotInt);
|
||||
$combos = $this->expandToCombinations($playCode, $number, is_string($dimension) ? $dimension : null, $digitSlotInt, $selectionType);
|
||||
$combinationCount = count($combos);
|
||||
if ($combinationCount < 1) {
|
||||
throw new TicketOperationException('empty_combinations', ErrorCode::BetInvalidPlayInput->value);
|
||||
}
|
||||
if ($selectionType === 'full_cover' && $amount % $combinationCount !== 0) {
|
||||
throw new TicketOperationException('full_cover_amount_not_divisible', ErrorCode::BetInvalidPlayInput->value);
|
||||
}
|
||||
|
||||
$unitBetAmount = $this->resolveUnitBetAmount($playCode, $amount, $combinationCount);
|
||||
$totalBetAmount = $this->resolveTotalBetAmount($playCode, $amount, $unitBetAmount, $combinationCount);
|
||||
$unitBetAmount = $this->resolveUnitBetAmount($selectionType === 'full_cover' ? 'mbox' : $playCode, $amount, $combinationCount);
|
||||
$totalBetAmount = $this->resolveTotalBetAmount($selectionType === 'full_cover' ? 'mbox' : $playCode, $amount, $unitBetAmount, $combinationCount);
|
||||
$dimensionInt = $this->toDimensionInt(is_string($dimension) ? $dimension : null, $playConfig);
|
||||
$primaryOdds = $this->pickPrimaryOdds($oddsItems);
|
||||
$rebateRate = (float) $primaryOdds->rebate_rate;
|
||||
@@ -64,7 +77,7 @@ final class PlayRuleEngine
|
||||
'play_code' => $playCode,
|
||||
'dimension' => $this->toDimensionInt(is_string($dimension) ? $dimension : null, $playConfig),
|
||||
'digit_slot' => $digitSlotInt,
|
||||
'bet_mode' => $playConfig->bet_mode,
|
||||
'bet_mode' => $selectionType,
|
||||
'unit_bet_amount' => $unitBetAmount,
|
||||
'total_bet_amount' => $totalBetAmount,
|
||||
'rebate_rate_snapshot' => number_format($rebateRate, 4, '.', ''),
|
||||
@@ -83,6 +96,7 @@ final class PlayRuleEngine
|
||||
'play_code' => $playCode,
|
||||
'dimension' => $dimension,
|
||||
'digit_slot' => $digitSlotInt,
|
||||
'selection_type' => $selectionType,
|
||||
'combination_count' => $combinationCount,
|
||||
'rounding_refund_amount' => $playCode === 'mbox'
|
||||
? max(0, $amount - $totalBetAmount)
|
||||
@@ -102,14 +116,24 @@ final class PlayRuleEngine
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
private function expandToCombinations(string $playCode, string $number, ?string $dimension, ?int $digitSlot): array
|
||||
private function expandToCombinations(string $playCode, string $number, ?string $dimension, ?int $digitSlot, string $selectionType = 'straight'): array
|
||||
{
|
||||
if (in_array($playCode, ['big', 'small', 'pos_4a', 'pos_4b', 'pos_4c', 'pos_4d', 'pos_4e', 'four_any', 'four_top', 'four_lower'], true)) {
|
||||
return $this->expandSelection(str_split($number), $selectionType);
|
||||
}
|
||||
if (str_starts_with($playCode, 'pos_3')) {
|
||||
return collect($this->expandSelection(str_split($number), $selectionType))
|
||||
->flatMap(fn (string $value) => $this->expandSuffix($value, 3))
|
||||
->unique()->sort()->values()->all();
|
||||
}
|
||||
if (str_starts_with($playCode, 'pos_2')) {
|
||||
return collect($this->expandSuffix($number, 2))->unique()->sort()->values()->all();
|
||||
}
|
||||
|
||||
return match ($playCode) {
|
||||
'big', 'small', 'pos_4a', 'pos_4b', 'pos_4c', 'pos_4d', 'pos_4e', 'straight' => [$number],
|
||||
'straight' => [$number],
|
||||
'ibox', 'mbox', 'box' => $this->uniquePermutations($number),
|
||||
'roll' => $this->expandRoll($number),
|
||||
'pos_3a', 'pos_3b', 'pos_3c', 'pos_3abc' => $this->expandSuffix($number, 3),
|
||||
'pos_2a', 'pos_2b', 'pos_2c', 'pos_2abc' => $this->expandSuffix($number, 2),
|
||||
'head' => $this->expandHeadTail(true),
|
||||
'tail' => $this->expandHeadTail(false),
|
||||
'odd' => $this->expandOddEven($dimension, true),
|
||||
@@ -120,6 +144,29 @@ final class PlayRuleEngine
|
||||
};
|
||||
}
|
||||
|
||||
/** @param list<string> $digits @return list<string> */
|
||||
private function expandSelection(array $digits, string $selectionType): array
|
||||
{
|
||||
$original = implode('', $digits);
|
||||
if ($selectionType === 'straight') return [$original];
|
||||
if ($selectionType === 'reverse') return array_values(array_unique([$original, strrev($original)]));
|
||||
|
||||
$all = $this->uniquePermutations($original);
|
||||
if ($selectionType === 'full_cover' || $selectionType === 'full_play') return $all;
|
||||
if ($selectionType === 'half_play') {
|
||||
if (count($digits) < 2) return $all;
|
||||
$first = $digits[0];
|
||||
$second = $digits[1];
|
||||
if ($first === $second) return $all;
|
||||
return array_values(array_filter($all, function (string $value) use ($first, $second): bool {
|
||||
$firstIndex = array_search($first, str_split($value), true);
|
||||
$secondIndex = array_search($second, str_split($value), true);
|
||||
return $firstIndex !== false && $secondIndex !== false && $firstIndex < $secondIndex;
|
||||
}));
|
||||
}
|
||||
throw new TicketOperationException('selection_type_invalid', ErrorCode::BetInvalidPlayInput->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user