Skip to content

Commit 8f83ace

Browse files
WIP
1 parent 1fcb519 commit 8f83ace

10 files changed

Lines changed: 402 additions & 354 deletions

File tree

islanders-client/src/lib/components/Map.svelte

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,23 @@
165165
if (!currentPlayer) return;
166166
const inWorld = toWorld(event.global);
167167
const hexToFind = grid.pointToHex(inWorld);
168-
const moveThiefAction = new MoveThiefAction(currentPlayer.name, hexToFind);
168+
const hexCoord = { x: hexToFind.col, y: hexToFind.row };
169+
const moveThiefAction = new MoveThiefAction(currentPlayer.name, hexCoord);
169170
dispatchActionClearCursor(moveThiefAction);
170171
ui.setMovingThief(false);
171-
ui.setStealingFromPlayers(true);
172+
// Don't set isStealingFromPlayers here - let the world update handler do it
172173
};
173174
174175
const handleIsPlayingKnightClick = (event: FederatedPointerEvent) => {
175176
if (!worldContainer || !grid) return;
176177
if (!currentPlayer) return;
177178
const inWorld = toWorld(event.global);
178179
const hexToFind = grid.pointToHex(inWorld);
179-
const moveThiefAction = new MoveThiefDevCardAction(currentPlayer.name, hexToFind);
180+
const hexCoord = { x: hexToFind.col, y: hexToFind.row };
181+
const moveThiefAction = new MoveThiefDevCardAction(currentPlayer.name, hexCoord);
180182
dispatchActionClearCursor(moveThiefAction);
181183
ui.setPlayingKnight(false);
182-
ui.setStealingFromPlayers(true);
184+
// Don't set isStealingFromPlayers here - let the world update handler do it
183185
};
184186
185187
const handleBuildClick = (event: FederatedPointerEvent) => {
@@ -191,7 +193,8 @@
191193
return;
192194
}
193195
const hexToFind = grid.pointToHex(inWorld);
194-
const coord = getMatrixCoordCorner(hexToFind, closestPoints[0].index);
196+
const hexCoord = { x: hexToFind.col, y: hexToFind.row };
197+
const coord = getMatrixCoordCorner(hexCoord, closestPoints[0].index);
195198
196199
if (isBuilding === 'House') {
197200
const action =
@@ -207,7 +210,7 @@
207210
ui.setBuilding('None');
208211
}
209212
if (isBuilding === 'Road' && closestPoints[1].index !== -1) {
210-
const coord2 = getMatrixCoordCorner(hexToFind, closestPoints[1].index);
213+
const coord2 = getMatrixCoordCorner(hexCoord, closestPoints[1].index);
211214
const action =
212215
game.world.gameState === 'Started'
213216
? new BuildRoadAction(currentPlayer.name, coord, coord2)
@@ -368,8 +371,13 @@
368371
369372
tileContainer?.addChild(tileSprite);
370373
if (newWorld.gameState === 'Started') {
371-
// Pass hex center directly instead of recalculating
372-
const tileNumber = generateTileNumber(tileWidth, { x: hex.x, y: hex.y }, point, tile);
374+
// Pass hex center and zero origin since hex.x/hex.y are already world coordinates
375+
const tileNumber = generateTileNumber(
376+
tileWidth,
377+
{ x: hex.x, y: hex.y },
378+
{ x: 0, y: 0 },
379+
tile
380+
);
373381
if (tileNumber) {
374382
tileContainer?.addChild(tileNumber);
375383
}

islanders-client/src/lib/components/mapUtils.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
22
type World,
33
getMatrixCoordCorner,
4-
type MatrixCoordinate
4+
type MatrixCoordinate,
5+
type Player
56
} from '../../../../islanders-shared/lib/Shared';
7+
import { neighbouringHexCoords } from '../../../../islanders-shared/lib/MatrixCoordinate';
68

79
export type HexGrid = {
810
pointToHex: (point: { x: number; y: number }) => {
@@ -143,3 +145,64 @@ export const matrixCoordToGridWorldCoord = (
143145
const worldY = (matrixCoord.y * originHex.height) / 2;
144146
return { x: worldX, y: worldY };
145147
};
148+
149+
/**
150+
* Gets the list of players who can be stolen from based on the thief's position.
151+
* A player can be stolen from if they have a settlement or city adjacent to the hex where the thief is located.
152+
*
153+
* @param world - The current game world state
154+
* @param currentPlayerName - The name of the current player (who cannot be stolen from themselves)
155+
* @returns Array of players who can be stolen from
156+
*/
157+
export const getStealablePlayers = (
158+
world: World,
159+
currentPlayerName: string | undefined
160+
): Player[] => {
161+
if (!world.thief || !world.players) return [];
162+
163+
const thiefHex = world.thief.hexCoordinate;
164+
console.log('Getting stealable players. Thief at:', thiefHex);
165+
const playersWithBuildings = new Set<string>();
166+
167+
world.players.forEach((player) => {
168+
// Check houses
169+
player.houses.forEach((house) => {
170+
const houseHexes = neighbouringHexCoords(house.position);
171+
console.log(
172+
`Checking house for ${player.name} at:`,
173+
house.position,
174+
'adjacent hexes:',
175+
houseHexes
176+
);
177+
if (houseHexes.some((hex) => hex.x === thiefHex.x && hex.y === thiefHex.y)) {
178+
console.log(`Player ${player.name} has house adjacent to thief`);
179+
playersWithBuildings.add(player.name);
180+
}
181+
});
182+
183+
// Check cities
184+
player.cities.forEach((city) => {
185+
const cityHexes = neighbouringHexCoords(city.position);
186+
console.log(
187+
`Checking city for ${player.name} at:`,
188+
city.position,
189+
'adjacent hexes:',
190+
cityHexes
191+
);
192+
if (cityHexes.some((hex) => hex.x === thiefHex.x && hex.y === thiefHex.y)) {
193+
console.log(`Player ${player.name} has city adjacent to thief`);
194+
playersWithBuildings.add(player.name);
195+
}
196+
});
197+
});
198+
199+
const result = Array.from(playersWithBuildings)
200+
.map((name) => world.players.find((p) => p.name === name))
201+
.filter((p): p is Player => p !== undefined);
202+
203+
console.log(
204+
'Stealable players:',
205+
result.map((p) => p.name)
206+
);
207+
return result;
208+
};

islanders-client/src/lib/stores/game.svelte.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,14 @@ class Game {
130130
if (world.conditions?.playedKnight && !world.conditions.playedKnight.movedThief) {
131131
ui.setPlayingKnight(true);
132132
}
133+
// Check if the thief was moved and stealing is required
134+
const knightNeedsStealing = world.conditions?.playedKnight?.movedThief && !world.conditions.playedKnight.stoleFromPlayer;
135+
const sevenNeedsStealing = world.conditions?.rolledASeven?.movedThief && !world.conditions.rolledASeven.stoleFromPlayer;
136+
if (knightNeedsStealing || sevenNeedsStealing) {
137+
ui.setStealingFromPlayers(true);
138+
} else {
139+
ui.setStealingFromPlayers(false);
140+
}
133141
if (world.conditions?.playedRoadBuilding) {
134142
const { roadsBuilt, expected } = world.conditions.playedRoadBuilding;
135143
if (expected && roadsBuilt && expected < roadsBuilt) {

islanders-client/src/routes/game/[gameId]/+layout.svelte

Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import { page } from '$app/state';
99
import ResourcesBar from '$lib/components/ResourcesBar.svelte';
1010
import type { Player } from '../../../../../islanders-shared/lib/Shared';
11-
import { notifications } from '$lib/stores/notifications.svelte';
11+
import { EndTurnAction, StealFromPlayerAction } from '../../../../../islanders-shared/lib/Action';
12+
import { ui } from '$lib/stores/ui.svelte';
13+
import { getStealablePlayers } from '$lib/components/mapUtils';
1214
1315
const props = $props();
1416
const { children, data } = props;
@@ -18,7 +20,7 @@
1820
1921
const tabs = [
2022
{ id: 'players' as const, href: `${base}`, label: 'Players' },
21-
{ id: 'build' as const, href: `${base}/build`, label: 'Build' },
23+
{ id: 'actions' as const, href: `${base}/actions`, label: 'Actions' },
2224
{ id: 'trade' as const, href: `${base}/trade`, label: 'Trade' },
2325
{ id: 'chat' as const, href: `${base}/chat`, label: 'Chat' },
2426
{ id: 'setup' as const, href: `${base}/setup`, label: 'Setup' }
@@ -103,6 +105,45 @@
103105
const playerResources = $derived(
104106
currentPlayer?.resources ?? { wood: 0, clay: 0, stone: 0, grain: 0, wool: 0 }
105107
);
108+
109+
let stealModalElement: HTMLDialogElement;
110+
const isStealingFromPlayers = $derived(ui.isStealingFromPlayers);
111+
112+
const stealablePlayers = $derived.by(() => {
113+
if (!game.world) return [];
114+
return getStealablePlayers(game.world, game.playerName);
115+
});
116+
117+
$effect(() => {
118+
console.log('Steal effect running:', {
119+
isStealingFromPlayers,
120+
stealableCount: stealablePlayers.length,
121+
modalElement: !!stealModalElement
122+
});
123+
124+
if (isStealingFromPlayers && stealablePlayers.length > 0) {
125+
console.log('Opening steal modal with players:', stealablePlayers.map(p => p.name));
126+
stealModalElement?.showModal();
127+
} else if (isStealingFromPlayers && stealablePlayers.length === 0) {
128+
console.log('No stealable players, closing steal UI');
129+
ui.setStealingFromPlayers(false);
130+
}
131+
});
132+
133+
const handleStealFrom = async (playerToStealFrom: string) => {
134+
if (!game.playerName) return;
135+
136+
const action = new StealFromPlayerAction(game.playerName, playerToStealFrom);
137+
await game.sendAction(action);
138+
139+
stealModalElement?.close();
140+
ui.setStealingFromPlayers(false);
141+
};
142+
143+
const handleSkipStealing = () => {
144+
stealModalElement?.close();
145+
ui.setStealingFromPlayers(false);
146+
};
106147
</script>
107148

108149
<div class="flex h-screen overflow-hidden">
@@ -111,12 +152,23 @@
111152
<Map />
112153
{#if game.isGameStarted && currentPlayer && game.world?.players[game.world?.currentPlayer].name === game.playerName}
113154
<div
114-
class="absolute right-4 bottom-4 z-10 flex h-14 gap-1 rounded-md bg-base-200/40 p-1 backdrop-blur-md"
155+
class="absolute right-4 bottom-20 z-10 flex h-28 w-28 flex-col items-center justify-center rounded-md bg-base-200/40 p-3 backdrop-blur-md"
156+
>
157+
<div class="text-xs font-medium opacity-70">Dice</div>
158+
<div class="text-4xl font-bold">
159+
{game.world?.currentDie !== 'None' ? game.world?.currentDie : '-'}
160+
</div>
161+
</div>
162+
<div
163+
class="absolute right-4 bottom-4 z-10 flex h-14 w-28 gap-1 rounded-md bg-base-200/40 p-1 backdrop-blur-md"
115164
>
116165
<button
117-
class="btn h-full btn-primary"
118-
onclick={() => {
119-
console.log('End turn clicked');
166+
class="btn h-full w-full btn-primary"
167+
onclick={async () => {
168+
if (game.playerName) {
169+
const action = new EndTurnAction(game.playerName);
170+
await game.sendAction(action);
171+
}
120172
}}
121173
>
122174
End Turn
@@ -209,4 +261,41 @@
209261
</form>
210262
</div>
211263
</dialog>
264+
265+
<dialog class="modal" bind:this={stealModalElement}>
266+
<div class="modal-box">
267+
<h2 class="mb-4 text-lg font-semibold">Steal from a Player</h2>
268+
<p class="mb-4 text-sm opacity-70">
269+
Choose a player to steal a random resource from. These players have settlements or cities
270+
adjacent to the robber.
271+
</p>
272+
273+
<div class="flex flex-col gap-2">
274+
{#each stealablePlayers as player}
275+
<button
276+
class="btn btn-block justify-start"
277+
onclick={() => handleStealFrom(player.name)}
278+
>
279+
<div
280+
class="h-4 w-4 flex-shrink-0 rounded-full"
281+
style="background-color: #{player.color.toString(16).padStart(6, '0')}"
282+
></div>
283+
<span class="flex-1 text-left">{player.name}</span>
284+
<span class="text-xs opacity-60">
285+
{player.resources.wood +
286+
player.resources.clay +
287+
player.resources.stone +
288+
player.resources.grain +
289+
player.resources.wool}
290+
resources
291+
</span>
292+
</button>
293+
{/each}
294+
</div>
295+
296+
<div class="modal-action">
297+
<button class="btn" onclick={handleSkipStealing}>Skip Stealing</button>
298+
</div>
299+
</div>
300+
</dialog>
212301
</div>

0 commit comments

Comments
 (0)