Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions client/src/adapter/__tests__/draftPodAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const mockHostOnEvent = vi.fn((_handler: (event: Record<string, unknown>) => voi
const mockHostInitialize = vi.fn(async () => {});
const mockHostStartDraft = vi.fn(async () => {});
const mockHostSubmitHostPick = vi.fn(async () => mockView("Drafting"));
const mockHostSubmitHostPickWithDraftEffect = vi.fn(async () => mockView("Drafting"));
const mockHostSubmitHostDeck = vi.fn(async () => mockView("Deckbuilding"));
const mockHostGetHostView = vi.fn(async () => mockView("Lobby"));
const mockHostKickPlayer = vi.fn();
Expand All @@ -64,6 +65,7 @@ vi.mock("../p2p-draft-host", () => ({
initialize: mockHostInitialize,
startDraft: mockHostStartDraft,
submitHostPick: mockHostSubmitHostPick,
submitHostPickWithDraftEffect: mockHostSubmitHostPickWithDraftEffect,
submitHostDeck: mockHostSubmitHostDeck,
getHostView: mockHostGetHostView,
kickPlayer: mockHostKickPlayer,
Expand All @@ -83,6 +85,7 @@ vi.mock("../p2p-draft-host", () => ({
const mockGuestOnEvent = vi.fn((_handler: (event: Record<string, unknown>) => void) => vi.fn());
const mockGuestInitialize = vi.fn(async () => {});
const mockGuestSubmitPick = vi.fn(async () => {});
const mockGuestSubmitPickWithDraftEffect = vi.fn(async () => {});
const mockGuestSubmitDeck = vi.fn(async () => {});
const mockGuestLeave = vi.fn(async () => {});

Expand All @@ -92,6 +95,7 @@ vi.mock("../p2p-draft-guest", () => ({
onEvent: mockGuestOnEvent,
initialize: mockGuestInitialize,
submitPick: mockGuestSubmitPick,
submitPickWithDraftEffect: mockGuestSubmitPickWithDraftEffect,
submitDeck: mockGuestSubmitDeck,
leave: mockGuestLeave,
view: null,
Expand All @@ -112,6 +116,7 @@ function mockView(status: string): DraftPlayerView {
pass_direction: "Left",
current_pack: null,
pool: [],
draft_effects: [],
pool_groups: {
color_groups: [],
type_groups: [],
Expand Down Expand Up @@ -294,6 +299,21 @@ describe("DraftPodHostAdapter", () => {
expect(view.status).toBe("Drafting");
});

it("delegates draft-effect picks and returns view", async () => {
await adapter.initialize({
poolInput: { type: "Set", data: { set_pool_json: "{}" } },
kind: "Premier",
podSize: 8,
hostDisplayName: "Host",
tournamentFormat: "Swiss",
podPolicy: "Competitive",
});

const view = await adapter.submitPickWithDraftEffect("cogwork-1", ["card-1", "card-2"]);
expect(mockHostSubmitHostPickWithDraftEffect).toHaveBeenCalledWith("cogwork-1", ["card-1", "card-2"]);
expect(view.status).toBe("Drafting");
});

it("delegates submitDeck and returns view", async () => {
await adapter.initialize({
poolInput: { type: "Set", data: { set_pool_json: "{}" } },
Expand Down Expand Up @@ -500,6 +520,13 @@ describe("DraftPodGuestAdapter", () => {
expect(mockGuestSubmitPick).toHaveBeenCalledWith("card-456");
});

it("delegates draft-effect picks to P2PDraftGuest", async () => {
await adapter.initialize({ roomCode: "ABCDE", displayName: "Alice" });

await adapter.submitPickWithDraftEffect("cogwork-1", ["card-1", "card-2"]);
expect(mockGuestSubmitPickWithDraftEffect).toHaveBeenCalledWith("cogwork-1", ["card-1", "card-2"]);
});

it("delegates submitDeck to P2PDraftGuest", async () => {
await adapter.initialize({ roomCode: "ABCDE", displayName: "Alice" });

Expand Down
94 changes: 94 additions & 0 deletions client/src/adapter/__tests__/p2pDraftEffectPick.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { describe, expect, it, vi } from "vitest";

import { P2PDraftGuest } from "../p2p-draft-guest";
import { P2PDraftHost } from "../p2p-draft-host";

describe("P2P draft-effect picks", () => {
it("serializes guest draft-effect picks without a client-supplied seat", async () => {
const guest = new P2PDraftGuest(
{} as never,
"host-peer",
{} as never,
"Alice",
);
const send = vi.fn(async () => {});
(guest as unknown as { session: { send: typeof send } }).session = { send };

await guest.submitPickWithDraftEffect("cogwork-1", ["card-1", "card-2"]);

expect(send).toHaveBeenCalledWith({
type: "draft_pick_with_draft_effect",
effectCardInstanceId: "cogwork-1",
cardInstanceIds: ["card-1", "card-2"],
});
});

it("binds a guest draft-effect pick to the host-assigned seat", async () => {
const host = new P2PDraftHost(
{ id: "host" } as never,
() => () => {},
{ type: "Set", data: { set_pool_json: "{}" } } as never,
"Premier",
8,
"Host",
"Swiss",
"Competitive",
);
const privateHost = host as unknown as {
draftStarted: boolean;
paused: boolean;
handleGuestMessage: (seat: number, message: unknown) => Promise<void>;
handlePickWithDraftEffect: ReturnType<typeof vi.fn>;
};
privateHost.draftStarted = true;
privateHost.paused = false;
privateHost.handlePickWithDraftEffect = vi.fn(async () => {});

await privateHost.handleGuestMessage(3, {
type: "draft_pick_with_draft_effect",
effectCardInstanceId: "cogwork-1",
cardInstanceIds: ["card-1", "card-2"],
});

expect(privateHost.handlePickWithDraftEffect).toHaveBeenCalledWith(
3,
"cogwork-1",
["card-1", "card-2"],
);
});

it("rejects host normal and draft-effect picks while paused", async () => {
const host = new P2PDraftHost(
{ id: "host" } as never,
() => () => {},
{ type: "Set", data: { set_pool_json: "{}" } } as never,
"Premier",
8,
"Host",
"Swiss",
"Competitive",
);
const privateHost = host as unknown as {
draftStarted: boolean;
paused: boolean;
adapter: {
submitPickForSeat: ReturnType<typeof vi.fn>;
submitPickWithDraftEffectForSeat: ReturnType<typeof vi.fn>;
};
};
privateHost.draftStarted = true;
privateHost.paused = true;
privateHost.adapter = {
submitPickForSeat: vi.fn(),
submitPickWithDraftEffectForSeat: vi.fn(),
};

await expect(host.submitHostPick("card-1")).rejects.toThrow("Draft is paused");
await expect(
host.submitHostPickWithDraftEffect("cogwork-1", ["card-1", "card-2"]),
).rejects.toThrow("Draft is paused");

expect(privateHost.adapter.submitPickForSeat).not.toHaveBeenCalled();
expect(privateHost.adapter.submitPickWithDraftEffectForSeat).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function createMockDraftView(overrides: Partial<DraftPlayerView> = {}): DraftPla
pass_direction: "Left",
current_pack: null,
pool: [],
draft_effects: [],
pool_groups: EMPTY_DRAFT_POOL_GROUPS,
seats: [],
cards_per_pack: 14,
Expand Down
27 changes: 27 additions & 0 deletions client/src/adapter/draft-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface DraftCardInstance {
colors: string[];
cmc: number;
type_line: string;
draft_effect?: "additional_pick";
}

export type DraftPoolGroupKind =
Expand Down Expand Up @@ -80,6 +81,7 @@ export interface SeatPublicView {
connected: boolean;
has_submitted_deck: boolean;
pick_status: "Pending" | "Picked" | "TimedOut" | "NotDrafting";
face_up_draft_cards: DraftCardInstance[];
}

export type DraftStatus =
Expand Down Expand Up @@ -169,6 +171,7 @@ export interface DraftPlayerView {
pass_direction: "Left" | "Right";
current_pack: DraftCardInstance[] | null;
pool: DraftCardInstance[];
draft_effects: DraftCardInstance[];
/** Engine-owned grouping, ordering, and duplicate counts for the pool. */
pool_groups: DraftPoolGroups;
/** Engine-provided sealed packs in opening order. Absent for draft events. */
Expand Down Expand Up @@ -291,6 +294,17 @@ export class DraftAdapter {
return wasm.submit_pick(cardInstanceId) as DraftPlayerView;
}

async submitPickWithDraftEffect(
effectCardInstanceId: string,
cardInstanceIds: string[],
): Promise<DraftPlayerView> {
const wasm = await ensureDraftWasm();
return wasm.submit_pick_with_draft_effect(
effectCardInstanceId,
JSON.stringify(cardInstanceIds),
) as DraftPlayerView;
}

/** Let the bot AI pick the best card from the current pack for the player. */
async autoPick(): Promise<DraftPlayerView> {
const wasm = await ensureDraftWasm();
Expand Down Expand Up @@ -360,6 +374,19 @@ export class DraftAdapter {
return wasm.submit_pick_for_seat(seat, cardInstanceId) as DraftPlayerView;
}

async submitPickWithDraftEffectForSeat(
seat: number,
effectCardInstanceId: string,
cardInstanceIds: string[],
): Promise<DraftPlayerView> {
const wasm = await ensureDraftWasm();
return wasm.submit_pick_with_draft_effect_for_seat(
seat,
effectCardInstanceId,
JSON.stringify(cardInstanceIds),
) as DraftPlayerView;
}

async submitDeckForSeat(seat: number, mainDeck: string[]): Promise<DraftPlayerView> {
const wasm = await ensureDraftWasm();
return wasm.submit_deck_for_seat(seat, JSON.stringify(mainDeck)) as DraftPlayerView;
Expand Down
8 changes: 8 additions & 0 deletions client/src/adapter/draftPodGuestAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,14 @@ export class DraftPodGuestAdapter {
await this.guest.submitPick(cardInstanceId);
}

async submitPickWithDraftEffect(
effectCardInstanceId: string,
cardInstanceIds: string[],
): Promise<void> {
if (!this.guest) throw new Error("Guest not initialized");
await this.guest.submitPickWithDraftEffect(effectCardInstanceId, cardInstanceIds);
}

async submitDeck(mainDeck: string[]): Promise<void> {
if (!this.guest) throw new Error("Guest not initialized");
await this.guest.submitDeck(mainDeck);
Expand Down
8 changes: 8 additions & 0 deletions client/src/adapter/draftPodHostAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ export class DraftPodHostAdapter {
return this.host.submitHostPick(cardInstanceId);
}

async submitPickWithDraftEffect(
effectCardInstanceId: string,
cardInstanceIds: string[],
): Promise<DraftPlayerView> {
if (!this.host) throw new Error("Host not initialized");
return this.host.submitHostPickWithDraftEffect(effectCardInstanceId, cardInstanceIds);
}

async submitDeck(mainDeck: string[]): Promise<DraftPlayerView> {
if (!this.host) throw new Error("Host not initialized");
return this.host.submitHostDeck(mainDeck);
Expand Down
12 changes: 12 additions & 0 deletions client/src/adapter/p2p-draft-guest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ export class P2PDraftGuest {
await this.session.send({ type: "draft_pick", cardInstanceId });
}

async submitPickWithDraftEffect(
effectCardInstanceId: string,
cardInstanceIds: string[],
): Promise<void> {
if (!this.session) throw new Error("Not connected to draft host");
await this.session.send({
type: "draft_pick_with_draft_effect",
effectCardInstanceId,
cardInstanceIds,
});
}

async submitDeck(mainDeck: string[]): Promise<void> {
if (!this.session) throw new Error("Not connected to draft host");
await this.session.send({ type: "draft_submit_deck", mainDeck });
Expand Down
Loading
Loading