Simple continent map generators for Freeciv -- no long wispy, stringy, strips of land

Can you help improve your favourite game? Hardcore C mages, talented artists, and players with any level of experience are welcome!
Post Reply
Molo_Parko
Hardened
Posts: 198
Joined: Fri Jul 02, 2021 4:00 pm

Simple continent map generators for Freeciv -- no long wispy, stringy, strips of land

Post by Molo_Parko »

This topic keeps coming-up, so here is a simple solution.

If you have other simple solutions, please feel free to post them in this topic.

This "continent map generator" is a bash script ( created and tested on a Mac. ) Copy the code from this post, or download the attached zip file and decompress it.

Also attached is a functional Freeciv 2.6.4 or later Scenario file including this generated map.

The script uses a simple process to create the map, and it's best shown in the pictures below.

The map starts out as a field of random characters which represent land tiles in Freeciv. Then the horizontal and vertical ocean areas ( the white spaces between and around the rectangles) are imposed. By the way, text characters are usually taller than they are wide, which results in a vertical stretching of what the final map will look like in Freeciv, if using square tiles at least.

Image

The script loops through the map multiple times, top-to-bottom and bottom-to-top, selecting land tiles near ocean ( the white spaces ) to become ocean.
Image

Next is the result of loop 2. If you like big clunky continents, you can change the # in the script to stop at loop 2 or 3.
Image

Below is loop 3's output.
Image

And finally, below loop 4 has finished and I like the continents.
Image

The last step in the script is to add the proper formatting for this map to be used in a Freeciv scenario ".sav" file.
Image

Below is the text of the final map in Freeciv scenario [map] layer format.

Code: Select all

t0000="                                                                  "
t0001="                                                                  "
t0002="                                                                  "
t0003="                                                        g         "
t0004="                                                  ggg  hgg        "
t0005="                       gp                        ggfghmhph        "
t0006="             fp       pfpfg            hg       hggpmphph         "
t0007="              h    p hpggpg            phgp   hhmgfffphgfm        "
t0008="             hmg  p gpphgmp             fggg  pggffgfpfgpf        "
t0009="             fgp   ghgppphg            hfmggg gfgggggpgff         "
t0010="             fgf  hggghffhg               hfgpgghpffpmggf         "
t0011="                  ggmggh  p             f fphfgpfpfhppfgp         "
t0012="                  fp hp                     gm h hg hgpgg         "
t0013="                  m                           g hf   f fg         "
t0014="                                                      pf          "
t0015="                                                                  "
t0016="                                                                  "
t0017="                                                                  "
t0018="                                                                  "
t0019="                                                                  "
t0020="                                                                  "
t0021="                                                                  "
t0022="           gf    gg    f                                          "
t0023="          fgggh gpfg pffg                       hm        p       "
t0024="         hfhghghhhgfggfh               h h     gpp     ggh g      "
t0025="        mhhhfmfgphpmpghg               pggp   gmfgg    pgpf       "
t0026="         phgpfhfhph  fgg               fhfppp fpfgh    gff        "
t0027="        hhggpffhpfg ghpg                 gppf ffpfhg   gmp        "
t0028="       g gfghfphggh  mpfmh               hgpppgmpmgf p  g         "
t0029="       m     phh pgh   gppp             gffpffgf gfpm   gp        "
t0030="                    hf ggpf            h fggppfhg  g     h        "
t0031="                         f                 fp             p       "
t0032="                                                                  "
t0033="                                                                  "
t0034="                                                                  "
t0035="                                                                  "

Image
^ Here's a screenshot of that upper-right island in Freeciv 2.6.4 with Amplio 2 (square) tile graphics.

Image
^ And a close-up of the minimap.

Image
^ This picture shows the top of the Freeciv scenario ".sav" file content.

Image
^ And here I had just pasted the new map over the old map in the scenario. Both the old and new maps are 66 x 36 tiles ( you can change the size in the script very easily) so a simple cut-and-paste of the new map over the old map is fine* and after saving the scenario file, it's ready to play.
* = Because none of the other layers has any features at all, no river, no roads, etc.

And finally, below is the full text of the bash script that generated the map.

Code: Select all

#!/usr/bin/env bash

# This line is a comment, the line at the top is not.
# map generator ContinentGen Bash script.sh v1.8

# Declare variables
declare    zeroPad="0000" tile test
declare -a terrain_layer terrain_layer_B
declare -i x y c=0 r blanklines maxgap=12

# The map size is determined by the xsize and ysize variables on the next line
declare -i xsize=120 ysize=60

# The # of rectangles is set in the variable rectangles, below.
# Valid values are 1, 2, -2, 4, 6, -6, 8, -8, 9, and 16.
# 2 gives 2 vertical continents, -2 gives 2 horizontal, similar for 6, -6, and 8, -8

declare -i rectangles=8

# maxgap controls how many contiguous blank horizontal lines
# are allowed in the map before looping stops.
# The map dimensions also effect the shape and size of each continent.
if [ ${rectangles} -eq 1 ] ; then
	maxgap=12
elif [ ${rectangles} -eq 2 ] ; then
	maxgap=10
elif [ ${rectangles} -eq -2 ] ; then
	maxgap=10
elif [ ${rectangles} -eq 4 ] ; then
	maxgap=10
elif [ ${rectangles} -eq 6 ] ; then
	maxgap=10
elif [ ${rectangles} -eq 8 ] ; then
	maxgap=7
elif [ ${rectangles} -eq -8 ] ; then
	maxgap=7
elif [ ${rectangles} -eq 9 ] ; then
	maxgap=12
elif [ ${rectangles} -eq 16 ] ; then
	maxgap=12
fi

# Ouput a blank line
echo

# Create terrain layer
for (( y=0 ; y<${ysize} ; y++ )) ; do
	for (( x=0 ; x<${xsize} ; x++ )) ; do

		r=$(( 1 + RANDOM % 19 ))

		# No lake tiles, add them later
		if [ ${r} -ge 1 ] && [ ${r} -le 5 ] ; then tile="g" ; fi
		if [ ${r} -ge 6 ] && [ ${r} -le 9 ] ; then tile="p" ; fi
		if [ ${r} -ge 10 ] && [ ${r} -le 12 ] ; then tile="h" ; fi
		if [ ${r} -ge 13 ] && [ ${r} -le 15 ] ; then tile="f" ; fi
		if [ ${r} -eq 16 ] ; then tile="m" ; fi
		if [ ${r} -eq 17 ] ; then tile="d" ; fi
		if [ ${r} -eq 18 ] ; then tile="j" ; fi
		if [ ${r} -eq 19 ] ; then tile="s" ; fi


		if [ ${rectangles} -eq 1 ] ; then
			# No divisions, 1 supercontinent
			:
		elif [ ${rectangles} -eq 2 ] ; then
			# 1 row of ocean vertical middle centerline
			if [ ${x} -eq $(( ${xsize} / 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq -2 ] ; then
			# 1 row of ocean horizontal middle centerline
			if [ ${y} -eq $(( ${ysize} / 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq 4 ] ; then
			# 1 column of ocean vertical middle centerine
			if [ ${x} -eq $(( ${xsize} / 2 )) ] ; then tile=" " ; fi
			# 1 row of ocean horizontal middle centerline
			if [ ${y} -eq $(( ${ysize} / 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq 6 ] ; then
			# 2 columns of ocean vertical
			if [ ${x} -eq $(( ${xsize} / 3 )) ] ; then tile=" " ; fi
			if [ ${x} -eq $(( ${xsize} / 3 * 2 )) ] ; then tile=" " ; fi
			# 1 row of ocean horizontal middle centerline
			if [ ${y} -eq $(( ${ysize} / 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq -6 ] ; then
			# 1 column of ocean vertical
			if [ ${x} -eq $(( ${xsize} / 2 )) ] ; then tile=" " ; fi
			# 2 rows of ocean horizontal
			if [ ${y} -eq $(( ${ysize} / 3 )) ] ; then tile=" " ; fi
			if [ ${y} -eq $(( ${ysize} / 3 * 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq 8 ] ; then
			# 3 columns of ocean vertical
			if [ ${x} -eq $(( ${xsize} / 4 )) ] ; then tile=" " ; fi
			if [ ${x} -eq $(( ${xsize} / 4 * 2 )) ] ; then tile=" " ; fi
			if [ ${x} -eq $(( ${xsize} / 4 * 3 )) ] ; then tile=" " ; fi
			# 1 row of ocean horizontal middle centerline
			if [ ${y} -eq $(( ${ysize} / 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq -8 ] ; then
			# 1 column of ocean vertical middle centerine
			if [ ${x} -eq $(( ${xsize} / 2 )) ] ; then tile=" " ; fi
			# 3 rows of ocean horizontal middle centerline
			if [ ${y} -eq $(( ${ysize} / 4 )) ] ; then tile=" " ; fi
			if [ ${y} -eq $(( ${ysize} / 4 * 2 )) ] ; then tile=" " ; fi
			if [ ${y} -eq $(( ${ysize} / 4 * 3 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq 9 ] ; then
			# 1 column of ocean vertical per 1/3rd of map
			if [ ${x} -eq $(( ${xsize} / 3 )) ] ; then tile=" " ; fi
			if [ ${x} -eq $(( ( ${xsize} / 3 ) * 2 )) ] ; then tile=" " ; fi
			# 1 row of ocean horizontal per 1/3rd of map
			if [ ${y} -eq $(( ${ysize} / 3 )) ] ; then tile=" " ; fi
			if [ ${y} -eq $(( ( ${ysize} / 3 ) * 2 )) ] ; then tile=" " ; fi
		elif [ ${rectangles} -eq 16 ] ; then
			# 1 column of ocean vertical per 1/4 of map
			if [ ${x} -eq $(( ${xsize} / 4 )) ] ; then tile=" " ; fi
			if [ ${x} -eq $(( ( ${xsize} / 4 ) * 2 )) ] ; then tile=" " ; fi
			if [ ${x} -eq $(( ( ${xsize} / 4 ) * 3 )) ] ; then tile=" " ; fi
			# 1 row of ocean horizontal per 1/4 of map
			if [ ${y} -eq $(( ${ysize} / 4 )) ] ; then tile=" " ; fi
			if [ ${y} -eq $(( ( ${ysize} / 4 ) * 2 )) ] ; then tile=" " ; fi
			if [ ${y} -eq $(( ( ${ysize} / 4 ) * 3 )) ] ; then tile=" " ; fi
		fi

		# 1 row of ocean at map edges left, right, top, bottom
		if [ ${x} -eq 0  ] ; then tile=" " ; fi
		if [ ${y} -eq 0  ] ; then tile=" " ; fi
		if [ ${x} -eq $(( ${xsize} - 1 )) ] || [ ${y} -eq $(( ${ysize} - 1 )) ] ; then tile=" " ; fi

		terrain_layer[${y}]="${terrain_layer[${y}]}${tile}"
	done
done

# output original rectangles
echo "Before the loops:"
for ((y=0;y<${ysize};y++)) ; do
	echo "t${zeroPad:${#y}}${y}=\"${terrain_layer[${y}]}\""
done


# Now loop top-to-bottom and bottom-to-top through the lines
# and look for land tiles next to ocean
# and change some of the land tiles to ocean too
# and output the map after each loop

# Loops up to 16 times, but stops if there are too many blank lines

for (( loop=1 ; loop<=18 ; loop++ )) ; do

	test=""
	blanklines=0
	echo ; echo

	# print loop header with loop #
	echo "loop: ${loop}"

	for (( z=0; z<${ysize} ; z++ )) ; do

		if [ $(( loop % 2 )) -eq 0 ] ; then
			# even loop number, loop backward, ysize to 0
			y=$(( ysize - z ))
		else
			# odd loop number, loop forward, 0 to ysize
			y=z
		fi

		terrain_layer_B[${y}]=""

		for (( x=0 ; x<${xsize} ; x++ )) ; do

			# This is the tile which we might change from land to ocean
			tile="${terrain_layer[${y}]:${x}:1}"

			# If this tile is not ocean then check the tiles around it
			if [ "${tile}" != " " ] ; then

				# Next are the 8 tiles of the ring around that tile

				# above the tile
				if [ "${terrain_layer[$((${y}-1))]:$((${x}-1)):1}" == " " ] ; then c=$((c+1)) ; fi
				if [ "${terrain_layer[$((${y}-1))]:${x}:1}" == " " ] ; then c=$((c+1)) ; fi
				if [ "${terrain_layer[$((${y}-1))]:$((${x}+1)):1}" == " " ] ; then c=$((c+1)) ; fi

				# beside the tile
				if [ "${terrain_layer[${y}]:$((${x}-1)):1}" == " " ] ; then c=$((c+1)) ; fi
				if [ "${terrain_layer[${y}]:$((${x}+1)):1}" == " " ] ; then c=$((c+1)) ; fi

				# below the tile
				if [ "${terrain_layer[$((${y}+1))]:$((${x}-1)):1}" == " " ] ; then c=$((c+1)) ; fi
				if [ "${terrain_layer[$((${y}+1))]:${x}:1}" == " " ] ; then c=$((c+1)) ; fi
				if [ "${terrain_layer[$((${y}+1))]:$((${x}+1)):1}" == " " ] ; then c=$((c+1)) ; fi

				if [ "${tile}" != " " ] ; then
					# If only 1 neighboring tile is ocean,
					# then only a small chance of changing the tile to ocean
					if [ "${c}" -eq 1 ] ; then
						if [ $(( 1 + RANDOM % 10 )) -ge 9 ] ; then
							tile=" "
						fi
					# If 2 neighboring tiles are ocean, slightly better chance
					elif [ "${c}" -eq 2 ] ; then
						if [ $(( 1 + RANDOM % 10 )) -ge 8 ] ; then
							tile=" "
						fi
					# If 3 neighboring tiles are ocean, an even better chance
					elif [ "${c}" -eq 3 ] ; then
						if [ $(( 1 + RANDOM % 10 )) -ge 7 ] ; then
							tile=" "
						fi
					# If more than 3 neighboring tiles are ocean,
					# then change this one to ocean too
					elif [ "${c}" -gt 3 ] ; then
						tile=" "
					fi
				fi
			fi
			c=0
			terrain_layer_B[${y}]="${terrain_layer_B[${y}]}${tile}"
		done
		terrain_layer[${y}]="${terrain_layer_B[${y}]}"

# track percentage of map that is land tiles?

		# if a significant portion of the map lines are blank on this loop, then break
		test="${terrain_layer[${y}]// /}"
		if [ "${#test}" -eq 0 ] ; then
			blanklines=$(( ${blanklines} + 1 ))
		else
			blanklines=0
		fi

		if [ ${blanklines} -gt ${maxgap} ] ; then
			echo "Loop ${loop} aborted due to # of blank lines=${blanklines}"
			break 3
		fi
	done

	# This would be a good place to add lake tiles

	for ((y=0;y<${ysize};y++)) ; do
		echo "t${zeroPad:${#y}}${y}=\"${terrain_layer_B[${y}]}\""
	done
done

# Print 1 last blank line
echo

exit 0
^ Updated to version 1.8 source

Image
^ Screenshot of a 120x60 map of 6 horizontally oriented continents, generated by v1.8. Two of the original rectangles separated, and it's now 8 continents and a few small islands.

Update to 1.8: does 1, 2, 4, 6, 8, 9, or 16 continents at a time, some in two orientations. Updated version 1.7 is attached allows 4 or 6 rectangles initially, 1.6 doesn't add lake tiles, does add Jungle and Swamp tiles. 1.5: changed to thinner white lines initially. 1.4 added looping up to 16 times but stops if there are too many blank lines (the land becomes too wispy.) Also displays each loop's output with proper formatting for insertion into Freeciv scenario [map] section -- so you can copy whichever loop's output you like.

That's it.
Attachments
map generator ContinentGen Bash script v1.8.sh.txt.zip
(5.03 KiB) Downloaded 21 times
map generator ContinentGen Bash script v1.7.sh.txt.zip
(2.56 KiB) Downloaded 23 times
ContinentGen sample scenario.sav.zip
(5.52 KiB) Downloaded 27 times
Last edited by Molo_Parko on Mon Dec 02, 2024 5:35 am, edited 1 time in total.
Molo_Parko
Hardened
Posts: 198
Joined: Fri Jul 02, 2021 4:00 pm

Re: Simple continent map generators for Freeciv

Post by Molo_Parko »

Well how about just simple rectangles and the whole set of other layers (empty) for at least Freeciv 2.6.4 through 2.6.11 ?

Image
^ Generates all the layers: t, blank startpos section (works well with the blocks of land), e00 through e03, and res.

Code: Select all

#!/usr/bin/env bash

# This generates blocks of land on ocean
# and output is the terrain layer, startpos (blank),
# and the other layers needed for Freeciv 2.6.4 through 2.6.11 scenarios

# Ouput a blank line
echo

# Declare variables
declare    zeroPad="0000" tile layerPrefix lineOfSpaces
declare -a terrain_layer
declare -i x y r breaks

# The map size is determined by the xsize and ysize variables on the next line
# and those can be set from the command line when invoking the script by adding 80 60, for example
declare -i xsize="${1:-92}" ysize="${2:-46}"

if [ ${xsize} -lt 30 ] || [ ${ysize} -lt 30 ] ; then
	echo "Too small, try x and y sizes greater or equal to 30"
	echo
	exit 1
fi

printf -v lineOfSpaces '%*s ' $(( ${xsize} - 1 ))

# Ouput a blank line
echo

for (( y=0 ; y<${ysize} ; y++ )) ; do
	for (( x=0 ; x<${xsize} ; x++ )) ; do

		r=$(( 1 + RANDOM % 19 ))

		# No lake tiles, add them later
		if [ ${r} -ge 1 ] && [ ${r} -le 5 ] ; then tile="g" ; fi
		if [ ${r} -ge 6 ] && [ ${r} -le 9 ] ; then tile="p" ; fi
		if [ ${r} -ge 10 ] && [ ${r} -le 12 ] ; then tile="h" ; fi
		if [ ${r} -ge 13 ] && [ ${r} -le 15 ] ; then tile="f" ; fi
		if [ ${r} -eq 16 ] ; then tile="m" ; fi
		if [ ${r} -eq 17 ] ; then tile="d" ; fi
		if [ ${r} -eq 18 ] ; then tile="j" ; fi
		if [ ${r} -eq 19 ] ; then tile="s" ; fi

		breaks=$(( ( ( "${xsize}" / 6 ) + ( "${ysize}" / 6 ) ) / 2 ))

		if [ "${x}" -ge 0 -a $(( "${x}" % "${breaks}" )) -ge 0 -a $(( "${x}" % "${breaks}" )) -lt 4 ] ; then
			tile=" "
		fi

		if [ "${y}" -ge 0 -a $(( "${y}" % "${breaks}" )) -ge 0 -a $(( "${y}" % "${breaks}" )) -lt 4 ] ; then
			tile=" "
		fi

		terrain_layer[${y}]="${terrain_layer[${y}]}${tile}"
	done
done

# Output the map sections formatted for at least, Freeiv 2.6.4 through 2.6.11
for (( y=0 ; y<${ysize} ; y++ )) ; do
	echo "t${zeroPad:${#y}}${y}=\"${terrain_layer[${y}]}\""
done

# Output the startpos info (blocks of land do well with startpos defaults so no specific values needed)
echo -e 'startpos_count=0\nstartpos={"x","y","exclude","nations"\n}'

# Output the other layers
for layerPrefix in e00_ e01_ e02_ e03_ res ; do
	for (( y=0 ; y<${ysize} ; y++ )) ; do
		echo -n "${layerPrefix}${zeroPad:${#y}}${y}="
		if [ "${layerPrefix}" = "res" ] ; then
			echo "\"${lineOfSpaces}\""
		else
			echo "\"${lineOfSpaces// /0}\""
		fi
	done
done

# Ouput a blank line
echo

exit 0

Or a Lua script for stand-alone Lua

Code: Select all

-- Written and tested in Lua 5.3.0 on Mac OS
-- http://www.lua.org/ftp/lua-5.3.0.tar.gz

-- Set map size here
xsize = 92
ysize = 46

-- Set # of breaks ( lines of ocean vs land)
breaks = math.floor( ( ( xsize / 6 ) + ( ysize / 6 ) ) / 2 )

terrain_layer = {}
zeroPad = "0000"
math.randomseed( ( '' .. os.time() ):reverse() )

for y = 0, ysize - 1, 1 do

	terrain_layer[ y ] = ""

	for x = 0, xsize - 1, 1 do

		local terrain = ""
		local z = math.random( 1, 74 )

		if     z >=  1 and z<= 30 then terrain = "g"
		elseif z >= 31 and z<= 61 then terrain = "p"
		elseif z >= 62 and z<= 65 then terrain = "f"
		elseif z >= 66 and z<= 70 then terrain = "h"
		elseif z == 71 then terrain = "j"
		elseif z == 72 then terrain = "s"
		elseif z == 73 then terrain = "d"
		elseif z == 74 then terrain = "m"
		end


		if x >= 0 and x % breaks >= 0 and x % breaks < 4 then
			terrain=" "
		end

		if y >= 0 and y % breaks >= 0 and y % breaks < 4 then
			terrain=" "
		end

		terrain_layer[ y ] = terrain_layer[ y ] .. terrain
	end

	print( "t" .. string.sub( zeroPad, 1, string.len( zeroPad ) - string.len( y ) )
		.. y .. "=\"" .. terrain_layer[ y ] .. "\"" )
end

-- output blank startpos info
print( 'startpos_count=0\nstartpos={"x","y","exclude","nations"\n}' )

-- output the "e" layers
layers = { "e00_", "e01_", "e02_", "e03_" }
for i = 1, #layers, 1 do
	for y = 0, ysize - 1, 1 do
		print( layers[ i ] .. string.sub( zeroPad, 1, string.len( zeroPad ) - string.len( y ) )
			.. y .. "=\"" .. string.rep( "0", xsize ) .. "\"" )
	end
end

-- output the "res" layer
for y = 0, ysize - 1, 1 do
	print( "res" .. string.sub( zeroPad, 1, string.len( zeroPad ) - string.len( y ) )
		.. y .. "=\"" .. string.rep( " ", xsize ) .. "\"" )
end
^ Updated to v1.1

Oops, I left e00 out of v1.0 of the Lua script, it's back in v1.1.
Attachments
map generator Continent squares Lua script v1.1.lua.txt.zip
(4.72 KiB) Downloaded 8 times
map generator Continent squares Bash script v1.0.sh.txt.zip
(5.35 KiB) Downloaded 10 times
Post Reply