-
Notifications
You must be signed in to change notification settings - Fork 0
/
game.js
153 lines (142 loc) · 4.42 KB
/
game.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import produce from "immer";
import { JSON_delta } from "./vendor-json-delta";
import deepcopy from "deepcopy";
const context = Symbol("context");
class Game {
static setupFilters(state) {
const filters = this.getFilters(state);
let f;
if (typeof filters == "function") {
f = { default: filters };
this[context].filterMode = "single";
} else {
f = filters;
this[context].filterMode = "multi";
}
this[context].filters = {};
for (let filterKey in f) {
this[context].filters[filterKey] = produce(f[filterKey]);
}
}
static filter(state, filterKey) {
const filters = this.getFilters(state);
if (typeof filters == "function") {
return produce(state, filters);
} else {
return produce(state, filters[filterKey]);
}
}
static getFilters() {
// default implementation if not overridden
return () => {};
}
static playAction(state, action) {
if (this[context] !== undefined) {
throw new Error(
"Nested calls to playAction()/replayAction() are not supported"
);
}
let views = {},
newState,
oldContext;
try {
this[context] = { mode: "play", diffs: {} };
this.setupFilters(state);
for (let filterKey in this[context].filters) {
views[filterKey] = this[context].filters[filterKey](state);
this[context].diffs[filterKey] = [];
}
newState = produce(state, draft => {
return this.updateState(draft, action);
});
} finally {
oldContext = this[context];
this[context] = undefined;
}
if (process.env.NODE_ENV != "production") {
// development or test
for (let filterKey in oldContext.filters) {
const replayResult = this.replayAction(
views[filterKey],
action,
oldContext.diffs[filterKey]
);
const filteredNewState = oldContext.filters[filterKey](newState);
const diff = JSON_delta.diff(replayResult, filteredNewState);
if (diff.length != 0) {
const error = new Error(
"Result of replaying the action did not match the new state"
);
error.result = filteredNewState;
error.replay = replayResult;
error.diff = diff;
throw error;
}
}
}
if (oldContext.filterMode == "single") {
const newInfo = oldContext.diffs["default"];
return { state: newState, newInfo };
} else {
return { state: newState, newInfos: oldContext.diffs };
}
}
static replayAction(state, action, diffs) {
if (this[context] !== undefined) {
throw new Error(
"Nested calls to playAction()/replayAction() are not supported"
);
}
let result;
try {
this[context] = { mode: "replay", diffs, diffIndex: 0 };
result = produce(state, draft => {
return this.updateState(draft, action);
});
} finally {
this[context] = undefined;
}
return result;
}
static applyUpdate(state, transform) {
switch (this[context].mode) {
case "play":
this._playApplyUpdate(state, transform);
break;
case "replay":
this._replayApplyUpdate(state, transform);
break;
}
}
static _playApplyUpdate(draft, transform) {
// Have to clone the previous state here as Immer will try to help out with
// structural sharing which breaks because this is really a mutable draft
const original = deepcopy(draft);
const views = {};
for (let filterKey in this[context].filters) {
views[filterKey] = produce(original, this[context].filters[filterKey]);
}
transform(draft);
const updatedViews = {};
for (let filterKey in this[context].filters) {
updatedViews[filterKey] = produce(
draft,
this[context].filters[filterKey]
);
// We deepcopy the diff here to ensure that it contains references
// only to plain objects and not proxies
const diff = deepcopy(
JSON_delta.diff(views[filterKey], updatedViews[filterKey])
);
this[context].diffs[filterKey].push(diff);
}
}
static _replayApplyUpdate(state, _transform) {
// We also have to clone the diff we are applying, because otherwise
// modifying the state later on can also modify the original diff!
const diff = deepcopy(this[context].diffs[this[context].diffIndex]);
JSON_delta.patch(state, diff);
this[context].diffIndex++;
}
}
export default Game;