I am coding a cost function for a game. Players have a hand of n < 14 Resources of the following possible types:

Fire, Air, Water, Earth, Good, Evil, Law, Chaos, Void

这些被细分为两类:元素(火、气、水、土)和哲学(善、恶、法、混沌).玩家采取的行动有cost个,可以是资源或类别的任意组合.

  • 示例成本:[Fire, Air, Evil, Philosophy, Philosophy]

cost中的每个资源必须由玩家hand中的相应资源支付.成本中的类别可以由该类别中的任何资源填充.成本也可能包括Any,可以由任何资源类型填充.要支付费用,玩家必须从hand个资源中拿出select个资源,然后单击该操作.这将触发一个求值函数,如果selected个资源满足cost,则返回True.

Void是一种"通配符"资源,可用于支付任何其他资源的费用.(这并不适用于其他情况-玩家无法通过使用其他资源支付Void美元的费用.)

  • 上述示例成本的有效付款为[Void, Void, Air, Good, Good]

我目前很难理解如何实现这一点.我当前的判断函数无法处理类别或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,    // This can only appear in a Cost, never a Payment
  PHILOSOPHY, // This can only appear in a Cost, never a Payment
  ANY         // This can only appear in a Cost, never a Payment
}

export const ElementResources: Resource[] = [Resource.FIRE, Resource.AIR, Resource.WATER, Resource.EARTH];
export const PhilosophyResources: Resource[] = [Resource.GOOD, Resource.EVIL, Resource.LAW, Resource.CHAOS];


function isValidExactPayment(cost: Resource[], payment: Resource[]): boolean {
  /** Logic here
   * Returns True if payment matches cost exactly, 
   * according to the rules above */
}

更多示例:

/** Example 1 */
const cost: Resource[] = [Resource.WATER, Resource, EVIL];

isValidExactPayment(cost, [Resource.WATER, Resource.EVIL]); // true
isValidExactPayment(cost, [Resource.EVIL, Resource.VOID]); // true
isValidExactPayment(cost, [Resource.VOID, Resource.EVIL]); // true, order should not matter
isValidExactPayment(cost, [Resource.WATER, Resource.VOID]); // true


/** Example 2 */
const cost: Resource[] = [Resource.VOID];

isValidExactPayment(cost, [Resource.VOID]); // true
isValidExactPayment(cost, [Resource.EVIL]); // false


/** Example 3 */
const cost: Resource[] = [Resource.GOOD];

isValidExactPayment(cost, [Resource.GOOD]); // true
isValidExactPayment(cost, [Resource.VOID]); // true
isValidExactPayment(cost, [Resource.EVIL]); // false


/** Example 4 */
const cost: Resource[] = [Resource.AIR, Resource.PHILOSOPHY, Resource.PHILOSOPHY];

isValidExactPayment(cost, [Resource.AIR, Resource.EVIL, Resource.CHAOS]); // true
isValidExactPayment(cost, [Resource.VOID, Resource.GOOD, Resource.GOOD]); // true
isValidExactPayment(cost, [Resource.AIR, Resource.CHAOS, Resource.VOID]); // true


/** Example 5 */
const cost: Resource[] = [Resource.ELEMENT]

isValidExactPayment(cost, [Resource.FIRE]); // true
isValidExactPayment(cost, [Resource.AIR]); // true
isValidExactPayment(cost, [Resource.WATER]); // true
isValidExactPayment(cost, [Resource.EARTH]); // true
isValidExactPayment(cost, [Resource.VOID]); // true


/** Example 6 */
const cost: Resource[] = [Resource.WATER, Resource.ANY, Resource.ANY]

isValidExactPayment(cost, [Resource.WATER, Resource.WATER, Resource.WATER]); // true
isValidExactPayment(cost, [Resource.FIRE, Resource.FIRE, Resource.FIRE]); // false
isValidExactPayment(cost, [Resource.VOID, Resource.WATER, Resource.LAW]); // true

/** Example 7 */
const cost: Resource[] = [Resource.FIRE, Resource.EVIL, Resource.PHILOSOPHY, Resource.ELEMENT];

isValidExactPayment(cost, [Resource.FIRE, Resource.EVIL, Resource.EVIL, Resource.EARTH]); // true
isValidExactPayment(cost, [Resource.FIRE, Resource.EVIL, Resource.EVIL, Resource.VOID]); // true
isValidExactPayment(cost, [Resource.VOID, Resource.EVIL, Resource.GOOD, Resource.WATER]); // true

我目前很难理解如何实现更复杂的成本判断功能.

推荐答案

为了简化工作,您可以重新组织资源/成本.例如,ANY不是一种资源,它只是一种成本.以下是一种方法:

enum Elements {
  FIRE = 'fire',
  AIR = 'air',
  WATER = 'water',
  EARTH = 'earth',
}

enum Philosophies {
  GOOD = 'good',
  EVIL = 'evil',
  LAW = 'law',
  CHAOS = 'chaos',
}

enum Void {
  VOID = 'void',
}

enum Resource {
  VOID = Void.VOID,
  FIRE = Elements.FIRE,
  AIR = Elements.AIR,
  WATER = Elements.WATER,
  EARTH = Elements.EARTH,
  GOOD = Philosophies.GOOD,
  EVIL = Philosophies.EVIL,
  LAW = Philosophies.LAW,
  CHAOS = Philosophies.CHAOS,
}

const ANY = 'any';
const ELEMENT = 'element';
const PHILOSOPHY = 'philosophy';
type Cost = Resource | typeof ANY | typeof ELEMENT | typeof PHILOSOPHY;

我喜欢使用字符串常量而不是数字枚举,这对于调试来说更好.

这里还有其他一些逻辑组,例如,ELEMENT的成本可以用ElementsVoid来支付,因此我们可以为这些逻辑组创建一些数据 struct .此外,将枚举转换为数组将有助于迭代.

const allResources = Object.values(Resource);

const elementsAndVoid: (Elements | Void)[] = [];
for (const e of Object.values(Elements)) elementsAndVoid.push(e);
elementsAndVoid.push(Void.VOID);

const philosophiesAndVoid: (Philosophies | Void)[] = [];
for (const p of Object.values(Philosophies)) philosophiesAndVoid.push(p);
philosophiesAndVoid.push(Void.VOID);

在开始处理之前,我们需要知道成本和付款数组中每个资源的数量.将资源存储为键值对而不是数组(例如{ fire: 2, water: 3 })要容易得多.我将不重构代码,而是将数组转换为函数中的键值对,但这绝对是您应该考虑的事情.

如果我们的成本和付款都计算在内,我们可以通过将成本减少1,并将我们支付的任何费用减少1来支付每项成本.这需要按照特定性的顺序进行.例如,如果我们把所有的FIRE英镑都押在ELEMENT英镑上,那么当我们试图支付FIRE英镑时,我们可能会缺FIRE英镑,而我们有足够的WATER英镑来支付ELEMENT英镑.因此,更具体的成本优先.

如果我们得到的成本大于0,但没有任何费用可以支付,那么我们知道我们无法支付成本.

下面是一个实现:

function isValidExactPayment(costs: Cost[], payments: Resource[]): boolean {
  // count payment amounts
  const paymentCounts: { [key: string]: number } = {};
  for (const p of payments) {
    if (paymentCounts[p] === undefined) paymentCounts[p] = 0;
    paymentCounts[p]++;
  }
  // count cost amounts
  const costCounts: { [key: string]: number } = {};
  for (const c of costs) {
    if (costCounts[c] === undefined) costCounts[c] = 0;
    costCounts[c]++;
  }
  // Attempt to pay for specific resource - void first
  for (const r of allResources) {
    while (costCounts[r] > 0) {
      if (paymentCounts[r] > 0) {
        costCounts[r]--;
        paymentCounts[r]--;
      }
      // Use leftover void if there's not enough
      else if (paymentCounts[Resource.VOID] > 0) {
        costCounts[r]--;
        paymentCounts[Resource.VOID]--;
      }
      // Not enough specific resource
      else {
        console.log('Not enough:', r);
        return false;
      }
    }
  }
  // Attempt to pay for general elements
  for (const r of elementsAndVoid) {
    while (costCounts[ELEMENT] > 0 && paymentCounts[r] > 0) {
      costCounts[ELEMENT]--;
      paymentCounts[r]--;
    }
  }
  // Not enough elements
  if (costCounts[ELEMENT] > 0) {
    console.log('Not enough:', ELEMENT);
    return false;
  }
  // Attempt to pay for general philosophies
  for (const r of philosophiesAndVoid) {
    while (costCounts[PHILOSOPHY] > 0 && paymentCounts[r] > 0) {
      costCounts[PHILOSOPHY]--;
      paymentCounts[r]--;
    }
  }
  // Not enough philosophies
  if (costCounts[PHILOSOPHY] > 0) {
    console.log('Not enough:', PHILOSOPHY);
    return false;
  }
  // Attempt to pay for any with anything
  for (const r of allResources) {
    while (costCounts[ANY] > 0 && paymentCounts[r] > 0) {
      costCounts[ANY]--;
      paymentCounts[r]--;
    }
  }
  // Not enough any
  if (costCounts[ANY] > 0) {
    console.log('Not enough:', ANY);
    return false;
  }
  // Paid in full :)
  console.log('Paid in full');
  return true;
}

显然可以进行性能优化,但这是可行的.

Typescript相关问答推荐

为什么AB和CD这两种类型在TypScript中可以相互分配?

如何使用Generics保留构造函数的类型信息?

使用带有RxJS主题的服务在Angular中的组件之间传递数据

角形标题大小写所有单词除外

无法将select选项的值设置为ngFor循环中的第一个元素

Typescript泛型类型:按其嵌套属性映射记录列表

匿名静态类推断组成不正确推断

如何使默认导出与CommonJS兼容

用于验证字符串变量的接口成员

ANGLING v17 CLI默认设置为独立:延迟加载是否仍需要@NgModule进行路由?

为什么&;(交集)运算符会删除不相交的属性?

在列表的指令中使用intersectionObservable隐藏按钮

如何合并两个泛型对象并获得完整的类型安全结果?

有没有一种方法可以确保类型的成员实际存在于TypeScript中的对象中?

为什么我的导航在使用Typescript的React Native中不起作用?

在Cypress中,是否可以在不标识错误的情况下对元素调用.Click()方法?

Typescript将键数组映射到属性数组

带有';的类型脚本嵌套对象可选属性错误满足';

内联类型断言的工作原理类似于TypeScrip中的断言函数?

JSX.Element';不可分配给类型';ReactNode';React功能HOC