Recognize group allies in bribes and deals
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
2fe97ddd1f
commit
6e26ceb216
12 files changed
+318
-29
No files matched your search
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { Action, GameView } from '@/game/engine';
|
||||
import { areCoalitionAllies } from '@/game/coalition-alliance';
|
||||
import { bribeAction } from '@/game/bribe-options';
|
||||
import { bribeClaimText, bribePromiseModeSupported, liveBribePromises } from '@/game/bribe-promises';
|
||||
import { liveBribeExpressionPromises } from '@/game/bribe-expression-promises';
|
||||
@@ -27,6 +28,7 @@ export function BribePromiseGuideButton(props: BribePromiseProps) {
|
||||
const target = typeof action.target === 'string' ? current.players.find(player => player.id === action.target) : undefined;
|
||||
const limit = options.targets.find(recipient => recipient.id === action.target);
|
||||
const payment = action.type === 'bribe' && !!me && !!target && target.id !== actor && target.id !== me.ally &&
|
||||
(!current.coalitionState || !areCoalitionAllies(current.players, current.coalitionState, actor, target.id)) &&
|
||||
options.blocked === null && Number.isSafeInteger(options.available) &&
|
||||
typeof action.amount === 'number' && action.amount <= options.available &&
|
||||
!!limit && Number.isSafeInteger(limit.maximum) && action.amount <= limit.maximum &&
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { GameView } from '@/game/engine';
|
||||
import { areCoalitionAllies } from '@/game/coalition-alliance';
|
||||
import type { MixedActionExpression } from '@/game/mixed-action-question';
|
||||
import { BRIBE_EXPRESSION_MAX_DEPTH, BRIBE_EXPRESSION_MAX_LEAVES } from '@/game/bribe-expression-promises';
|
||||
import { parseBribePaymentClaim, type BribePaymentClaim } from '@/game/mixed-bribe-question';
|
||||
@@ -9,7 +10,8 @@ import { Input } from './ui/input';
|
||||
|
||||
const recipients = (game: GameView, target: string) => {
|
||||
const player = game.players.find(player => player.id === target);
|
||||
return player ? game.players.filter(recipient => recipient.id !== target && recipient.id !== player.ally) : [];
|
||||
return player ? game.players.filter(recipient => recipient.id !== target && recipient.id !== player.ally &&
|
||||
(!game.coalitionState || !areCoalitionAllies(game.players, game.coalitionState, target, recipient.id))) : [];
|
||||
};
|
||||
const config: MixedActionConfig<BribePaymentClaim> = {
|
||||
prefix: 'mixed-bribe',
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { faction } from '@/game/catalog';
|
||||
import { publicDealAcceptVotes, publicDealParticipants } from '@/game/public-deals';
|
||||
import { areCoalitionAllies } from '@/game/coalition-alliance';
|
||||
import type { Action, GameView } from '@/game/engine';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -15,6 +16,13 @@ type DeferredPaymentProjection = GameView & {
|
||||
deferredDealPaymentOptions?: Record<string, { blocked: string | null }>;
|
||||
};
|
||||
|
||||
/** Alliance membership is public. Scalar links retain legacy pair behavior;
|
||||
* a staged group uses the same coalition quote as authoritative rules. */
|
||||
function allied(game: GameView, a: GameView['players'][number], b: GameView['players'][number]): boolean {
|
||||
return a.ally === b.id || b.ally === a.id ||
|
||||
!!game.coalitionState && areCoalitionAllies(game.players, game.coalitionState, a.id, b.id);
|
||||
}
|
||||
|
||||
/** Requests are local to the active seat; an owed payment is as important to
|
||||
* surface as a pending invitation or a request to release an agreement. */
|
||||
export function publicDealRequests(game: GameView): string[][] {
|
||||
@@ -71,7 +79,7 @@ export function PublicDealList({ game, act, disabled }: {
|
||||
const recipient = party && game.me !== deal.from;
|
||||
const seats = participants.map(id => game.players.find(player => player.id === id));
|
||||
const eligible = seats.every((player, index) => player && seats.slice(index + 1).every(other =>
|
||||
other && player.ally !== other.id && other.ally !== player.id));
|
||||
other && !allied(game, player, other)));
|
||||
const payment = (deal as unknown as { payment?: DealPayment }).payment;
|
||||
const paymentStatus = (deal as unknown as { paymentStatus?: DeferredPaymentStatus }).paymentStatus;
|
||||
const finalAcceptance = recipient && !accepted.includes(game.me) && accepted.length === participants.length - 1;
|
||||
@@ -141,11 +149,9 @@ export function PublicDeals({ game, act, busy }: {
|
||||
const me = game.players.find(player => player.id === game.me);
|
||||
const targets = game.players.filter(player => me && player.id !== me.id && !player.bot);
|
||||
const selectedPlayers = targets.filter(player => selected.includes(player.id));
|
||||
const allied = (a: typeof game.players[number], b: typeof game.players[number]) =>
|
||||
a.ally === b.id || b.ally === a.id;
|
||||
const legalSelection = !!me && selected.length > 0 && selected.length === selectedPlayers.length &&
|
||||
selectedPlayers.every((player, index) => !allied(me, player) &&
|
||||
selectedPlayers.slice(index + 1).every(other => !allied(player, other)));
|
||||
selectedPlayers.every((player, index) => !allied(game, me, player) &&
|
||||
selectedPlayers.slice(index + 1).every(other => !allied(game, player, other)));
|
||||
const parties = me ? [me, ...selectedPlayers] : [];
|
||||
const amount = Number(paymentAmount);
|
||||
const dueTurn = Number(paymentDueTurn);
|
||||
@@ -174,7 +180,7 @@ export function PublicDeals({ game, act, busy }: {
|
||||
<legend>Deal recipients</legend>
|
||||
{targets.map(player => {
|
||||
const checked = selected.includes(player.id);
|
||||
const conflicts = !!me && (allied(me, player) || selectedPlayers.some(other => other.id !== player.id && allied(other, player)));
|
||||
const conflicts = !!me && (allied(game, me, player) || selectedPlayers.some(other => other.id !== player.id && allied(game, other, player)));
|
||||
return <label key={player.id} className="checkbox-row">
|
||||
<input type="checkbox" checked={checked} disabled={disabled || (!checked && conflicts)}
|
||||
onChange={event => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client';
|
||||
import { areCoalitionAllies } from '@/game/coalition-alliance';
|
||||
import { SaphoTruthOrder } from './sapho-truth-order';
|
||||
import { TruthFutureBattleLeader } from './truth-future-battle-leader';
|
||||
import { RicheseBidPromiseFields, richeseBidMinimumError } from './richese-bid-promise-fields';
|
||||
@@ -156,7 +157,8 @@ export function Truthtrance({
|
||||
const [bribeSelection, setBribeSelection] = useState<Pick<BribeClaim, 'recipient' | 'minimum'>>({ recipient: g.me, minimum: 3 });
|
||||
const bribeClaim: BribeClaim = { turn: g.turn, ...bribeSelection };
|
||||
const bribeRespondent = g.players.find(player => player.id === target);
|
||||
const bribeRecipients = g.players.filter(player => player.id !== target && player.id !== bribeRespondent?.ally);
|
||||
const bribeRecipients = g.players.filter(player => player.id !== target && player.id !== bribeRespondent?.ally &&
|
||||
(!g.coalitionState || !bribeRespondent || !areCoalitionAllies(g.players, g.coalitionState, target, player.id)));
|
||||
const [bribeExpression, setBribeExpression] = useState<BribeExpression>({ kind: 'and', terms: [
|
||||
{ kind: 'payment', recipient: g.me, minimum: 3 }, { kind: 'payment', recipient: '', minimum: 2 },
|
||||
] });
|
||||
|
||||
Reference in new issue
Block a user