coin; if ($coin < $minCoin) { throw new ApiException('当前玩家余额'.$coin.'小于'.$minCoin.'无法继续游戏'); } $paid = (int) ($player->paid_ticket_count ?? 0); $free = (int) ($player->free_ticket_count ?? 0); if ($paid + $free <= 0) { throw new ApiException('抽奖券不足'); } $lotteryService = LotteryService::getOrCreate($playerId); $ticketType = LotteryService::drawTicketType($paid, $free); $config = $ticketType === self::LOTTERY_TYPE_PAID ? ($lotteryService->getConfigType0Id() ? DiceLotteryConfig::find($lotteryService->getConfigType0Id()) : null) : ($lotteryService->getConfigType1Id() ? DiceLotteryConfig::find($lotteryService->getConfigType1Id()) : null); if (!$config) { throw new ApiException('奖池配置不存在'); } // 先按奖池权重抽出档位 T1-T5 $tier = LotteryService::drawTierByWeights($config); // 生成 5 个 1-6 的点数,计算总和 roll_number(即本局摇到的点数) $rollArray = $this->generateRollArray(); $rollNumber = (int) array_sum($rollArray); // 索引范围为 0~25 共 26 个格子 $boardSize = 26; // 1. 根据抽到的档位,在 tier 相等的数据中任选一条,其 id 为结束索引 target_index $tierRewards = DiceRewardConfig::where('tier', $tier)->select()->toArray(); if (empty($tierRewards)) { Log::error("档位 {$tier} 无任何奖励配置"); throw new ApiException('该档位暂无奖励配置'); } $chosen = $tierRewards[array_rand($tierRewards)]; $reward = DiceRewardConfig::find($chosen['id']); if (!$reward) { throw new ApiException('奖励配置不存在'); } $targetIndex = (int) $reward->id; $targetIndex = (($targetIndex % $boardSize) + $boardSize) % $boardSize; // 2. 根据结果反推起始点 start_index(由 target_index 与方向反算) // 顺时针(direction=0): targetIndex = (startIndex + rollNumber) % 26 => startIndex = (targetIndex - rollNumber) % 26 // 逆时针(direction=1): targetIndex = (startIndex - rollNumber) % 26 => startIndex = (targetIndex + rollNumber) % 26 if ($direction === 0) { $startIndex = ($targetIndex - $rollNumber) % $boardSize; } else { $startIndex = ($targetIndex + $rollNumber) % $boardSize; } $startIndex = ($startIndex % $boardSize + $boardSize) % $boardSize; Log::info(sprintf( '摇取点数 roll_number=%d, 方向=%d, start_index=%d, target_index=%d', $rollNumber, $direction, $startIndex, $targetIndex )); $realEv = (float) $reward->real_ev; $winCoin = 100 + $realEv; // 赢取平台币 = 100 + DiceRewardConfig.real_ev $record = null; $configId = (int) $config->id; $rewardId = (int) $reward->id; $configName = (string) ($config->name ?? ''); $isTierT5 = (string) ($reward->tier ?? '') === 'T5'; try { Db::transaction(function () use ( $playerId, $configId, $rewardId, $configName, $ticketType, $winCoin, $realEv, $direction, $startIndex, $targetIndex, $rollArray, $isTierT5, &$record ) { $record = DicePlayRecord::create([ 'player_id' => $playerId, 'lottery_config_id' => $configId, 'lottery_type' => $ticketType, 'win_coin' => $winCoin, 'direction' => $direction, 'reward_config_id' => $rewardId, 'start_index' => $startIndex, 'target_index' => $targetIndex, 'roll_array' => is_array($rollArray) ? json_encode($rollArray) : $rollArray, 'lottery_name' => $configName, 'status' => self::RECORD_STATUS_SUCCESS, ]); $p = DicePlayer::find($playerId); if (!$p) { throw new \RuntimeException('玩家不存在'); } $coinBefore = (float) $p->coin; $coinAfter = $coinBefore + $winCoin; $p->coin = $coinAfter; $p->total_ticket_count = max(0, (int) $p->total_ticket_count - 1); if ($ticketType === self::LOTTERY_TYPE_PAID) { $p->paid_ticket_count = max(0, (int) $p->paid_ticket_count - 1); } else { $p->free_ticket_count = max(0, (int) $p->free_ticket_count - 1); } // 若本局中奖档位为 T5,则额外赠送 1 次免费抽奖次数(总次数也 +1),并记录抽奖券获取记录 if ($isTierT5) { $p->free_ticket_count = (int) $p->free_ticket_count + 1; $p->total_ticket_count = (int) $p->total_ticket_count + 1; DicePlayerTicketRecord::create([ 'player_id' => $playerId, 'free_ticket_count' => 1, 'remark' => '中奖结果为T5', ]); } $p->save(); // 累加彩金池盈利额度(累加值为 -real_ev)。若 dice_lottery_config 表有 ev 字段则执行 try { DiceLotteryConfig::where('id', $configId)->update([ 'ev' => Db::raw('IFNULL(ev,0) - ' . (float) $realEv), ]); } catch (\Throwable $_) { } DicePlayerWalletRecord::create([ 'player_id' => $playerId, 'coin' => $winCoin, 'type' => self::WALLET_TYPE_DRAW, 'wallet_before' => $coinBefore, 'wallet_after' => $coinAfter, 'remark' => '抽奖|play_record_id=' . $record->id, ]); }); } catch (\Throwable $e) { if ($record === null) { try { $record = DicePlayRecord::create([ 'player_id' => $playerId, 'lottery_config_id' => $configId ?? 0, 'lottery_type' => $ticketType, 'win_coin' => 0, 'direction' => $direction, 'reward_config_id' => 0, 'start_index' => $startIndex, 'target_index' => 0, 'roll_array' => '[]', 'status' => self::RECORD_STATUS_TIMEOUT, ]); } catch (\Throwable $_) { // 表可能无 status 字段时忽略 } } throw $e; } $updated = DicePlayer::find($playerId); if ($updated) { UserCache::setUser($playerId, $updated->hidden(['password'])->toArray()); } if (!$record instanceof DicePlayRecord) { throw new \RuntimeException('对局记录创建失败'); } $arr = $record->toArray(); if (isset($arr['roll_array']) && is_string($arr['roll_array'])) { $arr['roll_array'] = json_decode($arr['roll_array'], true) ?? []; } return $arr; } /** 生成 5 个 1-6 的点数,roll_number 为其总和 */ private function generateRollArray(): array { $dice = []; for ($i = 0; $i < 5; $i++) { $dice[] = random_int(1, 6); } return $dice; } }