Fractal Map Generator

Does not relate directly to the panda3d, but can be useful if you want to generate a map consisting of several types of surfaces. For example water, earth and mountains.
The algorithm is abstracted from any specific implementation of a map, and therefore need to pass two callback functions to work with your version. The first function should accept a tuple (x, y) and return surface type, located at xy.
The second - should take a tuple (x, y), surface type and set this type to the xy coordinate

The class constructor accepts the following parameters:
sample - a list or tuple of surface types. Elements of any type.
getCallback - pointer to the first callback function
setCallback - pointer to the second callback function
dimension - size of the map (x, y)
step - the number of cells that will divide the field at each iteration. Must be > 1. Size of the map should be divisible by step. Ideally - must be a power of step.

generate method takes 2 parameters and returns True if generation successful:
showProgress - show progress of generation (0 - no; 1 - simple (default); 2 - advanced output is only supported on unix-based terminals)
maxIters - limitation of iterations (0 - maximum number of iterations (default); an integer> 0 - restrict the number of iterations; an integer <0 - deducted from the maximum possible number of iterations)

You can assign a template to generate the maps in the form of two-dimensional array whose elements are the types of terrain, or None, which will be filled randomly. Array dimension must also be divisible by step.
setTemplate takes a template and returns True if successful.

import random,sys
class FractalMapGen:
    def __init__(self, sample, getCallback, setCallback, dimension = (64,64), step = 2):
        self.getTile = getCallback
        self.setTile = setCallback
        self.sample = sample
        self.step = step
        self.dimension = dimension
        self.citer = 1
        self.template = None
    
    def setTemplate(self,template):
        msx = len(template)
        step = self.step
        if msx % step:
            print "ERROR: template dimension should be divisible by step."
            print "Template does not apply"
            return False
        self.template = template
        self.citer = int(msx/step)
        citer = self.citer
        sx,sy = self.dimension
        for x in xrange(citer * step):
            for y in xrange(citer * step):
                x0 = int(sx/(citer * step)) * x
                x1 = int(sx/(citer * step)) * (x + 1) - 1
                y0 = int(sy/(citer * step)) * y
                y1 = int(sy/(citer * step)) * (y + 1) - 1
                if template[x][y] == None:
                    csample = self.sample
                else:
                    csample = (template[x][y],)
                self.fill(x0,y0,x1,y1,random.sample(csample,1)[0])
        self.citer += 1
        return True
    
    def fill(self, x0, y0, x1, y1, val):
        for x in xrange(x0,x1+1):
            for y in xrange(y0,y1+1):
                self.setTile((x,y),val)
                
    def getSample(self, x0, y0, x1, y1):
        sx,sy = self.dimension
        left = x0 - 1 > 0
        right = x1 + 1 < sx
        up = y0 - 1 > 0 
        down = y1 + 1 < sy
        sample = []
        if left: sample.append(self.getTile((x0 - 1, y0)))
        if right: sample.append(self.getTile((x1 + 1, y1)))
        if up: sample.append(self.getTile((x0, y0 - 1)))
        if down: sample.append(self.getTile((x1, y1 + 1)))
        return sample
        
    def generate(self, showProgress = 1, maxIters = 0):
        random.seed()
        citer = self.citer
        step = self.step
        sx,sy = self.dimension
        if step < 2:
            print 'ERROR: step should be > 1.'
            return False
        if sx % step or sy % step:
            print 'ERROR: map dimensions should be divisible by step.'
            print 'Generation aborted.'
            return False
        if maxIters > 0:
            minIters = float(min(float(sx)/step + 1, float(sy)/step + 1, maxIters - 1))
        elif maxIters < 0:
            minIters = float(min(float(sx)/step + maxIters, float(sy)/step + maxIters))
            if minIters < 1:
                print 'ERROR: Incorrect maxIters value.'
                print 'Generation aborted.'
                return False
        else:
            minIters = min(float(sx)/step + 1, float(sy)/step + 1)
        while (sx/(citer * step) >= 1) and (sy/(citer * step) >= 1):
            for x in xrange(citer * step):
                for y in xrange(citer * step):
                    x0 = int(sx/(citer * step)) * x
                    x1 = int(sx/(citer * step)) * (x + 1) - 1
                    y0 = int(sy/(citer * step)) * y
                    y1 = int(sy/(citer * step)) * (y + 1) - 1
                    if (citer > 1) or (self.template):
                        csample = self.getSample(x0,y0,x1,y1)
                    else:
                        csample = self.sample
                    self.fill(x0,y0,x1,y1,random.sample(csample,1)[0])
            citer += 1

            if showProgress == 1:
                sys.stdout.write('Progress %.1f' % (int(citer/(minIters) * 100)) + '%\r')
                sys.stdout.flush()
            elif showProgress == 2:
                percent = citer/(minIters)
                p0 = int(percent * 10)
                print '\033[0;0H'
                print 'Progress |\033[41m%s\033[0m%s| %.1f' % (' ' * p0, ' ' * (10 - p0), int(percent * 100)) + '%'
                print 'Iteration: %i' % citer
            if maxIters and citer >= minIters:
                return True
        return True

Example 1: as a map image is used. Working with PIL (must be installed)

from PIL import Image
sx,sy = 128,128
colors = ((255,0,0),(0,255,0),(0,0,255),(250,150,50))
image = Image.new("RGB", (sx,sy), (0,0,0,0))
fg = FractalMapGen(colors, image.getpixel, image.putpixel, (sx,sy), 4)
fg.generate()
image.save("./test1.png", "PNG")
del image

Result:

Example 2: as the map uses an array of characters. Apply template

sx,sy = 24,24
tiles = ('_','#','^')
field = [[None for y in xrange(sy)] for x in xrange(sx)]
       
def getS(pos):
    x,y = pos
    return field[x][y]
    
def setS(pos, val):
    x,y = pos
    field[x][y] = val


tmp = (('_',None,None,'#'),
       ('^','_','#','^'),
       ('^','#','_','^'),
       ('#',None,None,'_'))

fg = FractalMapGen(tiles, getS, setS, (sx,sy), 4)
fg.setTemplate(tmp)
fg.generate()

for y in xrange(sy):
    print ''
    for x in xrange(sx):
        print field[x][y],

Result:

_ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ # # # # # # # # # # #
_ ^ ^ ^ ^ ^ ^ ^ _ ^ ^ ^ # # # # # # # # # # # #
_ ^ ^ ^ ^ ^ _ _ _ ^ ^ ^ # # # # # # ^ ^ ^ ^ # #
^ ^ ^ ^ ^ ^ _ _ _ _ ^ ^ # # # # # # ^ ^ ^ ^ # #
^ _ _ _ _ ^ _ _ _ _ ^ ^ # # # # # # ^ ^ ^ ^ ^ ^
^ _ _ _ ^ ^ ^ _ _ _ ^ ^ # # # # # # # # ^ ^ ^ ^
_ _ _ ^ ^ ^ ^ _ _ ^ ^ ^ # # # # # # ^ # ^ ^ ^ ^
_ _ ^ _ _ _ ^ _ _ _ _ _ # _ _ _ ^ ^ _ # ^ ^ ^ ^
_ ^ ^ ^ ^ _ _ _ _ _ _ _ # # _ _ _ ^ _ ^ # ^ ^ #
^ ^ ^ ^ _ _ _ _ _ _ _ _ _ _ _ ^ _ ^ ^ # # # ^ #
^ ^ ^ ^ _ _ _ _ _ ^ _ _ _ _ _ ^ ^ ^ ^ ^ # # # #
^ ^ ^ ^ # _ ^ _ _ _ _ _ _ _ _ # ^ ^ ^ # # # # #
^ ^ ^ ^ # _ ^ ^ ^ ^ _ _ _ _ # # # _ _ _ # # # #
^ ^ ^ ^ # # # # ^ ^ ^ _ _ _ # # # _ # # # # # #
^ ^ ^ # # # # ^ # ^ ^ _ _ _ # # # # # # # # # #
^ ^ ^ ^ # # # # # ^ ^ ^ ^ _ # # # # # # _ # _ _
^ ^ ^ # # # # # ^ ^ ^ ^ ^ ^ # # # # # # _ # _ _
# # ^ # # # # # # ^ ^ ^ ^ ^ ^ ^ _ _ _ _ _ _ _ _
# # # # # # ^ ^ ^ ^ ^ # # # ^ ^ ^ # _ _ _ _ _ _
# # # # # # # ^ # ^ ^ # # ^ ^ # _ # _ _ _ _ _ _
# # # # # # # # # # # # # ^ # # # _ _ _ _ _ _ _
# # # # # # # # # # # ^ ^ ^ # _ _ _ _ _ _ _ _ _
# ^ ^ # # # # # # # # # ^ ^ # # _ _ _ _ _ _ _ _
# ^ ^ # # # # # # # # # # # # # _ _ _ _ _ _ _ _ 

a little tip - you can repeat the items in the “sample” tuple to adjust the ratio on the map:
tiles = (’’,’’,’#’,’#’,’#’,’^’)

Yes! I’ll never have to write a fractal map generator again! :smiley:
Thanks for the snippet!