Connect rooms in rogue-style levels.
authorNeil Moore <neil@s-z.org>
Wed, 28 May 2008 06:57:26 +0000 (02:57 -0400)
committerNeil Moore <neil@s-z.org>
Wed, 28 May 2008 06:57:26 +0000 (02:57 -0400)
level0.sav:
  Swap stairs to level1 and unassigned (random) stairs.

roguelike.py:
  Change starting positions.

level.py:
  New class Room (currently for internal use only).
  New method Level.connect to connect two rooms.
  Create Rooms in Level.make_room_map, connect each to
    one or two preceding rooms.

level.py
levels/level0.sav
roguelike.py

index 0fc1d51..ce2c50f 100644 (file)
--- a/level.py
+++ b/level.py
@@ -2,6 +2,7 @@ import curses
 import random
 import math
 import re
+from cStringIO import StringIO
 
 import loc
 import things
@@ -27,6 +28,71 @@ class LocationProxy (object):
 
     def location(self):
         return self.level().loc(self.y, self.x)
+    
+class Room (object):
+    """A room, with a union-find structure for connected rooms."""
+    def __init__(self, y, x, h, w):
+        (self.y, self.x, self.h, self.w) = (y, x, h, w)
+        self.ymax = y + h - 1
+        self.xmax = x + w - 1
+        self.doors = { 't': False, 'b': False, 'l': False, 'r': False }
+        self.__ufcomp = self
+
+    def component(self):
+        if self.__ufcomp is not self:
+            self.__ufcomp = self.__ufcomp.component()
+        return self.__ufcomp
+
+    def facing(self, room):
+        possible = []
+        assert not (self & room), "Can't face intersecting room"
+
+        if self.y > room.ymax:
+            dy = room.ymax - self.y
+        elif room.y > self.ymax:
+            dy = room.y - self.ymax
+        else:
+            dy = 0
+        
+        if self.x > room.xmax:
+            dx = room.xmax - self.x
+        elif room.x > self.xmax:
+            dx = room.x - self.xmax
+        else:
+            dx = 0
+
+        if abs(dx) > abs(dy):
+            return 'r' if dx > 0 else 'l'
+        else:
+            return 'b' if dy > 0 else 't'
+
+    def add_door(self, face):
+        if face == 't':
+            dx = random.randint(self.x+1, self.xmax-1)
+            dy = self.y
+        elif face == 'b':
+            dx = random.randint(self.x+1, self.xmax-1)
+            dy = self.ymax
+        elif face == 'l':
+            dx = self.x
+            dy = random.randint(self.y+1, self.ymax-1)
+        elif face == 'r':
+            dx = self.xmax
+            dy = random.randint(self.y+1, self.ymax-1)
+        else:
+            raise Exception("Unknown face %s" % (face,))
+
+        return (dy, dx)
+
+    def __and__(self, room):
+        yoverlap = (self.y <= room.ymax+1 and self.ymax >= room.y-1)
+        xoverlap = (self.x <= room.xmax+1 and self.xmax >= room.x-1)
+        if xoverlap and yoverlap:
+            self += room
+        return yoverlap and xoverlap
+
+    def __iadd__(self, room):
+        self.__ufcomp = room.component()
 
 
 class Level (object):
@@ -35,43 +101,7 @@ class Level (object):
     be travelled between via transport points.
     """
     __metaclass__ = cacher.FirstArg
-
-    def stair_unavailable(self, stair):
-        if stair in self.unconnected_stairs:
-            self.unconnected_stairs.remove(stair)
-    def stair_available(self, stair):
-        self.unconnected_stairs.add(stair)
-
-    def make_room_map(self, nrooms = 5, nstairs = 2):
-        self.grid = [ [ loc.Wall(self,i,j)
-            for j in xrange(self.w) ]
-            for i in xrange(self.h) ]
-        rooms = [ [ None for j in xrange(self.w) ] for i in xrange(self.h) ]
-
-        st_rooms = set(random.sample(xrange(nrooms), nstairs))
-
-        for rno in xrange(nrooms):
-            rh = random.randint(3, self.h/4)
-            rw = random.randint(3, self.w/4)
-            ry = random.randint(0, self.h - rh)
-            rx = random.randint(0, self.w - rw)
-            while rooms[ry][rx]:
-                rh = random.randint(3, self.h/4)
-                rw = random.randint(3, self.w/4)
-                ry = random.randint(0, self.h - rh)
-                rx = random.randint(0, self.w - rw)
-            for i in xrange(ry+1, ry+rh-1):
-                for j in xrange(rx+1, rx+rw-1):
-                    rooms[i][j] = rno
-                    if rno in st_rooms and i == ry + rh//2 and j == rx + rw//2:
-                        self.grid[i][j] = loc.Stair(self, i, j)
-                    else:
-                        self.grid[i][j] = loc.Floor(self, i, j)
-        
-        for row in self.grid:
-            for tile in row:
-                self.activate(tile)
-
+    
     def __init__(self, filename=None, height=None, width=None, name=None):
         self.active_tiles = set()
         self.invalid_tiles = set()
@@ -81,7 +111,6 @@ class Level (object):
         self.needs_full_repaint = True
         self.filename = None
 
-
         if filename:
             assert height == width == name == None, \
                     "Level constructor specified more than just the filename"
@@ -105,6 +134,107 @@ class Level (object):
         random.shuffle(self.unconnected_drops)
 
         self.boardwin = curses.newwin(self.h + 1, self.w + 1)
+
+    def stair_unavailable(self, stair):
+        if stair in self.unconnected_stairs:
+            self.unconnected_stairs.remove(stair)
+    def stair_available(self, stair):
+        self.unconnected_stairs.add(stair)
+
+
+    def connect(self, rm1, rm2):
+        # pick nearer sides of each room
+        faces = (rm1.facing(rm2), rm2.facing(rm1))
+        (y1,x1) = rm1.add_door(faces[0])
+        (y2,x2) = rm2.add_door(faces[1])
+        
+        self.grid[y1][x1] = loc.Door(self, y1, x1)
+        self.grid[y2][x2] = loc.Door(self, y2, x2)
+
+        dy = y2 - y1
+        dx = x2 - x1
+
+        if faces[0] == 'r' and faces[1] == 'l':
+            # >V> or >^>
+            ydir = dy/abs(dy) if dy else 0
+            ty = y1
+            for tx in xrange(x1+1, x2):
+                self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                if tx == (x1 + x2) // 2 and dy != 0:
+                    while ty != y2:
+                        self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                        ty = ty + ydir
+                    self.grid[ty][tx] = loc.Floor(self, ty,tx)
+        elif faces[0] == 'l' and faces[1] == 'r':
+            # >V> or >^>
+            ydir = -dy/abs(dy) if dy else 0
+            ty = y2
+            for tx in xrange(x2+1, x1):
+                self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                if tx == (x1 + x2) // 2 and dy != 0:
+                    while ty != y1:
+                        self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                        ty = ty + ydir
+                    self.grid[ty][tx] = loc.Floor(self, ty,tx)
+        elif faces[0] == 'b' and faces[1] == 't':
+            # V>V or V<V
+            xdir = dx/abs(dx) if dx else 0
+            tx = x1
+            for ty in xrange(y1+1, y2):
+                self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                if ty == (y1 + y2) // 2 and dx != 0:
+                    while tx != x2:
+                        self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                        tx = tx + xdir
+                    self.grid[ty][tx] = loc.Floor(self, ty,tx)
+        elif faces[0] == 't' and faces[1] == 'b':
+            # ^>^ or ^<^
+            xdir = -dx/abs(dx) if dx else 0
+            tx = x2
+            for ty in xrange(y2+1, y1):
+                self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                if ty == (y1 + y2) // 2 and dx != 0:
+                    while tx != x1:
+                        self.grid[ty][tx] = loc.Floor(self, ty, tx)
+                        tx = tx + xdir
+                    self.grid[ty][tx] = loc.Floor(self, ty,tx)
+
+    def make_room_map(self, nrooms = 5, nstairs = 2):
+        self.grid = [ [ loc.Wall(self,i,j)
+            for j in xrange(self.w) ]
+            for i in xrange(self.h) ]
+        rooms = []
+
+        st_rooms = set(random.sample(xrange(nrooms), nstairs))
+
+        for rno in xrange(nrooms):
+            rm = None
+
+            while not rm or any(orm & rm for orm in rooms):
+                rh = random.randint(4, self.h // (math.sqrt(nrooms)/1.5))
+                rw = random.randint(4, self.w // (math.sqrt(nrooms)/1.5))
+                ry = random.randint(0, self.h - rh)
+                rx = random.randint(0, self.w - rw)
+                rm = Room(ry, rx, rh, rw)
+            rooms.append(rm)
+            for i in xrange(rm.y+1, rm.ymax):
+                for j in xrange(rm.x+1, rm.xmax):
+                    if rno in st_rooms and i == ry + rh//2 and j == rx + rw//2:
+                        self.grid[i][j] = loc.Stair(self, i, j)
+                    else:
+                        self.grid[i][j] = loc.Floor(self, i, j)
+
+        ct = 0
+        for rno in xrange(1,nrooms):
+            nct = 1 if random.random() < 0.8 else 2
+            for tgt in random.sample(range(0, rno), nct):
+                self.connect(rooms[rno], rooms[tgt])
+                ct = ct + 1
+
+        for row in self.grid:
+            for tile in row:
+                self.activate(tile)
+        __main__.AppUI.instance.message("ct is now %s" % (ct,))
     
     def activate(self, tile):
         (y, x) = (tile.y, tile.x)
index 845eeb6..b6eab57 100644 (file)
 23 70 flowx float -0.942809041582
 8 80 flowy float +0.916619901538
 8 80 flowx float -0.157134840264
-13 2 tgt loc level1 44 88
-13 2 oneway bool True
+15 60 tgt loc level1 44 88
+15 60 oneway bool True
 1 71 tgt loc level0 36 9
 36 9 tgt loc level0 1 71
index c5070b9..dcf7ae8 100755 (executable)
@@ -319,7 +319,7 @@ if __name__ == '__main__':
         things.ItemClass.loadall(open("items/items.itm"))
 
         lvl = level.Level("levels/level0.sav")
-        me.place(lvl.loc(min(10, lvl.h - 2), min(5, lvl.w - 2)))
+        me.place(lvl.loc(min(13, lvl.h - 2), min(2, lvl.w - 2)))
         him.place(lvl.loc(max(2, lvl.h - 5), max(5, lvl.w - 10)))
         ui.set_level(lvl)