TypeScript实现支持通配符与类别的资源支付校验函数
游戏资源成本支付校验逻辑实现
核心规则
- 资源基础规则:玩家手牌资源持有量小于14,共9种基础资源,分为两个类别:
- 元素类:Fire、Air、Water、Earth
- 哲学类:Good、Evil、Law、Chaos
- 特殊基础资源:Void
- 成本抵扣规则:
- 成本可包含具体资源、类别标识、通用标识三类条目
ELEMENT标识:可被任意元素类资源抵扣PHILOSOPHY标识:可被任意哲学类资源抵扣ANY标识:可被任意资源抵扣
- 通配特殊规则:
- Void是支付侧专属通配,可抵扣任意类型成本项
- 若成本项为Void,仅能使用Void支付,其他资源无法替代
- 支付匹配不校验资源顺序,要求支付资源与成本完全匹配,无超额无短缺
原有缺陷代码
原有实现仅支持资源精确等值匹配,无法处理类别抵扣、Void通配场景:
/** Returns true if the resources currently selected by the player * satisfy the @cost of the option selected */ evaluatePayment(cost: Resource[]): boolean { let isValidPayment: boolean = true; if (cost) { const playerSelectedResources: Resource[] = this.playerHand .filter(r => r.isSelected) .map(hr => hr.type); // Check that selected resources cover cost const missingCost = cost.filter(r => playerSelectedResources.indexOf(r)); // Check that no additional resources are selected const excessPaid = playerSelectedResources.filter(r => cost.indexOf(r)); if (missingCost.length > 0 || excessPaid.length > 0) { isValidPayment = false; } } return isCostSelected; }
形式化类型定义
enum Resource { FIRE, AIR, WATER, EARTH, GOOD, EVIL, LAW, CHAOS, VOID, ELEMENT, // 仅出现在成本中,不会出现在支付资源里 PHILOSOPHY, // 仅出现在成本中,不会出现在支付资源里 ANY // 仅出现在成本中,不会出现在支付资源里 } export const ElementResources: Resource[] = [Resource.FIRE, Resource.AIR, Resource.WATER, Resource.EARTH]; export const PhilosophyResources: Resource[] = [Resource.GOOD, Resource.EVIL, Resource.LAW, Resource.CHAOS]; /** * 校验支付资源是否完全匹配成本要求 * @param cost 待支付成本项 * @param payment 玩家选择的支付资源 * @returns 匹配返回true,否则返回false */ function isValidExactPayment(cost: Resource[], payment: Resource[]): boolean { // 实现逻辑 }
校验用例
所有实现需满足以下示例的预期返回结果:
// 示例1 const cost1: Resource[] = [Resource.WATER, Resource.EVIL]; isValidExactPayment(cost1, [Resource.WATER, Resource.EVIL]); // true isValidExactPayment(cost1, [Resource.EVIL, Resource.VOID]); // true isValidExactPayment(cost1, [Resource.VOID, Resource.EVIL]); // true 顺序不影响结果 isValidExactPayment(cost1, [Resource.WATER, Resource.VOID]); // true // 示例2 const cost2: Resource[] = [Resource.VOID]; isValidExactPayment(cost2, [Resource.VOID]); // true isValidExactPayment(cost2, [Resource.EVIL]); // false // 示例3 const cost3: Resource[] = [Resource.GOOD]; isValidExactPayment(cost3, [Resource.GOOD]); // true isValidExactPayment(cost3, [Resource.VOID]); // true isValidExactPayment(cost3, [Resource.EVIL]); // false // 示例4 const cost4: Resource[] = [Resource.AIR, Resource.PHILOSOPHY, Resource.PHILOSOPHY]; isValidExactPayment(cost4, [Resource.AIR, Resource.EVIL, Resource.CHAOS]); // true isValidExactPayment(cost4, [Resource.VOID, Resource.GOOD, Resource.GOOD]); // true isValidExactPayment(cost4, [Resource.AIR, Resource.CHAOS, Resource.VOID]); // true // 示例5 const cost5: Resource[] = [Resource.ELEMENT] isValidExactPayment(cost5, [Resource.FIRE]); // true isValidExactPayment(cost5, [Resource.AIR]); // true isValidExactPayment(cost5, [Resource.WATER]); // true isValidExactPayment(cost5, [Resource.EARTH]); // true isValidExactPayment(cost5, [Resource.VOID]); // true // 示例6 const cost6: Resource[] = [Resource.WATER, Resource.ANY, Resource.ANY] isValidExactPayment(cost6, [Resource.WATER, Resource.WATER, Resource.WATER]); // true isValidExactPayment(cost6, [Resource.FIRE, Resource.FIRE, Resource.FIRE]); // false isValidExactPayment(cost6, [Resource.VOID, Resource.WATER, Resource.LAW]); // true // 示例7 const cost7: Resource[] = [Resource.FIRE, Resource.EVIL, Resource.PHILOSOPHY, Resource.ELEMENT]; isValidExactPayment(cost7, [Resource.FIRE, Resource.EVIL, Resource.EVIL, Resource.EARTH]); // true isValidExactPayment(cost7, [Resource.FIRE, Resource.EVIL, Resource.EVIL, Resource.VOID]); // true isValidExactPayment(cost7, [Resource.VOID, Resource.EVIL, Resource.GOOD, Resource.WATER]); // true
符合要求的实现
匹配逻辑按照从最严格规则到最宽松规则的顺序处理,避免通配资源被提前占用导致严格规则校验失败:
- 首先校验成本和支付资源长度是否一致,长度不同直接返回false
- 优先匹配成本中的Void项:这类成本只能用支付中的Void抵扣,优先占用Void资源
- 其次匹配成本中的具体基础资源项(Fire/Air/Water/Earth/Good/Evil/Law/Chaos):优先用同类型支付资源抵扣,不足部分用剩余Void抵扣
- 再匹配类别标识项:ELEMENT优先用剩余元素类资源抵扣,PHILOSOPHY优先用剩余哲学类资源抵扣,不足部分用剩余Void抵扣
- 最后匹配ANY通用标识项:优先用剩余所有非Void基础资源抵扣,不足部分用剩余Void抵扣
- 所有成本项匹配完成且无剩余支付资源时返回true,否则返回false
function isValidExactPayment(cost: Resource[], payment: Resource[]): boolean { // 长度不一致直接不匹配 if (cost.length !== payment.length) return false; // 统计支付侧各资源剩余数量,使用副本避免修改原数组 const paymentCount = new Map<Resource, number>(); for (const res of payment) { paymentCount.set(res, (paymentCount.get(res) || 0) + 1); } // 统计成本侧各类型条目剩余数量 const costCount = new Map<Resource, number>(); for (const c of cost) { costCount.set(c, (costCount.get(c) || 0) + 1); } // 工具函数:消耗指定数量的某类支付资源,返回实际成功消耗的数量 const consumePayment = (res: Resource, num: number): number => { const available = paymentCount.get(res) || 0; const consume = Math.min(available, num); if (consume > 0) { paymentCount.set(res, available - consume); } return consume; }; // 1. 优先匹配成本中的VOID,仅支持用VOID支付 let voidCost = costCount.get(Resource.VOID) || 0; const voidForVoidCost = consumePayment(Resource.VOID, voidCost); if (voidForVoidCost < voidCost) return false; costCount.delete(Resource.VOID); // 2. 匹配具体基础资源成本 const concreteResources = [...ElementResources, ...PhilosophyResources]; for (const res of concreteResources) { let need = costCount.get(res) || 0; if (need === 0) continue; // 优先用同类型资源支付 const paid = consumePayment(res, need); need -= paid; if (need > 0) { // 不足部分用Void补 const paidVoid = consumePayment(Resource.VOID, need); need -= paidVoid; } if (need > 0) return false; costCount.delete(res); } // 3. 匹配ELEMENT成本,优先用剩余元素资源,不足用Void let elementNeed = costCount.get(Resource.ELEMENT) || 0; if (elementNeed > 0) { for (const eleRes of ElementResources) { if (elementNeed <= 0) break; const paid = consumePayment(eleRes, elementNeed); elementNeed -= paid; } if (elementNeed > 0) { const paidVoid = consumePayment(Resource.VOID, elementNeed); elementNeed -= paidVoid; } if (elementNeed > 0) return false; costCount.delete(Resource.ELEMENT); } // 4. 匹配PHILOSOPHY成本,优先用剩余哲学资源,不足用Void let philoNeed = costCount.get(Resource.PHILOSOPHY) || 0; if (philoNeed > 0) { for (const philoRes of PhilosophyResources) { if (philoNeed <= 0) break; const paid = consumePayment(philoRes, philoNeed); philoNeed -= paid; } if (philoNeed > 0) { const paidVoid = consumePayment(Resource.VOID, philoNeed); philoNeed -= paidVoid; } if (philoNeed > 0) return false; costCount.delete(Resource.PHILOSOPHY); } // 5. 匹配ANY成本,优先用剩余所有非Void资源,不足用Void let anyNeed = costCount.get(Resource.ANY) || 0; if (anyNeed > 0) { for (const res of [...ElementResources, ...PhilosophyResources]) { if (anyNeed <= 0) break; const paid = consumePayment(res, anyNeed); anyNeed -= paid; } if (anyNeed > 0) { const paidVoid = consumePayment(Resource.VOID, anyNeed); anyNeed -= paidVoid; } if (anyNeed > 0) return false; costCount.delete(Resource.ANY); } // 校验无剩余未支付成本、无剩余超额支付资源 let remainingPayment = 0; for (const count of paymentCount.values()) { remainingPayment += count; } return costCount.size === 0 && remainingPayment === 0; }
内容的提问来源于stack exchange,提问作者GreatDane
相关产品推荐
相关产品推荐

