Allow clearing and replacing auction bids

This commit is contained in:
Quintine committed 2026-09-21 19:47:06 +10:00
1 parent c5661ec255
commit f9668a577e
4 files changed
+47 -19

No files matched your search

+10 -8
View File
@@ -1,4 +1,5 @@
'use client';
import { integerInputValue } from '../lib/integer-input';
import { EcazSetup } from './ecaz-setup';
import { StormCardInspector, StormCardLogInspector } from './storm-cards';
import { KwisatzInspector } from './kwisatz-inspector';
@@ -340,13 +341,13 @@ export function GameTable({
const [richeseShipmentEvent, setRicheseShipmentEvent] = useState('');
const useRicheseShipment = !!g.nexusRichese && richeseShipmentEvent === g.nexusRichese.event;
const [useSmuggler, setUseSmuggler] = useState(true);
const [bidDraft, setBidDraft] = useState({ auction: '', value: 1 });
const auctionKey = `${g.code}/${g.turn}/${g.auction?.remaining ?? 0}`;
const [bidDraft, setBidDraft] = useState({ auction: '', value: '' });
const minimumBid = (g.auction?.bid ?? 0) + 1;
const bidAmount = Math.max(
minimumBid,
bidDraft.auction === auctionKey ? bidDraft.value : minimumBid,
);
const auctionKey = `${g.code}/${g.turn}/${g.auction?.remaining ?? 0}/${minimumBid}`;
const bidText = bidDraft.auction === auctionKey
? bidDraft.value
: String(minimumBid);
const bidAmount = integerInputValue(bidText);
const maximumBid = me.hand?.some((c) =>
canUseAsKaramaRole(g, me, c),
)
@@ -3530,11 +3531,11 @@ export function GameTable({
type="number"
min={minimumBid}
max={maximumBid}
value={bidAmount}
value={bidText}
onChange={(e) =>
setBidDraft({
auction: auctionKey,
value: Number(e.target.value),
value: e.target.value,
})
}
/>
@@ -3552,6 +3553,7 @@ export function GameTable({
...payment,
},
!Number.isSafeInteger(bidAmount) ||
bidAmount < minimumBid ||
bidAmount > maximumBid,
)}
{actionButton('Pass this bid', { type: 'passBid' })}
+12 -11
View File
@@ -1,5 +1,6 @@
'use client';
import { integerInputValue } from '../lib/integer-input';
import { useId, useState } from 'react';
import type { Action, GameView } from '@/game/engine';
import type { RicheseAuctionMethod } from '@/game/richese-auction';
@@ -247,16 +248,16 @@ export function RicheseAuctionLot({ game, act, busy }: Props) {
const me = game.me;
const silent = auction.method === 'silent';
const minimum = silent ? 0 : auction.bid + 1;
const amount =
draft.event === auction.event && draft.amount !== ''
? Number(draft.amount)
: minimum;
const allyPayment = draft.event === auction.event ? Number(draft.ally) : 0;
const bidKey = `${auction.event}/${minimum}`;
const amountText = draft.event === bidKey ? draft.amount : String(minimum);
const allyText = draft.event === bidKey ? draft.ally : '0';
const amount = integerInputValue(amountText);
const allyPayment = integerInputValue(allyText);
const update = (part: Partial<typeof draft>) =>
setDraft({
event: auction.event,
amount: String(amount),
ally: String(allyPayment),
event: bidKey,
amount: amountText,
ally: allyText,
...part,
});
const ownPayment = amount - allyPayment;
@@ -368,7 +369,7 @@ export function RicheseAuctionLot({ game, act, busy }: Props) {
min={minimum}
step={1}
max={auction.ownAvailable + auction.allyAvailable}
value={amount}
value={amountText}
disabled={busy}
onChange={(event) => update({ amount: event.target.value })}
/>
@@ -380,8 +381,8 @@ export function RicheseAuctionLot({ game, act, busy }: Props) {
type="number"
min={0}
step={1}
max={Math.min(amount, auction.allyAvailable)}
value={allyPayment}
max={Number.isFinite(amount) ? Math.min(amount, auction.allyAvailable) : auction.allyAvailable}
value={allyText}
disabled={busy}
onChange={(event) => update({ ally: event.target.value })}
/>
+6
View File
@@ -0,0 +1,6 @@
// A blank edit is not a zero. Keep input text separate from submitted numbers.
export function integerInputValue(text: string): number {
if (text.trim() === '') return Number.NaN;
const value = Number(text);
return Number.isSafeInteger(value) ? value : Number.NaN;
}
+19
View File
@@ -0,0 +1,19 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { integerInputValue } from '../lib/integer-input';
void test('clearing a numeric draft stays invalid until a replacement is entered', () => {
for (const replacement of ['2', '5', '9', '20']) {
assert.equal(integerInputValue('1'), 1);
assert.ok(Number.isNaN(integerInputValue('')));
assert.equal(integerInputValue(replacement), Number(replacement));
}
});
void test('explicit zero remains distinct from an empty or non-integer bid', () => {
assert.equal(integerInputValue('0'), 0);
assert.equal(integerInputValue('02'), 2);
for (const draft of ['', ' ', '-', '1.5', 'Infinity', '9007199254740992']) {
assert.ok(Number.isNaN(integerInputValue(draft)), draft);
}
});