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
These are subdivided into two Categories: Elements (Fire, Air, Water, Earth) and Philosophies (Good, Evil, Law, Chaos). Actions taken by the player have a cost, which may be any combination of Resources or Categories.
- Example cost:
[Fire, Air, Evil, Philosophy, Philosophy]
Each resource in the cost must be paid by the corresponding resource from the player's hand. Categories in the cost may be filled by any Resource in that Category. Costs may also include Any
, which can be filled by any Resource type. To pay a cost, the player must select Resources from their hand, then click on the Action. This triggers an evaluation function which returns True
if the selected Resources fulfill the cost.
Void
is a "wildcard" Resource that may be used to pay for any other Resource. (This does not apply the other way around - a player cannot fulfill a Void
cost by paying with another resource.)
- A valid payment for the example cost above would be
[Void, Void, Air, Good, Good]
I am currently stumped for how to implement this. My current evaluation function cannot handle the substitutions of Categories or Void
; it simply checks for exact equivalence:
/** 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;
}
Based on feedback, here is a better formulation of the problem:
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 */
}
Some more examples:
/** 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
I'm currently pretty stumped for how to implement the more complex cost evaluation function.