Allow clearing and replacing auction bids
This commit is contained in:
1 parent
c5661ec255
commit
f9668a577e
4 files changed
+47
-19
No files matched your search
@@ -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' })}
|
||||
|
||||
@@ -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 })}
|
||||
/>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user