import cell from "./cell"; import { vector, V } from "./vector"; const RULE_INCOMPATIBLE = { 0: [ 2, 3 ], 1: [ 3, 4 ], 2: [ 0, 4 ], 3: [ 0, 1 ], 4: [ 1, 2] }; const RULE_WEIGHTS = { 0: 9, 1: 6, 2: 8, 3: 6, 4: 6 }; const RULE_SMOOTHNESS = 20; function weighted_pick(arr, nearby = null){ let new_arr = []; for(let i of arr){ for(let m=0;m<RULE_WEIGHTS[i];m++){ new_arr.push(i); } } if(nearby != null) { for(let i of Object.keys(nearby)){ for(let z=0;z<RULE_SMOOTHNESS;z++){ for(let m=0;m<RULE_WEIGHTS[i];m++){ new_arr.push(i); } } } } return rnd_pick(new_arr); } function rnd_pick(arr){ return arr[Math.floor(Math.random() * arr.length)]; } export class grid { width: number; height: number; cells: Array<Array<cell>>; iter(fn: (x: number, y: number) => void){ for(let y=0;y<this.height;y++){ for(let x=0;x<this.width;x++){ fn(x, y); } } } getCell(p: vector){ let c: cell = undefined; try { c = this.cells[p.y][p.x]; } catch (error) { } return c; } getCells() { let out: Array<cell> = []; this.iter( (x, y) => { out.push( this.getCell( new V(x, y) ) ); } ); return out; } setCell(p: vector, c: cell) { if(p.x < 0 || p.y < 0){ console.warn("SC || position ", p, " too low"); return } if(p.x > this.width || p.y > this.height){ console.warn("SC || position ", p, " too high"); return } let cellsCopy = this.cells.slice(); let group = cellsCopy[p.y].slice(); group[p.x] = c; cellsCopy[p.y] = group; this.cells = cellsCopy; } modifyCell(p: vector, props: Object) { this.setCell( p, { ...this.getCell(p), ...props, isCollapsed: function (): boolean { return this.possible.length < 2; } } ); } getCollapsedNeighbourValues( c: cell ){ let out = []; for(let nv of c.neighbours){ if(nv){ let n = this.getCell(nv); if(n){ if(n.isCollapsed()){ out.push(n.possible[0]); } } } } return out; } rule( c: cell ) { let possible = c.possible.slice(); for(let c2v of c.neighbours.filter(n=>n!=null)) { let c2 = this.getCell(c2v); if(c2 && c2.isCollapsed()) { for(let p of c.possible){ if(possible.includes(p)){ let incompatible = RULE_INCOMPATIBLE[ c2.possible[0] ]; try { possible = possible.filter( x=>!incompatible.includes(x) ); } catch (error) { console.log(incompatible); } } } } } return possible; } evaluate(){ for(let cell of this.getUncollapsed()) { let p = new V(cell.x, cell.y); let c = this.getCell( p ); this.modifyCell( p, { possible: this.rule(c) } ); } } propagate(c: cell, recursionLimit = 0) { if(recursionLimit > 10 || c.isCollapsed()){ return; } recursionLimit++; let newPossible = this.rule(c); if(newPossible.length != c.possible.length){ this.modifyCell( new V(c.x, c.y), { possible: newPossible } ); for(let nv of c.neighbours.filter(n=>n!=null)){ let n = this.getCell(nv); if(n && !n.isCollapsed()){ this.propagate( n, recursionLimit); } } } return; } getUncollapsed(){ return this.getCells().filter( x=>x.possible.length>1 ); } collapse( p: vector ) { let cell = this.getCell( p ); let neighbour_values = this.getCollapsedNeighbourValues( cell ); let neighbour_weights = {}; for(let val of neighbour_values){ neighbour_weights[val] = neighbour_weights[val] ? neighbour_weights[val] + 1 : 1; } this.modifyCell( p, { possible: [weighted_pick(cell.possible, neighbour_weights)] } ); } getCellEntropy( p: vector ) { } solve(){ this.evaluate(); let lowestEntropy = Number.MAX_VALUE; let lowestEntropyCell: cell = null; let uncollapsed = this.getUncollapsed(); for(let cell of uncollapsed) { let entropy = cell.possible.length; if(entropy < lowestEntropy && entropy > 0){ lowestEntropy = entropy; lowestEntropyCell = cell; } } if(lowestEntropyCell){ this.collapse( new V(lowestEntropyCell.x, lowestEntropyCell.y) ); } } curatedsolve(){ if(this.getUncollapsed().length < 1) { return true; } this.solve(); return false; } solveall() { for(;;){ if(this.curatedsolve()){ break; } } } constructor(w: number, h: number){ this.width = w; this.height = h; this.cells = new Array(h).fill( new Array(w).fill( null ) ); let index = 0; // Create Cells this.iter( (x,y) => { let position = new vector(x, y); let c = new cell( index, x, y ); c.x = x; c.y = y; this.setCell(position, c); index++; } ); // Assign cell neighbours this.iter( (x,y) => { let position = new vector(x, y); let c = this.getCell( position ); let neighbours = [ V.add(position, new V(0, 1) ), // UP V.add(position, new V(1, 1) ), // UP-RIGHT V.add(position, new V(1, 0) ), V.add(position, new V(1,-1) ), V.add(position, new V(0,-1) ), V.add(position, new V(-1,-1) ), V.add(position, new V(-1,0) ), V.add(position, new V(-1,1) ), ]; c.neighbours = neighbours; this.setCell(position, c); } ); // this.modifyCell( new V(0,0), { possible: [0], collapsed: true } ); // this.modifyCell( new V(1,0), { possible: [1], collapsed: true } ); // this.modifyCell( new V(2,0), { possible: [2], collapsed: true } ); } } export default grid;