Files
dafuweng-saiadmin6.x/server/app/dice/logic/reward/DiceRewardLogic.php
2026-03-16 11:23:28 +08:00

452 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | saiadmin [ saiadmin快速开发框架 ]
// +----------------------------------------------------------------------
namespace app\dice\logic\reward;
use app\dice\model\reward\DiceReward;
use app\dice\model\reward_config\DiceRewardConfig;
use plugin\saiadmin\exception\ApiException;
use support\think\Db;
/**
* 奖励对照逻辑层DiceReward
* 权重 1-10000区分顺时针/逆时针,修改后刷新 DiceReward 缓存
*/
class DiceRewardLogic
{
private const WEIGHT_MIN = 1;
private const WEIGHT_MAX = 10000;
/** 档位键 */
private const TIER_KEYS = ['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'];
/**
* 分页列表(按方向筛选,关联 dice_reward_config 展示 grid_number、ui_text、real_ev、remark
* @param int $direction 0=顺时针 1=逆时针
* @param array{tier?: string, orderField?: string, orderType?: string} $where tier 档位筛选
* @param int $page
* @param int $limit
* @return array{total: int, per_page: int, current_page: int, data: array}
*/
public function getListWithConfig(int $direction, array $where, int $page = 1, int $limit = 10): array
{
$tier = isset($where['tier']) ? trim((string) $where['tier']) : '';
$orderField = isset($where['orderField']) && $where['orderField'] !== '' ? (string) $where['orderField'] : 'r.tier';
$orderType = isset($where['orderType']) && strtoupper((string) $where['orderType']) === 'DESC' ? 'desc' : 'asc';
$query = DiceReward::alias('r')
->where('r.direction', $direction)
->field('r.id,r.tier,r.direction,r.end_index,r.weight,r.grid_number,r.start_index,r.ui_text,r.real_ev,r.remark,r.type,r.create_time,r.update_time')
->order($orderField, $orderType)
->order('r.end_index', 'asc');
if ($tier !== '') {
$query->where('r.tier', $tier);
}
$paginator = $query->paginate($limit, false, ['page' => $page]);
$arr = $paginator->toArray();
$data = isset($arr['data']) ? $arr['data'] : $arr['records'] ?? [];
$total = (int) ($arr['total'] ?? 0);
$perPage = (int) ($arr['per_page'] ?? $limit);
$currentPage = (int) ($arr['current_page'] ?? $page);
foreach ($data as $i => $row) {
if (isset($row['id']) && $row['id'] !== '' && $row['id'] !== null) {
$data[$i]['id'] = (int) $row['id'];
} else {
$data[$i]['id'] = isset($row['end_index']) ? (int) $row['end_index'] : 0;
}
$data[$i]['start_index'] = isset($row['start_index']) && $row['start_index'] !== '' && $row['start_index'] !== null
? (int) $row['start_index']
: 0;
}
return [
'total' => $total,
'per_page' => $perPage,
'current_page' => $currentPage,
'data' => $data,
];
}
/**
* 按单方向批量更新权重(仅更新当前方向的 weight并刷新缓存
* @param int $direction 0=顺时针 1=逆时针
* @param array<int, array{id: int, weight: int}> $items id 为 end_indexDiceRewardConfig.id
*/
public function batchUpdateWeightsByDirection(int $direction, array $items): void
{
if (empty($items)) {
return;
}
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$id = isset($item['id']) ? (int) $item['id'] : 0;
$weight = isset($item['weight']) ? (int) $item['weight'] : self::WEIGHT_MIN;
if ($id <= 0) {
throw new ApiException('存在无效的配置ID');
}
$weight = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, $weight));
$tier = DiceRewardConfig::where('id', $id)->value('tier');
if ($tier === null || $tier === '') {
throw new ApiException('配置ID ' . $id . ' 不存在或档位为空');
}
$tier = (string) $tier;
$affected = DiceReward::where('tier', $tier)->where('direction', $direction)->where('end_index', $id)->update(['weight' => $weight]);
if ($affected === 0) {
$m = new DiceReward();
$m->tier = $tier;
$m->direction = $direction;
$m->end_index = $id;
$m->weight = $weight;
$m->save();
}
}
DiceReward::refreshCache();
}
/**
* 按档位+单方向返回列表(用于权重编辑弹窗:当前方向下按档位分组的配置+权重)
* @param int $direction 0=顺时针 1=逆时针
* @return array<string, array> 键 T1|T2|...|BIGWIN值为该档位下带 weight 的行数组
*/
public function getListGroupedByTierForDirection(int $direction): array
{
$configInstance = DiceRewardConfig::getCachedInstance();
$byTier = $configInstance['by_tier'] ?? [];
$rewardInstance = DiceReward::getCachedInstance();
$byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
$result = [];
foreach (self::TIER_KEYS as $tier) {
$result[$tier] = [];
$rows = $byTier[$tier] ?? [];
$dirRows = $byTierDirection[$tier][$direction] ?? [];
$weightMap = [];
foreach ($dirRows as $r) {
$eid = isset($r['end_index']) ? (int) $r['end_index'] : 0;
$weightMap[$eid] = isset($r['weight']) ? max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $r['weight'])) : 1;
}
foreach ($rows as $row) {
$id = isset($row['id']) ? (int) $row['id'] : 0;
$result[$tier][] = [
'id' => $id,
'grid_number' => $row['grid_number'] ?? 0,
'ui_text' => $row['ui_text'] ?? '',
'real_ev' => $row['real_ev'] ?? 0,
'remark' => $row['remark'] ?? '',
'tier' => $tier,
'weight' => $weightMap[$id] ?? 1,
];
}
}
return $result;
}
/**
* 按档位+方向返回 DiceReward 列表(用于权重配比弹窗),直接读 dice_reward 表,不依赖 config
* 每行含 reward_id(DiceReward 主键,用于按 id 更新权重)、id(end_index 展示用)、grid_number、ui_text、real_ev、remark、weight
*
* @return array<string, array{0: array, 1: array}>
*/
public function getListGroupedByTierWithDirection(): array
{
$rewardInstance = DiceReward::getCachedInstance();
$byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
$result = [];
foreach (self::TIER_KEYS as $tier) {
$result[$tier] = [0 => [], 1 => []];
foreach ([0, 1] as $direction) {
$rows = $byTierDirection[$tier][$direction] ?? [];
foreach ($rows as $r) {
$result[$tier][$direction][] = [
'reward_id' => isset($r['id']) ? (int) $r['id'] : 0,
'id' => isset($r['end_index']) ? (int) $r['end_index'] : 0,
'grid_number' => isset($r['grid_number']) ? (int) $r['grid_number'] : 0,
'ui_text' => (string) ($r['ui_text'] ?? ''),
'real_ev' => $r['real_ev'] ?? 0,
'remark' => (string) ($r['remark'] ?? ''),
'weight' => isset($r['weight']) ? max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $r['weight'])) : self::WEIGHT_MIN,
];
}
}
}
return $result;
}
/**
* 批量更新权重:直接按 DiceReward 主键 id 更新 weight不依赖 direction/grid_number
*
* @param array<int, array{id: int, weight: int}> $items 每项 id 为 dice_reward 表主键weight 为 1-10000
* @throws ApiException
*/
public function batchUpdateWeights(array $items): void
{
if (empty($items)) {
return;
}
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$id = isset($item['id']) ? (int) $item['id'] : 0;
if ($id <= 0) {
$id = isset($item['reward_id']) ? (int) $item['reward_id'] : 0;
}
if ($id <= 0) {
throw new ApiException('存在无效的 DiceReward id');
}
$weight = isset($item['weight']) ? (int) $item['weight'] : self::WEIGHT_MIN;
$weight = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, $weight));
$model = DiceReward::find($id);
if ($model !== null) {
$model->weight = $weight;
$model->save();
}
}
DiceReward::refreshCache();
}
/** BIGWIN 权重范围0=0% 中奖10000=100% 中奖grid_number=5/30 固定 100% 不可改 */
private const BIGWIN_WEIGHT_MAX = 10000;
/**
* 按 grid_number 获取 BIGWIN 档位权重(取顺时针方向,用于编辑展示)
* 若 DiceReward 无该点数则 5/30 返回 10000其余返回 0
*/
public function getBigwinWeightByGridNumber(int $gridNumber): int
{
$inst = DiceReward::getCachedInstance();
$rows = $inst['by_tier_direction']['BIGWIN'][DiceReward::DIRECTION_CLOCKWISE] ?? [];
foreach ($rows as $row) {
if ((int) ($row['grid_number'] ?? 0) === $gridNumber) {
return min(self::BIGWIN_WEIGHT_MAX, (int) ($row['weight'] ?? self::BIGWIN_WEIGHT_MAX));
}
}
return in_array($gridNumber, [5, 30], true) ? self::BIGWIN_WEIGHT_MAX : 0;
}
/**
* 更新 BIGWIN 档位某点数的权重(顺/逆时针同时更新0=0% 中奖10000=100% 中奖
* 表 dice_reward 唯一键为 (direction, grid_number),同一点数同一方向仅一条记录,故先按该键查找再更新,避免重复插入
*/
public function updateBigwinWeight(int $gridNumber, int $weight): void
{
$weight = min(self::BIGWIN_WEIGHT_MAX, max(0, $weight));
$config = DiceRewardConfig::where('tier', 'BIGWIN')
->where('grid_number', $gridNumber)
->find();
if (! $config) {
return;
}
$configArr = $config->toArray();
foreach ([DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE] as $direction) {
// 按唯一键 (direction, grid_number) 查找,存在则更新,不存在则插入
$row = DiceReward::where('direction', $direction)
->where('grid_number', $gridNumber)
->find();
if ($row) {
$row->tier = 'BIGWIN';
$row->weight = $weight > 0 ? $weight : self::WEIGHT_MIN;
$row->start_index = (int) ($configArr['id'] ?? $row->start_index);
$row->end_index = (int) ($configArr['id'] ?? $row->end_index);
$row->ui_text = (string) ($configArr['ui_text'] ?? $row->ui_text);
$row->real_ev = (float) ($configArr['real_ev'] ?? $row->real_ev);
$row->remark = (string) ($configArr['remark'] ?? $row->remark);
$row->type = $configArr['type'] ?? $row->type;
$row->save();
} else {
$m = new DiceReward();
$m->tier = 'BIGWIN';
$m->direction = $direction;
$m->grid_number = (int) $gridNumber;
$m->start_index = (int) ($configArr['id'] ?? 0);
$m->end_index = (int) ($configArr['id'] ?? 0);
$m->ui_text = (string) ($configArr['ui_text'] ?? '');
$m->real_ev = (float) ($configArr['real_ev'] ?? 0);
$m->remark = (string) ($configArr['remark'] ?? '');
$m->type = $configArr['type'] ?? null;
$m->weight = $weight > 0 ? $weight : self::WEIGHT_MIN;
$m->save();
}
}
DiceReward::refreshCache();
}
/** 盘面格数(用于顺时针/逆时针计算 end_index */
private const BOARD_SIZE = 26;
/** 点数摇取范围5-30顺时针与逆时针均需创建 */
private const GRID_NUMBER_MIN = 5;
private const GRID_NUMBER_MAX = 30;
/**
* 创建奖励对照:先清空 dice_reward 表,再按两种方向为点数 5-30 生成记录。
*
* DiceReward 记录数据规则(与 config 通过 end_index 关联):
* - 方向direction = 0顺时针/ 1逆时针
* - 摇取点数grid_number
* - 起始索引start_index = DiceRewardConfig::where('grid_number', $grid_number)->first()->id
* - 结束索引顺时针end_index = ($start_index + $grid_number) % 26对 26 取余)
* - 结束索引逆时针end_index = ($start_index - $grid_number >= 0) ? ($start_index - $grid_number) : (26 + $start_index - $grid_number)
* - 奖励档位tier = DiceRewardConfig::where('id', $end_index)->first()->tier
* - 显示uiui_text = DiceRewardConfig::where('id', $end_index)->first()->ui_text
* - 实际中奖real_ev = DiceRewardConfig::where('id', $end_index)->first()->real_ev
* - 备注remark = DiceRewardConfig::where('id', $end_index)->first()->remark
* - 类型type = DiceRewardConfig::where('id', $end_index)->first()->type-2=唯一惩罚,-1=抽水,0=回本,1=再来一次,2=小赚,3=大奖格)
* - weight 默认 1后续在权重编辑弹窗设置
*
* 例如顺时针摇取点数为 5 时start_index = 配置中 grid_number=5 对应格位的 id
* 结束位置 = (起始位置 + grid_number) % 26再取该位置的 config 的 id 作为 end_index。
* 使用「按 id 排序后的盘面位置 0-25」做环形计算避免 config.id 非连续时取模结果找不到;
* 唯一键为 (direction, grid_number),保证每个点数、每个方向各一条记录,不因 end_index 相同而覆盖。
*
* @return array{created_clockwise: int, created_counterclockwise: int, updated_clockwise: int, updated_counterclockwise: int, skipped: int}
* @throws ApiException
*/
public function createRewardReferenceFromConfig(): array
{
$list = DiceRewardConfig::order('id', 'asc')->select()->toArray();
if (empty($list)) {
throw new ApiException('奖励配置为空,请先维护 dice_reward_config');
}
$configCount = count($list);
if ($configCount < self::BOARD_SIZE) {
throw new ApiException(
'奖励配置需覆盖 26 个格位id 0-25 或 1-26当前仅 ' . $configCount . ' 条,无法完整生成 5-30 共26个点数、顺时针与逆时针的奖励对照'
);
}
$table = (new DiceReward())->getTable();
Db::execute('DELETE FROM `' . $table . '`');
DiceReward::refreshCache();
// 按 id 排序后,盘面位置 0..25 对应 $list[$pos],避免 config.id 非 0-25/1-26 时取模结果找不到
$gridToPosition = [];
foreach ($list as $pos => $row) {
$gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
if ($gn >= self::GRID_NUMBER_MIN && $gn <= self::GRID_NUMBER_MAX && !isset($gridToPosition[$gn])) {
$gridToPosition[$gn] = $pos;
}
}
$createdCw = 0;
$createdCcw = 0;
$updatedCw = 0;
$updatedCcw = 0;
$skipped = 0;
for ($gridNumber = self::GRID_NUMBER_MIN; $gridNumber <= self::GRID_NUMBER_MAX; $gridNumber++) {
if (!isset($gridToPosition[$gridNumber])) {
$skipped++;
continue;
}
$startPos = $gridToPosition[$gridNumber];
$startRow = $list[$startPos];
$startId = isset($startRow['id']) ? (int) $startRow['id'] : 0;
$endPosCw = ($startPos + $gridNumber) % self::BOARD_SIZE;
$endPosCcw = $startPos - $gridNumber >= 0 ? $startPos - $gridNumber : self::BOARD_SIZE + $startPos - $gridNumber;
$configCw = $list[$endPosCw] ?? null;
$configCcw = $list[$endPosCcw] ?? null;
$endIdCw = $configCw !== null && isset($configCw['id']) ? (int) $configCw['id'] : 0;
$endIdCcw = $configCcw !== null && isset($configCcw['id']) ? (int) $configCcw['id'] : 0;
if ($configCw !== null) {
$tier = isset($configCw['tier']) ? trim((string) $configCw['tier']) : '';
if ($tier !== '') {
// 使用对应奖励配置的 weight 作为格子权重(若未配置则退回最小权重)
$weightCw = isset($configCw['weight']) && $configCw['weight'] !== null
? $configCw['weight']
: self::WEIGHT_MIN;
$payloadCw = [
'tier' => $tier,
'weight' => $weightCw,
'grid_number' => $gridNumber,
'start_index' => $startId,
'end_index' => $endIdCw,
'ui_text' => $configCw['ui_text'] ?? '',
'real_ev' => $configCw['real_ev'] ?? null,
'remark' => $configCw['remark'] ?? '',
'type' => isset($configCw['type']) ? (int) $configCw['type'] : 0,
];
$existing = DiceReward::where('direction', DiceReward::DIRECTION_CLOCKWISE)->where('grid_number', $gridNumber)->find();
if ($existing) {
DiceReward::where('id', $existing->id)->update($payloadCw);
$updatedCw++;
} else {
$m = new DiceReward();
$m->tier = $tier;
$m->direction = DiceReward::DIRECTION_CLOCKWISE;
$m->end_index = $endIdCw;
$m->weight = $weightCw;
$m->grid_number = $gridNumber;
$m->start_index = $startId;
$m->ui_text = $configCw['ui_text'] ?? '';
$m->real_ev = $configCw['real_ev'] ?? null;
$m->remark = $configCw['remark'] ?? '';
$m->type = isset($configCw['type']) ? (int) $configCw['type'] : 0;
$m->save();
$createdCw++;
}
}
}
if ($configCcw !== null) {
$tier = isset($configCcw['tier']) ? trim((string) $configCcw['tier']) : '';
if ($tier !== '') {
// 使用对应奖励配置的 weight 作为格子权重(若未配置则退回最小权重)
$weightCcw = isset($configCcw['weight']) && $configCcw['weight'] !== null
? $configCcw['weight']
: self::WEIGHT_MIN;
$payloadCcw = [
'tier' => $tier,
'weight' => $weightCcw,
'grid_number' => $gridNumber,
'start_index' => $startId,
'end_index' => $endIdCcw,
'ui_text' => $configCcw['ui_text'] ?? '',
'real_ev' => $configCcw['real_ev'] ?? null,
'remark' => $configCcw['remark'] ?? '',
'type' => isset($configCcw['type']) ? (int) $configCcw['type'] : 0,
];
$existing = DiceReward::where('direction', DiceReward::DIRECTION_COUNTERCLOCKWISE)->where('grid_number', $gridNumber)->find();
if ($existing) {
DiceReward::where('id', $existing->id)->update($payloadCcw);
$updatedCcw++;
} else {
$m = new DiceReward();
$m->tier = $tier;
$m->direction = DiceReward::DIRECTION_COUNTERCLOCKWISE;
$m->end_index = $endIdCcw;
$m->weight = $weightCcw;
$m->grid_number = $gridNumber;
$m->start_index = $startId;
$m->ui_text = $configCcw['ui_text'] ?? '';
$m->real_ev = $configCcw['real_ev'] ?? null;
$m->remark = $configCcw['remark'] ?? '';
$m->type = isset($configCcw['type']) ? (int) $configCcw['type'] : 0;
$m->save();
$createdCcw++;
}
}
}
}
DiceReward::refreshCache();
return [
'created_clockwise' => $createdCw,
'created_counterclockwise' => $createdCcw,
'updated_clockwise' => $updatedCw,
'updated_counterclockwise' => $updatedCcw,
'skipped' => $skipped,
];
}
}