1.优化奖励配置设置
This commit is contained in:
@@ -263,6 +263,23 @@ class DiceRewardConfigController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建奖励对照(预览):不写入 dice_reward,仅计算并返回预览分组数据。
|
||||
* 若当前 dice_reward 与计算结果一致,则 unchanged=true,并在预览中复用现有权重(导入时仍沿用旧权重)。
|
||||
*/
|
||||
#[Permission('创建奖励对照', 'dice:reward_config:index:createRewardReference')]
|
||||
public function createRewardReferencePreview(Request $request): Response
|
||||
{
|
||||
try {
|
||||
$rewardLogic = new DiceRewardLogic();
|
||||
$deptId = AdminScopeHelper::resolveConfigDeptId($this->adminInfo ?? null, $request->input('dept_id'));
|
||||
$result = $rewardLogic->createRewardReferencePreviewFromConfig($deptId);
|
||||
return $this->success($result, 'preview reward mapping success');
|
||||
} catch (\plugin\saiadmin\exception\ApiException $e) {
|
||||
return $this->fail($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权重配比测试:仅模拟落点统计,不创建游玩记录。按当前配置在内存中模拟 N 次抽奖,返回各 grid_number 落点次数,可选保存到 dice_reward_config_record。
|
||||
* @param Request $request test_count: 100|500|1000, save_record: bool, lottery_config_id: int|null 奖池配置ID,用于设定 T1-T5 概率
|
||||
|
||||
@@ -327,6 +327,79 @@ class DiceRewardLogic
|
||||
private const GRID_NUMBER_MIN = 5;
|
||||
private const GRID_NUMBER_MAX = 30;
|
||||
|
||||
/**
|
||||
* 预览:按当前 dice_reward_config 计算将要生成的 dice_reward(不写库)
|
||||
* 若当前 dice_reward 与计算结果完全一致,则标记 unchanged=true,并返回现有权重(导入时将复用旧权重)
|
||||
*
|
||||
* @return array{unchanged: bool, skipped: int, preview: array<string, array{0: array, 1: array}>}
|
||||
* @throws ApiException
|
||||
*/
|
||||
public function createRewardReferencePreviewFromConfig(?int $deptId = null): array
|
||||
{
|
||||
$normalizedDeptId = $deptId;
|
||||
$list = $this->loadConfigListForReference($normalizedDeptId);
|
||||
$computed = $this->computeReferenceRowsFromConfigList($list, $normalizedDeptId);
|
||||
|
||||
$existing = $this->loadExistingRewardRowsForReference($normalizedDeptId);
|
||||
$compare = $this->compareReferenceRows($computed['rows'], $existing);
|
||||
$unchanged = $compare['unchanged'];
|
||||
|
||||
$previewRows = [];
|
||||
foreach ($computed['rows'] as $row) {
|
||||
$key = $row['direction'] . ':' . $row['grid_number'];
|
||||
$weight = self::WEIGHT_MIN;
|
||||
$oldStart = null;
|
||||
$oldEnd = null;
|
||||
$oldTier = null;
|
||||
$oldWeight = null;
|
||||
if (isset($existing[$key])) {
|
||||
$oldStart = isset($existing[$key]['start_index']) ? (int) $existing[$key]['start_index'] : null;
|
||||
$oldEnd = isset($existing[$key]['end_index']) ? (int) $existing[$key]['end_index'] : null;
|
||||
$oldTier = isset($existing[$key]['tier']) ? (string) $existing[$key]['tier'] : null;
|
||||
$oldWeight = isset($existing[$key]['weight']) ? (int) $existing[$key]['weight'] : null;
|
||||
}
|
||||
if ($unchanged && $oldWeight !== null) {
|
||||
$weight = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $oldWeight));
|
||||
}
|
||||
|
||||
$diffChanged = false;
|
||||
$diffFields = [];
|
||||
if ($oldStart === null || $oldEnd === null || $oldTier === null) {
|
||||
$diffChanged = true;
|
||||
$diffFields[] = 'new';
|
||||
} else {
|
||||
if ((int) $oldStart !== (int) ($row['start_index'] ?? 0)) {
|
||||
$diffChanged = true;
|
||||
$diffFields[] = 'start_index';
|
||||
}
|
||||
if ((int) $oldEnd !== (int) ($row['end_index'] ?? 0)) {
|
||||
$diffChanged = true;
|
||||
$diffFields[] = 'end_index';
|
||||
}
|
||||
if (trim((string) $oldTier) !== trim((string) ($row['tier'] ?? ''))) {
|
||||
$diffChanged = true;
|
||||
$diffFields[] = 'tier';
|
||||
}
|
||||
}
|
||||
|
||||
$previewRows[] = array_merge($row, [
|
||||
'weight' => $weight,
|
||||
'old_start_index' => $oldStart,
|
||||
'old_end_index' => $oldEnd,
|
||||
'old_tier' => $oldTier,
|
||||
'old_weight' => $oldWeight,
|
||||
'diff_changed' => $diffChanged,
|
||||
'diff_fields' => $diffFields,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'unchanged' => $unchanged,
|
||||
'skipped' => $computed['skipped'],
|
||||
'preview' => $this->groupReferenceRowsByTierWithDirection($previewRows),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建奖励对照:先清空 dice_reward 表,再按两种方向为点数 5-30 生成记录。
|
||||
*
|
||||
@@ -352,10 +425,76 @@ class DiceRewardLogic
|
||||
* @throws ApiException
|
||||
*/
|
||||
public function createRewardReferenceFromConfig(?int $deptId = null): array
|
||||
{
|
||||
$normalizedDeptId = $deptId;
|
||||
$list = $this->loadConfigListForReference($normalizedDeptId);
|
||||
$computed = $this->computeReferenceRowsFromConfigList($list, $normalizedDeptId);
|
||||
|
||||
$existing = $this->loadExistingRewardRowsForReference($normalizedDeptId);
|
||||
$compare = $this->compareReferenceRows($computed['rows'], $existing);
|
||||
if ($compare['unchanged']) {
|
||||
return [
|
||||
'created_clockwise' => 0,
|
||||
'created_counterclockwise' => 0,
|
||||
'updated_clockwise' => 0,
|
||||
'updated_counterclockwise' => 0,
|
||||
'skipped' => $computed['skipped'],
|
||||
'unchanged' => true,
|
||||
];
|
||||
}
|
||||
|
||||
$table = (new DiceReward())->getTable();
|
||||
if ($normalizedDeptId === null) {
|
||||
Db::table($table)->whereNull('dept_id')->delete();
|
||||
} else {
|
||||
Db::table($table)->where('dept_id', $normalizedDeptId)->delete();
|
||||
}
|
||||
|
||||
$createdCw = 0;
|
||||
$createdCcw = 0;
|
||||
foreach ($computed['rows'] as $row) {
|
||||
$m = new DiceReward();
|
||||
$m->tier = $row['tier'];
|
||||
$m->direction = (int) $row['direction'];
|
||||
$m->end_index = (int) $row['end_index'];
|
||||
$m->weight = self::WEIGHT_MIN;
|
||||
$m->grid_number = (int) $row['grid_number'];
|
||||
$m->start_index = (int) $row['start_index'];
|
||||
$m->ui_text = (string) ($row['ui_text'] ?? '');
|
||||
$m->real_ev = $row['real_ev'] ?? null;
|
||||
$m->remark = (string) ($row['remark'] ?? '');
|
||||
$m->type = isset($row['type']) ? (int) $row['type'] : 0;
|
||||
if ($normalizedDeptId !== null) {
|
||||
$m->dept_id = $normalizedDeptId;
|
||||
}
|
||||
$m->save();
|
||||
if ((int) $row['direction'] === DiceReward::DIRECTION_CLOCKWISE) {
|
||||
$createdCw++;
|
||||
} else {
|
||||
$createdCcw++;
|
||||
}
|
||||
}
|
||||
|
||||
DiceReward::refreshCache($normalizedDeptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT);
|
||||
return [
|
||||
'created_clockwise' => $createdCw,
|
||||
'created_counterclockwise' => $createdCcw,
|
||||
'updated_clockwise' => 0,
|
||||
'updated_counterclockwise' => 0,
|
||||
'skipped' => $computed['skipped'],
|
||||
'unchanged' => false,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取奖励配置(按 id asc),并把模板 dept 转为 null
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private function loadConfigListForReference(?int &$deptId): array
|
||||
{
|
||||
$configQuery = DiceRewardConfig::order('id', 'asc');
|
||||
if ($deptId === null || $deptId === \app\dice\helper\AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) {
|
||||
$templateId = \app\dice\helper\AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
|
||||
if ($deptId === null || $deptId === AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) {
|
||||
$templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
|
||||
$configQuery->where(function ($q) use ($templateId) {
|
||||
$q->where('dept_id', $templateId)->whereOr('dept_id', 'null');
|
||||
});
|
||||
@@ -367,25 +506,25 @@ class DiceRewardLogic
|
||||
if (empty($list)) {
|
||||
throw new ApiException('Reward config is empty, please maintain dice_reward_config first');
|
||||
}
|
||||
$configCount = count($list);
|
||||
if ($configCount < self::BOARD_SIZE) {
|
||||
if (count($list) < self::BOARD_SIZE) {
|
||||
throw new ApiException(
|
||||
\app\api\util\ApiLang::translateParams(
|
||||
'奖励配置需覆盖 26 个格位(id 0-25 或 1-26),当前仅 %s 条,无法完整生成 5-30 共26个点数、顺时针与逆时针的奖励对照',
|
||||
[$configCount]
|
||||
[count($list)]
|
||||
)
|
||||
);
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
$table = (new DiceReward())->getTable();
|
||||
if ($deptId === null) {
|
||||
Db::table($table)->whereNull('dept_id')->delete();
|
||||
} else {
|
||||
Db::table($table)->where('dept_id', $deptId)->delete();
|
||||
}
|
||||
DiceReward::refreshCache($deptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT);
|
||||
|
||||
// 按 id 排序后,盘面位置 0..25 对应 $list[$pos],避免 config.id 非 0-25/1-26 时取模结果找不到
|
||||
/**
|
||||
* 计算 5-30 两个方向的对照行(不含权重)
|
||||
* @param array<int, array<string, mixed>> $list
|
||||
* @return array{rows: array<int, array<string, mixed>>, skipped: int}
|
||||
*/
|
||||
private function computeReferenceRowsFromConfigList(array $list, ?int $deptId): array
|
||||
{
|
||||
// 按 id 排序后,盘面位置 0..25 对应 $list[$pos]
|
||||
$gridToPosition = [];
|
||||
foreach ($list as $pos => $row) {
|
||||
$gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
||||
@@ -394,12 +533,8 @@ class DiceRewardLogic
|
||||
}
|
||||
}
|
||||
|
||||
$createdCw = 0;
|
||||
$createdCcw = 0;
|
||||
$updatedCw = 0;
|
||||
$updatedCcw = 0;
|
||||
$rows = [];
|
||||
$skipped = 0;
|
||||
|
||||
for ($gridNumber = self::GRID_NUMBER_MIN; $gridNumber <= self::GRID_NUMBER_MAX; $gridNumber++) {
|
||||
if (!isset($gridToPosition[$gridNumber])) {
|
||||
$skipped++;
|
||||
@@ -414,115 +549,131 @@ class DiceRewardLogic
|
||||
|
||||
$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 = [
|
||||
$rows[] = [
|
||||
'tier' => $tier,
|
||||
'weight' => $weightCw,
|
||||
'direction' => DiceReward::DIRECTION_CLOCKWISE,
|
||||
'weight' => self::WEIGHT_MIN,
|
||||
'grid_number' => $gridNumber,
|
||||
'start_index' => $startId,
|
||||
'end_index' => $endIdCw,
|
||||
'end_index' => isset($configCw['id']) ? (int) $configCw['id'] : 0,
|
||||
'ui_text' => $configCw['ui_text'] ?? '',
|
||||
'real_ev' => $configCw['real_ev'] ?? null,
|
||||
'remark' => $configCw['remark'] ?? '',
|
||||
'type' => isset($configCw['type']) ? (int) $configCw['type'] : 0,
|
||||
];
|
||||
$existingQuery = DiceReward::where('direction', DiceReward::DIRECTION_CLOCKWISE)->where('grid_number', $gridNumber);
|
||||
if ($deptId === null) {
|
||||
$existingQuery->whereNull('dept_id');
|
||||
} else {
|
||||
$existingQuery->where('dept_id', $deptId);
|
||||
}
|
||||
$existing = $existingQuery->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;
|
||||
if ($deptId !== null) {
|
||||
$m->dept_id = $deptId;
|
||||
}
|
||||
$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 = [
|
||||
$rows[] = [
|
||||
'tier' => $tier,
|
||||
'weight' => $weightCcw,
|
||||
'direction' => DiceReward::DIRECTION_COUNTERCLOCKWISE,
|
||||
'weight' => self::WEIGHT_MIN,
|
||||
'grid_number' => $gridNumber,
|
||||
'start_index' => $startId,
|
||||
'end_index' => $endIdCcw,
|
||||
'end_index' => isset($configCcw['id']) ? (int) $configCcw['id'] : 0,
|
||||
'ui_text' => $configCcw['ui_text'] ?? '',
|
||||
'real_ev' => $configCcw['real_ev'] ?? null,
|
||||
'remark' => $configCcw['remark'] ?? '',
|
||||
'type' => isset($configCcw['type']) ? (int) $configCcw['type'] : 0,
|
||||
];
|
||||
$existingQuery = DiceReward::where('direction', DiceReward::DIRECTION_COUNTERCLOCKWISE)->where('grid_number', $gridNumber);
|
||||
if ($deptId === null) {
|
||||
$existingQuery->whereNull('dept_id');
|
||||
} else {
|
||||
$existingQuery->where('dept_id', $deptId);
|
||||
}
|
||||
$existing = $existingQuery->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;
|
||||
if ($deptId !== null) {
|
||||
$m->dept_id = $deptId;
|
||||
}
|
||||
$m->save();
|
||||
$createdCcw++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ['rows' => $rows, 'skipped' => $skipped];
|
||||
}
|
||||
|
||||
DiceReward::refreshCache($deptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT);
|
||||
return [
|
||||
'created_clockwise' => $createdCw,
|
||||
'created_counterclockwise' => $createdCcw,
|
||||
'updated_clockwise' => $updatedCw,
|
||||
'updated_counterclockwise' => $updatedCcw,
|
||||
'skipped' => $skipped,
|
||||
];
|
||||
/**
|
||||
* 读出当前 dice_reward(用于对比/复用权重)。key = "direction:grid_number"
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
private function loadExistingRewardRowsForReference(?int $deptId): array
|
||||
{
|
||||
$query = DiceReward::whereIn('grid_number', range(self::GRID_NUMBER_MIN, self::GRID_NUMBER_MAX))
|
||||
->whereIn('direction', [DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE]);
|
||||
if ($deptId === null) {
|
||||
$query->whereNull('dept_id');
|
||||
} else {
|
||||
$query->where('dept_id', $deptId);
|
||||
}
|
||||
$rows = $query->select()->toArray();
|
||||
$map = [];
|
||||
foreach ($rows as $r) {
|
||||
$dir = isset($r['direction']) ? (int) $r['direction'] : 0;
|
||||
$gn = isset($r['grid_number']) ? (int) $r['grid_number'] : 0;
|
||||
$map[$dir . ':' . $gn] = $r;
|
||||
}
|
||||
return $map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对比 computed 与 existing 是否完全一致(忽略权重)
|
||||
* @param array<int, array<string, mixed>> $computedRows
|
||||
* @param array<string, array<string, mixed>> $existingMap
|
||||
* @return array{unchanged: bool}
|
||||
*/
|
||||
private function compareReferenceRows(array $computedRows, array $existingMap): array
|
||||
{
|
||||
if (empty($computedRows)) {
|
||||
return ['unchanged' => false];
|
||||
}
|
||||
foreach ($computedRows as $row) {
|
||||
$key = $row['direction'] . ':' . $row['grid_number'];
|
||||
if (!isset($existingMap[$key])) {
|
||||
return ['unchanged' => false];
|
||||
}
|
||||
$ex = $existingMap[$key];
|
||||
$same =
|
||||
((int) ($ex['start_index'] ?? 0) === (int) ($row['start_index'] ?? 0)) &&
|
||||
((int) ($ex['end_index'] ?? 0) === (int) ($row['end_index'] ?? 0)) &&
|
||||
(trim((string) ($ex['tier'] ?? '')) === trim((string) ($row['tier'] ?? '')));
|
||||
if (!$same) {
|
||||
return ['unchanged' => false];
|
||||
}
|
||||
}
|
||||
return ['unchanged' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将行按 tier -> {0:[],1:[]} 组织,便于前端展示(与 weightRatioList 输出结构一致)
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array<string, array{0: array, 1: array}>
|
||||
*/
|
||||
private function groupReferenceRowsByTierWithDirection(array $rows): array
|
||||
{
|
||||
$result = [];
|
||||
foreach (self::TIER_KEYS as $tier) {
|
||||
$result[$tier] = [0 => [], 1 => []];
|
||||
}
|
||||
foreach ($rows as $r) {
|
||||
$tier = isset($r['tier']) ? trim((string) $r['tier']) : '';
|
||||
if ($tier === '' || !isset($result[$tier])) {
|
||||
continue;
|
||||
}
|
||||
$dir = isset($r['direction']) ? (int) $r['direction'] : 0;
|
||||
$dir = $dir === 1 ? 1 : 0;
|
||||
$result[$tier][$dir][] = [
|
||||
'reward_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,
|
||||
'start_index' => isset($r['start_index']) ? (int) $r['start_index'] : 0,
|
||||
'tier' => (string) ($r['tier'] ?? ''),
|
||||
'old_start_index' => $r['old_start_index'] ?? null,
|
||||
'old_end_index' => $r['old_end_index'] ?? null,
|
||||
'old_tier' => $r['old_tier'] ?? null,
|
||||
'old_weight' => $r['old_weight'] ?? null,
|
||||
'diff_changed' => (bool) ($r['diff_changed'] ?? false),
|
||||
'diff_fields' => $r['diff_fields'] ?? [],
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user