-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutilities_common.js
393 lines (352 loc) · 14.3 KB
/
utilities_common.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
function isGameInProgress() {
return game.outcome === 0;
}
function isPhaseEncounter() {
return game.phase === 0;
}
function isPhaseManeuver() {
return game.phase === 1;
}
function isPhaseAttack() {
return game.phase === 2;
}
function isPhaseCounterAttack() {
return game.phase === 3;
}
function isPhaseWrapUp() {
return game.phase === 4;
}
function getCardImagePathForSquadMember(x, y, z) {
return ui.imageMap.squad[game.grid[x][y][z].name]["up"].card;
}
function getIconImageForSquadMember(x, y, z) {
let upStatus = game.grid[x][y][z].getUpStatusString();
return ui.imageMap.squad[game.grid[x][y][z].name][upStatus].icon;
}
function playSound(sound) {
// Don't play the sound if it hasn't loaded yet
if (sound) {
sound.playMode("restart");
sound.play();
}
}
function playSoundFromList(soundList) {
if (soundList && soundList.length > 0) {
const index = Math.floor(Math.random() * soundList.length);
playSound(soundList[index]);
}
}
function playSoundThreatSelect(threatType) {
if (!isGameInProgress()) {
return;
}
playSoundFromList(SOUND_MAPPING.SELECT_THREAT[threatType]);
}
function playSoundSquadSelect(squadMember) {
if (!squadMember.up || !isGameInProgress()) {
return;
}
playSoundFromList(SOUND_MAPPING.SELECT_SQUAD[squadMember.name][game.phase]);
}
// Given a list of map cards, this returns the index of the first map card
// that contains a specific value of interest. Returns -1 if no map cards were found.
function findMapCardWithValue(mapCardList, valueToFind) {
for (let i = 0; i < mapCardList.length; i++) {
if (mapCardList[i].includes(valueToFind)) {
return i;
}
}
return -1;
}
// This is using the Fisher-Yates shuffle algorithm
function shuffleListNonDestructively(list, finalOffset = 0) {
for (let i = 0; i < list.length; i++) {
let newIndex = Math.floor(Math.random() * (i + 1));
[list[i], list[newIndex]] = [list[newIndex], list[i]];
}
if (finalOffset > 0) {
return list.slice(0, -finalOffset);
}
return list;
}
function getRelativeMapCoordinate(startX, startY, finishX, finishY) {
return new MapCoordinate(finishX - startX, finishY - startY);
}
// Destructively modifies the grid object to remove all friendlies at the given cell
function removeFriendliesAt(column, row) {
for (let i = 0; i < game.grid[column][row].length; i++) {
if (game.grid[column][row][i].isFriendly > 0) {
game.grid[column][row].splice(i, 1);
increaseFinalTurns();
break;
}
}
}
// Returns whether the given cell contains an object with a particular friendly index
function containsFriendlyIndex(column, row, friendlyIndex) {
for (let i = 0; i < game.grid[column][row].length; i++) {
if (game.grid[column][row][i].isFriendly === friendlyIndex) {
return true;
}
}
return false;
}
// Returns the index of the first friendly entity in a given cell (-1 if there are none)
function getFriendlyIndexAt(column, row, friendlyIndex) {
for (let i = 0; i < game.grid[column][row].length; i++) {
if (game.grid[column][row][i].isFriendly === friendlyIndex) {
return i;
}
}
return -1;
}
// Returns the cell index at a given coordinate that has a specific threat type. Otherwise, returns -1
function containsThreatType(column, row, threatType) {
for (let i = 0; i < game.grid[column][row].length; i++) {
if (game.grid[column][row][i].isFriendly < 0 && game.grid[column][row][i].type === threatType) {
return i;
}
}
return -1;
}
// Returns the first squad index that matches the provided name
function getSquadMemberIndexWithName(name) {
for (let i = 0; i < game.squad.length; i++) {
if (game.squad[i].name === name) {
return i;
}
}
return -1;
}
// Returns all the threats at the given coordinates
function getAllThreatsAt(column, row) {
let threatsAtCoordinate = [];
for (let i = 0; i < game.grid[column][row].length; i++) {
if (game.grid[column][row][i].isFriendly < 0) {
threatsAtCoordinate.push(game.grid[column][row][i]);
}
}
return threatsAtCoordinate;
}
function canThreatMoveToNewColumn(threat, x, y) {
if (x > game.grid.length || x < 0 || !game.grid[x]) {
// The Y coordinate is handled outside of this function and doesn't need to be checked
console.debug(`${threat.name} cannot move to ${x},${y} because it is out of bounds`);
return false;
}
// Disable column changing using a base condition
let canMoveToNewCell = !threat.isFixedColumn();
for (let ci = 0; ci < game.grid[x][y].length && canMoveToNewCell; ci++) {
if ((game.grid[x][y][ci].isFriendly > 0 && !threat.canOverlapWithSquad) ||
(game.grid[x][y][ci].isFriendly < 0 && !threat.canOverlapWithSquad && !game.grid[x][y][ci].canOverlapWithSquad)) {
console.debug(`Cannot move ${threat.name} to new cell`);
canMoveToNewCell = false;
break;
}
}
return canMoveToNewCell;
}
function canFlipUp(x, y, z) {
if (game.grid[x][y][z].up) {
console.debug(`Not able to flip up because ${game.grid[x][y][z].name} is already up`);
return false;
}
if (game.grid[x][y][z].rotated) {
console.debug(`Not able to flip up because ${game.grid[x][y][z].name} is rotated`);
return false;
}
if (game.grid[x][y][z].canFlipUpWithoutSquadMember()) {
console.debug("The Leader can flip up by themselves");
return true;
}
let adjacentLeaders = filterAdjacentFriendliesAt(x, y, true, SquadMember.type["The Leader"], true);
if (adjacentLeaders.length > 0) {
console.debug("The Leader is nearby! Able to flip up from a diagonal position in addition to the normal flip up rules.");
return true;
}
let adjacentSquares = getAdjacentMapCoordinates(x, y, false, false);
for (let i = 0; i < adjacentSquares.length; i++) {
console.debug(`Checking ${adjacentSquares[i].x},${adjacentSquares[i].y}`);
let adjacentFriendlyIndex = getFriendlyIndexAt(adjacentSquares[i].x, adjacentSquares[i].y, SquadMember.friendlyIndex);
if (adjacentFriendlyIndex >= 0 && game.grid[adjacentSquares[i].x][adjacentSquares[i].y][adjacentFriendlyIndex].up) {
return true;
}
}
console.debug("Not able to flip up because there is no adjacent squad member who is up");
return false;
}
function getAdjacentMapCoordinates(startX, startY, includeSelf, includeDiagonals) {
let coordinates = [
new MapCoordinate(startX, startY - 1),
new MapCoordinate(startX + 1, startY),
new MapCoordinate(startX, startY + 1),
new MapCoordinate(startX - 1, startY),
];
if (includeSelf) {
coordinates.push(new MapCoordinate(startX, startY));
}
if (includeDiagonals) {
coordinates.push(new MapCoordinate(startX + 1, startY - 1),
new MapCoordinate(startX + 1, startY + 1),
new MapCoordinate(startX - 1, startY + 1),
new MapCoordinate(startX - 1, startY - 1));
}
for (let c = 0; c < coordinates.length; c++) {
// Remember that the first column and row is reserved
if (coordinates[c].x <= 0 || coordinates[c].x >= game.grid.length ||
coordinates[c].y <= 0 || coordinates[c].y >= game.grid[0].length) {
coordinates.splice(c, 1);
c--;
}
}
return coordinates;
}
function getMovementCoordinates(startX, startY) {
let coordinates = getAdjacentMapCoordinates(startX, startY, false, true);
let coordinatesValid = [];
for (let c = 0; c < coordinates.length; c++) {
if (containsFriendlyIndex(coordinates[c].x, coordinates[c].y, Threat.friendlyIndex)) {
// Space is occupied by a threat
console.debug("Space is occupied by a threat");
continue;
}
let friendlyIndex = getFriendlyIndexAt(coordinates[c].x, coordinates[c].y, SquadMember.friendlyIndex);
if (friendlyIndex >= 0 && (!game.grid[coordinates[c].x][coordinates[c].y][friendlyIndex].isMovable() || !game.grid[coordinates[c].x][coordinates[c].y][friendlyIndex].up)) {
// Space is occupied by a immovable squad member (remember that squad members who are down cannot move at all)
console.debug("Space is occupied by a immovable squad member");
continue;
}
// Exclude diagonal spaces that are cut off by threats
let relativeMapCoordinate = getRelativeMapCoordinate(startX, startY, coordinates[c].x, coordinates[c].y);
if ((relativeMapCoordinate.x === -1 && relativeMapCoordinate.y === -1) ||
(relativeMapCoordinate.x === 1 && relativeMapCoordinate.y === -1) ||
(relativeMapCoordinate.x === -1 && relativeMapCoordinate.y === 1) ||
(relativeMapCoordinate.x === 1 && relativeMapCoordinate.y === 1)) {
console.debug(`Testing diagonal square on ${coordinates[c].x},${coordinates[c].y}`);
console.debug(`using vector ${relativeMapCoordinate.x},${relativeMapCoordinate.y}`);
console.debug(`which means ${startX + relativeMapCoordinate.x},${startY}`);
console.debug(`and ${startX},${startY + relativeMapCoordinate.y}`);
// Since the start cell and relative coordinate are valid positions in the grid,
// it can be inferred that the adjacent coordinates shared by them are also valid
// (unless a non-qualdrilateral grid is allowed, then this would have to be revisited)
if (containsFriendlyIndex(startX + relativeMapCoordinate.x, startY, Threat.friendlyIndex) &&
containsFriendlyIndex(startX, startY + relativeMapCoordinate.y, Threat.friendlyIndex)) {
console.debug("Space is blocked off by adjacent threats");
continue;
}
}
coordinatesValid.push(coordinates[c]);
}
return coordinatesValid;
}
function getAttackCoordinates(startX, startY, includeDiagonals) {
let coordinates = getAdjacentMapCoordinates(startX, startY, false, includeDiagonals);
let coordinatesValid = [];
for (let c = 0; c < coordinates.length; c++) {
if (containsFriendlyIndex(coordinates[c].x, coordinates[c].y, Threat.friendlyIndex)) {
console.debug(`${coordinates[c].x}, ${coordinates[c].y} can be attacked`);
coordinatesValid.push(coordinates[c]);
}
}
return coordinatesValid;
}
// Returns a list of adjacent SquadMember objects at the given coordinates
function getAdjacentFriendliesAt(column, row, includeDiagonals) {
let adjacentSquares = getAdjacentMapCoordinates(column, row, false, includeDiagonals);
let friendlyList = [];
for (let i = 0; i < adjacentSquares.length; i++) {
let friendlyIndex = getFriendlyIndexAt(adjacentSquares[i].x, adjacentSquares[i].y, SquadMember.friendlyIndex);
if (friendlyIndex >= 0) {
friendlyList.push(game.grid[adjacentSquares[i].x][adjacentSquares[i].y][friendlyIndex]);
}
}
return friendlyList;
}
// Returns a list of adjacent SquadMember objects that meet certain filter criteria at the given coordinates
function filterAdjacentFriendliesAt(column, row, includeDiagonals, filterType, filterUp) {
let adjacentFriendlies = getAdjacentFriendliesAt(column, row, includeDiagonals);
let filteredFriendlies = [];
for (let i = 0; i < adjacentFriendlies.length; i++) {
if (adjacentFriendlies[i].type & filterType && adjacentFriendlies[i].up === filterUp) {
filteredFriendlies.push(adjacentFriendlies[i]);
}
}
return filteredFriendlies;
}
function increaseFinalTurns() {
// EXTRA: Players gain extra final turns with fewer squad members
if (!game.isInFinalTurns && game.settings.FINAL_TURNS_INCREASE_FROM_DEFEATED_SQUAD_MEMBERS) {
game.finalTurnsRemaining++;
}
}
// Returns the HTML text for a <div> tag with a provided CSS class and the inner text
function getHTMLTextDivWithClass(divText, divClass) {
return `<div class="${divClass}">${divText}</div>`;
}
function setTurnLabelText() {
ui.turnLabel.html(`Turn ${game.turn}`);
}
function isSettingsPromptActive() {
return document.getElementById("ui-settings-prompt").style.display === "block";
}
function generateButton(buttonX, buttonY, buttonWide, buttonHigh, buttonText, buttonCallback, isActive, buttonExtraCssClass = "") {
let uiButton = createButton(buttonText);
uiButton.position(buttonX, buttonY);
uiButton.size(buttonWide, buttonHigh);
uiButton.mousePressed(buttonCallback);
uiButton.addClass(`ui-button ui-element ${buttonExtraCssClass}`);
if (isActive) {
uiButton.show();
} else {
uiButton.hide();
}
return uiButton;
}
function generateParagraphText(paragraphX, paragraphY, paragraphText, paragraphClass, isActive) {
let uiParagraphText = createP(paragraphText);
uiParagraphText.position(paragraphX, paragraphY);
uiParagraphText.addClass(`ui-paragraph ui-element ${paragraphClass}`);
if (isActive) {
uiParagraphText.show();
} else {
uiParagraphText.hide();
}
return uiParagraphText;
}
function generateCardImage(imageX, imageY, imagePath, imageAltText, isActive) {
let uiCardImage = createImg(imagePath, imageAltText);
uiCardImage.addClass("ui-card-image ui-element");
uiCardImage.position(imageX, imageY);
if (isActive) {
uiCardImage.show();
} else {
uiCardImage.hide();
}
return uiCardImage;
}
function updateCardDescription(description, imagePath, cardDescriptionIndex, frameColour, useGrayscale) {
// Only update the HTML if there is an actual change (includes toggling of the same description after pressing an empty square)
// The same logic could be independently applied to the card image too, but the image is usually tied to the description anyway
if (ui.cardDescriptions[cardDescriptionIndex].description.html() !== description || ui.cardDescriptions[cardDescriptionIndex].description.style("display") === "none") {
ui.cardDescriptions[cardDescriptionIndex].description.html(description);
ui.cardDescriptions[cardDescriptionIndex].image.attribute("src", imagePath);
if (frameColour.length > 0) {
ui.cardDescriptions[cardDescriptionIndex].image.style("border", `${ui.lineThicknessCardDescriptionBorder} solid ${frameColour}`);
} else {
ui.cardDescriptions[cardDescriptionIndex].image.style("border", "none");
}
if (useGrayscale) {
grayscale = 100;
} else {
grayscale = 0;
}
ui.cardDescriptions[cardDescriptionIndex].image.style("filter", `grayscale(${grayscale}%)`);
// Show the new contents
ui.cardDescriptions[cardDescriptionIndex].description.show();
ui.cardDescriptions[cardDescriptionIndex].image.show();
}
}
// Module exports should only be set when running unit tests, as this causes a console error when running the sketch
if (typeof exports !== "undefined") {
module.exports = { findMapCardWithValue, shuffleListNonDestructively };
}