Orkn's Pulp Devlog

I’ll take the mad part haha, thanks! I’m glad these posts are of interest :slight_smile:

Procedural Generation of Maps

In Patterns of the Wind the player travels eastwards in their hot air balloon across a linear sequence of maps, eventually returning to the first map on circumnavigating the globe. Each map is procedurally generated, with each map room script defining how it is uniquely generated by chaining together various procgen events I have generically defined in a dedicated item tile script.

For example the first map room script looks like this:

on init do
	tell "procgen" to
		call "clear"
		call "river"
		call "river"
		call "townNW"
		call "townNE"
		call "townSW"
		call "townSE"
	end
	emit "country"
end

where procgen is the name of the item tile containing those generic events.

We already saw one such map generated with this script in the previous post, but here are a couple of others showing some of the variety possible:

Before taking a look at some of these procgen events, it's worth recalling that the PulpScript docs state right at the top:

A good rule of thumb when writing PulpScript is do less, less often.

This is very good advice! There are lots of approaches to procedural generation, but even those generally considered "simple" can be very taxing in Pulp with its lack of arrays and very low performance ceiling. Maintaining a good frame rate at least isn't too important for this use of procgen as the world is generated once upfront when starting a new run, and I simply fill the screen white with a "generating..." label while looping through the rooms and initialising them in the background. Currently this takes about one second per map room, so the player simply waits a few seconds at the start of the run and isn't aware that the frame rate drops to about 1 FPS! Of course I don't want the player waiting too long on starting a run, and I would definitely need to worry if a single frame took so long as to approach the Playdate's ten second watchdog limit for processing a single frame after which the Playdate will assume the game is frozen and force a crash, but I keep the generation simple enough for that not to be a problem.

Returning to those procgen events, the procgen tile actually fills the map area of each room prior to generation, with the clear event using two while loops to swap the procgen tile at every map coordinate. This makes it easy to complete the map generation, in this case by emitting the country event after placing the rivers and towns to make any remaining procgen tiles swap themselves to be either fields or forest like so:

on country do
	i = random 1
	if i==0 then
		swap "field"
	elseif i==1 then
		swap "trees"
	end
end

Generating rivers is a little more involved, although still kept as simple as possible. The river event itself calls a more generic waterway event. This is because rivers always start inland, but other maps have features like channels that start at the edge of the map in order to divide the map into separate islands.

on river do
	x = random 4,9
	y = random 5,10
	d = random 1,4
	call "waterway"
end

The waterway event requires a starting location and a direction in which the waterway should flow. It's possible for the waterway to turn to the left or right, but it will never flow back on itself in the opposite direction to its initial direction of flow, and it will always run straight for at least two tiles before turning:

on waterway do
	// Requires initial coords x,y and direction d
	if d==1 then
		dy = -1
		dx = 0
	elseif d==2 then
		dy = 1
		dx = 0
	elseif d==3 then
		dy = 0
		dx = -1
	elseif d==4 then
		dy = 0
		dx = 1
	end
	run = 0
	turned = 0
	t = name x,y
	while t=="procgen" do
		tell x,y to
			swap "water"
		end
		run++
		if run>2 then
			i = random 1
			if i==1 then
				run = 0
				if turned!=0 then
					_ = dx
					dx = dy
					dy = _
					dx *= turned
					dy *= turned
					turned = 0
				else
					turned = random 1
					if turned==0 then
						turned = -1
					end
					_ = dx
					dx = dy
					dy = _
					dx *= turned
					dy *= turned
				end
			end
		end
		x += dx
		y += dy
		t = name x,y
	end
end

A river will keep going until it hits any tile that isn't the procgen tile - for the first river on this map that'll be the ocean that surrounds the map, but the second river can terminate either by flowing into the ocean or into the first river. In the above screenshots you can see how the second river can become a tributary of the first, and even how a central lake with two rivers flowing from it can emerge from these very simple rules - the lake is really just two rivers starting near each other and flowing past each other in opposite directions without colliding! I landed on these rules for river generation by starting even simpler and experimenting until the generated feature started to look reliably river-like, so the emergent features like lakes were a nice surprise as I didn't explicitly code for them!

The first map is defined by those two rivers (and the emergent features they can create), but other maps follow very different rules. Here are some screenshots of a map using the same waterway event but starting at the map edges to form channels that divide the map into several large islands:

The above two maps both follow the approach of first placing key features and then emitting the country event to randomly fill in the rest of the land. Another map swaps that last step for filling what is left with water, creating a map of small islands instead:

Again you can see some emergent features here! The generation rules simply place four towns and attempt to place three land tiles around around those towns (using the country event again, but called on specific tiles, not emitted). This "should" just create four 2x2 islands. When the towns are up against the edge of the map however the islands can't extend into the ocean, semi-often creating these 2x1 (or 1x2) islands instead, and sometimes the towns will be closed sufficiently close together that larger islands will be created.

That's a quick look at procedural generation of the maps themselves, next up is the procedural generation of their wind patterns!

3 Likes