#!/usr/bin/env/python # Tells Linux systems how to run this file. """ Maui Ocean island generator experiment. """ __author__ = "Kris Schnee" __version__ = "2009.10.16" __license__ = "GPLv3" ##### Imported Modules ##### ## Standard import random ## Third-party ## Mine ##### Constants ##### TERRAIN_COLORS = {"desert":(200,200,50), "grass":(0,255,0), "jungle":(30,180,30), "ice":(200,200,255), "volcano":(255,50,50), } ##### Classes ##### class DSGenerator: def __init__(self): self.terrain_size = 129 self.background_color = (0,0,255) self.sea_level = .08 def DisplayAsImage(self,heightmap): """Displays a square heightmap as an image. The range of values is assumed to be 0.0 to 1.0, in a 2D, square array. The range is converted to greyscale values of 0 to 255.""" size = len(heightmap) screen.fill(self.background_color) terrain = pygame.surface.Surface((size,size)) terrain.fill((0,0,0)) for y in range(size): for x in range(size): h = int(heightmap[x][y] * 255) try: terrain.set_at((x,y),(h,h,h)) except: print "Error on x,y: "+str((x,y))+"; heightmap --> 0-255 value: "+str((heightmap[x][y],h)) screen.blit(terrain,(10,10)) pygame.display.update() def MakeMapImage(self,heightmap,terrain,scale=1.0): size = len(heightmap) img = pygame.surface.Surface((size,size)) img.fill((0,0,0)) img.set_colorkey((0,0,0)) for y in range(size): for x in range(size): h = heightmap[x][y] if h < self.sea_level: color = (0,0,0) else: c = TERRAIN_COLORS[terrain] h = min(h*2.0,1.0) color = (c[0]*h,c[1]*h,c[2]*h) img.set_at((x,y),color) if scale != 1.0: size = int(64.0 * scale) img = pygame.transform.smoothscale(img,(size,size)) return img def SaveImage(self,filename="terrain.bmp",border=False): """Save what's on the screen. Flying toasters not included.""" if border: pygame.image.save(screen,filename) else: s = pygame.surface.Surface((screen.get_width()-20,screen.get_height()-20)) s.blit(screen,(0,0),(10,10,self.terrain_size,self.terrain_size)) pygame.image.save(s,filename) def MakeHeightMap(self,size,random_data=False): """Generate random static or a blank map.""" heightmap = [] if random_data: for x in range(size): heightmap.append([]) for y in range(size): heightmap[-1].append( random.random() ) else: for x in range(size): heightmap.append([]) for y in range(size): heightmap[-1].append(0.0) return heightmap def MakeDiamondSquareTerrain(self,heightmap,show_in_progress=False,seed_values=(0.0,0.0,0.0,0.0)): """Make procedural terrain using the D/S Algorithm.""" size = len(heightmap) ## Seed the center. heightmap[size/2][size/2] = random.randint(25,100)/100.0 ## Seed the corners. corners = ((0,0),(size-1,0),(0,size-1),(size-1,size-1)) for n in range(4): point = corners[n] heightmap[point[0]][point[1]] = seed_values[n] edges = ((0,size/2),(size/2,0),(size/2,size-1),(size-1,size/2)) for n in range(4): point = edges[n] heightmap[point[0]][point[1]] = seed_values[n] ## Starting values. size_minus_1 = size - 1 ## For edge-wrapping purposes. cell_size = size-1 ## Examine squares of this size within the heightmap. cell_size_half = cell_size / 2 iteration = 0 ## How many times have we done the algorithm? (just FYI) chaos = 1.0 ## Total possible variation of a height from expected average. chaos_half = chaos * .5 ## Possible variation up or down. diamond_chaos_half = (chaos/1.414)*.5 ## Reduced by sqrt(2) for D step. ## (Wouldn't "Diamond Chaos Half" be a cool anime series title?) while cell_size > 1: ## For actual use. ## Begin the algorithm. ## print "Chaos: "+str(chaos)+" C.Half: "+str(chaos_half)+" D.C.Half: "+str(diamond_chaos_half) iteration += 1 """Find the "anchor points" that mark the upper-left corner of each cell.""" for anchor_y in range(0,size-1,cell_size): for anchor_x in range(0,size-1,cell_size): ## Calculate the center of the cell. cx = anchor_x + cell_size_half cy = anchor_y + cell_size_half ## The "Diamond" phase. if iteration == 1: pass else: ## Find the center's diagonal "neighbors." neighbors = ([cx-cell_size_half,cy-cell_size_half], [cx+cell_size_half,cy-cell_size_half], [cx-cell_size_half,cy+cell_size_half], [cx+cell_size_half,cy+cell_size_half]) ## Correct for points outside the map. for n in range(4): neighbor = neighbors[n] if neighbor[0] < 0: neighbors[n][0] += size elif neighbor[0] > size: neighbors[n][0] -= size if neighbor[1] < 0: neighbors[n][1] += size elif neighbor[1] > size: neighbors[n][1] -= size average = sum([heightmap[n[0]][n[1]] for n in neighbors]) * .25 h = average - chaos_half + (random.random() * chaos) ## h = average ## Test: No randomness. h = max(0.0,min(1.0,h)) heightmap[cx][cy] = h ## The "Square" phase. if iteration == 1: pass else: ## Calculate four "edge points" surrounding the center. edge_points = ((cx,cy-cell_size_half), (cx-cell_size_half,cy), (cx+cell_size_half,cy), (cx,cy+cell_size_half)) for point in edge_points: neighbors = [[point[0],point[1]-cell_size_half], [point[0]-cell_size_half,point[1]], [point[0]+cell_size_half,point[1]], [point[0],point[1]+cell_size_half]] ## Correct for points outside the map. for n in range(4): neighbor = neighbors[n] if neighbor[0] < 0: neighbors[n][0] += size elif neighbor[0] > size_minus_1: neighbors[n][0] -= size if neighbor[1] < 0: neighbors[n][1] += size elif neighbor[1] > size_minus_1: neighbors[n][1] -= size average = sum([heightmap[n[0]][n[1]] for n in neighbors]) * .25 h = average - chaos_half + (random.random() * chaos) h = max(0.0,min(1.0,h)) ## h = average ## Test: No randomness. heightmap[point[0]][point[1]] = h ## End of iteration. Reduce cell size and chaos. cell_size /= 2 cell_size_half /= 2 chaos *= .5 chaos_half = chaos * .5 diamond_chaos_half = (chaos / 1.414) * .5 if show_in_progress: DisplayAsImage(heightmap) return heightmap def ErodeTerrain(self,heightmap,iterations=1,show_in_progress=True): """Not working yet; always seems to blur to the right.""" erosion_threshold = .5 count = 0 ycount1, ycount2 = 0,0 xcount1, xcount2 = 0,0 for n in range(iterations): print "Iteration: "+str(n) size = len(heightmap) for y in range(1,size-1): for x in range(1,size-1): neighbors = ((x,y-1), (x-1,y), (x+1,y), (x,y+1)) height_here = heightmap[x][y] slopes = ((height_here-heightmap[x][y-1]), (height_here-heightmap[x-1][y]), (height_here-heightmap[x+1][y]), (height_here-heightmap[x][y+1])) steepest_neighbor = 0 for n in range(0,4): if slopes[n] > steepest_neighbor: steepest_neighbor = n ## Debugging code. if steepest_neighbor == 1: xcount1 += 1 elif steepest_neighbor == 2: xcount2 += 1 elif steepest_neighbor == 0: ycount1 += 1 elif steepest_neighbor == 3: ycount2 += 1 else: raise "Huh? "+str(steepest_neighbor) steepest_slope = slopes[steepest_neighbor] steepest_neighbor = neighbors[steepest_neighbor] if steepest_slope > 0 and steepest_slope < erosion_threshold: heightmap[x][y] -= steepest_slope*.5 heightmap[steepest_neighbor[0]][steepest_neighbor[1]] += steepest_slope*.5 count += 1 if show_in_progress: DisplayAsImage(heightmap) print count print (xcount1,xcount2,ycount1,ycount2) return heightmap class Ocean( DSGenerator ): def __init__(self,**options): DSGenerator.__init__(self) self.seed = options.get("seed") ## Set an overall theme for the world, eg. large islands. self.Seed() self.world_params = {} self.world_params["size"] = (100,100) ## self.world_params["number of islands"] = random.randint(10,40) self.world_params["number of islands"] = random.randint(25,25) self.world_params["island size"] = random.randint(75,125)/100.0 self.world_params["monsters"] = random.randint(-10,10) self.islands = [] self.MakeIslands() def Seed(self,suffix=None): if suffix: random.seed(self.seed+" "+str(suffix)) else: random.seed(self.seed) def MakeIslands(self): for n in range(self.world_params["number of islands"]): island = self.MakeIsland(n) ## Where does it go on the map? self.Seed(n) x = Number(self.world_params["size"][0]) y = Number(self.world_params["size"][1]) island["coords"] = (x,y) self.islands.append(island) def MakeIsland(self,number): self.Seed(number) ## Decide basic things about the island. size = ((Roll(4,50))/100.0) * self.world_params["island size"] size = max(size,.1) monsters = Roll() + self.world_params["monsters"] terrain = ["desert","grass","jungle","ice","volcano"][Number(4)] ## Decide its geography. heightmap = self.MakeDiamondSquareTerrain(self.MakeHeightMap(65)) img = self.MakeMapImage(heightmap,terrain,size) return {"size":size, "monsters":monsters, "terrain":terrain, ## "heightmap":heightmap, "image":img, } def PygameDemo(self): import pygame screen = pygame.display.set_mode((640,480)) screen.fill((0,0,255)) for island in self.islands: ## print island coords = island["coords"] ## draw_coords = int(coords[0]*6.4),int(coords[1]*4.8) ## r = island["size"] / 2 ## print draw_coords,r ## color = TERRAIN_COLORS[island["terrain"]] ## pygame.draw.circle(screen,color,draw_coords,r) draw_coords = int(coords[0]*6.4),int(coords[1]*4.8) screen.blit(island["image"],draw_coords) pygame.display.update return screen ##### Other Functions ##### def Number(a,b=None): """Wrapper for random.randint.""" if b: return random.randint(a,b) else: return random.randint(0,a) def Roll(dice=3,size=6): """Return total of [dice] dice with [size] sides (min value 1).""" total = 0 for n in range(dice): total += random.randint(1,size) return total ##### Autorun ##### if __name__ == "__main__": ## Runs if this module is the main module (not imported). import pygame screen = pygame.display.set_mode((640,480)) o = Ocean(seed="Otter") screen = o.PygameDemo()