World generator (and editor) made in pygame

This is the place to post any code that you want to share with the community. Only completed scripts should be posted here.

World generator (and editor) made in pygame

Postby billysback » Wed May 07, 2014 7:25 pm

Made this independently and whilst it's far from my first project, it's the first one I actually impressed myself with.
It was designed as a map generator and I added editing features in as an afterthought, it's all key-codes to use and there are quite a few controls, if anyone shows any interest I'll post a tutorial on how to actually edit a map (no way to save them).

Has a problem where it temporarily crashes if you do anything else whilst it's generating the map but once it's done it will be fine.

Here's a step-by-step picture of it generating a map:
Image

Here's a paste of it:
http://pastebin.com/y7e6fsWH

Code: Select all
import sys, pygame, math, random

from pygame.locals import *
pygame.init()

#random_seed = 238572983412674123
#random.seed(random_seed)

#people, houses


hmap = []
for i in range(100):
    hmap.append(0)

br, bcr = [0, 42], [ [0, 0, 100], [0, 0, 255] ] #water colour range for height map
sr, scr = [42, 45], [ [255, 204, 51], [255, 225, 80] ] #sand colour range for height map
gr, gcr = [45, 55], [ [0, 240, 0], [0, 200, 0] ] #grass colour range for height map
tr, tcr = [55, 64], [ [0, 160, 0], [0, 100, 0] ] #tree colour range for height map
rr, rcr = [64, 100], [ [150,150,150], [255, 255, 255] ] #mountain/rock/snow colour range for height map

rs = [ [br, bcr], [sr, scr], [gr, gcr], [tr, tcr], [rr, rcr] ]

for i in range(100):
    for j in range(len(rs)):
        r, c = rs[j][0], rs[j][1]
        if i >= r[0] and i < r[1]:
            c1, c2 = c[0], c[1]
            per = ( (i-r[0])/(r[1]-r[0]) )
            rgb = [ c1[0] + ( (c2[0]-c1[0]) * per),
                    c1[1] + ( (c2[1]-c1[1]) * per),
                    c1[2] + ( (c2[2]-c1[2]) * per) ]
            hmap[i] = rgb
           
width, height = 300, 300
scale = 3
maps = [1, 1]

dimensions = (width*scale*maps[0], height*scale*maps[1])

screen = pygame.display.set_mode(dimensions)

mapImg = pygame.Surface(dimensions)
overlay = pygame.Surface(dimensions).convert_alpha()
overlay.fill( (0,0,0,0) )

sky = pygame.Surface((width*scale, height*scale)).convert_alpha()
sky.fill( (0,0,0,0) )

def drawScreen():
    screen.blit(mapImg, (0, 0))
    screen.blit(overlay, (0, 0))
    screen.blit(sky, (0, 0))
    pygame.display.flip()

def isValid(x, y):
    if x >= 0 and x < width*maps[0] and y >= 0 and y < height*maps[1]:
        return True
    return False

bordersOn = True
border = [0, 0, 0]
borderDiff = 0
def drawPixel(target, x, y, col, borders, lighting):
    xf, yf = x*scale, y*scale
    xt, yt = (x*scale)+scale, (y*scale)+scale
   
    for rx in range(xf, xt):
        for ry in range(yf, yt):
            tcol = col
            border = [col[0]-100, col[1]-100, col[2]-100]
            if bordersOn and (col[0] > 0 or col[1] > 0):
                if (rx == xf and borders[3]): #West (Left)
                    tcol = border
                elif (rx == xt-1 and borders[1]): #East (Right)
                    tcol = border
                elif (ry == yf and borders[0]): #North (Up)
                    tcol = border
                elif (ry == yt-1 and borders[2]): #South (Down)
                    tcol = border

            for i in range(len(tcol)):
                if tcol[i] > 255:
                    tcol[i] = 255
                elif tcol[i] < 0:
                    tcol[i] = 0
                   
            target.set_at((rx, ry), tcol)

def getBorders(x, y, rdiff):
    borders = [False, False, False, False] #North, East, South, West
    if isValid(x, y):
        h = wmap[x][y][0]
        col = hmap[h]

        for o in range(-1, 2, 2):
            ps = [ [x+o, y], [x, y+o] ]
            for i in range(len(ps)):
                p = ps[i]

                if isValid(p[0], p[1]):
                   
                    col2 = hmap[wmap[p[0]][p[1]][0]]
                    diff = math.fabs(col[0]-col2[0]) + math.fabs(col[1]-col2[1]) + math.fabs(col[2]-col2[2])
                    if diff > rdiff and bordersOn:
                        if p[0] < x: #West
                            borders[3] = False
                        elif p[0] > x: #East
                            borders[1] = True
                        elif p[1] < y: #North
                            borders[0] = False
                        elif p[1] > y: #South
                            borders[2] = True
    return borders

wmap = []

for x in range(width*maps[0]):
    tab = []
    for y in range(height*maps[1]):
        h = random.randint(0,99)
       
        tab.append([h, 0])
    wmap.append(tab)

def getDist(x1, y1, x2, y2):
    return math.sqrt( ((x1-x2)*(x1-x2)) + ((y1-y2)*(y1-y2)) )

smooth = 3
sharp = 1
def smoothmapImg():
    for x in range(width*scale):
        for y in range(height*scale):
            col = mapImg.get_at((x, y))
            col = [col[0]*sharp, col[1]*sharp, col[2]*sharp]
            n = sharp
            for ox in range(-smooth, smooth+1):
                for oy in range(-smooth, smooth+1):
                    if (x+ox) >= 0 and (x+ox) < width*scale and (y+oy) >= 0 and (y+oy) < height*scale:
                        tempc = mapImg.get_at((x+ox, y+oy))
                        col = [col[0] + tempc[0], col[1]+tempc[1], col[2]+tempc[2] ]
                        n = n + 1
            col[0] = math.floor(col[0]/n)
            col[1] = math.floor(col[1]/n)
            col[2] = math.floor(col[2]/n)
            mapImg.set_at((x, y), col)
               

def drawWorld(surface, offsetx, offsety):
    for x in range(width*maps[0]):
        for y in range(height*maps[1]):
            h = wmap[x][y][0]
            col = hmap[h]

            lighting = wmap[x][y][1]
           
            drawPixel(surface, x, y, col, getBorders(x, y, borderDiff), lighting)
    drawScreen()


def drawArea(surface, fpos, tpos):
    for x in range(fpos[0], tpos[0]+1):
        for y in range(fpos[1], tpos[1]+1):
            h = wmap[x][y][0]
            col = hmap[h]

            lighting = wmap[x][y][1]
           
            drawPixel(surface, x, y, col, getBorders(x, y, borderDiff), lighting)
    drawScreen()

def addHotspot(x, y, h, intensity, dist):
    for ox in range(-dist, dist+1):
        for oy in range(-dist, dist+1):
            if (ox*ox)+(oy*oy) <= dist*dist and isValid(x+ox, y+oy):
                per = 100- ((( (ox*ox) + (oy*oy) )/(dist*dist))*100)
                i = math.ceil( (intensity/100)*per )
                wmap[x+ox][y+oy].append([h, i])

drange = [30, 70]
def generateHotspot(x, y):
    #print("Hotspot position: ",x,",",y)
    pos = [x, y]
    h = random.randint(0, 99)
    intensity = random.randint(6, 13)
    dist = random.randint(drange[0], drange[1])
    addHotspot(pos[0], pos[1], h, intensity, dist)

def genHotspots(offsetx, offsety):
    avgdrange = (drange[0]+drange[1])/2
    hotspot_amt = math.ceil( (width*height)/(math.pi*avgdrange*avgdrange) )
    hotroot = math.ceil(math.sqrt(hotspot_amt))
    wpart, hpart = width/hotroot, height/hotroot
    hotroot = hotroot*2
    for x in range(0, hotroot):
       
        for y in range(0, hotroot):
            rx = ( (x*wpart) + wpart )/2
            ry = ( (y*hpart) + hpart)/2
            offset = [random.randint(0, math.floor(wpart))-(wpart/2), random.randint(0, math.floor(hpart))-(hpart/2)]
            generateHotspot(math.floor(rx+offset[0])+offsetx, math.floor(ry+offset[1])+offsety)
           
    print(hotspot_amt, ", ", hotroot)


def getAverageHeight(x, y, sens, hotspots, circular):
    nlist = []
    total = 0
    n = 0
    if isValid(x, y) and hotspots == True:
        if len(wmap[x][y]) > 2:
            for i in range(2, len(wmap[x][y])):
                hotspot = wmap[x][y][i]
                total = total + (hotspot[0]*hotspot[1])
                n = n + hotspot[1]
    for ox in range(-sens, sens+1):
        for oy in range(-sens, sens+1):
            if isValid(x+ox, y+oy):
                if circular:
                    if getDist(x, y, x+ox, y+oy) <= sens:
                        total = total + wmap[x+ox][y+oy][0]
                        n = n + 1
                else:
                    total = total + wmap[x+ox][y+oy][0]
                    n = n + 1
    h = 0
    if n != 0:
        h = math.floor(total/n)
        if h > 99:
            h = 99
    return h

   

def smoothWorld(offset, amt, sens, hotspots):
    todo = []
    for x in range(width*amt[0]):
        for y in range(height*amt[1]):
            wmap[x+offset[0]][y+offset[1]][0] = getAverageHeight(x+offset[0], y+offset[1], sens, hotspots, False)

def smoothArea(offset, fpos, tpos, sens, hotspots, circ):
    for x in range(fpos[0], tpos[0]+1):
        for y in range(fpos[1], tpos[1]+1):
            if isValid(x, y):
                wmap[x+offset[0]][y+offset[1]][0] = getAverageHeight(x+offset[0], y+offset[1], sens, hotspots, circ)

def getTotalAverageHeight():
    total = 0
    n = 0
    for x in range(width):
        for y in range(height):
            total = total + wmap[x][y][0]
            n = n + 1
   
    return math.floor(total/n)

def createWorld(surface, offsetx, offsety):
    genHotspots(offsetx, offsety)
    #drawWorld(surface, offsetx, offsety)

    print("Smoothing world, pass 1")
    smoothWorld([offsetx, offsety], [1,1], 2, True)
    #drawWorld(surface, offsetx, offsety)

    print("Smoothing world, pass 2")
    smoothWorld([offsetx, offsety], [1,1], 2, False)
    #drawWorld(surface, offsetx, offsety)

    print("Smoothing world, pass 3")
    smoothWorld([offsetx, offsety], [1,1], 3, True)
    #drawWorld(surface, offsetx, offsety)

    print("Smoothing world, pass 4")
    smoothWorld([offsetx, offsety], [1,1], 4, False)
    #drawWorld(surface, offsetx, offsety)

createWorld(mapImg, 0, 0)
drawWorld(mapImg, 0, 0)
#createWorld(mapImg, width, 0)
#createWorld(mapImg, width, height)
#createWorld(mapImg, 0, height)

def joinWorlds():
    print("Smoothing all maps, pass 1")
    smoothWorld([0,0], maps, 4, True)
    drawWorld(mapImg, 0, 0)

    print("Smoothing all maps, pass 2")
    smoothWorld([0,0], maps, 3, False)
    drawWorld(mapImg, 0, 0)

#joinWorlds()
print("Done!")
print("Finding village locations...")

villageHouses = 7
villageSize = 40
houseSize = 2
def findVillageLocations(size):
    locations = []
    for x in range(width):
        for y in range(height):
            h = wmap[x][y][0]
            if h >= gr[0] and h < gr[1]:
                ah = getAverageHeight(x, y, size, False, True)
                if ah >= h-2 and ah <= h+2:
                    canCreate = True
                    for i in range(len(locations)):
                        if getDist(locations[i][0], locations[i][1], x, y) < size*2:
                            canCreate = False
                    if canCreate:
                        locations.append([x, y])
    return locations

class House:
    def __init__(self, village, x, y, size):
        self.village = village
        self.x = x
        self.y = y
        self.size = size

    def draw(self):
        for ox in range(-self.size, self.size+1):
            for oy in range(-self.size, self.size+1):
                if getDist(self.x, self.y, self.x+ox, self.y+oy) <= self.size:
                    drawPixel(overlay, self.x+ox, self.y+oy, [140, 123, 55, 255], [True, True, False, False], 0)

class Village:
    def __init__(self, loc, size, hamt, hsize):
        houses = []
        n = 0
        while len(houses) < hamt and n < 50:
            pos = [random.randint(loc[0]-size, loc[0]+size), random.randint(loc[1]-size, loc[1]+size)]
            dist = getDist(loc[0], loc[1], pos[0], pos[1])
            if dist <= size and isValid(pos[0], pos[1]):
                canCreate = True
                for i in range(len(houses)):
                    house = houses[i]
                    if getDist(pos[0], pos[1], house.x, house.y) < hsize*2:
                        canCreate = False
                ph = wmap[pos[0]][pos[1]][0]
                if canCreate and ph >= gr[0] and ph < gr[1]:
                    houses.append(House(self, pos[0], pos[1], hsize))
            n = n + 1
        self.houses = houses
        self.x = loc[0]
        self.y = loc[1]
        self.size = size

    def draw(self):
        for i in range(len(self.houses)):
            self.houses[i].draw()
                                 


#locs = findVillageLocations(villageSize)

#print("Creating villages... (",len(locs),")")
#villages = []
#for i in range(len(locs)):
#    v = Village(locs[i], villageSize, villageHouses, houseSize)
#    v.draw()
#    villages.append(v)

print("Done!")
drawScreen()

curSens = 2
def getTilePosition(pos):
    return [math.floor(pos[0]/scale), math.floor(pos[1]/scale)]

def raiseArea(amt, fpos, tpos, circ):
    centre = [(fpos[0]+tpos[0])/2, (fpos[1]+tpos[1])/2]
    for x in range(fpos[0], tpos[0]+1):
        for y in range(fpos[1], tpos[1]+1):
            if isValid(x, y):
                if (circ != 0 and getDist(x, y, centre[0], centre[1]) <= circ) or (circ == 0):
                    wmap[x][y][0] = wmap[x][y][0] + amt
                    if wmap[x][y][0] > 99:
                        wmap[x][y][0] = 99
                    if wmap[x][y][0] < 0:
                        wmap[x][y][0] = 0

mouseMode = 0
# 0 = smooth
# 1 = raise
# 2 = lower
# 3 = hotspot_add
# 4 = hotspot_remove
mouseHotspots = False
mouseCirc = True
mouseArea = 2

mouseHeight = 49
mouseIntensity = 7

scrollMode = 0
# 0 = brush size
# 1 = sensitivity
# 2 = height
# 3 = intensity

mticks = pygame.time.get_ticks()
kticks = pygame.time.get_ticks()
while 1:
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 4: #scroll up
                if scrollMode == 0:
                    mouseArea = mouseArea + 1
                    print("Brush size: ", mouseArea)
                elif scrollMode == 1:
                    curSens = curSens + 1
                    print("Cursor sensitivity: ", curSens)
                elif scrollMode == 2:
                    mouseHeight = mouseHeight + 1
                    print("Mouse height (hotspot): ", mouseHeight)
                elif scrollMode == 3:
                    mouseIntensity = mouseIntensity + 1
                    print("Mouse intensity (hotspot): ", mouseIntensity)
            elif event.button == 5: #scroll down
                if scrollMode == 0:
                    mouseArea = mouseArea - 1
                    if mouseArea < 0:
                        mouseArea = 0
                    print("Brush size: ", mouseArea)
                elif scrollMode == 1:
                    curSens = curSens - 1
                    if curSens < 0:
                        curSens = 0
                    print("Cursor sensitivity: ", curSens)
                elif scrollMode == 2:
                    mouseHeight = mouseHeight - 1
                    if mouseHeight < 0:
                        mouseHeight = 0
                    print("Mouse height (hotspot): ", mouseHeight)
                elif scrollMode == 3:
                    mouseIntensity = mouseIntensity - 1
                    if mouseIntensity < 0:
                        mouseIntensity = 0
                    print("Mouse intensity (hotspot): ", mouseIntensity)
   
    if pygame.time.get_ticks() - kticks >= 100:
        keys = pygame.key.get_pressed()
        if keys[pygame.K_1]:
            mouseMode = 0
            print("Mouse set to smooth")
        elif keys[pygame.K_2]:
            mouseMode = 1
            print("Mouse set to raise")
        elif keys[pygame.K_3]:
            mouseMose = 2
            print("Mouse set to lower")
        elif keys[pygame.K_4]:
            mouseMode = 3
            print("Mouse mode set to hotspot! (one use)")
        if keys[pygame.K_q]:
            mouseHotspots = (mouseHotspots == False)
            print("Smoothing hotspots set to ", str(mouseHotspots))
        if keys[pygame.K_w]:
            mouseCirc = (mouseCirc == False)
            print("Circular brush set to ", str(mouseCirc))
        if keys[pygame.K_a]:
            scrollMode = 0
            print("Scroll mode set to brush size")
        elif keys[pygame.K_s]:
            scrollMode = 1
            print("Scroll mode set to sensitivity")
        elif keys[pygame.K_d]:
            scrollMode = 2
            print("Scroll mode set to height (hotspot)")
        elif keys[pygame.K_f]:
            scrollMode = 3
            print("Scroll mode set to intensity")
        if keys[pygame.K_z]:
            print("Smoothing world...")
            smoothWorld([0,0], maps, curSens, mouseHotspots)
            print("Drawing world...")
            drawWorld(mapImg, 0, 0)
            print("Done!")
       
        kticks = pygame.time.get_ticks()
           
    if pygame.time.get_ticks() - mticks >= 50:
        prsd = pygame.mouse.get_pressed()
        if prsd[2] or prsd[0]:
            if mouseMode == 0:
                #print("SMOOTHING_TILE")
                pos = getTilePosition(pygame.mouse.get_pos())
                #offset, fpos, tpos, sens, hostpots, circular
                smoothArea([0,0], [pos[0]-mouseArea, pos[1]-mouseArea], [pos[0]+mouseArea, pos[1]+mouseArea], curSens, mouseHotspots, mouseCirc)
                #surface, fpos, tpos
                drawArea(mapImg, [pos[0]-mouseArea, pos[1]-mouseArea], [pos[0]+mouseArea, pos[1]+mouseArea])
            elif mouseMode == 1:
                pos = getTilePosition(pygame.mouse.get_pos())
                circ = 0
                if mouseCirc:
                    circ = curSens
                raiseArea(1, [pos[0]-mouseArea, pos[1]-mouseArea], [pos[0]+mouseArea, pos[1]+mouseArea], circ)
                drawArea(mapImg, [pos[0]-mouseArea, pos[1]-mouseArea], [pos[0]+mouseArea, pos[1]+mouseArea])
            elif mouseMode == 2:
                pos = getTilePosition(pygame.mouse.get_pos())
                circ = 0
                if mouseCirc:
                    circ = curSens
                raiseArea(-1, [pos[0]-mouseArea, pos[1]-mouseArea], [pos[0]+mouseArea, pos[1]+mouseArea], circ)
                drawArea(mapImg, [pos[0]-mouseArea, pos[1]-mouseArea], [pos[0]+mouseArea, pos[1]+mouseArea])
            elif mouseMode == 3: #hotspot
                #addHotspot(x, y, h, intensity, dist)
                pos = getTilePosition(pygame.mouse.get_pos())
                addHotspot(pos[0], pos[1], mouseHeight, mouseIntensity, mouseArea)
                mouseMode = 0
           
           
        mticks = pygame.time.get_ticks()


Hope you like it, I'd love to hear your criticisms.
Last edited by Yoriz on Wed May 07, 2014 7:42 pm, edited 2 times in total.
Reason: First post lock, Added code tags
billysback
 
Posts: 2
Joined: Wed May 07, 2014 7:20 pm

Re: World generator (and editor) made in pygame

Postby Yoriz » Wed May 07, 2014 7:43 pm

Hi, welcome to the forum, please read the new users read this link in my signature.
Is you code considered a completed script or did you want help with the problem where it temporarily crashes?
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Spam topic disapproval technician
Windows7, Python 2.7.4., WxPython 2.9.5.0., some Python 3.3
User avatar
Yoriz
 
Posts: 721
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: World generator (and editor) made in pygame

Postby billysback » Wed May 07, 2014 7:46 pm

Yoriz wrote:Hi, welcome to the forum, please read the new users read this link in my signature.
Is you code considered a completed script or did you want help with the problem where it temporarily crashes?

I was about to report this thread, I think I posted this in the wrong Category, it's pretty much finished and works fine. The temporarily crashes thing is something I'd like to fix but it doesn't stop the program working, other than that I just don't have a UI for the editor.
billysback
 
Posts: 2
Joined: Wed May 07, 2014 7:20 pm

Re: World generator (and editor) made in pygame

Postby Yoriz » Wed May 07, 2014 7:49 pm

Moved to completed scripts.
New Users, Read This
Join the #python-forum IRC channel on irc.freenode.net!
Spam topic disapproval technician
Windows7, Python 2.7.4., WxPython 2.9.5.0., some Python 3.3
User avatar
Yoriz
 
Posts: 721
Joined: Fri Feb 08, 2013 1:35 am
Location: UK

Re: World generator (and editor) made in pygame

Postby Mekire » Thu May 08, 2014 12:58 am

billysback wrote:Has a problem where it temporarily crashes if you do anything else whilst it's generating the map but once it's done it will be fine.
This is a result of the program not accessing the event queue during the processing phase. This causes the computer to think the program has stopped responding and hang it. The solution to this is to make calls to pygame.event.pump() if you are in a phase where examining the event queue is unfeasible.

The result of your program is quite nice but there are some issues you should address. The globals are a bit out of control, and the variable names are often completely unhelpful (you might know what gcr means now, but no one else will). Global constants should be written with CAPS_WITH_UNDERSCORES; global variables shouldn't be written at all. Also the flow is very awkward. You write a few functions; then do something in the global scope; then write a few more functions; then do some more stuff in the global scope. This makes the code near impossible to read without putting in more effort than should be necessary.

Also a technical detail. In python 3, math.floor, and math.ceil return ints. In python 2 they do not; they return floats (causing index errors). This is actually the only thing stopping the program from being cross compatibile and is very easily fixed.

And a last note. Don't do this:
Code: Select all
from pygame.locals import *
It is bad. Stop while you still can.

-Mek
User avatar
Mekire
 
Posts: 976
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan


Return to Completed Scripts

Who is online

Users browsing this forum: No registered users and 2 guests