diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index dced1a9..05463e2 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -3,6 +3,7 @@ import Board from "./Board.svelte"; import Card from "./Card.svelte"; import Help from "./Help.svelte"; + import { local } from "./local.svelte"; import { allCardDefs, cardDef, isNumberCard, SIDES, stepTarget, cellKey } from "@wizwar/engine"; import type { CardInstance, Side } from "@wizwar/engine"; @@ -13,6 +14,7 @@ let joinCode = $state(""); let claimPhrase = $state(""); let showHelp = $state(false); + let hotseatNames = $state(""); let drawCount = $state(2); let withExpansion = $state(true); /** Your creature selected for movement/attacks. */ @@ -45,7 +47,7 @@ /** Voluntary discard mode: hand clicks mark cards instead of playing them. */ let discardMode = $state(false); - const view = $derived(net.view); + const view = $derived(local.active ? local.view : net.view); const isYourTurn = $derived(view !== null && view.activePlayerId === view.you && !view.stack); const youMustRespond = $derived(view?.stack != null && view.stack.waitingOn === view.you); const youMustDiscard = $derived(view != null && view.pendingDiscard === view.you); @@ -75,6 +77,11 @@ ); const numberTotal = $derived(attachedNumber ? cardDef(attachedNumber.cardId).value! : 1); + function dispatch(command: Parameters[0]) { + if (local.active) local.command(command); + else net.command(command); + } + function clearSelection() { trapCells = []; tradeFrom = null; @@ -112,7 +119,7 @@ function selectCard(card: CardInstance) { if (!view) return; if (youMustRespond) { - net.command({ type: "counteract", instanceId: card.instanceId }); + dispatch({ type: "counteract", instanceId: card.instanceId }); return; } if (youMustDiscard || discardMode || discardSelection.size > 0) { @@ -163,7 +170,7 @@ const add = view.yourHand.find((c) => c.cardId === "add"); if (add) cmd.addInstanceId = add.instanceId; } - net.command(cmd); + dispatch(cmd); clearSelection(); } @@ -180,13 +187,13 @@ if (!selectedCard) return; const cmd: Parameters[0] = { type: "cast", instanceId: selectedCard.instanceId }; if (attachedNumber) cmd.numberInstanceIds = [attachedNumber.instanceId]; - net.command(cmd); + dispatch(cmd); clearSelection(); } function castPowerRun() { if (!selectedCard) return; - net.command({ type: "cast", instanceId: selectedCard.instanceId, params: { points: runPoints } }); + dispatch({ type: "cast", instanceId: selectedCard.instanceId, params: { points: runPoints } }); clearSelection(); } @@ -194,7 +201,7 @@ if (!view || !isYourTurn) return; if (pendingCellFor && selectedCard) { // Stage 2 of teleport-opponent: destination chosen. - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "player", playerId: pendingCellFor }, params: { cell }, @@ -207,7 +214,7 @@ pendingSectorFrom = cell; // first click: the sector to move return; } - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell }, params: { cell: pendingSectorFrom }, }); @@ -217,14 +224,14 @@ if (selectedCard?.cardId === "boobytrap") { trapCells = [...trapCells, cell]; if (trapCells.length === 4) { - net.command({ type: "cast", instanceId: selectedCard.instanceId, params: { cells: trapCells } }); + dispatch({ type: "cast", instanceId: selectedCard.instanceId, params: { cells: trapCells } }); clearSelection(); } return; } if (selectedCard && TWO_CELL_CARDS.has(selectedCard.cardId)) { if (!tradeFrom) { tradeFrom = cell; return; } - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell }, params: { cell: tradeFrom }, }); @@ -232,7 +239,7 @@ return; } if (selectedCard?.cardId === "rotate-sector") { - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell }, params: { clockwise: rotateCW }, }); @@ -240,7 +247,7 @@ return; } if (selectedCard && CELL_CARDS.has(selectedCard.cardId)) { - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "cell", cell }, }); @@ -254,7 +261,7 @@ const n = { x: creature.position.x + (side === "E" ? 1 : side === "W" ? -1 : 0), y: creature.position.y + (side === "S" ? 1 : side === "N" ? -1 : 0) }; if (cellKey(n) === cellKey(cell)) { - net.command({ type: "moveCreature", creatureId: selectedCreature, direction: side }); + dispatch({ type: "moveCreature", creatureId: selectedCreature, direction: side }); return; } } @@ -268,7 +275,7 @@ for (const side of SIDES) { const t = stepTarget(view.board, me.position, side); if (t.kind !== "blocked" && cellKey(t.to) === cellKey(cell)) { - net.command({ type: "move", direction: side }); + dispatch({ type: "move", direction: side }); return; } } @@ -278,7 +285,7 @@ const n = { x: me.position.x + (side === "E" ? 1 : side === "W" ? -1 : 0), y: me.position.y + (side === "S" ? 1 : side === "N" ? -1 : 0) }; if (cellKey(n) === cellKey(cell)) { - net.command({ type: "move", direction: side }); + dispatch({ type: "move", direction: side }); return; } } @@ -289,7 +296,7 @@ // Standing on the opening: step through. Otherwise treat it as a cell // click on the opening square (e.g. to walk toward it). if (me.position.x === cell.x && me.position.y === cell.y) { - net.command({ type: "move", direction: side }); + dispatch({ type: "move", direction: side }); } else { clickCell(cell); } @@ -297,7 +304,7 @@ function clickEdge(cell: { x: number; y: number }, side: Side) { if (!selectedCard || !edgeSelectMode) return; - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "edge", cell, side }, @@ -310,7 +317,7 @@ const creature = view.creatures.find((c) => c.id === creatureId); if (!creature) return; if (selectedCard && (CREATURE_TARGET_CARDS.has(selectedCard.cardId) || cardDef(selectedCard.cardId).cardType === "attack")) { - net.command({ + dispatch({ type: "cast", instanceId: selectedCard.instanceId, target: { kind: "creature", creatureId }, ...(attachedNumber ? { numberInstanceIds: [attachedNumber.instanceId] } : {}), @@ -320,7 +327,7 @@ } if (selectedCreature && selectedCreature !== creatureId) { // Your selected creature attacks another creature in its square. - net.command({ type: "creatureAttack", creatureId: selectedCreature, targetId: creatureId }); + dispatch({ type: "creatureAttack", creatureId: selectedCreature, targetId: creatureId }); selectedCreature = null; return; } @@ -333,7 +340,7 @@ function clickPlayer(playerId: string) { if (!view || !isYourTurn) return; if (selectedCreature) { - net.command({ type: "creatureAttack", creatureId: selectedCreature, targetId: playerId }); + dispatch({ type: "creatureAttack", creatureId: selectedCreature, targetId: playerId }); selectedCreature = null; return; } @@ -342,7 +349,7 @@ const me = view.players.find((p) => p.id === view.you)!; const them = view.players.find((p) => p.id === playerId)!; if (playerId !== view.you && cellKey(me.position) === cellKey(them.position)) { - net.command({ type: "punch", targetId: playerId }); + dispatch({ type: "punch", targetId: playerId }); } return; } @@ -365,24 +372,24 @@ if (!id) return; // needs a card name typed first cmd.params = { cardId: id }; } - net.command(cmd); + dispatch(cmd); clearSelection(); } function doDiscard() { - net.command({ type: "discard", instanceIds: [...discardSelection] }); + dispatch({ type: "discard", instanceIds: [...discardSelection] }); discardSelection = new Set(); discardMode = false; } function endTurn() { clearSelection(); - net.command({ type: "endTurn", draw: drawCount }); + dispatch({ type: "endTurn", draw: drawCount }); } - function pickUp() { net.command({ type: "pickUpTreasure" }); } - function drop() { net.command({ type: "dropTreasure" }); } - function pass() { net.command({ type: "pass" }); } + function pickUp() { dispatch({ type: "pickUpTreasure" }); } + function drop() { dispatch({ type: "dropTreasure" }); } + function pass() { dispatch({ type: "pass" }); } const me = $derived(view?.players.find((p) => p.id === view?.you) ?? null); const carryingTreasure = $derived(me?.carriedTreasureId != null); @@ -401,6 +408,7 @@ let chronicleEl = $state(null); $effect(() => { void net.log.length; + void local.log.length; if (chronicleEl) chronicleEl.scrollTop = chronicleEl.scrollHeight; }); @@ -450,7 +458,11 @@
Wiz-War sixth edition - {#if net.roomId} + {#if local.active} + hotseat + + + {:else if net.roomId} room {net.roomId} @@ -479,7 +491,18 @@ {/if} - {#if !net.roomId} + {#if local.handoffTo} +
local.takeSeat()} onkeydown={(e) => e.key === "Enter" && local.takeSeat()}> +
+
pass the device to
+
{local.handoffTo}
+
tap when only they can see the screen
+
+
+ {/if} + + {#if !net.roomId && !local.active}
Wiz-War
@@ -499,6 +522,24 @@
+
+ + + {#if local.hasSave()} + + {/if} +
+ +
@@ -545,7 +586,7 @@ {/if}
- {:else if !net.started} + {:else if !local.active && !net.started}
Room {net.roomId}
@@ -630,7 +671,7 @@
- {#each net.log.slice(-60) as line, i (i)} + {#each (local.active ? local.log : net.log).slice(-60) as line, i (i)}
{line}
{/each}
@@ -679,7 +720,7 @@ @@ -712,7 +753,7 @@
{#if isYourTurn && onWarpToken} - + {/if} {#if isYourTurn}