@mnbroatch/boardgame.io 0.0.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 +102 -0
- package/ai/package.json +7 -0
- package/client/package.json +7 -0
- package/core/package.json +7 -0
- package/debug/package.json +7 -0
- package/dist/boardgameio.es.js +14238 -0
- package/dist/boardgameio.js +14277 -0
- package/dist/boardgameio.min.js +16 -0
- package/dist/cjs/Debug-9d141c06.js +9586 -0
- package/dist/cjs/ai-e0e8a768.js +377 -0
- package/dist/cjs/ai.js +20 -0
- package/dist/cjs/client-76dec77b.js +258 -0
- package/dist/cjs/client-a22d7500.js +524 -0
- package/dist/cjs/client.js +26 -0
- package/dist/cjs/core.js +52 -0
- package/dist/cjs/debug.js +18 -0
- package/dist/cjs/filter-player-view-bb02e2f6.js +89 -0
- package/dist/cjs/initialize-267fcd69.js +61 -0
- package/dist/cjs/internal.js +25 -0
- package/dist/cjs/master-2904879d.js +320 -0
- package/dist/cjs/master.js +18 -0
- package/dist/cjs/multiplayer.js +23 -0
- package/dist/cjs/plugin-random-7425844d.js +229 -0
- package/dist/cjs/plugins.js +59 -0
- package/dist/cjs/react-native.js +182 -0
- package/dist/cjs/react.js +727 -0
- package/dist/cjs/reducer-16eec232.js +1203 -0
- package/dist/cjs/server.js +4087 -0
- package/dist/cjs/socketio-7a0837eb.js +478 -0
- package/dist/cjs/testing.js +30 -0
- package/dist/cjs/transport-b1874dfa.js +37 -0
- package/dist/cjs/turn-order-b2ff8740.js +1136 -0
- package/dist/cjs/util-fcfd8fb8.js +140 -0
- package/dist/esm/Debug-0141fe2d.js +9577 -0
- package/dist/esm/ai-5c06e761.js +371 -0
- package/dist/esm/ai.js +8 -0
- package/dist/esm/client-2e653027.js +522 -0
- package/dist/esm/client-5f57c3f2.js +255 -0
- package/dist/esm/client.js +16 -0
- package/dist/esm/core.js +40 -0
- package/dist/esm/debug.js +10 -0
- package/dist/esm/filter-player-view-2c6cc96f.js +87 -0
- package/dist/esm/initialize-11d626ca.js +59 -0
- package/dist/esm/internal.js +10 -0
- package/dist/esm/master-fa8f2e43.js +318 -0
- package/dist/esm/master.js +10 -0
- package/dist/esm/multiplayer.js +14 -0
- package/dist/esm/plugin-random-087f861e.js +226 -0
- package/dist/esm/plugins.js +55 -0
- package/dist/esm/react-native.js +173 -0
- package/dist/esm/react.js +716 -0
- package/dist/esm/reducer-c46da7e5.js +1198 -0
- package/dist/esm/socketio-c22ffa65.js +455 -0
- package/dist/esm/testing.js +26 -0
- package/dist/esm/transport-ce07b771.js +35 -0
- package/dist/esm/turn-order-376d315e.js +1091 -0
- package/dist/esm/util-b6147cef.js +135 -0
- package/dist/types/packages/ai.d.ts +5 -0
- package/dist/types/packages/client.d.ts +3 -0
- package/dist/types/packages/core.d.ts +5 -0
- package/dist/types/packages/debug.d.ts +2 -0
- package/dist/types/packages/internal.d.ts +8 -0
- package/dist/types/packages/master.d.ts +2 -0
- package/dist/types/packages/multiplayer.d.ts +3 -0
- package/dist/types/packages/plugins.d.ts +3 -0
- package/dist/types/packages/react-native.d.ts +2 -0
- package/dist/types/packages/react.d.ts +3 -0
- package/dist/types/packages/server.d.ts +6 -0
- package/dist/types/packages/testing.d.ts +1 -0
- package/dist/types/src/ai/ai.d.ts +53 -0
- package/dist/types/src/ai/ai.test.d.ts +1 -0
- package/dist/types/src/ai/bot.d.ts +40 -0
- package/dist/types/src/ai/mcts-bot.d.ts +60 -0
- package/dist/types/src/ai/random-bot.d.ts +27 -0
- package/dist/types/src/client/client.d.ts +104 -0
- package/dist/types/src/client/client.test.d.ts +1 -0
- package/dist/types/src/client/debug/tests/debug.test.d.ts +1 -0
- package/dist/types/src/client/manager.d.ts +61 -0
- package/dist/types/src/client/react.d.ts +75 -0
- package/dist/types/src/client/react.ssr.test.d.ts +4 -0
- package/dist/types/src/client/react.test.d.ts +1 -0
- package/dist/types/src/client/transport/dummy.d.ts +18 -0
- package/dist/types/src/client/transport/local.d.ts +59 -0
- package/dist/types/src/client/transport/local.test.d.ts +1 -0
- package/dist/types/src/client/transport/socketio.d.ts +45 -0
- package/dist/types/src/client/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/client/transport/transport.d.ts +50 -0
- package/dist/types/src/client/transport/transport.test.d.ts +1 -0
- package/dist/types/src/core/action-creators.d.ts +144 -0
- package/dist/types/src/core/action-types.d.ts +10 -0
- package/dist/types/src/core/backwards-compatibility.d.ts +12 -0
- package/dist/types/src/core/constants.d.ts +6 -0
- package/dist/types/src/core/errors.d.ts +15 -0
- package/dist/types/src/core/flow.d.ts +28 -0
- package/dist/types/src/core/flow.test.d.ts +1 -0
- package/dist/types/src/core/game-methods.d.ts +9 -0
- package/dist/types/src/core/game.d.ts +26 -0
- package/dist/types/src/core/game.test.d.ts +1 -0
- package/dist/types/src/core/initialize.d.ts +9 -0
- package/dist/types/src/core/logger.d.ts +2 -0
- package/dist/types/src/core/player-view.d.ts +7 -0
- package/dist/types/src/core/player-view.test.d.ts +1 -0
- package/dist/types/src/core/reducer.d.ts +155 -0
- package/dist/types/src/core/reducer.test.d.ts +1 -0
- package/dist/types/src/core/turn-order.d.ts +179 -0
- package/dist/types/src/core/turn-order.test.d.ts +8 -0
- package/dist/types/src/lobby/client.d.ts +194 -0
- package/dist/types/src/lobby/client.test.d.ts +1 -0
- package/dist/types/src/lobby/connection.d.ts +44 -0
- package/dist/types/src/lobby/connection.test.d.ts +1 -0
- package/dist/types/src/lobby/create-match-form.d.ts +26 -0
- package/dist/types/src/lobby/login-form.d.ts +23 -0
- package/dist/types/src/lobby/match-instance.d.ts +31 -0
- package/dist/types/src/lobby/react.d.ts +113 -0
- package/dist/types/src/lobby/react.ssr.test.d.ts +4 -0
- package/dist/types/src/lobby/react.test.d.ts +1 -0
- package/dist/types/src/master/filter-player-view.d.ts +96 -0
- package/dist/types/src/master/filter-player-view.test.d.ts +1 -0
- package/dist/types/src/master/master.d.ts +94 -0
- package/dist/types/src/master/master.test.d.ts +1 -0
- package/dist/types/src/plugins/events/events.d.ts +54 -0
- package/dist/types/src/plugins/events/events.test.d.ts +1 -0
- package/dist/types/src/plugins/main.d.ts +75 -0
- package/dist/types/src/plugins/main.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-events.d.ts +5 -0
- package/dist/types/src/plugins/plugin-immer.d.ts +7 -0
- package/dist/types/src/plugins/plugin-immer.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-log.d.ts +14 -0
- package/dist/types/src/plugins/plugin-log.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-player.d.ts +29 -0
- package/dist/types/src/plugins/plugin-player.test.d.ts +1 -0
- package/dist/types/src/plugins/plugin-random.d.ts +4 -0
- package/dist/types/src/plugins/plugin-serializable.d.ts +7 -0
- package/dist/types/src/plugins/plugin-serializable.test.d.ts +1 -0
- package/dist/types/src/plugins/random/random.alea.d.ts +19 -0
- package/dist/types/src/plugins/random/random.d.ts +54 -0
- package/dist/types/src/plugins/random/random.test.d.ts +1 -0
- package/dist/types/src/server/api.d.ts +13 -0
- package/dist/types/src/server/api.test.d.ts +1 -0
- package/dist/types/src/server/auth.d.ts +38 -0
- package/dist/types/src/server/auth.test.d.ts +1 -0
- package/dist/types/src/server/cors.d.ts +4 -0
- package/dist/types/src/server/cors.test.d.ts +1 -0
- package/dist/types/src/server/db/base.d.ts +192 -0
- package/dist/types/src/server/db/flatfile.d.ts +44 -0
- package/dist/types/src/server/db/flatfile.test.d.ts +1 -0
- package/dist/types/src/server/db/index.d.ts +4 -0
- package/dist/types/src/server/db/index.test.d.ts +1 -0
- package/dist/types/src/server/db/inmemory.d.ts +43 -0
- package/dist/types/src/server/db/inmemory.test.d.ts +1 -0
- package/dist/types/src/server/db/localstorage.d.ts +7 -0
- package/dist/types/src/server/db/localstorage.test.d.ts +1 -0
- package/dist/types/src/server/index.d.ts +68 -0
- package/dist/types/src/server/index.test.d.ts +1 -0
- package/dist/types/src/server/transport/pubsub/generic-pub-sub.d.ts +6 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.d.ts +7 -0
- package/dist/types/src/server/transport/pubsub/in-memory-pub-sub.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio-simultaneous.test.d.ts +1 -0
- package/dist/types/src/server/transport/socketio.d.ts +65 -0
- package/dist/types/src/server/transport/socketio.test.d.ts +1 -0
- package/dist/types/src/server/util.d.ts +35 -0
- package/dist/types/src/testing/mock-random.d.ts +15 -0
- package/dist/types/src/testing/mock-random.test.d.ts +1 -0
- package/dist/types/src/types.d.ts +387 -0
- package/internal/package.json +7 -0
- package/master/package.json +7 -0
- package/multiplayer/package.json +7 -0
- package/package.json +211 -0
- package/plugins/package.json +7 -0
- package/react/package.json +7 -0
- package/react-native/package.json +7 -0
- package/server/package.json +6 -0
- package/src/ai/ai.test.ts +433 -0
- package/src/ai/ai.ts +84 -0
- package/src/ai/bot.ts +122 -0
- package/src/ai/mcts-bot.ts +331 -0
- package/src/ai/random-bot.ts +20 -0
- package/src/client/client.test.ts +993 -0
- package/src/client/client.ts +588 -0
- package/src/client/debug/Debug.svelte +239 -0
- package/src/client/debug/Menu.svelte +65 -0
- package/src/client/debug/ai/AI.svelte +215 -0
- package/src/client/debug/ai/Options.svelte +48 -0
- package/src/client/debug/info/Info.svelte +22 -0
- package/src/client/debug/info/Item.svelte +24 -0
- package/src/client/debug/log/Log.svelte +157 -0
- package/src/client/debug/log/LogEvent.svelte +149 -0
- package/src/client/debug/log/LogMetadata.svelte +7 -0
- package/src/client/debug/log/PhaseMarker.svelte +27 -0
- package/src/client/debug/log/TurnMarker.svelte +23 -0
- package/src/client/debug/main/ClientSwitcher.svelte +59 -0
- package/src/client/debug/main/Controls.svelte +58 -0
- package/src/client/debug/main/Hotkey.svelte +84 -0
- package/src/client/debug/main/InteractiveFunction.svelte +85 -0
- package/src/client/debug/main/Main.svelte +121 -0
- package/src/client/debug/main/Move.svelte +68 -0
- package/src/client/debug/main/PlayerInfo.svelte +70 -0
- package/src/client/debug/mcts/Action.svelte +22 -0
- package/src/client/debug/mcts/MCTS.svelte +78 -0
- package/src/client/debug/mcts/Table.svelte +98 -0
- package/src/client/debug/tests/JSONTree.mock.svelte +3 -0
- package/src/client/debug/tests/debug.test.ts +183 -0
- package/src/client/debug/utils/shortcuts.js +50 -0
- package/src/client/debug/utils/shortcuts.test.js +49 -0
- package/src/client/manager.ts +177 -0
- package/src/client/react-native.js +136 -0
- package/src/client/react-native.test.js +229 -0
- package/src/client/react.ssr.test.tsx +24 -0
- package/src/client/react.test.tsx +213 -0
- package/src/client/react.tsx +192 -0
- package/src/client/transport/dummy.ts +19 -0
- package/src/client/transport/local.test.ts +353 -0
- package/src/client/transport/local.ts +230 -0
- package/src/client/transport/socketio.test.ts +328 -0
- package/src/client/transport/socketio.ts +210 -0
- package/src/client/transport/transport.test.ts +27 -0
- package/src/client/transport/transport.ts +95 -0
- package/src/core/action-creators.ts +159 -0
- package/src/core/action-types.ts +18 -0
- package/src/core/backwards-compatibility.ts +23 -0
- package/src/core/constants.ts +6 -0
- package/src/core/errors.ts +35 -0
- package/src/core/flow.test.ts +2433 -0
- package/src/core/flow.ts +897 -0
- package/src/core/game-methods.ts +9 -0
- package/src/core/game.test.ts +286 -0
- package/src/core/game.ts +114 -0
- package/src/core/initialize.ts +77 -0
- package/src/core/logger.test.js +90 -0
- package/src/core/logger.ts +18 -0
- package/src/core/player-view.test.ts +50 -0
- package/src/core/player-view.ts +39 -0
- package/src/core/reducer.test.ts +991 -0
- package/src/core/reducer.ts +532 -0
- package/src/core/turn-order.test.ts +1123 -0
- package/src/core/turn-order.ts +473 -0
- package/src/lobby/client.test.ts +385 -0
- package/src/lobby/client.ts +358 -0
- package/src/lobby/connection.test.ts +207 -0
- package/src/lobby/connection.ts +162 -0
- package/src/lobby/create-match-form.tsx +122 -0
- package/src/lobby/login-form.tsx +75 -0
- package/src/lobby/match-instance.tsx +135 -0
- package/src/lobby/react.ssr.test.tsx +22 -0
- package/src/lobby/react.test.tsx +594 -0
- package/src/lobby/react.tsx +402 -0
- package/src/master/filter-player-view.test.ts +381 -0
- package/src/master/filter-player-view.ts +102 -0
- package/src/master/master.test.ts +1068 -0
- package/src/master/master.ts +492 -0
- package/src/plugins/events/events.test.ts +108 -0
- package/src/plugins/events/events.ts +209 -0
- package/src/plugins/main.test.ts +411 -0
- package/src/plugins/main.ts +314 -0
- package/src/plugins/plugin-events.ts +40 -0
- package/src/plugins/plugin-immer.test.ts +86 -0
- package/src/plugins/plugin-immer.ts +37 -0
- package/src/plugins/plugin-log.test.ts +37 -0
- package/src/plugins/plugin-log.ts +40 -0
- package/src/plugins/plugin-player.test.ts +172 -0
- package/src/plugins/plugin-player.ts +100 -0
- package/src/plugins/plugin-random.ts +40 -0
- package/src/plugins/plugin-serializable.test.ts +40 -0
- package/src/plugins/plugin-serializable.ts +55 -0
- package/src/plugins/random/random.alea.ts +109 -0
- package/src/plugins/random/random.test.ts +167 -0
- package/src/plugins/random/random.ts +198 -0
- package/src/server/api.test.ts +1699 -0
- package/src/server/api.ts +527 -0
- package/src/server/auth.test.ts +275 -0
- package/src/server/auth.ts +89 -0
- package/src/server/cors.test.ts +121 -0
- package/src/server/cors.ts +7 -0
- package/src/server/db/base.ts +296 -0
- package/src/server/db/flatfile.test.ts +221 -0
- package/src/server/db/flatfile.ts +228 -0
- package/src/server/db/index.test.ts +8 -0
- package/src/server/db/index.ts +12 -0
- package/src/server/db/inmemory.test.ts +143 -0
- package/src/server/db/inmemory.ts +143 -0
- package/src/server/db/localstorage.test.ts +73 -0
- package/src/server/db/localstorage.ts +44 -0
- package/src/server/index.test.ts +265 -0
- package/src/server/index.ts +175 -0
- package/src/server/transport/pubsub/generic-pub-sub.ts +11 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.test.ts +47 -0
- package/src/server/transport/pubsub/in-memory-pub-sub.ts +28 -0
- package/src/server/transport/socketio-simultaneous.test.ts +603 -0
- package/src/server/transport/socketio.test.ts +303 -0
- package/src/server/transport/socketio.ts +279 -0
- package/src/server/util.ts +85 -0
- package/src/testing/mock-random.test.ts +45 -0
- package/src/testing/mock-random.ts +27 -0
- package/src/types.ts +511 -0
- package/testing/package.json +7 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 The boardgame.io Authors
|
|
3
|
+
*
|
|
4
|
+
* Use of this source code is governed by a MIT-style
|
|
5
|
+
* license that can be found in the LICENSE file or at
|
|
6
|
+
* https://opensource.org/licenses/MIT.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as Actions from './action-types';
|
|
10
|
+
import * as plugins from '../plugins/main';
|
|
11
|
+
import { ProcessGameConfig } from './game';
|
|
12
|
+
import { error } from './logger';
|
|
13
|
+
import { INVALID_MOVE } from './constants';
|
|
14
|
+
import type { Dispatch } from 'redux';
|
|
15
|
+
import type {
|
|
16
|
+
ActionShape,
|
|
17
|
+
Ctx,
|
|
18
|
+
ErrorType,
|
|
19
|
+
Game,
|
|
20
|
+
LogEntry,
|
|
21
|
+
LongFormMove,
|
|
22
|
+
Move,
|
|
23
|
+
State,
|
|
24
|
+
Store,
|
|
25
|
+
TransientMetadata,
|
|
26
|
+
TransientState,
|
|
27
|
+
Undo,
|
|
28
|
+
} from '../types';
|
|
29
|
+
import { stripTransients } from './action-creators';
|
|
30
|
+
import { ActionErrorType, UpdateErrorType } from './errors';
|
|
31
|
+
import { applyPatch } from 'rfc6902';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if the payload for the passed action contains a playerID.
|
|
35
|
+
*/
|
|
36
|
+
const actionHasPlayerID = (
|
|
37
|
+
action:
|
|
38
|
+
| ActionShape.MakeMove
|
|
39
|
+
| ActionShape.GameEvent
|
|
40
|
+
| ActionShape.Undo
|
|
41
|
+
| ActionShape.Redo
|
|
42
|
+
) => action.payload.playerID !== null && action.payload.playerID !== undefined;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns true if a move can be undone.
|
|
46
|
+
*/
|
|
47
|
+
const CanUndoMove = (G: any, ctx: Ctx, move: Move): boolean => {
|
|
48
|
+
function HasUndoable(move: Move): move is LongFormMove {
|
|
49
|
+
return (move as LongFormMove).undoable !== undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function IsFunction(
|
|
53
|
+
undoable: boolean | ((...args: any[]) => any)
|
|
54
|
+
): undoable is (...args: any[]) => any {
|
|
55
|
+
return undoable instanceof Function;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!HasUndoable(move)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (IsFunction(move.undoable)) {
|
|
63
|
+
return move.undoable({ G, ctx });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return move.undoable;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Update the undo and redo stacks for a move or event.
|
|
71
|
+
*/
|
|
72
|
+
function updateUndoRedoState(
|
|
73
|
+
state: State,
|
|
74
|
+
opts: {
|
|
75
|
+
game: Game;
|
|
76
|
+
action: ActionShape.GameEvent | ActionShape.MakeMove;
|
|
77
|
+
}
|
|
78
|
+
): State {
|
|
79
|
+
if (opts.game.disableUndo) return state;
|
|
80
|
+
|
|
81
|
+
const undoEntry: Undo = {
|
|
82
|
+
G: state.G,
|
|
83
|
+
ctx: state.ctx,
|
|
84
|
+
plugins: state.plugins,
|
|
85
|
+
playerID: opts.action.payload.playerID || state.ctx.currentPlayer,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
if (opts.action.type === 'MAKE_MOVE') {
|
|
89
|
+
undoEntry.moveType = opts.action.payload.type;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
...state,
|
|
94
|
+
_undo: [...state._undo, undoEntry],
|
|
95
|
+
// Always reset redo stack when making a move or event
|
|
96
|
+
_redo: [],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Process state, adding the initial deltalog for this action.
|
|
102
|
+
*/
|
|
103
|
+
function initializeDeltalog(
|
|
104
|
+
state: State,
|
|
105
|
+
action: ActionShape.MakeMove | ActionShape.Undo | ActionShape.Redo,
|
|
106
|
+
move?: Move
|
|
107
|
+
): TransientState {
|
|
108
|
+
// Create a log entry for this action.
|
|
109
|
+
const logEntry: LogEntry = {
|
|
110
|
+
action,
|
|
111
|
+
_stateID: state._stateID,
|
|
112
|
+
turn: state.ctx.turn,
|
|
113
|
+
phase: state.ctx.phase,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const pluginLogMetadata = state.plugins.log.data.metadata;
|
|
117
|
+
if (pluginLogMetadata !== undefined) {
|
|
118
|
+
logEntry.metadata = pluginLogMetadata;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof move === 'object' && move.redact === true) {
|
|
122
|
+
logEntry.redact = true;
|
|
123
|
+
} else if (typeof move === 'object' && move.redact instanceof Function) {
|
|
124
|
+
logEntry.redact = move.redact({ G: state.G, ctx: state.ctx });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
...state,
|
|
129
|
+
deltalog: [logEntry],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update plugin state after move/event & check if plugins consider the action to be valid.
|
|
135
|
+
* @param state Current version of state in the reducer.
|
|
136
|
+
* @param oldState State to revert to in case of error.
|
|
137
|
+
* @param pluginOpts Plugin configuration options.
|
|
138
|
+
* @returns Tuple of the new state updated after flushing plugins and the old
|
|
139
|
+
* state augmented with an error if a plugin declared the action invalid.
|
|
140
|
+
*/
|
|
141
|
+
function flushAndValidatePlugins(
|
|
142
|
+
state: State,
|
|
143
|
+
oldState: State,
|
|
144
|
+
pluginOpts: { game: Game; isClient?: boolean }
|
|
145
|
+
): [State, TransientState?] {
|
|
146
|
+
const [newState, isInvalid] = plugins.FlushAndValidate(state, pluginOpts);
|
|
147
|
+
if (!isInvalid) return [newState];
|
|
148
|
+
return [
|
|
149
|
+
newState,
|
|
150
|
+
WithError(oldState, ActionErrorType.PluginActionInvalid, isInvalid),
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* ExtractTransientsFromState
|
|
156
|
+
*
|
|
157
|
+
* Split out transients from the a TransientState
|
|
158
|
+
*/
|
|
159
|
+
function ExtractTransients(
|
|
160
|
+
transientState: TransientState | null
|
|
161
|
+
): [State | null, TransientMetadata | undefined] {
|
|
162
|
+
if (!transientState) {
|
|
163
|
+
// We preserve null for the state for legacy callers, but the transient
|
|
164
|
+
// field should be undefined if not present to be consistent with the
|
|
165
|
+
// code path below.
|
|
166
|
+
return [null, undefined];
|
|
167
|
+
}
|
|
168
|
+
const { transients, ...state } = transientState;
|
|
169
|
+
return [state as State, transients as TransientMetadata];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* WithError
|
|
174
|
+
*
|
|
175
|
+
* Augment a State instance with transient error information.
|
|
176
|
+
*/
|
|
177
|
+
function WithError<PT extends any = any>(
|
|
178
|
+
state: State,
|
|
179
|
+
errorType: ErrorType,
|
|
180
|
+
payload?: PT
|
|
181
|
+
): TransientState {
|
|
182
|
+
const error = {
|
|
183
|
+
type: errorType,
|
|
184
|
+
payload,
|
|
185
|
+
};
|
|
186
|
+
return {
|
|
187
|
+
...state,
|
|
188
|
+
transients: {
|
|
189
|
+
error,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Middleware for processing TransientState associated with the reducer
|
|
196
|
+
* returned by CreateGameReducer.
|
|
197
|
+
* This should pretty much be used everywhere you want realistic state
|
|
198
|
+
* transitions and error handling.
|
|
199
|
+
*/
|
|
200
|
+
export const TransientHandlingMiddleware =
|
|
201
|
+
(store: Store) =>
|
|
202
|
+
(next: Dispatch<ActionShape.Any>) =>
|
|
203
|
+
(action: ActionShape.Any) => {
|
|
204
|
+
const result = next(action);
|
|
205
|
+
switch (action.type) {
|
|
206
|
+
case Actions.STRIP_TRANSIENTS: {
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
default: {
|
|
210
|
+
const [, transients] = ExtractTransients(store.getState());
|
|
211
|
+
if (typeof transients !== 'undefined') {
|
|
212
|
+
store.dispatch(stripTransients());
|
|
213
|
+
// Dev Note: If parent middleware needs to correlate the spawned
|
|
214
|
+
// StripTransients action to the triggering action, instrument here.
|
|
215
|
+
//
|
|
216
|
+
// This is a bit tricky; for more details, see:
|
|
217
|
+
// https://github.com/boardgameio/boardgame.io/pull/940#discussion_r636200648
|
|
218
|
+
return {
|
|
219
|
+
...result,
|
|
220
|
+
transients,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* CreateGameReducer
|
|
230
|
+
*
|
|
231
|
+
* Creates the main game state reducer.
|
|
232
|
+
*/
|
|
233
|
+
export function CreateGameReducer({
|
|
234
|
+
game,
|
|
235
|
+
isClient,
|
|
236
|
+
}: {
|
|
237
|
+
game: Game;
|
|
238
|
+
isClient?: boolean;
|
|
239
|
+
}) {
|
|
240
|
+
game = ProcessGameConfig(game);
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* GameReducer
|
|
244
|
+
*
|
|
245
|
+
* Redux reducer that maintains the overall game state.
|
|
246
|
+
* @param {object} state - The state before the action.
|
|
247
|
+
* @param {object} action - A Redux action.
|
|
248
|
+
*/
|
|
249
|
+
return (
|
|
250
|
+
stateWithTransients: TransientState | null = null,
|
|
251
|
+
action: ActionShape.Any
|
|
252
|
+
): TransientState => {
|
|
253
|
+
let [state /*, transients */] = ExtractTransients(stateWithTransients);
|
|
254
|
+
switch (action.type) {
|
|
255
|
+
case Actions.STRIP_TRANSIENTS: {
|
|
256
|
+
// This action indicates that transient metadata in the state has been
|
|
257
|
+
// consumed and should now be stripped from the state..
|
|
258
|
+
return state;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case Actions.GAME_EVENT: {
|
|
262
|
+
state = { ...state, deltalog: [] };
|
|
263
|
+
|
|
264
|
+
// Process game events only on the server.
|
|
265
|
+
// These events like `endTurn` typically
|
|
266
|
+
// contain code that may rely on secret state
|
|
267
|
+
// and cannot be computed on the client.
|
|
268
|
+
if (isClient) {
|
|
269
|
+
return state;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Disallow events once the game is over.
|
|
273
|
+
if (state.ctx.gameover !== undefined) {
|
|
274
|
+
error(`cannot call event after game end`);
|
|
275
|
+
return WithError(state, ActionErrorType.GameOver);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Ignore the event if the player isn't active.
|
|
279
|
+
if (
|
|
280
|
+
actionHasPlayerID(action) &&
|
|
281
|
+
!game.flow.isPlayerActive(state.G, state.ctx, action.payload.playerID)
|
|
282
|
+
) {
|
|
283
|
+
error(`disallowed event: ${action.payload.type}`);
|
|
284
|
+
return WithError(state, ActionErrorType.InactivePlayer);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Execute plugins.
|
|
288
|
+
state = plugins.Enhance(state, {
|
|
289
|
+
game,
|
|
290
|
+
isClient: false,
|
|
291
|
+
playerID: action.payload.playerID,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Process event.
|
|
295
|
+
let newState = game.flow.processEvent(state, action);
|
|
296
|
+
|
|
297
|
+
// Execute plugins.
|
|
298
|
+
let stateWithError: TransientState | undefined;
|
|
299
|
+
[newState, stateWithError] = flushAndValidatePlugins(newState, state, {
|
|
300
|
+
game,
|
|
301
|
+
isClient: false,
|
|
302
|
+
});
|
|
303
|
+
if (stateWithError) return stateWithError;
|
|
304
|
+
|
|
305
|
+
// Update undo / redo state.
|
|
306
|
+
newState = updateUndoRedoState(newState, { game, action });
|
|
307
|
+
|
|
308
|
+
return { ...newState, _stateID: state._stateID + 1 };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case Actions.MAKE_MOVE: {
|
|
312
|
+
const oldState = (state = { ...state, deltalog: [] });
|
|
313
|
+
|
|
314
|
+
// Check whether the move is allowed at this time.
|
|
315
|
+
const move: Move = game.flow.getMove(
|
|
316
|
+
state.ctx,
|
|
317
|
+
action.payload.type,
|
|
318
|
+
action.payload.playerID || state.ctx.currentPlayer
|
|
319
|
+
);
|
|
320
|
+
if (move === null) {
|
|
321
|
+
error(`disallowed move: ${action.payload.type}`);
|
|
322
|
+
return WithError(state, ActionErrorType.UnavailableMove);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Don't run move on client if move says so.
|
|
326
|
+
if (isClient && (move as LongFormMove).client === false) {
|
|
327
|
+
return state;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Disallow moves once the game is over.
|
|
331
|
+
if (state.ctx.gameover !== undefined) {
|
|
332
|
+
error(`cannot make move after game end`);
|
|
333
|
+
return WithError(state, ActionErrorType.GameOver);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Ignore the move if the player isn't active.
|
|
337
|
+
if (
|
|
338
|
+
actionHasPlayerID(action) &&
|
|
339
|
+
!game.flow.isPlayerActive(state.G, state.ctx, action.payload.playerID)
|
|
340
|
+
) {
|
|
341
|
+
error(`disallowed move: ${action.payload.type}`);
|
|
342
|
+
return WithError(state, ActionErrorType.InactivePlayer);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Execute plugins.
|
|
346
|
+
state = plugins.Enhance(state, {
|
|
347
|
+
game,
|
|
348
|
+
isClient,
|
|
349
|
+
playerID: action.payload.playerID,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Process the move.
|
|
353
|
+
const G = game.processMove(state, action.payload);
|
|
354
|
+
|
|
355
|
+
// The game declared the move as invalid.
|
|
356
|
+
if (G === INVALID_MOVE) {
|
|
357
|
+
error(
|
|
358
|
+
`invalid move: ${action.payload.type} args: ${action.payload.args}`
|
|
359
|
+
);
|
|
360
|
+
// TODO(#723): Marshal a nice error payload with the processed move.
|
|
361
|
+
return WithError(state, ActionErrorType.InvalidMove);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const newState = { ...state, G };
|
|
365
|
+
|
|
366
|
+
// Some plugin indicated that it is not suitable to be
|
|
367
|
+
// materialized on the client (and must wait for the server
|
|
368
|
+
// response instead).
|
|
369
|
+
if (isClient && plugins.NoClient(newState, { game })) {
|
|
370
|
+
return state;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
state = newState;
|
|
374
|
+
|
|
375
|
+
// If we're on the client, just process the move
|
|
376
|
+
// and no triggers in multiplayer mode.
|
|
377
|
+
// These will be processed on the server, which
|
|
378
|
+
// will send back a state update.
|
|
379
|
+
if (isClient) {
|
|
380
|
+
let stateWithError: TransientState | undefined;
|
|
381
|
+
[state, stateWithError] = flushAndValidatePlugins(state, oldState, {
|
|
382
|
+
game,
|
|
383
|
+
isClient: true,
|
|
384
|
+
});
|
|
385
|
+
if (stateWithError) return stateWithError;
|
|
386
|
+
return {
|
|
387
|
+
...state,
|
|
388
|
+
_stateID: state._stateID + 1,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// On the server, construct the deltalog.
|
|
393
|
+
state = initializeDeltalog(state, action, move);
|
|
394
|
+
|
|
395
|
+
// Allow the flow reducer to process any triggers that happen after moves.
|
|
396
|
+
state = game.flow.processMove(state, action.payload);
|
|
397
|
+
let stateWithError: TransientState | undefined;
|
|
398
|
+
[state, stateWithError] = flushAndValidatePlugins(state, oldState, {
|
|
399
|
+
game,
|
|
400
|
+
});
|
|
401
|
+
if (stateWithError) return stateWithError;
|
|
402
|
+
|
|
403
|
+
// Update undo / redo state.
|
|
404
|
+
state = updateUndoRedoState(state, { game, action });
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
...state,
|
|
408
|
+
_stateID: state._stateID + 1,
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
case Actions.RESET:
|
|
413
|
+
case Actions.UPDATE:
|
|
414
|
+
case Actions.SYNC: {
|
|
415
|
+
return action.state;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
case Actions.UNDO: {
|
|
419
|
+
state = { ...state, deltalog: [] };
|
|
420
|
+
|
|
421
|
+
if (game.disableUndo) {
|
|
422
|
+
error('Undo is not enabled');
|
|
423
|
+
return WithError(state, ActionErrorType.ActionDisabled);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const { G, ctx, _undo, _redo, _stateID } = state;
|
|
427
|
+
|
|
428
|
+
if (_undo.length < 2) {
|
|
429
|
+
error(`No moves to undo`);
|
|
430
|
+
return WithError(state, ActionErrorType.ActionInvalid);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const last = _undo[_undo.length - 1];
|
|
434
|
+
const restore = _undo[_undo.length - 2];
|
|
435
|
+
|
|
436
|
+
// Only allow players to undo their own moves.
|
|
437
|
+
if (
|
|
438
|
+
actionHasPlayerID(action) &&
|
|
439
|
+
action.payload.playerID !== last.playerID
|
|
440
|
+
) {
|
|
441
|
+
error(`Cannot undo other players' moves`);
|
|
442
|
+
return WithError(state, ActionErrorType.ActionInvalid);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// If undoing a move, check it is undoable.
|
|
446
|
+
if (last.moveType) {
|
|
447
|
+
const lastMove: Move = game.flow.getMove(
|
|
448
|
+
restore.ctx,
|
|
449
|
+
last.moveType,
|
|
450
|
+
last.playerID
|
|
451
|
+
);
|
|
452
|
+
if (!CanUndoMove(G, ctx, lastMove)) {
|
|
453
|
+
error(`Move cannot be undone`);
|
|
454
|
+
return WithError(state, ActionErrorType.ActionInvalid);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
state = initializeDeltalog(state, action);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
...state,
|
|
462
|
+
G: restore.G,
|
|
463
|
+
ctx: restore.ctx,
|
|
464
|
+
plugins: restore.plugins,
|
|
465
|
+
_stateID: _stateID + 1,
|
|
466
|
+
_undo: _undo.slice(0, -1),
|
|
467
|
+
_redo: [last, ..._redo],
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
case Actions.REDO: {
|
|
472
|
+
state = { ...state, deltalog: [] };
|
|
473
|
+
|
|
474
|
+
if (game.disableUndo) {
|
|
475
|
+
error('Redo is not enabled');
|
|
476
|
+
return WithError(state, ActionErrorType.ActionDisabled);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const { _undo, _redo, _stateID } = state;
|
|
480
|
+
|
|
481
|
+
if (_redo.length === 0) {
|
|
482
|
+
error(`No moves to redo`);
|
|
483
|
+
return WithError(state, ActionErrorType.ActionInvalid);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const first = _redo[0];
|
|
487
|
+
|
|
488
|
+
// Only allow players to redo their own undos.
|
|
489
|
+
if (
|
|
490
|
+
actionHasPlayerID(action) &&
|
|
491
|
+
action.payload.playerID !== first.playerID
|
|
492
|
+
) {
|
|
493
|
+
error(`Cannot redo other players' moves`);
|
|
494
|
+
return WithError(state, ActionErrorType.ActionInvalid);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
state = initializeDeltalog(state, action);
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
...state,
|
|
501
|
+
G: first.G,
|
|
502
|
+
ctx: first.ctx,
|
|
503
|
+
plugins: first.plugins,
|
|
504
|
+
_stateID: _stateID + 1,
|
|
505
|
+
_undo: [..._undo, first],
|
|
506
|
+
_redo: _redo.slice(1),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
case Actions.PLUGIN: {
|
|
511
|
+
// TODO(#723): Expose error semantics to plugin processing.
|
|
512
|
+
return plugins.ProcessAction(state, action, { game });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
case Actions.PATCH: {
|
|
516
|
+
const oldState = state;
|
|
517
|
+
const newState = JSON.parse(JSON.stringify(oldState));
|
|
518
|
+
const patchError = applyPatch(newState, action.patch);
|
|
519
|
+
const hasError = patchError.some((entry) => entry !== null);
|
|
520
|
+
if (hasError) {
|
|
521
|
+
error(`Patch ${JSON.stringify(action.patch)} apply failed`);
|
|
522
|
+
return WithError(oldState, UpdateErrorType.PatchFailed, patchError);
|
|
523
|
+
} else {
|
|
524
|
+
return newState;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
default: {
|
|
528
|
+
return state;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|