@littlepartytime/sdk 2.2.0 → 2.2.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/GAME_DEV_GUIDE.md +95 -0
- package/dist/interfaces.d.ts +10 -0
- package/dist/interfaces.d.ts.map +1 -1
- package/package.json +1 -1
package/GAME_DEV_GUIDE.md
CHANGED
|
@@ -982,6 +982,101 @@ Renderer receives "stateUpdate" event with filtered state
|
|
|
982
982
|
React re-renders with new state
|
|
983
983
|
```
|
|
984
984
|
|
|
985
|
+
## Game Settlement Lifecycle
|
|
986
|
+
|
|
987
|
+
When `isGameOver()` returns `true`, the platform **immediately** takes over: it sends `game:result` to all clients, transitions to the settlement screen, and **unloads the game renderer**. This happens in the same tick as the final `game:state` broadcast:
|
|
988
|
+
|
|
989
|
+
```
|
|
990
|
+
engine.handleAction(state, playerId, action) → newState
|
|
991
|
+
broadcastPlayerViews(newState) ← last game frame sent
|
|
992
|
+
engine.isGameOver(newState) → true
|
|
993
|
+
handleGameEnd() ← platform takes over, renderer unloaded
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
If your game needs an in-game settlement screen (animations, rankings, replay, etc.), you **must not** let `isGameOver()` return `true` until that screen is done.
|
|
997
|
+
|
|
998
|
+
### Two-Phase Settlement Pattern
|
|
999
|
+
|
|
1000
|
+
Split game ending into two phases:
|
|
1001
|
+
|
|
1002
|
+
```
|
|
1003
|
+
playing → (winner decided) → settlement → (player confirms or timer expires) → finished
|
|
1004
|
+
↑ ↑
|
|
1005
|
+
isGameOver = false isGameOver = true
|
|
1006
|
+
game renderer shows platform takes over
|
|
1007
|
+
its own result screen
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
#### Engine Implementation
|
|
1011
|
+
|
|
1012
|
+
```typescript
|
|
1013
|
+
handleAction(state: GameState, playerId: string, action: GameAction): GameState {
|
|
1014
|
+
const data = state.data as MyGameData;
|
|
1015
|
+
|
|
1016
|
+
// 1. Normal gameplay — when winner is decided, enter settlement
|
|
1017
|
+
if (state.phase === "playing" && winnerDecided(data)) {
|
|
1018
|
+
return {
|
|
1019
|
+
...state,
|
|
1020
|
+
phase: "settlement",
|
|
1021
|
+
data: { ...data, rankings: computeRankings(data) },
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// 2. Settlement — wait for confirm action, then finish
|
|
1026
|
+
if (state.phase === "settlement" && action.type === "CONFIRM_RESULT") {
|
|
1027
|
+
return { ...state, phase: "finished" };
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return state;
|
|
1031
|
+
},
|
|
1032
|
+
|
|
1033
|
+
isGameOver(state: GameState): boolean {
|
|
1034
|
+
return state.phase === "finished"; // NOT "settlement"!
|
|
1035
|
+
},
|
|
1036
|
+
|
|
1037
|
+
getPlayerView(state: GameState, playerId: string): Partial<GameState> {
|
|
1038
|
+
if (state.phase === "settlement") {
|
|
1039
|
+
// Return rankings / stats for the in-game result screen
|
|
1040
|
+
return {
|
|
1041
|
+
...state,
|
|
1042
|
+
data: { rankings: (state.data as MyGameData).rankings },
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
// ...
|
|
1046
|
+
},
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
#### Renderer Implementation
|
|
1050
|
+
|
|
1051
|
+
```tsx
|
|
1052
|
+
export default function GameRenderer({ platform, state }: GameRendererProps) {
|
|
1053
|
+
if (state.phase === "settlement") {
|
|
1054
|
+
return (
|
|
1055
|
+
<div>
|
|
1056
|
+
{/* Show your in-game result screen here */}
|
|
1057
|
+
<Rankings data={state.data.rankings} />
|
|
1058
|
+
<button onClick={() => platform.send({ type: "CONFIRM_RESULT" })}>
|
|
1059
|
+
确认
|
|
1060
|
+
</button>
|
|
1061
|
+
</div>
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
// ... normal gameplay UI
|
|
1065
|
+
}
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
### Common Mistakes
|
|
1069
|
+
|
|
1070
|
+
| Mistake | Consequence |
|
|
1071
|
+
|---------|------------|
|
|
1072
|
+
| `isGameOver()` returns `true` in `settlement` phase | In-game result screen is skipped — platform unloads the renderer immediately |
|
|
1073
|
+
| No `settlement` phase; jumping straight from `playing` to `finished` | Players never see the in-game result animation / ranking |
|
|
1074
|
+
| Calling `platform.reportResult()` from the renderer | No-op — settlement is driven entirely by the server via `isGameOver` + `getResult` |
|
|
1075
|
+
|
|
1076
|
+
### One-Liner Rule
|
|
1077
|
+
|
|
1078
|
+
> **`isGameOver()` is the platform takeover switch. Do not return `true` until your in-game result screen is done.**
|
|
1079
|
+
|
|
985
1080
|
## Complete Example: Number Guessing Game
|
|
986
1081
|
|
|
987
1082
|
See the [`examples/number-guess`](../../examples/number-guess) directory for a complete working example.
|
package/dist/interfaces.d.ts
CHANGED
|
@@ -32,6 +32,16 @@ export interface GameConfig {
|
|
|
32
32
|
export interface GameEngine {
|
|
33
33
|
init(players: Player[], options?: Record<string, unknown>): GameState;
|
|
34
34
|
handleAction(state: GameState, playerId: string, action: GameAction): GameState;
|
|
35
|
+
/**
|
|
36
|
+
* Called after every handleAction. When this returns true, the platform
|
|
37
|
+
* immediately sends game:result, unloads the renderer, and shows the
|
|
38
|
+
* platform settlement screen.
|
|
39
|
+
*
|
|
40
|
+
* If your game has an in-game result screen (animations, rankings, etc.),
|
|
41
|
+
* keep returning false during that phase and only return true when the
|
|
42
|
+
* in-game result screen is done. See "Game Settlement Lifecycle" in
|
|
43
|
+
* GAME_DEV_GUIDE.md.
|
|
44
|
+
*/
|
|
35
45
|
isGameOver(state: GameState): boolean;
|
|
36
46
|
getResult(state: GameState): GameResult;
|
|
37
47
|
getPlayerView(state: GameState, playerId: string): Partial<GameState>;
|
package/dist/interfaces.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAChF,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC;IACxC,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CAC3B"}
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACtE,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAChF;;;;;;;;;OASG;IACH,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,CAAC;IACxC,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;CAC3B"}
|
package/package.json
CHANGED