Parse
File Parse origin-shift/origin-shift-main-wide.js
This runs the server-side parser and regenerates the documentation tree for this source file.
Source
const conf = originShiftConfigs.small({
rows: 20
, cols: 20
, lineWidth: 2
, lineColor: '#999'
, gap: 5
, pointSpread: 15
/* Player spot. */
, originColor: '#eeaadd'
, lineCap: 'square'
, drawPosition: false
, drawTip: false
})
// const conf = originShiftConfigs.large({})
// const conf = originShiftConfigs.maze({
// drawPosition: false
// , drawTip: false
// , rows: 20
// , cols: 20
// })
// const conf = originShiftConfigs.small() // large()
let os = new OriginShift(conf);
const autoMain = function(){
os.init() // run generate grid and any bits
stage = MainStage.go({
// loop: false
})
}
const addControls = function(){
addButton('walk', {
onclick(){
console.log('click')
// stage.walk(stage.largeWalk, random.color());
stage.walk(stage.largeWalk);
}
});
addButton('toe', {
onclick(){
console.log('click')
stage.toe(random.color());
}
});
addButton('rebake', {
onclick(){
os.rebake(stage);
}
})
addButton('export', {
onclick(){
if(os.export) {
let content = os.export()
let jsonString = JSON.stringify(content, null, 2);
let blob = new Blob([jsonString], { type: 'application/json' });
let url = URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = 'export.json';
a.click();
URL.revokeObjectURL(url);
}
}
})
addButton('export walls', {
label: 'Export Walls'
, onclick(){
let content = stage.exportWalls()
let jsonString = JSON.stringify(content, null, 2);
let blob = new Blob([jsonString], { type: 'application/json' });
let url = URL.createObjectURL(blob);
let a = document.createElement('a');
a.href = url;
a.download = 'export-walls.json';
a.click();
URL.revokeObjectURL(url);
}
})
addButton('invert', {
label: 'Invert'
, onclick(){
// acts like a toggle
if(stage.permanentWalls) {
delete stage.permanentWalls
stage.hidePaths = false
stage.refresh()
} else {
stage.invertToWalls(true, false) // sticky, hide-paths
}
}
})
addButton('togglePaths', {
label: 'Toggle Paths'
, onclick(){
stage.hidePaths = !stage.hidePaths
// stage.refresh()
}
})
addButton('drawpath', {
label: 'Draw Path'
, onclick(){
stage.drawPath(undefined, true, /*{timeout:2000}*/);
}
})
addControl('step count', {
field: 'input'
, value: 100
, type: 'number'
, onchange(ev) {
let sval = ev.currentTarget.value
stage.largeWalk = parseInt(sval)
}
})
addControl('pointSpread', {
field: 'input'
, value: conf.pointSpread
, type: 'number'
, onchange(ev) {
let sval = ev.currentTarget.value
conf.pointSpread = parseInt(sval)
}
})
addControl('rows', {
field: 'input'
, value: conf.rows
, type: 'number'
, onchange(ev) {
let sval = ev.currentTarget.value
conf.rows = parseInt(sval)
}
})
addControl('cols', {
field: 'input'
, value: conf.cols
, type: 'number'
, onchange(ev) {
let sval = ev.currentTarget.value
conf.cols = parseInt(sval)
}
})
}
class MainStage extends Stage {
canvas = 'playspace'
mounted(){
this.lineStroke = new Stroke({
color: conf.lineColor
, dash: [7, 4]
, march: .15 // A dash stroke addon per step (march * delta)
, width: 10
})
this.walkerIndicator = this.center.copy()
addControls()
this.largeWalk = 100
this.walkers = [
new Walker(os, 1)
// , new Walker(os, os.pointList.length - 3)
]
this.paths = new Array(this.walkers.length)
this.featurePoints = new PointList
/* Highlighted wall segments keyed by colour,
populated via addFeatureWall. Rendered on top of
everything in draw() / refresh(). */
this.featureWalls = []
let kb = this.keyboard
// kb.onKeydown(KC.UP, this.onUpKeydown.bind(this))
// kb.onKeyup(KC.UP, this.onUpKeyup.bind(this))
this.reset()
this.example()
}
onKeydown(ev){
let dir = ev.key
let name = this.getName(ev)
if(name) {
// console.log('onKeydown', dir, name)
this.walkers.forEach(w=>{
this.walkerStep(w, name.toLowerCase())
})
}
}
onKeyup(ev){
// let dir = ev.key
// let name = this.getName(ev)
// if(name) {
// console.log('onKeyup', dir, name)
// }
}
getName(ev){
for(let name in KC) {
if(KC[name].indexOf(ev.key) > -1) {
return name
}
}
}
shuffle(v=5) {
os.pointList.each.x = (p) => p.x + random.int(-v, v);
os.pointList.each.y = (p) => p.y + random.int(-v, v);
}
walkerStep(walker, dir) {
let r = walker.step(dir)
if(r) {
if(r == os.origin) {
console.log('Finished.')
os.rebake(this)
}
this.refresh();
}
}
draw(ctx=this.ctx, addData={}){
/* Without _clear_ the view will redraw on-top of the existing content */
// , lineCap: 'square'
ctx.lineCap = conf.lineCap || 'round'
ctx.fillStyle = 'green'
// ctx.lineCap = 'square'
// os.shift(addData.count || conf.drawStepCount, addData)
let lineStroke = this.lineStroke
this.clear(ctx)
// lineStroke.step()
// lineStroke.set(ctx)
if(this.hidePaths !== false){
this.drawPoints(ctx)
}
// lineStroke.unset(ctx)
// if(this.path) {
// // this.path.pen.fill(ctx, 'black', 3)
// this.path.pen.line(ctx, 'black', 4)
// }
if(this.permanentWalls) {
this.drawInverted(ctx)
}
if(this.paths) {
// this.path.pen.fill(ctx, 'black', 3)
this.paths.forEach(p=>{
p?.pen.line(ctx, 'black', 4)
})
}
/* Draw the walker */
this.walkers.forEach(w=>{
let index = w?.index
if(index) {
this.walkerIndicator.xy = os.pointList[index].xy
this.walkerIndicator.pen.fill(ctx, 'red')
}
})
this.featurePoints.pen.fill(ctx)
this.drawFeatureWalls(ctx)
}
refresh() {
/* no arguments - redraw without a move. */
let ctx = this.ctx;
this.clear(ctx)
this.drawPoints(ctx)
if(!this.hidePaths && this.paths) {
// this.path.pen.fill(ctx, 'black', 3)
this.paths.forEach(p=>{
p?.pen.line(ctx, 'black', 4)
})
}
if(this.permanentWalls) {
this.drawInverted(ctx)
}
this.featurePoints.pen.fill(ctx)
this.drawFeatureWalls(ctx)
}
drawPath(startIndex=undefined, clean=true, d={}){
this.walkers.forEach((w, i)=>{
let si = startIndex
if(si == undefined) {
si = w.index
// si = this.walkers[0].index
}
if(clean && i==0) {
os.pointList.each.lineColor = undefined;
}
let path = new PointList()
/* /origin-shift/origin-shift-drawpath.js */
drawPath(si, os, (p)=>{
path.push(p)
})
this.paths[i] = path
if(d.timeout) {
setTimeout(()=>{
this.paths[i] = undefined
}, d.timeout);
}
})
let addData = Object.assign({ count: 1 }, d)
this.draw(this.ctx, addData)
}
getPathFlatGraph(startIndex){
/* Keep the path as a KV of binding. Used for cross-referencing. */
if(startIndex == undefined) {
startIndex = this.walkers[0].index
}
let path = new PointList()
/* /origin-shift/origin-shift-drawpath.js */
drawPath(startIndex, os, (p)=>{
path.push(p)
})
// split be ab bc pair.
let pairs = path.siblings().map(pair=>[pair[0].uuid, pair[1].uuid]);
return pairs
}
example(){
this.walk(this.largeWalk, 'purple');
this.walk(10000);
this.walk(this.largeWalk, 'orange');
this.walk(10000);
this.walk(this.largeWalk, 'green');
this.walk(10000);
this.walk(this.largeWalk, '#880000');
this.walk(10000);
os.rebake(this)
}
walk(count=1000, color=undefined) {
// os.shift(count)
os.shift(count, color? {color}: {})
this.draw(this.ctx)
}
toe(color=undefined) {
let ctx = this.ctx
this.draw(ctx, {count: 1})
}
reset(draw=true){
os.reset()
/* Clear any feature overlays from a previous maze —
indices/positions are no longer meaningful after
a reset. */
this.featurePoints = new PointList
this.featureWalls = []
this._walls = null
this._features = null
draw && this.draw(this.ctx)
}
drawPoints(ctx, origin=os.origin) {
let drawPos = conf.drawPosition
, drawTip = conf.drawTip
;
os.forEach((p, i, a) => {
/* in cleanview, Do not render unvisited nodes */
if(!p.hit || p.ignore) { return }
this.drawPoint(ctx, p, i == origin, drawPos, drawTip, a)
})
this.drawOriginNode(ctx)
}
drawPoint(ctx, p, isOrigin, drawPos, drawTip, a) {
// debugger
let tip = p.target? a[p.target]: p.project() // to the radius
this.drawDirectionLine(ctx, p, tip, isOrigin)
drawPos && this.drawPosition(ctx, p, tip, isOrigin)
!isOrigin && drawTip && this.drawTip(ctx, p, tip)
}
drawDirectionLine(ctx, p, tip, isOrigin){
/* The origin has no direction - thus just ignore its line. */
if(isOrigin) { return }
let get = (k) => p[k] || conf[k]
// tip.pen.line(ctx, p, )
// tip.pen.fill(ctx, '#333', 3)
tip.pen.line(ctx, p, get('lineColor'), get('lineWidth'))
// tip.pen.fill(ctx, undefined, 3)
}
drawPosition(ctx, p, tip, isOrigin){
p.pen.circle(ctx, 2, 'black', 3)
}
/* Draw the _tip_ of a projection. */
drawTip(ctx, p, tip) {
// tip.pen.circle(ctx, conf.tipRadius, conf.tipColor, conf.tipWidth)
}
drawOriginNode(ctx) {
os.getOrigin().pen.fill(ctx, conf.originColor, 5)
// os.getOrigin().pen.circle(ctx, 2, conf.originColor, conf.lineWidth)
}
computeWalls() {
/* Build walls by inverting the current maze edges.
A wall exists wherever two adjacent cells do NOT
share an open passage. */
let rows = conf.rows
, cols = conf.cols
;
let open = new Set()
os.forEach((p, i) => {
if(p.target != null) {
let lo = Math.min(i, p.target)
, hi = Math.max(i, p.target)
;
open.add(lo + '-' + hi)
}
})
let walls = []
, pl = os.pointList
;
for(let r = 0; r < rows; r++) {
for(let c = 0; c < cols; c++) {
let index = r * cols + c
/* Skip unvisited cells — no walls around them. */
if(!pl[index].hit) { continue }
if(c + 1 < cols && pl[index + 1].hit
&& !open.has(`${index}-${index + 1}`)) {
walls.push([index, index + 1])
}
if(r + 1 < rows && pl[index + cols].hit
&& !open.has(`${index}-${index + cols}`)) {
walls.push([index, index + cols])
}
}
}
return walls
}
exportWalls() {
/* Export the maze in the same format as os.export(),
but with a `walls` key instead of `edges`,
and a `features` key with detected spatial features. */
let cols = conf.cols
let walls = this.computeWalls()
let nodes = []
os.forEach((p, i) => {
nodes.push({
index: i
, row: Math.floor(i / cols)
, col: i % cols
, x: p.x
, y: p.y
, target: p.target ?? null
, color: p.lineColor || null
})
})
/* Detect features and serialise. Clone the features
shape so converting the cells Map to an array for
JSON doesn't mutate the cached in-memory features. */
this._walls = walls
let source = this.detectFeatures().info
let feat = Object.assign({}, source, {
cells: Array.from(source.cells.values())
})
return {
meta: {
rows: conf.rows
, cols
, pointSpread: conf.pointSpread
, originIndex: os.origin
}
, nodes
, walls
, features: feat
}
}
invertToWalls(sticky=true, hidePaths=false) {
this._walls = this.computeWalls()
this._inverted = true
console.log('Inverted to walls. sticky:', sticky, 'hidePaths:', hidePaths)
this.permanentWalls = sticky
this.hidePaths = hidePaths
this.drawInverted(this.ctx)
}
addPointAtPosition(index, ex={color: 'lightblue'}) {
// (Assuming 'walls' visual)
// given an index within the grid,
// add a point at that position,
// allowing the presentation of 'detectFeatures'
// points.
// index to xy (between walls.)
let x = index % conf.cols
let y = Math.floor(index / conf.cols)
let size = conf.pointSpread
let firstPoint = os.pointList[0]
let offX = firstPoint.x // - size * 0.5
let offY = firstPoint.y // - size * 0.5
let px = offX + x * size
let py = offY + y * size
let c = Object.assign({x: px, y: py}, ex)
let p = new Point(c)
if(!this.featurePoints) {
this.featurePoints = new PointList
}
this.featurePoints.push(p)
}
addFeatureWall(a, b, color='yellow', width=4) {
/* Highlight a wall segment between cells a and b.
Used by FeaturesObject.showNarrowWalls etc.
The wall pair order is normalised to match the
format used by computeWalls / plotWall. */
let lo = Math.min(a, b), hi = Math.max(a, b)
let diff = hi - lo
/* plotWall only understands horizontal (diff=1, same row)
and vertical (diff=cols) adjacencies. Anything else
would produce nonsensical geometry. */
if(diff !== 1 && diff !== conf.cols) {
console.warn('addFeatureWall: non-adjacent pair', a, b)
return
}
if(diff === 1 && Math.floor(lo / conf.cols) !== Math.floor(hi / conf.cols)) {
console.warn('addFeatureWall: pair wraps row boundary', a, b)
return
}
if(!this.featureWalls) { this.featureWalls = [] }
this.featureWalls.push({a: lo, b: hi, color, width})
}
clearFeatureWalls() {
this.featureWalls = []
}
drawFeatureWalls(ctx) {
if(!this.featureWalls || this.featureWalls.length === 0) { return }
let prevCap = ctx.lineCap
ctx.lineCap = 'square'
this.featureWalls.forEach(w => {
let [x1, y1, x2, y2] = this.plotWall(w.a, w.b)
ctx.strokeStyle = w.color
ctx.lineWidth = w.width
ctx.beginPath()
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.stroke()
})
ctx.lineCap = prevCap
}
detectFeatures() {
/* Analyse the maze grid and classify every visited cell
by its spatial type, then group corridors into chains.
Returns {
cells: Map<index, {index, row, col, exits, wallCount, type, walls}>,
deadEnds: [index, ...],
corridors: [[index, ...], ...], // chains of 3+ corridor cells
culDeSacs: [[index, ...], ...], // corridor chains ending in a dead-end
alcoves: [index, ...], // 3-wall cells (1 exit)
junctions: [index, ...], // 1-wall cells (3 exits)
crossroads:[index, ...], // 0-wall cells (4 exits)
}
*/
let rows = conf.rows
, cols = conf.cols
, pl = os.pointList
, wallSet = new Set()
;
/* Build a fast wall lookup from the computed walls. */
let walls = this._walls || this.computeWalls()
walls.forEach(([a, b]) => {
let lo = Math.min(a, b), hi = Math.max(a, b)
wallSet.add(lo + '-' + hi)
})
const hasWall = (a, b) => {
let lo = Math.min(a, b), hi = Math.max(a, b)
return wallSet.has(lo + '-' + hi)
}
/* For each visited cell, determine wall sides and exit count. */
let cells = new Map()
for(let r = 0; r < rows; r++) {
for(let c = 0; c < cols; c++) {
let index = r * cols + c
if(!pl[index].hit) { continue }
let cellWalls = {
up: r === 0 || !pl[index - cols]?.hit || hasWall(index, index - cols)
, down: r === rows-1 || !pl[index + cols]?.hit || hasWall(index, index + cols)
, left: c === 0 || !pl[index - 1]?.hit || hasWall(index, index - 1)
, right: c === cols-1 || !pl[index + 1]?.hit || hasWall(index, index + 1)
}
let wallCount = 0
for(let k in cellWalls) { if(cellWalls[k]) wallCount++ }
let exits = 4 - wallCount
let type = 'open'
if(exits === 0) type = 'isolated'
else if(exits === 1) type = 'deadEnd'
else if(exits === 2) type = 'corridor'
else if(exits === 3) type = 'junction'
else if(exits === 4) type = 'crossroad'
cells.set(index, {
index, row: r, col: c
, exits, wallCount, type
, walls: cellWalls
})
}
}
/* Collect simple feature lists. */
let deadEnds = []
, alcoves = []
, junctions = []
, crossroads = []
;
cells.forEach((cell) => {
if(cell.type === 'deadEnd') deadEnds.push(cell.index)
if(cell.type === 'deadEnd') alcoves.push(cell.index)
if(cell.type === 'junction') junctions.push(cell.index)
if(cell.type === 'crossroad') crossroads.push(cell.index)
})
/* Chain corridor cells (2 exits) into connected runs.
Walk through each unvisited corridor cell, expanding
in both directions along connected corridor neighbours. */
let visited = new Set()
let corridors = []
const getNeighbours = (idx) => {
let r = Math.floor(idx / cols), c = idx % cols
let out = []
if(r > 0) out.push(idx - cols)
if(r < rows -1) out.push(idx + cols)
if(c > 0) out.push(idx - 1)
if(c < cols -1) out.push(idx + 1)
return out
}
const connectedPassage = (a, b) => {
return cells.has(b) && !hasWall(a, b)
}
cells.forEach((cell) => {
if(cell.type !== 'corridor' || visited.has(cell.index)) return
/* BFS/flood-fill along connected corridor cells. */
let chain = []
, queue = [cell.index]
;
visited.add(cell.index)
while(queue.length) {
let cur = queue.shift()
chain.push(cur)
getNeighbours(cur).forEach(nb => {
if(!visited.has(nb)
&& cells.has(nb)
&& cells.get(nb).type === 'corridor'
&& connectedPassage(cur, nb)) {
visited.add(nb)
queue.push(nb)
}
})
}
if(chain.length >= 3) {
corridors.push(chain)
}
})
/* Cul-de-sacs: corridor chains where at least one end
connects to a dead-end cell. */
let deadEndSet = new Set(deadEnds)
let culDeSacs = corridors.filter(chain => {
return chain.some(idx => {
return getNeighbours(idx).some(nb =>
deadEndSet.has(nb) && connectedPassage(idx, nb)
)
})
})
/* Corridors with dead-ends: the full run including the
dead-end cells at each terminus. Each entry is
{ corridor: [index,...], deadEnds: [index,...], full: [index,...] } */
let corridorDeadEnds = culDeSacs.map(chain => {
let ends = []
chain.forEach(idx => {
getNeighbours(idx).forEach(nb => {
if(deadEndSet.has(nb) && connectedPassage(idx, nb)) {
ends.push(nb)
}
})
})
return {
corridor: chain
, deadEnds: ends
, full: [...new Set([...ends, ...chain])]
}
})
let narrows = this.detectNarrows(cells, rows, cols)
let features = { cells, deadEnds, corridors, culDeSacs
, corridorDeadEnds, alcoves, junctions, crossroads
, narrows }
this._features = features
console.log('Features detected:', {
deadEnds: deadEnds.length
, corridors: corridors.length
, culDeSacs: culDeSacs.length
, corridorDeadEnds: corridorDeadEnds.length
, junctions: junctions.length
, crossroads: crossroads.length
, narrows: narrows.length
})
return new FeaturesObject(features, this)
}
detectNarrows(cells, rows, cols) {
/* Detect strict H-shaped doorway / pinch-point cells.
The H pattern (correct examples):
------- --------
| | | |__ __|
| | or | |
| | | --------
-------
Defined by an OPEN doorway edge with parallel
WALLED jamb edges on each side, where both rows
of the H are connected through.
A horizontal H around the doorway edge M↔M'
(M above, M' below):
+--+--+--+
| L M R| row r: L─M─R open across
+##+ +##+ jamb-L (L↔L') walled,
| L' M' R' gap (M↔M') open,
+--+--+--+ jamb-R (R↔R') walled
row r+1: L'─M'─R' open across
Detected per OPEN edge (not per cell), so we get one
entry per doorway. Each narrow carries:
- axis: 'horizontal' | 'vertical'
- gapIndex: cell on one side of the doorway
- gapPair: [a,b] the two cells of the doorway
- jambWalls: [[a,b],[a,b]] the two flanking walls
(pair ordering matches the main
walls list: a<b). */
let narrows = []
cells.forEach((m) => {
let {row: r, col: c, index: mid} = m
/* Horizontal H — vertical doorway edge between
M (row r) and M' (row r+1), centred at column c. */
if(r + 1 < rows && c > 0 && c < cols - 1) {
let mDown = cells.get(mid + cols)
let lUp = cells.get(mid - 1)
let lDown = cells.get(mid + cols - 1)
let rUp = cells.get(mid + 1)
let rDown = cells.get(mid + cols + 1)
if(mDown && lUp && lDown && rUp && rDown) {
let gapOpen = !m.walls.down
let leftJamb = lUp.walls.down
let rightJamb = rUp.walls.down
let topRow = !m.walls.left && !m.walls.right
let botRow = !mDown.walls.left && !mDown.walls.right
if(gapOpen && leftJamb && rightJamb && topRow && botRow) {
narrows.push({
axis: 'horizontal'
, gapIndex: mid
, gapPair: [mid, mid + cols]
/* The two jamb walls that flank the
doorway. Each pair is [a,b] with
a<b, matching the walls list format. */
, jambWalls: [
[mid - 1, mid - 1 + cols]
, [mid + 1, mid + 1 + cols]
]
})
}
}
}
/* Vertical H — horizontal doorway edge between
M (col c) and M' (col c+1), centred at row r. */
if(c + 1 < cols && r > 0 && r < rows - 1) {
let mRight = cells.get(mid + 1)
let aLeft = cells.get(mid - cols)
let aRight = cells.get(mid - cols + 1)
let bLeft = cells.get(mid + cols)
let bRight = cells.get(mid + cols + 1)
if(mRight && aLeft && aRight && bLeft && bRight) {
let gapOpen = !m.walls.right
let topJamb = aLeft.walls.right
let botJamb = bLeft.walls.right
let leftCol = !m.walls.up && !m.walls.down
let rightCol = !mRight.walls.up && !mRight.walls.down
if(gapOpen && topJamb && botJamb && leftCol && rightCol) {
narrows.push({
axis: 'vertical'
, gapIndex: mid
, gapPair: [mid, mid + 1]
/* Jamb walls above and below the
doorway. */
, jambWalls: [
[mid - cols, mid - cols + 1]
, [mid + cols, mid + cols + 1]
]
})
}
}
}
})
return narrows
}
drawInverted(ctx) {
this.clear(ctx)
this.drawMazeWalls(ctx)
this.drawMazeBorder(ctx)
}
drawMazeWalls(ctx) {
ctx.strokeStyle = conf.lineColor || '#999'
ctx.lineWidth = conf.lineWidth || 2
ctx.lineCap = 'square'
ctx.beginPath()
this._walls.forEach(([a, b]) => {
let [x1, y1, x2, y2] = this.plotWall(a, b)
ctx.moveTo(x1, y1)
ctx.lineTo(x2, y2)
})
ctx.stroke()
}
plotWall(a, b) {
let diff = b - a
, cols = conf.cols
, row = Math.floor(a / cols)
, col = a % cols
, size = conf.pointSpread
/* Offset so walls align with the existing grid positions. */
, firstPoint = os.pointList[0]
, offX = firstPoint.x - size * 0.5
, offY = firstPoint.y - size * 0.5
;
if(diff === 1) {
/* Vertical wall on the right edge of cell a. */
let x = offX + (col + 1) * size
let y = offY + row * size
return [x, y, x, y + size]
}
/* Horizontal wall on the bottom edge of cell a. */
let x = offX + col * size
let y = offY + (row + 1) * size
return [x, y, x + size, y]
}
drawMazeBorder(ctx) {
let size = conf.pointSpread
, firstPoint = os.pointList[0]
, lineWidth = 3
, padding = 2
, offX = firstPoint.x - size * 0.5 - (lineWidth * 0.5) - (padding * 0.5)
, offY = firstPoint.y - size * 0.5 - (lineWidth * 0.5) - (padding * 0.5)
, w = lineWidth + padding + (conf.cols * size)
, h = lineWidth + padding + (conf.rows * size)
;
ctx.strokeStyle = '#666'
ctx.lineWidth = lineWidth
ctx.strokeRect(offX, offY, w, h)
}
}
class FeaturesObject {
/* A simple wrapper for the features detected in the maze, with some helper methods for querying.
Synonmyms:
info.deadEnds.forEach(x=>stage.addPointAtPosition(x, {color:'red'}));
info.corridorDeadEnds.forEach(l=>{
stage.addPointAtPosition(l.deadEnds[0], {color:'white'})
});
*/
constructor(info, stage){
this.info = info
this.stage = stage
}
clearFeatures(stage=this.stage) {
stage.featurePoints = new PointList
stage.clearFeatureWalls && stage.clearFeatureWalls()
}
showCorridors(stage=this.stage) {
this.info.corridors.forEach(chain=>{
chain.forEach(x=>{
stage.addPointAtPosition(x, {color:'lightgreen'})
})
})
}
showJunctions(stage=this.stage) {
this.info.junctions.forEach(x=>stage.addPointAtPosition(x, {color:'lightblue'}));
}
showDeadEnds(stage=this.stage) {
this.info.deadEnds.forEach(x=>stage.addPointAtPosition(x, {color:'red'}));
}
showCorridorDeadEnds(stage=this.stage) {
this.info.corridorDeadEnds.forEach(l=>{
stage.addPointAtPosition(l.deadEnds[0], {color:'white'})
});
}
showCrossroads(stage=this.stage) {
this.info.crossroads.forEach(x=>stage.addPointAtPosition(x, {color:'purple'}));
}
showCulDeSacs(stage=this.stage) {
this.info.culDeSacs.forEach(chain=>{
chain.forEach(x=>{
stage.addPointAtPosition(x, {color:'orange'})
})
});
}
showNarrows(stage=this.stage) {
this.info.narrows.forEach(n=>{
stage.addPointAtPosition(n.gapIndex, {color:'yellow'})
});
}
showNarrowWalls(stage=this.stage, color='deeppink', width=4) {
/* Render the two jamb walls for every detected narrow
as highlighted wall segments — these are the door
placements for the game render. */
this.info.narrows.forEach(n => {
if(!n.jambWalls) { return }
n.jambWalls.forEach(([a, b]) => {
stage.addFeatureWall(a, b, color, width)
})
})
stage.refresh && stage.refresh()
}
showNarrowDoors(stage=this.stage, color='gold', width=4) {
/* Render the doorway GAP between the jambs for every
detected narrow — the open edge where a door would
be placed to seal the passage. */
this.info.narrows.forEach(n => {
if(!n.gapPair) { return }
let [a, b] = n.gapPair
stage.addFeatureWall(a, b, color, width)
})
stage.refresh && stage.refresh()
}
}
const subdivideA = function(points, rowCount) {
/* In this first version, we update the rows according to the following
1. for each item, insert an item _between_.
2. for each row, insert a new row _between_.
the positions will need regenerating - through arrange.grid(newRowCount.)
*/
let newList = new PointList;
let pl = points.length;
// debugger;
let rowIndex = 0
for (var i = 0; i < rowCount; i++) {
for (var j = 0; j < rowCount; j++) {
let index = (i * rowCount) + j
let point = points[index]
newList.push(point)
let lastRowItem = j == rowCount - 1
if(!lastRowItem) {
let innerPoint = new Point({color: '#550000'})
newList.push(innerPoint)
}
}
// After every row split insert, we inset a new row below.
// with the new row len.
let newRow = PointList.generate.countOf(rowCount + rowCount - 1)
newRow.each.color = 'green'
if(i < rowCount-1){
newList.push(...newRow)
}
rowIndex++
}
return newList
}
;autoMain();
copy