@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
package/dist/table.js
ADDED
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
import { AT, C, CenterText, Dragger, F, KeyBinder, S, ScaleableContainer, stime } from "@thegraid/easeljs-lib";
|
|
2
|
+
import { Container, EventDispatcher, Graphics, Shape, Text } from "@thegraid/easeljs-module";
|
|
3
|
+
import { NamedContainer } from "./game-play";
|
|
4
|
+
import { Hex2 } from "./hex";
|
|
5
|
+
import { Player } from "./player";
|
|
6
|
+
import { PlayerPanel } from "./player-panel";
|
|
7
|
+
import { CircleShape, HexShape, RectShape, UtilButton } from "./shapes";
|
|
8
|
+
import { playerColor0, playerColor1, TP } from "./table-params";
|
|
9
|
+
import { Tile } from "./tile";
|
|
10
|
+
//import { TablePlanner } from "./planner";
|
|
11
|
+
function firstChar(s, uc = true) { return uc ? s.substring(0, 1).toUpperCase() : s.substring(0, 1); }
|
|
12
|
+
;
|
|
13
|
+
/** to own file... */
|
|
14
|
+
class TablePlanner {
|
|
15
|
+
constructor(gamePlay) { }
|
|
16
|
+
}
|
|
17
|
+
class TextLog extends NamedContainer {
|
|
18
|
+
size;
|
|
19
|
+
lead;
|
|
20
|
+
constructor(Aname, nlines = 6, size = 30, lead = 3) {
|
|
21
|
+
super(Aname);
|
|
22
|
+
this.size = size;
|
|
23
|
+
this.lead = lead;
|
|
24
|
+
this.lines = new Array(nlines);
|
|
25
|
+
for (let ndx = 0; ndx < nlines; ndx++)
|
|
26
|
+
this.lines[ndx] = this.newText(`//0:`);
|
|
27
|
+
this.addChild(...this.lines);
|
|
28
|
+
}
|
|
29
|
+
lines;
|
|
30
|
+
lastLine = '';
|
|
31
|
+
nReps = 0;
|
|
32
|
+
height(n = this.lines.length) {
|
|
33
|
+
return (this.size + this.lead) * n;
|
|
34
|
+
}
|
|
35
|
+
clear() {
|
|
36
|
+
this.lines.forEach(tline => tline.text = '');
|
|
37
|
+
this.stage?.update();
|
|
38
|
+
}
|
|
39
|
+
newText(line = '') {
|
|
40
|
+
const text = new Text(line, F.fontSpec(this.size));
|
|
41
|
+
text.textAlign = 'left';
|
|
42
|
+
text.mouseEnabled = false;
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
spaceLines(cy = 0, lead = this.lead) {
|
|
46
|
+
this.lines.forEach(tline => (tline.y = cy, cy += tline.getMeasuredLineHeight() + lead));
|
|
47
|
+
}
|
|
48
|
+
log(line, from = '', toConsole = true) {
|
|
49
|
+
line = line.replace('/\n/g', '-');
|
|
50
|
+
toConsole && console.log(stime(`${from}:`), line);
|
|
51
|
+
if (line === this.lastLine) {
|
|
52
|
+
this.lines[this.lines.length - 1].text = `[${++this.nReps}] ${line}`;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.removeChild(this.lines.shift()); // assert is not undefined
|
|
56
|
+
this.lines.push(this.addChild(this.newText(line)));
|
|
57
|
+
this.spaceLines();
|
|
58
|
+
this.lastLine = line;
|
|
59
|
+
this.nReps = 0;
|
|
60
|
+
}
|
|
61
|
+
this.stage?.update();
|
|
62
|
+
return line;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** layout display components, setup callbacks to GamePlay */
|
|
66
|
+
export class Table extends EventDispatcher {
|
|
67
|
+
static table;
|
|
68
|
+
static stageTable(obj) {
|
|
69
|
+
return obj.stage.table;
|
|
70
|
+
}
|
|
71
|
+
namedOn(Aname, type, listener, scope, once, data, useCapture = false) {
|
|
72
|
+
const list2 = this.on(type, listener, scope, once, data, useCapture);
|
|
73
|
+
list2.Aname = Aname;
|
|
74
|
+
}
|
|
75
|
+
gamePlay;
|
|
76
|
+
stage;
|
|
77
|
+
bgRect;
|
|
78
|
+
hexMap; // from gamePlay.hexMap
|
|
79
|
+
undoCont = new NamedContainer('undoCont');
|
|
80
|
+
undoShape = new Shape();
|
|
81
|
+
skipShape = new Shape();
|
|
82
|
+
redoShape = new Shape();
|
|
83
|
+
undoText = new Text('', F.fontSpec(30)); // length of undo stack
|
|
84
|
+
redoText = new Text('', F.fontSpec(30)); // length of history stack
|
|
85
|
+
winText = new Text('', F.fontSpec(40), 'green');
|
|
86
|
+
winBack = new Shape(new Graphics().f(C.nameToRgbaString("lightgrey", .6)).r(-180, -5, 360, 130));
|
|
87
|
+
dragger;
|
|
88
|
+
overlayCont = new Container();
|
|
89
|
+
constructor(stage) {
|
|
90
|
+
super();
|
|
91
|
+
this.overlayCont.name = 'overlay';
|
|
92
|
+
// backpointer so Containers can find their Table (& curMark)
|
|
93
|
+
Table.table = stage.table = this;
|
|
94
|
+
this.stage = stage;
|
|
95
|
+
this.scaleCont = this.makeScaleCont(!!this.stage.canvas); // scaleCont & background
|
|
96
|
+
this.scaleCont.addChild(this.overlayCont); // will add again at top/end of the list.
|
|
97
|
+
}
|
|
98
|
+
/** shows the last 2 start of turn lines */
|
|
99
|
+
turnLog = new TextLog('turnLog', 2);
|
|
100
|
+
/** show [13] other interesting log strings */
|
|
101
|
+
textLog = new TextLog('textLog', TP.textLogLines);
|
|
102
|
+
logTurn(line) {
|
|
103
|
+
this.turnLog.log(line, 'table.logTurn'); // in top two lines
|
|
104
|
+
}
|
|
105
|
+
logText(line, from = '') {
|
|
106
|
+
const text = this.textLog.log(`${this.gamePlay.turnNumber}: ${line}`, from || '***'); // scrolling lines below
|
|
107
|
+
this.gamePlay.logWriter.writeLine(`// ${text}`);
|
|
108
|
+
// JSON string, instead of JSON5 comment:
|
|
109
|
+
// const text = this.textLog.log(`${this.gamePlay.turnNumber}: ${line}`, from); // scrolling lines below
|
|
110
|
+
// this.gamePlay.logWriter.writeLine(`"${line}",`);
|
|
111
|
+
}
|
|
112
|
+
setupUndoButtons(xOffs, bSize, skipRad, bgr, row = 8, col = -7) {
|
|
113
|
+
const undoC = this.undoCont; // holds the undo buttons.
|
|
114
|
+
this.setToRowCol(undoC, row, col);
|
|
115
|
+
const progressBg = new Shape(), bgw = 200, bgym = 140, y0 = 0; // bgym = 240
|
|
116
|
+
const bgc = C.nameToRgbaString(TP.bgColor, .8);
|
|
117
|
+
progressBg.graphics.f(bgc).r(-bgw / 2, y0, bgw, bgym - y0);
|
|
118
|
+
undoC.addChildAt(progressBg, 0);
|
|
119
|
+
this.enableHexInspector(30);
|
|
120
|
+
this.dragger.makeDragable(undoC);
|
|
121
|
+
if (true && xOffs > 0)
|
|
122
|
+
return;
|
|
123
|
+
this.skipShape.graphics.f("white").dp(0, 0, 40, 4, 0, skipRad);
|
|
124
|
+
this.undoShape.graphics.f("red").dp(-xOffs, 0, bSize, 3, 0, 180);
|
|
125
|
+
this.redoShape.graphics.f("green").dp(+xOffs, 0, bSize, 3, 0, 0);
|
|
126
|
+
this.undoText.x = -52;
|
|
127
|
+
this.undoText.textAlign = "center";
|
|
128
|
+
this.redoText.x = 52;
|
|
129
|
+
this.redoText.textAlign = "center";
|
|
130
|
+
this.winText.x = 0;
|
|
131
|
+
this.winText.textAlign = "center";
|
|
132
|
+
undoC.addChild(this.skipShape);
|
|
133
|
+
undoC.addChild(this.undoShape);
|
|
134
|
+
undoC.addChild(this.redoShape);
|
|
135
|
+
undoC.addChild(this.undoText);
|
|
136
|
+
this.undoText.y = -14;
|
|
137
|
+
undoC.addChild(this.redoText);
|
|
138
|
+
this.redoText.y = -14;
|
|
139
|
+
let bgrpt = this.bgRect.parent.localToLocal(bgr.x, bgr.h, undoC);
|
|
140
|
+
this.undoText.mouseEnabled = this.redoText.mouseEnabled = false;
|
|
141
|
+
let aiControl = this.aiControl('pink', 75);
|
|
142
|
+
aiControl.x = 0;
|
|
143
|
+
aiControl.y = 100;
|
|
144
|
+
let pmy = 0;
|
|
145
|
+
undoC.addChild(aiControl);
|
|
146
|
+
undoC.addChild(this.winBack);
|
|
147
|
+
undoC.addChild(this.winText);
|
|
148
|
+
this.winText.y = Math.min(pmy, bgrpt.y); // 135 = winBack.y = winBack.h
|
|
149
|
+
this.winBack.visible = this.winText.visible = false;
|
|
150
|
+
this.winBack.x = this.winText.x;
|
|
151
|
+
this.winBack.y = this.winText.y;
|
|
152
|
+
}
|
|
153
|
+
showWinText(msg, color = 'green') {
|
|
154
|
+
this.winText.text = msg || "COLOR WINS:\nSTALEMATE (10 -- 10)\n0 -- 0";
|
|
155
|
+
this.winText.color = color;
|
|
156
|
+
this.winText.visible = this.winBack.visible = true;
|
|
157
|
+
this.hexMap.update();
|
|
158
|
+
}
|
|
159
|
+
enableHexInspector(qY = 52, cont = this.undoCont) {
|
|
160
|
+
const qShape = new HexShape(TP.hexRad / 3);
|
|
161
|
+
qShape.paint(C.BLACK);
|
|
162
|
+
qShape.y = qY; // size of 'skip' Triangles
|
|
163
|
+
cont.addChild(qShape);
|
|
164
|
+
this.dragger.makeDragable(qShape, this,
|
|
165
|
+
// dragFunc:
|
|
166
|
+
(qShape, ctx) => { },
|
|
167
|
+
// dropFunc:
|
|
168
|
+
(qShape, ctx) => {
|
|
169
|
+
this.downClick = true;
|
|
170
|
+
const hex = this.hexUnderObj(qShape, false); // also check hexCont!
|
|
171
|
+
qShape.x = 0;
|
|
172
|
+
qShape.y = qY; // return to regular location
|
|
173
|
+
cont.addChild(qShape);
|
|
174
|
+
if (!hex)
|
|
175
|
+
return;
|
|
176
|
+
const info = hex; //{ hex, stone: hex.playerColor, InfName }
|
|
177
|
+
console.log(`HexInspector:`, hex.Aname, info);
|
|
178
|
+
});
|
|
179
|
+
qShape.on(S.click, () => this.toggleText(), this); // toggle visible
|
|
180
|
+
}
|
|
181
|
+
downClick = false;
|
|
182
|
+
isVisible = false;
|
|
183
|
+
/** method invokes closure defined in enableHexInspector. */
|
|
184
|
+
toggleText(vis) {
|
|
185
|
+
if (this.downClick)
|
|
186
|
+
return (this.downClick = false, undefined); // skip one 'click' when pressup/dropfunc
|
|
187
|
+
if (vis === undefined)
|
|
188
|
+
vis = this.isVisible = !this.isVisible;
|
|
189
|
+
Tile.allTiles.forEach(tile => tile.textVis(vis));
|
|
190
|
+
this.hexMap.forEachHex(hex => hex.showText(vis));
|
|
191
|
+
this.hexMap.update(); // after toggleText & updateCache()
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
aiControl(color = TP.bgColor, dx = 100, rad = 16) {
|
|
195
|
+
let table = this;
|
|
196
|
+
// c m v on buttons
|
|
197
|
+
let makeButton = (dx, bc = TP.bgColor, tc = TP.bgColor, text, key = text) => {
|
|
198
|
+
let cont = new Container();
|
|
199
|
+
cont.name = 'aiControl';
|
|
200
|
+
let circ = new Graphics().f(bc).drawCircle(0, 0, rad);
|
|
201
|
+
let txt = new Text(text, F.fontSpec(rad), tc);
|
|
202
|
+
txt.y = -rad / 2;
|
|
203
|
+
txt.textAlign = 'center';
|
|
204
|
+
txt.mouseEnabled = false;
|
|
205
|
+
cont.x = dx;
|
|
206
|
+
cont.addChild(new Shape(circ));
|
|
207
|
+
cont.addChild(txt);
|
|
208
|
+
cont.on(S.click, (ev) => { KeyBinder.keyBinder.dispatchChar(key); });
|
|
209
|
+
return cont;
|
|
210
|
+
};
|
|
211
|
+
let bpanel = new Container();
|
|
212
|
+
bpanel.name = 'bpanel';
|
|
213
|
+
let c0 = TP.colorScheme[playerColor0], c1 = TP.colorScheme[playerColor1];
|
|
214
|
+
let cm = "rgba(100,100,100,.5)";
|
|
215
|
+
let bc = makeButton(-dx, c0, c1, 'C', 'c');
|
|
216
|
+
let bv = makeButton(dx, c1, c0, 'V', 'v');
|
|
217
|
+
let bm = makeButton(0, cm, C.BLACK, 'M', 'm');
|
|
218
|
+
bm.y -= 10;
|
|
219
|
+
let bn = makeButton(0, cm, C.BLACK, 'N', 'n');
|
|
220
|
+
bn.y += rad * 2;
|
|
221
|
+
let bs = makeButton(0, cm, C.BLACK, ' ', ' ');
|
|
222
|
+
bs.y += rad * 5;
|
|
223
|
+
bpanel.addChild(bc);
|
|
224
|
+
bpanel.addChild(bv);
|
|
225
|
+
bpanel.addChild(bm);
|
|
226
|
+
bpanel.addChild(bn);
|
|
227
|
+
bpanel.addChild(bs);
|
|
228
|
+
return bpanel;
|
|
229
|
+
}
|
|
230
|
+
/** all the non-map hexes created by newHex2 */
|
|
231
|
+
newHexes = [];
|
|
232
|
+
newHex2(row = 0, col = 0, name, claz = Hex2, sy = 0) {
|
|
233
|
+
const hex = new claz(this.hexMap, row, col, name);
|
|
234
|
+
hex.distText.text = name;
|
|
235
|
+
if (row <= 0) {
|
|
236
|
+
hex.y += (sy + row * .5 - .75) * (this.hexMap.radius);
|
|
237
|
+
}
|
|
238
|
+
this.newHexes.push(hex);
|
|
239
|
+
return hex;
|
|
240
|
+
}
|
|
241
|
+
noRowHex(name, crxy, claz) {
|
|
242
|
+
const { row, col } = crxy;
|
|
243
|
+
const hex = this.newHex2(row, col, name, claz);
|
|
244
|
+
return hex;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
*
|
|
248
|
+
* @param x0 frame left (* colw); relative to scaleCont
|
|
249
|
+
* @param y0 frame top (* rowh); relative to scaleCont
|
|
250
|
+
* @param w0 pad width (* colw);
|
|
251
|
+
* @param h0 pad height (* rowh)
|
|
252
|
+
* @param dh
|
|
253
|
+
* @returns
|
|
254
|
+
*/
|
|
255
|
+
bgXYWH(x0 = -1, y0 = .5, w0 = 10, h0 = 1, dw = 0, dh = 0) {
|
|
256
|
+
const hexMap = this.hexMap;
|
|
257
|
+
// hexCont is offset to be centered on mapCont (center of hexCont is at mapCont[0,0])
|
|
258
|
+
// mapCont is offset [0,0] to scaleCont
|
|
259
|
+
const mapCont = hexMap.mapCont, hexCont = mapCont.hexCont; // local reference
|
|
260
|
+
this.scaleCont.addChild(mapCont);
|
|
261
|
+
// background sized for hexMap:
|
|
262
|
+
const { width, height } = hexCont.getBounds();
|
|
263
|
+
const { dxdc, dydr } = hexMap.xywh;
|
|
264
|
+
const xywh = { x: x0 * dxdc, y: y0 * dydr, w: width + w0 * dxdc, h: height + h0 * dydr };
|
|
265
|
+
// align center of mapCont(0,0) == hexMap(center) with center of background
|
|
266
|
+
mapCont.x = xywh.x + (xywh.w) / 2;
|
|
267
|
+
mapCont.y = xywh.y + (xywh.h) / 2;
|
|
268
|
+
xywh.w += dw * dxdc;
|
|
269
|
+
xywh.h += dh * dydr;
|
|
270
|
+
return xywh;
|
|
271
|
+
}
|
|
272
|
+
layoutTable(gamePlay) {
|
|
273
|
+
this.gamePlay = gamePlay;
|
|
274
|
+
const hexMap = this.hexMap = gamePlay.hexMap; // as AnkhMap<AnkhHex>
|
|
275
|
+
hexMap.addToMapCont(); // addToMapCont; make AnkhHex
|
|
276
|
+
hexMap.makeAllDistricts(); //
|
|
277
|
+
this.gamePlay.recycleHex = this.makeRecycleHex(TP.nHexes + 3.2);
|
|
278
|
+
const xywh = this.bgXYWH(); // override bgXYHW() to supply default/arg values
|
|
279
|
+
const hexCont = this.hexMap.mapCont.hexCont, hexp = this.scaleCont;
|
|
280
|
+
this.bgRect = this.setBackground(this.scaleCont, xywh); // bounded by xywh
|
|
281
|
+
const { x, y, width, height } = hexCont.getBounds();
|
|
282
|
+
hexCont.cache(x, y, width, height); // cache hexCont (bounded by bgr)
|
|
283
|
+
this.layoutTable2(); // supply args (mapCont?) if necessary; make overlays, score panel, extra tracks (auction...)
|
|
284
|
+
this.makePerPlayer();
|
|
285
|
+
this.setupUndoButtons(55, 60, 45, xywh); // & enableHexInspector()
|
|
286
|
+
const initialVis = false;
|
|
287
|
+
this.stage.on('drawend', () => setTimeout(() => this.toggleText(initialVis), 10), this, true);
|
|
288
|
+
this.hexMap.update();
|
|
289
|
+
// position turnLog & textLog
|
|
290
|
+
{
|
|
291
|
+
const parent = this.scaleCont, colx = -12;
|
|
292
|
+
this.setToRowCol(this.turnLog, 4, colx);
|
|
293
|
+
this.setToRowCol(this.textLog, 4, colx);
|
|
294
|
+
this.textLog.y += this.turnLog.height(Player.allPlayers.length + 1); // allow room for 1 line per player
|
|
295
|
+
parent.addChild(this.turnLog, this.textLog);
|
|
296
|
+
parent.stage.update();
|
|
297
|
+
}
|
|
298
|
+
this.namedOn("playerMoveEvent", S.add, this.gamePlay.playerMoveEvent, this.gamePlay);
|
|
299
|
+
}
|
|
300
|
+
// col locations, left-right mirrored:
|
|
301
|
+
colf(pIndex, icol, row) {
|
|
302
|
+
const dc = 10 - Math.abs(row) % 2;
|
|
303
|
+
const col = (pIndex == 0 ? (icol) : (dc - icol));
|
|
304
|
+
return { row, col };
|
|
305
|
+
}
|
|
306
|
+
layoutTable2() {
|
|
307
|
+
}
|
|
308
|
+
get panelHeight() { return (2 * TP.nHexes - 1) / 3 - .2; }
|
|
309
|
+
// col==0 is on left edge of hexMap; The *center* hex is col == (nHexes-1)
|
|
310
|
+
panelLoc(pIndex, np = Math.min(Player.allPlayers.length, 6), r0 = this.hexMap.centerHex.row, dr = this.panelHeight + .2) {
|
|
311
|
+
const nh1 = this.hexMap.centerHex.col, coff = TP.nHexes + 2;
|
|
312
|
+
const c0 = nh1 - coff, c1 = nh1 + coff;
|
|
313
|
+
const locs = [
|
|
314
|
+
[r0 - dr, c0, +1], [r0, c0, +1], [r0 + dr, c0, +1],
|
|
315
|
+
[r0 - dr, c1, -1], [r0, c1, -1], [r0 + dr, c1, -1]
|
|
316
|
+
];
|
|
317
|
+
const seq = [[], [0], [0, 3], [0, 3, 1], [0, 3, 4, 1], [0, 3, 4, 2, 1], [0, 3, 4, 5, 2, 1]];
|
|
318
|
+
const seqn = seq[np], ndx = seqn[Math.min(pIndex, np - 1)];
|
|
319
|
+
return locs[ndx];
|
|
320
|
+
}
|
|
321
|
+
allPlayerPanels = [];
|
|
322
|
+
/** make player panels, placed at locations... */
|
|
323
|
+
makePerPlayer() {
|
|
324
|
+
this.allPlayerPanels.length = 0; // TODO: maybe deconstruct
|
|
325
|
+
const high = this.panelHeight, wide = 4.5;
|
|
326
|
+
Player.allPlayers.forEach((player, pIndex) => {
|
|
327
|
+
const [row, col, dir] = this.panelLoc(pIndex);
|
|
328
|
+
this.allPlayerPanels[pIndex] = player.panel = new PlayerPanel(this, player, high, wide, row - high / 2, col - wide / 2, dir);
|
|
329
|
+
player.makePlayerBits();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/** move cont to nominal [row, col] of hexCont */
|
|
333
|
+
setToRowCol(cont, row = 0, col = 0) {
|
|
334
|
+
if (!cont.parent)
|
|
335
|
+
this.scaleCont.addChild(cont);
|
|
336
|
+
const hexCont = this.hexMap.mapCont.hexCont;
|
|
337
|
+
//if (cont.parent === hexCont) debugger;
|
|
338
|
+
const hexC = this.hexMap.centerHex;
|
|
339
|
+
const { x, y, dxdc, dydr } = hexC.xywh();
|
|
340
|
+
const xx = x + (col - hexC.col) * dxdc;
|
|
341
|
+
const yy = y + (row - hexC.row) * dydr;
|
|
342
|
+
hexCont.localToLocal(xx, yy, cont.parent, cont);
|
|
343
|
+
if (cont.parent === hexCont) {
|
|
344
|
+
cont.x = xx;
|
|
345
|
+
cont.y = yy;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
sourceOnHex(source, hex) {
|
|
349
|
+
if (source?.counter)
|
|
350
|
+
source.counter.mouseEnabled = false;
|
|
351
|
+
hex.legalMark.setOnHex(hex);
|
|
352
|
+
hex.cont.visible = false;
|
|
353
|
+
}
|
|
354
|
+
makeCircleButton(color = C.WHITE, rad = 20, c, fs = 30) {
|
|
355
|
+
const button = new Container();
|
|
356
|
+
button.name = 'circle';
|
|
357
|
+
const shape = new CircleShape(color, rad, '');
|
|
358
|
+
button.addChild(shape);
|
|
359
|
+
if (c) {
|
|
360
|
+
const t = new CenterText(c, fs);
|
|
361
|
+
t.y += 2;
|
|
362
|
+
button.addChild(t);
|
|
363
|
+
}
|
|
364
|
+
button.setBounds(-rad, -rad, rad * 2, rad * 2);
|
|
365
|
+
button.mouseEnabled = false;
|
|
366
|
+
return button;
|
|
367
|
+
}
|
|
368
|
+
makeSquareButton(color = C.WHITE, xywh, c, fs = 30) {
|
|
369
|
+
const button = new Container();
|
|
370
|
+
button.name = 'square';
|
|
371
|
+
const shape = new RectShape(xywh, color, '');
|
|
372
|
+
button.addChild(shape);
|
|
373
|
+
if (c) {
|
|
374
|
+
const t = new CenterText(c, fs);
|
|
375
|
+
t.y += 2;
|
|
376
|
+
button.addChild(t);
|
|
377
|
+
}
|
|
378
|
+
shape.mouseEnabled = false;
|
|
379
|
+
return button;
|
|
380
|
+
}
|
|
381
|
+
makeRecycleHex(row = TP.nHexes + 3.2, col = 0) {
|
|
382
|
+
const name = 'Recycle';
|
|
383
|
+
const image = new Tile(name).addImageBitmap(name); // ignore Tile, get image.
|
|
384
|
+
image.y = -TP.hexRad / 2; // recenter
|
|
385
|
+
const rHex = this.newHex2(row, col, name, Hex2);
|
|
386
|
+
this.setToRowCol(rHex.cont, row, col);
|
|
387
|
+
rHex.rcText.visible = rHex.distText.visible = false;
|
|
388
|
+
rHex.setHexColor(C.WHITE);
|
|
389
|
+
rHex.cont.addChild(image);
|
|
390
|
+
rHex.cont.updateCache();
|
|
391
|
+
return rHex;
|
|
392
|
+
}
|
|
393
|
+
doneButton;
|
|
394
|
+
doneClicked = (evt) => {
|
|
395
|
+
if (this.doneButton)
|
|
396
|
+
this.doneButton.visible = false;
|
|
397
|
+
this.gamePlay.phaseDone(); // <--- main doneButton does not supply 'panel'
|
|
398
|
+
};
|
|
399
|
+
addDoneButton(actionCont, rh) {
|
|
400
|
+
const w = 90, h = 56;
|
|
401
|
+
const doneButton = this.doneButton = new UtilButton('lightgreen', 'Done', 36, C.black);
|
|
402
|
+
doneButton.name = 'doneButton';
|
|
403
|
+
doneButton.x = -(w);
|
|
404
|
+
doneButton.y = 3 * rh;
|
|
405
|
+
doneButton.label.textAlign = 'right';
|
|
406
|
+
doneButton.on(S.click, this.doneClicked, this);
|
|
407
|
+
actionCont.addChild(doneButton);
|
|
408
|
+
// prefix advice: set text color
|
|
409
|
+
const o_cgf = doneButton.shape.cgf;
|
|
410
|
+
const cgf = (color) => {
|
|
411
|
+
const tcolor = (C.dist(color, C.WHITE) < C.dist(color, C.black)) ? C.black : C.white;
|
|
412
|
+
doneButton.label.color = tcolor;
|
|
413
|
+
return o_cgf(color);
|
|
414
|
+
};
|
|
415
|
+
doneButton.shape.cgf = cgf; // invokes shape.paint(cgf) !!
|
|
416
|
+
return actionCont;
|
|
417
|
+
}
|
|
418
|
+
setPlayerScore(plyr, score, rank) {
|
|
419
|
+
}
|
|
420
|
+
startGame(gameState) {
|
|
421
|
+
// All Tiles (& Meeple) are Dragable:
|
|
422
|
+
Tile.allTiles.forEach(tile => {
|
|
423
|
+
this.makeDragable(tile);
|
|
424
|
+
});
|
|
425
|
+
// this.stage.enableMouseOver(10);
|
|
426
|
+
this.scaleCont.addChild(this.overlayCont); // now at top of the list.
|
|
427
|
+
this.gamePlay.setNextPlayer(this.gamePlay.turnNumber > 0 ? this.gamePlay.turnNumber : 0);
|
|
428
|
+
}
|
|
429
|
+
makeDragable(tile) {
|
|
430
|
+
const dragger = this.dragger;
|
|
431
|
+
dragger.makeDragable(tile, this, this.dragFunc, this.dropFunc);
|
|
432
|
+
dragger.clickToDrag(tile, true); // also enable clickToDrag;
|
|
433
|
+
}
|
|
434
|
+
hexUnderObj(dragObj, legalOnly = true) {
|
|
435
|
+
return this.hexMap.hexUnderObj(dragObj, legalOnly);
|
|
436
|
+
}
|
|
437
|
+
dragContext;
|
|
438
|
+
dragFunc(tile, info) {
|
|
439
|
+
const hex = this.hexUnderObj(tile); // clickToDrag 'snaps' to non-original hex!
|
|
440
|
+
this.dragFunc0(tile, info, hex);
|
|
441
|
+
}
|
|
442
|
+
/** Table.dragFunc0 (Table.dragFunc to inject drag/start actions programatically)
|
|
443
|
+
* @param tile is being dragged
|
|
444
|
+
* @param info { first: boolean, mouse: MouseEvent }
|
|
445
|
+
* @param hex the Hex that tile is currently over (may be undefined or off map)
|
|
446
|
+
*/
|
|
447
|
+
dragFunc0(tile, info, hex = this.hexUnderObj(tile)) {
|
|
448
|
+
let ctx = this.dragContext;
|
|
449
|
+
if (info?.first) {
|
|
450
|
+
if (ctx?.tile) {
|
|
451
|
+
// clickToDrag intercepting a drag in progress!
|
|
452
|
+
// mouse not over drag object! fix XY in call to dragTarget()
|
|
453
|
+
console.log(stime(this, `.dragFunc: OOPS! adjust XY on dragTarget`), ctx);
|
|
454
|
+
this.stopDragging(ctx.targetHex); // stop original drag
|
|
455
|
+
this.dragger.stopDrag(); // stop new drag; this.dropFunc(ctx.tile, ctx.info);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const event = info.event?.nativeEvent;
|
|
459
|
+
tile.fromHex = tile.hex; // dragStart: set tile.fromHex when first move!
|
|
460
|
+
ctx = {
|
|
461
|
+
tile: tile,
|
|
462
|
+
targetHex: tile.fromHex,
|
|
463
|
+
lastShift: event?.shiftKey,
|
|
464
|
+
lastCtrl: event?.ctrlKey,
|
|
465
|
+
info: info,
|
|
466
|
+
nLegal: 0,
|
|
467
|
+
};
|
|
468
|
+
this.dragContext = ctx;
|
|
469
|
+
if (!tile.isDragable(ctx)) {
|
|
470
|
+
this.stopDragging(tile.fromHex); // just slide off this tile, no drag, no drop.
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
this.dragStart(tile, ctx); // canBeMoved, isLegalTarget, tile.dragStart(ctx);
|
|
474
|
+
if (!ctx.tile)
|
|
475
|
+
return; // stopDragging() was invoked
|
|
476
|
+
}
|
|
477
|
+
this.checkShift(hex, ctx);
|
|
478
|
+
tile.dragFunc0(hex, ctx);
|
|
479
|
+
}
|
|
480
|
+
// invoke dragShift 'event' if shift state changes
|
|
481
|
+
checkShift(hex, ctx) {
|
|
482
|
+
const nativeEvent = ctx.info.event?.nativeEvent;
|
|
483
|
+
ctx.lastCtrl = nativeEvent?.ctrlKey;
|
|
484
|
+
// track shiftKey because we don't pass 'event' to isLegalTarget(hex)
|
|
485
|
+
const shiftKey = nativeEvent?.shiftKey;
|
|
486
|
+
if (shiftKey !== ctx.lastShift || (hex && ctx.targetHex !== hex)) {
|
|
487
|
+
ctx.lastShift = shiftKey;
|
|
488
|
+
// do shift-down/shift-up actions...
|
|
489
|
+
this.dragShift(ctx.tile, shiftKey, ctx); // was interesting for hexmarket
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
dragStart(tile, ctx) {
|
|
493
|
+
// press SHIFT to capture [recycle] opponent's Criminals or Tiles
|
|
494
|
+
const reason = tile.cantBeMovedBy(this.gamePlay.curPlayer, ctx);
|
|
495
|
+
if (reason) {
|
|
496
|
+
console.log(stime(this, `.dragStart: ${reason}: ${tile},`), 'ctx=', { ...ctx });
|
|
497
|
+
// this.logText(`${reason}: ${tile}`, 'Table.dragStart');
|
|
498
|
+
this.stopDragging();
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
// mark legal targets for tile; SHIFT for all hexes, if payCost
|
|
502
|
+
tile.dragStart(ctx); // prepare for isLegalTarget
|
|
503
|
+
const countLegalHexes = (hex) => {
|
|
504
|
+
if (hex !== tile.hex && tile.isLegalTarget(hex, ctx)) {
|
|
505
|
+
hex.isLegal = true;
|
|
506
|
+
ctx.nLegal += 1;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
tile.markLegal(this, countLegalHexes, ctx); // delegate to check each potential target
|
|
510
|
+
this.gamePlay.recycleHex.isLegal = tile.isLegalRecycle(ctx); // do not increment ctx.nLegal!
|
|
511
|
+
tile.moveTo(undefined); // notify source Hex, so it can scale; also triggers nextUnit !!
|
|
512
|
+
this.hexMap.update();
|
|
513
|
+
if (ctx.nLegal === 0) {
|
|
514
|
+
tile.noLegal();
|
|
515
|
+
if (!this.gamePlay.recycleHex.isLegal) {
|
|
516
|
+
this.stopDragging(); // actually, maybe let it drag, so we can see beneath...
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/** state of shiftKey has changed during drag */
|
|
522
|
+
dragShift(tile, shiftKey, ctx) {
|
|
523
|
+
tile?.dragShift(shiftKey, ctx);
|
|
524
|
+
}
|
|
525
|
+
dropFunc(dobj, info, hex = this.hexUnderObj(dobj)) {
|
|
526
|
+
const tile = dobj;
|
|
527
|
+
tile.dropFunc0(hex, this.dragContext);
|
|
528
|
+
tile.markLegal(this); // hex => hex.isLegal = false;
|
|
529
|
+
this.gamePlay.recycleHex.isLegal = false;
|
|
530
|
+
this.dragContext.lastShift = undefined;
|
|
531
|
+
this.dragContext.tile = undefined; // mark not dragging
|
|
532
|
+
}
|
|
533
|
+
/** synthesize dragStart(tile), tile.dragFunc0(hex), dropFunc(tile); */
|
|
534
|
+
dragStartAndDrop(tile, toHex) {
|
|
535
|
+
if (!tile)
|
|
536
|
+
return; // C-q when no EventTile on eventHex
|
|
537
|
+
const info = { first: true }, hex = toHex;
|
|
538
|
+
this.dragFunc0(tile, info, tile.hex); // dragStart()
|
|
539
|
+
tile.dragFunc0(hex, this.dragContext);
|
|
540
|
+
this.dropFunc(tile, info, hex);
|
|
541
|
+
}
|
|
542
|
+
isDragging() { return this.dragContext?.tile !== undefined; }
|
|
543
|
+
/** Force this.dragger to drop the current drag object on given target Hex */
|
|
544
|
+
stopDragging(target = this.dragContext?.tile?.fromHex) {
|
|
545
|
+
//console.log(stime(this, `.stopDragging: dragObj=`), this.dragger.dragCont.getChildAt(0), {noMove, isDragging: this.isDragging()})
|
|
546
|
+
if (this.isDragging()) {
|
|
547
|
+
if (target)
|
|
548
|
+
this.dragContext.targetHex = target;
|
|
549
|
+
this.dragger.stopDrag(); // ---> dropFunc(this.dragContext.tile, info)
|
|
550
|
+
}
|
|
551
|
+
const data = this.dragger.getDragData(this.scaleCont);
|
|
552
|
+
if (data)
|
|
553
|
+
data.dragStopped = true;
|
|
554
|
+
}
|
|
555
|
+
/** Toggle dragging: dragTarget(target) OR stopDragging(targetHex)
|
|
556
|
+
* - attach supplied target to mouse-drag (default is eventHex.tile)
|
|
557
|
+
* @param target the DisplayObject being dragged
|
|
558
|
+
* @param xy offset from target to mouse pointer
|
|
559
|
+
*/
|
|
560
|
+
dragTarget(target = this.gamePlay.recycleHex.tile, xy = { x: TP.hexRad / 2, y: TP.hexRad / 2 }) {
|
|
561
|
+
if (this.isDragging()) {
|
|
562
|
+
this.stopDragging(this.dragContext.targetHex); // drop and make move
|
|
563
|
+
}
|
|
564
|
+
else if (target) {
|
|
565
|
+
this.dragger.dragTarget(target, xy);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
logCurPlayer(plyr) {
|
|
569
|
+
const history = this.gamePlay.history;
|
|
570
|
+
const tn = this.gamePlay.turnNumber;
|
|
571
|
+
const lm = history[0];
|
|
572
|
+
const prev = lm ? `${lm.Aname}${lm.ind}#${tn - 1}` : "";
|
|
573
|
+
const robo = plyr.useRobo ? AT.ansiText(['red', 'bold'], "robo") : "----";
|
|
574
|
+
const info = { turn: tn, plyr: plyr.Aname, prev, gamePlay: this.gamePlay, curPlayer: plyr };
|
|
575
|
+
console.log(stime(this, `.logCurPlayer --${robo}--`), info);
|
|
576
|
+
this.logTurn(`//${tn}: ${plyr.Aname}`);
|
|
577
|
+
}
|
|
578
|
+
showRedoUndoCount() {
|
|
579
|
+
this.undoText.text = `${this.gamePlay.undoRecs.length}`;
|
|
580
|
+
this.redoText.text = `${this.gamePlay.redoMoves.length}`;
|
|
581
|
+
}
|
|
582
|
+
showNextPlayer(log = true) {
|
|
583
|
+
let curPlayer = this.gamePlay.curPlayer; // after gamePlay.setNextPlayer()
|
|
584
|
+
if (log)
|
|
585
|
+
this.logCurPlayer(curPlayer);
|
|
586
|
+
this.showRedoUndoCount();
|
|
587
|
+
}
|
|
588
|
+
_tablePlanner;
|
|
589
|
+
get tablePlanner() {
|
|
590
|
+
return this._tablePlanner ||
|
|
591
|
+
(this._tablePlanner = new TablePlanner(this.gamePlay));
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* All manual moves feed through this (drop & redo)
|
|
595
|
+
* TablePlanner.logMove(); then dispatchEvent() --> gamePlay.doPlayerMove()
|
|
596
|
+
*
|
|
597
|
+
* New: let Ship (Drag & Drop) do this.
|
|
598
|
+
*/
|
|
599
|
+
doTableMove(ihex) {
|
|
600
|
+
}
|
|
601
|
+
/** All moves (GUI & player) feed through this: */
|
|
602
|
+
moveStoneToHex(ihex, sc) {
|
|
603
|
+
// let hex = Hex.ofMap(ihex, this.hexMap)
|
|
604
|
+
// this.hexMap.showMark(hex)
|
|
605
|
+
// this.dispatchEvent(new HexEvent(S.add, hex, sc)) // -> GamePlay.playerMoveEvent(hex, sc)
|
|
606
|
+
}
|
|
607
|
+
/** default scaling-up value */
|
|
608
|
+
upscale = 1.5;
|
|
609
|
+
/** change cont.scale to given scale value. */
|
|
610
|
+
scaleUp(cont, scale = this.upscale) {
|
|
611
|
+
cont.scaleX = cont.scaleY = scale;
|
|
612
|
+
}
|
|
613
|
+
scaleParams = { initScale: .125, scale0: .05, scaleMax: 4, steps: 30, zscale: .20, };
|
|
614
|
+
scaleCont;
|
|
615
|
+
/** makeScaleableBack and setup scaleParams
|
|
616
|
+
* @param bindkeys true if there's a GUI/user/keyboard
|
|
617
|
+
*/
|
|
618
|
+
makeScaleCont(bindKeys) {
|
|
619
|
+
/** scaleCont: a scalable background */
|
|
620
|
+
const scaleC = new ScaleableContainer2(this.stage, this.scaleParams);
|
|
621
|
+
this.dragger = new Dragger(scaleC);
|
|
622
|
+
if (!!scaleC.stage.canvas) {
|
|
623
|
+
// Special case of makeDragable; drag the parent of Dragger!
|
|
624
|
+
this.dragger.makeDragable(scaleC, scaleC, undefined, undefined, true); // THE case where not "useDragCont"
|
|
625
|
+
//this.scaleUp(Dragger.dragCont, 1.7); // Items being dragged appear larger!
|
|
626
|
+
}
|
|
627
|
+
if (bindKeys) {
|
|
628
|
+
this.bindKeysToScale(scaleC, "a", 436, 2);
|
|
629
|
+
KeyBinder.keyBinder.setKey('Space', { thisArg: this, func: () => this.dragTarget() });
|
|
630
|
+
KeyBinder.keyBinder.setKey('S-Space', { thisArg: this, func: () => this.dragTarget() });
|
|
631
|
+
KeyBinder.keyBinder.setKey('t', { thisArg: this, func: () => { this.toggleText(); } });
|
|
632
|
+
}
|
|
633
|
+
return scaleC;
|
|
634
|
+
}
|
|
635
|
+
/** put a Rectangle Shape at (0,0) with XYWH bounds as given */
|
|
636
|
+
setBackground(parent, bounds, bgColor = TP.bgColor) {
|
|
637
|
+
// specify an Area that is Dragable (mouse won't hit "empty" space)
|
|
638
|
+
const bgRect = new RectShape(bounds, bgColor, '');
|
|
639
|
+
bgRect.Aname = "BackgroundRect";
|
|
640
|
+
parent.addChildAt(bgRect, 0);
|
|
641
|
+
return bgRect;
|
|
642
|
+
}
|
|
643
|
+
zoom(z = 1.1) {
|
|
644
|
+
const stage = this.stage;
|
|
645
|
+
const pxy = { x: stage.mouseX / stage.scaleX, y: stage.mouseY / stage.scaleY };
|
|
646
|
+
this.scaleCont.setScale(this.scaleCont.scaleX * z, pxy);
|
|
647
|
+
// would require adjusting x,y offsets, so we just scale directly:
|
|
648
|
+
// TODO: teach ScaleableContainer to check scaleC.x,y before scroll-zooming.
|
|
649
|
+
// this.scaleCont.scaleX = this.scaleCont.scaleY = this.scaleCont.scaleX * z;
|
|
650
|
+
this.stage?.update();
|
|
651
|
+
}
|
|
652
|
+
pan(xy) {
|
|
653
|
+
this.scaleCont.x += xy.x;
|
|
654
|
+
this.scaleCont.y += xy.y;
|
|
655
|
+
this.stage?.update();
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* invoked before this.scaleC has been set
|
|
659
|
+
* @param scaleC same Container as this.scaleC
|
|
660
|
+
* @param char keybinding to set initial scale
|
|
661
|
+
* @param xos x-offset of scaleC in screen coords (pre-scale)
|
|
662
|
+
* @param yos y-offset of scaleC in screen coords (pre-scale)
|
|
663
|
+
* @param scale0 imitial scale [.5]
|
|
664
|
+
*/
|
|
665
|
+
// bindKeysToScale('a', scaleC, 436, 0, .5)
|
|
666
|
+
bindKeysToScale(scaleC, char, xos, yos, scale0 = .5) {
|
|
667
|
+
const nsA = scale0;
|
|
668
|
+
const apt = { x: xos, y: yos };
|
|
669
|
+
let nsZ = 0.647; //
|
|
670
|
+
const zpt = { x: 120, y: 118 };
|
|
671
|
+
// set Keybindings to reset Scale:
|
|
672
|
+
/** save scale & offsets for later: */
|
|
673
|
+
const saveScaleZ = () => {
|
|
674
|
+
nsZ = scaleC.scaleX;
|
|
675
|
+
zpt.x = scaleC.x;
|
|
676
|
+
zpt.y = scaleC.y;
|
|
677
|
+
};
|
|
678
|
+
// xy is the fixed point, but is ignored because we set xy directly.
|
|
679
|
+
// sxy is the final xy offset, saved by saveScaleZ()
|
|
680
|
+
const setScaleXY = (ns, sxy = { x: 0, y: 0 }) => {
|
|
681
|
+
scaleC.setScale(ns);
|
|
682
|
+
//console.log({si, ns, xy, sxy, cw: this.canvas.width, iw: this.map_pixels.width})
|
|
683
|
+
scaleC.x = sxy.x;
|
|
684
|
+
scaleC.y = sxy.y;
|
|
685
|
+
this.stage.update();
|
|
686
|
+
};
|
|
687
|
+
const getOop = () => {
|
|
688
|
+
this.stage.getObjectsUnderPoint(500, 100, 1);
|
|
689
|
+
};
|
|
690
|
+
// Scale-setting keystrokes:
|
|
691
|
+
KeyBinder.keyBinder.setKey("a", { func: () => setScaleXY(nsA, apt) });
|
|
692
|
+
KeyBinder.keyBinder.setKey("z", { func: () => setScaleXY(nsZ, zpt) });
|
|
693
|
+
KeyBinder.keyBinder.setKey("x", { func: () => saveScaleZ() });
|
|
694
|
+
KeyBinder.keyBinder.setKey("p", { func: () => getOop(), thisArg: this });
|
|
695
|
+
KeyBinder.keyBinder.setKey('S-ArrowUp', { thisArg: this, func: this.zoom, argVal: 1.03 });
|
|
696
|
+
KeyBinder.keyBinder.setKey('S-ArrowDown', { thisArg: this, func: this.zoom, argVal: 1 / 1.03 });
|
|
697
|
+
KeyBinder.keyBinder.setKey('S-ArrowLeft', { thisArg: this, func: this.pan, argVal: { x: -10, y: 0 } });
|
|
698
|
+
KeyBinder.keyBinder.setKey('ArrowRight', { thisArg: this, func: this.pan, argVal: { x: 10, y: 0 } });
|
|
699
|
+
KeyBinder.keyBinder.setKey('ArrowLeft', { thisArg: this, func: this.pan, argVal: { x: -10, y: 0 } });
|
|
700
|
+
KeyBinder.keyBinder.setKey('S-ArrowRight', { thisArg: this, func: this.pan, argVal: { x: 10, y: 0 } });
|
|
701
|
+
KeyBinder.keyBinder.setKey('ArrowUp', { thisArg: this, func: this.pan, argVal: { x: 0, y: -10 } });
|
|
702
|
+
KeyBinder.keyBinder.setKey('ArrowDown', { thisArg: this, func: this.pan, argVal: { x: 0, y: 10 } });
|
|
703
|
+
KeyBinder.keyBinder.dispatchChar(char);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
class ScaleableContainer2 extends ScaleableContainer {
|
|
707
|
+
}
|