@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
package/src/core/flow.ts
ADDED
|
@@ -0,0 +1,897 @@
|
|
|
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 {
|
|
10
|
+
SetActivePlayers,
|
|
11
|
+
UpdateActivePlayersOnceEmpty,
|
|
12
|
+
InitTurnOrderState,
|
|
13
|
+
UpdateTurnOrderState,
|
|
14
|
+
Stage,
|
|
15
|
+
TurnOrder,
|
|
16
|
+
} from './turn-order';
|
|
17
|
+
import { gameEvent } from './action-creators';
|
|
18
|
+
import * as plugin from '../plugins/main';
|
|
19
|
+
import * as logging from './logger';
|
|
20
|
+
import type {
|
|
21
|
+
ActionPayload,
|
|
22
|
+
ActionShape,
|
|
23
|
+
ActivePlayersArg,
|
|
24
|
+
State,
|
|
25
|
+
Ctx,
|
|
26
|
+
FnContext,
|
|
27
|
+
LogEntry,
|
|
28
|
+
Game,
|
|
29
|
+
PhaseConfig,
|
|
30
|
+
PlayerID,
|
|
31
|
+
Move,
|
|
32
|
+
} from '../types';
|
|
33
|
+
import { GameMethod } from './game-methods';
|
|
34
|
+
import { supportDeprecatedMoveLimit } from './backwards-compatibility';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Flow
|
|
38
|
+
*
|
|
39
|
+
* Creates a reducer that updates ctx (analogous to how moves update G).
|
|
40
|
+
*/
|
|
41
|
+
export function Flow({
|
|
42
|
+
moves,
|
|
43
|
+
phases,
|
|
44
|
+
endIf,
|
|
45
|
+
onEnd,
|
|
46
|
+
turn,
|
|
47
|
+
events,
|
|
48
|
+
plugins,
|
|
49
|
+
}: Game) {
|
|
50
|
+
// Attach defaults.
|
|
51
|
+
if (moves === undefined) {
|
|
52
|
+
moves = {};
|
|
53
|
+
}
|
|
54
|
+
if (events === undefined) {
|
|
55
|
+
events = {};
|
|
56
|
+
}
|
|
57
|
+
if (plugins === undefined) {
|
|
58
|
+
plugins = [];
|
|
59
|
+
}
|
|
60
|
+
if (phases === undefined) {
|
|
61
|
+
phases = {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!endIf) endIf = () => undefined;
|
|
65
|
+
if (!onEnd) onEnd = ({ G }) => G;
|
|
66
|
+
if (!turn) turn = {};
|
|
67
|
+
|
|
68
|
+
const phaseMap = { ...phases };
|
|
69
|
+
|
|
70
|
+
if ('' in phaseMap) {
|
|
71
|
+
logging.error('cannot specify phase with empty name');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
phaseMap[''] = {};
|
|
75
|
+
|
|
76
|
+
const moveMap = {};
|
|
77
|
+
const moveNames = new Set();
|
|
78
|
+
let startingPhase = null;
|
|
79
|
+
|
|
80
|
+
Object.keys(moves).forEach((name) => moveNames.add(name));
|
|
81
|
+
|
|
82
|
+
const HookWrapper = (
|
|
83
|
+
hook: (context: FnContext) => any,
|
|
84
|
+
hookType: GameMethod
|
|
85
|
+
) => {
|
|
86
|
+
const withPlugins = plugin.FnWrap(hook, hookType, plugins);
|
|
87
|
+
return (state: State & { playerID?: PlayerID }) => {
|
|
88
|
+
const pluginAPIs = plugin.GetAPIs(state);
|
|
89
|
+
return withPlugins({
|
|
90
|
+
...pluginAPIs,
|
|
91
|
+
G: state.G,
|
|
92
|
+
ctx: state.ctx,
|
|
93
|
+
playerID: state.playerID,
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const TriggerWrapper = (trigger: (context: FnContext) => any) => {
|
|
99
|
+
return (state: State) => {
|
|
100
|
+
const pluginAPIs = plugin.GetAPIs(state);
|
|
101
|
+
return trigger({
|
|
102
|
+
...pluginAPIs,
|
|
103
|
+
G: state.G,
|
|
104
|
+
ctx: state.ctx,
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const wrapped = {
|
|
110
|
+
onEnd: HookWrapper(onEnd, GameMethod.GAME_ON_END),
|
|
111
|
+
endIf: TriggerWrapper(endIf),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
for (const phase in phaseMap) {
|
|
115
|
+
const phaseConfig = phaseMap[phase];
|
|
116
|
+
|
|
117
|
+
if (phaseConfig.start === true) {
|
|
118
|
+
startingPhase = phase;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (phaseConfig.moves !== undefined) {
|
|
122
|
+
for (const move of Object.keys(phaseConfig.moves)) {
|
|
123
|
+
moveMap[phase + '.' + move] = phaseConfig.moves[move];
|
|
124
|
+
moveNames.add(move);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (phaseConfig.endIf === undefined) {
|
|
129
|
+
phaseConfig.endIf = () => undefined;
|
|
130
|
+
}
|
|
131
|
+
if (phaseConfig.onBegin === undefined) {
|
|
132
|
+
phaseConfig.onBegin = ({ G }) => G;
|
|
133
|
+
}
|
|
134
|
+
if (phaseConfig.onEnd === undefined) {
|
|
135
|
+
phaseConfig.onEnd = ({ G }) => G;
|
|
136
|
+
}
|
|
137
|
+
if (phaseConfig.turn === undefined) {
|
|
138
|
+
phaseConfig.turn = turn;
|
|
139
|
+
}
|
|
140
|
+
if (phaseConfig.turn.order === undefined) {
|
|
141
|
+
phaseConfig.turn.order = TurnOrder.DEFAULT;
|
|
142
|
+
}
|
|
143
|
+
if (phaseConfig.turn.onBegin === undefined) {
|
|
144
|
+
phaseConfig.turn.onBegin = ({ G }) => G;
|
|
145
|
+
}
|
|
146
|
+
if (phaseConfig.turn.onEnd === undefined) {
|
|
147
|
+
phaseConfig.turn.onEnd = ({ G }) => G;
|
|
148
|
+
}
|
|
149
|
+
if (phaseConfig.turn.endIf === undefined) {
|
|
150
|
+
phaseConfig.turn.endIf = () => false;
|
|
151
|
+
}
|
|
152
|
+
if (phaseConfig.turn.onMove === undefined) {
|
|
153
|
+
phaseConfig.turn.onMove = ({ G }) => G;
|
|
154
|
+
}
|
|
155
|
+
if (phaseConfig.turn.stages === undefined) {
|
|
156
|
+
phaseConfig.turn.stages = {};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// turns previously treated moveLimit as both minMoves and maxMoves, this behaviour is kept intentionally
|
|
160
|
+
supportDeprecatedMoveLimit(phaseConfig.turn, true);
|
|
161
|
+
|
|
162
|
+
for (const stage in phaseConfig.turn.stages) {
|
|
163
|
+
const stageConfig = phaseConfig.turn.stages[stage];
|
|
164
|
+
const moves = stageConfig.moves || {};
|
|
165
|
+
for (const move of Object.keys(moves)) {
|
|
166
|
+
const key = phase + '.' + stage + '.' + move;
|
|
167
|
+
moveMap[key] = moves[move];
|
|
168
|
+
moveNames.add(move);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
phaseConfig.wrapped = {
|
|
173
|
+
onBegin: HookWrapper(phaseConfig.onBegin, GameMethod.PHASE_ON_BEGIN),
|
|
174
|
+
onEnd: HookWrapper(phaseConfig.onEnd, GameMethod.PHASE_ON_END),
|
|
175
|
+
endIf: TriggerWrapper(phaseConfig.endIf),
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
phaseConfig.turn.wrapped = {
|
|
179
|
+
onMove: HookWrapper(phaseConfig.turn.onMove, GameMethod.TURN_ON_MOVE),
|
|
180
|
+
onBegin: HookWrapper(phaseConfig.turn.onBegin, GameMethod.TURN_ON_BEGIN),
|
|
181
|
+
onEnd: HookWrapper(phaseConfig.turn.onEnd, GameMethod.TURN_ON_END),
|
|
182
|
+
endIf: TriggerWrapper(phaseConfig.turn.endIf),
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
if (typeof phaseConfig.next !== 'function') {
|
|
186
|
+
const { next } = phaseConfig;
|
|
187
|
+
phaseConfig.next = () => next || null;
|
|
188
|
+
}
|
|
189
|
+
phaseConfig.wrapped.next = TriggerWrapper(phaseConfig.next);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function GetPhase(ctx: { phase: string }): PhaseConfig {
|
|
193
|
+
return ctx.phase ? phaseMap[ctx.phase] : phaseMap[''];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function OnMove(state: State) {
|
|
197
|
+
return state;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function Process(
|
|
201
|
+
state: State,
|
|
202
|
+
events: {
|
|
203
|
+
fn: (state: State, opts: any) => State;
|
|
204
|
+
arg?: any;
|
|
205
|
+
turn?: Ctx['turn'];
|
|
206
|
+
phase?: Ctx['phase'];
|
|
207
|
+
automatic?: boolean;
|
|
208
|
+
playerID?: PlayerID;
|
|
209
|
+
force?: boolean;
|
|
210
|
+
}[]
|
|
211
|
+
): State {
|
|
212
|
+
const phasesEnded = new Set();
|
|
213
|
+
const turnsEnded = new Set();
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < events.length; i++) {
|
|
216
|
+
const { fn, arg, ...rest } = events[i];
|
|
217
|
+
|
|
218
|
+
// Detect a loop of EndPhase calls.
|
|
219
|
+
// This could potentially even be an infinite loop
|
|
220
|
+
// if the endIf condition of each phase blindly
|
|
221
|
+
// returns true. The moment we detect a single
|
|
222
|
+
// loop, we just bail out of all phases.
|
|
223
|
+
if (fn === EndPhase) {
|
|
224
|
+
turnsEnded.clear();
|
|
225
|
+
const phase = state.ctx.phase;
|
|
226
|
+
if (phasesEnded.has(phase)) {
|
|
227
|
+
const ctx = { ...state.ctx, phase: null };
|
|
228
|
+
return { ...state, ctx };
|
|
229
|
+
}
|
|
230
|
+
phasesEnded.add(phase);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Process event.
|
|
234
|
+
const next = [];
|
|
235
|
+
state = fn(state, {
|
|
236
|
+
...rest,
|
|
237
|
+
arg,
|
|
238
|
+
next,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (fn === EndGame) {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check if we should end the game.
|
|
246
|
+
const shouldEndGame = ShouldEndGame(state);
|
|
247
|
+
if (shouldEndGame) {
|
|
248
|
+
events.push({
|
|
249
|
+
fn: EndGame,
|
|
250
|
+
arg: shouldEndGame,
|
|
251
|
+
turn: state.ctx.turn,
|
|
252
|
+
phase: state.ctx.phase,
|
|
253
|
+
automatic: true,
|
|
254
|
+
});
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check if we should end the phase.
|
|
259
|
+
const shouldEndPhase = ShouldEndPhase(state);
|
|
260
|
+
if (shouldEndPhase) {
|
|
261
|
+
events.push({
|
|
262
|
+
fn: EndPhase,
|
|
263
|
+
arg: shouldEndPhase,
|
|
264
|
+
turn: state.ctx.turn,
|
|
265
|
+
phase: state.ctx.phase,
|
|
266
|
+
automatic: true,
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check if we should end the turn.
|
|
272
|
+
if ([OnMove, UpdateStage, UpdateActivePlayers].includes(fn)) {
|
|
273
|
+
const shouldEndTurn = ShouldEndTurn(state);
|
|
274
|
+
if (shouldEndTurn) {
|
|
275
|
+
events.push({
|
|
276
|
+
fn: EndTurn,
|
|
277
|
+
arg: shouldEndTurn,
|
|
278
|
+
turn: state.ctx.turn,
|
|
279
|
+
phase: state.ctx.phase,
|
|
280
|
+
automatic: true,
|
|
281
|
+
});
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
events.push(...next);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return state;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
///////////
|
|
293
|
+
// Start //
|
|
294
|
+
///////////
|
|
295
|
+
|
|
296
|
+
function StartGame(state: State, { next }): State {
|
|
297
|
+
next.push({ fn: StartPhase });
|
|
298
|
+
return state;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function StartPhase(state: State, { next }): State {
|
|
302
|
+
let { G, ctx } = state;
|
|
303
|
+
const phaseConfig = GetPhase(ctx);
|
|
304
|
+
|
|
305
|
+
// Run any phase setup code provided by the user.
|
|
306
|
+
G = phaseConfig.wrapped.onBegin(state);
|
|
307
|
+
|
|
308
|
+
next.push({ fn: StartTurn });
|
|
309
|
+
|
|
310
|
+
return { ...state, G, ctx };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function StartTurn(state: State, { currentPlayer }): State {
|
|
314
|
+
let { ctx } = state;
|
|
315
|
+
const phaseConfig = GetPhase(ctx);
|
|
316
|
+
|
|
317
|
+
// Initialize the turn order state.
|
|
318
|
+
if (currentPlayer) {
|
|
319
|
+
ctx = { ...ctx, currentPlayer };
|
|
320
|
+
if (phaseConfig.turn.activePlayers) {
|
|
321
|
+
ctx = SetActivePlayers(ctx, phaseConfig.turn.activePlayers);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
// This is only called at the beginning of the phase
|
|
325
|
+
// when there is no currentPlayer yet.
|
|
326
|
+
ctx = InitTurnOrderState(state, phaseConfig.turn);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const turn = ctx.turn + 1;
|
|
330
|
+
ctx = { ...ctx, turn, numMoves: 0, _prevActivePlayers: [] };
|
|
331
|
+
|
|
332
|
+
const G = phaseConfig.turn.wrapped.onBegin({ ...state, ctx });
|
|
333
|
+
|
|
334
|
+
return { ...state, G, ctx, _undo: [], _redo: [] } as State;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
////////////
|
|
338
|
+
// Update //
|
|
339
|
+
////////////
|
|
340
|
+
|
|
341
|
+
function UpdatePhase(state: State, { arg, next, phase }): State {
|
|
342
|
+
const phaseConfig = GetPhase({ phase });
|
|
343
|
+
let { ctx } = state;
|
|
344
|
+
|
|
345
|
+
if (arg && arg.next) {
|
|
346
|
+
if (arg.next in phaseMap) {
|
|
347
|
+
ctx = { ...ctx, phase: arg.next };
|
|
348
|
+
} else {
|
|
349
|
+
logging.error('invalid phase: ' + arg.next);
|
|
350
|
+
return state;
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
ctx = { ...ctx, phase: phaseConfig.wrapped.next(state) || null };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
state = { ...state, ctx };
|
|
357
|
+
|
|
358
|
+
// Start the new phase.
|
|
359
|
+
next.push({ fn: StartPhase });
|
|
360
|
+
|
|
361
|
+
return state;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function UpdateTurn(state: State, { arg, currentPlayer, next }): State {
|
|
365
|
+
let { G, ctx } = state;
|
|
366
|
+
const phaseConfig = GetPhase(ctx);
|
|
367
|
+
|
|
368
|
+
// Update turn order state.
|
|
369
|
+
const { endPhase, ctx: newCtx } = UpdateTurnOrderState(
|
|
370
|
+
state,
|
|
371
|
+
currentPlayer,
|
|
372
|
+
phaseConfig.turn,
|
|
373
|
+
arg
|
|
374
|
+
);
|
|
375
|
+
ctx = newCtx;
|
|
376
|
+
|
|
377
|
+
state = { ...state, G, ctx };
|
|
378
|
+
|
|
379
|
+
if (endPhase) {
|
|
380
|
+
next.push({ fn: EndPhase, turn: ctx.turn, phase: ctx.phase });
|
|
381
|
+
} else {
|
|
382
|
+
next.push({ fn: StartTurn, currentPlayer: ctx.currentPlayer });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return state;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function UpdateStage(state: State, { arg, playerID }): State {
|
|
389
|
+
if (typeof arg === 'string' || arg === Stage.NULL) {
|
|
390
|
+
arg = { stage: arg };
|
|
391
|
+
}
|
|
392
|
+
if (typeof arg !== 'object') return state;
|
|
393
|
+
|
|
394
|
+
// `arg` should be of type `StageArg`, loose typing as `any` here for historic reasons
|
|
395
|
+
// stages previously did not enforce minMoves, this behaviour is kept intentionally
|
|
396
|
+
supportDeprecatedMoveLimit(arg);
|
|
397
|
+
|
|
398
|
+
let { ctx } = state;
|
|
399
|
+
let {
|
|
400
|
+
activePlayers,
|
|
401
|
+
_activePlayersMinMoves,
|
|
402
|
+
_activePlayersMaxMoves,
|
|
403
|
+
_activePlayersNumMoves,
|
|
404
|
+
} = ctx;
|
|
405
|
+
|
|
406
|
+
// Checking if stage is valid, even Stage.NULL
|
|
407
|
+
if (arg.stage !== undefined) {
|
|
408
|
+
if (activePlayers === null) {
|
|
409
|
+
activePlayers = {};
|
|
410
|
+
}
|
|
411
|
+
activePlayers[playerID] = arg.stage;
|
|
412
|
+
_activePlayersNumMoves[playerID] = 0;
|
|
413
|
+
|
|
414
|
+
if (arg.minMoves) {
|
|
415
|
+
if (_activePlayersMinMoves === null) {
|
|
416
|
+
_activePlayersMinMoves = {};
|
|
417
|
+
}
|
|
418
|
+
_activePlayersMinMoves[playerID] = arg.minMoves;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (arg.maxMoves) {
|
|
422
|
+
if (_activePlayersMaxMoves === null) {
|
|
423
|
+
_activePlayersMaxMoves = {};
|
|
424
|
+
}
|
|
425
|
+
_activePlayersMaxMoves[playerID] = arg.maxMoves;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
ctx = {
|
|
430
|
+
...ctx,
|
|
431
|
+
activePlayers,
|
|
432
|
+
_activePlayersMinMoves,
|
|
433
|
+
_activePlayersMaxMoves,
|
|
434
|
+
_activePlayersNumMoves,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
return { ...state, ctx };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function UpdateActivePlayers(state: State, { arg }): State {
|
|
441
|
+
return { ...state, ctx: SetActivePlayers(state.ctx, arg) };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
///////////////
|
|
445
|
+
// ShouldEnd //
|
|
446
|
+
///////////////
|
|
447
|
+
|
|
448
|
+
function ShouldEndGame(state: State): boolean {
|
|
449
|
+
return wrapped.endIf(state);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function ShouldEndPhase(state: State): boolean | void | { next: string } {
|
|
453
|
+
const phaseConfig = GetPhase(state.ctx);
|
|
454
|
+
return phaseConfig.wrapped.endIf(state);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function ShouldEndTurn(state: State): boolean | void | { next: PlayerID } {
|
|
458
|
+
const phaseConfig = GetPhase(state.ctx);
|
|
459
|
+
|
|
460
|
+
// End the turn if the required number of moves has been made.
|
|
461
|
+
const currentPlayerMoves = state.ctx.numMoves || 0;
|
|
462
|
+
if (
|
|
463
|
+
phaseConfig.turn.maxMoves &&
|
|
464
|
+
currentPlayerMoves >= phaseConfig.turn.maxMoves
|
|
465
|
+
) {
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return phaseConfig.turn.wrapped.endIf(state);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/////////
|
|
473
|
+
// End //
|
|
474
|
+
/////////
|
|
475
|
+
|
|
476
|
+
function EndGame(state: State, { arg, phase }): State {
|
|
477
|
+
state = EndPhase(state, { phase });
|
|
478
|
+
|
|
479
|
+
if (arg === undefined) {
|
|
480
|
+
arg = true;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
state = { ...state, ctx: { ...state.ctx, gameover: arg } };
|
|
484
|
+
|
|
485
|
+
// Run game end hook.
|
|
486
|
+
const G = wrapped.onEnd(state);
|
|
487
|
+
|
|
488
|
+
return { ...state, G };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function EndPhase(
|
|
492
|
+
state: State,
|
|
493
|
+
{ arg, next, turn: initialTurn, automatic }: any
|
|
494
|
+
): State {
|
|
495
|
+
// End the turn first.
|
|
496
|
+
state = EndTurn(state, { turn: initialTurn, force: true, automatic: true });
|
|
497
|
+
|
|
498
|
+
const { phase, turn } = state.ctx;
|
|
499
|
+
|
|
500
|
+
if (next) {
|
|
501
|
+
next.push({ fn: UpdatePhase, arg, phase });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// If we aren't in a phase, there is nothing else to do.
|
|
505
|
+
if (phase === null) {
|
|
506
|
+
return state;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Run any cleanup code for the phase that is about to end.
|
|
510
|
+
const phaseConfig = GetPhase(state.ctx);
|
|
511
|
+
const G = phaseConfig.wrapped.onEnd(state);
|
|
512
|
+
|
|
513
|
+
// Reset the phase.
|
|
514
|
+
const ctx = { ...state.ctx, phase: null };
|
|
515
|
+
|
|
516
|
+
// Add log entry.
|
|
517
|
+
const action = gameEvent('endPhase', arg);
|
|
518
|
+
const { _stateID } = state;
|
|
519
|
+
const logEntry: LogEntry = { action, _stateID, turn, phase };
|
|
520
|
+
if (automatic) logEntry.automatic = true;
|
|
521
|
+
|
|
522
|
+
const deltalog = [...(state.deltalog || []), logEntry];
|
|
523
|
+
|
|
524
|
+
return { ...state, G, ctx, deltalog };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function EndTurn(
|
|
528
|
+
state: State,
|
|
529
|
+
{ arg, next, turn: initialTurn, force, automatic, playerID }: any
|
|
530
|
+
): State {
|
|
531
|
+
// This is not the turn that EndTurn was originally
|
|
532
|
+
// called for. The turn was probably ended some other way.
|
|
533
|
+
if (initialTurn !== state.ctx.turn) {
|
|
534
|
+
return state;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const { currentPlayer, numMoves, phase, turn } = state.ctx;
|
|
538
|
+
const phaseConfig = GetPhase(state.ctx);
|
|
539
|
+
|
|
540
|
+
// Prevent ending the turn if minMoves haven't been reached.
|
|
541
|
+
const currentPlayerMoves = numMoves || 0;
|
|
542
|
+
if (
|
|
543
|
+
!force &&
|
|
544
|
+
phaseConfig.turn.minMoves &&
|
|
545
|
+
currentPlayerMoves < phaseConfig.turn.minMoves
|
|
546
|
+
) {
|
|
547
|
+
logging.info(
|
|
548
|
+
`cannot end turn before making ${phaseConfig.turn.minMoves} moves`
|
|
549
|
+
);
|
|
550
|
+
return state;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Run turn-end triggers.
|
|
554
|
+
const G = phaseConfig.turn.wrapped.onEnd(state);
|
|
555
|
+
|
|
556
|
+
if (next) {
|
|
557
|
+
next.push({ fn: UpdateTurn, arg, currentPlayer });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Reset activePlayers.
|
|
561
|
+
let ctx = { ...state.ctx, activePlayers: null };
|
|
562
|
+
|
|
563
|
+
// Remove player from playerOrder
|
|
564
|
+
if (arg && arg.remove) {
|
|
565
|
+
playerID = playerID || currentPlayer;
|
|
566
|
+
|
|
567
|
+
const playOrder = ctx.playOrder.filter((i) => i != playerID);
|
|
568
|
+
|
|
569
|
+
const playOrderPos =
|
|
570
|
+
ctx.playOrderPos > playOrder.length - 1 ? 0 : ctx.playOrderPos;
|
|
571
|
+
|
|
572
|
+
ctx = { ...ctx, playOrder, playOrderPos };
|
|
573
|
+
|
|
574
|
+
if (playOrder.length === 0) {
|
|
575
|
+
next.push({ fn: EndPhase, turn, phase });
|
|
576
|
+
return state;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Create log entry.
|
|
581
|
+
const action = gameEvent('endTurn', arg);
|
|
582
|
+
const { _stateID } = state;
|
|
583
|
+
const logEntry: LogEntry = { action, _stateID, turn, phase };
|
|
584
|
+
if (automatic) logEntry.automatic = true;
|
|
585
|
+
|
|
586
|
+
const deltalog = [...(state.deltalog || []), logEntry];
|
|
587
|
+
|
|
588
|
+
return { ...state, G, ctx, deltalog, _undo: [], _redo: [] };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function EndStage(
|
|
592
|
+
state: State,
|
|
593
|
+
{ arg, next, automatic, playerID }: any
|
|
594
|
+
): State {
|
|
595
|
+
playerID = playerID || state.ctx.currentPlayer;
|
|
596
|
+
|
|
597
|
+
let { ctx, _stateID } = state;
|
|
598
|
+
let {
|
|
599
|
+
activePlayers,
|
|
600
|
+
_activePlayersNumMoves,
|
|
601
|
+
_activePlayersMinMoves,
|
|
602
|
+
_activePlayersMaxMoves,
|
|
603
|
+
phase,
|
|
604
|
+
turn,
|
|
605
|
+
} = ctx;
|
|
606
|
+
|
|
607
|
+
const playerInStage = activePlayers !== null && playerID in activePlayers;
|
|
608
|
+
|
|
609
|
+
const phaseConfig = GetPhase(ctx);
|
|
610
|
+
|
|
611
|
+
if (!arg && playerInStage) {
|
|
612
|
+
const stage = phaseConfig.turn.stages[activePlayers[playerID]];
|
|
613
|
+
if (stage && stage.next) {
|
|
614
|
+
arg = stage.next;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Checking if arg is a valid stage, even Stage.NULL
|
|
619
|
+
if (next) {
|
|
620
|
+
next.push({ fn: UpdateStage, arg, playerID });
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// If player isn’t in a stage, there is nothing else to do.
|
|
624
|
+
if (!playerInStage) return state;
|
|
625
|
+
|
|
626
|
+
// Prevent ending the stage if minMoves haven't been reached.
|
|
627
|
+
const currentPlayerMoves = _activePlayersNumMoves[playerID] || 0;
|
|
628
|
+
if (
|
|
629
|
+
_activePlayersMinMoves &&
|
|
630
|
+
_activePlayersMinMoves[playerID] &&
|
|
631
|
+
currentPlayerMoves < _activePlayersMinMoves[playerID]
|
|
632
|
+
) {
|
|
633
|
+
logging.info(
|
|
634
|
+
`cannot end stage before making ${_activePlayersMinMoves[playerID]} moves`
|
|
635
|
+
);
|
|
636
|
+
return state;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Remove player from activePlayers.
|
|
640
|
+
activePlayers = { ...activePlayers };
|
|
641
|
+
delete activePlayers[playerID];
|
|
642
|
+
|
|
643
|
+
if (_activePlayersMinMoves) {
|
|
644
|
+
// Remove player from _activePlayersMinMoves.
|
|
645
|
+
_activePlayersMinMoves = { ..._activePlayersMinMoves };
|
|
646
|
+
delete _activePlayersMinMoves[playerID];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (_activePlayersMaxMoves) {
|
|
650
|
+
// Remove player from _activePlayersMaxMoves.
|
|
651
|
+
_activePlayersMaxMoves = { ..._activePlayersMaxMoves };
|
|
652
|
+
delete _activePlayersMaxMoves[playerID];
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
ctx = UpdateActivePlayersOnceEmpty({
|
|
656
|
+
...ctx,
|
|
657
|
+
activePlayers,
|
|
658
|
+
_activePlayersMinMoves,
|
|
659
|
+
_activePlayersMaxMoves,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Create log entry.
|
|
663
|
+
const action = gameEvent('endStage', arg);
|
|
664
|
+
const logEntry: LogEntry = { action, _stateID, turn, phase };
|
|
665
|
+
if (automatic) logEntry.automatic = true;
|
|
666
|
+
|
|
667
|
+
const deltalog = [...(state.deltalog || []), logEntry];
|
|
668
|
+
|
|
669
|
+
return { ...state, ctx, deltalog };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Retrieves the relevant move that can be played by playerID.
|
|
674
|
+
*
|
|
675
|
+
* If ctx.activePlayers is set (i.e. one or more players are in some stage),
|
|
676
|
+
* then it attempts to find the move inside the stages config for
|
|
677
|
+
* that turn. If the stage for a player is '', then the player is
|
|
678
|
+
* allowed to make a move (as determined by the phase config), but
|
|
679
|
+
* isn't restricted to a particular set as defined in the stage config.
|
|
680
|
+
*
|
|
681
|
+
* If not, it then looks for the move inside the phase.
|
|
682
|
+
*
|
|
683
|
+
* If it doesn't find the move there, it looks at the global move definition.
|
|
684
|
+
*
|
|
685
|
+
* @param {object} ctx
|
|
686
|
+
* @param {string} name
|
|
687
|
+
* @param {string} playerID
|
|
688
|
+
*/
|
|
689
|
+
function GetMove(ctx: Ctx, name: string, playerID: PlayerID): null | Move {
|
|
690
|
+
const phaseConfig = GetPhase(ctx);
|
|
691
|
+
const stages = phaseConfig.turn.stages;
|
|
692
|
+
const { activePlayers } = ctx;
|
|
693
|
+
|
|
694
|
+
if (
|
|
695
|
+
activePlayers &&
|
|
696
|
+
activePlayers[playerID] !== undefined &&
|
|
697
|
+
activePlayers[playerID] !== Stage.NULL &&
|
|
698
|
+
stages[activePlayers[playerID]] !== undefined &&
|
|
699
|
+
stages[activePlayers[playerID]].moves !== undefined
|
|
700
|
+
) {
|
|
701
|
+
// Check if moves are defined for the player's stage.
|
|
702
|
+
const stage = stages[activePlayers[playerID]];
|
|
703
|
+
const moves = stage.moves;
|
|
704
|
+
if (name in moves) {
|
|
705
|
+
return moves[name];
|
|
706
|
+
}
|
|
707
|
+
} else if (phaseConfig.moves) {
|
|
708
|
+
// Check if moves are defined for the current phase.
|
|
709
|
+
if (name in phaseConfig.moves) {
|
|
710
|
+
return phaseConfig.moves[name];
|
|
711
|
+
}
|
|
712
|
+
} else if (name in moves) {
|
|
713
|
+
// Check for the move globally.
|
|
714
|
+
return moves[name];
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function ProcessMove(state: State, action: ActionPayload.MakeMove): State {
|
|
721
|
+
const { playerID, type } = action;
|
|
722
|
+
const { currentPlayer, activePlayers, _activePlayersMaxMoves } = state.ctx;
|
|
723
|
+
const move = GetMove(state.ctx, type, playerID);
|
|
724
|
+
const shouldCount =
|
|
725
|
+
!move || typeof move === 'function' || move.noLimit !== true;
|
|
726
|
+
|
|
727
|
+
let { numMoves, _activePlayersNumMoves } = state.ctx;
|
|
728
|
+
if (shouldCount) {
|
|
729
|
+
if (playerID === currentPlayer) numMoves++;
|
|
730
|
+
if (activePlayers) _activePlayersNumMoves[playerID]++;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
state = {
|
|
734
|
+
...state,
|
|
735
|
+
ctx: {
|
|
736
|
+
...state.ctx,
|
|
737
|
+
numMoves,
|
|
738
|
+
_activePlayersNumMoves,
|
|
739
|
+
},
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
if (
|
|
743
|
+
_activePlayersMaxMoves &&
|
|
744
|
+
_activePlayersNumMoves[playerID] >= _activePlayersMaxMoves[playerID]
|
|
745
|
+
) {
|
|
746
|
+
state = EndStage(state, { playerID, automatic: true });
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const phaseConfig = GetPhase(state.ctx);
|
|
750
|
+
const G = phaseConfig.turn.wrapped.onMove({ ...state, playerID });
|
|
751
|
+
state = { ...state, G };
|
|
752
|
+
|
|
753
|
+
const events = [{ fn: OnMove }];
|
|
754
|
+
|
|
755
|
+
return Process(state, events);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
function SetStageEvent(state: State, playerID: PlayerID, arg: any): State {
|
|
759
|
+
return Process(state, [{ fn: EndStage, arg, playerID }]);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function EndStageEvent(state: State, playerID: PlayerID): State {
|
|
763
|
+
return Process(state, [{ fn: EndStage, playerID }]);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function SetActivePlayersEvent(
|
|
767
|
+
state: State,
|
|
768
|
+
_playerID: PlayerID,
|
|
769
|
+
arg: ActivePlayersArg
|
|
770
|
+
): State {
|
|
771
|
+
return Process(state, [{ fn: UpdateActivePlayers, arg }]);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function SetPhaseEvent(
|
|
775
|
+
state: State,
|
|
776
|
+
_playerID: PlayerID,
|
|
777
|
+
newPhase: string
|
|
778
|
+
): State {
|
|
779
|
+
return Process(state, [
|
|
780
|
+
{
|
|
781
|
+
fn: EndPhase,
|
|
782
|
+
phase: state.ctx.phase,
|
|
783
|
+
turn: state.ctx.turn,
|
|
784
|
+
arg: { next: newPhase },
|
|
785
|
+
},
|
|
786
|
+
]);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function EndPhaseEvent(state: State): State {
|
|
790
|
+
return Process(state, [
|
|
791
|
+
{ fn: EndPhase, phase: state.ctx.phase, turn: state.ctx.turn },
|
|
792
|
+
]);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function EndTurnEvent(state: State, _playerID: PlayerID, arg: any): State {
|
|
796
|
+
return Process(state, [
|
|
797
|
+
{ fn: EndTurn, turn: state.ctx.turn, phase: state.ctx.phase, arg },
|
|
798
|
+
]);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function PassEvent(state: State, _playerID: PlayerID, arg: any): State {
|
|
802
|
+
return Process(state, [
|
|
803
|
+
{
|
|
804
|
+
fn: EndTurn,
|
|
805
|
+
turn: state.ctx.turn,
|
|
806
|
+
phase: state.ctx.phase,
|
|
807
|
+
force: true,
|
|
808
|
+
arg,
|
|
809
|
+
},
|
|
810
|
+
]);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function EndGameEvent(state: State, _playerID: PlayerID, arg: any): State {
|
|
814
|
+
return Process(state, [
|
|
815
|
+
{ fn: EndGame, turn: state.ctx.turn, phase: state.ctx.phase, arg },
|
|
816
|
+
]);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const eventHandlers = {
|
|
820
|
+
endStage: EndStageEvent,
|
|
821
|
+
setStage: SetStageEvent,
|
|
822
|
+
endTurn: EndTurnEvent,
|
|
823
|
+
pass: PassEvent,
|
|
824
|
+
endPhase: EndPhaseEvent,
|
|
825
|
+
setPhase: SetPhaseEvent,
|
|
826
|
+
endGame: EndGameEvent,
|
|
827
|
+
setActivePlayers: SetActivePlayersEvent,
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const enabledEventNames = [];
|
|
831
|
+
|
|
832
|
+
if (events.endTurn !== false) {
|
|
833
|
+
enabledEventNames.push('endTurn');
|
|
834
|
+
}
|
|
835
|
+
if (events.pass !== false) {
|
|
836
|
+
enabledEventNames.push('pass');
|
|
837
|
+
}
|
|
838
|
+
if (events.endPhase !== false) {
|
|
839
|
+
enabledEventNames.push('endPhase');
|
|
840
|
+
}
|
|
841
|
+
if (events.setPhase !== false) {
|
|
842
|
+
enabledEventNames.push('setPhase');
|
|
843
|
+
}
|
|
844
|
+
if (events.endGame !== false) {
|
|
845
|
+
enabledEventNames.push('endGame');
|
|
846
|
+
}
|
|
847
|
+
if (events.setActivePlayers !== false) {
|
|
848
|
+
enabledEventNames.push('setActivePlayers');
|
|
849
|
+
}
|
|
850
|
+
if (events.endStage !== false) {
|
|
851
|
+
enabledEventNames.push('endStage');
|
|
852
|
+
}
|
|
853
|
+
if (events.setStage !== false) {
|
|
854
|
+
enabledEventNames.push('setStage');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function ProcessEvent(state: State, action: ActionShape.GameEvent): State {
|
|
858
|
+
const { type, playerID, args } = action.payload;
|
|
859
|
+
if (typeof eventHandlers[type] !== 'function') return state;
|
|
860
|
+
return eventHandlers[type](
|
|
861
|
+
state,
|
|
862
|
+
playerID,
|
|
863
|
+
...(Array.isArray(args) ? args : [args])
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function IsPlayerActive(_G: any, ctx: Ctx, playerID: PlayerID): boolean {
|
|
868
|
+
if (ctx.activePlayers) {
|
|
869
|
+
return playerID in ctx.activePlayers;
|
|
870
|
+
}
|
|
871
|
+
return ctx.currentPlayer === playerID;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return {
|
|
875
|
+
ctx: (numPlayers: number): Ctx => ({
|
|
876
|
+
numPlayers,
|
|
877
|
+
turn: 0,
|
|
878
|
+
currentPlayer: '0',
|
|
879
|
+
playOrder: [...Array.from({ length: numPlayers })].map((_, i) => i + ''),
|
|
880
|
+
playOrderPos: 0,
|
|
881
|
+
phase: startingPhase,
|
|
882
|
+
activePlayers: null,
|
|
883
|
+
}),
|
|
884
|
+
init: (state: State): State => {
|
|
885
|
+
return Process(state, [{ fn: StartGame }]);
|
|
886
|
+
},
|
|
887
|
+
isPlayerActive: IsPlayerActive,
|
|
888
|
+
eventHandlers,
|
|
889
|
+
eventNames: Object.keys(eventHandlers),
|
|
890
|
+
enabledEventNames,
|
|
891
|
+
moveMap,
|
|
892
|
+
moveNames: [...moveNames.values()],
|
|
893
|
+
processMove: ProcessMove,
|
|
894
|
+
processEvent: ProcessEvent,
|
|
895
|
+
getMove: GetMove,
|
|
896
|
+
};
|
|
897
|
+
}
|