Files
pixelheros/assets/script/game/common/config/CardSet.ts
panw 4a506555ba feat(卡牌系统): 实现卡牌池配置及随机选择功能
添加卡牌类型枚举和统一卡牌信息接口
实现等级池配置表及卡牌池缓存机制
提供根据等级获取随机卡牌选项的功能
包含权重随机选择算法和数组洗牌工具函数
2026-01-14 17:29:02 +08:00

252 lines
8.3 KiB
TypeScript
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.
import { AttrCards, AttrInfo } from "./AttrSet";
import { talConf, ItalConf } from "./TalSet";
import { SkillSet, CanSelectSkills, SkillConfig } from "./SkillSet";
import { HeroInfo, CanSelectHeros, heroInfo } from "./heroSet";
/**
* 卡牌类型枚举
*/
export enum CardType {
Skill = 1, // 技能
Talent = 2, // 天赋
Attr = 3, // 属性
Hero = 4 // 英雄(伙伴)
}
/**
* 统一卡牌信息接口 (用于UI显示和逻辑处理)
*/
export interface ICardInfo {
uuid: number;
type: CardType;
name: string;
desc: string;
icon: string;
weight: number; // 抽取权重
tag?: string; // 标签 (如 "special" 表示特殊属性)
payload: any; // 原始配置数据
}
/**
* 等级池配置项
*/
export interface IPoolWeight {
type: CardType;
weight: number;
tag?: string; // 如果指定只从该类型中包含此tag的卡牌中抽取
}
/**
* 1-20 级卡牌池配置表
* 定义每个等级可能出现的卡牌类型及其权重
*/
export const LevelPoolConfigs: Record<number, IPoolWeight[]> = {
1: [{ type: CardType.Skill, weight: 100 }],
2: [{ type: CardType.Attr, weight: 100 }], // 常规属性
3: [{ type: CardType.Talent, weight: 50 }, { type: CardType.Attr, weight: 50, tag: "special" }], // 天赋或特殊属性
4: [{ type: CardType.Attr, weight: 100 }],
5: [{ type: CardType.Talent, weight: 100 }],
6: [{ type: CardType.Hero, weight: 100 }], // 伙伴节点
7: [{ type: CardType.Attr, weight: 80 }, { type: CardType.Skill, weight: 20 }],
8: [{ type: CardType.Attr, weight: 80 }, { type: CardType.Skill, weight: 20 }],
9: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }],
10: [{ type: CardType.Talent, weight: 100 }],
11: [{ type: CardType.Attr, weight: 70 }, { type: CardType.Skill, weight: 30 }],
12: [{ type: CardType.Attr, weight: 70 }, { type: CardType.Skill, weight: 30 }],
13: [{ type: CardType.Attr, weight: 100 }],
14: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }],
15: [{ type: CardType.Talent, weight: 100 }],
16: [{ type: CardType.Attr, weight: 60 }, { type: CardType.Skill, weight: 40 }],
17: [{ type: CardType.Attr, weight: 60 }, { type: CardType.Skill, weight: 40 }],
18: [{ type: CardType.Attr, weight: 50, tag: "special" }, { type: CardType.Talent, weight: 50 }],
19: [{ type: CardType.Attr, weight: 100 }],
20: [{ type: CardType.Talent, weight: 100 }],
};
// ========== 卡牌池缓存 ==========
let _cachedPools: Map<CardType, ICardInfo[]> = new Map();
/**
* 初始化并获取指定类型的完整卡牌池
*/
function getFullPool(type: CardType): ICardInfo[] {
if (_cachedPools.has(type)) {
return _cachedPools.get(type)!;
}
const pool: ICardInfo[] = [];
switch (type) {
case CardType.Attr:
// 转换属性配置
Object.values(AttrCards).forEach(cfg => {
pool.push({
uuid: cfg.uuid,
type: CardType.Attr,
name: cfg.desc.split(" ")[0] || "属性强化", // 简单处理名称
desc: cfg.desc,
icon: cfg.icon,
weight: 100, // 属性默认权重 100
tag: cfg.isSpecial ? "special" : undefined,
payload: cfg
});
});
break;
case CardType.Talent:
// 转换天赋配置
Object.values(talConf).forEach(cfg => {
pool.push({
uuid: cfg.uuid,
type: CardType.Talent,
name: cfg.name,
desc: cfg.desc,
icon: cfg.icon,
weight: 50, // 天赋默认权重 50
payload: cfg
});
});
break;
case CardType.Skill:
// 转换技能配置 (仅包含 CanSelectSkills)
CanSelectSkills.forEach(uuid => {
const cfg = SkillSet[uuid];
if (cfg) {
pool.push({
uuid: cfg.uuid,
type: CardType.Skill,
name: cfg.name,
desc: cfg.info,
icon: cfg.icon,
weight: 80, // 技能默认权重 80
payload: cfg
});
}
});
break;
case CardType.Hero:
// 转换英雄配置 (仅包含 CanSelectHeros)
CanSelectHeros.forEach(uuid => {
const cfg = HeroInfo[uuid];
if (cfg) {
pool.push({
uuid: cfg.uuid,
type: CardType.Hero,
name: cfg.name,
desc: cfg.info,
icon: cfg.path, // 使用 path 作为图标引用
weight: 1000, // 英雄权重极高(如果池子里有的话)
payload: cfg
});
}
});
break;
}
_cachedPools.set(type, pool);
return pool;
}
/**
* 根据等级获取随机卡牌选项
* @param level 当前等级
* @param count 选项数量 (默认3个)
* @param excludeUuids 排除的卡牌UUID列表 (用于去重或排除已拥有)
*/
export function getCardOptions(level: number, count: number = 3, excludeUuids: number[] = []): ICardInfo[] {
// 1. 获取该等级的池配置,如果没有配置,默认给属性
const poolConfigs = LevelPoolConfigs[level] || [{ type: CardType.Attr, weight: 100 }];
const result: ICardInfo[] = [];
const excludeSet = new Set(excludeUuids);
// 循环抽取 count 次
for (let i = 0; i < count; i++) {
// 2.1 随机决定本次抽取的类型池
const selectedPoolConfig = weightedRandomPool(poolConfigs);
if (!selectedPoolConfig) continue;
// 2.2 获取该类型的所有卡牌
let candidates = getFullPool(selectedPoolConfig.type);
// 2.3 过滤 (Tag过滤 + 排除列表 + 已选中过滤)
candidates = candidates.filter(card => {
// Tag 匹配
if (selectedPoolConfig.tag && card.tag !== selectedPoolConfig.tag) return false;
// 排除列表
if (excludeSet.has(card.uuid)) return false;
// 当前轮次已选中去重
if (result.find(r => r.uuid === card.uuid)) return false;
return true;
});
// 2.4 如果该池子空了 (比如技能都学完了),尝试从属性池兜底
if (candidates.length === 0) {
candidates = getFullPool(CardType.Attr).filter(c => !result.find(r => r.uuid === c.uuid));
}
if (candidates.length > 0) {
// 2.5 按卡牌权重随机抽取一张
const card = weightedRandomCard(candidates);
if (card) {
result.push(card);
}
}
}
// 3. 最终结果洗牌 (避免顺序固定)
shuffleArray(result);
return result;
}
// ========== 工具函数 ==========
/**
* 权重随机选择池配置
*/
function weightedRandomPool(configs: IPoolWeight[]): IPoolWeight | null {
if (!configs || configs.length === 0) return null;
const totalWeight = configs.reduce((sum, item) => sum + item.weight, 0);
let randomVal = Math.random() * totalWeight;
for (const config of configs) {
randomVal -= config.weight;
if (randomVal <= 0) {
return config;
}
}
return configs[configs.length - 1];
}
/**
* 权重随机选择卡牌
*/
function weightedRandomCard(cards: ICardInfo[]): ICardInfo | null {
if (!cards || cards.length === 0) return null;
const totalWeight = cards.reduce((sum, item) => sum + item.weight, 0);
let randomVal = Math.random() * totalWeight;
for (const card of cards) {
randomVal -= card.weight;
if (randomVal <= 0) {
return card;
}
}
return cards[cards.length - 1];
}
/**
* 数组洗牌 (Fisher-Yates)
*/
function shuffleArray(array: any[]) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}