Skip to content

Commit 68cf644

Browse files
authored
Misc fixed BOTC (#399)
1 parent 76d8e3f commit 68cf644

10 files changed

Lines changed: 242 additions & 169 deletions

File tree

src/app/botc/blood-on-the-clocktower.tsx

Lines changed: 79 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,31 @@ import { useState } from 'react';
1010
import BOTCCharacterSelectTable from './character-select';
1111
import {
1212
BOTCPlayer,
13-
CharacterID,
13+
CharacterId,
14+
createPlayer,
1415
EDITIONS,
1516
getAllCharacters,
1617
} from './characters';
1718
import NightOrder from './night-order';
1819
import Grimoire from './grimoire';
20+
import { shuffle, zip } from 'utils/array';
1921

2022
export const metadata: Metadata = {
2123
title: 'Blood on the Clocktower',
2224
};
2325

2426
interface GameState {
25-
numberOfPlayers: number;
2627
editionId: string;
27-
characters: CharacterID[];
28+
characters: CharacterId[];
29+
lobby: { name: string; corpsId?: string }[];
2830
players: BOTCPlayer[];
2931
}
3032

3133
const newGameState: GameState = {
32-
numberOfPlayers: 7,
3334
editionId: 'trouble-brewing',
3435
characters: [],
3536
players: [],
37+
lobby: [],
3638
};
3739

3840
interface BloodOnTheClocktowerElementProps {
@@ -51,19 +53,24 @@ const BloodOnTheClocktowerElement = ({
5153
_setSearchParamsGameState(JSON.stringify(newGameState));
5254
};
5355

56+
const assignCharacters = () => {
57+
const players = zip(
58+
shuffle(gameState.characters.slice()),
59+
gameState.lobby,
60+
).map(([characterId, player]) => createPlayer({ ...player, characterId }));
61+
setGameState({
62+
...gameState,
63+
lobby: gameState.lobby.slice(players.length),
64+
players,
65+
});
66+
};
67+
5468
const edition = EDITIONS.find(
5569
(edition) => edition.id === gameState.editionId,
5670
);
5771

58-
if (!edition) {
59-
return <div>NO EDITION FOUND</div>;
60-
}
61-
6272
const detailsStartOpen = true;
6373

64-
// TODO: Change this to alive players
65-
const alivePlayers = gameState.characters;
66-
6774
return (
6875
<div className='flex max-w-3xl flex-col gap-2'>
6976
<h2>Bleck on the Corpstower</h2>
@@ -82,35 +89,59 @@ const BloodOnTheClocktowerElement = ({
8289
value={gameState.editionId}
8390
/>
8491
<div className='h-2' />
85-
<Modal
86-
title={`Select Characters - ${edition.name}`}
87-
target={
88-
<Button>
89-
<IconUser />
90-
Select Characters
91-
</Button>
92-
}
93-
withCloseButton
94-
>
95-
<BOTCCharacterSelectTable
96-
numberOfPlayers={gameState.numberOfPlayers}
97-
onNumberOfPlayersChange={(n) => {
98-
setGameState({ ...gameState, numberOfPlayers: n });
92+
{edition && (
93+
<div className='flex gap-4'>
94+
<Modal
95+
title={`Select Characters - ${edition.name}`}
96+
target={
97+
<Button>
98+
<IconUser />
99+
Select Characters
100+
</Button>
101+
}
102+
withCloseButton
103+
>
104+
<BOTCCharacterSelectTable
105+
edition={edition}
106+
onSelectedCharactersChange={(characters) => {
107+
setGameState({ ...gameState, characters });
108+
}}
109+
/>
110+
</Modal>
111+
{gameState.characters.length > 0 && (
112+
<Button onClick={assignCharacters}>Assign characters</Button>
113+
)}
114+
</div>
115+
)}
116+
<div className='h-2' />
117+
<div className='flex gap-4'>
118+
<Button
119+
onClick={() => {
120+
setGameState(newGameState);
99121
}}
100-
edition={edition}
101-
onSelectedCharactersChange={(characters) => {
102-
setGameState({ ...gameState, characters });
122+
>
123+
Clear cache
124+
</Button>
125+
<Button
126+
onClick={() => {
127+
setGameState({
128+
...gameState,
129+
lobby: [
130+
{ name: 'Hannes' },
131+
{ name: 'Hannes2' },
132+
{ name: 'Bartolomeus' },
133+
{ name: 'Kyoto' },
134+
{ name: 'Jörgen' },
135+
{ name: 'Pratkvarn' },
136+
{ name: 'Pelle' },
137+
{ name: 'Lars' },
138+
],
139+
});
103140
}}
104-
/>
105-
</Modal>
106-
<div className='h-2' />
107-
<Button
108-
onClick={() => {
109-
setGameState(newGameState);
110-
}}
111-
>
112-
Clear cache
113-
</Button>
141+
>
142+
Populate lobby
143+
</Button>
144+
</div>
114145
</details>
115146
<details open={detailsStartOpen} className='border p-2 shadow-md'>
116147
<summary className='select-none'>Grimoire</summary>
@@ -119,15 +150,16 @@ const BloodOnTheClocktowerElement = ({
119150
characters={gameState.characters}
120151
/>
121152
</details>
122-
<details open={detailsStartOpen} className='border p-2 shadow-md'>
123-
<summary className='select-none'>Night Order</summary>
124-
<div className='h-2' />
125-
<NightOrder
126-
alivePlayers={alivePlayers}
127-
numberOfPlayers={gameState.numberOfPlayers}
128-
allCharacters={getAllCharacters(edition)}
129-
/>
130-
</details>
153+
{edition && gameState.players.length > 0 && (
154+
<details open={detailsStartOpen} className='border p-2 shadow-md'>
155+
<summary className='select-none'>Night Order</summary>
156+
<div className='h-2' />
157+
<NightOrder
158+
players={gameState.players}
159+
allCharacters={getAllCharacters(edition)}
160+
/>
161+
</details>
162+
)}
131163
</div>
132164
);
133165
};

src/app/botc/character-panel.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ const BOTCCharacterPanel = ({
1414
return (
1515
<div className='flex flex-col gap-2'>
1616
<div className='flex items-center gap-4'>
17-
<img className='h-12 w-12 scale-150' loading='lazy' src={imgSrc} />
18-
<h4>{name}</h4>
17+
<img
18+
className='h-10 w-10 scale-150 lg:h-12 lg:w-12'
19+
loading='lazy'
20+
src={imgSrc}
21+
/>
22+
<h4 className='hidden lg:block'>{name}</h4>
23+
<h5 className='lg:hidden'>{name}</h5>
1924
</div>
20-
{showDescription && <p className='text-sm'>{description}</p>}
25+
{showDescription && <p className='text-xs'>{description}</p>}
2126
</div>
2227
);
2328
};

src/app/botc/character-select.tsx

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { initObject, range, shuffle } from 'utils/array';
55
import { cn } from 'utils/class-names';
66
import {
77
CHARACTER_TYPES,
8-
CharacterID,
8+
CharacterId,
99
CHARACTERS,
1010
CharacterType,
1111
Edition,
@@ -22,7 +22,7 @@ import React from 'react';
2222

2323
const getNumberOfCharacters = (
2424
players: number,
25-
selectedCharacters: CharacterID[] = [],
25+
selectedCharacters: CharacterId[] = [],
2626
): Record<CharacterType, number> => {
2727
const clampedPlayers = Math.min(players, MAX_PLAYERS);
2828
const res =
@@ -85,7 +85,7 @@ const selectRandom = (
8585
edition: Edition,
8686
numberOfCharacters: Record<CharacterType, number>,
8787
) => {
88-
const selected: CharacterID[] = [];
88+
const selected: CharacterId[] = [];
8989
for (const characterType of CHARACTER_TYPES) {
9090
const copy = shuffle(edition[characterType].slice());
9191
selected.push(...copy.slice(0, numberOfCharacters[characterType]));
@@ -124,7 +124,7 @@ const selectRandom = (
124124
};
125125

126126
const findSelectionError = (
127-
characters: CharacterID[],
127+
characters: CharacterId[],
128128
numberOfCharacters: Record<CharacterType, number>,
129129
) => {
130130
const res: Record<CharacterType, number> = initObject(CHARACTER_TYPES, 0);
@@ -160,23 +160,20 @@ const findSelectionError = (
160160
};
161161

162162
interface BOTCCharacterSelectProps {
163-
numberOfPlayers: number;
164-
onNumberOfPlayersChange: (numberOfPlayers: number) => void;
165163
edition: Edition;
166-
onSelectedCharactersChange: (selectedCharacters: CharacterID[]) => void;
164+
onSelectedCharactersChange: (selectedCharacters: CharacterId[]) => void;
167165
}
168166

169167
const BOTCCharacterSelect = ({
170-
numberOfPlayers,
171-
onNumberOfPlayersChange,
172168
edition,
173169
onSelectedCharactersChange,
174170
}: BOTCCharacterSelectProps) => {
175171
const [showDescriptions, setShowDescriptions] = useState(true);
176-
const [selectedCharacters, _setSelectedCharacters] = useState<CharacterID[]>(
172+
const [numberOfPlayers, setNumberOfPlayers] = useState(7);
173+
const [selectedCharacters, _setSelectedCharacters] = useState<CharacterId[]>(
177174
[],
178175
);
179-
const setSelectedCharacters = (c: CharacterID[]) => {
176+
const setSelectedCharacters = (c: CharacterId[]) => {
180177
onSelectedCharactersChange(c);
181178
_setSelectedCharacters(c);
182179
};
@@ -232,14 +229,15 @@ const BOTCCharacterSelect = ({
232229
</table>
233230
<label htmlFor='players'>Number of players: {numberOfPlayers}</label>
234231
<input
232+
className='max-w-sm'
235233
type='range'
236234
id='players'
237235
name='players'
238236
min={MIN_PLAYERS}
239237
max={MAX_PLAYERS}
240-
defaultValue={numberOfPlayers}
238+
value={numberOfPlayers}
241239
onChange={(e) => {
242-
onNumberOfPlayersChange(e.currentTarget.valueAsNumber);
240+
setNumberOfPlayers(e.currentTarget.valueAsNumber);
243241
}}
244242
/>
245243
<Switch

src/app/botc/character-token.tsx

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import Modal from 'components/modal';
12
import {
2-
CharacterID,
3+
CharacterId,
34
CHARACTERS,
45
FIRST_NIGHT_TEXT,
56
getImagePathFromId,
@@ -8,7 +9,7 @@ import {
89

910
interface CharacterTokenProps {
1011
playerName?: string;
11-
characterId?: CharacterID;
12+
characterId?: CharacterId;
1213
}
1314

1415
const FIRST_NIGHT_CHARACTERS = new Set(FIRST_NIGHT_TEXT.map(([id, _]) => id));
@@ -20,44 +21,56 @@ const CharacterToken = ({ playerName, characterId }: CharacterTokenProps) => {
2021
const hasRightLeaf = characterId && OTHER_NIGHT_CHARACTERS.has(characterId);
2122

2223
return (
23-
<div className='relative mt-1 h-32 w-32 rounded-full bg-[repeat] bg-[url(/botc/token-noise.webp)] bg-auto text-center shadow-md'>
24-
<h5 className='absolute -top-1 left-1/2 -translate-x-1/2 rounded bg-red-600 px-2 text-white'>
25-
{playerName}
26-
</h5>
27-
<img
28-
className='absolute left-1/2 top-1/2 w-24 -translate-x-1/2 -translate-y-1/2'
29-
src='/botc/clockface.webp'
30-
loading='lazy'
31-
/>
32-
{hasLeftLeaf && (
33-
<img className='absolute h-full w-full' src='/botc/leaf-left.webp' />
34-
)}
35-
{hasRightLeaf && (
36-
<img className='absolute h-full w-full' src='/botc/leaf-right.webp' />
37-
)}
38-
39-
{character && (
40-
<>
24+
<Modal
25+
target={
26+
<div className='relative mt-1 h-32 w-32 rounded-full bg-[repeat] bg-[url(/botc/token-noise.webp)] bg-auto text-center shadow-md'>
27+
<h5 className='absolute -top-1 left-1/2 -translate-x-1/2 rounded bg-red-600 px-2 text-white'>
28+
{playerName}
29+
</h5>
4130
<img
42-
className='absolute h-full w-full'
43-
src={getImagePathFromId(character.id)}
31+
className='absolute left-1/2 top-1/2 w-24 -translate-x-1/2 -translate-y-1/2'
32+
src='/botc/clockface.webp'
4433
loading='lazy'
4534
/>
46-
<svg viewBox='0 0 150 150'>
47-
<path
48-
d='M 13 75 C 13 160, 138 160, 138 75'
49-
id='curve'
50-
fill='transparent'
51-
></path>
52-
<text textAnchor='middle'>
53-
<textPath startOffset='50%' href='#curve'>
54-
{character.name}
55-
</textPath>
56-
</text>
57-
</svg>
58-
</>
59-
)}
60-
</div>
35+
{hasLeftLeaf && (
36+
<img
37+
className='absolute h-full w-full'
38+
src='/botc/leaf-left.webp'
39+
/>
40+
)}
41+
{hasRightLeaf && (
42+
<img
43+
className='absolute h-full w-full'
44+
src='/botc/leaf-right.webp'
45+
/>
46+
)}
47+
48+
{character && (
49+
<>
50+
<img
51+
className='absolute h-full w-full'
52+
src={getImagePathFromId(character.id)}
53+
loading='lazy'
54+
/>
55+
<svg viewBox='0 0 150 150'>
56+
<path
57+
d='M 13 75 C 13 160, 138 160, 138 75'
58+
id='curve'
59+
fill='transparent'
60+
></path>
61+
<text textAnchor='middle'>
62+
<textPath startOffset='50%' href='#curve'>
63+
{character.name}
64+
</textPath>
65+
</text>
66+
</svg>
67+
</>
68+
)}
69+
</div>
70+
}
71+
>
72+
TEST
73+
</Modal>
6174
);
6275
};
6376

0 commit comments

Comments
 (0)