@thegraid/hexlib 1.0.0
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/dist/choosers.js +59 -0
- package/dist/counters.js +155 -0
- package/dist/game-play.js +455 -0
- package/dist/game-setup.js +280 -0
- package/dist/game-state.js +112 -0
- package/dist/hex-intfs.js +82 -0
- package/dist/hex.js +714 -0
- package/dist/image-loader.js +69 -0
- package/dist/meeple.js +152 -0
- package/dist/plan-proxy.js +33 -0
- package/dist/player-panel.js +125 -0
- package/dist/player.js +112 -0
- package/dist/scenario-parser.js +67 -0
- package/dist/shapes.js +304 -0
- package/dist/stream-writer.js +192 -0
- package/dist/table-params.js +48 -0
- package/dist/table.js +707 -0
- package/dist/text-log.js +53 -0
- package/dist/tile-source.js +115 -0
- package/dist/tile.js +312 -0
- package/dist/types.js +15 -0
- package/package.json +45 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { C, DropdownChoice, ParamGUI, blinkAndThen, makeStage, stime } from "@thegraid/easeljs-lib";
|
|
2
|
+
import { Container } from "@thegraid/easeljs-module";
|
|
3
|
+
import { parse as JSON5_parse } from 'json5';
|
|
4
|
+
import { EBC, PidChoice } from "./choosers";
|
|
5
|
+
import { GamePlay, NamedContainer } from "./game-play";
|
|
6
|
+
import { Meeple } from "./meeple";
|
|
7
|
+
import { Player } from "./player";
|
|
8
|
+
import { ScenarioParser } from "./scenario-parser";
|
|
9
|
+
import { RectShape } from "./shapes";
|
|
10
|
+
import { LogReader, LogWriter } from "./stream-writer";
|
|
11
|
+
import { Table } from "./table";
|
|
12
|
+
import { TP } from "./table-params";
|
|
13
|
+
import { Tile } from "./tile";
|
|
14
|
+
/** show " R" for " N" */
|
|
15
|
+
stime.anno = (obj) => {
|
|
16
|
+
let stage = (typeof obj !== 'string') ? (obj?.stage || obj?.table?.stage) : undefined;
|
|
17
|
+
return !!stage ? (!!stage.canvas ? " C" : " R") : " -";
|
|
18
|
+
};
|
|
19
|
+
;
|
|
20
|
+
class MultiChoice extends DropdownChoice {
|
|
21
|
+
// constructor(items: MultiItem[], item_w: number, item_h: number, style?: DropdownStyle) {
|
|
22
|
+
// super(items, item_w, item_h, style);
|
|
23
|
+
// }
|
|
24
|
+
select(item) {
|
|
25
|
+
this.changed(item);
|
|
26
|
+
return item;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** initialize & reset & startup the application/game. */
|
|
30
|
+
export class GameSetup {
|
|
31
|
+
qParams;
|
|
32
|
+
stage;
|
|
33
|
+
gamePlay;
|
|
34
|
+
paramGUIs;
|
|
35
|
+
netGUI; // paramGUIs[2]
|
|
36
|
+
/**
|
|
37
|
+
* ngAfterViewInit --> start here!
|
|
38
|
+
* @param canvasId supply undefined for 'headless' Stage
|
|
39
|
+
*/
|
|
40
|
+
constructor(canvasId, qParams = []) {
|
|
41
|
+
this.qParams = qParams;
|
|
42
|
+
stime.fmt = "MM-DD kk:mm:ss.SSSL";
|
|
43
|
+
this.stage = makeStage(canvasId, false);
|
|
44
|
+
this.stage.snapToPixel = TP.snapToPixel;
|
|
45
|
+
this.setupToParseState(); // restart when/if 'SetState' button is clicked
|
|
46
|
+
this.setupToReadFileState(); // restart when/if 'LoadFile' button is clicked
|
|
47
|
+
Tile.loader.loadImages(() => this.startup(qParams));
|
|
48
|
+
}
|
|
49
|
+
/** set from qParams['n'] */
|
|
50
|
+
nPlayers = 2;
|
|
51
|
+
makeNplayers(gamePlay) {
|
|
52
|
+
// Create and Inject all the Players:
|
|
53
|
+
const allPlayers = gamePlay.allPlayers;
|
|
54
|
+
allPlayers.length = 0;
|
|
55
|
+
for (let ndx = 0; ndx < this.nPlayers; ndx++) {
|
|
56
|
+
new Player(ndx, gamePlay); // make real Players...
|
|
57
|
+
}
|
|
58
|
+
gamePlay.curPlayerNdx = 0; // gamePlay.setNextPlayer(0); ???
|
|
59
|
+
gamePlay.curPlayer = allPlayers[gamePlay.curPlayerNdx];
|
|
60
|
+
}
|
|
61
|
+
_netState = " "; // or "yes" or "ref"
|
|
62
|
+
set netState(val) {
|
|
63
|
+
this._netState = (val == "cnx") ? this._netState : val || " ";
|
|
64
|
+
this.gamePlay.ll(2) && console.log(stime(this, `.netState('${val}')->'${this._netState}'`));
|
|
65
|
+
this.netGUI?.selectValue("Network", val);
|
|
66
|
+
}
|
|
67
|
+
get netState() { return this._netState; }
|
|
68
|
+
set playerId(val) { this.netGUI?.selectValue("PlayerId", val || " "); }
|
|
69
|
+
logTime_js;
|
|
70
|
+
logWriter = this.makeLogWriter();
|
|
71
|
+
makeLogWriter() {
|
|
72
|
+
const logTime_js = this.logTime_js = `log_${stime.fs('MM-DD_Lkk_mm')}.js`;
|
|
73
|
+
const logWriter = new LogWriter(logTime_js, '[\n', ']\n'); // terminate array, but insert before terminal
|
|
74
|
+
return logWriter;
|
|
75
|
+
}
|
|
76
|
+
restartable = false;
|
|
77
|
+
/** C-s ==> kill game, start a new one, possibly with new dbp */
|
|
78
|
+
restart(stateInfo) {
|
|
79
|
+
if (!this.restartable)
|
|
80
|
+
return;
|
|
81
|
+
let netState = this.netState;
|
|
82
|
+
// this.gamePlay.closeNetwork('restart')
|
|
83
|
+
// this.gamePlay.logWriter?.closeFile()
|
|
84
|
+
this.gamePlay.forEachPlayer(p => p.endGame());
|
|
85
|
+
Tile.allTiles.forEach(tile => tile.hex = undefined);
|
|
86
|
+
let deContainer = (cont) => {
|
|
87
|
+
cont.children.forEach(dObj => {
|
|
88
|
+
dObj.removeAllEventListeners();
|
|
89
|
+
if (dObj instanceof Container)
|
|
90
|
+
deContainer(dObj);
|
|
91
|
+
});
|
|
92
|
+
cont.removeAllChildren();
|
|
93
|
+
};
|
|
94
|
+
deContainer(this.stage);
|
|
95
|
+
this.resetState(stateInfo);
|
|
96
|
+
// next tick, new thread...
|
|
97
|
+
setTimeout(() => this.netState = netState, 100); // onChange-> ("new", "join", "ref") initiate a new connection
|
|
98
|
+
}
|
|
99
|
+
/** override: invoked by restart(); with stateInfo JSON5_parse(stateText) */
|
|
100
|
+
resetState(stateInfo) {
|
|
101
|
+
const { mh, nh, hexRad } = stateInfo; // for example
|
|
102
|
+
TP.mHexes = mh ?? TP.mHexes;
|
|
103
|
+
TP.nHexes = nh ?? TP.nHexes;
|
|
104
|
+
TP.hexRad = hexRad ?? TP.hexRad;
|
|
105
|
+
this.startup();
|
|
106
|
+
}
|
|
107
|
+
/** read & parse State from text element */
|
|
108
|
+
setupToParseState() {
|
|
109
|
+
const parseStateButton = document.getElementById('parseStateButton');
|
|
110
|
+
const parseStateText = document.getElementById('parseStateText');
|
|
111
|
+
parseStateButton.onclick = () => {
|
|
112
|
+
const stateText = parseStateText.value;
|
|
113
|
+
const state = JSON5_parse(stateText);
|
|
114
|
+
state.Aname = state.Aname ?? `parseStateText`;
|
|
115
|
+
blinkAndThen(this.gamePlay.hexMap.mapCont.markCont, () => this.restart(state));
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
fileReadPromise;
|
|
119
|
+
async setupToReadFileState() {
|
|
120
|
+
const logReader = new LogReader(`log/date_time.js`, 'fsReadFileButton');
|
|
121
|
+
this.fileReadPromise = logReader.setButtonToReadFile();
|
|
122
|
+
const fileHandle = await this.fileReadPromise;
|
|
123
|
+
const fileText = await logReader.readFile(fileHandle);
|
|
124
|
+
const fullName = fileHandle.name;
|
|
125
|
+
const [fileName, ext] = fullName.split('.');
|
|
126
|
+
const readFileNameElt = document.getElementById('readFileName');
|
|
127
|
+
const readFileName = readFileNameElt.value;
|
|
128
|
+
const [fname, turnstr] = readFileName.split('@'); // fileName@turn
|
|
129
|
+
const turn = Number.parseInt(turnstr);
|
|
130
|
+
const state = this.extractStateFromString(fileName, fileText, turn);
|
|
131
|
+
this.setupToReadFileState(); // another thread to wait for next click
|
|
132
|
+
this.restart(state);
|
|
133
|
+
}
|
|
134
|
+
extractStateFromString(fileName, fileText, turn) {
|
|
135
|
+
const logArray = JSON5_parse(fileText);
|
|
136
|
+
const [, ...stateArray] = logArray;
|
|
137
|
+
const state = stateArray.find(state => state.turn === turn) ?? {};
|
|
138
|
+
state.Aname = `${fileName}@${turn}`;
|
|
139
|
+
return state;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Make new Table/layout & gamePlay/hexMap & Players.
|
|
143
|
+
* @param qParams from URL
|
|
144
|
+
*/
|
|
145
|
+
startup(qParams = this.qParams) {
|
|
146
|
+
this.nPlayers = Math.min(TP.maxPlayers, qParams?.['n'] ? Number.parseInt(qParams?.['n']) : 2);
|
|
147
|
+
this.startScenario({ turn: 0, Aname: 'defaultScenario' });
|
|
148
|
+
}
|
|
149
|
+
/** scenario.turn indicate a FULL/SAVED scenario */
|
|
150
|
+
startScenario(scenario) {
|
|
151
|
+
Tile.allTiles = [];
|
|
152
|
+
Meeple.allMeeples = [];
|
|
153
|
+
Player.allPlayers = [];
|
|
154
|
+
const table = new Table(this.stage); // EventDispatcher, ScaleCont, GUI-Player
|
|
155
|
+
// Inject Table into GamePlay & make allPlayers:
|
|
156
|
+
const gamePlay = new GamePlay(scenario, table, this); // hexMap, players, fillBag, gStats, mouse/keyboard->GamePlay
|
|
157
|
+
this.gamePlay = gamePlay;
|
|
158
|
+
this.makeNplayers(gamePlay); // Players have: civics & meeples & TownSpec
|
|
159
|
+
// Inject GamePlay to Table; all the GUI components, makeAllDistricts(), addTerrain, initialRegions
|
|
160
|
+
table.layoutTable(gamePlay); // mutual injection & make all panelForPlayer
|
|
161
|
+
gamePlay.forEachPlayer(p => table.setPlayerScore(p, 0));
|
|
162
|
+
this.gamePlay.turnNumber = -1; // in prep for setNextPlayer or parseScenario
|
|
163
|
+
// Place Pieces and Figures on map:
|
|
164
|
+
this.parseScenenario(scenario); // may change gamePlay.turnNumber, gamePlay.phase (& conflictRegion)
|
|
165
|
+
this.gamePlay.logWriterLine0();
|
|
166
|
+
gamePlay.forEachPlayer(p => p.newGame(gamePlay)); // make Planner *after* table & gamePlay are setup
|
|
167
|
+
this.restartable = false;
|
|
168
|
+
this.makeGUIs(table);
|
|
169
|
+
this.restartable = true; // *after* makeLines has stablilized selectValue
|
|
170
|
+
table.startGame(scenario); // parseScenario; allTiles.makeDragable(); setNextPlayer();
|
|
171
|
+
return gamePlay;
|
|
172
|
+
}
|
|
173
|
+
makeGUIs(table) {
|
|
174
|
+
const scaleCont = table.scaleCont, scale = TP.hexRad / 60, cx = -200, cy = 250, d = 5;
|
|
175
|
+
// this.makeParamGUI(table.scaleCont, -400, 250);
|
|
176
|
+
const gpanel = (makeGUI, name, cx, cy, scale = 1) => {
|
|
177
|
+
const guiC = new NamedContainer(name, cx * scale, cy * scale);
|
|
178
|
+
// const map = table.hexMap.mapCont.parent;
|
|
179
|
+
scaleCont.addChildAt(guiC);
|
|
180
|
+
guiC.scaleX = guiC.scaleY = scale;
|
|
181
|
+
const gui = makeGUI.call(this, guiC); // @[0, 0]
|
|
182
|
+
guiC.x -= (gui.linew + d) * scale;
|
|
183
|
+
const bgr = new RectShape({ x: -d, y: -d, w: gui.linew + 2 * d, h: gui.ymax + 2 * d }, 'rgb(200,200,200,.5)', '');
|
|
184
|
+
guiC.addChildAt(bgr, 0);
|
|
185
|
+
table.dragger.makeDragable(guiC);
|
|
186
|
+
return gui;
|
|
187
|
+
};
|
|
188
|
+
let ymax = 0;
|
|
189
|
+
const gui3 = gpanel(this.makeNetworkGUI, 'NetGUI', cx, cy + ymax, scale);
|
|
190
|
+
ymax += gui3.ymax + 20;
|
|
191
|
+
const gui1 = gpanel(this.makeParamGUI, 'ParamGUI', cx, cy + ymax, scale);
|
|
192
|
+
ymax += gui1.ymax + 20;
|
|
193
|
+
const gui2 = gpanel(this.makeParamGUI2, 'AI_GUI', cx, cy + ymax, scale);
|
|
194
|
+
ymax += gui2.ymax + 20;
|
|
195
|
+
scaleCont.addChild(gui2.parent, gui1.parent, gui3.parent); // lower y values ABOVE to dropdown is not obscured
|
|
196
|
+
// TODO: dropdown to use given 'top' container!
|
|
197
|
+
gui1.stage.update();
|
|
198
|
+
}
|
|
199
|
+
scenarioParser;
|
|
200
|
+
parseScenenario(scenario) {
|
|
201
|
+
const hexMap = this.gamePlay.hexMap;
|
|
202
|
+
const scenarioParser = this.scenarioParser = new ScenarioParser(hexMap, this.gamePlay);
|
|
203
|
+
this.gamePlay.logWriter.writeLine(`// GameSetup.parseScenario: ${scenario.Aname}`);
|
|
204
|
+
scenarioParser.parseScenario(scenario);
|
|
205
|
+
}
|
|
206
|
+
/** affects the rules of the game & board
|
|
207
|
+
*
|
|
208
|
+
* ParamGUI --> board & rules [under stats panel]
|
|
209
|
+
* ParamGUI2 --> AI Player [left of ParamGUI]
|
|
210
|
+
* NetworkGUI --> network [below ParamGUI2]
|
|
211
|
+
*/
|
|
212
|
+
makeParamGUI(parent, x = 0, y = 0) {
|
|
213
|
+
const gui = new ParamGUI(TP, { textAlign: 'right' });
|
|
214
|
+
gui.makeParamSpec('hexRad', [30, 60, 90, 120], { fontColor: 'red' });
|
|
215
|
+
TP.hexRad;
|
|
216
|
+
gui.makeParamSpec('nHexes', [2, 3, 4, 5, 6, 7, 8, 9, 10, 11], { fontColor: 'red' });
|
|
217
|
+
TP.nHexes;
|
|
218
|
+
gui.makeParamSpec('mHexes', [1, 2, 3], { fontColor: 'red' });
|
|
219
|
+
TP.mHexes;
|
|
220
|
+
gui.spec("hexRad").onChange = (item) => { this.restart({ hexRad: item.value }); };
|
|
221
|
+
gui.spec("nHexes").onChange = (item) => { this.restart({ nh: item.value }); };
|
|
222
|
+
gui.spec("mHexes").onChange = (item) => { this.restart({ mh: item.value }); };
|
|
223
|
+
parent.addChild(gui);
|
|
224
|
+
gui.x = x; // (3*cw+1*ch+6*m) + max(line.width) - (max(choser.width) + 20)
|
|
225
|
+
gui.y = y;
|
|
226
|
+
gui.makeLines();
|
|
227
|
+
return gui;
|
|
228
|
+
}
|
|
229
|
+
/** configures the AI player */
|
|
230
|
+
makeParamGUI2(parent, x = 0, y = 0) {
|
|
231
|
+
const gui = new ParamGUI(TP, { textAlign: 'center' });
|
|
232
|
+
gui.makeParamSpec("log", [-1, 0, 1, 2], { style: { textAlign: 'right' } });
|
|
233
|
+
TP.log;
|
|
234
|
+
gui.makeParamSpec("maxPlys", [1, 2, 3, 4, 5, 6, 7, 8], { fontColor: "blue" });
|
|
235
|
+
TP.maxPlys;
|
|
236
|
+
gui.makeParamSpec("maxBreadth", [5, 6, 7, 8, 9, 10], { fontColor: "blue" });
|
|
237
|
+
TP.maxBreadth;
|
|
238
|
+
parent.addChild(gui);
|
|
239
|
+
gui.x = x;
|
|
240
|
+
gui.y = y;
|
|
241
|
+
gui.makeLines();
|
|
242
|
+
gui.stage.update();
|
|
243
|
+
return gui;
|
|
244
|
+
}
|
|
245
|
+
netColor = "rgba(160,160,160, .8)";
|
|
246
|
+
netStyle = { textAlign: 'right' };
|
|
247
|
+
/** controls multiplayer network participation */
|
|
248
|
+
makeNetworkGUI(parent, x = 0, y = 0) {
|
|
249
|
+
const gui = this.netGUI = new ParamGUI(TP, this.netStyle);
|
|
250
|
+
gui.makeParamSpec("Network", [" ", "new", "join", "no", "ref", "cnx"], { fontColor: "red" });
|
|
251
|
+
gui.makeParamSpec("PlayerId", [" ", 0, 1, 2, 3, "ref"], { chooser: PidChoice, fontColor: "red" });
|
|
252
|
+
gui.makeParamSpec("networkGroup", [TP.networkGroup], { chooser: EBC, name: 'gid', fontColor: C.GREEN, style: { textColor: C.BLACK } });
|
|
253
|
+
TP.networkGroup;
|
|
254
|
+
gui.spec("Network").onChange = (item) => {
|
|
255
|
+
if (['new', 'join', 'ref'].includes(item.value)) {
|
|
256
|
+
const group = gui.findLine('networkGroup').chooser.editBox.innerText;
|
|
257
|
+
// this.gamePlay.closeNetwork()
|
|
258
|
+
// this.gamePlay.network(item.value, gui, group)
|
|
259
|
+
}
|
|
260
|
+
// if (item.value === "no") this.gamePlay.closeNetwork() // provoked by ckey
|
|
261
|
+
};
|
|
262
|
+
this.stage.canvas?.parentElement?.addEventListener('paste', (ev) => {
|
|
263
|
+
const text = ev.clipboardData?.getData('Text');
|
|
264
|
+
;
|
|
265
|
+
gui.findLine('networkGroup').chooser.setValue(text);
|
|
266
|
+
});
|
|
267
|
+
this.showNetworkGroup();
|
|
268
|
+
parent.addChild(gui);
|
|
269
|
+
gui.makeLines();
|
|
270
|
+
gui.x = x;
|
|
271
|
+
gui.y = y;
|
|
272
|
+
parent.stage.update();
|
|
273
|
+
return gui;
|
|
274
|
+
}
|
|
275
|
+
showNetworkGroup(group_name = TP.networkGroup) {
|
|
276
|
+
document.getElementById('group_name').innerText = group_name;
|
|
277
|
+
const line = this.netGUI.findLine("networkGroup"), chooser = line?.chooser;
|
|
278
|
+
chooser?.setValue(group_name, chooser.items[0], undefined);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { stime } from "@thegraid/common-lib";
|
|
2
|
+
export class GameState {
|
|
3
|
+
gamePlay;
|
|
4
|
+
constructor(gamePlay) {
|
|
5
|
+
this.gamePlay = gamePlay;
|
|
6
|
+
Object.keys(this.states).forEach((key) => this.states[key].Aname = key);
|
|
7
|
+
}
|
|
8
|
+
state;
|
|
9
|
+
get table() { return this.gamePlay?.table; }
|
|
10
|
+
get curPlayer() { return this.gamePlay.curPlayer; }
|
|
11
|
+
saveGame() {
|
|
12
|
+
this.gamePlay.gameSetup.scenarioParser.saveState(this.gamePlay);
|
|
13
|
+
}
|
|
14
|
+
// [eventName, eventSpecial, phase, args]
|
|
15
|
+
saveState() {
|
|
16
|
+
}
|
|
17
|
+
parseState(args) {
|
|
18
|
+
}
|
|
19
|
+
startPhase = 'BeginTurn';
|
|
20
|
+
startArgs = [];
|
|
21
|
+
/** Bootstrap the Scenario: set bastetPlayer and then this.phase(startPhase, ...startArgs). */
|
|
22
|
+
start() {
|
|
23
|
+
this.phase(this.startPhase, ...this.startArgs);
|
|
24
|
+
}
|
|
25
|
+
/** set state and start with given args. */
|
|
26
|
+
phase(phase, ...args) {
|
|
27
|
+
console.log(stime(this, `.phase: ${this.state?.Aname ?? 'Initialize'} -> ${phase}`));
|
|
28
|
+
this.state = this.states[phase];
|
|
29
|
+
this.state.start(...args);
|
|
30
|
+
}
|
|
31
|
+
/** set label & paint button with color;
|
|
32
|
+
* empty label hides & disables.
|
|
33
|
+
* optional continuation function on 'drawend'.
|
|
34
|
+
*/
|
|
35
|
+
doneButton(label, color = this.curPlayer.color, afterUpdate = undefined) {
|
|
36
|
+
const doneButton = this.table.doneButton;
|
|
37
|
+
doneButton.visible = !!label;
|
|
38
|
+
doneButton.label_text = label;
|
|
39
|
+
doneButton.paint(color, true);
|
|
40
|
+
doneButton.updateWait(false, afterUpdate);
|
|
41
|
+
}
|
|
42
|
+
/** invoked when 'Done' button clicked. [or whenever phase is 'done' by other means] */
|
|
43
|
+
done(...args) {
|
|
44
|
+
(this.state.done ?? ((...args) => { alert('no done method'); }))(...args);
|
|
45
|
+
}
|
|
46
|
+
undoAction() {
|
|
47
|
+
// const action = this.selectedAction;
|
|
48
|
+
// if (!action) return;
|
|
49
|
+
// this.states[action].undo?.();
|
|
50
|
+
}
|
|
51
|
+
states = {
|
|
52
|
+
BeginTurn: {
|
|
53
|
+
start: () => {
|
|
54
|
+
this.saveGame();
|
|
55
|
+
this.phase('ChooseAction');
|
|
56
|
+
},
|
|
57
|
+
done: () => {
|
|
58
|
+
this.phase('ChooseAction');
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
Move: {
|
|
62
|
+
start: () => {
|
|
63
|
+
this.doneButton('Move done');
|
|
64
|
+
},
|
|
65
|
+
done: (ok) => {
|
|
66
|
+
this.phase('EndAction');
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
Summon: {
|
|
70
|
+
start: () => {
|
|
71
|
+
this.doneButton('Summon done');
|
|
72
|
+
},
|
|
73
|
+
done: () => {
|
|
74
|
+
this.phase('EndAction');
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
EndAction: {
|
|
78
|
+
nextPhase: 'ChooseAction',
|
|
79
|
+
start: () => {
|
|
80
|
+
const nextPhase = this.state.nextPhase = 'Event';
|
|
81
|
+
this.phase(nextPhase); // directl -> nextPhase
|
|
82
|
+
},
|
|
83
|
+
done: () => {
|
|
84
|
+
this.phase(this.state.nextPhase ?? 'Start'); // TS want defined...
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
Conflict: {
|
|
88
|
+
start: () => {
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
ConflictRegionDone: {
|
|
92
|
+
start: () => {
|
|
93
|
+
this.phase('ConflictNextRegion');
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
ConflictDone: {
|
|
97
|
+
start: () => {
|
|
98
|
+
this.phase('EventDone');
|
|
99
|
+
},
|
|
100
|
+
// TODO: coins from Scales to Toth, add Devotion(Scales)
|
|
101
|
+
},
|
|
102
|
+
EndTurn: {
|
|
103
|
+
start: () => {
|
|
104
|
+
this.gamePlay.endTurn();
|
|
105
|
+
this.phase('BeginTurn');
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
/** Hathor: after addFollowers() Ankh-Event, BuildMonument, Worshipful, Summon-AnubisRansom */
|
|
109
|
+
};
|
|
110
|
+
setup() {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Hexagonal canonical directions */
|
|
2
|
+
export var Dir;
|
|
3
|
+
(function (Dir) {
|
|
4
|
+
Dir[Dir["C"] = 0] = "C";
|
|
5
|
+
Dir[Dir["NE"] = 1] = "NE";
|
|
6
|
+
Dir[Dir["E"] = 2] = "E";
|
|
7
|
+
Dir[Dir["SE"] = 3] = "SE";
|
|
8
|
+
Dir[Dir["SW"] = 4] = "SW";
|
|
9
|
+
Dir[Dir["W"] = 5] = "W";
|
|
10
|
+
Dir[Dir["NW"] = 6] = "NW";
|
|
11
|
+
})(Dir || (Dir = {}));
|
|
12
|
+
/** Hex things */
|
|
13
|
+
export var H;
|
|
14
|
+
(function (H) {
|
|
15
|
+
H.degToRadians = Math.PI / 180;
|
|
16
|
+
H.sqrt3 = Math.sqrt(3); // 1.7320508075688772
|
|
17
|
+
H.sqrt3_2 = H.sqrt3 / 2;
|
|
18
|
+
H.infin = String.fromCodePoint(0x221E);
|
|
19
|
+
H.C = "C"; // not a HexDir, but identifies a Center
|
|
20
|
+
H.N = "N";
|
|
21
|
+
H.S = "S";
|
|
22
|
+
H.E = "E";
|
|
23
|
+
H.W = "W";
|
|
24
|
+
H.NE = "NE";
|
|
25
|
+
H.SE = "SE";
|
|
26
|
+
H.SW = "SW";
|
|
27
|
+
H.NW = "NW";
|
|
28
|
+
H.EN = "EN";
|
|
29
|
+
H.ES = "ES";
|
|
30
|
+
H.WS = "WS";
|
|
31
|
+
H.WN = "WN";
|
|
32
|
+
function hexBounds(r, tilt = 0) {
|
|
33
|
+
// dp(...6), so tilt: 30 | 0; being nsAxis (ewTopo) or ewAxis (nsTopo);
|
|
34
|
+
const w = r * Math.cos(H.degToRadians * tilt);
|
|
35
|
+
const h = r * Math.cos(H.degToRadians * (tilt - 30));
|
|
36
|
+
return { x: -w, y: -h, width: 2 * w, height: 2 * h };
|
|
37
|
+
}
|
|
38
|
+
H.hexBounds = hexBounds;
|
|
39
|
+
/** neighborhood topology, E-W & N-S orientation; even(n0) & odd(n1) rows: */
|
|
40
|
+
H.ewEvenRow = {
|
|
41
|
+
NE: { dc: 0, dr: -1 }, E: { dc: 1, dr: 0 }, SE: { dc: 0, dr: 1 },
|
|
42
|
+
SW: { dc: -1, dr: 1 }, W: { dc: -1, dr: 0 }, NW: { dc: -1, dr: -1 }
|
|
43
|
+
};
|
|
44
|
+
H.ewOddRow = {
|
|
45
|
+
NE: { dc: 1, dr: -1 }, E: { dc: 1, dr: 0 }, SE: { dc: 1, dr: 1 },
|
|
46
|
+
SW: { dc: 0, dr: 1 }, W: { dc: -1, dr: 0 }, NW: { dc: 0, dr: -1 }
|
|
47
|
+
};
|
|
48
|
+
H.nsEvenCol = {
|
|
49
|
+
EN: { dc: +1, dr: -1 }, N: { dc: 0, dr: -1 }, ES: { dc: +1, dr: 0 },
|
|
50
|
+
WS: { dc: -1, dr: 0 }, S: { dc: 0, dr: +1 }, WN: { dc: -1, dr: -1 }
|
|
51
|
+
};
|
|
52
|
+
H.nsOddCol = {
|
|
53
|
+
EN: { dc: 1, dr: 0 }, N: { dc: 0, dr: -1 }, ES: { dc: 1, dr: 1 },
|
|
54
|
+
WS: { dc: -1, dr: 1 }, S: { dc: 0, dr: 1 }, WN: { dc: -1, dr: 0 }
|
|
55
|
+
};
|
|
56
|
+
function nsTopo(rc) { return (rc.col % 2 == 0) ? H.nsEvenCol : H.nsOddCol; }
|
|
57
|
+
H.nsTopo = nsTopo;
|
|
58
|
+
;
|
|
59
|
+
function ewTopo(rc) { return (rc.row % 2 == 0) ? H.ewEvenRow : H.ewOddRow; }
|
|
60
|
+
H.ewTopo = ewTopo;
|
|
61
|
+
;
|
|
62
|
+
/** includes E & W, suitable for EwTopo */
|
|
63
|
+
H.ewDirs = [H.NE, H.E, H.SE, H.SW, H.W, H.NW]; // directions for EwTOPO
|
|
64
|
+
/** includes N & S, suitable for NsTopo */
|
|
65
|
+
H.nsDirs = [H.N, H.EN, H.ES, H.S, H.WS, H.WN]; // directions for NsTOPO
|
|
66
|
+
/** all hexDirs */
|
|
67
|
+
H.hexDirs = H.ewDirs.concat(H.nsDirs); // standard direction signifiers () ClockWise
|
|
68
|
+
// angles for ewTopo!
|
|
69
|
+
H.ewDirRot = { NE: 30, E: 90, SE: 150, SW: 210, W: 270, NW: 330 };
|
|
70
|
+
// angles for nwTopo!
|
|
71
|
+
H.nsDirRot = { N: 0, EN: 60, ES: 120, S: 180, WS: 240, WN: 300 };
|
|
72
|
+
H.dirRot = { ...H.ewDirRot, ...H.nsDirRot };
|
|
73
|
+
H.dirRev = { N: H.S, S: H.N, E: H.W, W: H.E, NE: H.SW, SE: H.NW, SW: H.NE, NW: H.SE, ES: H.WN, EN: H.WS, WS: H.EN, WN: H.ES };
|
|
74
|
+
H.dirRevEW = { E: H.W, W: H.E, NE: H.SW, SE: H.NW, SW: H.NE, NW: H.SE };
|
|
75
|
+
H.dirRevNS = { N: H.S, S: H.N, EN: H.WS, ES: H.WN, WS: H.EN, WN: H.ES };
|
|
76
|
+
H.rotDir = { 0: 'N', 30: 'NE', 60: 'EN', 90: 'E', 120: 'ES', 150: 'SE', 180: 'S', 210: 'SW', 240: 'WS', 270: 'W', 300: 'WN', 330: 'NW', 360: 'N' };
|
|
77
|
+
H.capColor1 = "rgba(150, 0, 0, .8)"; // unplayable: captured last turn
|
|
78
|
+
H.capColor2 = "rgba(128, 80, 80, .8)"; // protoMove would capture
|
|
79
|
+
H.sacColor1 = "rgba(228, 80, 0, .8)"; // unplayable: sacrifice w/o capture
|
|
80
|
+
H.sacColor2 = "rgba(228, 120, 0, .6)"; // isplayable: sacrifice w/ capture
|
|
81
|
+
H.fjColor = "rgba(228, 228, 0, .8)"; // ~unplayable: jeopardy w/o capture
|
|
82
|
+
})(H || (H = {}));
|