|
|
|
@ -1572,6 +1572,9 @@ class Game { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Base class for orb placer. |
|
|
|
|
*/ |
|
|
|
|
class BaseOrbPlacer { |
|
|
|
|
constructor(game, template) { |
|
|
|
|
this.template = template; |
|
|
|
@ -1581,6 +1584,12 @@ class BaseOrbPlacer { |
|
|
|
|
this.board = game.board; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Place an orb. The position must be inside template and free. |
|
|
|
|
* |
|
|
|
|
* @param n - position |
|
|
|
|
* @param symbol - symbol to place |
|
|
|
|
*/ |
|
|
|
|
placeOrb(n, symbol) { |
|
|
|
|
if (!this.templateMap[n]) { |
|
|
|
|
throw Error(`Position ${n} not allowed by template`); |
|
|
|
@ -1592,6 +1601,11 @@ class BaseOrbPlacer { |
|
|
|
|
this.board.grid[n] = symbol; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Remove an orb, if any. |
|
|
|
|
* |
|
|
|
|
* @param n - position |
|
|
|
|
*/ |
|
|
|
|
removeOrb(n) { |
|
|
|
|
let old = this.board.grid[n]; |
|
|
|
|
this.board.grid[n] = null; |
|
|
|
@ -1599,10 +1613,12 @@ class BaseOrbPlacer { |
|
|
|
|
return old; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Check if a cell is available for selection - 3 free slots */ |
|
|
|
|
isAvailable(n) { |
|
|
|
|
return this.board.isAvailable(n); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Get cell info by number */ |
|
|
|
|
getCellInfo(n) { |
|
|
|
|
return this.board.getCellInfo(n); |
|
|
|
|
} |
|
|
|
@ -1627,6 +1643,13 @@ class BaseOrbPlacer { |
|
|
|
|
this.game.error(...args); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Build a list of orbs in the right order. |
|
|
|
|
* They are always grouped in matching pairs, and metals are always in |
|
|
|
|
* the correct reaction order. |
|
|
|
|
* |
|
|
|
|
* @return {string[]} |
|
|
|
|
*/ |
|
|
|
|
buildPlacementList() { |
|
|
|
|
let toPlace = [ |
|
|
|
|
['air', 'air'], |
|
|
|
@ -1713,7 +1736,9 @@ class BaseOrbPlacer { |
|
|
|
|
}, []); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Run the placement logic. |
|
|
|
|
*/ |
|
|
|
|
place() { |
|
|
|
|
this.board.removeAllOrbs(); |
|
|
|
|
this.solution = []; |
|
|
|
@ -1747,7 +1772,8 @@ class BaseOrbPlacer { |
|
|
|
|
this.placeOrb(60, 'gold'); |
|
|
|
|
this.solution.push(['gold', 60]); |
|
|
|
|
|
|
|
|
|
let rv = this.doPlace(); |
|
|
|
|
let toPlace = this.buildPlacementList(); |
|
|
|
|
let rv = this.doPlace(toPlace); |
|
|
|
|
|
|
|
|
|
let solution = this.solution; |
|
|
|
|
|
|
|
|
@ -1765,12 +1791,42 @@ class BaseOrbPlacer { |
|
|
|
|
return rv; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
doPlace() { |
|
|
|
|
/** |
|
|
|
|
* Perform the orbs placement. |
|
|
|
|
* |
|
|
|
|
* Orb symbols to place are given as an argument. |
|
|
|
|
* |
|
|
|
|
* The layout template is in `this.template`, also `this.templateMap` as an array |
|
|
|
|
* with indices 0-121 containing true/false to indicate template membership. |
|
|
|
|
* |
|
|
|
|
* `this.outsideTemplate` is a list of cell indices that are not in the template. |
|
|
|
|
* |
|
|
|
|
* this.solution is an array to populate with orb placements in the format [n, symbol]. |
|
|
|
|
* |
|
|
|
|
* The board is cleared now. When the function ends, the board should contain |
|
|
|
|
* the new layout (as symbol name strings) |
|
|
|
|
* |
|
|
|
|
* After placing each orb, make sure to call `this.solution.push([symbol, index])`; |
|
|
|
|
* in case of backtracking, pop it again. |
|
|
|
|
* |
|
|
|
|
* Return object to return to parent; 'solution' will be added automatically. |
|
|
|
|
*/ |
|
|
|
|
doPlace(toPlace) { |
|
|
|
|
throw new Error("Not implemented"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Orb placement algorithm that starts in the center and places orbs in rings, with some |
|
|
|
|
* small jitter allowed. |
|
|
|
|
*/ |
|
|
|
|
class RadialOrbPlacer extends BaseOrbPlacer { |
|
|
|
|
/** |
|
|
|
|
* Find a candidate cell |
|
|
|
|
* |
|
|
|
|
* @param {number[]|null} except - indices to exclude |
|
|
|
|
* @return {number} |
|
|
|
|
*/ |
|
|
|
|
findBestCandidate(except = null) { |
|
|
|
|
let candidates = []; |
|
|
|
|
for (let n of this.template) { |
|
|
|
@ -1810,6 +1866,12 @@ class RadialOrbPlacer extends BaseOrbPlacer { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Find index for next placement |
|
|
|
|
* |
|
|
|
|
* @param {number[]|null} except - indices to exclude |
|
|
|
|
* @return {number} - index |
|
|
|
|
*/ |
|
|
|
|
findAvailableIndex(except = null) { |
|
|
|
|
const n = this.findBestCandidate(except); |
|
|
|
|
if (n !== false) return n; |
|
|
|
@ -1846,9 +1908,8 @@ class RadialOrbPlacer extends BaseOrbPlacer { |
|
|
|
|
throw Error("Failed to find available board tile."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
doPlace() { |
|
|
|
|
doPlace(toPlace) { |
|
|
|
|
this.tilesAdded = 0; |
|
|
|
|
const toPlace = this.buildPlacementList(); |
|
|
|
|
|
|
|
|
|
while (toPlace.length > 0) { |
|
|
|
|
this.trace('placing a pair.'); |
|
|
|
|