Using "Generate Noise Patterns" with Simplex2 to create a 2D Array for a Dungeon Map

Please specify version and format if asking for help, or apply optional tags above:
Twine Version: 2
Story Format: SugarCube

I’ve watched Dan Cox’s youtube video on Procedural Generation in Twine: Introducing Generative Art (https://youtu.be/6InRIveRig4). And found that I can create a pattern that resembles a 2D array Dungeon Map using his example on Noise Simplex 2 by substituting the characters to 1 and 0:

``````<<nobr>>
<<for _i = 0; _i < 10; _i++>>
<<for _k = 0; _k < 10; _k++>>
<<if window.noise.simplex2(_i, _k) < 0>>
"""0"""
<<else>>
"""1"""
<</if>>
<</for>>
<br>
<</for>>
<</nobr>>
``````

The code for the story JavaScript can be found here: https://github.com/josephg/noisejs

Here is the output:

Can I somehow get this output and substitute this to an array variable?

``````<<set \$mapArray to
[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,1,0,0,0,1,1,1,1,0,0,0,0,0],
[1,0,0,1,0,1,1,1,1,1,0,0,0,0,0],
[1,1,1,1,0,1,0,0,1,1,0,0,1,1,0],
[0,1,1,0,0,1,0,0,1,0,0,0,1,1,0],
[0,1,0,1,0,1,0,0,1,0,0,0,1,1,0],
[0,1,1,1,1,1,0,0,1,1,1,1,1,1,0],
[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]>>
``````
1 Like

You already have most of the code for that, you just need to make a few substitutions:

``````<<nobr>>
<<set \$mapArray = []>>
<<for _i = 0; _i < 10; _i++>>
<<set _tempArray = []>>
<<for _k = 0; _k < 10; _k++>>
<<if window.noise.simplex2(_i, _k) < 0>>
<<set _tempArray.push(0)>>
<<else>>
<<set _tempArray.push(1)>>
<</if>>
<</for>>
<<set \$mapArray.push(_tempArray)>>
<</for>>
<</nobr>>
``````

And that will generate the map array within the \$mapArray variable.

That said, it probably won’t always generate a valid map, so you might want to look for a better method to generate mazes and such.

Enjoy!

Thank you for this! You taught me how to populate an array variable from for loops.

Best!

Took the challenge and used alex-c’s random-walk-map (https://github.com/alex-c/random-walk-map) to generate valid (contiguous maps), and Dan Cox’s “moving through a dungeon” code (https://twinery.org/cookbook/dungeonmoving/sugarcube/sugarcube_dungeonmoving.html) in Twine 2 Sugarcube.

The following Twee Code:

``````:: StoryTitle
[Random Walk Map]

:: Story Stylesheet [stylesheet]
#map {

font-family: monospace;
}

:: Story JavaScript [script]
function random(min, max) {
return Math.floor(Math.random() * (max - min)) + min;
}

function step(x, y, direction) {
return [x + direction[0], y + direction[1]];
}

function validatePosition(map, x, y) {
return x >= 0
&& x < map[0].length
&& y >= 0
&& y < map.length;
}

function generateMap(configOverride) {

//Make config parameter optional
var configOverride = configOverride || {}

//Default configuration overwritten if needed, found this setting to be more organic
var config = {
numberOfPaths: configOverride.numberOfPaths || 125,
minPathLength: configOverride.minPathLength || 3,
maxPathLength: configOverride.maxPathLength || 6,
maxWidth: configOverride.maxWidth || 28,
maxHeight: configOverride.maxHeight || 15,
startPosition: configOverride.startPosition || {x: -1, y: -1}
}

//Ensure valid starting position
if (config.startPosition.x < 0 || config.startPosition.x >= config.maxWidth) {
config.startPosition.x = random(0, config.maxWidth);
}
if (config.startPosition.y < 0 || config.startPosition.y >= config.maxHeight) {
config.startPosition.y = random(0, config.maxHeight);
}

//Generate empty map
var map = [];
for (var y = 0; y < config.maxHeight; y++) {
map.push([]);
for (var x = 0; x < config.maxWidth; x++) {
map[y].push(0);
}
}

//Available directions
var directions = [
[-1,  0], //left
[ 1,  0], //right
[ 0, -1], //down
[ 0,  1]  //up
]

//Initialize variables needed during generation
var x = config.startPosition.x;
var y = config.startPosition.y;
map[y][x] = 1;
var direction;
var lastDirection;
var pathLength;

//Generate paths
for (var i = 0; i < config.numberOfPaths; i++) {

//Select a path length
pathLength = random(config.minPathLength, config.maxPathLength + 1);

//Select direction, which allows at least one step, and is different from the previous direction
do {
direction = directions[random(0, directions.length)];
} while (!validatePosition(map, ...step(x, y, direction)) || (lastDirection && direction == lastDirection));

//Step until path length or map boundary reached
for (var j = 0; j < pathLength; j++) {
//Check if step is possible
if (!validatePosition(map, ...step(x, y, direction))) {
break;
}
//Perform step
[x, y] = step(x, y, direction);
map[y][x] = 1;
}

//Store last direction
lastDirection = direction;
}

//Done generating!
return map;
}

window.exports = generateMap; //use window.exports to call the javascript in your passage

:: Start {"position":"120,288","size":"100,100"}
A map generated using the random-walk method.
<<display Location>>
<<button "Generate">>
<<run Engine.restart()>>
<</button>>

:: StoryInit {"position":"119,396","size":"100,100"}
<<set \$mapArray to window.exports()>>

:: Location {"position":"117,504","size":"100,100"}
<<nobr>>
<span id="map">
<<for \$i to 0; \$i lt \$mapArray.length; \$i++>>
<<for \$k to 0; \$k lt \$mapArray[\$i].length; \$k++>>
<<if \$k eq \$positionX and \$i eq \$positionY>>
<<print "&#64;">>
<<elseif \$mapArray[\$i][\$k] eq 1>>
<<print "&#46;">>
<<elseif \$mapArray[\$i][\$k] eq 0>>
<<print "&#35;">>
<</if>>
<</for>>
<<print "<br>">>
<</for>>
</span>
<</nobr>>
``````

@domvmd
I would leave the special StoryData passage out of any code example I post, because the IFID within it needs to be unique for every Interactive Fiction project. So if someone inadvertently adds your IFID to their project there would now be two projects with the same unique ID.

Done! Thank you for pointing that out. Will do from now on.