import test from 'node:test'; import assert from 'node:assert/strict'; import { applyAction, viewGame } from '../game/engine'; import { botActions } from '../game/bots'; import { quoteNexusAlliance, type NexusAllianceSeat, } from '../game/nexus-alliance'; import { nexusInventory, nexusPlayer, nexusReady, nexusTurnTwo, orderNexusSpice, } from './fixture-nexus-cards'; const players = (): NexusAllianceSeat[] => [ { id: 'a', ally: null }, { id: 'b', ally: null }, { id: 'c', ally: null }, { id: 'd', ally: null }, ]; void test('a unilateral offer preserves every alliance and unrelated offer without mutating input', () => { const seats = players(); const offers = { c: 'd' }; const before = structuredClone({ seats, offers }); Object.freeze(seats); Object.freeze(offers); for (const seat of seats) Object.freeze(seat); const quote = quoteNexusAlliance({ players: seats, offers, actor: 'a', target: 'b', }); assert.deepEqual(quote, { allies: { a: null, b: null, c: null, d: null }, offers: { c: 'd', a: 'b' }, formed: null, }); assert.deepEqual({ seats, offers }, before); }); void test('mutual offers form a reciprocal pair and remove only that pair own offers', () => { const quote = quoteNexusAlliance({ players: players(), offers: { b: 'a', c: 'a', d: 'c' }, actor: 'a', target: 'b', }); assert.deepEqual(quote, { allies: { a: 'b', b: 'a', c: null, d: null }, offers: { c: 'a', d: 'c' }, formed: ['a', 'b'], }); }); void test('withdrawing breaks the reciprocal pair and removes only the actor offer', () => { const quote = quoteNexusAlliance({ players: [ { id: 'a', ally: 'b' }, { id: 'b', ally: 'a' }, { id: 'c', ally: null }, ], offers: { a: 'c', b: 'c', c: 'a' }, actor: 'a', target: null, }); assert.deepEqual(quote, { allies: { a: null, b: null, c: null }, offers: { b: 'c', c: 'a' }, formed: null, }); }); void test('invalid actors, targets and occupied alliances reject without mutation', () => { const seats = players(); const offers = { c: 'd' }; const before = structuredClone({ seats, offers }); for (const [actor, target, pattern] of [ ['missing', 'a', /seated player/], ['a', 'a', /another player/], ['a', 'missing', /another seated player/], ] as const) assert.throws( () => quoteNexusAlliance({ players: seats, offers, actor, target }), pattern, ); seats[0].ally = 'c'; seats[2].ally = 'a'; assert.throws( () => quoteNexusAlliance({ players: seats, offers, actor: 'a', target: 'b', }), /Break existing alliances/, ); assert.deepEqual( { seats, offers }, { seats: [ { id: 'a', ally: 'c' }, before.seats[1], { id: 'c', ally: 'a' }, before.seats[3], ], offers: before.offers, }, ); }); function negotiations() { let game = nexusTurnTwo(); orderNexusSpice(game, ['worm', 'land', 'land']); game = nexusReady(game); assert.ok(game.spiceWindow); game = nexusReady(game); assert.equal(game.nexus, true); assert.equal(game.spiceWindow, null); return game; } void test('AI initiates a reciprocal Nexus alliance without retrying its outstanding offer', () => { let game = negotiations(); nexusPlayer(game, 'f').bot = 'Easy'; nexusPlayer(game, 'a').bot = 'Medium'; nexusPlayer(game, 'h').bot = 'Hard'; const proposal = botActions(viewGame(game, 'a'))[0]; assert.deepEqual(proposal, { type: 'alliance', target: 'f' }); game = applyAction(game, 'a', proposal); assert.deepEqual(game.allianceOffers, { a: 'f' }); assert.deepEqual(botActions(viewGame(game, 'a'))[0], { type: 'ready' }); const acceptance = botActions(viewGame(game, 'f'))[0]; assert.deepEqual(acceptance, { type: 'alliance', target: 'a' }); game = applyAction(game, 'f', acceptance); assert.equal(nexusPlayer(game, 'a').ally, 'f'); assert.equal(nexusPlayer(game, 'f').ally, 'a'); assert.deepEqual(game.allianceOffers, {}); game = nexusReady(game); assert.equal(game.nexusCards?.phase?.stage, 'drawing'); const draw = botActions(viewGame(game, 'h'))[0]; assert.equal(draw.type, 'nexusCardChoice'); game = applyAction(game, 'h', draw); assert.equal(game.nexusCards?.phase?.stage, 'complete'); nexusInventory(game); }); void test('the actual engine preserves ordinary offer, formation and withdrawal side effects', () => { let game = negotiations(); game.ready = ['h']; game = applyAction(game, 'f', { type: 'alliance', target: 'a' }); assert.deepEqual(game.allianceOffers, { f: 'a' }); assert.equal(nexusPlayer(game, 'f').ally, null); assert.deepEqual(game.ready, []); game.ready = ['h']; game = applyAction(game, 'a', { type: 'alliance', target: 'f' }); assert.equal(nexusPlayer(game, 'f').ally, 'a'); assert.equal(nexusPlayer(game, 'a').ally, 'f'); assert.equal(nexusPlayer(game, 'f').allySinceTurn, game.turn); assert.equal(nexusPlayer(game, 'a').allySinceTurn, game.turn); assert.deepEqual(game.allianceOffers, {}); assert.deepEqual(game.ready, []); game.ready = ['h']; game = applyAction(game, 'f', { type: 'alliance', target: null }); assert.equal(nexusPlayer(game, 'f').ally, null); assert.equal(nexusPlayer(game, 'a').ally, null); assert.deepEqual(game.ready, []); });