Files
webman-buildadmin/app/common/library/game/ZiHuaDictionary.php

227 lines
8.2 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
declare(strict_types=1);
namespace app\common\library\game;
use InvalidArgumentException;
/**
* 36 字花字典game_config.zi_hua_36_dictionary仅存 JSON 数组
* category 使用英文zodiac|beast|fowl|vermin|divine
*/
final class ZiHuaDictionary
{
public const CONFIG_KEY = 'zi_hua_36_dictionary';
/** 与表单下拉一致,顺序即展示顺序 */
public const CATEGORIES = ['zodiac', 'beast', 'fowl', 'vermin', 'divine'];
private const LEGACY_CATEGORY_MAP = [
'生肖' => 'zodiac',
'猛兽' => 'beast',
'飞禽' => 'fowl',
'虫蛇' => 'vermin',
'神兽' => 'divine',
'zodiac' => 'zodiac',
'beast' => 'beast',
'fowl' => 'fowl',
'vermin' => 'vermin',
'divine' => 'divine',
];
/**
* 默认 36 条(与 PRD 一致category 为英文)
*
* @return list<array{no: int, name: string, category: string}>
*/
public static function defaultItems(): array
{
return [
['no' => 1, 'name' => '鼠', 'category' => 'zodiac'],
['no' => 2, 'name' => '牛', 'category' => 'zodiac'],
['no' => 3, 'name' => '虎', 'category' => 'zodiac'],
['no' => 4, 'name' => '兔', 'category' => 'zodiac'],
['no' => 5, 'name' => '龙', 'category' => 'zodiac'],
['no' => 6, 'name' => '蛇', 'category' => 'zodiac'],
['no' => 7, 'name' => '马', 'category' => 'zodiac'],
['no' => 8, 'name' => '羊', 'category' => 'zodiac'],
['no' => 9, 'name' => '猴', 'category' => 'zodiac'],
['no' => 10, 'name' => '鸡', 'category' => 'zodiac'],
['no' => 11, 'name' => '狗', 'category' => 'zodiac'],
['no' => 12, 'name' => '猪', 'category' => 'zodiac'],
['no' => 13, 'name' => '狮', 'category' => 'beast'],
['no' => 14, 'name' => '象', 'category' => 'beast'],
['no' => 15, 'name' => '鹿', 'category' => 'beast'],
['no' => 16, 'name' => '熊', 'category' => 'beast'],
['no' => 17, 'name' => '狼', 'category' => 'beast'],
['no' => 18, 'name' => '豹', 'category' => 'beast'],
['no' => 19, 'name' => '鹰', 'category' => 'fowl'],
['no' => 20, 'name' => '鹤', 'category' => 'fowl'],
['no' => 21, 'name' => '孔雀', 'category' => 'fowl'],
['no' => 22, 'name' => '鸳鸯', 'category' => 'fowl'],
['no' => 23, 'name' => '鸵鸟', 'category' => 'fowl'],
['no' => 24, 'name' => '天鹅', 'category' => 'fowl'],
['no' => 25, 'name' => '蟾蜍', 'category' => 'vermin'],
['no' => 26, 'name' => '蜘蛛', 'category' => 'vermin'],
['no' => 27, 'name' => '蝙蝠', 'category' => 'vermin'],
['no' => 28, 'name' => '蜈蚣', 'category' => 'vermin'],
['no' => 29, 'name' => '蝎', 'category' => 'vermin'],
['no' => 30, 'name' => '蛇蜥', 'category' => 'vermin'],
['no' => 31, 'name' => '麒麟', 'category' => 'divine'],
['no' => 32, 'name' => '凤凰', 'category' => 'divine'],
['no' => 33, 'name' => '青龙', 'category' => 'divine'],
['no' => 34, 'name' => '白虎', 'category' => 'divine'],
['no' => 35, 'name' => '朱雀', 'category' => 'divine'],
['no' => 36, 'name' => '玄武', 'category' => 'divine'],
];
}
/**
* @return list<array{no: int, name: string, category: string}>
*/
public static function parseFromConfigValue(?string $configValue): array
{
if ($configValue === null || trim($configValue) === '') {
return self::defaultItems();
}
$decoded = json_decode($configValue, true);
if (!is_array($decoded)) {
return self::defaultItems();
}
if (isset($decoded['items']) && is_array($decoded['items'])) {
return self::normalizeItemsList($decoded['items']);
}
if ($decoded !== [] && array_is_list($decoded)) {
return self::normalizeItemsList($decoded);
}
return self::defaultItems();
}
/**
* @param list<mixed> $items
* @return list<array{no: int, name: string, category: string}>
*/
public static function normalizeItemsList(array $items): array
{
$defaults = self::defaultItems();
$defaultByNo = [];
foreach ($defaults as $row) {
$defaultByNo[$row['no']] = $row;
}
$out = [];
foreach ($items as $row) {
if (!is_array($row)) {
continue;
}
$no = $row['no'] ?? null;
if (!is_numeric($no)) {
continue;
}
$n = intval($no, 10);
if ($n < 1 || $n > 36) {
continue;
}
$name = isset($row['name']) && is_string($row['name']) ? trim($row['name']) : '';
if ($name === '') {
$name = $defaultByNo[$n]['name'] ?? '';
}
$catRaw = $row['category'] ?? 'zodiac';
$catStr = is_string($catRaw) ? trim($catRaw) : '';
$category = self::LEGACY_CATEGORY_MAP[$catStr] ?? (in_array($catStr, self::CATEGORIES, true) ? $catStr : 'zodiac');
$out[] = ['no' => $n, 'name' => $name, 'category' => $category];
}
if (count($out) !== 36) {
return self::defaultItems();
}
usort($out, static fn (array $a, array $b): int => $a['no'] <=> $b['no']);
$seen = [];
foreach ($out as $row) {
if (isset($seen[$row['no']])) {
return self::defaultItems();
}
$seen[$row['no']] = true;
}
for ($i = 1; $i <= 36; $i++) {
if (!isset($seen[$i])) {
return self::defaultItems();
}
}
return $out;
}
/**
* 校验 POST 数据并输出入库用的 36 条no 升序)
*
* @param list<array{no?: mixed, name?: mixed, category?: mixed}> $items
*
* @return list<array{no: int, name: string, category: string}>
*
* @throws InvalidArgumentException
*/
public static function prepareItemsForSave(array $items): array
{
if (count($items) !== 36) {
throw new InvalidArgumentException('Must be exactly 36 items');
}
$nos = [];
$out = [];
foreach ($items as $idx => $row) {
if (!is_array($row)) {
throw new InvalidArgumentException('Row format error');
}
$no = $row['no'] ?? null;
if (!is_numeric($no)) {
throw new InvalidArgumentException('No must be numeric');
}
$n = intval($no, 10);
if ($n < 1 || $n > 36) {
throw new InvalidArgumentException('No must be between 1 and 36');
}
if (isset($nos[$n])) {
throw new InvalidArgumentException('Duplicate no');
}
$nos[$n] = true;
$name = $row['name'] ?? '';
if (!is_string($name) || trim($name) === '') {
throw new InvalidArgumentException('Name is required');
}
$cat = $row['category'] ?? '';
$catTrim = is_string($cat) ? trim($cat) : '';
if (!in_array($catTrim, self::CATEGORIES, true)) {
throw new InvalidArgumentException('Category is invalid');
}
$out[] = ['no' => $n, 'name' => trim($name), 'category' => $catTrim];
}
for ($i = 1; $i <= 36; $i++) {
if (!isset($nos[$i])) {
throw new InvalidArgumentException('Missing no');
}
}
usort($out, static fn (array $a, array $b): int => $a['no'] <=> $b['no']);
return $out;
}
/**
* @param list<array{no: int, name: string, category: string}> $items
*/
public static function encodeForDb(array $items): string
{
usort($items, static fn (array $a, array $b): int => $a['no'] <=> $b['no']);
$encoded = json_encode($items, JSON_UNESCAPED_UNICODE);
if ($encoded === false) {
throw new InvalidArgumentException('JSON encode failed');
}
return $encoded;
}
}