fork(1) download
  1. import random
  2. import itertools
  3.  
  4. # dungeon size
  5. width = 80
  6. height = 40
  7.  
  8. # how big are rooms?
  9. room_width_max = 8
  10. room_width_min = 3
  11. room_height_max = 8
  12. room_height_min = 3
  13.  
  14. # display style
  15. tiles = {False:'#',True:'.'}
  16.  
  17. # how is connectivity defined?
  18. dirs4 = [(0,1),(0,-1),(1,0),(-1,0)]
  19. dirs8 = dirs4 + [(1,1),(1,-1),(-1,1),(-1,-1)]
  20.  
  21. # is (x,y) in the level rectangle?
  22. def in_range(x,y):
  23. return x >= 0 and y >= 0 and x < width and y < height
  24.  
  25. # unique integer index for each tile
  26. def index(x,y):
  27. return y*width+x
  28.  
  29. # print walls and floors nicely
  30. def render(m):
  31. for row in m:
  32. print ''.join(map(lambda x: tiles[x], row))
  33. print "\n"
  34.  
  35. # width * height grid of walls
  36. def empty_map():
  37. m = [[False]*width for y in range(height)]
  38. return m
  39.  
  40. # n randomly placed rooms of various sizes.
  41. # can overlap. no corridors
  42. def disconnected_map(n):
  43. m = empty_map()
  44. for i in range(n):
  45. # room size
  46. room_width = random.randint(room_width_min,room_width_max)
  47. room_height = random.randint(room_height_min,room_height_max)
  48. # room location. mustn't be out of range.
  49. room_x = random.randint(0,width-room_width)
  50. room_y = random.randint(0,height-room_height)
  51. # make floors in the designated area
  52. for x in range(room_x,room_x+room_width):
  53. for y in range(room_y,room_y+room_height):
  54. m[y][x] = True
  55. return m
  56.  
  57. # find and mark the connected component that contains a given location
  58. # m: wall map
  59. # f: floodfill map
  60. # (x,y): seed coordinate
  61. # forbid: treat this tile as a wall
  62. # returns component size
  63. def floodfill_sub(m,f,(x,y),forbid=(-1,-1)):
  64. # don't floodfill from wall tiles
  65. if m[y][x] == False or f[y][x] != -1 or (x,y) == forbid:
  66. return 0
  67. # this is the number to tag tiles in this connected component with
  68. my_index = index(x,y)
  69. # coordinates that might be in this connected component
  70. boundary = [(x,y)]
  71. # how big is the connected component?
  72. size = 0
  73. while len(boundary) > 0:
  74. (xx,yy) = boundary.pop()
  75. # ignore tiles that are already claimed, walls, not in range, or forbidden
  76. if (xx,yy) == forbid: continue
  77. if not in_range(xx,yy): continue
  78. if not m[yy][xx]: continue
  79. if f[yy][xx] != -1: continue
  80. # mark the tile
  81. size += 1
  82. f[yy][xx] = my_index
  83. # queue all neighbouring tiles
  84. for (dx,dy) in dirs4:
  85. boundary.append((xx+dx,yy+dy))
  86. return size
  87.  
  88. # take a list of up to 8 coordinates and check whether they
  89. # form a single connected component. some effect duplication
  90. # with floodfill_sub.
  91. def connected_neighbourhood(neighbours):
  92. # store whether each neighbour has been connected to the first
  93. m_neighbours = dict(zip(neighbours,itertools.repeat(False)))
  94. # store neighbours that need to be expanded
  95. boundary = [neighbours[0]]
  96. while len(boundary) > 0:
  97. (x,y) = boundary.pop()
  98. # mark as explored
  99. m_neighbours[(x,y)] = True
  100. for (dx,dy) in dirs4:
  101. # only consider listed neighbours
  102. try:
  103. if m_neighbours[(x+dx,y+dy)] == False:
  104. boundary.append((x+dx,y+dy))
  105. except KeyError: pass
  106. # are all the neighbours in the same component?
  107. components = set(m_neighbours.values())
  108. return components == set([True])
  109.  
  110. # check if the whole floodfill can be skipped
  111. def skip_floodfill(m,(x,y),connected=False):
  112. if connected: # if the base map is not connected we can't skip
  113. good_neighbours = []
  114. for (dx,dy) in dirs8:
  115. (xx,yy) = (x+dx,y+dy)
  116. # if the neighbourhood of the forbidden square is connected and the
  117. #map is connected, then the forbidden square is not a bridge
  118. if in_range(xx,yy) and m[yy][xx]:
  119. good_neighbours.append((xx,yy))
  120. if connected_neighbourhood(good_neighbours):
  121. return True
  122. # the forbidden square may be a bridge or the map is disconnected
  123. return False
  124.  
  125.  
  126. # find all connected components of map m, treating forbidden tile as a wall.
  127. # cheat when the map is known to be connected.
  128. def floodfill(m, forbid=(-1,-1), connected=False):
  129. if skip_floodfill(m,forbid,connected):
  130. return [],{}
  131. # initialise floodfill map with no tiles claimed
  132. f = [[-1]*width for y in range(height)]
  133. components = {}
  134. for y in range(height):
  135. for x in range(width):
  136. # claim all tiles in the same component as (x,y)
  137. ff_return = floodfill_sub(m,f,(x,y),forbid)
  138. if ff_return > 0:
  139. # make a note of components' size
  140. components[(x,y)] = ff_return
  141. return f,components
  142.  
  143. # find a corridor that connects the first two connected components of m,
  144. # based on a precomputed floodfill f
  145. def find_connector(m,f,components, forbid=(-1,-1)):
  146. if len(components) < 2:
  147. return None # nothing to be done
  148. # seed locations of two randomly chosen connected components
  149. # can be altered to connect all components but just two is enough
  150. keys = components.keys()
  151. random.shuffle(keys)
  152. # values in the floodfill map are indices, not coordinates
  153. index_a = index(*keys[0])
  154. index_b = index(*keys[1])
  155. # list all members of the connected components and choose two at random
  156. component_a_coords = [(x,y) for x in range(width)
  157. for y in range(height)
  158. if f[y][x] == index_a]
  159. component_b_coords = [(x,y) for x in range(width)
  160. for y in range(height)
  161. if f[y][x] == index_b]
  162. ax,ay = random.choice(component_a_coords)
  163. bx,by = random.choice(component_b_coords)
  164. # which direction is (bx,by) from (ax,ay)?
  165. dx,dy = cmp(bx,ax),cmp(by,ay)
  166. # sequence of coordinates up to but not including bx,by
  167. ax_bx = range(ax,bx,dx) if dx != 0 else [ax]
  168. ay_by = range(ay,by,dy) if dy != 0 else [ay]
  169. # try going horizontally then vertically
  170. connect_h = zip(ax_bx,[ay]*len(ax_bx)) + \
  171. zip([bx]*len(ay_by),ay_by) + \
  172. [(bx,by)]
  173. # try instead going vertically then horizontally
  174. connect_v = zip([ax]*len(ay_by),ay_by) + \
  175. zip(ax_bx,[by]*len(ax_bx)) + \
  176. [(bx,by)]
  177. # the connector is no use if a tile is forbidden
  178. if forbid in connect_h:
  179. connect = connect_v
  180. elif forbid in connect_v:
  181. connect = connect_h
  182. else: # choose randomly if possible
  183. connect = random.choice([connect_h,connect_v])
  184. a_idx = 0
  185. b_idx = 0
  186. # what's the last tile in component a before the first tile in component b?
  187. for (i,(x,y)) in zip(itertools.count(),connect):
  188. if f[y][x] == index_a:
  189. a_idx = i
  190. elif f[y][x] == index_b:
  191. b_idx = i
  192. break
  193. # cut out all unnecessary coordinates from the connector and return
  194. connect = connect[a_idx+1:b_idx]
  195. return connect
  196.  
  197. # take any map with at least 2 floor tiles and make it biconnected.
  198. # there will be at least 2 disjoint paths between any pair of floor tiles.
  199. def biconnected_map(m):
  200. # queue all floor tiles for testing
  201. c =[(x,y) for x in range(width) for y in range(height) if m[y][x]]
  202. random.shuffle(c)
  203. connected = False
  204. while len(c) > 0:
  205. (x,y) = c.pop()
  206. # treat the chosen tile as wall, making an induced subgraph.
  207. # What are the components of this subgraph?
  208. f,components = floodfill(m,(x,y), connected)
  209. if len(components) <= 1:
  210. connected = True
  211. continue
  212. # If there's more than one connected component,
  213. # find a way to connect them (not including (x,y)
  214. connector = find_connector(m,f,components,(x,y))
  215. if connector != None:
  216. # check the tile in case any more needs to be done
  217. c.append((x,y))
  218. # add some of the new connector tiles for checking if necessary
  219. if not connected:
  220. c.append(connector[0])
  221. c.append(connector[-1])
  222. # alter the wall map
  223. for (xx,yy) in connector:
  224. m[yy][xx] = True
  225. return m
  226.  
  227. # example usage
  228. if __name__ == '__main__':
  229. # start with something that has < 1 connected component
  230. m = disconnected_map(20)
  231. # print non-biconnected map for reference
  232. render(m)
  233. m = biconnected_map(m)
  234. # print the map in order to gloat about the biconnected magicalness
  235. render(m)
  236.  
  237. # This is free and unencumbered software released into the public domain.
  238.  
  239. # Anyone is free to copy, modify, publish, use, compile, sell, or
  240. # distribute this software, either in source code form or as a compiled
  241. # binary, for any purpose, commercial or non-commercial, and by any
  242. # means.
  243.  
  244. # In jurisdictions that recognize copyright laws, the author or authors
  245. # of this software dedicate any and all copyright interest in the
  246. # software to the public domain. We make this dedication for the benefit
  247. # of the public at large and to the detriment of our heirs and
  248. # successors. We intend this dedication to be an overt act of
  249. # relinquishment in perpetuity of all present and future rights to this
  250. # software under copyright law.
  251.  
  252. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  253. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  254. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  255. # IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  256. # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  257. # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  258. # OTHER DEALINGS IN THE SOFTWARE.
  259.  
  260. # For more information, please refer to <http://u...content-available-to-author-only...e.org/>
Success #stdin #stdout 0.34s 10024KB
stdin
Standard input is empty
stdout
################################################################.......#########
##################################....##########################.......#########
##################################....##########################.......#########
##################################....##########################.......#########
################################################################.......#########
########.......#################################################.......#########
########.......#################################################.......#########
########.......##########.......################################################
##########.....##########.......##############....##############################
##########.....##########.......##############....############....##############
##############################################....############....##############
###########################################.......############....##############
###########################################.......############....##....########
#######........############################.......#...########....##....########
#######........############################.......#...########....##....########
#######........############################.......#...########..........########
#######........#################.......####......##...#########.........########
########......##################.......####......##...#########.........########
################################.......############...#########.........########
################################.......############...#########.......##########
################################.......##........##...#########.......##########
################################.......##........##############.......##########
################################.......##........###############################
#####################......#####.......##........###############################
#####################......#####################################################
#####################......#####################################################
##################.........#####################################################
##################........######################################################
##################........######################################################
##################........#########################################........#####
##################........#########################################........#####
##################........#########################################........#####
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################


################################################################.......#########
##################################.....................................#########
##################################....##########################.......#########
##################################....##########################.......#########
#####################..................................................#########
########...............................................................#########
########....................########.###########################.......#########
########.......##########.......####.###########################################
##########.....##########.......####.#########....##############################
##########.....##########.......####.#########....############....##############
##########.##############.##########.#########....############....##############
##########.##############.##########.######.......############....##############
##########.##############.##########.######.......############....##....########
#######...........................................#.......................######
#######........##########.######..................#...########....##....#.######
#######........##########.#################.......#...########..........#.######
#######........##########.######.......####......##...#########.........#.######
########......###########.######.......####......##...#########.........#.######
#########.###############.######.......############...#########.........#.######
#########.###############.######.......############...#########.......###.######
#########.###############.######.......##........##...#########.......###.######
#########.###############.######.......##.............................###.######
#########.###############.######.......##........########################.######
#########........................................########################.######
#####################......##############################################.######
#####################......##############################################.######
##################.........##############################################.######
##################........###############################################.######
##################........###############################################.######
##################.........................................................#####
##################........#########################################........#####
##################........#########################################........#####
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################
################################################################################