You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

符合要求的实现

匹配逻辑按照从最严格规则到最宽松规则的顺序处理,避免通配资源被提前占用导致严格规则校验失败:

  1. 首先校验成本和支付资源长度是否一致,长度不同直接返回false
  2. 优先匹配成本中的Void项:这类成本只能用支付中的Void抵扣,优先占用Void资源
  3. 其次匹配成本中的具体基础资源项(Fire/Air/Water/Earth/Good/Evil/Law/Chaos):优先用同类型支付资源抵扣,不足部分用剩余Void抵扣
  4. 再匹配类别标识项:ELEMENT优先用剩余元素类资源抵扣,PHILOSOPHY优先用剩余哲学类资源抵扣,不足部分用剩余Void抵扣
  5. 最后匹配ANY通用标识项:优先用剩余所有非Void基础资源抵扣,不足部分用剩余Void抵扣
  6. 所有成本项匹配完成且无剩余支付资源时返回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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:00:53