nearly working backtracking solver

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

@ -194,11 +194,16 @@ class Board {
* Remove orb by array index
*
* @param {Number} index
* @param {Boolean} errorIfEmpty
*/
removeOrbByIndex(index) {
removeOrbByIndex(index, errorIfEmpty = false) {
if (this.grid[index]) {
this.$orbs.removeChild(this.grid[index].node);
delete this.grid[index];
} else {
if (errorIfEmpty) {
throw new Error(`Position ${index} is already empty.`);
}
}
}
@ -573,7 +578,7 @@ class Rng {
*/
constructor(seed=null) {
if (seed === null) {
seed = Math.random();
seed = +new Date;
}
this.seed(seed);
}
@ -606,10 +611,12 @@ class Game {
*/
constructor() {
this.board = new Board();
this.rng = new Rng();
this.rng = new Rng(1);
this.layoutTemplates = {
'wheel': [0,1,2,3,4,5,11,12,13,14,15,16,17,22,23,24,27,28,29,33,34,36,38,40,41,44,45,48,49,52,53,55,56,57,58,59,60,61,62,63,64,65,67,68,71,72,75,76,79,80,82,84,86,87,91,92,93,96,97,98,103,104,105,106,107,108,109,115,116,117,118,119,120],
// templates apparently all have 55 items
//'wheel': [0,1,2,3,4,5,11,12,13,14,15,16,17,22,23,24,27,28,29,33,34,36,38,40,41,44,45,48,49,52,53,55,56,57,58,59,60,61,62,63,64,65,67,68,71,72,75,76,79,80,82,84,86,87,91,92,93,96,97,98,103,104,105,106,107,108,109,115,116,117,118,119,120],
'beyblade': [0,1,2,3,4,5,12,14,15,23,26,34,35,37,38,39,40,46,47,48,50,51,52,55,58,60,61,64,65,67,68,69,70,71,73,76,79,80,83,84,85,86,87,91,94,95,97,98,103,104,105,106,109,115,120],
'tulip': [4,14,15,16,23,24,25,26,27,28,34,35,36,37,39,40,45,46,47,48,49,50,51,52,56,57,59,60,61,62,63,67,68,69,70,71,72,73,74,80,81,82,83,85,86,93,94,95,96,97,105,106,107,108,109],
'alien': [3,4,14,15,16,22,25,26,27,28,34,35,36,37,38,39,40,41,45,46,48,49,51,56,57,58,59,60,61,62,67,68,69,70,71,72,73,74,79,80,81,82,84,85,86,94,95,96,97,98,106,107,108,109,117],
@ -683,17 +690,22 @@ class Game {
/**
* Get a random and randomly flipped template
*
* @return {{template: number[], name: string, flipped: boolean}}
* @return Number[]
*/
getRandomTemplate() {
let names = Object.keys(this.layoutTemplates);
let name = names[Math.floor(this.rng.next() * names.length)];
let flipped = this.rng.next() > 0.5;
return {
template: this.getTemplate(name, flipped),
name,
flipped
};
let tpl = this.getTemplate(name, flipped);
// 60 (center) must be included to place gold
if (!tpl.includes(60)) {
throw Error(`Template "${name}", flip=${+flipped}, lacks 60.`);
}
console.info(`Selected board layout template "${name}", flipped=${+flipped}`);
return tpl;
}
/**
@ -701,20 +713,23 @@ class Game {
*
* The array is modified in place!
*
* @param tpl
* @returns {Uint16Array}
* @param {Number[]} tpl
* @returns {Number[]}
*/
flipTemplate(tpl) {
return tpl.sort((a,b) => a-b).map((n) => {
let { x, y } = this.board.gridIndexToXy(n);
return this.board.xyToGridIndex(5 + y - x, y);
});
return tpl
.sort((a, b) => a - b)
.map((n) => {
let { x, y } = this.board.gridIndexToXy(n);
return this.board.xyToGridIndex(5 + y - x, y);
})
.sort((a, b) => a - b);
}
/**
* Print array of all occupied orbs as a layout template
*
* @returns {number[]}
* @returns {Number[]}
*/
toTemplate() {
return Object.keys(game.board.grid)
@ -742,8 +757,256 @@ class Game {
};
}
isOutside(x, y) {
return x < 0
|| x > 10
|| y < 0
|| y > 10
|| (y <= 5 && x > 5 + y)
|| (y > 5 && x < y - 5);
}
isAvailable(n) {
let {x, y} = this.board.gridIndexToXy(n);
let neighbors = [
this.isOutside(x - 1, y) || !this.board.grid[n - 1],
this.isOutside(x - 1, y - 1) || !this.board.grid[n - 12],
this.isOutside(x, y - 1) || !this.board.grid[n - 11],
this.isOutside(x + 1, y) || !this.board.grid[n + 1],
this.isOutside(x + 1, y + 1) || !this.board.grid[n + 12],
this.isOutside(x, y + 1) || !this.board.grid[n + 11],
];
neighbors.push(neighbors[0]);
neighbors.push(neighbors[1]);
let conseq = 0;
for (let i = 0; i < neighbors.length; i++) {
if (neighbors[i]) {
conseq++;
if (conseq === 3) {
return true;
}
} else {
conseq = 0;
}
}
return false;
}
placeOrbs() {
this.board.removeAllOrbs();
let template = this.getRandomTemplate();
let allowed = [];
for(let i = 0; i <= 120; i++) {
allowed.push(template.includes(i));
if (!template.includes(i)) {
if (this.board.tiles[i]) {
this.board.$bg.removeChild(this.board.tiles[i])
}
}
}
let place = (n, symbol) => {
if (!allowed[n]) throw Error(`Position ${n} not allowed by template`);
if (this.board.grid[n]) throw Error(`Position ${n} is occupied`);
this.board.placeOrbByIndex(n, symbol);
};
let findAvailableIndex = () => {
let candidates = [];
for(let i=0; i<template.length;i++) {
let n = template[i];
if (!this.board.grid[n] && this.isAvailable(n)) {
candidates.push(n);
}
}
if (candidates.length) {
return candidates[Math.floor(this.rng.next()*candidates.length)]
} else {
throw Error("Failed to find available tile");
}
};
try {
place(60, 'gold');
let toPlace = [
// gold is placed always to 60 (5x5)
['salt', 'salt'],
['salt', 'salt'],
['mors', 'vitae'],
['mors', 'vitae'],
['mors', 'vitae'],
['mors', 'vitae'],
['air', 'air'],
['air', 'air'],
['air', 'air'],
['air', 'air'],
['fire', 'fire'],
['fire', 'fire'],
['fire', 'fire'],
['fire', 'fire'],
['water', 'water'],
['water', 'water'],
['water', 'water'],
['water', 'water'],
['earth', 'earth'],
['earth', 'earth'],
['earth', 'earth'],
['earth', 'earth'],
['lead', 'mercury'],
['tin', 'mercury'],
['iron', 'mercury'],
['copper', 'mercury'],
['silver', 'mercury'],
];
let shuffle = (a) => {
let j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(this.rng.next() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
};
shuffle(toPlace);
toPlace = toPlace.reduce((a,c) => {
a.push(c[0]);
a.push(c[1]);
return a;
}, []);
console.log(toPlace);
let history = [];
const maxiter = 1000;
for (let itern = 0; itern < maxiter && toPlace.length > 0; itern++) {
console.log('--- main iteration -----------');
let symbol = toPlace.pop();
console.log('take ' + symbol);
try {
console.log('?place ' + symbol);
let index = findAvailableIndex();
place(index, symbol);
console.log('placed ' + symbol + ' to ' + index);
history.push({symbol, index});
} catch (e) {
toPlace.push(symbol);
console.log('*return unplaced ' + symbol);
console.log('BACKTRACK----------- failed to place ' + symbol);
console.log('toPlace=',toPlace);
console.log('history=',history);
let depth = Math.min(1, history.length);
let backtracking = true;
// let retryAtThisLevel=0;
while (backtracking) {
// if (retryAtThisLevel > 0) {
// retryAtThisLevel = 0;
// depth++;
// console.log(`DEEPER`);
// } else {
// retryAtThisLevel++;
// }
console.log('>>> Attempt backtrack of depth ' + depth);
for (let j = 0; j < depth; j++) {
let hist = history.pop();
this.board.removeOrbByIndex(hist.index, true);
toPlace.push(hist.symbol);
console.log('undo & return (hist) ' + hist.symbol + ' at ' + hist.index)
}
console.log(`try getting back ${depth + 1} steps`);
let successes = 0;
for (let j = 0; j <= depth; j++) {
let symbol = toPlace.pop();
console.log('take ' + symbol);
itern++;
if (itern > maxiter) {
throw Error("Exceeded max iteration depth");
}
try {
console.log('?place ' + symbol);
let index = findAvailableIndex();
place(index, symbol);
history.push({symbol, index});
console.log('placed ' + symbol + ' to ' + index);
if (j === depth) {
console.log('Backtracking success');
backtracking = false;
break;
}
successes++;
} catch (e) {
console.log(`backtrack of depth ${depth} fail`);
toPlace.push(symbol);
console.log('return unplaced ' + symbol);
// for(let k = 0; k < successes; k++) {
// let hist = history.pop();
// this.board.removeOrbByIndex(hist.index, true);
// toPlace.push(hist.symbol);
// console.log('undo & return (hist) ' + hist.symbol + ' at ' + hist.index);
// }
console.log(`DEEPER`);
depth++;
break;
}
}
}
console.log('end of backtrack loop, toPlace=', toPlace);
console.log('history=',history);
}
}
if (toPlace.length > 0) {
throw Error("Failed to place some symbols: " + JSON.stringify(toPlace));
}
//place(findAvailableIndex(), metals.pop());
} catch (e) {
console.error(e)
for(let i=0;i<120;i++) {
if (!this.board.grid[i] && allowed[i]) {
console.log('Free cell '+i+' -> isAvail? '+this.isAvailable(i), this.board.gridIndexToXy(i));
}
}
}
}
newGame() {
//
this.board.onTileClick = (n) => {
console.log(n, this.board.gridIndexToXy(n));
};
this.placeOrbs()
}
}

Loading…
Cancel
Save