Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions packages/ludiek/src/plugins/currency/CurrencyPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,13 @@ export class CurrencyPlugin extends LudiekPlugin {
/**
* Try to spend the specified amount of currencies if you have it.
* @return true if it was spent
*
* @todo(#61) Each currency is checked individually, meaning that if there are duplicate Ids it could lead to negative balance.
*/
public payCurrencies(currencies: Currency[]): boolean {
if (!this.hasCurrencies(currencies)) {
const simplified = this.simplifyCurrencyList(currencies);
if (!this.hasCurrencies(simplified)) {
return false;
}
this.loseCurrencies(currencies);
this.loseCurrencies(simplified);
return true;
}

Expand All @@ -120,13 +119,9 @@ export class CurrencyPlugin extends LudiekPlugin {

/**
* Whether we have a list of currencies
*
* @todo(#61) Each currency is checked individually, meaning that if there are duplicate Ids the answer could be misleading.
*/
public hasCurrencies(currencies: Currency[]): boolean {
return currencies.every((c) => {
return this.hasCurrency(c);
});
return this.simplifyCurrencyList(currencies).every((c) => this.hasCurrency(c));
}

/**
Expand Down Expand Up @@ -167,6 +162,14 @@ export class CurrencyPlugin extends LudiekPlugin {
return id in this._state.balances;
}

private simplifyCurrencyList(currencies: Currency[]): Currency[] {
const merged: Record<string, number> = {};
for (const c of currencies) {
merged[c.id] = (merged[c.id] ?? 0) + c.amount;
}
return Object.entries(merged).map(([id, amount]) => ({ id, amount }));
}

private validate(currency: Currency, action: string): void {
if (!this.supportsCurrency(currency.id)) {
throw new InvalidCurrencyError(`Cannot ${action} '${currency.amount}' of '${currency.id}'. Unknown currency`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,48 @@ describe('Multiple currencies', () => {
// Assert
expect(isPaid).toBe(false);
});

it('does not pay duplicate currencies when combined amount exceeds balance', () => {
// Arrange
currency.gainCurrency({ id: '/currency/money', amount: 1 });

// Act — paying 1+1=2 money with only 1 in balance should fail
const isPaid = currency.payCurrencies([
{ id: '/currency/money', amount: 1 },
{ id: '/currency/money', amount: 1 },
]);

// Assert
expect(isPaid).toBe(false);
expect(currency.getBalance('/currency/money')).toBe(1);
});

it('pays duplicate currencies when combined amount is within balance', () => {
// Arrange
currency.gainCurrency({ id: '/currency/money', amount: 3 });

// Act — paying 1+1=2 money with 3 in balance should succeed
const isPaid = currency.payCurrencies([
{ id: '/currency/money', amount: 1 },
{ id: '/currency/money', amount: 1 },
]);

// Assert
expect(isPaid).toBe(true);
expect(currency.getBalance('/currency/money')).toBe(1);
});

it('hasCurrencies returns false for duplicate currencies exceeding balance', () => {
// Arrange
currency.gainCurrency({ id: '/currency/money', amount: 1 });

// Act
const has = currency.hasCurrencies([
{ id: '/currency/money', amount: 1 },
{ id: '/currency/money', amount: 1 },
]);

// Assert
expect(has).toBe(false);
});
});