refactor(CardSet): 简化卡牌选项获取逻辑,移除冗余循环
- 将多类型权重随机选择改为直接使用指定类型 - 简化候选列表构建和抽取流程,避免嵌套循环 - 保留属性卡兜底逻辑,但仅在非属性类型且数量不足时触发
This commit is contained in:
@@ -253,98 +253,58 @@ function getDefaultPool(type: CardType, level: number = 1): IPoolItem[] {
|
||||
* @param forcedType 强制指定卡牌类型 (用于特殊获取,如商店、技能书等)
|
||||
*/
|
||||
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = [], forcedType?: CardType): ICardInfo[] {
|
||||
// 1. 获取该等级的池配置
|
||||
// 如果强制指定类型,则构造一个只包含该类型的配置
|
||||
const initialPoolConfigs = forcedType
|
||||
? [{ type: forcedType, poolWeight: 100 }]
|
||||
: (LevelPoolConfigs[level] || [{ type: CardType.Attr, poolWeight: 100 }]);
|
||||
// 1. 确定类型:强制指定 > 默认为属性
|
||||
const type = forcedType !== undefined ? forcedType : CardType.Attr;
|
||||
|
||||
const result: ICardInfo[] = [];
|
||||
const excludeSet = new Set(excludeUuids);
|
||||
|
||||
// 循环获取 count 张卡牌
|
||||
for (let i = 0; i < count; i++) {
|
||||
// 在每一轮获取中,我们使用一个临时的配置列表
|
||||
// 这样如果某个类型池空了,我们可以从临时列表中移除它,避免重复选中
|
||||
let currentConfigs = [...initialPoolConfigs];
|
||||
let cardFound = false;
|
||||
|
||||
while (currentConfigs.length > 0) {
|
||||
// 2.1 随机决定本次抽取的类型池
|
||||
const selectedConfig = weightedRandomPool(currentConfigs);
|
||||
if (!selectedConfig) break; // 理论上不会发生
|
||||
|
||||
// 2.2 获取该类型的所有候选卡牌
|
||||
// 直接使用默认全池 (传入level以获取该等级特定的默认配置)
|
||||
const rawCandidates = getDefaultPool(selectedConfig.type, level);
|
||||
|
||||
// 2.3 过滤与构建完整信息
|
||||
const validCandidates: ICardInfo[] = [];
|
||||
for (const item of rawCandidates) {
|
||||
// 排除全局排除项
|
||||
if (excludeSet.has(item.id)) continue;
|
||||
// 排除本轮已选项
|
||||
if (result.find(r => r.uuid === item.id)) continue;
|
||||
|
||||
// 获取详情
|
||||
const info = getCardBaseInfo(selectedConfig.type, item.id);
|
||||
if (!info) continue;
|
||||
|
||||
// Tag 过滤
|
||||
if (selectedConfig.tag && info.tag !== selectedConfig.tag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 赋予配置的权重
|
||||
info.weight = item.weight;
|
||||
validCandidates.push(info);
|
||||
}
|
||||
|
||||
// 2.4 检查该类型是否有可用卡牌
|
||||
if (validCandidates.length > 0) {
|
||||
// 有卡!随机抽取一张
|
||||
const card = weightedRandomCard(validCandidates);
|
||||
if (card) {
|
||||
result.push(card);
|
||||
cardFound = true;
|
||||
break; // 成功获取一张,跳出内层循环,进行下一张的获取
|
||||
}
|
||||
} else {
|
||||
// 没卡!从当前配置中移除这个类型,重试
|
||||
const index = currentConfigs.indexOf(selectedConfig);
|
||||
if (index > -1) {
|
||||
currentConfigs.splice(index, 1);
|
||||
}
|
||||
// 继续 while 循环,重新随机类型
|
||||
}
|
||||
}
|
||||
|
||||
// 2.5 如果尝试了所有类型都没找到卡 (极少见兜底)
|
||||
if (!cardFound) {
|
||||
// 尝试从属性池硬拿一个不重复的
|
||||
const attrItems = getDefaultPool(CardType.Attr);
|
||||
for (const item of attrItems) {
|
||||
if (excludeSet.has(item.id) || result.find(r => r.uuid === item.id)) continue;
|
||||
const info = getCardBaseInfo(CardType.Attr, item.id);
|
||||
if (info) {
|
||||
info.weight = 100;
|
||||
result.push(info);
|
||||
cardFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2. 获取该类型的候选池
|
||||
const rawItems = getDefaultPool(type, level);
|
||||
|
||||
// 3. 构建候选列表(包含完整信息)
|
||||
const candidates: ICardInfo[] = [];
|
||||
for (const item of rawItems) {
|
||||
if (excludeSet.has(item.id)) continue;
|
||||
|
||||
// 如果连兜底都找不到(比如所有属性都拿完了),那也没办法了,可能返回少于 count 张
|
||||
if (!cardFound) {
|
||||
console.warn(`[CardSet] 无法为等级 ${level} 找到足够的卡牌选项,当前已选: ${result.length}/${count}`);
|
||||
break;
|
||||
const info = getCardBaseInfo(type, item.id);
|
||||
if (info) {
|
||||
info.weight = item.weight;
|
||||
candidates.push(info);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 最终结果洗牌 (虽然逻辑上已经是随机的,但洗牌可以打乱类型顺序)
|
||||
shuffleArray(result);
|
||||
// 4. 抽取
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (candidates.length === 0) break;
|
||||
|
||||
const card = weightedRandomCard(candidates);
|
||||
if (card) {
|
||||
result.push(card);
|
||||
// 移除已选,防止重复
|
||||
const idx = candidates.indexOf(card);
|
||||
if (idx > -1) candidates.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 兜底逻辑:如果数量不足,且当前类型不是属性,则用属性卡补齐
|
||||
if (result.length < count && type !== CardType.Attr) {
|
||||
const attrItems = getDefaultPool(CardType.Attr); // 兜底可以用全量属性池
|
||||
for (const item of attrItems) {
|
||||
if (result.length >= count) break;
|
||||
|
||||
// 检查是否已存在(ID去重)
|
||||
if (excludeSet.has(item.id) || result.some(r => r.uuid === item.id)) continue;
|
||||
|
||||
const info = getCardBaseInfo(CardType.Attr, item.id);
|
||||
if (info) {
|
||||
info.weight = 100;
|
||||
result.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shuffleArray(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user