DRUNK TURTLE'S RANDOM DIFFUSION LIMITED AGGREGATION WALK
Diffusion Limited Automata script based on http://www.roguebasin.com/index.php?title=Diffusion-limited_aggregation
DRUNK TURTLE'S RANDOM DIFFUSION LIMITED AGGREGATION WALK
Diffusion Limited Automata script based on http://www.roguebasin.com/index.php?title=Diffusion-limited_aggregation
DRUNK TURTLE'S RANDOM DIFFUSION LIMITED AGGREGATION WALK
Diffusion Limited Automata script based on http://www.roguebasin.com/index.php?title=Diffusion-limited_aggregation
see also http://www.roguebasin.com/index.php?title=Random_Walk_Cave_Generation
and http://www.roguebasin.com/index.php?title=Basic_BSP_Dungeon_generation
#output | |
canvas(id="map" width="500" height="500") | |
#controls | |
fieldset | |
legend New | |
button#render(onclick="render(parse_config(ui))") Render | |
fieldset | |
legend Export | |
button#save_img Save Image | |
button#save_txt Save Text | |
button#save_config Save Settings | |
fieldset | |
legend Canvas | |
label(for="block_size") Block Size | |
input#block_size(type="number" min="1" value="10") | |
hr | |
label(for="map_size") Map Size | |
input#map_size(type="number" min="1" value="50") | |
fieldset | |
legend Starting Location | |
label(for="root_y") X | |
input#root_x(type="range" min="0" max="1" step="0.01" value="0.5") | |
input#random_root_x(type="checkbox") | |
label(for="random_root_x") Random | |
hr | |
label(for="root_y") Y | |
input#root_y(type="range" min="0" max="1" step="0.01" value="0.5") | |
input#random_root_y(type="checkbox") | |
label(for="random_root_y") Random | |
fieldset | |
legend Draw Area | |
input#draw_area(type="range" min="0" max="1" step="0.01" value="0.125") | |
input#random_draw_area(type="checkbox") | |
label(for="random_draw_area") Random | |
fieldset | |
legend Regularity | |
input#regularity(type="range" min="1" max="100" step="1" value="4") | |
input#random_regularity(type="checkbox") | |
label(for="random_regularity") Random | |
fieldset | |
legend Walking Directions | |
input#walk_cardinal(type="radio" name="walking") | |
label(for="walk_cardinal") Cardinal | |
input#walk_diagonal(type="radio" name="walking" checked) | |
label(for="walk_diagonal") Diagonal | |
hr | |
input#walk_orthogonal(type="checkbox" name="walking") | |
label(for="walk_orthogonal") Orthogonal | |
fieldset | |
legend Colors | |
input#color_bg(type="color" value="#ff00ff") | |
label(for="color_bg") Canvas | |
input#color_bg_trans(type="checkbox" name="colors" checked) | |
label(for="color_bg_trans") Transparent | |
hr | |
input#color_fg(type="color" value="#000000") | |
label(for="color_fg") Cave | |
input#color_fg_trans(type="checkbox" name="colors") | |
label(for="color_fg_trans") Transparent | |
fieldset | |
legend Defaults | |
button#reset Reset | |
button#load_config Load Settings | |
fieldset | |
legend About | |
span SF2016 | |
/* | |
DRUNK TURTLE'S RANDOM DIFFUSION LIMITED AGGREGATION WALK | |
*/ | |
"use strict"; | |
function random(count) { | |
return Math.floor(Math.random() * count); | |
} | |
function render(config) { | |
/* | |
CONFIGURATION | |
*/ | |
var config = config || {}; | |
console.log(config) | |
// size of the map | |
var edge = config.map_size || 50; | |
var area = edge * edge; | |
// size in pixels of final rendered blocks | |
var block = config.block_size || 10; | |
// base block where growth begins | |
var rootX = config.random_root_x ? edge * Math.random() : edge * config.root_x || edge * 0.5; | |
var rootY = config.random_root_y ? edge * Math.random() : edge * config.root_y || edge * 0.5; | |
// maximum number of allocated blocks | |
var limit = config.random_draw_area ? area * Math.random() : area * config.draw_area || area * 0.125; | |
// | |
// maximum corridor length | |
// low = dense, blobby, organic | |
// high = elongated, angular | |
// minimum = 1, maximum = ? | |
var regularity = config.random_regularity ? random(100) : config.regularity || 4; | |
// allow orthogonal movement? | |
// false = blockier, easier to navigate, no orphans | |
// true = finer, harder to navigate, some orphans | |
var orthogonal = config.walk_orthogonal || false; | |
// choose a random direction to walk | |
// change available directions: | |
// 0-3 = NSEW only, 4-7 = all directions | |
var where = 0; | |
if (config.walk_cardinal === true) { where = 3 } | |
else if (config.walk_diagonal === true) { where = 7 } | |
else { where = 7 } | |
var stagger = function() { | |
return Math.floor(Math.random() * where); | |
} | |
/* | |
FUNCTIONAL VARIABLES | |
*/ | |
// set up null values | |
var y, x, cy, cx; | |
var walking = false; | |
var direction = 0; | |
var stepped = 0; | |
var allocated = 0; | |
// build a blank map | |
var map = base(); | |
/* | |
UTILITIES | |
*/ | |
function base() { | |
// set up empty map array | |
var grid = new Array(edge); | |
// add dimension to map array | |
for (var i = 0; i < area; ++i) { | |
grid[i] = new Array(edge); | |
} | |
// fill the map with zeros | |
for (var x = 0; x < edge; ++x) { | |
for (var y = 0; y < edge; ++y) { | |
grid[x][y] = 0; | |
} | |
} | |
// return completed base map | |
return grid; | |
} | |
/* | |
AGGREGATION LOOP | |
*/ | |
while (allocated < limit) { | |
if (walking === false) { | |
spawnTurtle(); | |
} else { | |
moveTurtle(); | |
// ensure that the turtle is touching an existing spot | |
if (cx < edge && cy < edge && cx > 1 && cy > 1 && stepped <= regularity) { | |
turtleBuild(); | |
} else { | |
walking = false; | |
} | |
} | |
} | |
function spawnTurtle() { | |
// spawn turtle at random position | |
cx = random(edge); | |
cy = random(edge); | |
// check if turtle spawned on root | |
if (Math.abs(rootX - cx) === 0 && Math.abs(rootY - cy) === 0) { | |
// turtle spawned too close to root, respawn somewhere else | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
} | |
} | |
// drunken turtle takes a shot | |
else { | |
stepped = 0; | |
walking = true; | |
direction = stagger(); | |
} | |
} | |
function moveTurtle() { | |
// drunk turtle takes a step | |
stepped++; | |
// which way does drunk turtle go? | |
// N | |
if (direction === 0 && cy > 0) { | |
cy--; | |
} | |
// NE | |
else if (direction === 4 && cx < edge && cy > 0) { | |
cy--; | |
cx++; | |
} | |
// E | |
else if (direction === 1 && cx < edge) { | |
cx++; | |
} | |
// SE | |
else if (direction === 5 && cx < edge && cy < edge) { | |
cy++; | |
cx++; | |
} | |
// S | |
else if (direction === 2 && cy < edge) { | |
cy++; | |
} | |
// SW | |
else if (direction === 6 && cx > 0 && cy < edge) { | |
cy++; | |
cx--; | |
} | |
// W | |
else if (direction === 3 && cx > 0) { | |
cx++; | |
} | |
// NW | |
else if (direction === 7 && cx > 0 && cy > 0) { | |
cy--; | |
cx--; | |
} | |
} | |
function turtleBuild() { | |
// N | |
if (map[cx][cy - 1] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
} | |
} | |
// NE | |
else if (map[cx + 1][cy - 1] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
if (!orthogonal) { | |
map[cx + 1][cy] = 1; | |
allocated++; | |
} | |
} | |
} | |
// E | |
else if (map[cx + 1][cy] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
} | |
} | |
// SE | |
else if (map[cx + 1][cy + 1] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
if (!orthogonal) { | |
map[cx + 1][cy] = 1; | |
allocated++; | |
} | |
} | |
} | |
// S | |
else if (map[cx][cy + 1] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
} | |
} | |
// SW | |
else if (map[cx - 1][cy + 1] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
if (!orthogonal) { | |
map[cx - 1][cy] = 1; | |
allocated++; | |
} | |
} | |
} | |
// W | |
else if (map[cx - 1][cy] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
} | |
} | |
// NW | |
else if (map[cx - 1][cy - 1] === 1) { | |
if (map[cx][cy] != 1) { | |
map[cx][cy] = 1; | |
allocated++; | |
if (!orthogonal) { | |
map[cx - 1][cy] = 1; | |
allocated++; | |
} | |
} | |
} | |
} | |
/* | |
RENDERER | |
*/ | |
// Draw the map | |
var canvas = document.getElementById("map"); | |
var context = canvas.getContext("2d"); | |
for (var x = 0; x < edge; ++x) { | |
for (var y = 0; y < edge; ++y) { | |
if (map[x][y] === 0) { | |
context.fillStyle = config.color_bg_trans ? "rgba(0,0,0,0)" : config.color_bg || "#d8d8d8"; | |
context.fillRect(x * block, y * block, block, block); | |
} else if (map[x][y] === 1) { | |
context.fillStyle = config.color_fg_trans ? "rgba(0,0,0,0)" : config.color_fg || "#121212"; | |
context.fillRect(x * block, y * block, block, block); | |
} | |
} | |
} | |
} | |
var ui = [ | |
'render', | |
'save_img', | |
'save_txt', | |
'save_config', | |
'block_size', | |
'map_size', | |
'root_x', | |
'random_root_x', | |
'root_y', | |
'random_root_y', | |
'draw_area', | |
'random_draw_area', | |
'regularity', | |
'random_regularity', | |
'walk_cardinal', | |
'walk_diagonal', | |
'walk_orthogonal', | |
'color_bg', | |
'color_bg_trans', | |
'color_fg', | |
'color_fg_trans', | |
'reset', | |
'load_config' | |
]; | |
function parse_config(config) { | |
var settings = {} | |
function parse(a) { | |
var x = document.getElementById(a) | |
if (x.type === 'number' || x.type === 'range' || x.type === 'color') { | |
settings[a] = x.value | |
} | |
if (x.type === 'checkbox' || x.type === 'radio') { | |
settings[a] = x.checked | |
} | |
//settings[a] = x | |
} | |
if (document && config) { | |
config.forEach(parse) | |
} | |
return settings; | |
} | |
var settings = parse_config(ui); | |
render(settings); | |
//console.log(settings) |
* { | |
box-sizing: border-box; | |
} | |
body { | |
background: #212121; | |
color: #f8f8f8; | |
margin: 0; | |
} | |
#output { | |
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAAAAACo4kLRAAAAF0lEQVR4AWN4DQcP4WBoCyKYCOkhLQgA355ncNg2H2MAAAAASUVORK5CYII='); | |
background-color: #f8f8f8; | |
height: 60%; | |
overflow: auto; | |
position: absolute; | |
text-align: center; | |
top: 0; | |
width: 100%; | |
} | |
#controls { | |
//align-items: top; | |
background: #212121; | |
bottom: 0; | |
color: #f8f8f8; | |
//display: flex; | |
//flex-flow: row wrap; | |
font-family: monospace; | |
height: 40%; | |
//justify-content: flex-start; | |
overflow: scroll; | |
padding: 1em; | |
position: absolute; | |
width: 100%; | |
} | |
fieldset { | |
background: #898989; | |
border: 2px solid #989898; | |
border-radius: 0.25em; | |
box-shadow: 0.25em 0.25em 0.25em rgba(0,0,0,0.2); | |
display: inline-block; | |
margin: 0.25em; | |
vertical-align: top; | |
} | |
legend { | |
background: #212121; | |
border: none; | |
border-bottom: 2px solid #989898; | |
border-radius: 0.25em; | |
color: #989898; | |
padding: 0.25em 0.5em; | |
} | |
hr { | |
border: none; | |
border-top: 2px solid #989898; | |
} | |
button, | |
input[type=checkbox], | |
input[type=radio], | |
input[type=range], | |
input[type=color] { | |
.reset-inheritance; | |
} | |
input[type=number], | |
input[type=text] { | |
.reset-inheritance; | |
width: 8em; | |
padding: 0.25em; | |
&:focus { | |
outline: 3px solid rgba(0,255,255,0.6); | |
} | |
} | |
.reset-inheritance { | |
font-family: inherit; | |
font-size: inherit; | |
font-weight: inherit; | |
line-height: inherit; | |
vertical-align: baseline; | |
} |