@tastic/shell 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -2
- package/assets/sounds/notification.wav +0 -0
- package/assets/sounds/select.wav +0 -0
- package/dist/index.d.mts +487 -0
- package/dist/index.d.ts +487 -0
- package/dist/index.js +1636 -0
- package/dist/index.mjs +1562 -0
- package/dist/notification-CGGFPYK7.wav +0 -0
- package/dist/select-MLBNNA4N.wav +0 -0
- package/package.json +163 -4
- package/src/PackContext.tsx +22 -0
- package/src/RootLayout.tsx +186 -0
- package/src/components/Fonts.tsx +41 -0
- package/src/components/GuideHost.tsx +104 -0
- package/src/components/Providers.tsx +86 -0
- package/src/components/SeatTwoPanel.tsx +132 -0
- package/src/components/SettingsDialog.tsx +42 -0
- package/src/components/Theme.tsx +75 -0
- package/src/components/UpdateDialog.tsx +13 -0
- package/src/components/imageGameIcon.tsx +13 -0
- package/src/gameStats.tsx +90 -0
- package/src/hooks/useActiveRound.ts +16 -0
- package/src/hooks/useAppSettings.ts +19 -0
- package/src/hooks/useFrozenWhileHidden.ts +33 -0
- package/src/hooks/useLastActiveGame.tsx +73 -0
- package/src/hooks/useOpponent.ts +14 -0
- package/src/hooks/useProfiles.tsx +134 -0
- package/src/hooks/useSeatColors.ts +148 -0
- package/src/index.ts +39 -0
- package/src/navigation.ts +11 -0
- package/src/playerSettings.ts +112 -0
- package/src/redux/appSettingsSlice.ts +34 -0
- package/src/redux/createPackStore.ts +78 -0
- package/src/redux/gameGuideSlice.ts +39 -0
- package/src/redux/lastActiveGameSlice.ts +12 -0
- package/src/redux/liveplaySlice.ts +34 -0
- package/src/redux/onProfileRemoved.ts +25 -0
- package/src/redux/opponentSlice.ts +21 -0
- package/src/redux/profileSelectionSlice.ts +17 -0
- package/src/redux/seatColorsSlice.ts +24 -0
- package/src/redux/shellState.ts +59 -0
- package/src/screens/GameRoute.tsx +48 -0
- package/src/screens/HomeScreen.tsx +378 -0
- package/src/screens/LoadoutScreen.tsx +271 -0
- package/src/screens/ProfilesRoute.tsx +14 -0
- package/src/screens/StatsScreen.tsx +133 -0
- package/src/screens/homeLayout.ts +129 -0
- package/src/splashGate.ts +3 -0
- package/src/types.ts +216 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import type { ThemeAppearance } from '@rific/auto-paper'
|
|
2
|
+
import type { ViewRotation } from '@tastic/core'
|
|
3
|
+
import type { MenuOption, PopoverHost } from '@tastic/hud'
|
|
4
|
+
import type { GuideStep } from '@tastic/hud/guide'
|
|
5
|
+
import type { FontSource } from 'expo-font'
|
|
6
|
+
import type { ComponentType, ReactNode } from 'react'
|
|
7
|
+
import type { Action, Reducer } from 'redux'
|
|
8
|
+
|
|
9
|
+
import type { ShellState } from './redux/shellState'
|
|
10
|
+
|
|
11
|
+
// The two seats every game in a pack shares: seat 1 is whoever picked a player on Home, seat 2 is
|
|
12
|
+
// the CPU, a second person, or (only in a game with a solo mode) nobody at all.
|
|
13
|
+
export type Seat = 1 | 2
|
|
14
|
+
|
|
15
|
+
// Who sits in seat 2. One value for the whole pack (shell/redux/opponentSlice.ts), so the same two
|
|
16
|
+
// people stay set up from one game to the next.
|
|
17
|
+
export type SeatTwo = 'cpu' | 'person' | 'nobody'
|
|
18
|
+
|
|
19
|
+
// What a round is, from seat 2: a CPU opponent, two people on one device, or one player alone.
|
|
20
|
+
export type RoundMode = 'vsCpu' | 'twoPlayer' | 'solo'
|
|
21
|
+
|
|
22
|
+
export function roundModeFor(seatTwo: SeatTwo): RoundMode {
|
|
23
|
+
return seatTwo === 'person' ? 'twoPlayer' : seatTwo === 'nobody' ? 'solo' : 'vsCpu'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Seat 2's saved choice in one game (GameDefinition.seatTwo): a saved 'nobody' reads as the CPU in a
|
|
27
|
+
// game without a solo mode, and so does anything else the game doesn't offer. A game with no seat 2
|
|
28
|
+
// at all (no seatTwo) is always nobody, so it plays solo.
|
|
29
|
+
export function seatTwoFor(saved: SeatTwo, choices: readonly SeatTwo[] | undefined): SeatTwo {
|
|
30
|
+
if (!choices) return 'nobody'
|
|
31
|
+
return choices.includes(saved) ? saved : 'cpu'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The players who take part in a round and have to press Ready: a CPU and nobody don't.
|
|
35
|
+
export function humanSeatsFor(mode: RoundMode): Seat[] {
|
|
36
|
+
return mode === 'twoPlayer' ? [1, 2] : [1]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// What the loadout's match-options row is handed (see GameLoadout.options): the popover host it
|
|
40
|
+
// shares with seat 1's panel, the screen's rotation, and the colors the row draws with.
|
|
41
|
+
export interface LoadoutOptionsProps {
|
|
42
|
+
host: PopoverHost
|
|
43
|
+
rotation: ViewRotation
|
|
44
|
+
accentColor: string
|
|
45
|
+
mutedColor: string
|
|
46
|
+
onAccentColor: string
|
|
47
|
+
dark: boolean
|
|
48
|
+
// Undefined while the loadout folds randomize/reset into its shared action band instead.
|
|
49
|
+
onRandomize?: () => void
|
|
50
|
+
onReset?: () => void
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The colors a seat's controls picker draws with: the loadout's tertiary accent, or the seat's own.
|
|
54
|
+
export interface SeatControlLook {
|
|
55
|
+
accentColor: string
|
|
56
|
+
seatColor: string
|
|
57
|
+
mutedColor: string
|
|
58
|
+
dark: boolean
|
|
59
|
+
rotation: ViewRotation
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// One game's part of the shared loadout (shell/screens/LoadoutScreen.tsx), built by the game's
|
|
63
|
+
// own useLoadout hook from its own saved settings.
|
|
64
|
+
export interface GameLoadout {
|
|
65
|
+
// The settings snapshot committed as the round's when every human player readies up.
|
|
66
|
+
roundSettings: object
|
|
67
|
+
// The match-options row (board, rules, puck): the game's own controls, drawn under the shared
|
|
68
|
+
// popover host so its menus open, elevate and close like every other loadout menu.
|
|
69
|
+
options: (props: LoadoutOptionsProps) => ReactNode
|
|
70
|
+
// The popover ids the options row opens, so the loadout can elevate the row while one is open.
|
|
71
|
+
optionIds: readonly string[]
|
|
72
|
+
randomize: () => void
|
|
73
|
+
reset: () => void
|
|
74
|
+
// A human seat's controls picker (shot style, keys), or undefined when the game has none.
|
|
75
|
+
seatControl: (seat: Seat, host: PopoverHost, look: SeatControlLook) => ReactNode | undefined
|
|
76
|
+
// Seat 2's CPU difficulty, saved per game since each game's CPU plays differently. Left out by a
|
|
77
|
+
// game with no seat 2 (GameDefinition.seatTwo), or whose CPU has no levels: seat 2's menu then
|
|
78
|
+
// offers one plain CPU.
|
|
79
|
+
cpuDifficulty?: { value: string; options: MenuOption<string>[]; onChange: (value: string) => void }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// What a game's How to Play cards are built from: the live settings (through the game's own
|
|
83
|
+
// selector, so the guide host never has to know their shape), how seat 2 is set up, and whether
|
|
84
|
+
// the device is touch-first.
|
|
85
|
+
export interface GameGuide {
|
|
86
|
+
version: number
|
|
87
|
+
selectVersionSeen: (state: ShellState) => number
|
|
88
|
+
markSeen: (version: number) => Action
|
|
89
|
+
// Returns a value compared with shallowEqual, so keep it a flat object of the settings read.
|
|
90
|
+
selectInput: (state: ShellState) => object
|
|
91
|
+
getSteps: (input: object, context: { touch: boolean; mode: RoundMode }) => GuideStep[]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// A game's icon on Home, drawn in the seats' colors (seat 1's from Home's button, seat 2's from
|
|
95
|
+
// whoever sits there), so it matches the players about to play, and in the theme where a part of it
|
|
96
|
+
// follows the board's own colors. An icon drawn as a light and dark pair of pictures instead is
|
|
97
|
+
// imageGameIcon's (components/imageGameIcon.tsx).
|
|
98
|
+
export interface GameIconProps {
|
|
99
|
+
size: number
|
|
100
|
+
p1Color: string
|
|
101
|
+
p2Color: string
|
|
102
|
+
dark: boolean
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// A game's preview on Home (GameDefinition.Preview), drawn in a box of this size: what a deal or a
|
|
106
|
+
// board of the game looks like, in place of its icon. Every preview box has the same shape
|
|
107
|
+
// (PREVIEW_ASPECT in screens/homeLayout.ts); the seats' colors and the theme as for the icon.
|
|
108
|
+
export interface GamePreviewProps {
|
|
109
|
+
width: number
|
|
110
|
+
height: number
|
|
111
|
+
p1Color: string
|
|
112
|
+
p2Color: string
|
|
113
|
+
dark: boolean
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface GameDefinition {
|
|
117
|
+
id: string
|
|
118
|
+
title: string
|
|
119
|
+
// One line under the tile's name on Home.
|
|
120
|
+
blurb: string
|
|
121
|
+
icon: ComponentType<GameIconProps>
|
|
122
|
+
// Drawn on Home in place of the icon, a frame after Home first paints (a preview is far more to
|
|
123
|
+
// draw than an icon). While any game in the pack has one, Home lays its games out as tiles: rows
|
|
124
|
+
// with the preview at their start, and a grid beside the hero on a wide screen. A game without one
|
|
125
|
+
// keeps its icon there. What it draws is the game's own.
|
|
126
|
+
Preview?: ComponentType<GamePreviewProps>
|
|
127
|
+
// Seat 2's choices in this game, in menu order. 'nobody' only where the game has a solo mode.
|
|
128
|
+
// Left out by a game for one player only: its loadout has no seat 2 button and every round is solo.
|
|
129
|
+
seatTwo?: readonly SeatTwo[]
|
|
130
|
+
// How the 2 Player loadout seats its players, to match where the game puts them: always face
|
|
131
|
+
// to face (or only off the web, where a desktop window seats them side by side), and whether an
|
|
132
|
+
// upside-down hold swaps who is at the bottom. Only read in 2 Player, so a game without 'person'
|
|
133
|
+
// among its seatTwo choices can leave it out.
|
|
134
|
+
twoPlayerSeats?: { faceToFaceOnWeb: boolean; followUpsideDown: boolean }
|
|
135
|
+
// The store keys this game mounts (settings, achievements, guide), merged into the pack's store.
|
|
136
|
+
reducers: Record<string, Reducer>
|
|
137
|
+
// Mounted at the app root for the game's lifetime (its stats and achievements context).
|
|
138
|
+
Provider: ComponentType<{ children: ReactNode }>
|
|
139
|
+
// Left out by a game with nothing to set up before it starts: Home opens its play screen
|
|
140
|
+
// straight away (after its How to Play the first time), and its rule variants are rows in its
|
|
141
|
+
// play screen's own Settings (SettingsDialog's children).
|
|
142
|
+
useLoadout?: (mode: RoundMode) => GameLoadout
|
|
143
|
+
PlayScreen: ComponentType
|
|
144
|
+
// A cold launch drops the player back into this game's play screen if that's where they left the
|
|
145
|
+
// app (they didn't back out of it first). For a game whose play screen saves and restores its own
|
|
146
|
+
// board: the shell only remembers which game it was (redux/lastActiveGameSlice.ts). A match that
|
|
147
|
+
// isn't saved (liveplay) shouldn't opt in, or a relaunch would open a fresh one.
|
|
148
|
+
resumable?: boolean
|
|
149
|
+
// The game's stats sections and its achievement list for the stats screen, for one profile
|
|
150
|
+
// (null is Everyone).
|
|
151
|
+
StatsSections: ComponentType<{ profileId: string | null }>
|
|
152
|
+
AchievementList: ComponentType<{ profileId: string | null }>
|
|
153
|
+
// Clears the game's stats and achievements (the stats screen's Reset).
|
|
154
|
+
resetStats: () => Action
|
|
155
|
+
guide: GameGuide
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// What Home hands the pack's hero (PackDefinition.HeroTitle).
|
|
159
|
+
export interface HeroTitleProps {
|
|
160
|
+
// The pack's wordmark (see PackDefinition.wordmark).
|
|
161
|
+
words: readonly [string, string]
|
|
162
|
+
color: string
|
|
163
|
+
// The live theme's primary/secondary: whatever the players actually picked, not a fixed pair, so
|
|
164
|
+
// an accent in each player's color matches their real in-game color.
|
|
165
|
+
p1Color: string
|
|
166
|
+
p2Color: string
|
|
167
|
+
// False while another screen covers Home or the app is in the background: a hero that animates
|
|
168
|
+
// forever pauses then.
|
|
169
|
+
active: boolean
|
|
170
|
+
// The box the hero has to fit in, when Home gives it one: beside the game grid on a wide screen
|
|
171
|
+
// (a pack whose games have previews, see GameDefinition.Preview), where the hero shares a narrow
|
|
172
|
+
// pane with seat 1's button. Undefined is the full width of a phone held upright, its natural size.
|
|
173
|
+
fit?: { width: number; height: number }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// What the stats screen hands a pack's own section (PackDefinition.StatsSections): its two filters,
|
|
177
|
+
// the player (null is Everyone) and the game (null is All Games).
|
|
178
|
+
export interface PackStatsSectionsProps {
|
|
179
|
+
profileId: string | null
|
|
180
|
+
gameId: string | null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface PackDefinition {
|
|
184
|
+
name: string
|
|
185
|
+
// The Home wordmark's two lines.
|
|
186
|
+
wordmark: readonly [string, string]
|
|
187
|
+
// A seat's color pick for a profile is kept for this pack only; the seat button says so.
|
|
188
|
+
colorCaption: string
|
|
189
|
+
games: readonly GameDefinition[]
|
|
190
|
+
// Player 1's and player 2's own default colors. They seed the theme's primary and secondary (the
|
|
191
|
+
// tertiary is derived from them, see shellState.ts) and the guest and CPU colors a fresh install
|
|
192
|
+
// starts with (seatColorsSlice.ts), so a fresh install's live theme and its remembered colors agree.
|
|
193
|
+
// Also what @tastic/profile's avoidSeatColorClash moves seat 2 to when both seats land on one color.
|
|
194
|
+
seatColors: Record<Seat, string>
|
|
195
|
+
// The theme's appearance on a fresh install ('system' when unset). Still changeable in Settings.
|
|
196
|
+
appearance?: ThemeAppearance
|
|
197
|
+
// Home's wordmark, drawn by the pack: the shell only says where and in which colors.
|
|
198
|
+
HeroTitle: ComponentType<HeroTitleProps>
|
|
199
|
+
// Fonts the pack's own art needs (its hero's display face), loaded beside the icon font behind
|
|
200
|
+
// the splash screen's 'fonts' gate (components/Fonts.tsx).
|
|
201
|
+
fonts?: Record<string, FontSource>
|
|
202
|
+
// The typeface every Paper text variant is set in (auto-paper's Provider). Unset is Paper's own.
|
|
203
|
+
fontFamily?: string
|
|
204
|
+
// The version label in Settings: src/constants/release.ts's otaVersion, which the OTA tooling bumps.
|
|
205
|
+
otaVersion: number
|
|
206
|
+
// The pack's own Settings sections (how cards look, how play is helped along), in every Settings
|
|
207
|
+
// dialog under the shell's own rows and any a play screen adds. Their values live in `reducers`.
|
|
208
|
+
SettingsSections?: ComponentType
|
|
209
|
+
// Store keys of the pack's own, beside the shell's and each game's (GameDefinition.reducers): what
|
|
210
|
+
// SettingsSections saves, or anything else that belongs to every game. Never a key the shell or a
|
|
211
|
+
// game already mounts.
|
|
212
|
+
reducers?: Record<string, Reducer>
|
|
213
|
+
// A section of the pack's own at the top of the stats screen's Stats tab, across its games (a
|
|
214
|
+
// calendar of play), above each game's own StatsSections. It follows the screen's two filters.
|
|
215
|
+
StatsSections?: ComponentType<PackStatsSectionsProps>
|
|
216
|
+
}
|