Fixed most input validation bugs, and did too many improvements to remember. Did I mention that I am typing these words with my hands?

This commit is contained in:
Patrick Marsee 2020-05-10 23:26:21 -04:00
parent ee5c4da549
commit 4a398cc2a3
11 changed files with 546 additions and 253 deletions

View file

@ -1,8 +1,51 @@
#gamethings.py
"""Standard thing classes.
Classes:
- ThingGraphic: Represents how thing is to be visualized.
- Thing: The abstract base class for every thing.
- Observer: The abstract base class for things that listen to events.
- Item: A thing that can exist in a character's inventory.
- Useable: A thing that can be used by a character.
- Character: A thing that represents a character.
- NPC: A character that is not controlled by the player.
- Door: A thing that sometimes blocks paths.
- MapExit: A technical thing that marks a map transition.
"""
import ruamel.yaml
from ruamel.yaml.comments import CommentedMap
class ThingGraphic(object):
"""Represent how a thing is to be visualized.
Background color, foreground color, and shape are represented.
This could hypothetically be extended to hold sprites, textures or models."""
def __init__(self, background: str, foreground: str, shape: str):
"""Create a graphic for a thing.
The background and foregrond must be strings with color hex representations.
For instance: '#00FF00' means bright green.
The shape must be a string containing exactly one of the following shapes:
o: circle
x: cross
-: horizontal line
|: vertical line
#: square
^: triangle
A graphic may be invalid.
"""
if not isinstance(background, str):
raise TypeError("Background must be a string of form '#[0-9A-Fa-f]{6}'.")
if not isinstance(foreground, str):
raise TypeError("Foreground must be a string of form '#[0-9A-Fa-f]{6}'.")
if not isinstance(shape, str):
raise TypeError("Shape must be a string of form '[-|ox#^]'.")
self.background = background
self.foreground = foreground
self.shape = shape
class Thing(object):
def __init__(self, thingType: str, name: str, x: int, y: int, description: str, flags: int, playerx = None, playery = None, **kwargs):
@ -24,7 +67,7 @@ class Thing(object):
self.lookable = bool(flags & 4)
self.takeable = bool(flags & 8)
self.useable = bool(flags & 16)
self.graphic = ('clear', '#7F7F7F', ' ')
self.graphic = ThingGraphic('clear', '#7F7F7F', ' ')
self.thingID = -1 # ID not assigned
def __str__(self):
@ -49,7 +92,7 @@ class Observer(Thing):
class Item(Thing):
yaml_flag = u'!Item'
defaultGraphic = ('clear', '#00BF00', '^')
defaultGraphic = ThingGraphic('clear', '#00BF00', '^')
def __init__(self, name, x: int, y: int, description: str, useFunc: str, useOnFunc: str, customValues: dict, ranged: bool, graphic = defaultGraphic):
super(Item, self).__init__('i', name, x, y, description, 13)
@ -68,12 +111,12 @@ class Item(Thing):
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
# save graphic
graphic = {}
if node.graphic[0] != Item.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0]
if node.graphic[1] != Item.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1]
if node.graphic[2] != Item.defaultGraphic[2]:
graphic['shape'] = node.graphic[2]
if node.graphic.background != Item.defaultGraphic.background:
graphic['bgc'] = node.graphic.background
if node.graphic.foreground != Item.defaultGraphic.forground:
graphic['fgc'] = node.graphic.foreground
if node.graphic.shape != Item.defaultGraphic.shape:
graphic['shape'] = node.graphicshape
if len(graphic) > 0:
ret['graphic'] = graphic
# save use functions
@ -96,9 +139,9 @@ class Item(Thing):
useOnFunc = ''
customValues = {}
ranged = False
bgc = Item.defaultGraphic[0]
fgc = Item.defaultGraphic[1]
shape = Item.defaultGraphic[2]
bgc = Item.defaultGraphic.background
fgc = Item.defaultGraphic.foreground
shape = Item.defaultGraphic.shape
# load graphic
if 'graphic' in parts:
if 'bgc' in parts['graphic']:
@ -107,7 +150,7 @@ class Item(Thing):
fgc = parts['graphic']['fgc']
if 'shape' in parts['graphic']:
shape = parts['graphic']['shape']
graphic = (bgc, fgc, shape)
graphic = ThingGraphic(bgc, fgc, shape)
# load use functions
if 'useFunc' in parts:
useFunc = parts['useFunc']
@ -131,7 +174,7 @@ class Item(Thing):
class Useable(Thing):
yaml_flag = u'!Useable'
defaultGraphic = ('clear', '#0000FF', '#')
defaultGraphic = ThingGraphic('clear', '#0000FF', '#')
def __init__(self, name, x: int, y: int, description: str, useFunc: str, customValues: dict, playerx = None, playery = None, graphic = defaultGraphic):
super(Useable, self).__init__('u', name, x, y, description, 16, playerx, playery)
@ -145,12 +188,12 @@ class Useable(Thing):
ret = {'name': node.name, 'location': (node.x, node.y), 'description': node.description}
# save graphic
graphic = {}
if node.graphic[0] != Useable.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0]
if node.graphic[1] != Useable.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1]
if node.graphic[2] != Useable.defaultGraphic[2]:
graphic['shape'] = node.graphic[2]
if node.graphic.background != Useable.defaultGraphic.background:
graphic['bgc'] = node.graphic.background
if node.graphic.foreground != Useable.defaultGraphic.foreground:
graphic['fgc'] = node.graphic.foreground
if node.graphic.shape != Useable.defaultGraphic.shape:
graphic['shape'] = node.graphic.shape
if len(graphic) > 0:
ret['graphic'] = graphic
# save use functions
@ -170,9 +213,9 @@ class Useable(Thing):
useFunc = ''
customValues = {}
playerx, playery = parts['location']
bgc = Useable.defaultGraphic[0]
fgc = Useable.defaultGraphic[1]
shape = Useable.defaultGraphic[2]
bgc = Useable.defaultGraphic.background
fgc = Useable.defaultGraphic.foreground
shape = Useable.defaultGraphic.shape
# load graphic
if 'graphic' in parts:
if 'bgc' in parts['graphic']:
@ -181,7 +224,7 @@ class Useable(Thing):
fgc = parts['graphic']['fgc']
if 'shape' in parts['graphic']:
shape = parts['graphic']['shape']
graphic = (bgc, fgc, shape)
graphic = ThingGraphic(bgc, fgc, shape)
# load use functions
if 'useFunc' in parts:
useFunc = parts['useFunc']
@ -199,7 +242,7 @@ class Useable(Thing):
pass
class Character(Thing):
defaultGraphic = ('clear', '#000000', 'o')
defaultGraphic = ThingGraphic('clear', '#000000', 'o')
def __init__(self, inventory: dict, customValues: dict, graphic = defaultGraphic, **kwargs):
super(Character, self).__init__(**kwargs)
@ -211,7 +254,7 @@ class Character(Thing):
self.__inventory = inventory
self.customValues = customValues
self.graphic = graphic
self.thingNames = {}
self.thingNames = {} #{str: int}
# set up inventory shtuff
for i in self.__inventory:
if self.__inventory[i].name in self.thingNames:
@ -229,7 +272,10 @@ class Character(Thing):
self.thingNames[thing.name] = [thing.thingID]
def getThingByID(self, thingID):
return self.__inventory[thingID]
if thingID in self.__inventory:
return self.__inventory[thingID]
else:
return None
def getThingByName(self, name):
if name in self.thingNames:
@ -247,10 +293,10 @@ class Character(Thing):
def removeThingByName(self, name):
ret = self.getThingByName(name)
self.thingNames[ret.name].remove(thingID)
self.thingNames[ret.name].remove(ret.thingID)
if len(self.thingNames[ret.name]) == 0:
del self.thingNames[ret.name]
del self.__inventory[thingID]
del self.__inventory[ret.thingID]
return ret
def removeThing(self, ret):
@ -267,7 +313,7 @@ class Character(Thing):
class NPC(Character, Observer):
yaml_flag = u'!NPC'
defaultGraphic = ('clear', '#000000', 'o')
defaultGraphic = ThingGraphic('clear', '#000000', 'o')
def __init__(self, behaviors: dict, tempInventory: list, **kwargs):
if 'graphic' not in kwargs:
@ -284,12 +330,12 @@ class NPC(Character, Observer):
'description': node.description, 'behaviors': node.behaviors}
# save graphic
graphic = {}
if node.graphic[0] != NPC.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0]
if node.graphic[1] != NPC.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1]
if node.graphic[2] != NPC.defaultGraphic[2]:
graphic['shape'] = node.graphic[2]
if node.graphic.background != Useable.defaultGraphic.background:
graphic['bgc'] = node.graphic.background
if node.graphic.foreground != Useable.defaultGraphic.foreground:
graphic['fgc'] = node.graphic.foreground
if node.graphic.shape != Useable.defaultGraphic.shape:
graphic['shape'] = node.graphic.shape
if len(graphic) > 0:
ret['graphic'] = graphic
# save use functions
@ -310,9 +356,9 @@ class NPC(Character, Observer):
minventory = []
mcustomValues = {}
mplayerx, mplayery = parts['location']
bgc = NPC.defaultGraphic[0]
fgc = NPC.defaultGraphic[1]
shape = NPC.defaultGraphic[2]
bgc = NPC.defaultGraphic.background
fgc = NPC.defaultGraphic.foreground
shape = NPC.defaultGraphic.shape
# load graphic
if 'graphic' in parts:
if 'bgc' in parts['graphic']:
@ -321,7 +367,7 @@ class NPC(Character, Observer):
fgc = parts['graphic']['fgc']
if 'shape' in parts['graphic']:
shape = parts['graphic']['shape']
mgraphic = (bgc, fgc, shape)
mgraphic = ThingGraphic(bgc, fgc, shape)
# load use functions
if 'inventory' in parts:
inventory = parts['inventory']
@ -338,7 +384,7 @@ class NPC(Character, Observer):
class Door(Thing):
yaml_flag = u'!Door'
defaultGraphic = ('clear', '#7F3F00', '#')
defaultGraphic = ThingGraphic('clear', '#7F3F00', '#')
def __init__(self, name, x: int, y: int, locked: bool, description = None, key = None, graphic = defaultGraphic):
self.descBase = description
@ -379,12 +425,12 @@ class Door(Thing):
ret = {'name': node.name, 'location': (node.x, node.y)}
# save graphic
graphic = {}
if node.graphic[0] != Door.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0]
if node.graphic[1] != Door.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1]
if node.graphic[2] != Door.defaultGraphic[2]:
graphic['shape'] = node.graphic[2]
if node.graphic.background != Useable.defaultGraphic.background:
graphic['bgc'] = node.graphic.background
if node.graphic.foreground != Useable.defaultGraphic.foreground:
graphic['fgc'] = node.graphic.foreground
if node.graphic.shape != Useable.defaultGraphic.shape:
graphic['shape'] = node.graphic.shape
if len(graphic) > 0:
ret['graphic'] = graphic
# save door state
@ -404,9 +450,9 @@ class Door(Thing):
description = None
locked = False
key = None
bgc = Door.defaultGraphic[0]
fgc = Door.defaultGraphic[1]
shape = Door.defaultGraphic[2]
bgc = Door.defaultGraphic.background
fgc = Door.defaultGraphic.foreground
shape = Door.defaultGraphic.shape
# load graphic
if 'graphic' in parts:
if 'bgc' in parts['graphic']:
@ -415,7 +461,7 @@ class Door(Thing):
fgc = parts['graphic']['fgc']
if 'shape' in parts['graphic']:
shape = parts['graphic']['shape']
graphic = (bgc, fgc, shape)
graphic = ThingGraphic(bgc, fgc, shape)
# load door state
if 'description' in parts:
description = parts['description']
@ -428,7 +474,7 @@ class Door(Thing):
class MapExit(Thing):
yaml_flag = u'!MapExit'
defaultGraphic = ('clear', '#FF0000', 'x')
defaultGraphic = ThingGraphic('clear', '#FF0000', 'x')
def __init__(self, name, x: int, y: int, exitid: int, destination: str, prefix = None, onUse = '', key = True, graphic = defaultGraphic):
description = name
@ -448,12 +494,12 @@ class MapExit(Thing):
ret = {'name': node.name, 'location': (node.x, node.y), 'id': node.exitid, 'destination': node.destination}
# save graphic
graphic = {}
if node.graphic[0] != MapExit.defaultGraphic[0]:
graphic['bgc'] = node.graphic[0]
if node.graphic[1] != MapExit.defaultGraphic[1]:
graphic['fgc'] = node.graphic[1]
if node.graphic[2] != MapExit.defaultGraphic[2]:
graphic['shape'] = node.graphic[2]
if node.graphic.background != Useable.defaultGraphic.background:
graphic['bgc'] = node.graphic.background
if node.graphic.foreground != Useable.defaultGraphic.foreground:
graphic['fgc'] = node.graphic.foreground
if node.graphic.shape != Useable.defaultGraphic.shape:
graphic['shape'] = node.graphic.shape
if len(graphic) > 0:
ret['graphic'] = graphic
if node.prefix != None:
@ -472,9 +518,9 @@ class MapExit(Thing):
prefix = None
onUse = ''
key = True
bgc = MapExit.defaultGraphic[0]
fgc = MapExit.defaultGraphic[1]
shape = MapExit.defaultGraphic[2]
bgc = MapExit.defaultGraphic.background
fgc = MapExit.defaultGraphic.foreground
shape = MapExit.defaultGraphic.shape
# load graphic
if 'graphic' in parts:
if 'bgc' in parts['graphic']:
@ -483,7 +529,7 @@ class MapExit(Thing):
fgc = parts['graphic']['fgc']
if 'shape' in parts['graphic']:
shape = parts['graphic']['shape']
graphic = (bgc, fgc, shape)
graphic = ThingGraphic(bgc, fgc, shape)
if 'prefix' in parts:
prefix = parts['prefix']
if 'onUse' in parts:
@ -495,7 +541,7 @@ class MapExit(Thing):
class MapEntrance(Thing):
yaml_flag = u'!MapEntrance'
defaultGraphic = ('clear', 'clear', 'x')
defaultGraphic = ThingGraphic('clear', 'clear', 'x')
# no graphic - should not be drawn
def __init__(self, x: int, y: int, exitid: int, name = None):
@ -503,6 +549,7 @@ class MapEntrance(Thing):
name = 'entrance {}'.format(exitid)
super(MapEntrance, self).__init__('a', name, x, y, '', 1)
self.exitid = exitid
#self.graphic = MapEntrance.defaultGraphic
@classmethod
def to_yaml(cls, representer, node):
@ -519,7 +566,7 @@ class MapEntrance(Thing):
class PlayerCharacter(Character):
"""Player object. Cannot be created with yaml."""
defaultGraphic = ('clear', '#0000FF', 'o')
defaultGraphic = ThingGraphic('clear', '#0000FF', 'o')
def __init__(self, **kwargs):
if 'name' not in kwargs: