Okay, here is the code for two python scripts. The first implements the diamond-square algorithm (which is the algorithm described in the article r1ckparker linked to. That happens to be one of the articles I learned from as well. Very good - study it!). The second, while I have modified it a bit, is not my own, though I cannot remember exactly where I got it from. It outputs the heightmap as a bmp file.
I make no claims as to the robustness of this code and it isn't well commented (I never intended for it to be seen by others), but it should be good enough to learn from and as a starting point to tinker with. I've stripped out almost everything except for the actual diamond-square functions and some helpers. Find the meat of the algorithm at the bottom of the first script.
Diamond-square:
#Aule
#Function for generating a world heightmap using fractal algorithms
import random
from bmp import *
#bmp.py must exist in same directory as this script
size = 0
start_Value = 0
n = 0
A = [[v]*n for x in xrange(n)]
range_Mod = 1
square_Side = n
H = .5
change = False
normal = 0
variation = 50
start_Height = 0
def gen(N_start=8, mapName="map", H_val=.5, norm=5, vary=40, start_H=5):
if N_start > 12:
print "That is a very large map. Please enter a value of 11 or less."
return
if N_start < 2:
print "Let's think a tad larger, shall we?"
return
global n
global square_Side
global start_Value
global A
global range_Mod
global size
global H
global change
global normal
global variation
global start_Height
H = H_val
normal = norm
variation = vary
start_Height = start_H
range_Mod = 1
n = (2**N_start) + 1
size = n
v = 0
A = [[start_Value]*n for x in xrange(n)]
A[0][0] = start_Height
A[0][size-1] = start_Height
A[size-1][0] = start_Height
A[size-1][size-1] = start_Height
square_Side = n
run()
print_BMP(mapName)
def print_BMP(name="map"):
create_BMP(A, size, name)
def in_Array(point):
"""returns True if (x, y) is within the array"""
if X(point) >= n:
return False
elif Y(point) >= n:
return False
elif X(point) < 0:
return False
elif Y(point) < 0:
return False
else:
return True
def get(point, square_Side=size):
x = X(point)
y = Y(point)
if x >= n:
x = square_Side/2
if x < 0:
x = (n-1)- (square_Side/2)
if y >= n:
y = square_Side/2
if y < 0:
y = (n-1)- (square_Side/2)
return A[x][y]
def get_Simple(point):
x = X(point)
y = Y(point)
if in_Array(point):
return A[x][y]
else:
return False
def X(point):
"""Retrieves X coord from point tuple"""
return point[0]
def Y(point):
"""Retrieves Y coord from point tuple."""
return point[1]
def highest_Value():
"""Returns the most positive value in the array."""
highest=0
for row in A:
for cell in row:
if cell > highest:
highest = cell
return highest
def highest_Coordinate():
"""Returns the most positive value in the array."""
highest_val = 0
highest_coord = (0, 0)
for x, row in enumerate(A):
for y, cell in enumerate(row):
if cell > highest_val:
highest_val = cell
highest_coord = (x, y)
return highest_coord
def lowest_Value():
"""Returns the most negative value in the array."""
lowest=0
for row in A:
for cell in row:
if cell < lowest:
lowest = cell
return lowest
def lowest_Coordinate():
"""returns tuple (x,y) of the lowest value, or False if not found."""
lowest_val = 0
lowest_coord = (0, 0)
for x, row in enumerate(A):
for y, cell in enumerate(row):
if cell < lowest_val:
lowest_val = cell
lowest_coord = (x, y)
return lowest_coord
def get_Neighbors(current):
"""Returns a list with the eight neighboring coordinates of any point."""
N = (current[0], current[1]-1)
NE = (current[0]+1, current[1]-1)
E = (current[0]+1, current[1])
SE = (current[0]+1, current[1]+1)
S = (current[0], current[1]+1)
SW = (current[0]-1, current[1]+1)
W = (current[0]-1, current[1])
NW = (current[0]-1, current[1]-1)
nList = [N, NE, E, SE, S, SW, W, NW]
return nList
def get_Cardinal_Neighbors(current):
"""Returns a list with the eight neighboring coordinates of any point."""
N = (current[0], current[1]-1)
E = (current[0]+1, current[1])
S = (current[0], current[1]+1)
W = (current[0]-1, current[1])
nList = [N, E, S, W]
return nList
def diamond(point, square_Side):
"""Operates diamond step on four values around point."""
p1 = X(point), Y(point) - (square_Side/2)
p2 = X(point) + (square_Side/2), Y(point)
p3 = X(point), Y(point) + (square_Side/2)
p4 = X(point) - (square_Side/2), Y(point)
points = [p1, p2, p3, p4]
for p in points:
if A[X(p)][Y(p)] != start_Value: #already set
continue
c1 = X(p), Y(p) - (square_Side/2)
c2 = X(p) + (square_Side/2), Y(p)
c3 = X(p), Y(p) + (square_Side/2)
c4 = X(p) - (square_Side/2), Y(p)
total_Points = 4
point_Total = 0
point_Total += get(c1, square_Side)
point_Total += get(c2, square_Side)
point_Total += get(c3, square_Side)
point_Total += get(c4, square_Side)
point_Average = point_Total/total_Points
point_Value = int((point_Average + \
random.normalvariate(normal, variation)*range_Mod))
A[X(p)][Y(p)] = point_Value
return
def square(point, square_Side):
"""Operates square step around point."""
if A[X(point)][Y(point)] != start_Value:
print "Conflict",
c1 = X(point) - (square_Side/2), Y(point) + (square_Side/2)
c2 = X(point) + (square_Side/2), Y(point) + (square_Side/2)
c3 = X(point) + (square_Side/2), Y(point) - (square_Side/2)
c4 = X(point) - (square_Side/2), Y(point) - (square_Side/2)
total_Points = 4
point_Total = 0
point_Total += get(c1)
point_Total += get(c2)
point_Total += get(c3)
point_Total += get(c4)
point_Average = point_Total/total_Points
point_Value = int((point_Average + \
random.normalvariate(normal, variation)*range_Mod))
A[X(point)][Y(point)] = point_Value
return
def run():
"""Pretty self-explanatory, dont you think?"""
global square_Side
global range_Mod
global H
global change
if square_Side < 3: #recursion end case
return
else:
if (n == square_Side):
squares = 1
else:
squares = n/(square_Side-1)
for i in range(squares):
for j in range(squares):
x = square_Side/2 + ((square_Side-1)*i)
y = square_Side/2 + ((square_Side-1)*j)
point = x, y
square(point, square_Side)
for i in range(squares):
for j in range(squares):
x = square_Side/2 + ((square_Side-1)*i)
y = square_Side/2 + ((square_Side-1)*j)
point = x, y
diamond(point, square_Side)
#have now run one iteration. Decrement square_Side
#and recalc range_Mod and keep going.
if change and square_Side < ((n/8)+1):
H = 0.8
change = False
square_Side = (square_Side/2)+1
range_Mod = round(range_Mod * 2**-H, 2)
run()
def get_Max_Size():
return n
def get_Section(size_X, size_Y, startX=0, startY=0):
"""Returns a subsection of A from the origin (0, 0) to (size_X, size_Y)"""
if size_X >= n or size_Y >= n:
return False
sub = [[0]*size_X for x in xrange(size_Y)]
for x in range(size_Y):
for y in range(size_X):
sub[x][y] = A[x][y]
return sub
if __name__ == "__main__":
gen()
bmp-drawing
#Some key imports.
#Struct is used to create the actual bytes.
#It is super handy for this type of thing.
import struct, random
#Function to write a bmp file. It takes a dictionary (d) of
#header values and the pixel data (bytes) and writes them
#to a file. This function is called at the bottom of the code.
def bmp_write(d,the_bytes, mapName):
mn1 = struct.pack('<B',d['mn1'])
mn2 = struct.pack('<B',d['mn2'])
filesize = struct.pack('<L',d['filesize'])
undef1 = struct.pack('<H',d['undef1'])
undef2 = struct.pack('<H',d['undef2'])
offset = struct.pack('<L',d['offset'])
headerlength = struct.pack('<L',d['headerlength'])
width = struct.pack('<L',d['width'])
height = struct.pack('<L',d['height'])
colorplanes = struct.pack('<H',d['colorplanes'])
colordepth = struct.pack('<H',d['colordepth'])
compression = struct.pack('<L',d['compression'])
imagesize = struct.pack('<L',d['imagesize'])
res_hor = struct.pack('<L',d['res_hor'])
res_vert = struct.pack('<L',d['res_vert'])
palette = struct.pack('<L',d['palette'])
importantcolors = struct.pack('<L',d['importantcolors'])
#create the outfile
outfile = open(mapName + ".bmp",'wb')
#write the header + the_bytes
outfile.write(mn1+mn2+filesize+undef1+undef2+offset+headerlength+width+height+\
colorplanes+colordepth+compression+imagesize+res_hor+res_vert+\
palette+importantcolors+the_bytes)
outfile.close()
###################################
def create_BMP(height_array, size, mapName):
#Here is a minimal dictionary with header values.
#Of importance is the offset, headerlength, width,
#height and colordepth.
#Edit the width and height to your liking.
#These header values are described in the bmp format spec.
#You can find it on the internet. This is for a Windows
#Version 3 DIB header.
d = {
'mn1':66,
'mn2':77,
'filesize':0,
'undef1':0,
'undef2':0,
'offset':54,
'headerlength':40,
'width':size,
'height':size,
'colorplanes':0,
'colordepth':24,
'compression':0,
'imagesize':0,
'res_hor':0,
'res_vert':0,
'palette':0,
'importantcolors':0
}
#Function to generate a random number between 0 and 255
def rand_color():
x = random.randint(0,255)
return x
#Build the byte array. This code takes the height
#and width values from the dictionary above and
#generates the pixels row by row. The row_mod and padding
#stuff is necessary to ensure that the byte count for each
#row is divisible by 4. This is part of the specification.
the_bytes = ''
for row in range(d['height']-1,-1,-1):# (BMPs are L to R from the bottom L row)
for column in range(d['width']):
h = height_array[row][column]
if h > 255:
h = 255
if h < 0:
b = 255 - abs(h)
g = 0
r = 0
else:
b = h
g = h
r = h
pixel = struct.pack('<BBB',b,g,r)
the_bytes = the_bytes + pixel
row_mod = (d['width']*d['colordepth']/8) % 4
if row_mod == 0:
padding = 0
else:
padding = (4 - row_mod)
padbytes = ''
for i in range(padding):
x = struct.pack('<B',0)
padbytes = padbytes + x
the_bytes = the_bytes + padbytes
#call the bmp_write function with the
#dictionary of header values and the
#bytes created above.
bmp_write(d,the_bytes, mapName)