@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jay Deaton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,155 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tastic/shell
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The app around a pack of React Native games: Home, each game's loadout, the stats screen, Profiles
|
|
4
|
+
and Settings, the pack's one Redux store, and every provider the games run under. An app hands it one
|
|
5
|
+
`PackDefinition` (its name, its games, its seat colors, its hero) and keeps only its games, its hero
|
|
6
|
+
and a handful of thin expo-router routes. Two-seat duels and games for one player alike: a game can
|
|
7
|
+
leave out its seat 2 and its loadout, come back on a cold launch, and show a preview on Home.
|
|
8
|
+
|
|
9
|
+
Built from the shell PuckTastic and DuelTastic each carried in `src/shell/` (made identical on
|
|
10
|
+
2026-10-28), so the packs and the next ones (CardTastic) share one copy. Part of the `@tastic`
|
|
11
|
+
package ecosystem, on [`@tastic/hud`](https://github.com/jayrdeaton/react-native-hud),
|
|
12
|
+
[`@tastic/profile`](https://github.com/jayrdeaton/react-native-game-profile) and
|
|
13
|
+
[`@tastic/core`](https://github.com/jayrdeaton/react-native-game-core).
|
|
14
|
+
|
|
15
|
+
## What's in here
|
|
16
|
+
|
|
17
|
+
- **`RootLayout`**: the root of the app (gesture root, safe area, fonts, Redux and its persist gate,
|
|
18
|
+
the theme, profiles, every game's own provider, the splash gates, the shared orientation reading,
|
|
19
|
+
toasts, How to Play, the OTA update dialog) around expo-router's stack.
|
|
20
|
+
- **Screens** for the routes: `HomeScreen` (the pack's hero, seat 1's button, a row per game, or a
|
|
21
|
+
grid of preview tiles on a wide screen), `LoadoutRoute` and `PlayRoute` (`loadout/[game]`,
|
|
22
|
+
`play/[game]`), `ProfilesRoute` and `StatsScreen`.
|
|
23
|
+
- **The store**: `createPackStore(pack, { keyPrefix, migrate })` builds one persisted store from the
|
|
24
|
+
shell's keys (theme, haptics, sound, Keep Awake, profiles, seat selection, seat colors, app settings,
|
|
25
|
+
seat 2, the never-saved `liveplay`, and `lastActiveGame` in a pack with a resumable game), the
|
|
26
|
+
pack's own `reducers` and every game's own `reducers`.
|
|
27
|
+
- **What a game builds on**: `createPlayerSettings` (its settings and controls, kept per player),
|
|
28
|
+
`createGameStats` (its stats and achievements), `createGameGuideSlice` (which version of its How to
|
|
29
|
+
Play a player has seen), `onProfileRemoved` (forget a deleted profile), `useActiveRound`,
|
|
30
|
+
`useProfiles`, `useSeatColors`, `SettingsDialog`, `safeBack` and `imageGameIcon`.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @tastic/shell
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The shell is an Expo Router app's own code, so it needs every peer below in the app's `dependencies`.
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
`src/pack.ts` describes the pack and builds its store:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { createPackStore, type GameDefinition, type PackDefinition } from '@tastic/shell'
|
|
46
|
+
|
|
47
|
+
export const games: readonly GameDefinition[] = [lightCycles, turf, tag, snake]
|
|
48
|
+
|
|
49
|
+
export const pack: PackDefinition = {
|
|
50
|
+
name: 'DuelTastic',
|
|
51
|
+
wordmark: ['Duel', 'Tastic'],
|
|
52
|
+
colorCaption: 'DuelTastic only',
|
|
53
|
+
games,
|
|
54
|
+
seatColors: { 1: '#2196f3', 2: '#f44336' },
|
|
55
|
+
appearance: 'dark',
|
|
56
|
+
HeroTitle,
|
|
57
|
+
fonts: HERO_FONTS,
|
|
58
|
+
fontFamily: MONO_FONT,
|
|
59
|
+
otaVersion: release.otaVersion
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Never change the prefix or a key once the pack ships: every player would start over.
|
|
63
|
+
export const { store, persistor } = createPackStore(pack, { keyPrefix: 'dueltastic:' })
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
and `src/app/` holds six thin routes:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// _layout.tsx
|
|
70
|
+
export default function Layout() {
|
|
71
|
+
return <RootLayout pack={pack} store={store} persistor={persistor} />
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// index.tsx, profiles.tsx, stats.tsx
|
|
75
|
+
export default HomeScreen // ProfilesRoute, StatsScreen
|
|
76
|
+
|
|
77
|
+
// loadout/[game].tsx and play/[game].tsx (PlayRoute): the web build is a static export
|
|
78
|
+
export function generateStaticParams() {
|
|
79
|
+
return games.map((g) => ({ game: g.id }))
|
|
80
|
+
}
|
|
81
|
+
export default LoadoutRoute
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The game id comes second in a route, never first: expo-router strips the web build's base path off
|
|
85
|
+
the front of an in-app link, so a route starting with a game id equal to the base path
|
|
86
|
+
(`boxhockey/loadout` under `/boxhockey`) would lose that segment.
|
|
87
|
+
|
|
88
|
+
### `PackDefinition`
|
|
89
|
+
|
|
90
|
+
| Field | |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| `name`, `wordmark`, `colorCaption` | The pack's name, Home's two-line wordmark, and the caption under a seat's Color row while a profile is in it ("DuelTastic only"). |
|
|
93
|
+
| `games` | Every `GameDefinition`, in Home's order. |
|
|
94
|
+
| `seatColors` | Player 1's and player 2's default colors: the theme's primary and secondary, and a fresh install's guest and CPU colors. |
|
|
95
|
+
| `appearance?` | The theme's appearance on a fresh install (`'system'` when unset). |
|
|
96
|
+
| `HeroTitle` | Home's wordmark, drawn by the pack (`HeroTitleProps`: `words`, `color`, `p1Color`, `p2Color`, `active`, false while Home is covered or the app is in the background, and `fit`, the box it has beside the preview grid on a wide screen, undefined at its own size). |
|
|
97
|
+
| `fonts?` | Fonts the pack's own art needs, loaded behind the splash screen beside the icon font. |
|
|
98
|
+
| `fontFamily?` | The typeface on every Paper text variant. Unset is Paper's own. |
|
|
99
|
+
| `otaVersion` | The version label in Settings. |
|
|
100
|
+
| `SettingsSections?` | The pack's own Settings sections, in every Settings dialog, under the shell's rows and any a play screen adds. |
|
|
101
|
+
| `reducers?` | The pack's own store keys (what `SettingsSections` saves), beside the shell's and the games'. Never a key either already mounts. |
|
|
102
|
+
| `StatsSections?` | A section across the pack's games (a calendar of play), first on the stats screen's Stats tab, handed its two filters (`PackStatsSectionsProps`: `profileId`, null for Everyone, and `gameId`, null for All Games). |
|
|
103
|
+
|
|
104
|
+
### `GameDefinition`
|
|
105
|
+
|
|
106
|
+
Each game is one object: its `id`, `title`, `blurb` and `icon` (a component drawn in the seats'
|
|
107
|
+
colors, or `imageGameIcon({ light, dark })` for a light and dark pair of pictures), its seat 2
|
|
108
|
+
choices (`seatTwo`: `'cpu'`, `'person'`, `'nobody'`) and how 2 Player seats its players
|
|
109
|
+
(`twoPlayerSeats`), the store `reducers` it mounts, its root `Provider`, its `useLoadout(mode)`, its
|
|
110
|
+
`PlayScreen`, its `StatsSections` and `AchievementList`, `resetStats`, and its How to Play `guide`.
|
|
111
|
+
See `src/types.ts` for every field.
|
|
112
|
+
|
|
113
|
+
What a game can leave out or add, each optional, so a two-seat game sets none of them:
|
|
114
|
+
|
|
115
|
+
| Field | |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| no `seatTwo` (nor `twoPlayerSeats`) | A game for one player: its loadout has no seat 2 button, every round is solo (`useActiveRound` falls back to `'solo'`), and its How to Play describes a solo round. |
|
|
118
|
+
| no `useLoadout` | Nothing to set up first: Home opens its play screen straight away, after its How to Play the first time, and a link to its loadout plays it. Its rule variants go in its play screen's Settings: `<SettingsDialog>` takes `children`. |
|
|
119
|
+
| no `cpuDifficulty` (in `GameLoadout`) | A CPU with no levels: seat 2's menu offers one plain CPU. |
|
|
120
|
+
| `resumable` | A cold launch drops the player back into its play screen if they left the app there rather than backing out of it. The shell remembers only which game (`lastActiveGame`); the game saves and restores its own board. |
|
|
121
|
+
| `Preview` | Drawn on Home in place of the icon (`GamePreviewProps`: `width`, `height`, the seats' colors, `dark`), in a box of `PREVIEW_ASPECT` (360:200), a frame after Home first paints. While any game has one, Home shows tiles: rows upright, and on a wide screen a grid beside the hero and seat 1's button. A game without one keeps its icon. |
|
|
122
|
+
|
|
123
|
+
A game never imports another game, and the shell never imports a game: games reach the shell only
|
|
124
|
+
through the pack (`usePack()`).
|
|
125
|
+
|
|
126
|
+
## Peer dependencies
|
|
127
|
+
|
|
128
|
+
None are bundled. The floors are the versions PuckTastic and DuelTastic ran the shell on when it
|
|
129
|
+
became a package (the lower of the two where they differ).
|
|
130
|
+
|
|
131
|
+
| Peer | Floor | Used for |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| `react`, `react-native` | >=19.3.0, >=0.88.0 | everything |
|
|
134
|
+
| `expo-router` | >=58.0.26 | the stack, the routes, `safeBack`, focus effects, the web tab title |
|
|
135
|
+
| `expo-font`, `@expo/vector-icons` | >=58.0.10, >=15.1.1 | the icon font and the pack's `fonts`, behind the splash screen |
|
|
136
|
+
| `expo-splash-screen`, `@rific/splash-gate` | >=58.0.11, >=0.2.3 | holding the splash screen until the theme, fonts and profiles are ready |
|
|
137
|
+
| `expo-constants` | >=58.0.13 | the web tab title |
|
|
138
|
+
| `expo-blur`, `@react-native-masked-view/masked-view` | >=58.0.5, >=0.3.2 | auto-paper's blur and its scroll fade |
|
|
139
|
+
| `expo-haptics`, `expo-keep-awake`, `expo-sensors` | >=58.0.7, >=58.0.3, >=58.0.4 | the toaster's haptics, Keep Awake, the orientation reading |
|
|
140
|
+
| `react-native-gesture-handler`, `react-native-reanimated`, `react-native-safe-area-context` | >=3.3.0, >=4.7.1, >=5.10.1 | the gesture root, auto-paper's dialogs and the loadout's fades, insets |
|
|
141
|
+
| `react-native-paper` | >=5.15.3 | text, icons, the toaster and feedback-press |
|
|
142
|
+
| `@reduxjs/toolkit`, `redux`, `react-redux`, `redux-persist`, `@react-native-async-storage/async-storage` | >=2.13.0, >=5.0.1, >=9.3.0, >=6.0.0, >=2.2.0 | the store and where it's saved |
|
|
143
|
+
| `@rific/auto-paper` | >=0.13.4 | the theme |
|
|
144
|
+
| `@rific/core` | >=0.5.0 | settings slices, Keep Awake, `safeBack` |
|
|
145
|
+
| `@rific/feedback-press` | >=0.13.1 | haptic and sound settings, the UI sounds |
|
|
146
|
+
| `@rific/toaster` | >=0.10.1 | toasts (DuelTastic runs 0.12, PuckTastic 0.10) |
|
|
147
|
+
| `@tastic/core` | >=0.14.0 | orientation, rotation, the status bar and root background |
|
|
148
|
+
| `@tastic/hud` | >=0.25.1 | Settings, the stats screen, the loadout's panels, How to Play |
|
|
149
|
+
| `@tastic/profile` | >=0.17.0 | profiles, the seat buttons, seat colors |
|
|
150
|
+
| `@tastic/achievements` | >=0.6.0 | `createGameStats`, `forgetProfileAchievements` |
|
|
151
|
+
| `@tastic/split-screen` | >=0.5.5 | the 2 Player loadout's two halves |
|
|
152
|
+
| `@tastic/edge-guard` | >=0.1.5 | Edge Guard during play |
|
|
153
|
+
|
|
154
|
+
The two UI sounds every button plays (`assets/sounds/select.wav` and `notification.wav`) ship with the
|
|
155
|
+
package.
|
|
Binary file
|
|
Binary file
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentType, ReactNode } from 'react';
|
|
3
|
+
import * as _rific_auto_paper from '@rific/auto-paper';
|
|
4
|
+
import { ThemeAppearance } from '@rific/auto-paper';
|
|
5
|
+
import { ViewRotation } from '@tastic/core';
|
|
6
|
+
import { PopoverHost, MenuOption } from '@tastic/hud';
|
|
7
|
+
import { GuideStep } from '@tastic/hud/guide';
|
|
8
|
+
import { FontSource } from 'expo-font';
|
|
9
|
+
import * as redux from 'redux';
|
|
10
|
+
import { Reducer, Action } from 'redux';
|
|
11
|
+
import * as _rific_core from '@rific/core';
|
|
12
|
+
export { safeBack } from '@rific/core';
|
|
13
|
+
import * as _tastic_profile from '@tastic/profile';
|
|
14
|
+
import { profilesReducer, Profile, CreateProfileInput, SeatColorsState as SeatColorsState$1 } from '@tastic/profile';
|
|
15
|
+
import * as _rific_feedback_press from '@rific/feedback-press';
|
|
16
|
+
import { SoundSettings } from '@rific/feedback-press';
|
|
17
|
+
import * as redux_persist from 'redux-persist';
|
|
18
|
+
import { PersistedState, Persistor } from 'redux-persist';
|
|
19
|
+
import * as _reduxjs_toolkit from '@reduxjs/toolkit';
|
|
20
|
+
import * as redux_thunk from 'redux-thunk';
|
|
21
|
+
import * as redux_persist_es_persistReducer from 'redux-persist/es/persistReducer';
|
|
22
|
+
import { ImageSourcePropType } from 'react-native';
|
|
23
|
+
import * as _tastic_achievements from '@tastic/achievements';
|
|
24
|
+
import { UnlockedAchievementsState, AchievementDefinition, AchievementsState } from '@tastic/achievements';
|
|
25
|
+
|
|
26
|
+
interface ActiveRound {
|
|
27
|
+
game: string;
|
|
28
|
+
mode: RoundMode;
|
|
29
|
+
settings: object;
|
|
30
|
+
}
|
|
31
|
+
type LiveplaySliceState = {
|
|
32
|
+
activelyPlaying: boolean;
|
|
33
|
+
activeRound: ActiveRound | null;
|
|
34
|
+
};
|
|
35
|
+
declare const reducer$3: _rific_core.SettingsReducer<LiveplaySliceState>;
|
|
36
|
+
declare const liveplayActions: {
|
|
37
|
+
initialize: {
|
|
38
|
+
(payload: LiveplaySliceState): _rific_core.SettingsAction<LiveplaySliceState>;
|
|
39
|
+
type: string;
|
|
40
|
+
match: (action: {
|
|
41
|
+
type: string;
|
|
42
|
+
}) => action is _rific_core.SettingsAction<LiveplaySliceState>;
|
|
43
|
+
};
|
|
44
|
+
} & {
|
|
45
|
+
setActivelyPlaying: {
|
|
46
|
+
(payload: boolean): _rific_core.SettingsAction<boolean>;
|
|
47
|
+
type: string;
|
|
48
|
+
match: (action: {
|
|
49
|
+
type: string;
|
|
50
|
+
}) => action is _rific_core.SettingsAction<boolean>;
|
|
51
|
+
};
|
|
52
|
+
setActiveRound: {
|
|
53
|
+
(payload: ActiveRound | null): _rific_core.SettingsAction<ActiveRound | null>;
|
|
54
|
+
type: string;
|
|
55
|
+
match: (action: {
|
|
56
|
+
type: string;
|
|
57
|
+
}) => action is _rific_core.SettingsAction<ActiveRound | null>;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
interface OpponentState {
|
|
62
|
+
seatTwo: SeatTwo;
|
|
63
|
+
}
|
|
64
|
+
declare const DEFAULT_OPPONENT: OpponentState;
|
|
65
|
+
declare const reducer$2: _rific_core.SettingsReducer<OpponentState>;
|
|
66
|
+
declare const opponentActions: {
|
|
67
|
+
initialize: {
|
|
68
|
+
(payload: OpponentState): _rific_core.SettingsAction<OpponentState>;
|
|
69
|
+
type: string;
|
|
70
|
+
match: (action: {
|
|
71
|
+
type: string;
|
|
72
|
+
}) => action is _rific_core.SettingsAction<OpponentState>;
|
|
73
|
+
};
|
|
74
|
+
} & {
|
|
75
|
+
setSeatTwo: {
|
|
76
|
+
(payload: SeatTwo): _rific_core.SettingsAction<SeatTwo>;
|
|
77
|
+
type: string;
|
|
78
|
+
match: (action: {
|
|
79
|
+
type: string;
|
|
80
|
+
}) => action is _rific_core.SettingsAction<SeatTwo>;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
interface AppSettings {
|
|
85
|
+
lockOrientation: boolean;
|
|
86
|
+
lockedRotation?: ViewRotation;
|
|
87
|
+
deferBottomEdgeGestures: boolean;
|
|
88
|
+
}
|
|
89
|
+
declare const DEFAULT_APP_SETTINGS: AppSettings;
|
|
90
|
+
declare const reducer$1: _rific_core.SettingsReducer<AppSettings>;
|
|
91
|
+
declare const appSettingsActions: {
|
|
92
|
+
initialize: {
|
|
93
|
+
(payload: Partial<AppSettings>): _rific_core.SettingsAction<Partial<AppSettings>>;
|
|
94
|
+
type: string;
|
|
95
|
+
match: (action: {
|
|
96
|
+
type: string;
|
|
97
|
+
}) => action is _rific_core.SettingsAction<Partial<AppSettings>>;
|
|
98
|
+
};
|
|
99
|
+
} & {};
|
|
100
|
+
|
|
101
|
+
type LastActiveGameState = {
|
|
102
|
+
gameId: string | null;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
declare function createShellReducers({ seatColors, appearance }: Pick<PackDefinition, 'seatColors' | 'appearance'>): {
|
|
106
|
+
theme: _rific_core.SettingsReducer<_rific_auto_paper.ThemeState>;
|
|
107
|
+
haptic: _rific_core.SettingsReducer<_rific_feedback_press.HapticSettings>;
|
|
108
|
+
sound: (state: SoundSettings | undefined, action: {
|
|
109
|
+
type: string;
|
|
110
|
+
}) => SoundSettings;
|
|
111
|
+
keepAwake: _rific_core.SettingsReducer<_rific_core.KeepAwakeSettings>;
|
|
112
|
+
profiles: typeof profilesReducer;
|
|
113
|
+
profileSelection: (state: _tastic_profile.ProfileSelectionState<Seat> | undefined, action: {
|
|
114
|
+
type: string;
|
|
115
|
+
payload?: unknown;
|
|
116
|
+
}) => _tastic_profile.ProfileSelectionState<Seat>;
|
|
117
|
+
seatColors: (state: _tastic_profile.SeatColorsState<Seat> | undefined, action: {
|
|
118
|
+
type: string;
|
|
119
|
+
payload?: unknown;
|
|
120
|
+
}) => _tastic_profile.SeatColorsState<Seat>;
|
|
121
|
+
appSettings: _rific_core.SettingsReducer<AppSettings>;
|
|
122
|
+
opponent: _rific_core.SettingsReducer<OpponentState>;
|
|
123
|
+
liveplay: _rific_core.SettingsReducer<LiveplaySliceState>;
|
|
124
|
+
};
|
|
125
|
+
type ShellReducers = ReturnType<typeof createShellReducers>;
|
|
126
|
+
type ShellState = {
|
|
127
|
+
[K in keyof ShellReducers]: ReturnType<ShellReducers[K]>;
|
|
128
|
+
} & {
|
|
129
|
+
lastActiveGame?: LastActiveGameState;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
type Seat = 1 | 2;
|
|
133
|
+
type SeatTwo = 'cpu' | 'person' | 'nobody';
|
|
134
|
+
type RoundMode = 'vsCpu' | 'twoPlayer' | 'solo';
|
|
135
|
+
declare function roundModeFor(seatTwo: SeatTwo): RoundMode;
|
|
136
|
+
declare function seatTwoFor(saved: SeatTwo, choices: readonly SeatTwo[] | undefined): SeatTwo;
|
|
137
|
+
declare function humanSeatsFor(mode: RoundMode): Seat[];
|
|
138
|
+
interface LoadoutOptionsProps {
|
|
139
|
+
host: PopoverHost;
|
|
140
|
+
rotation: ViewRotation;
|
|
141
|
+
accentColor: string;
|
|
142
|
+
mutedColor: string;
|
|
143
|
+
onAccentColor: string;
|
|
144
|
+
dark: boolean;
|
|
145
|
+
onRandomize?: () => void;
|
|
146
|
+
onReset?: () => void;
|
|
147
|
+
}
|
|
148
|
+
interface SeatControlLook {
|
|
149
|
+
accentColor: string;
|
|
150
|
+
seatColor: string;
|
|
151
|
+
mutedColor: string;
|
|
152
|
+
dark: boolean;
|
|
153
|
+
rotation: ViewRotation;
|
|
154
|
+
}
|
|
155
|
+
interface GameLoadout {
|
|
156
|
+
roundSettings: object;
|
|
157
|
+
options: (props: LoadoutOptionsProps) => ReactNode;
|
|
158
|
+
optionIds: readonly string[];
|
|
159
|
+
randomize: () => void;
|
|
160
|
+
reset: () => void;
|
|
161
|
+
seatControl: (seat: Seat, host: PopoverHost, look: SeatControlLook) => ReactNode | undefined;
|
|
162
|
+
cpuDifficulty?: {
|
|
163
|
+
value: string;
|
|
164
|
+
options: MenuOption<string>[];
|
|
165
|
+
onChange: (value: string) => void;
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
interface GameGuide {
|
|
169
|
+
version: number;
|
|
170
|
+
selectVersionSeen: (state: ShellState) => number;
|
|
171
|
+
markSeen: (version: number) => Action;
|
|
172
|
+
selectInput: (state: ShellState) => object;
|
|
173
|
+
getSteps: (input: object, context: {
|
|
174
|
+
touch: boolean;
|
|
175
|
+
mode: RoundMode;
|
|
176
|
+
}) => GuideStep[];
|
|
177
|
+
}
|
|
178
|
+
interface GameIconProps {
|
|
179
|
+
size: number;
|
|
180
|
+
p1Color: string;
|
|
181
|
+
p2Color: string;
|
|
182
|
+
dark: boolean;
|
|
183
|
+
}
|
|
184
|
+
interface GamePreviewProps {
|
|
185
|
+
width: number;
|
|
186
|
+
height: number;
|
|
187
|
+
p1Color: string;
|
|
188
|
+
p2Color: string;
|
|
189
|
+
dark: boolean;
|
|
190
|
+
}
|
|
191
|
+
interface GameDefinition {
|
|
192
|
+
id: string;
|
|
193
|
+
title: string;
|
|
194
|
+
blurb: string;
|
|
195
|
+
icon: ComponentType<GameIconProps>;
|
|
196
|
+
Preview?: ComponentType<GamePreviewProps>;
|
|
197
|
+
seatTwo?: readonly SeatTwo[];
|
|
198
|
+
twoPlayerSeats?: {
|
|
199
|
+
faceToFaceOnWeb: boolean;
|
|
200
|
+
followUpsideDown: boolean;
|
|
201
|
+
};
|
|
202
|
+
reducers: Record<string, Reducer>;
|
|
203
|
+
Provider: ComponentType<{
|
|
204
|
+
children: ReactNode;
|
|
205
|
+
}>;
|
|
206
|
+
useLoadout?: (mode: RoundMode) => GameLoadout;
|
|
207
|
+
PlayScreen: ComponentType;
|
|
208
|
+
resumable?: boolean;
|
|
209
|
+
StatsSections: ComponentType<{
|
|
210
|
+
profileId: string | null;
|
|
211
|
+
}>;
|
|
212
|
+
AchievementList: ComponentType<{
|
|
213
|
+
profileId: string | null;
|
|
214
|
+
}>;
|
|
215
|
+
resetStats: () => Action;
|
|
216
|
+
guide: GameGuide;
|
|
217
|
+
}
|
|
218
|
+
interface HeroTitleProps {
|
|
219
|
+
words: readonly [string, string];
|
|
220
|
+
color: string;
|
|
221
|
+
p1Color: string;
|
|
222
|
+
p2Color: string;
|
|
223
|
+
active: boolean;
|
|
224
|
+
fit?: {
|
|
225
|
+
width: number;
|
|
226
|
+
height: number;
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
interface PackStatsSectionsProps {
|
|
230
|
+
profileId: string | null;
|
|
231
|
+
gameId: string | null;
|
|
232
|
+
}
|
|
233
|
+
interface PackDefinition {
|
|
234
|
+
name: string;
|
|
235
|
+
wordmark: readonly [string, string];
|
|
236
|
+
colorCaption: string;
|
|
237
|
+
games: readonly GameDefinition[];
|
|
238
|
+
seatColors: Record<Seat, string>;
|
|
239
|
+
appearance?: ThemeAppearance;
|
|
240
|
+
HeroTitle: ComponentType<HeroTitleProps>;
|
|
241
|
+
fonts?: Record<string, FontSource>;
|
|
242
|
+
fontFamily?: string;
|
|
243
|
+
otaVersion: number;
|
|
244
|
+
SettingsSections?: ComponentType;
|
|
245
|
+
reducers?: Record<string, Reducer>;
|
|
246
|
+
StatsSections?: ComponentType<PackStatsSectionsProps>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
declare function PackProvider({ pack, children }: {
|
|
250
|
+
pack: PackDefinition;
|
|
251
|
+
children: ReactNode;
|
|
252
|
+
}): react.JSX.Element;
|
|
253
|
+
declare function usePack(): PackDefinition;
|
|
254
|
+
declare function usePackGame(id: string | undefined): GameDefinition | undefined;
|
|
255
|
+
|
|
256
|
+
interface PackStoreConfig {
|
|
257
|
+
keyPrefix: string;
|
|
258
|
+
migrate?: (state: PersistedState, version: number) => Promise<PersistedState>;
|
|
259
|
+
}
|
|
260
|
+
type PackStoreSource = Pick<PackDefinition, 'seatColors' | 'appearance' | 'reducers'> & {
|
|
261
|
+
games: readonly Pick<GameDefinition, 'reducers' | 'resumable'>[];
|
|
262
|
+
};
|
|
263
|
+
declare function createPackStore(pack: PackStoreSource, { keyPrefix, migrate }: PackStoreConfig): {
|
|
264
|
+
store: _reduxjs_toolkit.EnhancedStore<{
|
|
265
|
+
theme: _rific_auto_paper.ThemeState;
|
|
266
|
+
haptic: _rific_feedback_press.HapticSettings;
|
|
267
|
+
sound: _rific_feedback_press.SoundSettings;
|
|
268
|
+
keepAwake: _rific_core.KeepAwakeSettings;
|
|
269
|
+
profiles: _tastic_profile.Profile[];
|
|
270
|
+
profileSelection: _tastic_profile.ProfileSelectionState<Seat>;
|
|
271
|
+
seatColors: _tastic_profile.SeatColorsState<Seat>;
|
|
272
|
+
appSettings: AppSettings;
|
|
273
|
+
opponent: OpponentState;
|
|
274
|
+
liveplay: LiveplaySliceState;
|
|
275
|
+
} & redux_persist_es_persistReducer.PersistPartial, {
|
|
276
|
+
type: string;
|
|
277
|
+
payload?: unknown;
|
|
278
|
+
} | {
|
|
279
|
+
type: string;
|
|
280
|
+
payload?: unknown;
|
|
281
|
+
} | {
|
|
282
|
+
type: string;
|
|
283
|
+
} | {
|
|
284
|
+
type: string;
|
|
285
|
+
} | {
|
|
286
|
+
type: string;
|
|
287
|
+
}, _reduxjs_toolkit.Tuple<[redux.StoreEnhancer<{
|
|
288
|
+
dispatch: redux_thunk.ThunkDispatch<{
|
|
289
|
+
theme: _rific_auto_paper.ThemeState;
|
|
290
|
+
haptic: _rific_feedback_press.HapticSettings;
|
|
291
|
+
sound: _rific_feedback_press.SoundSettings;
|
|
292
|
+
keepAwake: _rific_core.KeepAwakeSettings;
|
|
293
|
+
profiles: _tastic_profile.Profile[];
|
|
294
|
+
profileSelection: _tastic_profile.ProfileSelectionState<Seat>;
|
|
295
|
+
seatColors: _tastic_profile.SeatColorsState<Seat>;
|
|
296
|
+
appSettings: AppSettings;
|
|
297
|
+
opponent: OpponentState;
|
|
298
|
+
liveplay: LiveplaySliceState;
|
|
299
|
+
} & redux_persist_es_persistReducer.PersistPartial, undefined, redux.UnknownAction>;
|
|
300
|
+
}>, redux.StoreEnhancer]>>;
|
|
301
|
+
persistor: redux_persist.Persistor;
|
|
302
|
+
};
|
|
303
|
+
type PackStore = ReturnType<typeof createPackStore>['store'];
|
|
304
|
+
|
|
305
|
+
interface RootLayoutProps {
|
|
306
|
+
pack: PackDefinition;
|
|
307
|
+
store: PackStore;
|
|
308
|
+
persistor: Persistor;
|
|
309
|
+
}
|
|
310
|
+
declare function RootLayout({ pack, store, persistor }: RootLayoutProps): react.JSX.Element;
|
|
311
|
+
|
|
312
|
+
declare function LoadoutRoute(): react.JSX.Element;
|
|
313
|
+
declare function PlayRoute(): react.JSX.Element;
|
|
314
|
+
|
|
315
|
+
declare function HomeScreen(): react.JSX.Element;
|
|
316
|
+
|
|
317
|
+
declare const PREVIEW_ASPECT: number;
|
|
318
|
+
|
|
319
|
+
declare function ProfilesRoute(): react.JSX.Element;
|
|
320
|
+
|
|
321
|
+
declare function StatsScreen(): react.JSX.Element;
|
|
322
|
+
|
|
323
|
+
interface GameGuideContextValue {
|
|
324
|
+
openFirstTime: (gameId: string, next: () => void) => void;
|
|
325
|
+
openFor: (gameId: string) => void;
|
|
326
|
+
}
|
|
327
|
+
declare function useGameGuide(): GameGuideContextValue;
|
|
328
|
+
|
|
329
|
+
declare function imageGameIcon(images: {
|
|
330
|
+
light: ImageSourcePropType;
|
|
331
|
+
dark: ImageSourcePropType;
|
|
332
|
+
}): ComponentType<GameIconProps>;
|
|
333
|
+
|
|
334
|
+
interface SettingsDialogProps {
|
|
335
|
+
visible: boolean;
|
|
336
|
+
onDismiss: () => void;
|
|
337
|
+
rotation?: ViewRotation;
|
|
338
|
+
onShowHowToPlay?: () => void;
|
|
339
|
+
children?: ReactNode;
|
|
340
|
+
}
|
|
341
|
+
declare function SettingsDialog({ visible, onDismiss, rotation, onShowHowToPlay, children }: SettingsDialogProps): react.JSX.Element;
|
|
342
|
+
|
|
343
|
+
declare function useActiveRound<S extends object>(game: string, defaults: S): {
|
|
344
|
+
mode: RoundMode;
|
|
345
|
+
settings: S;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
declare function useAppSettings(): {
|
|
349
|
+
settings: AppSettings;
|
|
350
|
+
setSettings: (patch: Partial<AppSettings>) => void;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
declare function useOpponent(): {
|
|
354
|
+
seatTwo: SeatTwo;
|
|
355
|
+
setSeatTwo: (seatTwo: SeatTwo) => void;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
interface ProfilesContextValue {
|
|
359
|
+
profiles: Profile[];
|
|
360
|
+
lastSelected: Record<Seat, string | null>;
|
|
361
|
+
loaded: boolean;
|
|
362
|
+
createProfile: (input: CreateProfileInput) => Profile;
|
|
363
|
+
updateProfile: (id: string, patch: Partial<CreateProfileInput>) => void;
|
|
364
|
+
deleteProfile: (id: string) => void;
|
|
365
|
+
selectProfile: (seat: Seat, profileId: string | null) => void;
|
|
366
|
+
}
|
|
367
|
+
interface Props {
|
|
368
|
+
children: ReactNode;
|
|
369
|
+
}
|
|
370
|
+
declare function ProfilesProvider({ children }: Props): react.JSX.Element;
|
|
371
|
+
declare function useProfiles(): ProfilesContextValue;
|
|
372
|
+
|
|
373
|
+
declare function useSeatColors(p2IsHuman: boolean): {
|
|
374
|
+
p1Color: string;
|
|
375
|
+
p2Color: string;
|
|
376
|
+
handleP1ColorChange: (hex: string) => void;
|
|
377
|
+
handleP2ColorChange: (hex: string) => void;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
interface GameGuideState {
|
|
381
|
+
versionSeen: number;
|
|
382
|
+
}
|
|
383
|
+
declare function createGameGuideSlice(mountKey: string): {
|
|
384
|
+
actions: {
|
|
385
|
+
markSeen: (version: number) => {
|
|
386
|
+
type: string;
|
|
387
|
+
payload: number;
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
reducer: (state: GameGuideState | undefined, action: Action & {
|
|
391
|
+
payload?: unknown;
|
|
392
|
+
}) => GameGuideState;
|
|
393
|
+
selectVersionSeen: (rootState: object) => number;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
declare function onProfileRemoved<S>(reducer: Reducer<S>, forget: (state: S, profileId: string) => S): Reducer<S>;
|
|
397
|
+
declare function forgetProfileAchievements<T, S extends {
|
|
398
|
+
stats: T;
|
|
399
|
+
unlocked: UnlockedAchievementsState;
|
|
400
|
+
}>(dropStats: (stats: T, profileId: string) => T): (state: S, profileId: string) => S;
|
|
401
|
+
|
|
402
|
+
type ProfileSelectionState = Record<Seat, string | null>;
|
|
403
|
+
declare const defaultProfileSelectionState: ProfileSelectionState;
|
|
404
|
+
declare const reducer: (state: _tastic_profile.ProfileSelectionState<Seat> | undefined, action: {
|
|
405
|
+
type: string;
|
|
406
|
+
payload?: unknown;
|
|
407
|
+
}) => _tastic_profile.ProfileSelectionState<Seat>;
|
|
408
|
+
declare const profileSelectionActions: _tastic_profile.ProfileSelectionActions<Seat>;
|
|
409
|
+
|
|
410
|
+
type SeatColorsState = SeatColorsState$1<Seat>;
|
|
411
|
+
declare function defaultSeatColorsState(seatColors: Record<Seat, string>): SeatColorsState;
|
|
412
|
+
declare function createSeatColorsReducer(seatColors: Record<Seat, string>): (state: SeatColorsState$1<Seat> | undefined, action: {
|
|
413
|
+
type: string;
|
|
414
|
+
payload?: unknown;
|
|
415
|
+
}) => SeatColorsState$1<Seat>;
|
|
416
|
+
declare const seatColorsActions: _tastic_profile.SeatColorsActions<Seat>;
|
|
417
|
+
|
|
418
|
+
interface RecordResult<TStats> {
|
|
419
|
+
device: AchievementDefinition<TStats>[];
|
|
420
|
+
profiles: Partial<Record<Seat, AchievementDefinition<TStats>[]>>;
|
|
421
|
+
}
|
|
422
|
+
interface GameStatsContextValue<TStats> {
|
|
423
|
+
stats: TStats;
|
|
424
|
+
unlockedAchievements: UnlockedAchievementsState;
|
|
425
|
+
record: (update: (prev: TStats) => TStats, profileIds?: Partial<Record<Seat, string>>) => RecordResult<TStats>;
|
|
426
|
+
resetAll: () => void;
|
|
427
|
+
}
|
|
428
|
+
interface GameStatsOptions<TStats, TBucket> {
|
|
429
|
+
key: string;
|
|
430
|
+
defaultStats: TStats;
|
|
431
|
+
isValidStats?: (value: unknown) => boolean;
|
|
432
|
+
catalog: AchievementDefinition<TStats>[];
|
|
433
|
+
getProfileStatsView: (bucket: TBucket) => TStats;
|
|
434
|
+
}
|
|
435
|
+
declare function createGameStats<TStats extends {
|
|
436
|
+
profiles: Record<string, TBucket>;
|
|
437
|
+
}, TBucket>({ key, defaultStats, isValidStats, catalog, getProfileStatsView }: GameStatsOptions<TStats, TBucket>): {
|
|
438
|
+
reducer: redux.Reducer<AchievementsState<TStats>>;
|
|
439
|
+
actions: _tastic_achievements.AchievementsActions<TStats>;
|
|
440
|
+
Provider: ({ children }: {
|
|
441
|
+
children: ReactNode;
|
|
442
|
+
}) => react.JSX.Element;
|
|
443
|
+
useStats: () => GameStatsContextValue<TStats>;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
declare function seatPlayerKey(selection: Record<Seat, string | null>, seat: Seat): string;
|
|
447
|
+
type WithControls<C> = {
|
|
448
|
+
controlScheme: Record<Seat, C>;
|
|
449
|
+
};
|
|
450
|
+
type PlayerSettingsState<S> = Record<string, Partial<Omit<S, 'controlScheme'>>>;
|
|
451
|
+
type PlayerControlsState<C> = Record<string, C>;
|
|
452
|
+
interface PlayerSettingsOptions<S extends WithControls<C>, C> {
|
|
453
|
+
id: string;
|
|
454
|
+
defaults: S;
|
|
455
|
+
distinctControls?: readonly C[];
|
|
456
|
+
}
|
|
457
|
+
declare function createPlayerSettings<S extends WithControls<C>, C>({ id, defaults, distinctControls }: PlayerSettingsOptions<S, C>): {
|
|
458
|
+
reducers: {
|
|
459
|
+
[x: string]: redux.Reducer<PlayerSettingsState<S>> | redux.Reducer<PlayerControlsState<C>>;
|
|
460
|
+
};
|
|
461
|
+
settingsReducer: redux.Reducer<PlayerSettingsState<S>>;
|
|
462
|
+
controlsReducer: redux.Reducer<PlayerControlsState<C>>;
|
|
463
|
+
actions: {
|
|
464
|
+
patch: (player: string, changes: Partial<Omit<S, "controlScheme">>) => {
|
|
465
|
+
type: string;
|
|
466
|
+
payload: {
|
|
467
|
+
player: string;
|
|
468
|
+
changes: Partial<Omit<S, "controlScheme">>;
|
|
469
|
+
};
|
|
470
|
+
};
|
|
471
|
+
setControl: (player: string, control: C) => {
|
|
472
|
+
type: string;
|
|
473
|
+
payload: {
|
|
474
|
+
player: string;
|
|
475
|
+
control: C;
|
|
476
|
+
};
|
|
477
|
+
};
|
|
478
|
+
};
|
|
479
|
+
compose: (saved: PlayerSettingsState<S> | undefined, controls: PlayerControlsState<C> | undefined, selection: Record<Seat, string | null>, seatTwoIsPerson: boolean) => S;
|
|
480
|
+
select: (state: ShellState) => S;
|
|
481
|
+
useSettings: () => {
|
|
482
|
+
settings: S;
|
|
483
|
+
setSettings: (patch: Partial<S>) => void;
|
|
484
|
+
};
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
export { type ActiveRound, type AppSettings, DEFAULT_APP_SETTINGS, DEFAULT_OPPONENT, type GameDefinition, type GameGuide, type GameGuideState, type GameIconProps, type GameLoadout, type GamePreviewProps, type HeroTitleProps, HomeScreen, type LiveplaySliceState, type LoadoutOptionsProps, LoadoutRoute, type OpponentState, PREVIEW_ASPECT, type PackDefinition, PackProvider, type PackStatsSectionsProps, type PackStore, type PackStoreConfig, type PackStoreSource, PlayRoute, type PlayerControlsState, type PlayerSettingsState, type ProfileSelectionState, ProfilesProvider, ProfilesRoute, type RecordResult, RootLayout, type RoundMode, type Seat, type SeatColorsState, type SeatControlLook, type SeatTwo, SettingsDialog, type SettingsDialogProps, type ShellState, StatsScreen, appSettingsActions, reducer$1 as appSettingsReducer, createGameGuideSlice, createGameStats, createPackStore, createPlayerSettings, createSeatColorsReducer, createShellReducers, defaultProfileSelectionState, defaultSeatColorsState, forgetProfileAchievements, humanSeatsFor, imageGameIcon, liveplayActions, reducer$3 as liveplayReducer, onProfileRemoved, opponentActions, reducer$2 as opponentReducer, profileSelectionActions, reducer as profileSelectionReducer, roundModeFor, seatColorsActions, seatPlayerKey, seatTwoFor, useActiveRound, useAppSettings, useGameGuide, useOpponent, usePack, usePackGame, useProfiles, useSeatColors };
|