make do with a uglier template if it gives us a valid board

master
Ondřej Hruška 5 years ago
parent be54507006
commit 2e9555ee80
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 198
      script.js

@ -264,15 +264,15 @@ class Board {
orb.dataset.symbol = symbol; orb.dataset.symbol = symbol;
this.$orbs.appendChild(orb); this.$orbs.appendChild(orb);
orb.addEventListener('click', () => {
this.onOrbClick(arrayIndex, orb);
});
let object = { let object = {
node: orb, node: orb,
symbol symbol
}; };
orb.addEventListener('click', () => {
this.onOrbClick(arrayIndex, object);
});
this.grid[arrayIndex] = object; this.grid[arrayIndex] = object;
return object; return object;
} }
@ -518,6 +518,8 @@ class Board {
'mercury', 'lead', 'tin', 'iron', 'copper', 'silver', 'gold' 'mercury', 'lead', 'tin', 'iron', 'copper', 'silver', 'gold'
]; ];
this.metalSequence = ['lead', 'tin', 'iron', 'copper', 'silver', 'gold'];
this.orbColors = { this.orbColors = {
salt: '#f2e7b4', salt: '#f2e7b4',
air: '#9ee0ff', air: '#9ee0ff',
@ -717,7 +719,7 @@ class Game {
/** /**
* Get a random and randomly flipped template * Get a random and randomly flipped template
* *
* @return Number[] * @return Number[] - template indices; this is a clone, free to modify
*/ */
getRandomTemplate() { getRandomTemplate() {
let names = Object.keys(this.layoutTemplates); let names = Object.keys(this.layoutTemplates);
@ -842,10 +844,16 @@ class Game {
this.board.removeAllOrbs(); this.board.removeAllOrbs();
let allowed = []; let allowed = [];
let outsideTemplate = [];
for (let i = 0; i <= 120; i++) { for (let i = 0; i <= 120; i++) {
let allo = template.includes(i); let allo = template.includes(i);
allowed.push(allo); allowed.push(allo);
let { x, y } = this.board.gridIndexToXy(i);
if (!allo && !this.isOutside(x, y)) {
outsideTemplate.push(i);
}
// Highlight pattern shape // Highlight pattern shape
// if (this.board.tiles[i]) { // if (this.board.tiles[i]) {
@ -883,9 +891,23 @@ class Game {
}; };
const findAvailableIndex = () => { const findAvailableIndex = () => {
for (let i = 6; i >= 0; i--) { for (let j = 0; j < 2; j++) {
const n = findAvailableIndexWithNeighbours(i); for (let i = 6; i >= 0; i--) {
if (n !== false) return n; const n = findAvailableIndexWithNeighbours(i);
if (n !== false) return n;
}
// this corrupts the template, but makes the likelihood of quickly finding a valid solution much higher.
if (template.length !== 121) {
console.warn("Adding extra tile to template");
while (1) {
let toAdd = outsideTemplate[this.rng.nextInt(outsideTemplate.length)];
if (allowed.includes(toAdd)) continue;
allowed[toAdd] = true;
template.push(toAdd);
break;
}
}
} }
throw Error("Failed to find available tile"); throw Error("Failed to find available tile");
@ -895,20 +917,33 @@ class Game {
const toPlace = this.buildPlacementList(); const toPlace = this.buildPlacementList();
// unpack the pairs let solution = [];
let toPlace_stack = toPlace.reduce((a, c) => { while (toPlace.length > 0) {
a.push(c[0]); let symbol1 = toPlace.pop();
a.push(c[1]); let index1 = findAvailableIndex();
return a; place(index1, symbol1);
}, []);
let symbol2 = toPlace.pop();
let index2 = findAvailableIndex();
place(index2, symbol2);
while (toPlace_stack.length > 0) { if (!this.isAvailable(index1)) {
place(findAvailableIndex(), toPlace_stack.pop()) throw new Error("Solution contains a deadlock.");
}
let p;
p = this.board.gridIndexToXy(index1);
solution.push([symbol1, `${p.x}×${p.y}`, index1]);
p = this.board.gridIndexToXy(index1);
solution.push([symbol1, `${p.x}×${p.y}`, index1]);
} }
solution.reverse();
console.info("Found a valid board!"); console.info("Found a valid board!");
console.log('Solution: ', toPlace); console.log('Solution: ', solution);
} }
shuffleArray(a) { shuffleArray(a) {
@ -1000,9 +1035,16 @@ class Game {
toPlace.splice(mPos[i] + i, 0, pair); toPlace.splice(mPos[i] + i, 0, pair);
}); });
return toPlace; return toPlace.reduce((a, c) => {
a.push(c[0]);
a.push(c[1]);
return a;
}, []);
} }
/**
* Convert the strings in the board array to actual SVG orbs (strings are a placeholder to speed up board solving)
*/
renderPreparedBoard() { renderPreparedBoard() {
for (let n = 0; n <= 120; n++) { for (let n = 0; n <= 120; n++) {
if (this.board.grid[n] !== null) { if (this.board.grid[n] !== null) {
@ -1013,10 +1055,74 @@ class Game {
} }
} }
/**
* Update orb availability status (includes effects)
*/
updateOrbDisabledStatus() { updateOrbDisabledStatus() {
for (let n = 0; n <= 120; n++) { for (let n = 0; n <= 120; n++) {
if (this.board.grid[n]) { if (this.board.grid[n]) {
this.board.grid[n].node.classList.toggle('disabled', !this.isAvailable(n)); const ava = this.isAvailableAdvanced(n);
this.board.grid[n].node.classList.toggle('disabled', !ava);
}
}
}
isAvailableAdvanced(n) {
let ava = this.isAvailable(n);
const sym = this.board.grid[n].symbol;
if (this.board.metalSequence.includes(sym)) {
if (sym !== this.nextMetal) {
ava = false;
}
}
return ava;
}
/**
* Handle orb click
* @param n
* @param orb
*/
ingameBoardClick(n, orb) {
if (this.isAvailableAdvanced(n)) {
if (orb.symbol === 'gold') {
this.board.removeOrbByIndex(n);
this.selectedOrb = null;
if (this.countOrbs() === 0) {
console.info("Good work!");
}
return;
}
if (this.selectedOrb === null) {
this.selectedOrb = { n, orb };
orb.node.classList.add('selected');
}
else {
if (this.selectedOrb.n === n) {
orb.node.classList.remove('selected');
this.selectedOrb = null;
}
else {
const otherSymbol = this.selectedOrb.orb.symbol;
if (this.getPairSymbols(orb.symbol).includes(otherSymbol)) {
// paired
this.board.removeOrbByIndex(n);
this.board.removeOrbByIndex(this.selectedOrb.n);
if ([orb.symbol, otherSymbol].includes(this.nextMetal)) {
this.advanceMetal();
}
this.selectedOrb = null;
}
this.updateOrbDisabledStatus();
}
} }
} }
} }
@ -1026,8 +1132,13 @@ class Game {
// console.log(n, this.board.gridIndexToXy(n)); // console.log(n, this.board.gridIndexToXy(n));
// }; // };
const RETRY_IN_TEMPLATE = 100; this.selectedOrb = null;
const RETRY_NEW_TEMPLATE = 15;
let self = this;
this.board.onOrbClick = (n, orb) => self.ingameBoardClick(n, orb);
const RETRY_IN_TEMPLATE = 50;
const RETRY_NEW_TEMPLATE = 50;
// retry loop, should not be needed if everything is correct // retry loop, should not be needed if everything is correct
let suc = false; let suc = false;
@ -1052,19 +1163,50 @@ class Game {
} }
} }
if (!suc) { this.nextMetal = 'lead';
// show the failed attempt this.renderPreparedBoard();
this.renderPreparedBoard(); this.updateOrbDisabledStatus();
this.updateOrbDisabledStatus();
if (!suc) {
alert(`Sorry, could not find a valid board setup after ${numretries} retries.`); alert(`Sorry, could not find a valid board setup after ${numretries} retries.`);
return;
} else { } else {
console.info(`Found valid solution (with ${numretries} retries)`); console.info(`Found valid solution (with ${numretries} retries)`);
} }
}
this.renderPreparedBoard(); getPairSymbols(first) {
this.updateOrbDisabledStatus() 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() {
this.nextMetal = this.board.metalSequence[this.board.metalSequence.indexOf(this.nextMetal) + 1];
console.debug(`Next metal unlocked: ${this.nextMetal}`);
}
countOrbs() {
let n = 0;
// todo use reduce
this.board.grid.forEach((x) => {
if (x !== null) {
n++;
}
});
return n;
} }
} }

Loading…
Cancel
Save