#!/usr/bin/env python import pickle import base64 ## ## Goal: To make a cool roguelike game starting with an inn. I hope to ## flesh out the inn as a concept with enough flexibility to make it ## seem like a nicely crafted system. The stuff I need to make an inn ## should be similiar enough to the stuff I need to make an evil temple, ## a magical forest, or just about anything else. It needs to be a ## black box system that I can pass state into and receive state back ## out of. In other words, I hope to use a web server to drive this via ## forms. That will kludge up things a bit, but I think it will still ## be usable if a bit archaic in its user interface. ## ## Currently a lot of this is just mental exercise, with code mocked up ## but no real testing done. ## ## I refer from time to time to something I call the "engine". I don't ## have one that works with this set of code, and the term engine seems ## outdated versus the current plan, but in general, the engine will: ## ## * display a map ## * display any messages from the last iteration ## * provide a list of interactions found on/near the player ## ## When the player clicks on an interaction the engine will pass the ## state and the interaction to the appropriate methods to get a result, ## and start over. ## ## A lot of work, and thought, needs to go into messages that get passed ## back to the player describing events. Especially when dealing with ## things on different turn schedules than the player. ## ## ## There are many things in here that aren't good OO practice. ## ## ## Our own little error codes that belong to us! ## class ERROR( Exception ): pass class unreachableERROR( ERROR ): pass class unimplementedERROR( ERROR): pass class uninitializedERROR( ERROR ): pass class existsERROR( ERROR ): pass class internalERROR( ERROR ): pass class userERROR( ERROR ): pass class messageClass( object ): ## ## Class data so that I don't have to pass the instance around ## _errors = [] _messages = [] _warnings = [] _debugs = [] def getErrors( self ): return self._errors def getMessages( self ): return self._messages def getWarnings( self ): return self._warnings def getDebugs( self ): return self._debugs def reset( self ): self._errors = [] self._warnings = [] self._messages = [] self._debugs = [] def addMessage( self, msg ): self._messages.append( msg ) def addWarning( self, msg ): self._warnings.append( msg ) def addError( self, msg ): self._errors.append( msg ) def addDebug( self, msg ): self._debugs.append( msg ) ## ## This is the game. Data is in the class instance, so we can get to it ## as needed. ## class gameClass( object ): _map = None _player = None _citizens = None _randoms = None _turn = 0 def __init__( self ): super( gameClass, self ).__init__() self._map = mapClass() self._player = characterClass() createInn( self._map ) def _wrap( self, data ): return base64.urlsafe_b64encode( pickle.dumps( data ) ) def _unwrap( self, data ): return pickle.loads( base64.urlsafe_b64decode( data ) ) def _checksum( self, data ): import md5 return self._wrap( md5.md5( data ).hexdigest() ) def newGame( self ): return self._wrap( {} ) def dump( self ): d = { 'game:turn':self._turn } d.update( self._player.dump() ) d.update( self._map.dump() ) w = { 'data':self._wrap( d ) } w['checksum'] = self._checksum( w['data'] ) return self._wrap( w ) def load( self, token ): w = self._unwrap( token ) if 'data' in w and 'checksum' in w: if self._checksum( w['data'] ) != w['checksum']: raise userERROR d = self._unwrap( w['data'] ) if 'game:turn' in d: self._turn = d['game:turn'] self._player.load( d ) self._map.load( d ) def turn( self ): return self._turn def player( self ): return( self._player ) def map( self ): return self._map def incrementTurn( self, increment ): self._turn = self._turn + increment ## ## There has to be a generic way for things to be interacted with in the ## game. I can't special case everything because that would be a ## nightmare. Thus, the interactionClass. My innkeeper will be an npc ## that has some sell interactions. He will sell beer, cheese, and room ## keys. Each of these will be an interaction. I don't yet have a ## notion how gold will be implemented as a requirement. ## ## Here is a mock up of how it might look: ## ## class buyBeerInteraction( interactionClass ): ## _title = "Buy a beer." ## _requires = [<1 gold>] ## _action = _buyBeerMethod( self, caller ) ## _description = "Warm and dark." ## def _buyBeerMethod( self, interaction, caller ): ## ## ## ## I immediately realize that I was quite simplistic about requirements. ## There are multiple choices for complex interactions. Create multiple ## interactions for each valid combination of requirements. Create a ## simple language to describe interactions ("and", "or", grouping, and ## negation). Test requirements with a method that determines if the ## requirements are met. I'm leaning towards a method now since it ## gives me the most flexibility, but it requires more thought. If I ## make a method, then listInteractions() will probably end up doing the ## test itself. Any listInteractions() call probably needs several ## peices of critical data: the locaiton of the thing listInteractions() ## is being called on, the location of the player (or monster), and the ## map. The engine will have some notion of how far to search from the ## player (or monster) to find interactions to list. ## ## Now imagine combat. If a character is adjacent to a foe, then the ## players listInteractions() call would include an interaction to ## attack the foe. Possibly multiples interactions to attack the foe ## depending on items and skills the character has. If a character can ## use ranged weapons, and a foe is in range, then a listInteractions() ## call would include an interaction to attack the foe as well. This ## means that combat could be handled just like everything else. ## Different kinds of attacks would produce different kinds of ## interactions that could be selected. A very skilled character with ## many items and nearby features could be presented with a tremendous ## number of options. This will be kludgy. ## ## The same works for targetted spells, but area of effects are a bit ## harder. We don't want to produce a list of interactions for each ## targettable fill in range, that would be a nightmare. Instead, ## perhaps I should conceive of a way to request parameters from the ## user, such as angle and range. I kind of like this idea because of ## the thought it will require to target things. It reminds me of the ## "good old days" of computer games. ## #class interactionClass( object ): # _title = None # _key = None # _description = "Undescribed" # _method = None # ## # ## requires() returns a list of things that are needed to call the # ## action() method. For instance, a door might require a certain key # ## object, in which case the key object would be in the requires list. # ## The engine will be responsible for verifying that the requirements # ## are met. # ## # def requires( self, caller ): # return self._requires # def title( self, caller ): # return self._title # ## # ## The action is the method that does whatever the interaction is. It # ## is passed the caller so that it can manipulate the inventory or # ## other stats of the character. Each action must override this # ## default method. # ## # def action( self, caller ): # raise unimplementedERROR # ## # ## It takes time to do things. The time tells us how long it takes. # ## Some actions might be really short, others might be really long. # ## # def time( self, caller ): # return self._time class interactionClass( object) : def __init__( self, title, command, hint ): super( interactionClass, self ).__init__() self._title = title self._command = command self._hint = hint def title( self ): return self._title def command( self ): return self._command def hint( self ): return self._hint ### ### Characters are both the player and the monsters. Monsters will need ## a method on them to control their actions. They should be able to ## interact with interactions just like players. Creating AIs for each ## monster might end up being a nightmare. I imagine in the beginning, ## monsters will stand still and attack things that move adjacent to ## them. There probably needs to be some smart handling of characters ## once they can move and act. Some are permanent, such as the ## innkeeper, and will probably just go idle when the player is away and ## contain code to catch themselves up when the player returns. Most ## are transient, and will probably be removed because the player killed ## them. But if the player doesn't kill them, then they should probably ## have a range (by paththing through the map, not just a vector) at ## which the despawn. On the same note, monsters need to be spawned in ## to populate areas where the player currently is. ## class characterClass( object ): def __init__( self ): super( characterClass, self).__init__() self._location = (0,0,0) def location( self ): return self._location def newLocation( self, location ): self._location = location def dumpV1( self ): d = {} d['player:location'] = self._location return d def loadV1( self, d ): if 'player:location' in d: self._location = d['player:location'] def dump( self ): return self.dumpV1() def load( self, data ): return self.loadV1( data ) def interact( self, command ): found = False if command[0:5] == "move:": dir = eval( command[5:] ) g = gameClass() m = g.map() x = self._location[0] + dir[0] y = self._location[1] + dir[1] z = self._location[2] + dir[2] if m.getFill( x, y, z ).isPassable( None ): self._location = ( x,y,z) found = True else: raise internalERROR if not found: raise internalERROR def listInteractions( self, caller ): ret = [] ## ## movement first! ## g = gameClass() m = g.map() x = self._location[0] y = self._location[1] z = self._location[2] hint = { (-1,1,0):'move:NW', (0,1,0):'move:N', (1,1,0):'move:NE', (-1,0,0):'move:W', (1,0,0):'move:E', (-1,-1,0):'move:SW', (0,-1,0):'move:S', (1,-1,0):'move:SE', (0,0,1):'move:U', (0,0,-1):'move:D' } title = { (-1,1,0):'Northwest', (0,1,0):'North', (1,1,0):'Northeast', (-1,0,0):'West', (1,0,0):'East', (-1,-1,0):'Southwest', (0,-1,0):'South', (1,-1,0):'Southeast', (0,0,1):'Up', (0,0,-1):'Down' } zoff = 0 for xoff in [-1, 0, 1]: for yoff in [-1, 0, 1]: if m.getFill( x+xoff, y+yoff, z+zoff ).isPassable( None ): if xoff == 0 and yoff == 0 and zoff == 0: pass else: dir = (xoff,yoff,zoff) ret.append( interactionClass( title[dir], "move:%s" % str( dir ), hint[dir] ) ) if m.getFill( x, y, z ).allowsDownwardMovement( None ): if m.getFill( x, y, z-1).isPassable( None ): dir = (0,0,-1) ret.append( interactionClass( title[dir], "move:%s" % str( dir ), hint[dir] ) ) if m.getFill( x, y, z ).allowsUpwardMovement( None ): if m.getFill( x, y, z+1).isPassable( None ): dir = (0,0,1) ret.append( interactionClass( title[dir], "move:%s" % str( dir ), hint[dir] ) ) ## ## Now we should scan for other interactions. Such as a door! ## return ret def getMap( self, map ): m = [] y = self._location[1] + 8 z = self._location[2] while y >= self._location[1] - 8: m.append( [] ) for x in range( self._location[0]-8, self._location[0] + 8 + 1 ): m[-1].append( map.getFill( x, y, z) ) y = y -1 return m ## ## Replication has told me that I need one of these, so it has a bunch ## of stuff that not everything needs but everything it has it something ## that most things need. ## class coreClass( object ): ## ## I'm not so sure I really want a boolean for this. I might want ## different levels of light! Dim light, bright light, sunlight, ## black light, etc! For now, things key off the boolean, but I'll ## put in a brightness field just in case. ## def __init__( self ): super( coreClass, self ).__init__() self._lit = False, self._brightness = 0 self._shortdesc = "Undescribed." self._shortdescdark = "Dark." self._desc = "This is undescribed." self._descdark = "This is dark." self._title = "This has no title." self._subtitle = "This has no subtitle." self._ascii = 'X' self._traits = [] def setLit( self, lit ): self._lit = lit def setBrightness( self, brightness ): self._brightness = brightness def setShortDesc( self, desc ): self._shortdesc = desc def setShortDescDark( self, desc): self_shortdescdark = desc def setdesc( self, desc ): self._desc = desc def setDescDark( self, desc): self._descdark = desc def setTitle( self, title): self._title = title def setSubTitle( self, title): self._subtitle = subtitle def setAscii( self, ascii ): self._ascii = ascii def isLit( self, caller ): return self._lit def brightness( self, caller ): return self._brightness def title( self, caller ): return self._title def ascii( self, caller ): return self._ascii def blocksLOS( self, caller ): return False def shortDescription( self, caller ): if self._lit: return self._shortdesc else: return self._shortdescdark def description( self, caller ): if self._lit: return self._desc else: return self._deskdark def listTraits( self, caller ): return [] def getTrait( self, caller, trait ): return None def listInteractions( self, caller ): return [] ## ## We need objects that exist in the dungeon. If an object is near a ## player then the object will emit a get interaction. An object is ## something that can be picked up, a feature is something that cannot ## be picked up. Features can interact if you are in their location. ## The coin will be the basis of weight and size in the system. Objects ## in your inventory will emit a drop interaction. This will be quite ## kludgy I think. ## class objectClass( coreClass ): def __init__( self ): super( objectClass, self ).__init__() self._size = 0 # per object if stacked self._weight = 0 # per object if stacked self._count = 0 self._unique = False def setSize( self, size ): self._size = size def setWeight( self, weight ): self._weight = weight def setCount( self, count ): self._count = count def setUnique( self, unique ): self._unique = unique def weight( self, caller ): return( self._weight * self._count ) def size( self, caller ): return( self._size * self._count ) def count( self, caller ): if self.count < 0: raise internalERROR return( self._count ) def add( self, caller, count ): if self._unique: raise internalERROR self._count = self._count + count def remove( self, caller, count ): if count > self._count: raise internalERROR self.count = self.cont - count ## ## In addition to interactions, I need some way to put in additional ## effects for things like features. No one would click on a "set off ## arrow trap" interaction, so the arrow trap needs to be able to do ## that on its own! Perhaps just adding an effect() method to features ## will do this well enough? ## ## ## I need traits. I don't know how they work. They are not coreClass ## objects. ## class traitClass( object ): _title = None _duration = None def __init__( self, title, duration ): super( traitClass, self ).__init__() self._title = title self._duration = duration ## ## Traits will have durations. When the duration expires, the trait ## will go away. I'm calling this a timerClass because it seems to be a ## more appropriate naming for what it does. A duration of 5 turns ## doesn't mean anything when our state will only know the current turn. ## A timer tells us the start and end time, and our state knows if the ## current turn is within that. ## #class timerClass( object ): # def isActive( self, turn ): # raise unimplementedERROR # # def isExpired( self, turn ): # raise unimplementedERROR ## ## This is a simple implementation of a cyclic effect such as day/night. ## #class periodicDurationClass( timerClass ): # _period = None # def _even( t ): # return (t % 2) == 0 # def isActive( self, turn ): # assert self._period # return self._even( turn / self._period ) # def isExpired( self, turn ): # assert self._period # return False #class permanentDurationClass( timerClass ): # def isActive( self, turn ): # return True # def isExpired( self, turn ): # return False #class timedDurationClass( timerClass ): # _start = None # _end = None # _duration = None # # def __init__( self, turn ): # if not self._duration: # raise unimplementedERROR # self._start = turn # self._end = turn + self._duration # # def isActive( self, turn ): # if self._start and self._end: # return self._start <= turn <= self._end # if self._start: # return turn >= self._start # if self._end: # return turn <= self._end # raise unreachableERROR # # def isExpired( self, turn ): # if self._end: # return turn > self._end # return False ## ## The simple idea of a "turn" being one unit might be problematic. ## Instead, I will assume that a turn is 10 units long. That lets me do ## things such as haste and slow spells. I can also do long cast spells ## that make take two or three simple turns to complete. ## #class shortDurationClass( timedDurationClass ): # _duration = 100 #class mediumDurationClass( timedDurationClass ): # _duration = 500 #class longDurationClass( timedDurationClass ): # _duration = 1000 ## ## A fill is the contents of a cube in the map. The fill title is like ## a room name. A 3x3 room will have 9 fills of the same title. The ## zone is the collection of rooms. Eventually zone traits will come ## from the zone. ## class fillClass( coreClass ): def __init__( self ): super( fillClass, self ).__init__( ) self._zone = None self._subzone = None def isPassable( self, caller ): raise unimplementedERROR def allowsUpwardMovement( self, caller ): return False def allowsDownwardMovement( self, caller ): return False def setZone( self, zone ): self._zone = zone def setSubZone( self, subzone ): self._subzone = subzone def zone( self, caller ): return self._zone def subZone( self, caller ): return self._subzone def allowsVertical( self, caller ): raise internalError ## ## The void is any location without an explicit fill ## class voidFillClass( fillClass ): def __init__( self, title=None ): super( fillClass, self ).__init__( ) self.setTitle( 'The Void' ) self.setAscii( '*' ) def isPassable( self, caller ): raise internalERROR ## ## Solids are walls, floors, and ceilings ## class solidFillClass( fillClass ): def __init__( self ): super( solidFillClass, self ).__init__() self._title = 'Solid Stone' self._ascii = '#' def isLit( self, caller ): raise internalERROR def blocksLOS( self, caller ): return True def isPassable( self, caller ): return False ## ## Open fills are where the player moves ## class openFillClass( fillClass ): def __init__( self ): super( openFillClass, self ).__init__() self._ascii = '.' def isPassable( self, caller ): return True def allowsUpwardMovement( self, caller ): return False def allowsDownwardMovement( self, caller ): return False ## ## A stair will be a fill that you can enter without falling if there is ## no floor. You can move vertically on a stair. A stair with an empty ## fill under it is a trap (a one way stair if there is only one blank ## fill beneath it, or a damage causing fall if there is more than one ## blank fill)! A stair with a solid fill under it is the bottom of the ## stairs. A stair with a solid fill above it is the top of the stairs. ## A stair with an empty fill above it is a trap as well (you could go ## up the stair, but not come back down). ## class stairFillClass( openFillClass ): def __init__( self ): super( stairFillClass, self ).__init__() self._ascii = '/' class litStairFillClass( stairFillClass ): _lit = True class darkStairFillClass( stairFillClass ): _lit = False def allowsUpwardMovement( self, caller ): return True def allowsDownwardMovement( self, caller ): return True ## ## Doors are harder. They have a two basic states (open or closed). An ## open door can either be open or broken. A closed door can either be ## locked, unlocked, or stuck. Actually, it could be locked and stuck! ## There should be different kinds of locked and stuck. ## class doorFillClass( fillClass ): def __init__( self ): super( doorFillClass, self ).__init__() self._open = False self._broken = False # self._locked = 0 # 0 is unlocked, higher numbers are better locks self._key = None self._stuck = 0 # 0 is not stuck, higher numbers are more stuck self._ascii = '+' def blocksLOS( self, caller ): if self._broken: return False return not self._open def isPassable( self, caller ): return True ## ## an opaque fill blocks LOS but not movement. If you are in an opaque ## fill, then you can see in all directions out of it. Examples of ## opaque squares would be curtains and smoke. ## class opaqueFillClass( fillClass ): def blocksLOS( self, caller ): return True ## ## Instances of lit and unlit areas. ## solid = solidFillClass() theVoid = voidFillClass() class featureClass( coreClass ): def __init__( self ): super( featureClass, self ).__init__() self._desc = 'This feature is undescribed.' class waypointFeatureClass( featureClass ): def __init__( self, title, x, y, z ): super( waypointFeatureClass, self ).__init__() self._title = title self._location = (x, y, z) def shortDescription( self, caller ): return 'Waypoint ' + str( self._locaiton ) def description( self, caller ): return 'Offical Waypoint: This is location ' + str( self._location ) + '.' ## ## Most things will move through open fills. If something moves into a ## square without a solid fill beneath it, then it falls. Stairs are ## special. They are most often treated as open fills. If you are in a ## stair fill then you can move up or down if there is a stair fill or ## an open fill above or below you. I assume stairs are spiral, so they ## block line of sight. ## ## The mapClass keeps track of where everything is. Everything, so far, ## is fills, objects, and features. Characters (player and non-player) ## need to go in there too. ## ## Eventually the map should be backed by an SQL database instead of ## being crafted on the fly. ## ## There needs to be a way to dump the state so that it can be passed ## back and forth between web page views. ## ## The map holds state for the fills. All fills query the map to find ## out anything they need to find out. They should use the format ## 'fillname:location:trait' for the key. ## class mapClass( object ): def __init__( self ): super( mapClass, self ).__init__() self._zones = {} self._fills = {} self._features = {} self._objects = {} self._state = {} self._lx = 0 self._ly = 0 self._lz = 0 self._ux = 0 self._uy = 0 self._uz = 0 def load( self, d ): if 'map:state' in d: self._state = d['map:state'] def dump( self ): return { 'map:state':self._state } def setState( self, key, data ): self.state[key] = data def getState( self, key ): if key in self.state: return self.state[key] return None def _bounds( self, x, y, z ): if x < self._lx: self._lx = x if x > self._ux: self._ux = x if y < self._ly: self._ly = y if y > self._uy: self._uy = y if z < self._lz: self._lz = z if z > self._uz: self._uz = z def getLowerBounds( self ): return (self._lx, self._ly, self._lz) def getUpperBounds( self ): return (self._ux, self._uy, self._uz) def countFills( self ): return len( self._fills.keys() ) def setFill( self, x, y, z, f ): if (x,y,z) in self._fills: print 'Exists', (x,y,z), self._fills[(x,y,z)] raise existsERROR self._fills[(x,y,z)] = f self._bounds( x, y, z ) if f.zone( None ): z = f.zone( None ) if z in self._zones: self._zones[z].append( (x,y,z) ) else: self._zones[z] = [(x,y,z)] def forceSetFill( self, x, y, z, f ): self._fills[(x,y,z)] = f self._bounds( x, y, z ) def getFill( self, x, y, z ): if (x,y,z) in self._fills: return( self._fills[(x,y,z)] ) else: return( theVoid ) def getLocation( self, x, y, z ): # deprecated print 'getLocation() is deprecated' return self.getFill( x, y, z ) def setLocation( self, x, y, z, t ): # deprecated print 'setLocation() is deprecated' self.setFill( x, y, z, t ) def addObject( self, object, x, y, z, turn ): if (x,y,z) in self._objects: self._objects[(x,y,z)].append( { 'turn':turn, 'object':object } ) else: self._objects[(x,y,z)] [{ 'turn':turn, 'object':object }] def listObjects( self, caller, object, x, y, z ): if (x,y,z) in self._objects: return [x['object'] for x in self._objects[(x,y,z)]] def removeObject( self, caller, object, x, y, z ): raise unimplementedERROR def addFeature( self, x, y, z, feature ): if (x,y,z) in self._features: self._features[(x,y,z)].append( feature ) else: self._features[(x,y,z)] = [feature] def listFeatures( self, caller, x, y, z ): if (x,y,z) in self._features: return self._features[(x,y,z)] else: return [] def encaseOpen( self, fill ): for loc in self._fills.keys(): if isinstance( self._fills[loc], openFillClass): for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): for z in range( -1, 1 + 1 ): if (x+loc[0],y+loc[1],z+loc[2]) not in self._fills: self.setFill( x+loc[0], y+loc[1], z+loc[2], fill ) def validate( self ): ret = True for loc in self._fills.keys(): if isinstance( self._fills[loc], openFillClass): for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): for z in range( -1, 1 + 1 ): if (x+loc[0],y+loc[1],z+loc[2]) not in self._fills: print loc, self._fills[loc], 'not bounded at', (x+loc[0],y+loc[1],z+loc[2]) ret = False return ret return ret def hasLOS( self, caller, f, t ): raise unimplementedERROR ## ## This is just for testing. It won't work for real since the map ## icon for an open fill depends on the fill beneath the current z ## location, and individual fills don't know their own coordinates. ## def ascii( self, minx, miny, maxx, maxy, z ): import sys y = maxy while y >= miny: for x in range( minx, maxx + 1): sys.stdout.write( self.getFill( x, y, z ).ascii() ) y = y - 1 print originFeature = waypointFeatureClass( 'The Origin', 0, 0, 0 ) def createInn( inn ): ## ## Inn: my prototype inn ## inn.addFeature( 0, 0, 0, originFeature ) class innCommonRoomClass( openFillClass ): def __init__( self ): super( innCommonRoomClass, self ).__init__() self._lit = True self._title = "Common Room" self._zone = "The Lonely Inn" self._shortdesc = "A dark room infested with adventurers." self._desc = "This is a large open area set with many tables and chairs. Adventurers gather here to rest bewtween excursions." class innGuestRoomClass( openFillClass ): def __init__( self ): super( innGuestRoomClass, self ).__init__() self._lit = False self._title = "Guest Room" self._shortdesc = "A small room with a bed." self._desc = "This is a private room for guests of the inn. It has a bed, and a locking door." class innDoorClass( doorFillClass ): def __init__( self ): super( innDoorClass, self ).__init__() self._zone = "The Lonely Inn" class innStairClass( stairFillClass ): def __init__( self ): super( innStairClass, self ).__init__() self._zone = "The Lonely Inn" innCommonRoom = innCommonRoomClass() innGuestRoom = innGuestRoomClass() innDoor = innDoorClass() innStair = innStairClass() class tunnelStairClass( stairFillClass ): def __init__( self ): super( tunnelStairClass, self ).__init__() self._zone = "The Tunnel To No Where" class tunnelToNowhereClass( openFillClass ): def __init__( self ): self._title = None self._zone = "The Tunnel To No Where" self._shortdesc = "Tunnel" self._desc = "A tunnel to nowhere." tunnelToNowhere = tunnelToNowhereClass() stairsToNowhere = tunnelStairClass() z = 0 for x in range( -3, 3 + 1 ): for y in range( -8, 8 + 1): inn.setFill( x, y, z, innCommonRoom ) inn.setFill( -4, 0, z, innDoor ) # should be a door basex = -6 basey = 0 for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): inn.setFill( basex + x, basey + y, z, innGuestRoom ) inn.setFill( 4, 0, z, innDoor ) # should be a door basex = 6 basey = 0 for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): inn.setFill( basex + x, basey + y, z, innGuestRoom ) inn.setFill( 4, 8, z, innDoor ) # should be a door basex = 6 basey = 8 for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): inn.setFill( basex + x, basey + y, z, innGuestRoom ) inn.setFill( 4, -8, z, innDoor ) # should be a door basex = 6 basey = -8 for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): inn.setFill( basex + x, basey + y, z, innGuestRoom ) inn.setFill( -4, 8, z, innDoor ) # should be a door basex = -6 basey = 8 for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): inn.setFill( basex + x, basey + y, z, innGuestRoom ) inn.setFill( -4, -8, z, innDoor ) # should be a door basex = -6 basey = -8 for x in range( -1, 1 + 1 ): for y in range( -1, 1 + 1 ): inn.setFill( basex + x, basey + y, z, innGuestRoom ) inn.setFill( -3, 9, 0, innStair ) inn.setFill( -3, 9, 1, innStair ) inn.setFill( -3, 9, 2, innStair ) ## ## The tunnel to nowhere ## import random random.seed( 79 ) # always make the same tunnel def tunnellen(): return random.randint( 4, 11 ) def stairlen(): if random.randint( 0, 5 ) == 0: return 2 else: return random.randint( 3, 6 ) def direction( last ): dirs = [(1,0,0), (-1,0,0), (0,0,1), (0,0,-1)] try: dirs.remove( (last[0]*-1, last[1]*-1, last[2]*-1) ) dirs.remove( last ) except: pass return random.choice( dirs ) ## ## It goes North ## dir = (0,1,0) count = 16 x = -3 y = 9 z = 2 flip = 0 for i in range( 0, 100 ): while count: if dir[0] or dir[1]: inn.setFill( x+dir[0], y+dir[1], z+dir[2], tunnelToNowhere ) else: inn.forceSetFill( x, y, z, stairsToNowhere ) inn.setFill( x+dir[0], y+dir[1], z+dir[2], stairsToNowhere ) count = count - 1 x = x+dir[0] y = y+dir[1] z = z+dir[2] last = dir flip = flip + 1 flip = flip % 2 if flip: dir = direction( dir ) else: dir = (0,1,0) if dir[0] or dir[1]: count = tunnellen() else: count=stairlen() class commonsRoomClass( openFillClass ): def __init__( self ): super( commonsRoomClass, self ).__init__() self._lit = True self._title = None self._zone = "The Commons" self._shortdesc = "The Commons of the underground empire." self._desc = "This is a public space for vendoring." commonsRoom = commonsRoomClass() inn.setFill( 0, -9, 0, commonsRoom ) inn.setFill( 0, -10, 0, commonsRoom ) inn.setFill( 0, -11, 0, commonsRoom ) for x in range( -5, 5 + 1): for y in range( -22, -11): z = 0 inn.setFill( x, y, z, commonsRoom ) class bankRoomClass( openFillClass ): def __init__( self ): super( bankRoomClass, self ).__init__() self._lit = True self._title = None self._zone = "The Underbank" self._shortdesc = "The Underbank, for all your underground banking need." self._desc = "This is a place to store loot!" bankRoom = bankRoomClass() inn.setFill( 6, -14, 0, bankRoom ) for x in range( 7, 11 ): for y in range( -15, -12 ): z = 0 inn.setFill( x, y, z, bankRoom ) inn.setFill( x, y, z+1, bankRoom ) inn.setFill( x, y, z+2, bankRoom ) class sleeplessNightsInnHallwayClass( openFillClass ): def __init__( self ): super( sleeplessNightsInnHallwayClass, self ).__init__() self._lit = False self._title = "Entrance Hallway" self._zone = "The Sleepless Nights Inn" self._shortdesc = "A underground brothel." self._desc = "The biggest known brothel in the underground empire." sleeplessNightsInnHallway = sleeplessNightsInnHallwayClass() for x in range( 6,16): inn.setFill( x,-22,0, sleeplessNightsInnHallway ) class sleeplessNightsInnLobbyClass( openFillClass ): def __init__( self ): super( sleeplessNightsInnLobbyClass, self ).__init__() self._lit = False self._title = "Lobby" self._zone = "The Sleepless Nights Inn" self._shortdesc = "A underground brothel." self._desc = "The biggest known brothel in the underground empire." sleeplessNightsInnLobby = sleeplessNightsInnLobbyClass() for x in range( 16, 21): for y in range( -27, -18): inn.setFill( x, y, 0, sleeplessNightsInnLobby ) inn.encaseOpen( solid ) #print inn.getLowerBounds() #print inn.getUpperBounds() #inn.ascii( -10, -10, 10, 10, 0 ) #print #print inn.getLowerBounds() #print inn.getUpperBounds() #inn.ascii( -10, -10, 10, 10, 0 ) # #lower = inn.getLowerBounds() #upper = inn.getUpperBounds() # #for z in range( lower[2], upper[2] + 1 ): # print # print 'Z', z # inn.ascii( lower[0], lower[1], upper[0], upper[1], z ) # ## ## Some (old) testing code (that no longer quite works) ## #map = mapClass() # #for x in range( -25, 25 + 1 ): # for y in range( -25, 25 + 1 ): # z = -1 # map.setLocation( x, y, z, solid ) # z = 0 # map.setLocation( x, y, z, solid ) # z = 1 # map.setLocation( x, y, z, solid ) # z = 2 # map.setLocation( x, y, z, solid ) # #for x in range( -2, 2 + 1 ): # for y in range( -2, 2 + 1 ): # z = 1 # map.setLocation( x, y, z, litOpen ) # #map.setLocation( 0, -3, 1, darkOpen ) #map.setLocation( 0, -4, 1, darkOpen ) #map.setLocation( 0, -5, 1, darkOpen ) #map.setLocation( -1, -5, 1, darkOpen ) #map.setLocation( 1, -5, 1, darkOpen ) #map.setLocation( -2, -5, 1, darkOpen ) #map.setLocation( 2, -5, 1, darkOpen ) # #print 'map.validate()', map.validate() # #print 'map.getLocation( 0, 0, 0 ).isLit()', map.getLocation( 0, 0, 0 ).isLit() #print 'map.getLocation( 0, 0, 0 ).title()', map.getLocation( 0, 0, 0 ).title() #print 'map.getLocation( 0, 0, -5 ).isLit()', map.getLocation( 0, 0, -5 ).isLit() #print 'map.getLocation( 0, 0, -5 ).title()', map.getLocation( 0, 0, -5 ).title() # #map.ascii( -10, -10, 10, 10, 1 ) #print # #map.setLocation( 0, 0, 10, litOpen ) #map.encaseOpen( solid ) #map.ascii( -5, -5, 5, 5, 10 )