#!/usr/bin/env/python # Tells Linux systems how to run this file. """ Virtual Sea Creates a landscape of water and island terrain. Involves a massive amount of ad-hockery. This code lives at http://kschnee.xepher.net/code/ with screenshots at http://kschnee.xepher.net/pics/code_projects/ . KNOWN BUG: Look at the greyscale output by switching out which of the two image-creation lines is commented out, in the autorun code at the bottom. Look for the slight distortions around the edges of each grid zone. Something is making the values near the zone edges abnormally low and I've not been able to figure out what. It's not the image-generation, I think... Obvious directions for improvement besides that: -Calculate more terrain types based on slope and some notion of rainfall/heat. -Apply regional (multi-zone) figures for weather to create regions with eg. lots of desert or swamp. -Create frequent sets of 2-4 moderate-value, gentle-sloped points in the same zone to create substantial plains without mountains. -Create control points that lower terrain or create other varied effects. """ __author__ = "Kris Schnee" __version__ = "2011.03.21" __license__ = "GPLv3 or MIT" ##### Imported Modules ##### import math import copy import random import pygame ##### Constants ##### ZONE_SIZE = 50 EMPTY_ZONE_CHANCE = .50 CP_DIST = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,4,4,4,5,5,5,8,8,8,8,10,10,10,10,10,10,15,15,18,18,18,18,18,20,20] POINT_CUTOFF = 50.0 ##### Functions ##### def FindControlPoints(zx,zy,seed): points = [] ## Make certain contiguous chunks of the map empty of land. random.seed((zx/3,zy/3,seed)) if random.random() < EMPTY_ZONE_CHANCE / 2: return points ## Stop right there. random.seed(((zx+1)/3,(zy+3)/2,seed)) if random.random() < EMPTY_ZONE_CHANCE / 2: return points ## Stop right there. ## Now bias the point array based on a large map chunk. ## There's a chance of a 4x4 zone chunk having much or little land. cp_left = 0 cp_right = len(CP_DIST) - 1 random.seed((zx/4,zy/4,seed)) bias = random.random() if bias < .3: cp_right = len(CP_DIST) - 20 ## print "bias in zone: "+str((zx,zy))+": water" elif bias > .7: cp_left = 18 ## print "bias in zone: "+str((zx,zy))+": land" ## Okay, now we use the CP_DIST list to find the number of points. random.seed((zx,zy,seed)) dist = CP_DIST[cp_left:cp_right] num_points = dist[ random.randint(0,len(dist)-1) ] ## Decide what those points are. for n in range(num_points): x = random.randint(0,ZONE_SIZE) y = random.randint(0,ZONE_SIZE) height = random.randint(40,70) * .01 if random.randint(0,99) < 20: ## Unusual double point. height = random.randint(15,40) * .01 slope = random.randint(10,25) * -.001 points.append(((x+(zx*ZONE_SIZE),y+(zy*ZONE_SIZE)),height,slope)) x += random.randint(-20,20) y += random.randint(-20,20) points.append(((x+(zx*ZONE_SIZE),y+(zy*ZONE_SIZE)),height,slope)) else: slope = random.randint(15,50) * -.001 points.append(((x+(zx*ZONE_SIZE),y+(zy*ZONE_SIZE)),height,slope)) return points def FindControlPointsAroundZone(zx,zy,seed): np = ((0,0), (-1,0),(1,0),(0,-1),(0,1), (-1,-1),(1,-1),(-1,1),(1,1)) points = [] for neighbor in np: zx_adjusted = zx + neighbor[0] zy_adjusted = zy + neighbor[1] points += FindControlPoints(zx_adjusted,zy_adjusted,seed) return points def FindHeight(zx,zy,seed): heightmap = MakeHeightMap(ZONE_SIZE,ZONE_SIZE) points = FindControlPointsAroundZone(zx,zy,seed) for x in range(len(heightmap)): for y in range(len(heightmap[0])): xa = x+(zx*ZONE_SIZE) ya = y+(zy*ZONE_SIZE) ## heightmap[x][y] = random.random() * .045 for p in points: px, py = p[0] d = math.sqrt( pow(xa-px,2) + pow(ya-py,2) ) if d < POINT_CUTOFF: h = p[1] + (d*p[2]) + (random.random() * .15) if h > heightmap[x][y]: heightmap[x][y] = h return heightmap def SmoothHeightMap(heightmap,passes=1): for n in range(passes): h2 = copy.copy(heightmap) np = ((0,0), (-1,0),(1,0),(0,-1),(0,1), (-1,-1),(1,-1),(-1,1),(1,1)) len_np = float(len(np)) for x in range(1,ZONE_SIZE-1): for y in range(1,ZONE_SIZE-1): h = 0.0 for neighbor in np: h += heightmap[x+neighbor[0]][y+neighbor[1]] h2[x][y] = h / len_np len_np = 4 y = 0 for x in range(1,ZONE_SIZE-1): h = 0.0 np = ((0,0),(-1,0),(1,0),(0,1)) for neighbor in np: h += heightmap[x+neighbor[0]][y+neighbor[1]] h2[x][y] = h / len_np y = ZONE_SIZE - 1 for x in range(1,ZONE_SIZE-1): h = 0.0 np = ((0,0),(-1,0),(1,0),(0,-1)) for neighbor in np: h += heightmap[x+neighbor[0]][y+neighbor[1]] h2[x][y] = h / len_np x = 0 for y in range(1,ZONE_SIZE-1): h = 0.0 np = ((0,0),(0,-1),(0,1),(1,0)) for neighbor in np: h += heightmap[x+neighbor[0]][y+neighbor[1]] h2[x][y] = h / len_np x = ZONE_SIZE - 1 for y in range(1,ZONE_SIZE-1): h = 0.0 np = ((0,0),(0,-1),(0,1),(-1,0)) for neighbor in np: h += heightmap[x+neighbor[0]][y+neighbor[1]] h2[x][y] = h / len_np h2[0][0] = .33333*(heightmap[0][0]+heightmap[1][0]+heightmap[0][1]) h2[ZONE_SIZE-1][0] = .33333*(heightmap[0][0]+heightmap[ZONE_SIZE-2][0]+heightmap[ZONE_SIZE-2][1]) h2[0][ZONE_SIZE-1] = .33333*(heightmap[0][ZONE_SIZE-1]+heightmap[1][ZONE_SIZE-1]+heightmap[0][ZONE_SIZE-2]) h2[ZONE_SIZE-1][ZONE_SIZE-1] = .33333*(heightmap[ZONE_SIZE-1][ZONE_SIZE-1]+heightmap[ZONE_SIZE-2][ZONE_SIZE-1]+heightmap[ZONE_SIZE-1][ZONE_SIZE-2]) heightmap = h2 return heightmap def MakeHeightMap(size_x=ZONE_SIZE,size_y=ZONE_SIZE,random_data=False): """Generate random static or a blank map.""" heightmap = [] if random_data: for x in range(size_x): heightmap.append([]) for y in range(size_y): heightmap[-1].append( random.random() ) else: for x in range(size_x): heightmap.append([]) for y in range(size_y): heightmap[-1].append(0.0) return heightmap def ConvertHeightmapToLibertyTerrain(heightmap): """Assigns colors and terrain (a letter) to each point. Uses ad-hoc decisions based purely on the height of each point.""" terrainmap = [] img = pygame.surface.Surface((ZONE_SIZE,ZONE_SIZE)) for x in range(ZONE_SIZE): terrainmap.append([]) for y in range(ZONE_SIZE): v = heightmap[x][y] if v < .015: t = "w" c = (0,0,200) elif v < .03: t = "w" c = (0,0,220) elif v < .04: t = "w" c = (0,0,255) elif v < .055: t = "w" c = (0,64,255) elif v < .065: t = "w" c = (0,92,255) elif v < .08: t = "w" c = (0,128,255) elif v < .12: t = "w" c = (0,192,255) elif v < .20: t = "s" c = (240,240,127) elif v < .30: t = "g" c = (0,255,0) elif v < .35: if random.randint(0,99) < 15: t = "f" c = (32,200,32) else: t = "g" c = (0,255,0) elif v < .42: if random.randint(0,99) < 35: t = "f" c = (32,200,32) else: t = "g" c = (0,255,0) elif v < .50: t = "d" c = (200,180,127) elif v < .64: t = "m" c = (150,63,63) else: t = "m" c = (210,220,255) terrainmap[-1].append(t) img.set_at((x,y),c) return terrainmap, img def ConvertToScaledImage(heightmap,scale_x=1.0,scale_y=1.0): """Use Pygame to stretch heightmap to any size/shape. Returns image surface.""" size = len(heightmap) img = pygame.surface.Surface((size,size)) img.fill((0,0,0)) for y in range(size): for x in range(size): h = int(heightmap[x][y] * 255) try: img.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)) if (scale_x != 1.0) or (scale_y != 1.0): print "Scaling." size_x = int(ZONE_SIZE * scale_x) size_y = int(ZONE_SIZE * scale_y) img = pygame.transform.smoothscale(img,(size_x,size_y)) return img ##### Autorun ##### if __name__ == "__main__": ## h = MakeHeightMap(50,50) ## h2 = FindHeight(0,0,"sail") ## img = ConvertToScaledImage(h2,4,4) ## screen = pygame.display.set_mode((500,500)) ## COLOR_BACKGROUND = (0,0,255) ## screen.fill(COLOR_BACKGROUND) ## screen.blit(img,(0,0)) ## pygame.display.update() ## Draw a big map of many connected zones. mult = 1.0 screen = pygame.display.set_mode((800,500)) COLOR_BACKGROUND = (0,0,255) screen.fill(COLOR_BACKGROUND) LEFT_EDGE = 60 TOP_EDGE = 8 WIDTH = 16 HEIGHT = 10 for zy in range(TOP_EDGE,TOP_EDGE+HEIGHT): for zx in range(LEFT_EDGE,LEFT_EDGE+WIDTH): h = MakeHeightMap(50,50) h2 = FindHeight(zx,zy,"sail") h2 = SmoothHeightMap(h2,3) ## Use the first line of these two for color, the second instead for greyscale. ht, img = ConvertHeightmapToLibertyTerrain(h2) ## img = ConvertToScaledImage(h2,mult,mult) screen.blit(img,((zx-LEFT_EDGE)*50*mult,(zy-TOP_EDGE)*50*mult)) pygame.display.update() pass