|
|
|
@ -113,6 +113,7 @@ class Board { |
|
|
|
|
// Orb grid
|
|
|
|
|
this.grid = []; |
|
|
|
|
this.tiles = []; |
|
|
|
|
this.buttons = {}; |
|
|
|
|
|
|
|
|
|
for (let i = 0; i < BOARD_SIZE; i++) { |
|
|
|
|
this.grid[i] = null; |
|
|
|
@ -132,6 +133,8 @@ class Board { |
|
|
|
|
|
|
|
|
|
this.buildBackground(); |
|
|
|
|
|
|
|
|
|
this.buildGUI(); |
|
|
|
|
|
|
|
|
|
this.initAutoScaling(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -219,6 +222,19 @@ class Board { |
|
|
|
|
return {rx, ry}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Convert GUI coordinates to graphic coordinates |
|
|
|
|
* |
|
|
|
|
* @param {Number} x |
|
|
|
|
* @param {Number} y |
|
|
|
|
* @returns {{rx: number, ry: number}} |
|
|
|
|
*/ |
|
|
|
|
guiXyToCoord(x, y) { |
|
|
|
|
let rx = this.TILE_W * (-5.9 + x); |
|
|
|
|
let ry = this.TILE_H * (-5.5 + y); |
|
|
|
|
return {rx, ry}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Remove an orb from the grid at the given coordinates. |
|
|
|
|
* |
|
|
|
@ -598,6 +614,36 @@ class Board { |
|
|
|
|
getOrbByIndex(n) { |
|
|
|
|
return this.grid[n]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
buildGUI() { |
|
|
|
|
const y0 = 0.05; |
|
|
|
|
const x0 = 0; |
|
|
|
|
const ysp = 0.75; |
|
|
|
|
const ysp2 = 0.6; |
|
|
|
|
|
|
|
|
|
this.buttons.randomize = this.addButton(x0, y0, 'Randomize'); |
|
|
|
|
this.buttons.restart = this.addButton(x0, y0 + ysp, 'Try Again', 'disabled'); |
|
|
|
|
// this.buttons.undo = this.addButton(x0, y0 + ysp*2, 'Undo');
|
|
|
|
|
|
|
|
|
|
const cfgy0 = 10; |
|
|
|
|
|
|
|
|
|
this.buttons.optFancy = this.addButton(x0, cfgy0, 'Effects:', 'config'); |
|
|
|
|
this.buttons.optBlockedEffect = this.addButton(x0, cfgy0+ysp2, 'Dim Blocked:', 'config'); |
|
|
|
|
this.buttons.optSloppy = this.addButton(x0, cfgy0+ysp2*2, 'Sloppy Gen:', 'config'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
updateSettingsGUI(cfg) { |
|
|
|
|
this.buttons.optFancy.textContent = 'Effects: '+((cfg.svgAnimations || cfg.svgBlur) ? 'On' : 'Off'); |
|
|
|
|
this.buttons.optBlockedEffect.textContent = 'Dim Blocked: '+(cfg.disabledEffect ? 'On' : 'Off'); |
|
|
|
|
this.buttons.optSloppy.textContent = 'Sloppy Gen: '+(cfg.allowTemplateAugmenting ? 'On' : 'Off'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
addButton(x, y, text, classes='') { |
|
|
|
|
let { rx, ry } = this.guiXyToCoord(x, y); |
|
|
|
|
let button = Svg.fromXML(`<text class="button-text${classes?' '+classes:''}" x="${rx}" y="${ry}" >${text}</text>`); |
|
|
|
|
this.$root.appendChild(button); |
|
|
|
|
return button; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
@ -847,6 +893,7 @@ class Game { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.applySettings(); |
|
|
|
|
this.installButtonHandlers(); |
|
|
|
|
|
|
|
|
|
// Defer start to give browser time to render the background
|
|
|
|
|
setTimeout(() => { |
|
|
|
@ -886,6 +933,7 @@ class Game { |
|
|
|
|
this.board.$svg.classList.toggle('cfg-no-fade-disabled', !this.cfg.disabledEffect); |
|
|
|
|
this.board.$svg.classList.toggle('cfg-no-blur', !this.cfg.svgBlur); |
|
|
|
|
this.applyLogFilter(); |
|
|
|
|
this.board.updateSettingsGUI(this.cfg); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
setCfg(update) { |
|
|
|
@ -1343,6 +1391,31 @@ class Game { |
|
|
|
|
}, []); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getPairSymbols(first) { |
|
|
|
|
return { |
|
|
|
|
'salt': ['salt', 'air', 'fire', 'water', 'earth'], |
|
|
|
|
'air': ['salt', 'air'], |
|
|
|
|
'fire': ['salt', 'fire'], |
|
|
|
|
'water': ['salt', 'water'], |
|
|
|
|
'earth': ['salt', 'earth'], |
|
|
|
|
'mercury': [this.nextMetal], |
|
|
|
|
'lead': ['mercury'], |
|
|
|
|
'tin': ['mercury'], |
|
|
|
|
'iron': ['mercury'], |
|
|
|
|
'copper': ['mercury'], |
|
|
|
|
'silver': ['mercury'], |
|
|
|
|
'gold': [], |
|
|
|
|
'mors': ['vitae'], |
|
|
|
|
'vitae': ['mors'], |
|
|
|
|
}[first]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
advanceMetal() { |
|
|
|
|
if (this.nextMetal === 'gold') throw new Error("No metals to unlock beyond gold."); |
|
|
|
|
this.nextMetal = METAL_SEQ[METAL_SEQ.indexOf(this.nextMetal) + 1]; |
|
|
|
|
console.debug(`Next metal unlocked: ${this.nextMetal}`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Convert the strings in the board array to actual SVG orbs (strings are a placeholder to speed up board solving) |
|
|
|
|
*/ |
|
|
|
@ -1356,18 +1429,6 @@ class Game { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Update orb availability status (includes effects) |
|
|
|
|
*/ |
|
|
|
|
updateOrbDisabledStatus() { |
|
|
|
|
for (let n = 0; n < BOARD_SIZE; n++) { |
|
|
|
|
if (this.board.grid[n]) { |
|
|
|
|
const ava = this.isAvailableAtPlaytime(n); |
|
|
|
|
this.board.grid[n].node.classList.toggle('disabled', !ava); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Check if a tile is available at play-time (checking unlocked metals) |
|
|
|
|
* |
|
|
|
@ -1457,7 +1518,55 @@ class Game { |
|
|
|
|
this.info("Good work!"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.updateOrbDisabledStatus(); |
|
|
|
|
this.updateGameGUI(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Add event handlers for the menu buttons |
|
|
|
|
*/ |
|
|
|
|
installButtonHandlers() { |
|
|
|
|
this.board.buttons.restart.addEventListener('click', () => { |
|
|
|
|
this.newGame(this.rng.seed); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.board.buttons.randomize.addEventListener('click', () => { |
|
|
|
|
this.newGame(+new Date); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.board.buttons.optFancy.addEventListener('click', () => { |
|
|
|
|
let val = !(this.cfg.svgAnimations || this.cfg.svgBlur); |
|
|
|
|
this.setCfg({ |
|
|
|
|
svgAnimations: val, |
|
|
|
|
svgBlur: val, |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.board.buttons.optBlockedEffect.addEventListener('click', () => { |
|
|
|
|
this.setCfg({ |
|
|
|
|
disabledEffect: !this.cfg.disabledEffect, |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.board.buttons.optSloppy.addEventListener('click', () => { |
|
|
|
|
this.setCfg({ |
|
|
|
|
allowTemplateAugmenting: !this.cfg.allowTemplateAugmenting, |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Update button hiding attributes, disabled orb effects, etc |
|
|
|
|
*/ |
|
|
|
|
updateGameGUI() { |
|
|
|
|
this.board.buttons.restart.classList.toggle('disabled', this.countOrbs() === 55); |
|
|
|
|
|
|
|
|
|
// Update orb disabled status
|
|
|
|
|
for (let n = 0; n < BOARD_SIZE; n++) { |
|
|
|
|
if (this.board.grid[n]) { |
|
|
|
|
const ava = this.isAvailableAtPlaytime(n); |
|
|
|
|
this.board.grid[n].node.classList.toggle('disabled', !ava); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1480,6 +1589,7 @@ class Game { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.selectedOrb = null; |
|
|
|
|
this.nextMetal = 'lead'; |
|
|
|
|
|
|
|
|
|
let self = this; |
|
|
|
|
this.board.onOrbClick = (n, orb) => self.inGameBoardClick(n, orb); |
|
|
|
@ -1521,9 +1631,8 @@ class Game { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.nextMetal = 'lead'; |
|
|
|
|
this.renderPreparedBoard(); |
|
|
|
|
this.updateOrbDisabledStatus(); |
|
|
|
|
this.updateGameGUI(); |
|
|
|
|
|
|
|
|
|
if (!suc) { |
|
|
|
|
alert(`Sorry, could not find a valid board setup after ${retry_count} retries.`); |
|
|
|
@ -1548,31 +1657,6 @@ class Game { |
|
|
|
|
}, '')); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getPairSymbols(first) { |
|
|
|
|
return { |
|
|
|
|
'salt': ['salt', 'air', 'fire', 'water', 'earth'], |
|
|
|
|
'air': ['salt', 'air'], |
|
|
|
|
'fire': ['salt', 'fire'], |
|
|
|
|
'water': ['salt', 'water'], |
|
|
|
|
'earth': ['salt', 'earth'], |
|
|
|
|
'mercury': [this.nextMetal], |
|
|
|
|
'lead': ['mercury'], |
|
|
|
|
'tin': ['mercury'], |
|
|
|
|
'iron': ['mercury'], |
|
|
|
|
'copper': ['mercury'], |
|
|
|
|
'silver': ['mercury'], |
|
|
|
|
'gold': [], |
|
|
|
|
'mors': ['vitae'], |
|
|
|
|
'vitae': ['mors'], |
|
|
|
|
}[first]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
advanceMetal() { |
|
|
|
|
if (this.nextMetal === 'gold') throw new Error("No metals to unlock beyond gold."); |
|
|
|
|
this.nextMetal = METAL_SEQ[METAL_SEQ.indexOf(this.nextMetal) + 1]; |
|
|
|
|
console.debug(`Next metal unlocked: ${this.nextMetal}`); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Start */ |
|
|
|
|