Allow Distrans between completed auction bids
Verify and publish checkpoint container / container (push) Successful in 10m44s
Verify and publish checkpoint container / container (push) Successful in 10m44s
This commit is contained in:
1 parent
ab7a947833
commit
ccc4c7bb40
16 files changed
+250
-80
No files matched your search
+63
-39
@@ -15,46 +15,70 @@ type DistransAuctionContext = {
|
||||
currentAuctionSale?: unknown;
|
||||
};
|
||||
|
||||
/** The printed card excludes use during a bid. Provisionally, an ordinary
|
||||
* lot has not begun bidding until its first bid or pass. Inspection and
|
||||
* opening the physical lot do not start that bid or change the dealt pool.
|
||||
* An untouched Richese lot likewise has no bid yet.
|
||||
* Once any bidder acts, even a pass or a zero sealed bid, it is closed. */
|
||||
const amount = (value: unknown): value is number =>
|
||||
typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
|
||||
const ids = (value: unknown): value is string[] =>
|
||||
Array.isArray(value) && value.every(id => typeof id === 'string' && id.length > 0) &&
|
||||
new Set(value).size === value.length;
|
||||
const record = (value: unknown): value is Record<string, unknown> =>
|
||||
!!value && typeof value === 'object' && !Array.isArray(value);
|
||||
|
||||
function validSpecial(s: RicheseAuction, funding: Record<string, unknown> | null | undefined): boolean {
|
||||
if (!(s.method === 'onceAround' || s.method === 'silent' || s.method === 'normal') ||
|
||||
!(s.source === 'cache' || s.source === 'blackMarket') ||
|
||||
(s.source === 'cache' && s.method === 'normal') ||
|
||||
typeof s.event !== 'string' || !s.event || typeof s.cardId !== 'string' || !s.cardId ||
|
||||
typeof s.owner !== 'string' || !s.owner || s.outcome !== null ||
|
||||
!ids(s.eligible) || !s.eligible.length || !ids(s.order) || !ids(s.tieOrder) ||
|
||||
s.order.length !== s.eligible.length || s.tieOrder.length !== s.eligible.length ||
|
||||
!s.order.every(id => s.eligible.includes(id)) || !s.tieOrder.every(id => s.eligible.includes(id)) ||
|
||||
!ids(s.acted) || !ids(s.passed) ||
|
||||
!s.acted.every(id => s.eligible.includes(id)) || !s.passed.every(id => s.eligible.includes(id)) ||
|
||||
!amount(s.bid) || (s.bid === 0 ? s.bidder !== null : !s.bidder || !s.eligible.includes(s.bidder)) ||
|
||||
!record(s.sealed) || (funding != null && !record(funding))) return false;
|
||||
|
||||
if (funding && !Object.entries(funding).every(([id, entry]) => {
|
||||
if (!s.eligible.includes(id) || !record(entry) || !amount(entry.amount) ||
|
||||
!amount(entry.allyPayment) || entry.allyPayment > entry.amount) return false;
|
||||
return entry.donor === null || (typeof entry.donor === 'string' && entry.donor.length > 0);
|
||||
})) return false;
|
||||
|
||||
if (s.method === 'silent') {
|
||||
const sealed = Object.keys(s.sealed);
|
||||
return s.active === null && s.bid === 0 && s.bidder === null && s.passed.length === 0 &&
|
||||
sealed.length === s.acted.length && sealed.every(id => s.acted.includes(id) && amount(s.sealed[id]));
|
||||
}
|
||||
if (Object.keys(s.sealed).length) return false;
|
||||
if (s.method === 'onceAround') {
|
||||
const remaining = s.order.filter(id => !s.acted.includes(id));
|
||||
return remaining.length > 0 && s.active === remaining[0] &&
|
||||
(s.bidder !== null || remaining.some(id => id !== s.owner)) &&
|
||||
s.passed.every(id => s.acted.includes(id)) &&
|
||||
(!s.bidder || (s.acted.includes(s.bidder) && !s.passed.includes(s.bidder)));
|
||||
}
|
||||
if (s.acted.length || !s.active || !s.eligible.includes(s.active) ||
|
||||
s.passed.includes(s.active) || s.active === s.bidder ||
|
||||
(s.bidder !== null && s.passed.includes(s.bidder))) return false;
|
||||
const previous = s.passed.at(-1) ?? s.bidder;
|
||||
if (!previous) return s.active === s.order[0];
|
||||
const start = s.order.indexOf(previous);
|
||||
const next = s.order.slice(start + 1).concat(s.order.slice(0, start + 1))
|
||||
.find(id => id !== s.bidder && !s.passed.includes(id));
|
||||
return s.active === next;
|
||||
}
|
||||
|
||||
/** Provisional interpretation: a completed bid or pass ends the interval
|
||||
* during which Distrans is forbidden. Preserve the existing open lot and all
|
||||
* accepted bid reservations; transfers must still satisfy their normal rules. */
|
||||
export function distransAuctionBlock(g: DistransAuctionContext): string | null {
|
||||
if (g.currentAuctionSale || (!g.auction && !g.richeseAuction)) return null;
|
||||
const special = g.richeseAuction;
|
||||
if (g.phase === 3 && !g.auction && special &&
|
||||
(special.method === 'onceAround' || special.method === 'silent' || special.method === 'normal') &&
|
||||
(special.source === 'cache' || special.source === 'blackMarket') &&
|
||||
(special.source !== 'cache' || special.method !== 'normal') &&
|
||||
typeof special.event === 'string' && special.event.length > 0 &&
|
||||
typeof special.cardId === 'string' && special.cardId.length > 0 &&
|
||||
typeof special.owner === 'string' && special.owner.length > 0 &&
|
||||
special.outcome === null && special.bid === 0 && special.bidder === null &&
|
||||
Array.isArray(special.eligible) && special.eligible.length > 0 &&
|
||||
special.eligible.every(id => typeof id === 'string' && id.length > 0) &&
|
||||
new Set(special.eligible).size === special.eligible.length &&
|
||||
Array.isArray(special.order) && special.order.length === special.eligible.length &&
|
||||
new Set(special.order).size === special.order.length &&
|
||||
special.order.every(id => special.eligible.includes(id)) &&
|
||||
Array.isArray(special.tieOrder) && special.tieOrder.length === special.eligible.length &&
|
||||
new Set(special.tieOrder).size === special.tieOrder.length &&
|
||||
special.tieOrder.every(id => special.eligible.includes(id)) &&
|
||||
(special.method === 'silent' ? special.active === null : special.active === special.order[0]) &&
|
||||
(special.method !== 'onceAround' || special.order.some(id => id !== special.owner)) &&
|
||||
Array.isArray(special.acted) && special.acted.length === 0 &&
|
||||
Array.isArray(special.passed) && special.passed.length === 0 &&
|
||||
special.sealed && typeof special.sealed === 'object' && !Array.isArray(special.sealed) &&
|
||||
Object.keys(special.sealed).length === 0 &&
|
||||
(!g.richeseFunding || (typeof g.richeseFunding === 'object' && !Array.isArray(g.richeseFunding) &&
|
||||
Object.keys(g.richeseFunding).length === 0))) return null;
|
||||
if (g.phase === 3 && !g.auction && g.richeseAuction &&
|
||||
validSpecial(g.richeseAuction, g.richeseFunding)) return null;
|
||||
const lot = g.auction;
|
||||
if (
|
||||
g.phase === 3 && !g.richeseAuction && lot &&
|
||||
Number.isSafeInteger(lot.index) && lot.index >= 0 &&
|
||||
lot.index < lot.cards.length && !!lot.cards[lot.index] &&
|
||||
lot.bid === 0 && lot.bidder === null && lot.passed.length === 0 &&
|
||||
(lot.allyPayment ?? 0) === 0
|
||||
) return null;
|
||||
return 'Distrans is unavailable after bidding starts on this lot. Finish its auction first; other special-auction timing remains guarded.';
|
||||
if (g.phase === 3 && !g.richeseAuction && lot && Array.isArray(lot.cards) &&
|
||||
Number.isSafeInteger(lot.index) && lot.index >= 0 && lot.index < lot.cards.length && !!lot.cards[lot.index] &&
|
||||
amount(lot.bid) && (lot.bid === 0 ? lot.bidder === null : typeof lot.bidder === 'string' && !!lot.bidder) &&
|
||||
ids(lot.passed) && (!lot.bidder || !lot.passed.includes(lot.bidder)) &&
|
||||
amount(lot.allyPayment ?? 0) && (lot.allyPayment ?? 0) <= lot.bid) return null;
|
||||
return 'Distrans is unavailable during this unresolved auction state; finish the current bid or auction first.';
|
||||
}
|
||||
Reference in new issue
Block a user