From a1136f067c8c94008e64a9af4091e9e6a49e3b34 Mon Sep 17 00:00:00 2001 From: TanPat Date: Fri, 2 Jan 2026 17:51:30 +0530 Subject: [PATCH 1/3] Add custom points cap to splendor games --- src/ps/games/splendor/index.ts | 34 ++++++++++++++++++++-------------- src/ps/games/splendor/types.ts | 1 + 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/ps/games/splendor/index.ts b/src/ps/games/splendor/index.ts index de53d0a..c50ff2a 100644 --- a/src/ps/games/splendor/index.ts +++ b/src/ps/games/splendor/index.ts @@ -29,6 +29,11 @@ export class Splendor extends BaseGame { constructor(ctx: BaseContext) { super(ctx); + + this.state.pointsToWin = parseInt(ctx.args.join('')); + if (this.state.pointsToWin < 8 || this.state.pointsToWin > 20) this.throw(); + if (isNaN(this.state.pointsToWin)) this.state.pointsToWin = POINTS_TO_WIN; + super.persist(ctx); if (ctx.backup) return; @@ -67,7 +72,7 @@ export class Splendor extends BaseGame { this.turn === lastPlayerInRound && Object.values(this.players) .filter(player => !player.out) - .some(player => this.state.playerData[player.turn].points >= POINTS_TO_WIN) + .some(player => this.state.playerData[player.turn].points >= this.state.pointsToWin) ); } @@ -216,8 +221,7 @@ export class Splendor extends BaseGame { case VIEW_ACTION_TYPE.CLICK_DECK: { if (!['1', '2', '3'].includes(actionCtx)) throw new ChatError(this.$T('GAME.SPLENDOR.WHICH_TIER')); const tier = +actionCtx as 1 | 2 | 3; - if (this.state.board.cards[tier].deck.length === 0) - throw new ChatError(this.$T('GAME.SPLENDOR.DECK_EMPTY', { tier })); + if (this.state.board.cards[tier].deck.length === 0) throw new ChatError(this.$T('GAME.SPLENDOR.DECK_EMPTY', { tier })); const canReserve = this.canReserve(player); if (!canReserve) throw new ChatError(this.$T('GAME.SPLENDOR.RESERVE_LIMIT')); @@ -234,10 +238,8 @@ export class Splendor extends BaseGame { const tokens = this.parseTokens(actionCtx, true); const discarding = Object.values(tokens).sum(); - if (discarding < toDiscard) - throw new ChatError(this.$T('GAME.SPLENDOR.DISCARD_MORE', { required: toDiscard, discarding })); - if (!this.canAfford(tokens, playerData.tokens, null, false)) - throw new ChatError(this.$T('GAME.SPLENDOR.CANNOT_DISCARD')); + if (discarding < toDiscard) throw new ChatError(this.$T('GAME.SPLENDOR.DISCARD_MORE', { required: toDiscard, discarding })); + if (!this.canAfford(tokens, playerData.tokens, null, false)) throw new ChatError(this.$T('GAME.SPLENDOR.CANNOT_DISCARD')); this.spendTokens(tokens, playerData); logEntry = { turn: player.turn, time: new Date(), action: VIEW_ACTION_TYPE.TOO_MANY_TOKENS, ctx: { discard: tokens } }; @@ -278,8 +280,7 @@ export class Splendor extends BaseGame { let reservedId: string; if (deckReserve) { const tier = deckReserve as 1 | 2 | 3; - if (this.state.board.cards[tier].deck.length === 0) - throw new ChatError(this.$T('GAME.SPLENDOR.DECK_EMPTY', { tier })); + if (this.state.board.cards[tier].deck.length === 0) throw new ChatError(this.$T('GAME.SPLENDOR.DECK_EMPTY', { tier })); const [card] = this.state.board.cards[tier].deck.splice(0, 1); playerData.reserved.push(card); @@ -421,8 +422,7 @@ export class Splendor extends BaseGame { const amt = +(entry.match(/\d/) ?? '0'); if (!(amt >= 0 && amt < 10)) throw new ChatError(this.$T('GAME.SPLENDOR.INVALID_COUNT', { value: entry.substring(1) })); if (!AllTokenTypes.includes(type)) throw new ChatError(this.$T('GAME.SPLENDOR.UNRECOGNIZED_TYPE', { type })); - if (type === TOKEN_TYPE.DRAGON && !allowDragon) - throw new ChatError(this.$T('GAME.SPLENDOR.DRAGON_NOT_ALLOWED')); + if (type === TOKEN_TYPE.DRAGON && !allowDragon) throw new ChatError(this.$T('GAME.SPLENDOR.DRAGON_NOT_ALLOWED')); tokens[type] += amt; }); return tokens; @@ -433,8 +433,7 @@ export class Splendor extends BaseGame { if (count > 0) return { type, count, available: this.state.board.tokens[type], name: metadata.types[type].name }; }); - if (tokens[TOKEN_TYPE.DRAGON]) - return { success: false, error: this.$T('GAME.SPLENDOR.DRAGON_ONLY_BY_RESERVE') }; + if (tokens[TOKEN_TYPE.DRAGON]) return { success: false, error: this.$T('GAME.SPLENDOR.DRAGON_ONLY_BY_RESERVE') }; const tooMany = input.filter(({ count, available }) => count > available); if (tooMany.length > 0) { @@ -513,7 +512,14 @@ export class Splendor extends BaseGame { else view = { type: 'player', active: false, self: side }; } else view = { type: 'spectator', active: false, action: this.winCtx ? VIEW_ACTION_TYPE.GAME_END : null }; - const ctx: RenderCtx = { id: this.id, board: this.state.board, players: this.state.playerData, turns: this.turns, view, $T: this.$T }; + const ctx: RenderCtx = { + id: this.id, + board: this.state.board, + players: this.state.playerData, + turns: this.turns, + view, + $T: this.$T, + }; if (this.winCtx) { ctx.header = this.$T('GAME.GAME_ENDED'); diff --git a/src/ps/games/splendor/types.ts b/src/ps/games/splendor/types.ts index 100d46e..8ef91f9 100644 --- a/src/ps/games/splendor/types.ts +++ b/src/ps/games/splendor/types.ts @@ -82,6 +82,7 @@ export type ViewType = | (ActivePlayer & ActionState); export type State = { + pointsToWin: number; turn: Turn; board: Board; playerData: Record; From 3dea7769dba7a0241a38b73bfe08fe0ecbe3e233 Mon Sep 17 00:00:00 2001 From: PartMan Date: Fri, 2 Jan 2026 18:28:11 +0530 Subject: [PATCH 2/3] chore: Rename POINTS_TO_WIN to DEFAULT_POINTS_TO_WIN --- src/ps/games/splendor/constants.ts | 2 +- src/ps/games/splendor/index.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ps/games/splendor/constants.ts b/src/ps/games/splendor/constants.ts index 89b0619..7326a76 100644 --- a/src/ps/games/splendor/constants.ts +++ b/src/ps/games/splendor/constants.ts @@ -28,6 +28,6 @@ export enum VIEW_ACTION_TYPE { GAME_END = 'end', } -export const POINTS_TO_WIN = 15; +export const DEFAULT_POINTS_TO_WIN = 15; export const MAX_TOKEN_COUNT = 10; export const MAX_RESERVE_COUNT = 3; diff --git a/src/ps/games/splendor/index.ts b/src/ps/games/splendor/index.ts index c50ff2a..d07fc7c 100644 --- a/src/ps/games/splendor/index.ts +++ b/src/ps/games/splendor/index.ts @@ -2,9 +2,9 @@ import { BaseGame } from '@/ps/games/game'; import { ACTIONS, AllTokenTypes, + DEFAULT_POINTS_TO_WIN, MAX_RESERVE_COUNT, MAX_TOKEN_COUNT, - POINTS_TO_WIN, TOKEN_TYPE, TokenTypes, VIEW_ACTION_TYPE, @@ -32,7 +32,7 @@ export class Splendor extends BaseGame { this.state.pointsToWin = parseInt(ctx.args.join('')); if (this.state.pointsToWin < 8 || this.state.pointsToWin > 20) this.throw(); - if (isNaN(this.state.pointsToWin)) this.state.pointsToWin = POINTS_TO_WIN; + if (isNaN(this.state.pointsToWin)) this.state.pointsToWin = DEFAULT_POINTS_TO_WIN; super.persist(ctx); From 233fd0306db7f148e12359f9e0c37938a7fd0d4a Mon Sep 17 00:00:00 2001 From: PartMan Date: Fri, 2 Jan 2026 18:43:40 +0530 Subject: [PATCH 3/3] chore: Add error message for invalid cap --- src/i18n/languages/english.ts | 1 + src/i18n/languages/french.ts | 1 + src/i18n/languages/hindi.ts | 1 + src/i18n/languages/portuguese.ts | 1 + src/ps/games/splendor/constants.ts | 2 ++ src/ps/games/splendor/index.ts | 10 +++++++--- 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/i18n/languages/english.ts b/src/i18n/languages/english.ts index f720b72..039e2f2 100644 --- a/src/i18n/languages/english.ts +++ b/src/i18n/languages/english.ts @@ -168,6 +168,7 @@ export default { RESERVE: 'Reserve!', BUY_CARD: 'Buy {{card}}!', }, + INVALID_POINTS_CAP: 'The point cap must be between {{minCap}} and {{maxCap}}. {{cap}} is invalid.', INVALID_CARD: '{{card}} is not a valid card.', CARD_NOT_ACCESSIBLE: 'Cannot access {{card}} for the desired action.', DISCARD_TOKENS_REQUIRED: 'You need to discard tokens!', diff --git a/src/i18n/languages/french.ts b/src/i18n/languages/french.ts index 220358b..8378ffe 100644 --- a/src/i18n/languages/french.ts +++ b/src/i18n/languages/french.ts @@ -165,6 +165,7 @@ export default { RESERVE: 'Réserver !', BUY_CARD: 'Acheter {{card}} !', }, + INVALID_POINTS_CAP: 'Le plafond de points doit être entre {{minCap}} et {{maxCap}}. {{cap}} est invalide.', INVALID_CARD: "{{card}} n'est pas une carte valide.", CARD_NOT_ACCESSIBLE: "Impossible d'accéder à {{card}} pour l'action souhaitée.", DISCARD_TOKENS_REQUIRED: 'Vous devez défausser des jetons !', diff --git a/src/i18n/languages/hindi.ts b/src/i18n/languages/hindi.ts index 079cb5d..fa1670e 100644 --- a/src/i18n/languages/hindi.ts +++ b/src/i18n/languages/hindi.ts @@ -153,6 +153,7 @@ export default { RESERVE: 'Reserve karo!', BUY_CARD: '{{card}} kharido!', }, + INVALID_POINTS_CAP: 'Point cap {{minCap}} aur {{maxCap}} ke beech hona chahiye. {{cap}} valid nahi hai.', INVALID_CARD: '{{card}} ek valid card nahi hai.', CARD_NOT_ACCESSIBLE: '{{card}} is action ke liye accessible nahi hai.', DISCARD_TOKENS_REQUIRED: 'Aapko tokens discard karne honge!', diff --git a/src/i18n/languages/portuguese.ts b/src/i18n/languages/portuguese.ts index 45c954f..f13bde1 100644 --- a/src/i18n/languages/portuguese.ts +++ b/src/i18n/languages/portuguese.ts @@ -164,6 +164,7 @@ export default { RESERVE: 'Reservar!', BUY_CARD: 'Comprar {{card}}!', }, + INVALID_POINTS_CAP: 'O limite de pontos deve estar entre {{minCap}} e {{maxCap}}. {{cap}} é inválido.', INVALID_CARD: '{{card}} não é uma carta válida.', CARD_NOT_ACCESSIBLE: 'Não é possível acessar {{card}} para a ação desejada.', DISCARD_TOKENS_REQUIRED: 'Você precisa descartar fichas!', diff --git a/src/ps/games/splendor/constants.ts b/src/ps/games/splendor/constants.ts index 7326a76..5e9013c 100644 --- a/src/ps/games/splendor/constants.ts +++ b/src/ps/games/splendor/constants.ts @@ -28,6 +28,8 @@ export enum VIEW_ACTION_TYPE { GAME_END = 'end', } +export const MIN_POINTS_TO_WIN = 8; +export const MAX_POINTS_TO_WIN = 21; export const DEFAULT_POINTS_TO_WIN = 15; export const MAX_TOKEN_COUNT = 10; export const MAX_RESERVE_COUNT = 3; diff --git a/src/ps/games/splendor/index.ts b/src/ps/games/splendor/index.ts index d07fc7c..6c81356 100644 --- a/src/ps/games/splendor/index.ts +++ b/src/ps/games/splendor/index.ts @@ -3,8 +3,10 @@ import { ACTIONS, AllTokenTypes, DEFAULT_POINTS_TO_WIN, + MAX_POINTS_TO_WIN, MAX_RESERVE_COUNT, MAX_TOKEN_COUNT, + MIN_POINTS_TO_WIN, TOKEN_TYPE, TokenTypes, VIEW_ACTION_TYPE, @@ -30,9 +32,11 @@ export class Splendor extends BaseGame { constructor(ctx: BaseContext) { super(ctx); - this.state.pointsToWin = parseInt(ctx.args.join('')); - if (this.state.pointsToWin < 8 || this.state.pointsToWin > 20) this.throw(); - if (isNaN(this.state.pointsToWin)) this.state.pointsToWin = DEFAULT_POINTS_TO_WIN; + const givenCap = ctx.args.join('').trim(); + this.state.pointsToWin = givenCap ? parseInt(givenCap) : DEFAULT_POINTS_TO_WIN; + if (this.state.pointsToWin < MIN_POINTS_TO_WIN || this.state.pointsToWin > MAX_POINTS_TO_WIN || isNaN(this.state.pointsToWin)) { + this.throw('GAME.SPLENDOR.INVALID_POINTS_CAP', { cap: givenCap, minCap: MIN_POINTS_TO_WIN, maxCap: MAX_POINTS_TO_WIN }); + } super.persist(ctx);