From 010c67fd78b2ba34dcf60a644629fe2d7da89de3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 1 May 2022 18:03:47 -0700 Subject: [PATCH 001/234] Attempt #1 to dig tunnels -- it does not work --- roguebasin/map.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index ad7fd22..83ce901 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -3,6 +3,7 @@ import logging import numpy as np +import random import tcod from .geometry import Point, Rect, Size from .tile import Floor, Wall @@ -79,14 +80,38 @@ class RoomsAndCorridorsGenerator(MapGenerator): rooms: List['RectangularRoom'] = [] # For nicer debug logging indent = 0 - for node in bsp.pre_order(): + + for node in bsp.post_order(): node_bounds = self.__rect_from_bsp_node(node) if node.children: - if LOG.getEffectiveLevel() == logging.DEBUG: - LOG.debug(f'{" " * indent}{node_bounds}') - indent += 2 - # TODO: Connect the two child rooms + LOG.debug(f'{" " * indent}{node_bounds}') + + left_node_bounds = self.__rect_from_bsp_node(node.children[0]) + right_node_bounds = self.__rect_from_bsp_node(node.children[1]) + + LOG.debug(f'{" " * indent} left:{node.children[0]}, {left_node_bounds}') + LOG.debug(f'{" " * indent}right:{node.children[1]}, {right_node_bounds}') + + start_point = left_node_bounds.midpoint + end_point = right_node_bounds.midpoint + + # Randomly choose whether to move horizontally then vertically or vice versa + if random.random() < 0.5: + corner = Point(end_point.x, start_point.y) + else: + corner = Point(start_point.x, end_point.y) + + LOG.debug(f'{" " * indent}Digging tunnel between {start_point} and {end_point} with corner {corner}') + LOG.debug(f'{" " * indent}`-> start:{left_node_bounds}') + LOG.debug(f'{" " * indent}`-> end:{right_node_bounds}') + + for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): + tiles[x, y] = Floor + for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): + tiles[x, y] = Floor + + indent += 2 else: LOG.debug(f'{" " * indent}{node_bounds} (room) {node}') @@ -101,8 +126,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): room = RectangularRoom(bounds) rooms.append(room) - if LOG.getEffectiveLevel() == logging.DEBUG: - indent -= 2 + indent -= 2 self.rooms = rooms From 558bd86b1673e4c1b4e039fb541d35372b1d2aaa Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 07:15:41 -0700 Subject: [PATCH 002/234] Initial commit --- .gitignore | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + 2 files changed, 131 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a31c71 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# going-rogue +An experiment building a Roguelike with libtcod and Python From e1044f3a73f9a55199496dc6045c333484486232 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 18:21:24 -0700 Subject: [PATCH 003/234] Move MovePlayerAction.Direction to geometry.Direction --- roguebasin/actions.py | 17 ++++++----------- roguebasin/events.py | 9 +++++---- roguebasin/geometry.py | 10 ++++++++++ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 5cbea22..77e0f28 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -2,7 +2,12 @@ # Eryn Wells import logging -from .geometry import Vector +from .geometry import Direction +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .engine import Engine + from .object import Entity LOG = logging.getLogger('events') @@ -26,16 +31,6 @@ class RegenerateRoomsAction(Action): ... class MovePlayerAction(Action): - class Direction: - North = Vector(0, -1) - NorthEast = Vector(1, -1) - East = Vector(1, 0) - SouthEast = Vector(1, 1) - South = Vector(0, 1) - SouthWest = Vector(-1, 1) - West = Vector(-1, 0) - NorthWest = Vector(-1, -1) - def __init__(self, direction: Direction): self.direction = direction diff --git a/roguebasin/events.py b/roguebasin/events.py index 588e1f8..670cb49 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -3,6 +3,7 @@ import tcod from .actions import Action, ExitAction, MovePlayerAction, RegenerateRoomsAction +from .geometry import Direction from typing import Optional class EventHandler(tcod.event.EventDispatch[Action]): @@ -15,13 +16,13 @@ class EventHandler(tcod.event.EventDispatch[Action]): sym = event.sym if sym == tcod.event.KeySym.h: - action = MovePlayerAction(MovePlayerAction.Direction.West) + action = MovePlayerAction(Direction.West) elif sym == tcod.event.KeySym.j: - action = MovePlayerAction(MovePlayerAction.Direction.South) + action = MovePlayerAction(Direction.South) elif sym == tcod.event.KeySym.k: - action = MovePlayerAction(MovePlayerAction.Direction.North) + action = MovePlayerAction(Direction.North) elif sym == tcod.event.KeySym.l: - action = MovePlayerAction(MovePlayerAction.Direction.East) + action = MovePlayerAction(Direction.East) elif sym == tcod.event.KeySym.SPACE: action = RegenerateRoomsAction() diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index b126835..f0200c1 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -37,6 +37,16 @@ class Vector: def __str__(self): return f'(δx:{self.x}, δy:{self.y})' +class Direction: + North = Vector(0, -1) + NorthEast = Vector(1, -1) + East = Vector(1, 0) + SouthEast = Vector(1, 1) + South = Vector(0, 1) + SouthWest = Vector(-1, 1) + West = Vector(-1, 0) + NorthWest = Vector(-1, -1) + @dataclass(frozen=True) class Size: width: int = 0 From 50d6550e17dcca5125d411efbb8776ca4321df2f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 18:23:48 -0700 Subject: [PATCH 004/234] Parameterize maximum room size; make minimum_room_size the actual floor size, not counting walls --- roguebasin/map.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 83ce901..18e6123 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -45,11 +45,13 @@ class RoomsAndCorridorsGenerator(MapGenerator): '''Generate a rooms-and-corridors style map with BSP.''' class Configuration: - def __init__(self, min_room_size: Size): + def __init__(self, min_room_size: Size, max_room_size: Size): self.minimum_room_size = min_room_size + self.maximum_room_size = max_room_size DefaultConfiguration = Configuration( - min_room_size=Size(8, 8) + min_room_size=Size(5, 5), + max_room_size=Size(15, 15), ) def __init__(self, *, size: Size, config: Optional[Configuration] = None): @@ -66,12 +68,14 @@ class RoomsAndCorridorsGenerator(MapGenerator): return self.tiles minimum_room_size = self.configuration.minimum_room_size + maximum_room_size = self.configuration.maximum_room_size # Recursively divide the map into squares of various sizes to place rooms in. bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) bsp.split_recursive( depth=4, - min_width=minimum_room_size.width, min_height=minimum_room_size.height, + # Add 2 to the minimum width and height to account for walls + min_width=minimum_room_size.width + 2, min_height=minimum_room_size.height + 2, max_horizontal_ratio=1.5, max_vertical_ratio=1.5) tiles = np.full(tuple(self.size), fill_value=Wall, order='F') From 7ba33cc6eb5539b63bf89c91dbb1c81a9d1a9c0f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 18:24:14 -0700 Subject: [PATCH 005/234] Fix this type annotation --- roguebasin/map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 18e6123..9018efe 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -60,7 +60,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): self.rng: tcod.random.Random = tcod.random.Random() - self.rooms: List['RectanularRoom'] = [] + self.rooms: List['RectangularRoom'] = [] self.tiles: Optional[np.ndarray] = None def generate(self) -> np.ndarray: From 58e732c923e7bcda0c68d4535b8b059fab821280 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 18:25:30 -0700 Subject: [PATCH 006/234] Clean up and document room rect generation --- roguebasin/map.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 9018efe..3fc5996 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -119,8 +119,13 @@ class RoomsAndCorridorsGenerator(MapGenerator): else: LOG.debug(f'{" " * indent}{node_bounds} (room) {node}') - size = Size(self.rng.randint(5, min(15, max(5, node.width - 2))), - self.rng.randint(5, min(15, max(5, node.height - 2)))) + # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is + # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of + # the node. + width_range = (minimum_room_size.width, min(maximum_room_size.width, max(minimum_room_size.width, node.width - 2))) + height_range = (minimum_room_size.height, min(maximum_room_size.height, max(minimum_room_size.height, node.height - 2))) + + size = Size(self.rng.randint(*width_range), self.rng.randint(*height_range)) origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 1)), node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) bounds = Rect(origin, size) From 09bdf8a7a66326950e77e66e4906d9a05ad1fbb2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 19:00:45 -0700 Subject: [PATCH 007/234] Place NPCs randomly in a generated room --- roguebasin/engine.py | 4 ++-- roguebasin/map.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index f6f0cd3..0eb13c1 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -33,8 +33,8 @@ class Engine: self.player = Entity('@', position=player_start_position, fg=tcod.white) self.entities: AbstractSet[Entity] = {self.player} - for _ in range(self.rng.randint(1, 15)): - position = Point(self.rng.randint(0, map_size.width), self.rng.randint(0, map_size.height)) + for _ in range(self.rng.randint(5, 15)): + position = self.map.random_walkable_position() self.entities.add(Entity('@', position=position, fg=tcod.yellow)) def handle_event(self, event: tcod.event.Event): diff --git a/roguebasin/map.py b/roguebasin/map.py index 3fc5996..693f54b 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -18,6 +18,14 @@ class Map: self.generator = RoomsAndCorridorsGenerator(size=size) self.tiles = self.generator.generate() + def random_walkable_position(self) -> Point: + # TODO: Include hallways + random_room: RectangularRoom = random.choice(self.generator.rooms) + floor = random_room.floor_bounds + random_position_in_room = Point(random.randint(floor.min_x, floor.max_x), + random.randint(floor.min_y, floor.max_y)) + return random_position_in_room + def tile_is_in_bounds(self, point: Point) -> bool: return 0 <= point.x < self.size.width and 0 <= point.y < self.size.height From fcfab9fc1b23f5bd463fc6d159206a390f20cd95 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 19:02:39 -0700 Subject: [PATCH 008/234] Add Rect.inset_rect --- roguebasin/geometry.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index f0200c1..72ca7be 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -98,6 +98,15 @@ class Rect: def midpoint(self) -> Point: return Point(self.mid_x, self.mid_y) + def inset_rect(self, top: int = 0, right: int = 0, bottom: int = 0, left: int = 0) -> 'Rect': + ''' + Return a new Rect inset from this rect by the specified values. Arguments are listed in clockwise order around + the permeter. This method doesn't do any validation or transformation of the returned Rect to make sure it's + valid. + ''' + return Rect(Point(self.origin.x + left, self.origin.y + top), + Size(self.size.width - right - left, self.size.height - top - bottom)) + def __iter__(self): yield tuple(self.origin) yield tuple(self.size) From 7c5c3c57ec27ccc24c8d6b01eb4d5a25a745e074 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 19:03:31 -0700 Subject: [PATCH 009/234] Fill the map with Empty tiles instead of Wall tiles --- roguebasin/map.py | 4 ++-- roguebasin/tile.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 693f54b..8a2f53e 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -6,7 +6,7 @@ import numpy as np import random import tcod from .geometry import Point, Rect, Size -from .tile import Floor, Wall +from .tile import Empty, Floor, Wall from typing import List, Optional LOG = logging.getLogger('map') @@ -86,7 +86,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): min_width=minimum_room_size.width + 2, min_height=minimum_room_size.height + 2, max_horizontal_ratio=1.5, max_vertical_ratio=1.5) - tiles = np.full(tuple(self.size), fill_value=Wall, order='F') + tiles = np.full(tuple(self.size), fill_value=Empty, order='F') # Generate the rooms rooms: List['RectangularRoom'] = [] diff --git a/roguebasin/tile.py b/roguebasin/tile.py index 80abfce..7048a50 100644 --- a/roguebasin/tile.py +++ b/roguebasin/tile.py @@ -25,5 +25,6 @@ tile_datatype = np.dtype([ def tile(*, walkable: int, transparent: int, dark: Tuple[int, Tuple[int, int, int], Tuple[int, int ,int]]) -> np.ndarray: return np.array((walkable, transparent, dark), dtype=tile_datatype) +Empty = tile(walkable=False, transparent=False, dark=(ord(' '), (255, 255, 255), (0, 0, 0))) Floor = tile(walkable=True, transparent=True, dark=(ord(' '), (255, 255, 255), (50, 50, 150))) Wall = tile(walkable=False, transparent=False, dark=(ord(' '), (255, 255, 255), (0, 0, 150))) \ No newline at end of file From c638ee506af33f428fcff4d9064784d65c9fc7fa Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 19:04:59 -0700 Subject: [PATCH 010/234] Carve walls and floors out of Empty tiles Walls fill all the Empty tiles around Floors, including rooms and hallways. --- roguebasin/map.py | 86 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 8a2f53e..95e5e29 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -5,9 +5,9 @@ import logging import numpy as np import random import tcod -from .geometry import Point, Rect, Size +from .geometry import Direction, Point, Rect, Size from .tile import Empty, Floor, Wall -from typing import List, Optional +from typing import Iterator, List, Optional LOG = logging.getLogger('map') @@ -93,20 +93,25 @@ class RoomsAndCorridorsGenerator(MapGenerator): # For nicer debug logging indent = 0 + room_attrname = f'{__class__.__name__}.room' + for node in bsp.post_order(): node_bounds = self.__rect_from_bsp_node(node) if node.children: LOG.debug(f'{" " * indent}{node_bounds}') - left_node_bounds = self.__rect_from_bsp_node(node.children[0]) - right_node_bounds = self.__rect_from_bsp_node(node.children[1]) + left_room: RectangularRoom = getattr(node.children[0], room_attrname) + right_room: RectangularRoom = getattr(node.children[1], room_attrname) - LOG.debug(f'{" " * indent} left:{node.children[0]}, {left_node_bounds}') - LOG.debug(f'{" " * indent}right:{node.children[1]}, {right_node_bounds}') + left_room_bounds = left_room.bounds + right_room_bounds = right_room.bounds - start_point = left_node_bounds.midpoint - end_point = right_node_bounds.midpoint + LOG.debug(f'{" " * indent} left:{node.children[0]}, {left_room_bounds}') + LOG.debug(f'{" " * indent}right:{node.children[1]}, {right_room_bounds}') + + start_point = left_room_bounds.midpoint + end_point = right_room_bounds.midpoint # Randomly choose whether to move horizontally then vertically or vice versa if random.random() < 0.5: @@ -115,8 +120,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): corner = Point(start_point.x, end_point.y) LOG.debug(f'{" " * indent}Digging tunnel between {start_point} and {end_point} with corner {corner}') - LOG.debug(f'{" " * indent}`-> start:{left_node_bounds}') - LOG.debug(f'{" " * indent}`-> end:{right_node_bounds}') + LOG.debug(f'{" " * indent}`-> start:{left_room_bounds}') + LOG.debug(f'{" " * indent}`-> end:{right_room_bounds}') for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): tiles[x, y] = Floor @@ -141,15 +146,58 @@ class RoomsAndCorridorsGenerator(MapGenerator): LOG.debug(f'{" " * indent}`-> {bounds}') room = RectangularRoom(bounds) + setattr(node, room_attrname, room) rooms.append(room) + if not hasattr(node.parent, room_attrname): + setattr(node.parent, room_attrname, room) + elif random.random() < 0.5: + setattr(node.parent, room_attrname, room) + indent -= 2 + # Pass up a random child room so that parent nodes can connect subtrees to each other. + parent = node.parent + if parent: + node_room = getattr(node, room_attrname) + if not hasattr(node.parent, room_attrname): + setattr(node.parent, room_attrname, node_room) + elif random.random() < 0.5: + setattr(node.parent, room_attrname, node_room) + self.rooms = rooms for room in rooms: + for wall_position in room.walls: + if tiles[wall_position.x, wall_position.y] != Floor: + tiles[wall_position.x, wall_position.y] = Wall + bounds = room.bounds - tiles[bounds.min_x:bounds.max_x, bounds.min_y:bounds.max_y] = Floor + # The range of a numpy array slice is [a, b). + floor_rect = bounds.inset_rect(top=1, right=1, bottom=1, left=1) + tiles[floor_rect.min_x:floor_rect.max_x + 1, floor_rect.min_y:floor_rect.max_y + 1] = Floor + + for y in range(self.size.height): + for x in range(self.size.width): + pos = Point(x, y) + if tiles[x, y] != Floor: + continue + + neighbors = [ + pos + Direction.North, + pos + Direction.NorthEast, + pos + Direction.East, + pos + Direction.SouthEast, + pos + Direction.South, + pos + Direction.SouthWest, + pos + Direction.West, + pos + Direction.NorthWest, + ] + + for neighbor in neighbors: + if tiles[neighbor.x, neighbor.y] != Empty: + continue + tiles[neighbor.x, neighbor.y] = Wall self.tiles = tiles @@ -170,5 +218,21 @@ class RectangularRoom: def center(self) -> Point: return self.bounds.midpoint + @property + def floor_bounds(self) -> Rect: + return self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) + + @property + def walls(self) -> Iterator[Point]: + bounds = self.bounds + min_y = bounds.min_y + max_y = bounds.max_y + min_x = bounds.min_x + max_x = bounds.max_x + for y in range(min_y, max_y + 1): + for x in range(min_x, max_x + 1): + if y == min_y or y == max_y or x == min_x or x == max_x: + yield Point(x, y) + def __repr__(self) -> str: return f'{self.__class__.__name__}({self.bounds})' \ No newline at end of file From 39832c7f74dfaf85adf8d549f9e9dac220af6503 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 3 May 2022 19:05:53 -0700 Subject: [PATCH 011/234] A little script to visualize how BSP works; emits a DOT graph --- bsp_visualizer.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 bsp_visualizer.py diff --git a/bsp_visualizer.py b/bsp_visualizer.py new file mode 100644 index 0000000..334ea0e --- /dev/null +++ b/bsp_visualizer.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# Eryn Wells + +import argparse +import tcod + +def parse_args(argv, *a, **kw): + parser = argparse.ArgumentParser(*a, **kw) + parser.add_argument('width', type=int) + parser.add_argument('height', type=int) + args = parser.parse_args(argv) + return args + +def main(argv): + args = parse_args(argv[1:], prog=argv[0]) + + bsp = tcod.bsp.BSP(0, 0, args.width, args.height) + bsp.split_recursive( + depth=3, + min_width=5, min_height=5, + max_vertical_ratio=1.5, max_horizontal_ratio=1.5 + ) + + node_names = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + current_node_name_index = 0 + + print('digraph {') + + for node in bsp.post_order(): + try: + node_name = getattr(node, 'viz_name') + except AttributeError: + node_name = node_names[current_node_name_index] + setattr(node, 'viz_name', node_name) + + bounds = (node.x, node.y, node.width, node.height) + print(f' {node_name} [label=\"{current_node_name_index}: {bounds}\"]') + + current_node_name_index += 1 + + if node.children: + node_name = getattr(node, 'viz_name') + left_child_name = getattr(node.children[0], 'viz_name') + right_child_name = getattr(node.children[1], 'viz_name') + print(f' {node_name} -> {left_child_name}') + print(f' {node_name} -> {right_child_name}') + + print('}') + +if __name__ == '__main__': + import sys + result = main(sys.argv) + sys.exit(0 if not result else result) From 25aa5506c8db1421b972e55cbce8f11ab0efd576 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 4 May 2022 09:22:06 -0700 Subject: [PATCH 012/234] Convert the generator.Configuration class to a dataclass --- roguebasin/map.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 95e5e29..14611c4 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -7,6 +7,7 @@ import random import tcod from .geometry import Direction, Point, Rect, Size from .tile import Empty, Floor, Wall +from dataclasses import dataclass from typing import Iterator, List, Optional LOG = logging.getLogger('map') @@ -52,14 +53,14 @@ class MapGenerator: class RoomsAndCorridorsGenerator(MapGenerator): '''Generate a rooms-and-corridors style map with BSP.''' + @dataclass class Configuration: - def __init__(self, min_room_size: Size, max_room_size: Size): - self.minimum_room_size = min_room_size - self.maximum_room_size = max_room_size + minimum_room_size: Size + maximum_room_size: Size DefaultConfiguration = Configuration( - min_room_size=Size(5, 5), - max_room_size=Size(15, 15), + minimum_room_size=Size(5, 5), + maximum_room_size=Size(15, 15), ) def __init__(self, *, size: Size, config: Optional[Configuration] = None): From 084385f8f2bc2c1abd8eea130acbc0f48b03a5c9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 4 May 2022 09:22:40 -0700 Subject: [PATCH 013/234] Add a map shroud over tiles and compute field of view based on player position!!! --- roguebasin/engine.py | 17 +++++++++++++++++ roguebasin/map.py | 16 ++++++++++++++-- roguebasin/tile.py | 25 ++++++++++++++++++++----- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 0eb13c1..d85cf71 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -37,6 +37,8 @@ class Engine: position = self.map.random_walkable_position() self.entities.add(Entity('@', position=position, fg=tcod.yellow)) + self.update_field_of_view() + def handle_event(self, event: tcod.event.Event): action = self.event_handler.dispatch(event) @@ -45,8 +47,23 @@ class Engine: action.perform(self, self.player) + self.update_field_of_view() + def print_to_console(self, console): self.map.print_to_console(console) for ent in self.entities: + # Only print entities that are in the field of view + if not self.map.visible[tuple(ent.position)]: + continue ent.print_to_console(console) + + def update_field_of_view(self) -> None: + '''Compute visible area of the map based on the player's position and point of view.''' + self.map.visible[:] = tcod.map.compute_fov( + self.map.tiles['transparent'], + tuple(self.player.position), + radius=8) + + # Visible tiles should be added to the explored list + self.map.explored |= self.map.visible diff --git a/roguebasin/map.py b/roguebasin/map.py index 14611c4..58bd27b 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -6,7 +6,7 @@ import numpy as np import random import tcod from .geometry import Direction, Point, Rect, Size -from .tile import Empty, Floor, Wall +from .tile import Empty, Floor, Shroud, Wall from dataclasses import dataclass from typing import Iterator, List, Optional @@ -19,6 +19,11 @@ class Map: self.generator = RoomsAndCorridorsGenerator(size=size) self.tiles = self.generator.generate() + # Map tiles that are currently visible to the player + self.visible = np.full(tuple(self.size), fill_value=False, order='F') + # Map tiles that the player has explored + self.explored = np.full(tuple(self.size), fill_value=False, order='F') + def random_walkable_position(self) -> Point: # TODO: Include hallways random_room: RectangularRoom = random.choice(self.generator.rooms) @@ -34,8 +39,15 @@ class Map: return self.tiles[point.x, point.y]['walkable'] def print_to_console(self, console: tcod.Console) -> None: + '''Render the map to the console.''' size = self.size - console.tiles_rgb[0:size.width, 0:size.height] = self.tiles["dark"] + + # If a tile is in the visible array, draw it with the "light" color. If it's not, but it's in the explored + # array, draw it with the "dark" color. Otherwise, draw it as Empty. + console.tiles_rgb[0:size.width, 0:size.height] = np.select( + condlist=[self.visible, self.explored], + choicelist=[self.tiles['light'], self.tiles['dark']], + default=Shroud) class MapGenerator: def __init__(self, *, size: Size): diff --git a/roguebasin/tile.py b/roguebasin/tile.py index 7048a50..07fa0a3 100644 --- a/roguebasin/tile.py +++ b/roguebasin/tile.py @@ -20,11 +20,26 @@ tile_datatype = np.dtype([ ('transparent', np.bool), # A graphic struct (as above) defining the look of this tile when it's not visible ('dark', graphic_datatype), + # A graphic struct (as above) defining the look of this tile when it's visible + ('light', graphic_datatype), ]) -def tile(*, walkable: int, transparent: int, dark: Tuple[int, Tuple[int, int, int], Tuple[int, int ,int]]) -> np.ndarray: - return np.array((walkable, transparent, dark), dtype=tile_datatype) +def tile(*, + walkable: int, + transparent: int, + dark: Tuple[int, Tuple[int, int, int], Tuple[int, int ,int]], + light: Tuple[int, Tuple[int, int, int], Tuple[int, int ,int]]) -> np.ndarray: + return np.array((walkable, transparent, dark, light), dtype=tile_datatype) -Empty = tile(walkable=False, transparent=False, dark=(ord(' '), (255, 255, 255), (0, 0, 0))) -Floor = tile(walkable=True, transparent=True, dark=(ord(' '), (255, 255, 255), (50, 50, 150))) -Wall = tile(walkable=False, transparent=False, dark=(ord(' '), (255, 255, 255), (0, 0, 150))) \ No newline at end of file +# An overlay color for tiles that are not visible and have not been explored +Shroud = np.array((ord(' '), (255, 255, 255), (0, 0, 0)), dtype=graphic_datatype) + +Empty = tile(walkable=False, transparent=False, + dark=(ord(' '), (255, 255, 255), (0, 0, 0)), + light=(ord(' '), (255, 255, 255), (0, 0, 0))) +Floor = tile(walkable=True, transparent=True, + dark=(ord('·'), (80, 80, 100), (50, 50, 50)), + light=(ord('·'), (100, 100, 120), (80, 80, 100))) +Wall = tile(walkable=False, transparent=False, + dark=(ord(' '), (255, 255, 255), (0, 0, 150)), + light=(ord(' '), (255, 255, 255), (50, 50, 200))) \ No newline at end of file From 1247617b8754b396e8f947589c48e6ab6c086538 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 4 May 2022 09:25:35 -0700 Subject: [PATCH 014/234] Add diagonal movement --- roguebasin/events.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 670cb49..2ca754d 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -15,7 +15,9 @@ class EventHandler(tcod.event.EventDispatch[Action]): sym = event.sym - if sym == tcod.event.KeySym.h: + if sym == tcod.event.KeySym.b: + action = MovePlayerAction(Direction.SouthWest) + elif sym == tcod.event.KeySym.h: action = MovePlayerAction(Direction.West) elif sym == tcod.event.KeySym.j: action = MovePlayerAction(Direction.South) @@ -23,6 +25,12 @@ class EventHandler(tcod.event.EventDispatch[Action]): action = MovePlayerAction(Direction.North) elif sym == tcod.event.KeySym.l: action = MovePlayerAction(Direction.East) + elif sym == tcod.event.KeySym.n: + action = MovePlayerAction(Direction.SouthEast) + elif sym == tcod.event.KeySym.u: + action = MovePlayerAction(Direction.NorthEast) + elif sym == tcod.event.KeySym.y: + action = MovePlayerAction(Direction.NorthWest) elif sym == tcod.event.KeySym.SPACE: action = RegenerateRoomsAction() From 1cd45d366bf30aefb2a7362c0924f0d5f060139a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 5 May 2022 08:37:48 -0700 Subject: [PATCH 015/234] Add Direction.all() that returns an iterator that produces all the Direction values --- roguebasin/geometry.py | 13 ++++++++++++- roguebasin/map.py | 12 +----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index 72ca7be..203cc1f 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -2,7 +2,7 @@ # Eryn Wells from dataclasses import dataclass -from typing import Any, Tuple, overload +from typing import Any, Iterator, overload @dataclass(frozen=True) class Point: @@ -47,6 +47,17 @@ class Direction: West = Vector(-1, 0) NorthWest = Vector(-1, -1) + @classmethod + def all(cls) -> Iterator['Direction']: + yield Direction.North + yield Direction.NorthEast + yield Direction.East + yield Direction.SouthEast + yield Direction.South + yield Direction.SouthWest + yield Direction.West + yield Direction.NorthWest + @dataclass(frozen=True) class Size: width: int = 0 diff --git a/roguebasin/map.py b/roguebasin/map.py index 58bd27b..57078c7 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -196,17 +196,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): if tiles[x, y] != Floor: continue - neighbors = [ - pos + Direction.North, - pos + Direction.NorthEast, - pos + Direction.East, - pos + Direction.SouthEast, - pos + Direction.South, - pos + Direction.SouthWest, - pos + Direction.West, - pos + Direction.NorthWest, - ] - + neighbors = (pos + direction for direction in Direction.all()) for neighbor in neighbors: if tiles[neighbor.x, neighbor.y] != Empty: continue From a43e403e9c55d864e0c70cabdf9dba2445018e89 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 5 May 2022 08:38:06 -0700 Subject: [PATCH 016/234] Let NPCs randomly walk --- roguebasin/engine.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index d85cf71..43f0347 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -2,10 +2,11 @@ # Eryn Wells import logging +import random import tcod from .actions import ExitAction, MovePlayerAction, RegenerateRoomsAction from .events import EventHandler -from .geometry import Point, Size +from .geometry import Direction, Point, Size from .map import Map from .object import Entity from typing import AbstractSet @@ -47,6 +48,18 @@ class Engine: action.perform(self, self.player) + directions = list(Direction.all()) + + for ent in self.entities: + if ent == self.player: + continue + + while True: + new_position = ent.position + random.choice(directions) + if self.map.tile_is_walkable(new_position): + ent.position = new_position + break + self.update_field_of_view() def print_to_console(self, console): From 1d4882a8ace64f0524b7f80f18a86cfc84388413 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 5 May 2022 08:45:30 -0700 Subject: [PATCH 017/234] Do not allow entities to move into squares previously moved entities have moved to --- roguebasin/engine.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 43f0347..63358ce 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -49,6 +49,7 @@ class Engine: action.perform(self, self.player) directions = list(Direction.all()) + moved_entities = [self.player] for ent in self.entities: if ent == self.player: @@ -56,8 +57,10 @@ class Engine: while True: new_position = ent.position + random.choice(directions) - if self.map.tile_is_walkable(new_position): + overlaps_with_previously_moved_entity = any(new_position == moved_ent.position for moved_ent in moved_entities) + if not overlaps_with_previously_moved_entity and self.map.tile_is_walkable(new_position): ent.position = new_position + moved_entities.append(ent) break self.update_field_of_view() From 6a431ee5749d5330f72b60ca1de47ac8e60fa03c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 5 May 2022 08:55:49 -0700 Subject: [PATCH 018/234] Tweaking the room size ratios --- roguebasin/map.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 57078c7..89e245e 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -97,7 +97,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): depth=4, # Add 2 to the minimum width and height to account for walls min_width=minimum_room_size.width + 2, min_height=minimum_room_size.height + 2, - max_horizontal_ratio=1.5, max_vertical_ratio=1.5) + max_horizontal_ratio=3, max_vertical_ratio=3) tiles = np.full(tuple(self.size), fill_value=Empty, order='F') From 00462f60054b583fc56440f32286d8f68099a8fd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 6 May 2022 14:34:26 -0700 Subject: [PATCH 019/234] Change Engine.Configuration to a clas and convert moved_entities to a set --- roguebasin/engine.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 63358ce..b81eb65 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -9,22 +9,22 @@ from .events import EventHandler from .geometry import Direction, Point, Size from .map import Map from .object import Entity -from typing import AbstractSet +from dataclasses import dataclass +from typing import MutableSet LOG = logging.getLogger('engine') EVENT_LOG = logging.getLogger('events') +@dataclass class Configuration: - def __init__(self, map_size: Size): - self.map_size = map_size - self.random_seed = None + map_size: Size class Engine: def __init__(self, event_handler: EventHandler, configuration: Configuration): self.event_handler = event_handler self.configuration = configuration - self.rng = tcod.random.Random(seed=configuration.random_seed) + self.rng = tcod.random.Random() map_size = configuration.map_size self.map = Map(map_size) @@ -33,7 +33,7 @@ class Engine: player_start_position = first_room.center self.player = Entity('@', position=player_start_position, fg=tcod.white) - self.entities: AbstractSet[Entity] = {self.player} + self.entities: MutableSet[Entity] = {self.player} for _ in range(self.rng.randint(5, 15)): position = self.map.random_walkable_position() self.entities.add(Entity('@', position=position, fg=tcod.yellow)) @@ -49,7 +49,7 @@ class Engine: action.perform(self, self.player) directions = list(Direction.all()) - moved_entities = [self.player] + moved_entities: MutableSet[Entity] = {self.player} for ent in self.entities: if ent == self.player: @@ -60,7 +60,7 @@ class Engine: overlaps_with_previously_moved_entity = any(new_position == moved_ent.position for moved_ent in moved_entities) if not overlaps_with_previously_moved_entity and self.map.tile_is_walkable(new_position): ent.position = new_position - moved_entities.append(ent) + moved_entities.add(ent) break self.update_field_of_view() From c2c67ae9ef0f77f8ba13ac3214647adf48884a87 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 6 May 2022 21:13:37 -0700 Subject: [PATCH 020/234] Move logging hero movement into the if block where it actually does the move --- roguebasin/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 77e0f28..ebcc838 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -41,6 +41,6 @@ class MovePlayerAction(Action): position_is_walkable = engine.map.tile_is_walkable(new_player_position) overlaps_another_entity = any(new_player_position == ent.position for ent in engine.entities if ent is not entity) - LOG.debug(f'Attempting to move player to {new_player_position} (in_bounds:{position_is_in_bounds} walkable:{position_is_walkable} overlaps:{overlaps_another_entity})') if position_is_in_bounds and position_is_walkable and not overlaps_another_entity: + LOG.info(f'Moving hero to {new_player_position} (in_bounds:{position_is_in_bounds} walkable:{position_is_walkable} overlaps:{overlaps_another_entity})') entity.position = new_player_position From 5f6247ef13a5214d7deac477ab16b65de7c7d5d7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 6 May 2022 21:14:42 -0700 Subject: [PATCH 021/234] I decided: every dungeon map has rooms --- roguebasin/map.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index 89e245e..cc3929e 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -24,12 +24,15 @@ class Map: # Map tiles that the player has explored self.explored = np.full(tuple(self.size), fill_value=False, order='F') + @property + def rooms(self) -> List['Room']: + return self.generator.rooms + def random_walkable_position(self) -> Point: # TODO: Include hallways - random_room: RectangularRoom = random.choice(self.generator.rooms) - floor = random_room.floor_bounds - random_position_in_room = Point(random.randint(floor.min_x, floor.max_x), - random.randint(floor.min_y, floor.max_y)) + random_room: RectangularRoom = random.choice(self.rooms) + floor: List[Point] = list(random_room.walkable_tiles) + random_position_in_room = random.choice(floor) return random_position_in_room def tile_is_in_bounds(self, point: Point) -> bool: @@ -52,6 +55,7 @@ class Map: class MapGenerator: def __init__(self, *, size: Size): self.size = size + self.rooms: List['Room'] = [] def generate(self) -> np.ndarray: ''' @@ -212,8 +216,14 @@ class RoomsAndCorridorsGenerator(MapGenerator): def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: return Rect(Point(node.x, node.y), Size(node.width, node.height)) +class Room: + '''An abstract room. It can be any size or shape.''' -class RectangularRoom: + @property + def walkable_tiles(self) -> Iterator[Point]: + raise NotImplementedError() + +class RectangularRoom(Room): def __init__(self, bounds: Rect): self.bounds = bounds @@ -222,8 +232,11 @@ class RectangularRoom: return self.bounds.midpoint @property - def floor_bounds(self) -> Rect: - return self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) + def walkable_tiles(self) -> Rect: + floor_rect = self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) + for y in range(floor_rect.min_y, floor_rect.max_y + 1): + for x in range(floor_rect.min_x, floor_rect.max_x + 1): + yield Point(x, y) @property def walls(self) -> Iterator[Point]: From 1e69667ba86f0d59785baf268d8d9680a9cb1c26 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 6 May 2022 21:16:00 -0700 Subject: [PATCH 022/234] Create a Hero class for the character the player moves; stop using tcod.Color for foreground and background; use tuples instead --- roguebasin/object.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/roguebasin/object.py b/roguebasin/object.py index 291ed32..91f329f 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -3,17 +3,17 @@ import tcod from .geometry import Point -from typing import Optional +from typing import Optional, Tuple class Entity: '''A single-tile drawable entity with a symbol and position.''' def __init__(self, symbol: str, *, position: Optional[Point] = None, - fg: Optional[tcod.Color] = None, - bg: Optional[tcod.Color] = None): + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None): self.position = position if position else Point() - self.foreground = fg if fg else tcod.white + self.foreground = fg if fg else (255, 255, 255) self.background = bg self.symbol = symbol @@ -24,4 +24,8 @@ class Entity: return f'{self.symbol}[{self.position}]' def __repr__(self): - return f'{self.__class__.__name__}({self.symbol}, position={self.position}, fg={self.foreground}, bg={self.background})' \ No newline at end of file + return f'{self.__class__.__name__}({self.symbol}, position={self.position}, fg={self.foreground}, bg={self.background})' + +class Hero(Entity): + def __init__(self, position: Point): + super().__init__('@', position=position, fg=tuple(tcod.white)) \ No newline at end of file From 08b1841bdf024e60b1548ad2955fb3c81dbe261c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 6 May 2022 21:16:19 -0700 Subject: [PATCH 023/234] Add monsters module and define Species, Monster, Orc, and Troll --- roguebasin/monsters.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 roguebasin/monsters.py diff --git a/roguebasin/monsters.py b/roguebasin/monsters.py new file mode 100644 index 0000000..a692a50 --- /dev/null +++ b/roguebasin/monsters.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# Eryn Wells + +import tcod +from .geometry import Point +from .object import Entity +from dataclasses import dataclass +from typing import Tuple + +@dataclass(frozen=True) +class Species: + '''A kind of monster.''' + name: str + symbol: str + maximum_hit_points: int + foreground_color: Tuple[int, int, int] + background_color: Tuple[int, int, int] = None + +class Monster(Entity): + '''An instance of a Species.''' + + def __init__(self, species: Species, position: Point = None): + super().__init__(species.symbol, position=position, fg=species.foreground_color, bg=species.background_color) + self.species: Species = species + self.hit_points: int = species.maximum_hit_points + + def __str__(self) -> str: + return f'{self.symbol}[{self.species.name}][{self.position}][{self.hit_points}/{self.species.maximum_hit_points}]' + +Orc = Species(name='Orc', symbol='o', foreground_color=(63, 127, 63), maximum_hit_points=10) +Troll = Species(name='Troll', symbol='T', foreground_color=(0, 127, 0), maximum_hit_points=20) \ No newline at end of file From d99c97408c6be05825502d3129e2f85b85c7a592 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 6 May 2022 21:16:39 -0700 Subject: [PATCH 024/234] Generate orcs and trolls randomly throughout the dungeon --- roguebasin/engine.py | 48 +++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index b81eb65..4c4fbaf 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -4,11 +4,12 @@ import logging import random import tcod -from .actions import ExitAction, MovePlayerAction, RegenerateRoomsAction +from . import monsters from .events import EventHandler -from .geometry import Direction, Point, Size +from .geometry import Direction, Size from .map import Map -from .object import Entity +from .monsters import Monster +from .object import Entity, Hero from dataclasses import dataclass from typing import MutableSet @@ -25,18 +26,33 @@ class Engine: self.configuration = configuration self.rng = tcod.random.Random() - - map_size = configuration.map_size - self.map = Map(map_size) + self.map = Map(configuration.map_size) first_room = self.map.generator.rooms[0] - player_start_position = first_room.center - self.player = Entity('@', position=player_start_position, fg=tcod.white) + hero_start_position = first_room.center + self.hero = Hero(position=hero_start_position) - self.entities: MutableSet[Entity] = {self.player} - for _ in range(self.rng.randint(5, 15)): - position = self.map.random_walkable_position() - self.entities.add(Entity('@', position=position, fg=tcod.yellow)) + self.entities: MutableSet[Entity] = {self.hero} + for room in self.map.rooms: + should_spawn_monster_chance = random.random() + if should_spawn_monster_chance < 0.4: + continue + + floor = list(room.walkable_tiles) + while True: + random_start_position = random.choice(floor) + if not any(ent.position == random_start_position for ent in self.entities): + break + + for _ in range(2): + spawn_monster_chance = random.random() + if spawn_monster_chance > 0.8: + monster = Monster(monsters.Troll, position=random_start_position) + else: + monster = Monster(monsters.Orc, position=random_start_position) + + LOG.info(f'Spawning monster {monster}') + self.entities.add(monster) self.update_field_of_view() @@ -46,13 +62,13 @@ class Engine: if not action: return - action.perform(self, self.player) + action.perform(self, self.hero) directions = list(Direction.all()) - moved_entities: MutableSet[Entity] = {self.player} + moved_entities: MutableSet[Entity] = {self.hero} for ent in self.entities: - if ent == self.player: + if ent == self.hero: continue while True: @@ -78,7 +94,7 @@ class Engine: '''Compute visible area of the map based on the player's position and point of view.''' self.map.visible[:] = tcod.map.compute_fov( self.map.tiles['transparent'], - tuple(self.player.position), + tuple(self.hero.position), radius=8) # Visible tiles should be added to the explored list From ded318e659b3a5ea7ea6f5ffa74c8f37e32bed2a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 08:51:44 -0700 Subject: [PATCH 025/234] Reorganize the package - Create an __init__.py, which makes this directory a Python package. - Replace the contents of __main__.py with the contents of main.py --- roguebasin/__init__.py | 2 + roguebasin/__main__.py | 94 ++++++++++++++++++++++++++++++++++++------ roguebasin/main.py | 82 ------------------------------------ 3 files changed, 84 insertions(+), 94 deletions(-) create mode 100644 roguebasin/__init__.py delete mode 100644 roguebasin/main.py diff --git a/roguebasin/__init__.py b/roguebasin/__init__.py new file mode 100644 index 0000000..666a09c --- /dev/null +++ b/roguebasin/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python3 +# Eryn Wells diff --git a/roguebasin/__main__.py b/roguebasin/__main__.py index b02b0e9..42aa9c0 100644 --- a/roguebasin/__main__.py +++ b/roguebasin/__main__.py @@ -1,15 +1,85 @@ -#!/usr/bin/env python3 # Eryn Wells -from . import actions -from . import events -from . import geometry -from . import main -from . import map -from . import object -from . import tile +import argparse +import logging +import os.path +import sys +import tcod +from .engine import Configuration, Engine +from .events import EventHandler +from .geometry import Size -if __name__ == '__main__': - import sys - result = main.main(sys.argv) - sys.exit(0 if not result else result) \ No newline at end of file +LOG = logging.getLogger('main') + +CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50 +MAP_WIDTH, MAP_HEIGHT = 80, 45 + +FONT = 'terminal16x16_gs_ro.png' + +def parse_args(argv, *a, **kw): + parser = argparse.ArgumentParser(*a, **kw) + parser.add_argument('--debug', action='store_true', default=True) + args = parser.parse_args(argv) + return args + +def init_logging(args): + root_logger = logging.getLogger('') + root_logger.setLevel(logging.DEBUG if args.debug else logging.INFO) + + stderr_handler = logging.StreamHandler() + stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) + + root_logger.addHandler(stderr_handler) + +def find_fonts_directory(): + '''Walk up the filesystem tree from this script to find a fonts/ directory.''' + parent_dir = os.path.dirname(__file__) + while parent_dir and parent_dir != '/': + possible_fonts_dir = os.path.join(parent_dir, 'fonts') + LOG.debug('Checking for fonts dir at %s', possible_fonts_dir) + if os.path.isdir(possible_fonts_dir): + LOG.info('Found fonts dir %s', possible_fonts_dir) + break + parent_dir = os.path.dirname(parent_dir) + else: + return None + + return possible_fonts_dir + +def main(argv): + args = parse_args(argv[1:], prog=argv[0]) + + init_logging(args) + + fonts_directory = find_fonts_directory() + if not fonts_directory: + LOG.error("Couldn't find a fonts/ directory") + return -1 + + font = os.path.join(fonts_directory, FONT) + if not os.path.isfile(font): + LOG.error("Font file %s doesn't exist", font) + return -1 + + tileset = tcod.tileset.load_tilesheet(font, 16, 16, tcod.tileset.CHARMAP_CP437) + console = tcod.Console(CONSOLE_WIDTH, CONSOLE_HEIGHT, order='F') + + event_handler = EventHandler() + configuration = Configuration(map_size=Size(MAP_WIDTH, MAP_HEIGHT)) + engine = Engine(event_handler, configuration) + + with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: + while True: + console.clear() + engine.print_to_console(console) + context.present(console) + + for event in tcod.event.wait(): + engine.handle_event(event) + +def run_until_exit(): + '''Run the package's main() and call sys.exit when it finishes.''' + result = main(sys.argv) + sys.exit(0 if not result else result) + +run_until_exit() \ No newline at end of file diff --git a/roguebasin/main.py b/roguebasin/main.py deleted file mode 100644 index 872f2e8..0000000 --- a/roguebasin/main.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -# Eryn Wells - -''' -New script. -''' - -import argparse -import logging -import os.path -import random -import tcod -from .engine import Configuration, Engine -from .events import EventHandler -from .geometry import Size - -LOG = logging.getLogger('main') - -CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50 -MAP_WIDTH, MAP_HEIGHT = 80, 45 - -FONT = 'terminal16x16_gs_ro.png' - -def parse_args(argv, *a, **kw): - parser = argparse.ArgumentParser(*a, **kw) - parser.add_argument('--debug', action='store_true', default=True) - args = parser.parse_args(argv) - return args - -def init_logging(args): - root_logger = logging.getLogger('') - root_logger.setLevel(logging.DEBUG if args.debug else logging.INFO) - - stderr_handler = logging.StreamHandler() - stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) - - root_logger.addHandler(stderr_handler) - -def find_fonts_directory(): - '''Walk up the filesystem tree from this script to find a fonts/ directory.''' - parent_dir = os.path.dirname(__file__) - while parent_dir and parent_dir != '/': - possible_fonts_dir = os.path.join(parent_dir, 'fonts') - LOG.debug(f'Checking for fonts dir at {possible_fonts_dir}') - if os.path.isdir(possible_fonts_dir): - LOG.info(f'Found fonts dir: {possible_fonts_dir}') - return possible_fonts_dir - - parent_dir = os.path.dirname(parent_dir) - else: - return None - -def main(argv): - args = parse_args(argv[1:], prog=argv[0]) - - init_logging(args) - - fonts_directory = find_fonts_directory() - if not fonts_directory: - LOG.error("Couldn't find a fonts/ directory") - return -1 - - font = os.path.join(fonts_directory, FONT) - if not os.path.isfile(font): - LOG.error(f"Font file {font} doesn't exist") - return -1 - - tileset = tcod.tileset.load_tilesheet(font, 16, 16, tcod.tileset.CHARMAP_CP437) - console = tcod.Console(CONSOLE_WIDTH, CONSOLE_HEIGHT, order='F') - - event_handler = EventHandler() - configuration = Configuration(map_size=Size(MAP_WIDTH, MAP_HEIGHT)) - engine = Engine(event_handler, configuration) - - with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: - while True: - console.clear() - engine.print_to_console(console) - context.present(console) - - for event in tcod.event.wait(): - engine.handle_event(event) \ No newline at end of file From a1c16099081c38090d8fd53451f7cf98c27e8938 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 08:51:55 -0700 Subject: [PATCH 026/234] Enable linting --- .vscode/settings.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cc67606 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "python.linting.pylintEnabled": true, + "python.linting.enabled": true +} \ No newline at end of file From 16b4b640992ae3c5dd48ed59ca8fa5cd094cb943 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 08:53:58 -0700 Subject: [PATCH 027/234] Clean up the logging; use % formats instead of f-strings --- roguebasin/actions.py | 6 +++++- roguebasin/engine.py | 2 +- roguebasin/map.py | 22 ++++++++-------------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index ebcc838..b12bafd 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -42,5 +42,9 @@ class MovePlayerAction(Action): overlaps_another_entity = any(new_player_position == ent.position for ent in engine.entities if ent is not entity) if position_is_in_bounds and position_is_walkable and not overlaps_another_entity: - LOG.info(f'Moving hero to {new_player_position} (in_bounds:{position_is_in_bounds} walkable:{position_is_walkable} overlaps:{overlaps_another_entity})') + LOG.info('Moving hero to %s (in_bounds:%s walkable:%s overlaps:%s)', + new_player_position, + position_is_in_bounds, + position_is_walkable, + overlaps_another_entity) entity.position = new_player_position diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 4c4fbaf..3f6bddf 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -51,7 +51,7 @@ class Engine: else: monster = Monster(monsters.Orc, position=random_start_position) - LOG.info(f'Spawning monster {monster}') + LOG.info('Spawning monster %s', monster) self.entities.add(monster) self.update_field_of_view() diff --git a/roguebasin/map.py b/roguebasin/map.py index cc3929e..bcde3e0 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -107,8 +107,6 @@ class RoomsAndCorridorsGenerator(MapGenerator): # Generate the rooms rooms: List['RectangularRoom'] = [] - # For nicer debug logging - indent = 0 room_attrname = f'{__class__.__name__}.room' @@ -116,7 +114,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): node_bounds = self.__rect_from_bsp_node(node) if node.children: - LOG.debug(f'{" " * indent}{node_bounds}') + LOG.debug(node_bounds) left_room: RectangularRoom = getattr(node.children[0], room_attrname) right_room: RectangularRoom = getattr(node.children[1], room_attrname) @@ -124,8 +122,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): left_room_bounds = left_room.bounds right_room_bounds = right_room.bounds - LOG.debug(f'{" " * indent} left:{node.children[0]}, {left_room_bounds}') - LOG.debug(f'{" " * indent}right:{node.children[1]}, {right_room_bounds}') + LOG.debug(' left: %s, %s', node.children[0], left_room_bounds) + LOG.debug('right: %s, %s', node.children[1], right_room_bounds) start_point = left_room_bounds.midpoint end_point = right_room_bounds.midpoint @@ -136,18 +134,16 @@ class RoomsAndCorridorsGenerator(MapGenerator): else: corner = Point(start_point.x, end_point.y) - LOG.debug(f'{" " * indent}Digging tunnel between {start_point} and {end_point} with corner {corner}') - LOG.debug(f'{" " * indent}`-> start:{left_room_bounds}') - LOG.debug(f'{" " * indent}`-> end:{right_room_bounds}') + LOG.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + LOG.debug('|-> start: %s', left_room_bounds) + LOG.debug('`-> end: %s', right_room_bounds) for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): tiles[x, y] = Floor for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): tiles[x, y] = Floor - - indent += 2 else: - LOG.debug(f'{" " * indent}{node_bounds} (room) {node}') + LOG.debug('%s (room) %s', node_bounds, node) # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of @@ -160,7 +156,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) bounds = Rect(origin, size) - LOG.debug(f'{" " * indent}`-> {bounds}') + LOG.debug('`-> %s', bounds) room = RectangularRoom(bounds) setattr(node, room_attrname, room) @@ -171,8 +167,6 @@ class RoomsAndCorridorsGenerator(MapGenerator): elif random.random() < 0.5: setattr(node.parent, room_attrname, room) - indent -= 2 - # Pass up a random child room so that parent nodes can connect subtrees to each other. parent = node.parent if parent: From f3d5e273db5676697d8f62f1b0db8141adda9813 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 08:54:46 -0700 Subject: [PATCH 028/234] Remove this unimplemented method --- roguebasin/map.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index bcde3e0..8ed4f0a 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -204,9 +204,6 @@ class RoomsAndCorridorsGenerator(MapGenerator): return tiles - def generate_tunnel(self, start_room_bounds: Rect, end_room_bounds: Rect): - pass - def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: return Rect(Point(node.x, node.y), Size(node.width, node.height)) From 7720bc525a02495fab20fd9fe5fa78eb06dd22fe Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 08:55:10 -0700 Subject: [PATCH 029/234] Address some linter issues; add doc strings - Clean up some import ordering - Write some Numpy style doc strings for classes and functions --- roguebasin/actions.py | 46 +++++++++++++++++++++++++++++++++++-------- roguebasin/engine.py | 9 +++++++-- roguebasin/map.py | 11 +++++++++++ 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index b12bafd..ff59daa 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -3,7 +3,7 @@ import logging from .geometry import Direction -from typing import TYPE_CHECKING +from typing import Optional, TYPE_CHECKING if TYPE_CHECKING: from .engine import Engine @@ -11,11 +11,41 @@ if TYPE_CHECKING: LOG = logging.getLogger('events') +class ActionResult: + '''An object that represents the result of an Action. + + + Attributes + ---------- + success : bool + True if the action succeeded + done : bool + True if the action is complete, and no follow-up action is needed. + alternate : Action, optional + An alternate action to perform if this action failed + ''' + + def __init__(self, success: bool, done: bool = True, alternate: Optional['Action'] = None): + self.success = success + self.done = done + self.alternate = alternate + class Action: - def perform(self, engine: 'Engine', entity: 'Entity') -> None: - ''' - Perform this action. This is an abstract method that all subclasses - should implement. + def perform(self, engine: 'Engine', entity: 'Entity') -> ActionResult: + '''Perform this action. + + Parameters + ---------- + engine : Engine + The game engine + entity : Entity + The entity that this action is being performed on + + Returns + ------- + ActionResult + A result object reflecting how the action was handled, and what follow-up actions, if any, are needed to + complete the action. ''' raise NotImplementedError() @@ -23,12 +53,12 @@ class Action: return f'{self.__class__.__name__}()' class ExitAction(Action): - def perform(self, engine: 'Engine', entity: 'Entity') -> None: + def perform(self, engine: 'Engine', entity: 'Entity') -> ActionResult: raise SystemExit() class RegenerateRoomsAction(Action): - def perform(self, engine: 'Engine', entity: 'Entity') -> None: - ... + def perform(self, engine: 'Engine', entity: 'Entity') -> ActionResult: + return ActionResult(True) class MovePlayerAction(Action): def __init__(self, direction: Direction): diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 3f6bddf..136a08b 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -1,17 +1,21 @@ #!/usr/bin/env python3 # Eryn Wells +'''Defines the core game engine.''' + import logging import random +from dataclasses import dataclass +from typing import MutableSet + import tcod + from . import monsters from .events import EventHandler from .geometry import Direction, Size from .map import Map from .monsters import Monster from .object import Entity, Hero -from dataclasses import dataclass -from typing import MutableSet LOG = logging.getLogger('engine') EVENT_LOG = logging.getLogger('events') @@ -82,6 +86,7 @@ class Engine: self.update_field_of_view() def print_to_console(self, console): + '''Print the whole game to the given console.''' self.map.print_to_console(console) for ent in self.entities: diff --git a/roguebasin/map.py b/roguebasin/map.py index 8ed4f0a..260b683 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -205,6 +205,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): return tiles def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: + '''Create a Rect from the given BSP node object''' return Rect(Point(node.x, node.y), Size(node.width, node.height)) class Room: @@ -215,11 +216,21 @@ class Room: raise NotImplementedError() class RectangularRoom(Room): + '''A rectangular room defined by a Rect. + + Attributes + ---------- + bounds : Rect + A rectangle that defines the room. This rectangle includes the tiles used for the walls, so the floor is 1 tile + inset from the bounds. + ''' + def __init__(self, bounds: Rect): self.bounds = bounds @property def center(self) -> Point: + '''The center of the room, truncated according to integer math rules''' return self.bounds.midpoint @property From 57bbb2c3fc26dd6a6c1dd47900e897e5332ba406 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 09:57:39 -0700 Subject: [PATCH 030/234] Some more fixes from the linter --- roguebasin/__main__.py | 2 +- roguebasin/actions.py | 3 +-- roguebasin/map.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/roguebasin/__main__.py b/roguebasin/__main__.py index 42aa9c0..e745f9b 100644 --- a/roguebasin/__main__.py +++ b/roguebasin/__main__.py @@ -82,4 +82,4 @@ def run_until_exit(): result = main(sys.argv) sys.exit(0 if not result else result) -run_until_exit() \ No newline at end of file +run_until_exit() diff --git a/roguebasin/actions.py b/roguebasin/actions.py index ff59daa..6b20174 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -2,8 +2,8 @@ # Eryn Wells import logging -from .geometry import Direction from typing import Optional, TYPE_CHECKING +from .geometry import Direction if TYPE_CHECKING: from .engine import Engine @@ -14,7 +14,6 @@ LOG = logging.getLogger('events') class ActionResult: '''An object that represents the result of an Action. - Attributes ---------- success : bool diff --git a/roguebasin/map.py b/roguebasin/map.py index 260b683..644761c 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -253,4 +253,4 @@ class RectangularRoom(Room): yield Point(x, y) def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.bounds})' \ No newline at end of file + return f'{self.__class__.__name__}({self.bounds})' From 4002b64640882b760c220c906e19b010c4175384 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:16:17 -0700 Subject: [PATCH 031/234] Attack!!! Refactor MovePlayerAction into a few different Action subclasses. Move direction to a parent MoveAction, and create three new subclasses of MoveAction: - BumpAction: perform the test that an action can be performed in the given direction - WalkAction, take a step in the given direction - MeleeAction, attack another Entity in the given direction Add an ActionResult class that communicates the result of performing an Action. - ActionResult.succeeded indicates whether the action succeeded. - ActionResult.done indicates if the action is fully complete or requires followup. - ActionResult.alternate specifies the follow-up action to perform. Convert all the key handling actions to BumpActions. In the Engine's event handler method, loop until an action is completed, performing specified alternate actions until the result object indicates the action is done. --- roguebasin/actions.py | 147 ++++++++++++++++++++++++++++++++++------- roguebasin/engine.py | 21 +++++- roguebasin/events.py | 18 ++--- roguebasin/geometry.py | 3 +- 4 files changed, 153 insertions(+), 36 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 6b20174..0990475 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -1,36 +1,75 @@ #!/usr/bin/env python3 # Eryn Wells +'''This module defines all of the actions that can be performed by the game. These actions can come from the player +(e.g. via keyboard input), or from non-player entities (e.g. AI deciboard input), or from non-player entities (e.g. AI +decisions). + +Class Hierarchy +--------------- + +Action : Base class of all actions + MoveAction : Base class for all actions that are performed with a direction + WalkAction + MeleeAction + ExitAction +''' + import logging from typing import Optional, TYPE_CHECKING from .geometry import Direction +from .object import Entity if TYPE_CHECKING: from .engine import Engine - from .object import Entity LOG = logging.getLogger('events') class ActionResult: - '''An object that represents the result of an Action. + '''The result of an Action. + + `Action.perform()` returns an instance of this class to inform the caller of the result Attributes ---------- - success : bool + action : Action + The Action that was performed + success : bool, optional True if the action succeeded - done : bool - True if the action is complete, and no follow-up action is needed. + done : bool, optional + True if the action is complete, and no follow-up action is needed alternate : Action, optional An alternate action to perform if this action failed ''' - def __init__(self, success: bool, done: bool = True, alternate: Optional['Action'] = None): - self.success = success - self.done = done + def __init__(self, action: 'Action', *, + success: Optional[bool] = None, + done: Optional[bool] = None, + alternate: Optional['Action'] = None): + self.action = action self.alternate = alternate + if success is not None: + self.success = success + elif alternate: + self.success = False + else: + self.success = True + + if done is not None: + self.done = done + elif self.success: + self.done = True + else: + self.done = not alternate + + def __repr__(self): + return f'{self.__class__.__name__}({self.action}, success={self.success}, done={self.done}, alternate={self.alternate})' + class Action: - def perform(self, engine: 'Engine', entity: 'Entity') -> ActionResult: + '''An action that an Entity should perform.''' + + def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: '''Perform this action. Parameters @@ -52,28 +91,86 @@ class Action: return f'{self.__class__.__name__}()' class ExitAction(Action): - def perform(self, engine: 'Engine', entity: 'Entity') -> ActionResult: + '''Exit the game.''' + + def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: raise SystemExit() class RegenerateRoomsAction(Action): - def perform(self, engine: 'Engine', entity: 'Entity') -> ActionResult: - return ActionResult(True) + '''Regenerate the dungeon map''' + + def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + return ActionResult(self, success=False) + +# pylint: disable=abstract-method +class MoveAction(Action): + '''An abstract Action that requires a direction to complete.''' -class MovePlayerAction(Action): def __init__(self, direction: Direction): + super().__init__() self.direction = direction - def perform(self, engine: 'Engine', entity: 'Entity') -> None: - new_player_position = entity.position + self.direction + def __repr__(self): + return f'{self.__class__.__name__}({self.direction})' - position_is_in_bounds = engine.map.tile_is_in_bounds(new_player_position) - position_is_walkable = engine.map.tile_is_walkable(new_player_position) - overlaps_another_entity = any(new_player_position == ent.position for ent in engine.entities if ent is not entity) +class BumpAction(MoveAction): + '''Attempt to perform a movement action in a direction. - if position_is_in_bounds and position_is_walkable and not overlaps_another_entity: - LOG.info('Moving hero to %s (in_bounds:%s walkable:%s overlaps:%s)', - new_player_position, - position_is_in_bounds, - position_is_walkable, - overlaps_another_entity) - entity.position = new_player_position + This action tests if an action in the direction is possible and returns the action that can be completed. + + Attributes + ---------- + direction : Direction + The direction to test + ''' + + def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + new_position = entity.position + self.direction + + position_is_in_bounds = engine.map.tile_is_in_bounds(new_position) + position_is_walkable = engine.map.tile_is_walkable(new_position) + + for ent in engine.entities: + if new_position != ent.position: + continue + entity_occupying_position = ent + break + else: + entity_occupying_position = None + + LOG.info('Bumping %s (in_bounds:%s walkable:%s overlaps:%s)', + new_position, + position_is_in_bounds, + position_is_walkable, + entity_occupying_position) + + if not position_is_in_bounds or not position_is_walkable: + return ActionResult(self, success=False) + + if entity_occupying_position: + return ActionResult(self, alternate=MeleeAction(self.direction, entity_occupying_position)) + + return ActionResult(self, alternate=WalkAction(self.direction)) + + +class WalkAction(MoveAction): + '''Walk one step in the given direction.''' + + def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + new_position = entity.position + self.direction + + LOG.info('Moving %s to %s', entity, new_position) + entity.position = new_position + + return ActionResult(self, success=True) + +class MeleeAction(MoveAction): + '''Perform a melee attack on another entity''' + + def __init__(self, direction: Direction, target: Entity): + super().__init__(direction) + self.target = target + + def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + LOG.info('Attack! %s', self.target) + return ActionResult(self, success=True) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 136a08b..22dd6c4 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -66,7 +66,26 @@ class Engine: if not action: return - action.perform(self, self.hero) + result = action.perform(self, self.hero) + LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) + + while not result.done: + alternate = result.alternate + assert alternate is not None, f'Action {result.action} incomplete but no alternate action given' + + result = alternate.perform(self, self.hero) + LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) + + if result.success: + LOG.info('Action succeded!') + break + + if result.done: + LOG.info('Action failed!') + break + + if not result.success and result.done: + return directions = list(Direction.all()) moved_entities: MutableSet[Entity] = {self.hero} diff --git a/roguebasin/events.py b/roguebasin/events.py index 2ca754d..7d837b4 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -2,7 +2,7 @@ # Eryn Wells import tcod -from .actions import Action, ExitAction, MovePlayerAction, RegenerateRoomsAction +from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction from .geometry import Direction from typing import Optional @@ -16,21 +16,21 @@ class EventHandler(tcod.event.EventDispatch[Action]): sym = event.sym if sym == tcod.event.KeySym.b: - action = MovePlayerAction(Direction.SouthWest) + action = BumpAction(Direction.SouthWest) elif sym == tcod.event.KeySym.h: - action = MovePlayerAction(Direction.West) + action = BumpAction(Direction.West) elif sym == tcod.event.KeySym.j: - action = MovePlayerAction(Direction.South) + action = BumpAction(Direction.South) elif sym == tcod.event.KeySym.k: - action = MovePlayerAction(Direction.North) + action = BumpAction(Direction.North) elif sym == tcod.event.KeySym.l: - action = MovePlayerAction(Direction.East) + action = BumpAction(Direction.East) elif sym == tcod.event.KeySym.n: - action = MovePlayerAction(Direction.SouthEast) + action = BumpAction(Direction.SouthEast) elif sym == tcod.event.KeySym.u: - action = MovePlayerAction(Direction.NorthEast) + action = BumpAction(Direction.NorthEast) elif sym == tcod.event.KeySym.y: - action = MovePlayerAction(Direction.NorthWest) + action = BumpAction(Direction.NorthWest) elif sym == tcod.event.KeySym.SPACE: action = RegenerateRoomsAction() diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index 203cc1f..988f675 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -35,7 +35,7 @@ class Vector: yield self.dy def __str__(self): - return f'(δx:{self.x}, δy:{self.y})' + return f'(δx:{self.dx}, δy:{self.dy})' class Direction: North = Vector(0, -1) @@ -49,6 +49,7 @@ class Direction: @classmethod def all(cls) -> Iterator['Direction']: + '''Iterate through all directions, starting with North and proceeding clockwise''' yield Direction.North yield Direction.NorthEast yield Direction.East From 3510bab79a81f455f7392a380064bee2cdfd5afd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:20:41 -0700 Subject: [PATCH 032/234] Document return value of MapGenerator.generate --- roguebasin/map.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/roguebasin/map.py b/roguebasin/map.py index 644761c..a9485c6 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -63,6 +63,11 @@ class MapGenerator: Subclasses should implement this and fill in their specific map generation algorithm. + + Returns + ------- + np.ndarray + A two-dimensional array of tiles. Dimensions should match the given size. ''' raise NotImplementedError() From 372cd5f295792f67435663e9ac58c785662982de Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:20:57 -0700 Subject: [PATCH 033/234] Generate a .pylintrc and tweak it slightly for this project --- .pylintrc | 589 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 589 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..b0ee38a --- /dev/null +++ b/.pylintrc @@ -0,0 +1,589 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold to be exceeded before program exits with error. +fail-under=10.0 + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the ignore-list. The +# regex matches against paths and can be in Posix or Windows format. +ignore-paths= + +# Files or directories matching the regex patterns are skipped. The regex +# matches against base names, not paths. The default value ignores emacs file +# locks +ignore-patterns=^\.# + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the 'python-enchant' package. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear and the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +#notes-rgx= + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# class is considered mixin if its name matches the mixin-class-rgx option. +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins ignore-mixin- +# members is set to 'yes' +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=200 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=no + +# Signatures are removed from the similarity computation +ignore-signatures=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=dx, + dy, + i, + j, + k, + x, + y, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods=.*Action,ActionResult + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception From d0a2e2c2efc3f760ebb7e5dabad9ed13a7a30fd5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:22:54 -0700 Subject: [PATCH 034/234] Clean up imports and terminal newlines in files according to pylint --- roguebasin/events.py | 4 +++- roguebasin/geometry.py | 2 +- roguebasin/map.py | 10 ++++++---- roguebasin/object.py | 8 +++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 7d837b4..1cd5077 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # Eryn Wells +from typing import Optional + import tcod + from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction from .geometry import Direction -from typing import Optional class EventHandler(tcod.event.EventDispatch[Action]): def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index 988f675..83b8c81 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -124,4 +124,4 @@ class Rect: yield tuple(self.size) def __str__(self): - return f'[{self.origin}, {self.size}]' \ No newline at end of file + return f'[{self.origin}, {self.size}]' diff --git a/roguebasin/map.py b/roguebasin/map.py index a9485c6..c66ac63 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -2,14 +2,16 @@ # Eryn Wells import logging -import numpy as np import random -import tcod -from .geometry import Direction, Point, Rect, Size -from .tile import Empty, Floor, Shroud, Wall from dataclasses import dataclass from typing import Iterator, List, Optional +import numpy as np +import tcod + +from .geometry import Direction, Point, Rect, Size +from .tile import Empty, Floor, Shroud, Wall + LOG = logging.getLogger('map') class Map: diff --git a/roguebasin/object.py b/roguebasin/object.py index 91f329f..c67343a 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 # Eryn Wells -import tcod -from .geometry import Point from typing import Optional, Tuple +import tcod + +from .geometry import Point + class Entity: '''A single-tile drawable entity with a symbol and position.''' @@ -28,4 +30,4 @@ class Entity: class Hero(Entity): def __init__(self, position: Point): - super().__init__('@', position=position, fg=tuple(tcod.white)) \ No newline at end of file + super().__init__('@', position=position, fg=tuple(tcod.white)) From 04aa61fe4b0825d5cc31e82e9f99fb67d6212e80 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:42:48 -0700 Subject: [PATCH 035/234] Doc strings and import reordering per pylint --- roguebasin/engine.py | 21 +++++++++++++++++++++ roguebasin/monsters.py | 24 +++++++++++++++++++----- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 22dd6c4..cfa0b33 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -25,6 +25,26 @@ class Configuration: map_size: Size class Engine: + '''The main game engine. + + This class provides the event handling, map drawing, and maintains the list of entities. + + Attributes + ---------- + configuration : Configuration + Defines the basic configuration for the game + entities : MutableSet[Entity] + A set of all the entities on the current map, including the Hero + event_handler : EventHandler + An event handler object that can handle events from `tcod` + hero : Hero + The hero, the Entity controlled by the player + map : Map + A map of the current level + rng : tcod.random.Random + A random number generator + ''' + def __init__(self, event_handler: EventHandler, configuration: Configuration): self.event_handler = event_handler self.configuration = configuration @@ -61,6 +81,7 @@ class Engine: self.update_field_of_view() def handle_event(self, event: tcod.event.Event): + '''Handle the specified event. Transform that event into an Action via an EventHandler and perform it.''' action = self.event_handler.dispatch(event) if not action: diff --git a/roguebasin/monsters.py b/roguebasin/monsters.py index a692a50..a9866a3 100644 --- a/roguebasin/monsters.py +++ b/roguebasin/monsters.py @@ -1,15 +1,29 @@ -#!/usr/bin/env python3 # Eryn Wells -import tcod -from .geometry import Point -from .object import Entity from dataclasses import dataclass from typing import Tuple +from .geometry import Point +from .object import Entity + @dataclass(frozen=True) class Species: - '''A kind of monster.''' + '''A kind of monster. + + Attributes + ---------- + name : str + A friendly, user-visiable name for the monster + symbol : str + The symbol used to render the monster on the map + maximum_hit_points : int + The maximum number of hit points the monster can be spawned with + foreground_color : Tuple[int, int, int] + The foreground color used to render the monster on the map + background_color : Tuple[int, int, int], optional + The background color used to render the monster on the map; if none is given, the tile color specified by the + map will be used. + ''' name: str symbol: str maximum_hit_points: int From 54568d70c264338939cb574e189f3e7e3910d71d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:43:02 -0700 Subject: [PATCH 036/234] Add a sight_radius parameter to the Species dataclass --- roguebasin/monsters.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/roguebasin/monsters.py b/roguebasin/monsters.py index a9866a3..115cd95 100644 --- a/roguebasin/monsters.py +++ b/roguebasin/monsters.py @@ -18,6 +18,8 @@ class Species: The symbol used to render the monster on the map maximum_hit_points : int The maximum number of hit points the monster can be spawned with + sight_radius : int + The number of tiles this monster can see foreground_color : Tuple[int, int, int] The foreground color used to render the monster on the map background_color : Tuple[int, int, int], optional @@ -27,6 +29,7 @@ class Species: name: str symbol: str maximum_hit_points: int + sight_radius: int foreground_color: Tuple[int, int, int] background_color: Tuple[int, int, int] = None @@ -41,5 +44,5 @@ class Monster(Entity): def __str__(self) -> str: return f'{self.symbol}[{self.species.name}][{self.position}][{self.hit_points}/{self.species.maximum_hit_points}]' -Orc = Species(name='Orc', symbol='o', foreground_color=(63, 127, 63), maximum_hit_points=10) -Troll = Species(name='Troll', symbol='T', foreground_color=(0, 127, 0), maximum_hit_points=20) \ No newline at end of file +Orc = Species(name='Orc', symbol='o', foreground_color=(63, 127, 63), maximum_hit_points=10, sight_radius=4) +Troll = Species(name='Troll', symbol='T', foreground_color=(0, 127, 0), maximum_hit_points=20, sight_radius=4) \ No newline at end of file From 7b747fb4d3fc5460e4a4f897da094ceab96dc737 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:56:55 -0700 Subject: [PATCH 037/234] Doc comments and stuff --- roguebasin/__init__.py | 1 - roguebasin/__main__.py | 5 ++++- roguebasin/events.py | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/roguebasin/__init__.py b/roguebasin/__init__.py index 666a09c..12ba5c3 100644 --- a/roguebasin/__init__.py +++ b/roguebasin/__init__.py @@ -1,2 +1 @@ -#!/usr/bin/env python3 # Eryn Wells diff --git a/roguebasin/__main__.py b/roguebasin/__main__.py index e745f9b..eeb6912 100644 --- a/roguebasin/__main__.py +++ b/roguebasin/__main__.py @@ -78,7 +78,10 @@ def main(argv): engine.handle_event(event) def run_until_exit(): - '''Run the package's main() and call sys.exit when it finishes.''' + ''' + Run main() and call sys.exit when it finishes. In practice, this function will never return. The game engine has + other mechanisms for exiting. + ''' result = main(sys.argv) sys.exit(0 if not result else result) diff --git a/roguebasin/events.py b/roguebasin/events.py index 1cd5077..49ecc1f 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -1,6 +1,7 @@ -#!/usr/bin/env python3 # Eryn Wells +'''Defines event handling mechanisms.''' + from typing import Optional import tcod @@ -9,6 +10,8 @@ from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction from .geometry import Direction class EventHandler(tcod.event.EventDispatch[Action]): + '''Handler of `tcod` events''' + def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: return ExitAction() From 15e188b9f27c488ac121fb85d9509df540720183 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 11:57:08 -0700 Subject: [PATCH 038/234] Convert the keysym matching to match/case from if/elif --- roguebasin/events.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 49ecc1f..d729567 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -19,24 +19,24 @@ class EventHandler(tcod.event.EventDispatch[Action]): action: Optional[Action] = None sym = event.sym - - if sym == tcod.event.KeySym.b: - action = BumpAction(Direction.SouthWest) - elif sym == tcod.event.KeySym.h: - action = BumpAction(Direction.West) - elif sym == tcod.event.KeySym.j: - action = BumpAction(Direction.South) - elif sym == tcod.event.KeySym.k: - action = BumpAction(Direction.North) - elif sym == tcod.event.KeySym.l: - action = BumpAction(Direction.East) - elif sym == tcod.event.KeySym.n: - action = BumpAction(Direction.SouthEast) - elif sym == tcod.event.KeySym.u: - action = BumpAction(Direction.NorthEast) - elif sym == tcod.event.KeySym.y: - action = BumpAction(Direction.NorthWest) - elif sym == tcod.event.KeySym.SPACE: - action = RegenerateRoomsAction() + match sym: + case tcod.event.KeySym.b: + action = BumpAction(Direction.SouthWest) + case tcod.event.KeySym.h: + action = BumpAction(Direction.West) + case tcod.event.KeySym.j: + action = BumpAction(Direction.South) + case tcod.event.KeySym.k: + action = BumpAction(Direction.North) + case tcod.event.KeySym.l: + action = BumpAction(Direction.East) + case tcod.event.KeySym.n: + action = BumpAction(Direction.SouthEast) + case tcod.event.KeySym.u: + action = BumpAction(Direction.NorthEast) + case tcod.event.KeySym.y: + action = BumpAction(Direction.NorthWest) + case tcod.event.KeySym.SPACE: + action = RegenerateRoomsAction() return action From d75c9faea31659bf7b3f986700ce2f018f25038d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 12:25:44 -0700 Subject: [PATCH 039/234] Little bits of cleanup --- roguebasin/actions.py | 14 ++++++++------ roguebasin/engine.py | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 0990475..3b03c8f 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # Eryn Wells -'''This module defines all of the actions that can be performed by the game. These actions can come from the player -(e.g. via keyboard input), or from non-player entities (e.g. AI deciboard input), or from non-player entities (e.g. AI +''' +This module defines all of the actions that can be performed by the game. These actions can come from the player (e.g. +via keyboard input), or from non-player entities (e.g. AI deciboard input), or from non-player entities (e.g. AI decisions). Class Hierarchy @@ -10,6 +11,7 @@ Class Hierarchy Action : Base class of all actions MoveAction : Base class for all actions that are performed with a direction + BumpAction WalkAction MeleeAction ExitAction @@ -23,7 +25,7 @@ from .object import Entity if TYPE_CHECKING: from .engine import Engine -LOG = logging.getLogger('events') +LOG = logging.getLogger(__name__) class ActionResult: '''The result of an Action. @@ -64,7 +66,7 @@ class ActionResult: self.done = not alternate def __repr__(self): - return f'{self.__class__.__name__}({self.action}, success={self.success}, done={self.done}, alternate={self.alternate})' + return f'{self.__class__.__name__}({self.action!r}, success={self.success}, done={self.done}, alternate={self.alternate!r})' class Action: '''An action that an Entity should perform.''' @@ -88,7 +90,7 @@ class Action: raise NotImplementedError() def __repr__(self): - return f'{self.__class__.__name__}()' + return f'{self.__class__.__name__}({self.entity!r})' class ExitAction(Action): '''Exit the game.''' @@ -111,7 +113,7 @@ class MoveAction(Action): self.direction = direction def __repr__(self): - return f'{self.__class__.__name__}({self.direction})' + return f'{self.__class__.__name__}({self.entity!r}, {self.direction!r})' class BumpAction(MoveAction): '''Attempt to perform a movement action in a direction. diff --git a/roguebasin/engine.py b/roguebasin/engine.py index cfa0b33..a253cfb 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -18,7 +18,6 @@ from .monsters import Monster from .object import Entity, Hero LOG = logging.getLogger('engine') -EVENT_LOG = logging.getLogger('events') @dataclass class Configuration: From 8b3c0137a5100c5d35c46e9c1cad343655494848 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 12:25:46 -0700 Subject: [PATCH 040/234] Refactor event handling into EventHandler Move all the event handling code from Engine to EventHandler. EventHandler has a reference to Engine and can deal with entities from its methods. Refactor Action to take an optional Entity in its initializer. Some actions don't require an Entity, but many do/will. --- roguebasin/__main__.py | 7 ++-- roguebasin/actions.py | 40 +++++++++--------- roguebasin/engine.py | 50 +---------------------- roguebasin/events.py | 92 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 107 insertions(+), 82 deletions(-) diff --git a/roguebasin/__main__.py b/roguebasin/__main__.py index eeb6912..8232c2a 100644 --- a/roguebasin/__main__.py +++ b/roguebasin/__main__.py @@ -64,9 +64,9 @@ def main(argv): tileset = tcod.tileset.load_tilesheet(font, 16, 16, tcod.tileset.CHARMAP_CP437) console = tcod.Console(CONSOLE_WIDTH, CONSOLE_HEIGHT, order='F') - event_handler = EventHandler() configuration = Configuration(map_size=Size(MAP_WIDTH, MAP_HEIGHT)) - engine = Engine(event_handler, configuration) + engine = Engine(configuration) + event_handler = EventHandler(engine) with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: while True: @@ -74,8 +74,7 @@ def main(argv): engine.print_to_console(console) context.present(console) - for event in tcod.event.wait(): - engine.handle_event(event) + event_handler.wait_for_events() def run_until_exit(): ''' diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 3b03c8f..5e849fa 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -71,15 +71,16 @@ class ActionResult: class Action: '''An action that an Entity should perform.''' - def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + def __init__(self, entity: Optional[Entity] = None): + self.entity = entity + + def perform(self, engine: 'Engine') -> ActionResult: '''Perform this action. Parameters ---------- engine : Engine The game engine - entity : Entity - The entity that this action is being performed on Returns ------- @@ -95,21 +96,21 @@ class Action: class ExitAction(Action): '''Exit the game.''' - def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + def perform(self, engine: 'Engine') -> ActionResult: raise SystemExit() class RegenerateRoomsAction(Action): '''Regenerate the dungeon map''' - def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + def perform(self, engine: 'Engine') -> ActionResult: return ActionResult(self, success=False) # pylint: disable=abstract-method class MoveAction(Action): '''An abstract Action that requires a direction to complete.''' - def __init__(self, direction: Direction): - super().__init__() + def __init__(self, entity: Entity, direction: Direction): + super().__init__(entity) self.direction = direction def __repr__(self): @@ -126,8 +127,8 @@ class BumpAction(MoveAction): The direction to test ''' - def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: - new_position = entity.position + self.direction + def perform(self, engine: 'Engine') -> ActionResult: + new_position = self.entity.position + self.direction position_is_in_bounds = engine.map.tile_is_in_bounds(new_position) position_is_walkable = engine.map.tile_is_walkable(new_position) @@ -140,7 +141,8 @@ class BumpAction(MoveAction): else: entity_occupying_position = None - LOG.info('Bumping %s (in_bounds:%s walkable:%s overlaps:%s)', + LOG.info('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', + self.entity, new_position, position_is_in_bounds, position_is_walkable, @@ -150,29 +152,29 @@ class BumpAction(MoveAction): return ActionResult(self, success=False) if entity_occupying_position: - return ActionResult(self, alternate=MeleeAction(self.direction, entity_occupying_position)) + return ActionResult(self, alternate=MeleeAction(self.entity, self.direction, entity_occupying_position)) - return ActionResult(self, alternate=WalkAction(self.direction)) + return ActionResult(self, alternate=WalkAction(self.entity, self.direction)) class WalkAction(MoveAction): '''Walk one step in the given direction.''' - def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: - new_position = entity.position + self.direction + def perform(self, engine: 'Engine') -> ActionResult: + new_position = self.entity.position + self.direction - LOG.info('Moving %s to %s', entity, new_position) - entity.position = new_position + LOG.info('Moving %s to %s', self.entity, new_position) + self.entity.position = new_position return ActionResult(self, success=True) class MeleeAction(MoveAction): '''Perform a melee attack on another entity''' - def __init__(self, direction: Direction, target: Entity): - super().__init__(direction) + def __init__(self, entity: Entity, direction: Direction, target: Entity): + super().__init__(entity, direction) self.target = target - def perform(self, engine: 'Engine', entity: Entity) -> ActionResult: + def perform(self, engine: 'Engine') -> ActionResult: LOG.info('Attack! %s', self.target) return ActionResult(self, success=True) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index a253cfb..9c9c714 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -34,8 +34,6 @@ class Engine: Defines the basic configuration for the game entities : MutableSet[Entity] A set of all the entities on the current map, including the Hero - event_handler : EventHandler - An event handler object that can handle events from `tcod` hero : Hero The hero, the Entity controlled by the player map : Map @@ -44,8 +42,7 @@ class Engine: A random number generator ''' - def __init__(self, event_handler: EventHandler, configuration: Configuration): - self.event_handler = event_handler + def __init__(self, configuration: Configuration): self.configuration = configuration self.rng = tcod.random.Random() @@ -79,51 +76,6 @@ class Engine: self.update_field_of_view() - def handle_event(self, event: tcod.event.Event): - '''Handle the specified event. Transform that event into an Action via an EventHandler and perform it.''' - action = self.event_handler.dispatch(event) - - if not action: - return - - result = action.perform(self, self.hero) - LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) - - while not result.done: - alternate = result.alternate - assert alternate is not None, f'Action {result.action} incomplete but no alternate action given' - - result = alternate.perform(self, self.hero) - LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) - - if result.success: - LOG.info('Action succeded!') - break - - if result.done: - LOG.info('Action failed!') - break - - if not result.success and result.done: - return - - directions = list(Direction.all()) - moved_entities: MutableSet[Entity] = {self.hero} - - for ent in self.entities: - if ent == self.hero: - continue - - while True: - new_position = ent.position + random.choice(directions) - overlaps_with_previously_moved_entity = any(new_position == moved_ent.position for moved_ent in moved_entities) - if not overlaps_with_previously_moved_entity and self.map.tile_is_walkable(new_position): - ent.position = new_position - moved_entities.add(ent) - break - - self.update_field_of_view() - def print_to_console(self, console): '''Print the whole game to the given console.''' self.map.print_to_console(console) diff --git a/roguebasin/events.py b/roguebasin/events.py index d729567..55ac392 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -2,40 +2,112 @@ '''Defines event handling mechanisms.''' -from typing import Optional +import logging +import random +from typing import MutableSet, Optional, TYPE_CHECKING import tcod -from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction +from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction from .geometry import Direction +from .object import Entity + +if TYPE_CHECKING: + from .engine import Engine + +LOG = logging.getLogger('events') class EventHandler(tcod.event.EventDispatch[Action]): '''Handler of `tcod` events''' + def __init__(self, engine: 'Engine'): + super().__init__() + self.engine = engine + + def wait_for_events(self): + '''Wait for events and handle them.''' + for event in tcod.event.wait(): + self.handle_event(event) + + def handle_event(self, event: tcod.event.Event) -> None: + '''Handle the given event. Transform that event into an Action via an EventHandler and perform it.''' + action = self.dispatch(event) + + if not action: + return + + result = self.perform_action_until_done(action) + + # Action failed, so do nothing further. + if not result.success and result.done: + return + + directions = list(Direction.all()) + + hero = self.engine.hero + moved_entities: MutableSet[Entity] = {self.engine.hero} + + for ent in self.engine.entities: + if ent == hero: + continue + + while True: + new_position = ent.position + random.choice(directions) + overlaps_with_previously_moved_entity = any(new_position == moved_ent.position for moved_ent in moved_entities) + tile_is_walkable = self.engine.map.tile_is_walkable(new_position) + if not overlaps_with_previously_moved_entity and tile_is_walkable: + ent.position = new_position + moved_entities.add(ent) + break + + self.engine.update_field_of_view() + + def perform_action_until_done(self, action: Action) -> ActionResult: + '''Perform the given action and any alternate follow-up actions until the action chain is done.''' + result = action.perform(self.engine) + LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) + + while not result.done: + alternate = result.alternate + assert alternate is not None, f'Action {result.action} incomplete but no alternate action given' + + result = alternate.perform(self.engine) + LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) + + if result.success: + LOG.info('Action succeded!') + break + else: + LOG.info('Action failed!') + + return result + def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: return ExitAction() def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: action: Optional[Action] = None + hero = self.engine.hero + sym = event.sym match sym: case tcod.event.KeySym.b: - action = BumpAction(Direction.SouthWest) + action = BumpAction(hero, Direction.SouthWest) case tcod.event.KeySym.h: - action = BumpAction(Direction.West) + action = BumpAction(hero, Direction.West) case tcod.event.KeySym.j: - action = BumpAction(Direction.South) + action = BumpAction(hero, Direction.South) case tcod.event.KeySym.k: - action = BumpAction(Direction.North) + action = BumpAction(hero, Direction.North) case tcod.event.KeySym.l: - action = BumpAction(Direction.East) + action = BumpAction(hero, Direction.East) case tcod.event.KeySym.n: - action = BumpAction(Direction.SouthEast) + action = BumpAction(hero, Direction.SouthEast) case tcod.event.KeySym.u: - action = BumpAction(Direction.NorthEast) + action = BumpAction(hero, Direction.NorthEast) case tcod.event.KeySym.y: - action = BumpAction(Direction.NorthWest) + action = BumpAction(hero, Direction.NorthWest) case tcod.event.KeySym.SPACE: action = RegenerateRoomsAction() From 427e7c8e84aad9a6245fc9c7cf2a5ba5225c50c5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 12:28:02 -0700 Subject: [PATCH 041/234] Remove some unused imports --- roguebasin/engine.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 9c9c714..5188e9a 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Eryn Wells '''Defines the core game engine.''' @@ -11,8 +10,7 @@ from typing import MutableSet import tcod from . import monsters -from .events import EventHandler -from .geometry import Direction, Size +from .geometry import Size from .map import Map from .monsters import Monster from .object import Entity, Hero From eea49ed3c100991b41dee66b4a10bbde66b5bf7f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 12:37:35 -0700 Subject: [PATCH 042/234] Resolve all the pylint warnings in geometry --- .pylintrc | 4 +++- roguebasin/geometry.py | 34 +++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/.pylintrc b/.pylintrc index b0ee38a..b7b7c7e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -544,7 +544,9 @@ preferred-modules= # List of regular expressions of class ancestor names to ignore when counting # public methods (see R0903) -exclude-too-few-public-methods=.*Action,ActionResult +exclude-too-few-public-methods=.*Action, + ActionResult, + Direction # List of qualified class names to ignore when counting class parents (see # R0901) diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index 83b8c81..f4fa0ee 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -1,11 +1,14 @@ -#!/usr/bin/env python3 # Eryn Wells +'''A bunch of geometric primitives''' + from dataclasses import dataclass from typing import Any, Iterator, overload @dataclass(frozen=True) class Point: + '''A two-dimensional point, with coordinates in X and Y axes''' + x: int = 0 y: int = 0 @@ -27,6 +30,8 @@ class Point: @dataclass(frozen=True) class Vector: + '''A two-dimensional vector, representing change in position in X and Y axes''' + dx: int = 0 dy: int = 0 @@ -38,6 +43,8 @@ class Vector: return f'(δx:{self.dx}, δy:{self.dy})' class Direction: + '''A collection of simple uint vectors in each of the eight major compass directions. This is a namespace, not a class.''' + North = Vector(0, -1) NorthEast = Vector(1, -1) East = Vector(1, 0) @@ -61,6 +68,8 @@ class Direction: @dataclass(frozen=True) class Size: + '''A two-dimensional size, representing size in X (width) and Y (height) axes''' + width: int = 0 height: int = 0 @@ -73,6 +82,8 @@ class Size: @dataclass(frozen=True) class Rect: + '''A two-dimensional rectangle, defined by an origin point and size''' + origin: Point size: Size @@ -108,13 +119,30 @@ class Rect: @property def midpoint(self) -> Point: + '''A Point in the middle of the Rect''' return Point(self.mid_x, self.mid_y) def inset_rect(self, top: int = 0, right: int = 0, bottom: int = 0, left: int = 0) -> 'Rect': ''' Return a new Rect inset from this rect by the specified values. Arguments are listed in clockwise order around - the permeter. This method doesn't do any validation or transformation of the returned Rect to make sure it's - valid. + the permeter. This method doesn't validate the returned Rect, or transform it to a canonical representation with + the origin at the top-left. + + Parameters + ---------- + top : int + Amount to inset from the top + right : int + Amount to inset from the right + bottom : int + Amount to inset from the bottom + left : int + Amount to inset from the left + + Returns + ------- + Rect + A new Rect, inset from `self` by the given amount on each side ''' return Rect(Point(self.origin.x + left, self.origin.y + top), Size(self.size.width - right - left, self.size.height - top - bottom)) From 85423e739ca0435dba6913a4bc5bb0172eea9125 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 17:44:30 -0700 Subject: [PATCH 043/234] Remove the shbang from map.py --- roguebasin/map.py | 1 - 1 file changed, 1 deletion(-) diff --git a/roguebasin/map.py b/roguebasin/map.py index c66ac63..2a993d3 100644 --- a/roguebasin/map.py +++ b/roguebasin/map.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Eryn Wells import logging From 4f7e477b24f4047c334c3ea96e1b67959ebd756a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 7 May 2022 22:34:43 -0700 Subject: [PATCH 044/234] Add a WaitAction and trigger it with . --- roguebasin/actions.py | 7 +++++++ roguebasin/events.py | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 5e849fa..c9426a0 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -178,3 +178,10 @@ class MeleeAction(MoveAction): def perform(self, engine: 'Engine') -> ActionResult: LOG.info('Attack! %s', self.target) return ActionResult(self, success=True) + +class WaitAction(Action): + '''Wait a turn''' + + def perform(self, engine: 'Engine') -> ActionResult: + LOG.info('%s is waiting a turn', self.entity) + return ActionResult(self, success=True) diff --git a/roguebasin/events.py b/roguebasin/events.py index 55ac392..33074a9 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -8,7 +8,7 @@ from typing import MutableSet, Optional, TYPE_CHECKING import tcod -from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction +from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .geometry import Direction from .object import Entity @@ -77,8 +77,10 @@ class EventHandler(tcod.event.EventDispatch[Action]): if result.success: LOG.info('Action succeded!') break - else: - LOG.info('Action failed!') + + if result.done: + LOG.info('Action failed!') + break return result @@ -110,5 +112,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): action = BumpAction(hero, Direction.NorthWest) case tcod.event.KeySym.SPACE: action = RegenerateRoomsAction() + case tcod.event.KeySym.PERIOD: + action = WaitAction(hero) return action From a9ebc3807834348132dfba353368dae73fcfc605 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 08:54:54 -0700 Subject: [PATCH 045/234] Update the Makefile to use .venv as the virtual env directory --- Makefile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index a2a447d..e68fb12 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ +VENV_DIR=.venv -.PHONY: env -venv: env - python3 -m venv env +.PHONY: venv +venv: + python3 -m venv ${VENV_DIR} -deps: env/bin/pip - ./env/bin/pip install -r requirements.txt +deps: ${VENV_DIR}/bin/pip requirements.txt + ${VENV_DIR}/bin/pip install -r requirements.txt -freeze: - ./env/bin/pip freeze > requirements.txt +freeze: ${VENV_DIR}/bin/pip + ${VENV_DIR}/bin/pip freeze > requirements.txt From f5a8a55182f7006b8e787db317cb3a6c6fb09162 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 08:55:08 -0700 Subject: [PATCH 046/234] Add WaitAction to the class hierarchy --- roguebasin/actions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index c9426a0..f021855 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -15,6 +15,7 @@ Action : Base class of all actions WalkAction MeleeAction ExitAction + WaitAction ''' import logging From cf0b120fad05cfbf33b24a3b26e433dc47fb5791 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:46:32 -0700 Subject: [PATCH 047/234] Add a Fighter component that tracks hit points, attack power, defense, etc --- roguebasin/components.py | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 roguebasin/components.py diff --git a/roguebasin/components.py b/roguebasin/components.py new file mode 100644 index 0000000..86b266f --- /dev/null +++ b/roguebasin/components.py @@ -0,0 +1,44 @@ +# Eryn Wells + +from typing import Optional + +# pylint: disable=too-few-public-methods +class Component: + '''A base, abstract Component that implement some aspect of an Entity's behavior.''' + +class Fighter(Component): + '''A Fighter is an Entity that can fight. That is, it has hit points (health), attack, and defense. + + Attributes + ---------- + maximum_hit_points : int + Maximum number of hit points the Fighter can have. In almost every case, a Fighter will be spawned with this + many hit points. + attack_power : int + The amount of damage the Figher can do. + defense : int + The amount of damage the Fighter can deflect or resist. + hit_points : int + The current number of hit points remaining. When this reaches 0, the Fighter dies. + ''' + + def __init__(self, *, maximum_hit_points: int, attack_power: int, defense: int, hit_points: Optional[int] = None): + self.maximum_hit_points = maximum_hit_points + self.__hit_points = hit_points if hit_points else maximum_hit_points + # TODO: Rename these two attributes something better + self.attack_power = attack_power + self.defense = defense + + @property + def hit_points(self) -> int: + '''Number of hit points remaining. When a Fighter reaches 0 hit points, they die.''' + return self.__hit_points + + @hit_points.setter + def hit_points(self, value: int) -> None: + self.__hit_points = min(self.maximum_hit_points, max(0, value)) + + @property + def is_dead(self) -> bool: + '''True if the Fighter has died, i.e. reached 0 hit points''' + return self.__hit_points == 0 From cf9ec2d17eb3eccf6606462721d7a572353c23b4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:48:05 -0700 Subject: [PATCH 048/234] Move Monster to the object module --- roguebasin/monsters.py | 33 +++++++++++++++++---------------- roguebasin/object.py | 20 +++++++++++++++++++- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/roguebasin/monsters.py b/roguebasin/monsters.py index 115cd95..2333ba8 100644 --- a/roguebasin/monsters.py +++ b/roguebasin/monsters.py @@ -1,11 +1,12 @@ # Eryn Wells +'''Defines the Species type, which represents a class of monsters, and all the monster types the hero can encounter in +the dungeon.''' + from dataclasses import dataclass from typing import Tuple -from .geometry import Point -from .object import Entity - +# pylint: disable=too-many-instance-attributes @dataclass(frozen=True) class Species: '''A kind of monster. @@ -30,19 +31,19 @@ class Species: symbol: str maximum_hit_points: int sight_radius: int + # TODO: Rename these two attributes something better + attack_power: int + defense: int foreground_color: Tuple[int, int, int] background_color: Tuple[int, int, int] = None -class Monster(Entity): - '''An instance of a Species.''' - - def __init__(self, species: Species, position: Point = None): - super().__init__(species.symbol, position=position, fg=species.foreground_color, bg=species.background_color) - self.species: Species = species - self.hit_points: int = species.maximum_hit_points - - def __str__(self) -> str: - return f'{self.symbol}[{self.species.name}][{self.position}][{self.hit_points}/{self.species.maximum_hit_points}]' - -Orc = Species(name='Orc', symbol='o', foreground_color=(63, 127, 63), maximum_hit_points=10, sight_radius=4) -Troll = Species(name='Troll', symbol='T', foreground_color=(0, 127, 0), maximum_hit_points=20, sight_radius=4) \ No newline at end of file +Orc = Species(name='Orc', symbol='o', + foreground_color=(63, 127, 63), + maximum_hit_points=10, + sight_radius=4, + attack_power=4, defense=1) +Troll = Species(name='Troll', symbol='T', + foreground_color=(0, 127, 0), + maximum_hit_points=16, + sight_radius=4, + attack_power=3, defense=0) diff --git a/roguebasin/object.py b/roguebasin/object.py index c67343a..255eaf0 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -1,11 +1,16 @@ #!/usr/bin/env python3 # Eryn Wells -from typing import Optional, Tuple +from typing import TYPE_CHECKING, Optional, Tuple, Type import tcod +from .components import Fighter from .geometry import Point +from .monsters import Species + +if TYPE_CHECKING: + from .ai import AI class Entity: '''A single-tile drawable entity with a symbol and position.''' @@ -31,3 +36,16 @@ class Entity: class Hero(Entity): def __init__(self, position: Point): super().__init__('@', position=position, fg=tuple(tcod.white)) + +class Monster(Entity): + '''An instance of a Species.''' + + def __init__(self, species: Species, ai_class: Type['AI'], position: Point = None): + super().__init__(species.symbol, ai=ai_class(self), position=position, fg=species.foreground_color, bg=species.background_color) + self.species = species + self.fighter = Fighter(maximum_hit_points=species.maximum_hit_points, + attack_power=species.attack_power, + defense=species.defense) + + def __str__(self) -> str: + return f'{self.symbol}[{self.species.name}][{self.position}][{self.fighter.hit_points}/{self.fighter.maximum_hit_points}]' From 49b48ec7a8f7583b84a724abdbaeeea86f512c75 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:48:22 -0700 Subject: [PATCH 049/234] Add a HostileEnemy AI component --- roguebasin/ai.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 roguebasin/ai.py diff --git a/roguebasin/ai.py b/roguebasin/ai.py new file mode 100644 index 0000000..f758fb2 --- /dev/null +++ b/roguebasin/ai.py @@ -0,0 +1,22 @@ +# Eryn Wells + +from typing import TYPE_CHECKING + +from .actions import Action, WaitAction +from .components import Component +from .object import Entity + +if TYPE_CHECKING: + from .engine import Engine + +class AI(Component): + def __init__(self, entity: Entity) -> None: + super().__init__() + self.entity = entity + + def act(self, engine: 'Engine') -> Action: + raise NotImplementedError() + +class HostileEnemy(AI): + def act(self, engine: 'Engine') -> Action: + return WaitAction(self.entity) \ No newline at end of file From 687511d69e335571fdd77e43c0563252f62e0362 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:54:08 -0700 Subject: [PATCH 050/234] Add an ai attribute to Entity --- roguebasin/object.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roguebasin/object.py b/roguebasin/object.py index 255eaf0..b6473eb 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -17,12 +17,14 @@ class Entity: def __init__(self, symbol: str, *, position: Optional[Point] = None, + ai: Optional[Type['AI']] = None, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None): self.position = position if position else Point() self.foreground = fg if fg else (255, 255, 255) self.background = bg self.symbol = symbol + self.ai = ai def print_to_console(self, console: tcod.Console) -> None: console.print(x=self.position.x, y=self.position.y, string=self.symbol, fg=self.foreground, bg=self.background) From 550cde6a8f2d6ca61e8ea9c8d27c0ca15738e98c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:54:24 -0700 Subject: [PATCH 051/234] Allow "ai" as a variable name --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index b7b7c7e..0a68b15 100644 --- a/.pylintrc +++ b/.pylintrc @@ -407,7 +407,8 @@ function-naming-style=snake_case #function-rgx= # Good variable names which should always be accepted, separated by a comma. -good-names=dx, +good-names=ai, + dx, dy, i, j, From ee0e4b1dbaa7c9b5b37ee7408811c8d180160d7a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:55:10 -0700 Subject: [PATCH 052/234] Instantiate Monsters with a HostileEnemy AI --- roguebasin/engine.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 5188e9a..0db3d86 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -10,10 +10,10 @@ from typing import MutableSet import tcod from . import monsters +from .ai import HostileEnemy from .geometry import Size from .map import Map -from .monsters import Monster -from .object import Entity, Hero +from .object import Entity, Hero, Monster LOG = logging.getLogger('engine') @@ -65,9 +65,9 @@ class Engine: for _ in range(2): spawn_monster_chance = random.random() if spawn_monster_chance > 0.8: - monster = Monster(monsters.Troll, position=random_start_position) + monster = Monster(monsters.Troll, ai_class=HostileEnemy, position=random_start_position) else: - monster = Monster(monsters.Orc, position=random_start_position) + monster = Monster(monsters.Orc, ai_class=HostileEnemy, position=random_start_position) LOG.info('Spawning monster %s', monster) self.entities.add(monster) From e1562b2b2bddc1ad80142a4fadb682adba81e4bd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:55:56 -0700 Subject: [PATCH 053/234] Add a Fighter component to the Hero --- roguebasin/object.py | 1 + 1 file changed, 1 insertion(+) diff --git a/roguebasin/object.py b/roguebasin/object.py index b6473eb..b2e41d6 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -38,6 +38,7 @@ class Entity: class Hero(Entity): def __init__(self, position: Point): super().__init__('@', position=position, fg=tuple(tcod.white)) + self.fighter = Fighter(maximum_hit_points=30, attack_power=5, defense=2) class Monster(Entity): '''An instance of a Species.''' From 6bb5d819bfdd6496cd31d5caabc4a082bf1d4384 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 09:56:14 -0700 Subject: [PATCH 054/234] Lots of comment and type documentation in object.py --- roguebasin/object.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/roguebasin/object.py b/roguebasin/object.py index b2e41d6..0568aec 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 # Eryn Wells from typing import TYPE_CHECKING, Optional, Tuple, Type @@ -13,7 +12,21 @@ if TYPE_CHECKING: from .ai import AI class Entity: - '''A single-tile drawable entity with a symbol and position.''' + '''A single-tile drawable entity with a symbol and position. + + Attributes + ---------- + position : Point + The Entity's location on the map + foreground : Tuple[int, int, int] + The foreground color used to render this Entity + background : Tuple[int, int, int], optional + The background color used to render this Entity + symbol : str + A single character string that represents this character on the map + ai : Type[AI], optional + If an entity can act on its own behalf, an instance of an AI class + ''' def __init__(self, symbol: str, *, position: Optional[Point] = None, @@ -27,19 +40,26 @@ class Entity: self.ai = ai def print_to_console(self, console: tcod.Console) -> None: + '''Render this Entity to the console''' console.print(x=self.position.x, y=self.position.y, string=self.symbol, fg=self.foreground, bg=self.background) - def __str__(self): + def __str__(self) -> str: return f'{self.symbol}[{self.position}]' - def __repr__(self): + def __repr__(self) -> str: return f'{self.__class__.__name__}({self.symbol}, position={self.position}, fg={self.foreground}, bg={self.background})' class Hero(Entity): + '''The hero, the player character''' + def __init__(self, position: Point): super().__init__('@', position=position, fg=tuple(tcod.white)) self.fighter = Fighter(maximum_hit_points=30, attack_power=5, defense=2) + def __str__(self) -> str: + return f'{self.symbol}[{self.position}][{self.fighter.hit_points}/{self.fighter.maximum_hit_points}]' + + class Monster(Entity): '''An instance of a Species.''' From 46e1a4206033fbc6d39c420ad61e60f578ba6aa3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 10:03:28 -0700 Subject: [PATCH 055/234] Let Entity.ai produce its own Actions! --- roguebasin/events.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 33074a9..9b862b1 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -3,14 +3,12 @@ '''Defines event handling mechanisms.''' import logging -import random -from typing import MutableSet, Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING import tcod from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .geometry import Direction -from .object import Entity if TYPE_CHECKING: from .engine import Engine @@ -42,23 +40,13 @@ class EventHandler(tcod.event.EventDispatch[Action]): if not result.success and result.done: return - directions = list(Direction.all()) - - hero = self.engine.hero - moved_entities: MutableSet[Entity] = {self.engine.hero} - for ent in self.engine.entities: - if ent == hero: + ent_ai = ent.ai + if not ent_ai: continue - while True: - new_position = ent.position + random.choice(directions) - overlaps_with_previously_moved_entity = any(new_position == moved_ent.position for moved_ent in moved_entities) - tile_is_walkable = self.engine.map.tile_is_walkable(new_position) - if not overlaps_with_previously_moved_entity and tile_is_walkable: - ent.position = new_position - moved_entities.add(ent) - break + action = ent_ai.act(self.engine) + self.perform_action_until_done(action) self.engine.update_field_of_view() From 7653df1e3f77de38986f170f348532dde70dbdd8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:34:09 -0700 Subject: [PATCH 056/234] Add Action.success() and Action.failure() helper methods to produce results for straight success and failure --- roguebasin/actions.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index f021855..1bceb48 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -91,6 +91,14 @@ class Action: ''' raise NotImplementedError() + def failure(self) -> ActionResult: + '''Create an ActionResult indicating failure with no follow-up''' + return ActionResult(self, success=False) + + def success(self) -> ActionResult: + '''Create an ActionResult indicating success with no follow-up''' + return ActionResult(self, success=True) + def __repr__(self): return f'{self.__class__.__name__}({self.entity!r})' @@ -150,7 +158,7 @@ class BumpAction(MoveAction): entity_occupying_position) if not position_is_in_bounds or not position_is_walkable: - return ActionResult(self, success=False) + return self.failure() if entity_occupying_position: return ActionResult(self, alternate=MeleeAction(self.entity, self.direction, entity_occupying_position)) @@ -167,7 +175,7 @@ class WalkAction(MoveAction): LOG.info('Moving %s to %s', self.entity, new_position) self.entity.position = new_position - return ActionResult(self, success=True) + return self.success() class MeleeAction(MoveAction): '''Perform a melee attack on another entity''' @@ -178,11 +186,11 @@ class MeleeAction(MoveAction): def perform(self, engine: 'Engine') -> ActionResult: LOG.info('Attack! %s', self.target) - return ActionResult(self, success=True) + return self.success() class WaitAction(Action): '''Wait a turn''' def perform(self, engine: 'Engine') -> ActionResult: LOG.info('%s is waiting a turn', self.entity) - return ActionResult(self, success=True) + return self.success() From 021b82c93a2b06e3c8728453a55f7e6f2a8b70b2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:35:47 -0700 Subject: [PATCH 057/234] Add an Actor subclass of Entity Make Hero and Monster subclasses of Actor --- roguebasin/object.py | 84 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/roguebasin/object.py b/roguebasin/object.py index 0568aec..a536da0 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -49,26 +49,90 @@ class Entity: def __repr__(self) -> str: return f'{self.__class__.__name__}({self.symbol}, position={self.position}, fg={self.foreground}, bg={self.background})' -class Hero(Entity): +class Actor(Entity): + def __init__(self, symbol: str, *, + position: Optional[Point] = None, + blocks_movement: Optional[bool] = True, + ai: Optional[Type['AI']] = None, + fighter: Optional[Fighter] = None, + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None): + super().__init__(symbol, position=position, blocks_movement=blocks_movement, fg=fg, bg=bg) + + # Components + self.ai = ai + self.fighter = fighter + + @property + def name(self) -> str: + return 'Actor' + + @property + def sight_radius(self) -> int: + '''The number of tiles this entity can see around itself''' + return 0 + + @property + def yields_corpse_on_death(self) -> bool: + '''True if this Actor should produce a corpse when it dies''' + return False + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fighter={self.fighter!r}, ai={self.ai!r}, fg={self.foreground!r}, bg={self.background!r})' + +class Hero(Actor): '''The hero, the player character''' def __init__(self, position: Point): - super().__init__('@', position=position, fg=tuple(tcod.white)) - self.fighter = Fighter(maximum_hit_points=30, attack_power=5, defense=2) + super().__init__('@', + position=position, + fighter=Fighter(maximum_hit_points=30, attack_power=5, defense=2), + fg=tuple(tcod.white)) + + @property + def name(self) -> str: + return 'Hero' + + @property + def sight_radius(self) -> int: + # TODO: Make this configurable + return 8 def __str__(self) -> str: return f'{self.symbol}[{self.position}][{self.fighter.hit_points}/{self.fighter.maximum_hit_points}]' -class Monster(Entity): - '''An instance of a Species.''' +class Monster(Actor): + '''An instance of a Species''' def __init__(self, species: Species, ai_class: Type['AI'], position: Point = None): - super().__init__(species.symbol, ai=ai_class(self), position=position, fg=species.foreground_color, bg=species.background_color) + fighter = Fighter( + maximum_hit_points=species.maximum_hit_points, + attack_power=species.attack_power, + defense=species.defense) + + super().__init__( + species.symbol, + ai=ai_class(self), + position=position, + fighter=fighter, + fg=species.foreground_color, + bg=species.background_color) + self.species = species - self.fighter = Fighter(maximum_hit_points=species.maximum_hit_points, - attack_power=species.attack_power, - defense=species.defense) + + @property + def name(self) -> str: + return self.species.name + + @property + def sight_radius(self) -> int: + return self.species.sight_radius + + @property + def yields_corpse_on_death(self) -> bool: + return True def __str__(self) -> str: - return f'{self.symbol}[{self.species.name}][{self.position}][{self.fighter.hit_points}/{self.fighter.maximum_hit_points}]' + return f'{self.name} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp at {self.position}' + From a13ef89832f0961c82b4242360af50789f43dcc7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:36:13 -0700 Subject: [PATCH 058/234] Add an Item type class and a Corpse item type --- roguebasin/items.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 roguebasin/items.py diff --git a/roguebasin/items.py b/roguebasin/items.py new file mode 100644 index 0000000..eafd248 --- /dev/null +++ b/roguebasin/items.py @@ -0,0 +1,35 @@ +# Eryn Wells + +from dataclasses import dataclass +from typing import Tuple + +@dataclass(frozen=True) +class Item: + '''A record of a kind of item + + This class follows the "type class" pattern. It represents a kind of item, not a specific instance of that item. + (See `object.Item` for that.) + + Attributes + ---------- + symbol : str + The symbol used to render this item on the map + foreground_color : Tuple[int, int, int] + The foreground color used to render this item on the map + background_color : Tuple[int, int, int], optional + The background color used to render this item on the map + name : str + The name of this item + description : str + A description of this item + ''' + symbol: str + name: str + description: str + foreground_color: Tuple[int, int, int] + background_color: Tuple[int, int, int] = None + +Corpse = Item('%', + name="Corpse", + description="The corpse of a once-living being", + foreground_color=(128, 128, 255)) From e8b2729353eac1c1ca5d6aa07a61b2ecb2e1f42a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:36:56 -0700 Subject: [PATCH 059/234] Add blocks_movement to the Entity class --- roguebasin/object.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/roguebasin/object.py b/roguebasin/object.py index a536da0..25fa784 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from .ai import AI class Entity: - '''A single-tile drawable entity with a symbol and position. + '''A single-tile drawable entity with a symbol and position Attributes ---------- @@ -26,18 +26,20 @@ class Entity: A single character string that represents this character on the map ai : Type[AI], optional If an entity can act on its own behalf, an instance of an AI class + blocks_movement : bool + True if this Entity blocks other Entities from moving through its position ''' def __init__(self, symbol: str, *, position: Optional[Point] = None, - ai: Optional[Type['AI']] = None, + blocks_movement: Optional[bool] = True, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None): self.position = position if position else Point() self.foreground = fg if fg else (255, 255, 255) self.background = bg self.symbol = symbol - self.ai = ai + self.blocks_movement = blocks_movement def print_to_console(self, console: tcod.Console) -> None: '''Render this Entity to the console''' @@ -47,7 +49,7 @@ class Entity: return f'{self.symbol}[{self.position}]' def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.symbol}, position={self.position}, fg={self.foreground}, bg={self.background})' + return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fg={self.foreground!r}, bg={self.background!r})' class Actor(Entity): def __init__(self, symbol: str, *, From 1f750a0c7cb34338250e0e4236739e2000ac07f1 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:37:31 -0700 Subject: [PATCH 060/234] Add an Item subclass of Entity for instances of items on the map --- roguebasin/object.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/roguebasin/object.py b/roguebasin/object.py index 25fa784..89c4bf0 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Tuple, Type import tcod +from . import items from .components import Fighter from .geometry import Point from .monsters import Species @@ -138,3 +139,21 @@ class Monster(Actor): def __str__(self) -> str: return f'{self.name} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp at {self.position}' +class Item(Entity): + '''An instance of an Item''' + + def __init__(self, kind: items.Item, position: Point = None, name: str = None): + super().__init__(kind.symbol, + position=position, + blocks_movement=False, + fg=kind.foreground_color, + bg=kind.background_color) + self.kind = kind + self._name = name + + @property + def name(self) -> str: + '''The name of the item''' + if self._name: + return self._name + return self.kind.name From b604ff30ecbed86d0d23409f7669a6f7f13a5af6 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:38:48 -0700 Subject: [PATCH 061/234] Implement a basic AI for HostileMonster This AI will walk randomly around the dungeon (pausing periodically) and if the Hero comes into view, will b-line and attack --- roguebasin/ai.py | 100 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/roguebasin/ai.py b/roguebasin/ai.py index f758fb2..eb54f5c 100644 --- a/roguebasin/ai.py +++ b/roguebasin/ai.py @@ -1,22 +1,112 @@ # Eryn Wells -from typing import TYPE_CHECKING +import logging +from os import path +import random +from typing import TYPE_CHECKING, List, Optional -from .actions import Action, WaitAction +import numpy as np +import tcod + +from .actions import Action, BumpAction, MeleeAction, WaitAction from .components import Component +from .geometry import Direction, Point from .object import Entity if TYPE_CHECKING: from .engine import Engine +LOG = logging.getLogger(__name__) + class AI(Component): def __init__(self, entity: Entity) -> None: super().__init__() self.entity = entity - def act(self, engine: 'Engine') -> Action: + def act(self, engine: 'Engine') -> Optional[Action]: + '''Produce an action to perform''' raise NotImplementedError() class HostileEnemy(AI): - def act(self, engine: 'Engine') -> Action: - return WaitAction(self.entity) \ No newline at end of file + def act(self, engine: 'Engine') -> Optional[Action]: + visible_tiles = tcod.map.compute_fov( + engine.map.tiles['transparent'], + pov=tuple(self.entity.position), + radius=self.entity.sight_radius) + + hero_position = engine.hero.position + hero_is_visible = visible_tiles[hero_position.x, hero_position.y] + + if hero_is_visible: + path_to_hero = self.get_path_to(hero_position, engine) + entity_position = self.entity.position + + next_position = path_to_hero.pop(0) if len(path_to_hero) else hero_position + direction_to_next_position = entity_position.direction_to_adjacent_point(next_position) + LOG.info('Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position) + + return BumpAction(self.entity, direction_to_next_position) + else: + move_or_wait_chance = random.random() + if move_or_wait_chance <= 0.7: + # Pick a random adjacent tile to move to + directions = list(Direction.all()) + while len(directions) > 0: + direction = random.choice(directions) + directions.remove(direction) + new_position = self.entity.position + direction + overlaps_existing_entity = any(new_position == ent.position for ent in engine.entities) + tile_is_walkable = engine.map.tile_is_walkable(new_position) + if not overlaps_existing_entity and tile_is_walkable: + LOG.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) + action = BumpAction(self.entity, direction) + break + else: + # If this entity somehow can't move anywhere, just wait + LOG.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity) + action = WaitAction(self.entity) + + return action + else: + return WaitAction(self.entity) + + def get_path_to(self, point: Point, engine: 'Engine') -> List[Point]: + '''Compute a path to the given position. + + Copied from the Roguelike tutorial. :) + + Arguments + --------- + point : Point + The target point + engine : Engine + The game engine + + Returns + ------- + List[Point] + An array of Points representing a path from the Entity's position to the target point + ''' + # Copy the walkable array + cost = np.array(engine.map.tiles['walkable'], dtype=np.int8) + + for ent in engine.entities: + # Check that an entity blocks movement and the cost isn't zero (blocking) + position = ent.position + if ent.blocks_movement and cost[position.x, position.y]: + # Add to the cost of a blocked position. A lower number means more enemies will crowd behind each other + # in hallways. A higher number means enemies will take longer paths in order to surround the player. + cost[position.x, position.y] += 10 + + # Create a graph from the cost array and pass that graph to a new pathfinder. + graph = tcod.path.SimpleGraph(cost=cost, cardinal=2, diagonal=3) + pathfinder = tcod.path.Pathfinder(graph) + + # Set the starting position + pathfinder.add_root(tuple(self.entity.position)) + + # Compute the path to the destination and remove the starting point. + path: List[List[int]] = pathfinder.path_to(tuple(point))[1:].tolist() + + # Convert from List[List[int]] to List[Tuple[int, int]]. + return [Point(index[0], index[1]) for index in path] \ No newline at end of file From aae1251660b851d05c3f59ab6cc24b4ba109dff9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:40:33 -0700 Subject: [PATCH 062/234] Rename Action.entity -> Action.actor --- roguebasin/actions.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 1bceb48..203650d 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -72,8 +72,8 @@ class ActionResult: class Action: '''An action that an Entity should perform.''' - def __init__(self, entity: Optional[Entity] = None): - self.entity = entity + def __init__(self, actor: Optional[Actor]): + self.actor = actor def perform(self, engine: 'Engine') -> ActionResult: '''Perform this action. @@ -100,7 +100,7 @@ class Action: return ActionResult(self, success=True) def __repr__(self): - return f'{self.__class__.__name__}({self.entity!r})' + return f'{self.__class__.__name__}({self.actor!r})' class ExitAction(Action): '''Exit the game.''' @@ -118,12 +118,12 @@ class RegenerateRoomsAction(Action): class MoveAction(Action): '''An abstract Action that requires a direction to complete.''' - def __init__(self, entity: Entity, direction: Direction): - super().__init__(entity) + def __init__(self, actor: Actor, direction: Direction): + super().__init__(actor) self.direction = direction def __repr__(self): - return f'{self.__class__.__name__}({self.entity!r}, {self.direction!r})' + return f'{self.__class__.__name__}({self.actor!r}, {self.direction!r})' class BumpAction(MoveAction): '''Attempt to perform a movement action in a direction. @@ -137,7 +137,7 @@ class BumpAction(MoveAction): ''' def perform(self, engine: 'Engine') -> ActionResult: - new_position = self.entity.position + self.direction + new_position = self.actor.position + self.direction position_is_in_bounds = engine.map.tile_is_in_bounds(new_position) position_is_walkable = engine.map.tile_is_walkable(new_position) @@ -151,7 +151,7 @@ class BumpAction(MoveAction): entity_occupying_position = None LOG.info('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', - self.entity, + self.actor, new_position, position_is_in_bounds, position_is_walkable, @@ -161,27 +161,27 @@ class BumpAction(MoveAction): return self.failure() if entity_occupying_position: - return ActionResult(self, alternate=MeleeAction(self.entity, self.direction, entity_occupying_position)) + return ActionResult(self, alternate=MeleeAction(self.actor, self.direction, entity_occupying_position)) - return ActionResult(self, alternate=WalkAction(self.entity, self.direction)) + return ActionResult(self, alternate=WalkAction(self.actor, self.direction)) class WalkAction(MoveAction): '''Walk one step in the given direction.''' def perform(self, engine: 'Engine') -> ActionResult: - new_position = self.entity.position + self.direction + new_position = self.actor.position + self.direction - LOG.info('Moving %s to %s', self.entity, new_position) - self.entity.position = new_position + LOG.info('Moving %s to %s', self.actor, new_position) + self.actor.position = new_position return self.success() class MeleeAction(MoveAction): - '''Perform a melee attack on another entity''' + '''Perform a melee attack on another Actor''' - def __init__(self, entity: Entity, direction: Direction, target: Entity): - super().__init__(entity, direction) + def __init__(self, actor: Actor, direction: Direction, target: Actor): + super().__init__(actor, direction) self.target = target def perform(self, engine: 'Engine') -> ActionResult: @@ -192,5 +192,5 @@ class WaitAction(Action): '''Wait a turn''' def perform(self, engine: 'Engine') -> ActionResult: - LOG.info('%s is waiting a turn', self.entity) + LOG.info('%s is waiting a turn', self.actor) return self.success() From 2266511ec5902ca61070ab21fe2788e881162264 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:41:54 -0700 Subject: [PATCH 063/234] Implement attacking and reducing hit points Attacks are computed with attack_power and defense. When an Actor dies, a DieAction is produced and possibly also a DropItemAction. --- roguebasin/actions.py | 47 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 203650d..cc25a46 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -20,8 +20,10 @@ Action : Base class of all actions import logging from typing import Optional, TYPE_CHECKING + +from . import items from .geometry import Direction -from .object import Entity +from .object import Actor, Item if TYPE_CHECKING: from .engine import Engine @@ -185,7 +187,23 @@ class MeleeAction(MoveAction): self.target = target def perform(self, engine: 'Engine') -> ActionResult: - LOG.info('Attack! %s', self.target) + if not self.target: + return self.failure() + + if not self.actor.fighter or not self.target.fighter: + return self.failure() + + damage = self.actor.fighter.attack_power - self.target.fighter.defense + if damage > 0: + LOG.info('%s attacks %s for %d damage!', self.actor, self.target, damage) + self.target.fighter.hit_points -= damage + else: + LOG.info('%s attacks %s but does no damage!', self.actor, self.target) + + if self.target.fighter.is_dead: + LOG.info('%s is dead!', self.target) + return ActionResult(self, alternate=DieAction(self.target)) + return self.success() class WaitAction(Action): @@ -194,3 +212,28 @@ class WaitAction(Action): def perform(self, engine: 'Engine') -> ActionResult: LOG.info('%s is waiting a turn', self.actor) return self.success() + +class DieAction(Action): + '''Kill an Actor''' + + def perform(self, engine: 'Engine') -> ActionResult: + LOG.info('%s dies', self.actor) + engine.entities.remove(self.actor) + + if self.actor.yields_corpse_on_death: + LOG.info('%s leaves a corpse behind', self.actor) + corpse = Item(kind=items.Corpse, name=f'{self.actor.name} Corpse', position=self.actor.position) + return ActionResult(self, alternate=DropItemAction(self.actor, corpse)) + + return self.success() + +class DropItemAction(Action): + '''Drop an item''' + + def __init__(self, actor: 'Actor', item: 'Item'): + super().__init__(actor) + self.item = item + + def perform(self, engine: 'Engine') -> ActionResult: + engine.entities.add(self.item) + return self.success() \ No newline at end of file From c0c8584f452346d787545a492931e12f1bc887bd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:42:24 -0700 Subject: [PATCH 064/234] Implement is_adjacent_to and direction_to_adjacent_point on Point --- roguebasin/geometry.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index f4fa0ee..e76ef36 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -3,7 +3,7 @@ '''A bunch of geometric primitives''' from dataclasses import dataclass -from typing import Any, Iterator, overload +from typing import Any, Iterator, Optional, overload @dataclass(frozen=True) class Point: @@ -12,6 +12,29 @@ class Point: x: int = 0 y: int = 0 + def is_adjacent_to(self, other: 'Point') -> bool: + '''Check if this point is adjacent to, but not overlapping the given point + + Parameters + ---------- + other : Point + The point to check + + Returns + ------- + bool + True if this point is adjacent to the other point + ''' + return (self.x in (other.x - 1, other.x + 1)) and (self.y in (other.y -1, other.y + 1)) + + def direction_to_adjacent_point(self, other: 'Point') -> Optional['Direction']: + for direction in Direction.all(): + if (self + direction) != other: + continue + return direction + + return None + @overload def __add__(self, other: 'Vector') -> 'Point': ... From cef1ad25cbacd5e52be299a831a65d19d9eeba81 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:42:36 -0700 Subject: [PATCH 065/234] Small cleanup of log statement --- roguebasin/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 0db3d86..1e3a949 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -69,7 +69,7 @@ class Engine: else: monster = Monster(monsters.Orc, ai_class=HostileEnemy, position=random_start_position) - LOG.info('Spawning monster %s', monster) + LOG.info('Spawning %s', monster) self.entities.add(monster) self.update_field_of_view() From 7d871e52a93f99d62339270c14aa393190d9f56d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:43:08 -0700 Subject: [PATCH 066/234] Copy the entities set into a list before iterating it so there's no risk of modifying the array while iterating --- roguebasin/events.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 9b862b1..5d8a025 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -9,6 +9,7 @@ import tcod from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .geometry import Direction +from .object import Actor if TYPE_CHECKING: from .engine import Engine @@ -40,7 +41,12 @@ class EventHandler(tcod.event.EventDispatch[Action]): if not result.success and result.done: return - for ent in self.engine.entities: + # Copy the list so we only act on the entities that exist at the start of this turn + entities = list(self.engine.entities) + for ent in entities: + if not isinstance(ent, Actor): + continue + ent_ai = ent.ai if not ent_ai: continue From 17bad9fd4d55fa81af669ce4b6371c1a105b43e6 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:43:33 -0700 Subject: [PATCH 067/234] Don't try to Melee entities that don't block movement --- roguebasin/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index cc25a46..7f26917 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -162,7 +162,7 @@ class BumpAction(MoveAction): if not position_is_in_bounds or not position_is_walkable: return self.failure() - if entity_occupying_position: + if entity_occupying_position and entity_occupying_position.blocks_movement: return ActionResult(self, alternate=MeleeAction(self.actor, self.direction, entity_occupying_position)) return ActionResult(self, alternate=WalkAction(self.actor, self.direction)) From bc46856117355c6250587999073357924a227937 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 8 May 2022 23:45:20 -0700 Subject: [PATCH 068/234] Every action needs an actor, even ExitAction --- roguebasin/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 5d8a025..aba2b0c 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -79,7 +79,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): return result def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: - return ExitAction() + return ExitAction(self.engine.hero) def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: action: Optional[Action] = None From 8849a9de7311e93c5f962e628d127d0fb7e265dd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 11 May 2022 07:50:35 -0700 Subject: [PATCH 069/234] Allow 'hp' as a valid variable name --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index 0a68b15..c795719 100644 --- a/.pylintrc +++ b/.pylintrc @@ -410,6 +410,7 @@ function-naming-style=snake_case good-names=ai, dx, dy, + hp, i, j, k, From 2762933c83287184d7d9f9351d7c1d58490ad7e1 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 11 May 2022 07:52:35 -0700 Subject: [PATCH 070/234] Configure logging with logging_config.json See https://docs.python.org/3/library/logging.config.html for details on the schema for this file. --- logging_config.json | 27 +++++++++++++++++++++++++++ roguebasin/__main__.py | 42 +++++++++++++++++++++++++++++++++--------- 2 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 logging_config.json diff --git a/logging_config.json b/logging_config.json new file mode 100644 index 0000000..0c03b92 --- /dev/null +++ b/logging_config.json @@ -0,0 +1,27 @@ +{ + "version": 1, + "formatters": { + "default": { + "format": "%(asctime)s %(name)s: %(message)s", + "datefmt": "%Y-%m-%d %I:%M:%S" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "default", + "level": "DEBUG", + "stream": "ext://sys.stdout" + } + }, + "loggers": { + "actions.movement": { + "level": "DEBUG", + "handlers": ["console"] + } + }, + "root": { + "level": "DEBUG", + "handlers": ["console"] + } +} \ No newline at end of file diff --git a/roguebasin/__main__.py b/roguebasin/__main__.py index 8232c2a..a396d7a 100644 --- a/roguebasin/__main__.py +++ b/roguebasin/__main__.py @@ -1,7 +1,9 @@ # Eryn Wells import argparse +import json import logging +import logging.config import os.path import sys import tcod @@ -23,29 +25,51 @@ def parse_args(argv, *a, **kw): return args def init_logging(args): - root_logger = logging.getLogger('') - root_logger.setLevel(logging.DEBUG if args.debug else logging.INFO) + '''Set up the logging system by (preferrably) reading a logging configuration file.''' + logging_config_path = find_logging_config() + if logging_config_path: + with open(logging_config_path, encoding='utf-8') as logging_config_file: + logging_config = json.load(logging_config_file) + logging.config.dictConfig(logging_config) + LOG.info('Found logging configuration at %s', logging_config_path) + else: + root_logger = logging.getLogger('') + root_logger.setLevel(logging.DEBUG if args.debug else logging.INFO) - stderr_handler = logging.StreamHandler() - stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) + stderr_handler = logging.StreamHandler() + stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) - root_logger.addHandler(stderr_handler) + root_logger.addHandler(stderr_handler) + +def walk_up_directories_of_path(path): + while path and path != '/': + path = os.path.dirname(path) + yield path def find_fonts_directory(): '''Walk up the filesystem tree from this script to find a fonts/ directory.''' - parent_dir = os.path.dirname(__file__) - while parent_dir and parent_dir != '/': + for parent_dir in walk_up_directories_of_path(__file__): possible_fonts_dir = os.path.join(parent_dir, 'fonts') - LOG.debug('Checking for fonts dir at %s', possible_fonts_dir) if os.path.isdir(possible_fonts_dir): LOG.info('Found fonts dir %s', possible_fonts_dir) break - parent_dir = os.path.dirname(parent_dir) else: return None return possible_fonts_dir +def find_logging_config(): + '''Walk up the filesystem from this script to find a logging_config.json''' + for parent_dir in walk_up_directories_of_path(__file__): + possible_logging_config_file = os.path.join(parent_dir, 'logging_config.json') + if os.path.isfile(possible_logging_config_file): + LOG.info('Found logging config file %s', possible_logging_config_file) + break + else: + return None + + return possible_logging_config_file + def main(argv): args = parse_args(argv[1:], prog=argv[0]) From c9b86271d3f664957f31bdd035615cc29befe51a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 11 May 2022 07:55:47 -0700 Subject: [PATCH 071/234] Remove these two logging messages They aren't serving a good purpose. --- roguebasin/events.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index aba2b0c..a9d210c 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -69,11 +69,9 @@ class EventHandler(tcod.event.EventDispatch[Action]): LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) if result.success: - LOG.info('Action succeded!') break if result.done: - LOG.info('Action failed!') break return result From 7820adf05769d73e89ad686777b5793a90b17719 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 11 May 2022 07:57:28 -0700 Subject: [PATCH 072/234] Generate entities in rooms at different locations Prior to this change, entities in rooms would always be spawned on the same tile. --- roguebasin/engine.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 1e3a949..679a168 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -57,12 +57,12 @@ class Engine: continue floor = list(room.walkable_tiles) - while True: - random_start_position = random.choice(floor) - if not any(ent.position == random_start_position for ent in self.entities): - break - for _ in range(2): + while True: + random_start_position = random.choice(floor) + if not any(ent.position == random_start_position for ent in self.entities): + break + spawn_monster_chance = random.random() if spawn_monster_chance > 0.8: monster = Monster(monsters.Troll, ai_class=HostileEnemy, position=random_start_position) @@ -78,7 +78,10 @@ class Engine: '''Print the whole game to the given console.''' self.map.print_to_console(console) - for ent in self.entities: + hp, max_hp = self.hero.fighter.hit_points, self.hero.fighter.maximum_hit_points + console.print(x=1, y=47, string=f'HP: {hp}/{max_hp}') + + for ent in sorted(self.entities, key=lambda e: e.render_order.value): # Only print entities that are in the field of view if not self.map.visible[tuple(ent.position)]: continue From a4adbcca85cf5f2fc05eb5e0a2dee8f8fff3c9d0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 11 May 2022 07:59:54 -0700 Subject: [PATCH 073/234] Add RenderOrder to Entity This enum dictates what order an entity will be rendered in. --- roguebasin/object.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/roguebasin/object.py b/roguebasin/object.py index 89c4bf0..8c92e6e 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -1,5 +1,6 @@ # Eryn Wells +from enum import Enum from typing import TYPE_CHECKING, Optional, Tuple, Type import tcod @@ -12,6 +13,15 @@ from .monsters import Species if TYPE_CHECKING: from .ai import AI +class RenderOrder(Enum): + ''' + These values indicate the order that an Entity should be rendered. Higher values are rendered later and therefore on + top of items at with lower orderings. + ''' + ITEM = 1000 + ACTOR = 2000 + HERO = 3000 + class Entity: '''A single-tile drawable entity with a symbol and position @@ -34,6 +44,7 @@ class Entity: def __init__(self, symbol: str, *, position: Optional[Point] = None, blocks_movement: Optional[bool] = True, + render_order: RenderOrder = RenderOrder.ITEM, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None): self.position = position if position else Point() @@ -41,6 +52,7 @@ class Entity: self.background = bg self.symbol = symbol self.blocks_movement = blocks_movement + self.render_order = render_order def print_to_console(self, console: tcod.Console) -> None: '''Render this Entity to the console''' @@ -56,11 +68,12 @@ class Actor(Entity): def __init__(self, symbol: str, *, position: Optional[Point] = None, blocks_movement: Optional[bool] = True, + render_order: RenderOrder = RenderOrder.ACTOR, ai: Optional[Type['AI']] = None, fighter: Optional[Fighter] = None, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None): - super().__init__(symbol, position=position, blocks_movement=blocks_movement, fg=fg, bg=bg) + super().__init__(symbol, position=position, blocks_movement=blocks_movement, fg=fg, bg=bg, render_order=render_order) # Components self.ai = ai @@ -90,6 +103,7 @@ class Hero(Actor): super().__init__('@', position=position, fighter=Fighter(maximum_hit_points=30, attack_power=5, defense=2), + render_order=RenderOrder.HERO, fg=tuple(tcod.white)) @property @@ -146,6 +160,7 @@ class Item(Entity): super().__init__(kind.symbol, position=position, blocks_movement=False, + render_order=RenderOrder.ITEM, fg=kind.foreground_color, bg=kind.background_color) self.kind = kind From 99ca0904486283649156a498145f320539739fa8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 11 May 2022 08:08:47 -0700 Subject: [PATCH 074/234] Remove a newline --- roguebasin/object.py | 1 - 1 file changed, 1 deletion(-) diff --git a/roguebasin/object.py b/roguebasin/object.py index 8c92e6e..70ee0a2 100644 --- a/roguebasin/object.py +++ b/roguebasin/object.py @@ -118,7 +118,6 @@ class Hero(Actor): def __str__(self) -> str: return f'{self.symbol}[{self.position}][{self.fighter.hit_points}/{self.fighter.maximum_hit_points}]' - class Monster(Actor): '''An instance of a Species''' From e9db004a7a17911c33c01c6190e62af4567c2f36 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:46:45 -0700 Subject: [PATCH 075/234] Add Point.euclidean_distance_to() Does what it says on the tin. --- roguebasin/geometry.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/roguebasin/geometry.py b/roguebasin/geometry.py index e76ef36..872777d 100644 --- a/roguebasin/geometry.py +++ b/roguebasin/geometry.py @@ -2,6 +2,7 @@ '''A bunch of geometric primitives''' +import math from dataclasses import dataclass from typing import Any, Iterator, Optional, overload @@ -35,6 +36,10 @@ class Point: return None + def euclidean_distance_to(self, other: 'Point') -> float: + '''Compute the Euclidean distance to another Point''' + return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) + @overload def __add__(self, other: 'Vector') -> 'Point': ... From 6f1d68db205640ab0505e4d83bdc267c35488d55 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:46:59 -0700 Subject: [PATCH 076/234] Update the logging config --- logging_config.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/logging_config.json b/logging_config.json index 0c03b92..4512f77 100644 --- a/logging_config.json +++ b/logging_config.json @@ -15,7 +15,30 @@ } }, "loggers": { + "ai": { + "level": "DEBUG", + "handlers": ["console"], + "propagate": false + }, + "actions": { + "level": "INFO", + "handlers": ["console"] + }, "actions.movement": { + "level": "ERROR", + "handlers": ["console"] + }, + "actions.tree": { + "level": "INFO", + "handlers": ["console"], + "propagate": false + }, + "events": { + "level": "WARN", + "handlers": ["console"], + "propagate": false + }, + "visible": { "level": "DEBUG", "handlers": ["console"] } From e4f8aa5e80f049678bb8d33f769a56115539aa35 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:48:28 -0700 Subject: [PATCH 077/234] Clean up the __str__ for a few Action subclasses --- roguebasin/actions.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 7f26917..390dc85 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -101,6 +101,9 @@ class Action: '''Create an ActionResult indicating success with no follow-up''' return ActionResult(self, success=True) + def __str__(self) -> str: + return f'{self.__class__.__name__} for {self.actor!s}' + def __repr__(self): return f'{self.__class__.__name__}({self.actor!r})' @@ -127,6 +130,9 @@ class MoveAction(Action): def __repr__(self): return f'{self.__class__.__name__}({self.actor!r}, {self.direction!r})' + def __str__(self) -> str: + return f'{self.__class__.__name__} toward {self.direction} by {self.actor!s}' + class BumpAction(MoveAction): '''Attempt to perform a movement action in a direction. @@ -236,4 +242,4 @@ class DropItemAction(Action): def perform(self, engine: 'Engine') -> ActionResult: engine.entities.add(self.item) - return self.success() \ No newline at end of file + return self.success() From 70c17b6235a30f9484480844eb70ba5f9fb330ae Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:49:46 -0700 Subject: [PATCH 078/234] Condense the declaration of engine.hero; add a FIXME --- roguebasin/engine.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/roguebasin/engine.py b/roguebasin/engine.py index 679a168..fbe2895 100644 --- a/roguebasin/engine.py +++ b/roguebasin/engine.py @@ -45,10 +45,7 @@ class Engine: self.rng = tcod.random.Random() self.map = Map(configuration.map_size) - - first_room = self.map.generator.rooms[0] - hero_start_position = first_room.center - self.hero = Hero(position=hero_start_position) + self.hero = Hero(position=self.map.generator.rooms[0].center) self.entities: MutableSet[Entity] = {self.hero} for room in self.map.rooms: @@ -89,6 +86,7 @@ class Engine: def update_field_of_view(self) -> None: '''Compute visible area of the map based on the player's position and point of view.''' + # FIXME: Move this to the Map class self.map.visible[:] = tcod.map.compute_fov( self.map.tiles['transparent'], tuple(self.hero.position), From 4084d98efd715a57f97b384b5ce3c74d0b0caadc Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:53:26 -0700 Subject: [PATCH 079/234] Clean up Action processing a little bit - Sort entities by their Euclidean distance to the hero so actions from entities near the hero are processed first - Fewer local variables for cleaner reading - Pass hero into the RegerateRoomsAction, which was causing a pylint error --- roguebasin/events.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index a9d210c..09dfef2 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -32,18 +32,25 @@ class EventHandler(tcod.event.EventDispatch[Action]): '''Handle the given event. Transform that event into an Action via an EventHandler and perform it.''' action = self.dispatch(event) + # Unhandled event. Ignore it. if not action: + LOG.debug('Unhandled event: %s', event) return result = self.perform_action_until_done(action) - # Action failed, so do nothing further. + # Player's action failed, don't proceed with turn. if not result.success and result.done: return - # Copy the list so we only act on the entities that exist at the start of this turn - entities = list(self.engine.entities) - for ent in entities: + # Copy the list so we only act on the entities that exist at the start of this turn. Sort it by Euclidean + # distance to the Hero, so entities closer to the hero act first. + hero_position = self.engine.hero.position + entities = sorted( + self.engine.entities, + key=lambda e: e.position.euclidean_distance_to(hero_position)) + + for i, ent in enumerate(entities): if not isinstance(ent, Actor): continue @@ -62,18 +69,15 @@ class EventHandler(tcod.event.EventDispatch[Action]): LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) while not result.done: - alternate = result.alternate - assert alternate is not None, f'Action {result.action} incomplete but no alternate action given' + action = result.alternate + assert action is not None, f'Action {result.action} incomplete but no alternate action given' - result = alternate.perform(self.engine) + result = action.perform(self.engine) LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) if result.success: break - if result.done: - break - return result def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: @@ -103,7 +107,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): case tcod.event.KeySym.y: action = BumpAction(hero, Direction.NorthWest) case tcod.event.KeySym.SPACE: - action = RegenerateRoomsAction() + action = RegenerateRoomsAction(hero) case tcod.event.KeySym.PERIOD: action = WaitAction(hero) From d236b827cdc1c96f69fe9deab44e7daa7c1fb0a0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:55:22 -0700 Subject: [PATCH 080/234] Move most of the basic actions logs to debug (probably just for now) --- roguebasin/actions.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/roguebasin/actions.py b/roguebasin/actions.py index 390dc85..01fffaf 100644 --- a/roguebasin/actions.py +++ b/roguebasin/actions.py @@ -28,7 +28,7 @@ from .object import Actor, Item if TYPE_CHECKING: from .engine import Engine -LOG = logging.getLogger(__name__) +LOG = logging.getLogger('actions') class ActionResult: '''The result of an Action. @@ -158,12 +158,12 @@ class BumpAction(MoveAction): else: entity_occupying_position = None - LOG.info('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', - self.actor, - new_position, - position_is_in_bounds, - position_is_walkable, - entity_occupying_position) + LOG.debug('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', + self.actor, + new_position, + position_is_in_bounds, + position_is_walkable, + entity_occupying_position) if not position_is_in_bounds or not position_is_walkable: return self.failure() @@ -180,7 +180,7 @@ class WalkAction(MoveAction): def perform(self, engine: 'Engine') -> ActionResult: new_position = self.actor.position + self.direction - LOG.info('Moving %s to %s', self.actor, new_position) + LOG.debug('Moving %s to %s', self.actor, new_position) self.actor.position = new_position return self.success() @@ -200,11 +200,11 @@ class MeleeAction(MoveAction): return self.failure() damage = self.actor.fighter.attack_power - self.target.fighter.defense - if damage > 0: - LOG.info('%s attacks %s for %d damage!', self.actor, self.target, damage) + if damage > 0 and self.target: + LOG.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) self.target.fighter.hit_points -= damage else: - LOG.info('%s attacks %s but does no damage!', self.actor, self.target) + LOG.debug('%s attacks %s but does no damage!', self.actor, self.target) if self.target.fighter.is_dead: LOG.info('%s is dead!', self.target) @@ -216,18 +216,18 @@ class WaitAction(Action): '''Wait a turn''' def perform(self, engine: 'Engine') -> ActionResult: - LOG.info('%s is waiting a turn', self.actor) + LOG.debug('%s is waiting a turn', self.actor) return self.success() class DieAction(Action): '''Kill an Actor''' def perform(self, engine: 'Engine') -> ActionResult: - LOG.info('%s dies', self.actor) + LOG.debug('%s dies', self.actor) engine.entities.remove(self.actor) if self.actor.yields_corpse_on_death: - LOG.info('%s leaves a corpse behind', self.actor) + LOG.debug('%s leaves a corpse behind', self.actor) corpse = Item(kind=items.Corpse, name=f'{self.actor.name} Corpse', position=self.actor.position) return ActionResult(self, alternate=DropItemAction(self.actor, corpse)) From da3d30872b1658e39e5f65144c0d13f7773878d1 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:56:15 -0700 Subject: [PATCH 081/234] Add logging action handling in a tree-like fashion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These logs are available in the actions.tree logger. They'll print a helpful list of actions and their results in a tree-like way. For example: ``` 2022-05-12 08:57:57 actions.tree: Processing Hero Actions 2022-05-12 08:57:57 actions.tree: |-> @[(x:4, y:6)][30/30] 2022-05-12 08:57:57 actions.tree: | |-> BumpAction toward (δx:-1, δy:1) by @[(x:4, y:6)][30/30] => success=False done=False alternate=WalkAction[@] 2022-05-12 08:57:57 actions.tree: | `-> WalkAction toward (δx:-1, δy:1) by @[(x:3, y:7)][30/30] => success=True done=True alternate=None 2022-05-12 08:57:57 actions.tree: Processing Entity Actions 2022-05-12 08:57:57 actions.tree: |-> Orc with 10/10 hp at (x:4, y:5) 2022-05-12 08:57:57 actions.tree: | |-> BumpAction toward (δx:-1, δy:1) by Orc with 10/10 hp at (x:4, y:5) => success=False done=False alternate=WalkAction[o] 2022-05-12 08:57:57 actions.tree: | `-> WalkAction toward (δx:-1, δy:1) by Orc with 10/10 hp at (x:3, y:6) => success=True done=True alternate=None 2022-05-12 08:57:57 actions.tree: |-> Orc with 10/10 hp at (x:5, y:5) 2022-05-12 08:57:57 actions.tree: | |-> BumpAction toward (δx:-1, δy:1) by Orc with 10/10 hp at (x:5, y:5) => success=False done=False alternate=WalkAction[o] 2022-05-12 08:57:57 actions.tree: | `-> WalkAction toward (δx:-1, δy:1) by Orc with 10/10 hp at (x:4, y:6) => success=True done=True alternate=None ``` --- roguebasin/events.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/roguebasin/events.py b/roguebasin/events.py index 09dfef2..46295d2 100644 --- a/roguebasin/events.py +++ b/roguebasin/events.py @@ -15,6 +15,7 @@ if TYPE_CHECKING: from .engine import Engine LOG = logging.getLogger('events') +ACTIONS_TREE_LOG = logging.getLogger('actions.tree') class EventHandler(tcod.event.EventDispatch[Action]): '''Handler of `tcod` events''' @@ -37,6 +38,9 @@ class EventHandler(tcod.event.EventDispatch[Action]): LOG.debug('Unhandled event: %s', event) return + ACTIONS_TREE_LOG.info('Processing Hero Actions') + ACTIONS_TREE_LOG.info('|-> %s', action.actor) + result = self.perform_action_until_done(action) # Player's action failed, don't proceed with turn. @@ -50,6 +54,8 @@ class EventHandler(tcod.event.EventDispatch[Action]): self.engine.entities, key=lambda e: e.position.euclidean_distance_to(hero_position)) + ACTIONS_TREE_LOG.info('Processing Entity Actions') + for i, ent in enumerate(entities): if not isinstance(ent, Actor): continue @@ -58,6 +64,9 @@ class EventHandler(tcod.event.EventDispatch[Action]): if not ent_ai: continue + if self.engine.map.visible[tuple(ent.position)]: + ACTIONS_TREE_LOG.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent) + action = ent_ai.act(self.engine) self.perform_action_until_done(action) @@ -66,14 +75,36 @@ class EventHandler(tcod.event.EventDispatch[Action]): def perform_action_until_done(self, action: Action) -> ActionResult: '''Perform the given action and any alternate follow-up actions until the action chain is done.''' result = action.perform(self.engine) - LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) + + if ACTIONS_TREE_LOG.isEnabledFor(logging.INFO) and self.engine.map.visible[tuple(action.actor.position)]: + if result.alternate: + alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' + else: + alternate_string = str(result.alternate) + ACTIONS_TREE_LOG.info('| %s-> %s => success=%s done=%s alternate=%s', + '|' if not result.success or not result.done else '`', + action, + result.success, + result.done, + alternate_string) while not result.done: action = result.alternate assert action is not None, f'Action {result.action} incomplete but no alternate action given' result = action.perform(self.engine) - LOG.debug('Performed action success=%s done=%s alternate=%s', result.success, result.done, result.alternate) + + if ACTIONS_TREE_LOG.isEnabledFor(logging.INFO) and self.engine.map.visible[tuple(action.actor.position)]: + if result.alternate: + alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' + else: + alternate_string = str(result.alternate) + ACTIONS_TREE_LOG.info('| %s-> %s => success=%s done=%s alternate=%s', + '|' if not result.success or not result.done else '`', + action, + result.success, + result.done, + alternate_string) if result.success: break From 95cc3034b8393b6c7d7fa46679056c7c8cd695d4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:58:51 -0700 Subject: [PATCH 082/234] Clean up unused imports in ai.py --- roguebasin/ai.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roguebasin/ai.py b/roguebasin/ai.py index eb54f5c..850fc6a 100644 --- a/roguebasin/ai.py +++ b/roguebasin/ai.py @@ -1,14 +1,13 @@ # Eryn Wells import logging -from os import path import random from typing import TYPE_CHECKING, List, Optional import numpy as np import tcod -from .actions import Action, BumpAction, MeleeAction, WaitAction +from .actions import Action, BumpAction, WaitAction from .components import Component from .geometry import Direction, Point from .object import Entity From 7a5f131973dbcb9708dc4cf14c89d17c70e4f1da Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:59:24 -0700 Subject: [PATCH 083/234] Log AI for entities that are visible to the hero to the `ai` logger --- roguebasin/ai.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/roguebasin/ai.py b/roguebasin/ai.py index 850fc6a..75ca3d3 100644 --- a/roguebasin/ai.py +++ b/roguebasin/ai.py @@ -15,7 +15,7 @@ from .object import Entity if TYPE_CHECKING: from .engine import Engine -LOG = logging.getLogger(__name__) +LOG = logging.getLogger('ai') class AI(Component): def __init__(self, entity: Entity) -> None: @@ -33,16 +33,26 @@ class HostileEnemy(AI): pov=tuple(self.entity.position), radius=self.entity.sight_radius) + if engine.map.visible[tuple(self.entity.position)]: + LOG.debug("AI for %s", self.entity) + hero_position = engine.hero.position hero_is_visible = visible_tiles[hero_position.x, hero_position.y] if hero_is_visible: path_to_hero = self.get_path_to(hero_position, engine) + assert len(path_to_hero) > 0, f'{self.entity} attempting to find a path to hero while on top of the hero!' + entity_position = self.entity.position - next_position = path_to_hero.pop(0) if len(path_to_hero) else hero_position + if engine.map.visible[tuple(self.entity.position)]: + LOG.debug('|-> Path to hero %s', path_to_hero) + + next_position = path_to_hero.pop(0) if len(path_to_hero) > 1 else hero_position direction_to_next_position = entity_position.direction_to_adjacent_point(next_position) - LOG.info('Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position) + + if engine.map.visible[tuple(self.entity.position)]: + LOG.info('`-> Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position) return BumpAction(self.entity, direction_to_next_position) else: @@ -57,12 +67,14 @@ class HostileEnemy(AI): overlaps_existing_entity = any(new_position == ent.position for ent in engine.entities) tile_is_walkable = engine.map.tile_is_walkable(new_position) if not overlaps_existing_entity and tile_is_walkable: - LOG.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) + if engine.map.visible[tuple(self.entity.position)]: + LOG.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) action = BumpAction(self.entity, direction) break else: # If this entity somehow can't move anywhere, just wait - LOG.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity) + if engine.map.visible[tuple(self.entity.position)]: + LOG.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity) action = WaitAction(self.entity) return action From 0e917ac111aaa535e068668072e2d4cffdde2bd2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 08:59:36 -0700 Subject: [PATCH 084/234] Move the ai log config to ERROR level --- logging_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logging_config.json b/logging_config.json index 4512f77..a770168 100644 --- a/logging_config.json +++ b/logging_config.json @@ -16,7 +16,7 @@ }, "loggers": { "ai": { - "level": "DEBUG", + "level": "ERROR", "handlers": ["console"], "propagate": false }, From 1244c97493715a198fef1056598ddd3a98ffe281 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 09:02:15 -0700 Subject: [PATCH 085/234] Move the VS Code workspace to LYARLFGG --- tcod.code-workspace => lyarlfgg.code-workspace | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tcod.code-workspace => lyarlfgg.code-workspace (100%) diff --git a/tcod.code-workspace b/lyarlfgg.code-workspace similarity index 100% rename from tcod.code-workspace rename to lyarlfgg.code-workspace From cc6c701c594feab9fc15f257731d679e96390cfe Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 09:02:53 -0700 Subject: [PATCH 086/234] Move first steps scripts to their own directory --- 01_fixed_size_console.py => first_steps/01_fixed_size_console.py | 0 .../02_dynamically_sized_console.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename 01_fixed_size_console.py => first_steps/01_fixed_size_console.py (100%) rename 02_dynamically_sized_console.py => first_steps/02_dynamically_sized_console.py (100%) diff --git a/01_fixed_size_console.py b/first_steps/01_fixed_size_console.py similarity index 100% rename from 01_fixed_size_console.py rename to first_steps/01_fixed_size_console.py diff --git a/02_dynamically_sized_console.py b/first_steps/02_dynamically_sized_console.py similarity index 100% rename from 02_dynamically_sized_console.py rename to first_steps/02_dynamically_sized_console.py From f6fe9d0f09774665e56d864d7a581da57c2b26d8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 09:05:27 -0700 Subject: [PATCH 087/234] Move the roguebasin package to erynrl --- .vscode/launch.json | 4 ++-- {roguebasin => erynrl}/__init__.py | 0 {roguebasin => erynrl}/__main__.py | 0 {roguebasin => erynrl}/actions.py | 0 {roguebasin => erynrl}/ai.py | 0 {roguebasin => erynrl}/components.py | 0 {roguebasin => erynrl}/engine.py | 0 {roguebasin => erynrl}/events.py | 0 {roguebasin => erynrl}/geometry.py | 0 {roguebasin => erynrl}/items.py | 0 {roguebasin => erynrl}/map.py | 0 {roguebasin => erynrl}/monsters.py | 0 {roguebasin => erynrl}/object.py | 0 {roguebasin => erynrl}/tile.py | 0 14 files changed, 2 insertions(+), 2 deletions(-) rename {roguebasin => erynrl}/__init__.py (100%) rename {roguebasin => erynrl}/__main__.py (100%) rename {roguebasin => erynrl}/actions.py (100%) rename {roguebasin => erynrl}/ai.py (100%) rename {roguebasin => erynrl}/components.py (100%) rename {roguebasin => erynrl}/engine.py (100%) rename {roguebasin => erynrl}/events.py (100%) rename {roguebasin => erynrl}/geometry.py (100%) rename {roguebasin => erynrl}/items.py (100%) rename {roguebasin => erynrl}/map.py (100%) rename {roguebasin => erynrl}/monsters.py (100%) rename {roguebasin => erynrl}/object.py (100%) rename {roguebasin => erynrl}/tile.py (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 42a7be2..bfcf2c1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,8 +8,8 @@ "name": "Python: Module", "type": "python", "request": "launch", - "module": "roguebasin", + "module": "erynrl", "justMyCode": true } ] -} \ No newline at end of file +} diff --git a/roguebasin/__init__.py b/erynrl/__init__.py similarity index 100% rename from roguebasin/__init__.py rename to erynrl/__init__.py diff --git a/roguebasin/__main__.py b/erynrl/__main__.py similarity index 100% rename from roguebasin/__main__.py rename to erynrl/__main__.py diff --git a/roguebasin/actions.py b/erynrl/actions.py similarity index 100% rename from roguebasin/actions.py rename to erynrl/actions.py diff --git a/roguebasin/ai.py b/erynrl/ai.py similarity index 100% rename from roguebasin/ai.py rename to erynrl/ai.py diff --git a/roguebasin/components.py b/erynrl/components.py similarity index 100% rename from roguebasin/components.py rename to erynrl/components.py diff --git a/roguebasin/engine.py b/erynrl/engine.py similarity index 100% rename from roguebasin/engine.py rename to erynrl/engine.py diff --git a/roguebasin/events.py b/erynrl/events.py similarity index 100% rename from roguebasin/events.py rename to erynrl/events.py diff --git a/roguebasin/geometry.py b/erynrl/geometry.py similarity index 100% rename from roguebasin/geometry.py rename to erynrl/geometry.py diff --git a/roguebasin/items.py b/erynrl/items.py similarity index 100% rename from roguebasin/items.py rename to erynrl/items.py diff --git a/roguebasin/map.py b/erynrl/map.py similarity index 100% rename from roguebasin/map.py rename to erynrl/map.py diff --git a/roguebasin/monsters.py b/erynrl/monsters.py similarity index 100% rename from roguebasin/monsters.py rename to erynrl/monsters.py diff --git a/roguebasin/object.py b/erynrl/object.py similarity index 100% rename from roguebasin/object.py rename to erynrl/object.py diff --git a/roguebasin/tile.py b/erynrl/tile.py similarity index 100% rename from roguebasin/tile.py rename to erynrl/tile.py From 5d4e0cff3d76ecde252463d113e134c91c25b1f5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 20:29:44 -0700 Subject: [PATCH 088/234] Rename the launch config --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index bfcf2c1..7d612ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Python: Module", + "name": "ErynRL", "type": "python", "request": "launch", "module": "erynrl", From ce63c825b0485c16a9d400527a3e8c0844285aa0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 20:40:06 -0700 Subject: [PATCH 089/234] Move all the logging to log.py and prefix all the log names with "erynrl" --- erynrl/__main__.py | 44 ++++------------------------- erynrl/actions.py | 30 +++++++++----------- erynrl/ai.py | 14 ++++----- erynrl/engine.py | 6 ++-- erynrl/events.py | 33 ++++++++++------------ erynrl/log.py | 69 +++++++++++++++++++++++++++++++++++++++++++++ erynrl/map.py | 20 ++++++------- logging_config.json | 12 ++++---- 8 files changed, 126 insertions(+), 102 deletions(-) create mode 100644 erynrl/log.py diff --git a/erynrl/__main__.py b/erynrl/__main__.py index a396d7a..cee3559 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -1,21 +1,16 @@ # Eryn Wells import argparse -import json -import logging -import logging.config import os.path import sys import tcod +from . import log from .engine import Configuration, Engine from .events import EventHandler from .geometry import Size -LOG = logging.getLogger('main') - CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50 MAP_WIDTH, MAP_HEIGHT = 80, 45 - FONT = 'terminal16x16_gs_ro.png' def parse_args(argv, *a, **kw): @@ -24,23 +19,6 @@ def parse_args(argv, *a, **kw): args = parser.parse_args(argv) return args -def init_logging(args): - '''Set up the logging system by (preferrably) reading a logging configuration file.''' - logging_config_path = find_logging_config() - if logging_config_path: - with open(logging_config_path, encoding='utf-8') as logging_config_file: - logging_config = json.load(logging_config_file) - logging.config.dictConfig(logging_config) - LOG.info('Found logging configuration at %s', logging_config_path) - else: - root_logger = logging.getLogger('') - root_logger.setLevel(logging.DEBUG if args.debug else logging.INFO) - - stderr_handler = logging.StreamHandler() - stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) - - root_logger.addHandler(stderr_handler) - def walk_up_directories_of_path(path): while path and path != '/': path = os.path.dirname(path) @@ -51,38 +29,26 @@ def find_fonts_directory(): for parent_dir in walk_up_directories_of_path(__file__): possible_fonts_dir = os.path.join(parent_dir, 'fonts') if os.path.isdir(possible_fonts_dir): - LOG.info('Found fonts dir %s', possible_fonts_dir) + log.ROOT.info('Found fonts dir %s', possible_fonts_dir) break else: return None return possible_fonts_dir -def find_logging_config(): - '''Walk up the filesystem from this script to find a logging_config.json''' - for parent_dir in walk_up_directories_of_path(__file__): - possible_logging_config_file = os.path.join(parent_dir, 'logging_config.json') - if os.path.isfile(possible_logging_config_file): - LOG.info('Found logging config file %s', possible_logging_config_file) - break - else: - return None - - return possible_logging_config_file - def main(argv): args = parse_args(argv[1:], prog=argv[0]) - init_logging(args) + log.init() fonts_directory = find_fonts_directory() if not fonts_directory: - LOG.error("Couldn't find a fonts/ directory") + log.ROOT.error("Couldn't find a fonts/ directory") return -1 font = os.path.join(fonts_directory, FONT) if not os.path.isfile(font): - LOG.error("Font file %s doesn't exist", font) + log.ROOT.error("Font file %s doesn't exist", font) return -1 tileset = tcod.tileset.load_tilesheet(font, 16, 16, tcod.tileset.CHARMAP_CP437) diff --git a/erynrl/actions.py b/erynrl/actions.py index 01fffaf..ca3f447 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -18,18 +18,16 @@ Action : Base class of all actions WaitAction ''' -import logging from typing import Optional, TYPE_CHECKING from . import items +from . import log from .geometry import Direction from .object import Actor, Item if TYPE_CHECKING: from .engine import Engine -LOG = logging.getLogger('actions') - class ActionResult: '''The result of an Action. @@ -158,12 +156,12 @@ class BumpAction(MoveAction): else: entity_occupying_position = None - LOG.debug('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', - self.actor, - new_position, - position_is_in_bounds, - position_is_walkable, - entity_occupying_position) + log.ACTIONS.debug('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', + self.actor, + new_position, + position_is_in_bounds, + position_is_walkable, + entity_occupying_position) if not position_is_in_bounds or not position_is_walkable: return self.failure() @@ -180,7 +178,7 @@ class WalkAction(MoveAction): def perform(self, engine: 'Engine') -> ActionResult: new_position = self.actor.position + self.direction - LOG.debug('Moving %s to %s', self.actor, new_position) + log.ACTIONS.debug('Moving %s to %s', self.actor, new_position) self.actor.position = new_position return self.success() @@ -201,13 +199,13 @@ class MeleeAction(MoveAction): damage = self.actor.fighter.attack_power - self.target.fighter.defense if damage > 0 and self.target: - LOG.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) + log.ACTIONS.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) self.target.fighter.hit_points -= damage else: - LOG.debug('%s attacks %s but does no damage!', self.actor, self.target) + log.ACTIONS.debug('%s attacks %s but does no damage!', self.actor, self.target) if self.target.fighter.is_dead: - LOG.info('%s is dead!', self.target) + log.ACTIONS.info('%s is dead!', self.target) return ActionResult(self, alternate=DieAction(self.target)) return self.success() @@ -216,18 +214,18 @@ class WaitAction(Action): '''Wait a turn''' def perform(self, engine: 'Engine') -> ActionResult: - LOG.debug('%s is waiting a turn', self.actor) + log.ACTIONS.debug('%s is waiting a turn', self.actor) return self.success() class DieAction(Action): '''Kill an Actor''' def perform(self, engine: 'Engine') -> ActionResult: - LOG.debug('%s dies', self.actor) + log.ACTIONS.debug('%s dies', self.actor) engine.entities.remove(self.actor) if self.actor.yields_corpse_on_death: - LOG.debug('%s leaves a corpse behind', self.actor) + log.ACTIONS.debug('%s leaves a corpse behind', self.actor) corpse = Item(kind=items.Corpse, name=f'{self.actor.name} Corpse', position=self.actor.position) return ActionResult(self, alternate=DropItemAction(self.actor, corpse)) diff --git a/erynrl/ai.py b/erynrl/ai.py index 75ca3d3..3fc1904 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -1,12 +1,12 @@ # Eryn Wells -import logging import random from typing import TYPE_CHECKING, List, Optional import numpy as np import tcod +from . import log from .actions import Action, BumpAction, WaitAction from .components import Component from .geometry import Direction, Point @@ -15,8 +15,6 @@ from .object import Entity if TYPE_CHECKING: from .engine import Engine -LOG = logging.getLogger('ai') - class AI(Component): def __init__(self, entity: Entity) -> None: super().__init__() @@ -34,7 +32,7 @@ class HostileEnemy(AI): radius=self.entity.sight_radius) if engine.map.visible[tuple(self.entity.position)]: - LOG.debug("AI for %s", self.entity) + log.AI.debug("AI for %s", self.entity) hero_position = engine.hero.position hero_is_visible = visible_tiles[hero_position.x, hero_position.y] @@ -46,13 +44,13 @@ class HostileEnemy(AI): entity_position = self.entity.position if engine.map.visible[tuple(self.entity.position)]: - LOG.debug('|-> Path to hero %s', path_to_hero) + log.AI.debug('|-> Path to hero %s', path_to_hero) next_position = path_to_hero.pop(0) if len(path_to_hero) > 1 else hero_position direction_to_next_position = entity_position.direction_to_adjacent_point(next_position) if engine.map.visible[tuple(self.entity.position)]: - LOG.info('`-> Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position) + log.AI.info('`-> Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position) return BumpAction(self.entity, direction_to_next_position) else: @@ -68,13 +66,13 @@ class HostileEnemy(AI): tile_is_walkable = engine.map.tile_is_walkable(new_position) if not overlaps_existing_entity and tile_is_walkable: if engine.map.visible[tuple(self.entity.position)]: - LOG.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) + log.AI.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) action = BumpAction(self.entity, direction) break else: # If this entity somehow can't move anywhere, just wait if engine.map.visible[tuple(self.entity.position)]: - LOG.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity) + log.AI.info("Hero is NOT visible to %s and it can't move anywhere, waiting", self.entity) action = WaitAction(self.entity) return action diff --git a/erynrl/engine.py b/erynrl/engine.py index fbe2895..e168ae5 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -2,21 +2,19 @@ '''Defines the core game engine.''' -import logging import random from dataclasses import dataclass from typing import MutableSet import tcod +from . import log from . import monsters from .ai import HostileEnemy from .geometry import Size from .map import Map from .object import Entity, Hero, Monster -LOG = logging.getLogger('engine') - @dataclass class Configuration: map_size: Size @@ -66,7 +64,7 @@ class Engine: else: monster = Monster(monsters.Orc, ai_class=HostileEnemy, position=random_start_position) - LOG.info('Spawning %s', monster) + log.ENGINE.info('Spawning %s', monster) self.entities.add(monster) self.update_field_of_view() diff --git a/erynrl/events.py b/erynrl/events.py index 46295d2..d6d4d2a 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -2,11 +2,11 @@ '''Defines event handling mechanisms.''' -import logging from typing import Optional, TYPE_CHECKING import tcod +from . import log from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .geometry import Direction from .object import Actor @@ -14,9 +14,6 @@ from .object import Actor if TYPE_CHECKING: from .engine import Engine -LOG = logging.getLogger('events') -ACTIONS_TREE_LOG = logging.getLogger('actions.tree') - class EventHandler(tcod.event.EventDispatch[Action]): '''Handler of `tcod` events''' @@ -35,11 +32,11 @@ class EventHandler(tcod.event.EventDispatch[Action]): # Unhandled event. Ignore it. if not action: - LOG.debug('Unhandled event: %s', event) + log.EVENTS.debug('Unhandled event: %s', event) return - ACTIONS_TREE_LOG.info('Processing Hero Actions') - ACTIONS_TREE_LOG.info('|-> %s', action.actor) + log.ACTIONS_TREE.info('Processing Hero Actions') + log.ACTIONS_TREE.info('|-> %s', action.actor) result = self.perform_action_until_done(action) @@ -54,7 +51,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): self.engine.entities, key=lambda e: e.position.euclidean_distance_to(hero_position)) - ACTIONS_TREE_LOG.info('Processing Entity Actions') + log.ACTIONS_TREE.info('Processing Entity Actions') for i, ent in enumerate(entities): if not isinstance(ent, Actor): @@ -65,7 +62,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): continue if self.engine.map.visible[tuple(ent.position)]: - ACTIONS_TREE_LOG.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent) + log.ACTIONS_TREE.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent) action = ent_ai.act(self.engine) self.perform_action_until_done(action) @@ -76,17 +73,17 @@ class EventHandler(tcod.event.EventDispatch[Action]): '''Perform the given action and any alternate follow-up actions until the action chain is done.''' result = action.perform(self.engine) - if ACTIONS_TREE_LOG.isEnabledFor(logging.INFO) and self.engine.map.visible[tuple(action.actor.position)]: + if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.engine.map.visible[tuple(action.actor.position)]: if result.alternate: alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' else: alternate_string = str(result.alternate) - ACTIONS_TREE_LOG.info('| %s-> %s => success=%s done=%s alternate=%s', - '|' if not result.success or not result.done else '`', - action, - result.success, - result.done, - alternate_string) + log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', + '|' if not result.success or not result.done else '`', + action, + result.success, + result.done, + alternate_string) while not result.done: action = result.alternate @@ -94,12 +91,12 @@ class EventHandler(tcod.event.EventDispatch[Action]): result = action.perform(self.engine) - if ACTIONS_TREE_LOG.isEnabledFor(logging.INFO) and self.engine.map.visible[tuple(action.actor.position)]: + if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.engine.map.visible[tuple(action.actor.position)]: if result.alternate: alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' else: alternate_string = str(result.alternate) - ACTIONS_TREE_LOG.info('| %s-> %s => success=%s done=%s alternate=%s', + log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', '|' if not result.success or not result.done else '`', action, result.success, diff --git a/erynrl/log.py b/erynrl/log.py new file mode 100644 index 0000000..d61295e --- /dev/null +++ b/erynrl/log.py @@ -0,0 +1,69 @@ +# Eryn Wells + +''' +Initializes and sets up +''' + +import json +import logging +import logging.config +import os.path + +# These are re-imports so clients of this module don't have to also import logging +# pylint: disable=unused-import +from logging import CRITICAL, DEBUG, ERROR, FATAL, INFO, NOTSET, WARN, WARNING + +def _log_name(*components): + return '.'.join(['erynrl'] + list(components)) + +ROOT = logging.getLogger(_log_name()) +AI = logging.getLogger(_log_name('ai')) +ACTIONS = logging.getLogger(_log_name('actions')) +ACTIONS_TREE = logging.getLogger(_log_name('actions', 'tree')) +ENGINE = logging.getLogger(_log_name('engine')) +EVENTS = logging.getLogger(_log_name('events')) +MAP = logging.getLogger(_log_name('map')) + +def walk_up_directories_of_path(path): + '''Walk up a path, yielding each directory, until the root of the filesystem is found''' + while path and path != '/': + if os.path.isdir(path): + yield path + path = os.path.dirname(path) + +def find_logging_config(): + '''Walk up the filesystem from this script to find a logging_config.json''' + for parent_dir in walk_up_directories_of_path(__file__): + possible_logging_config_file = os.path.join(parent_dir, 'logging_config.json') + if os.path.isfile(possible_logging_config_file): + ROOT.info('Found logging config file %s', possible_logging_config_file) + break + else: + return None + + return possible_logging_config_file + +def init(config_file=None): + ''' + Set up the logging system by (preferrably) reading a logging configuration file. + + Parameters + ---------- + config_file : str + Path to a file containing a Python logging configuration in JSON + ''' + logging_config_path = config_file if config_file else find_logging_config() + + if os.path.isfile(logging_config_path): + with open(logging_config_path, encoding='utf-8') as logging_config_file: + logging_config = json.load(logging_config_file) + logging.config.dictConfig(logging_config) + ROOT.info('Found logging configuration at %s', logging_config_path) + else: + root_logger = logging.getLogger('') + root_logger.setLevel(logging.DEBUG) + + stderr_handler = logging.StreamHandler() + stderr_handler.setFormatter(logging.Formatter("%(asctime)s %(name)s: %(message)s")) + + root_logger.addHandler(stderr_handler) diff --git a/erynrl/map.py b/erynrl/map.py index 2a993d3..33b3bbd 100644 --- a/erynrl/map.py +++ b/erynrl/map.py @@ -1,6 +1,5 @@ # Eryn Wells -import logging import random from dataclasses import dataclass from typing import Iterator, List, Optional @@ -8,11 +7,10 @@ from typing import Iterator, List, Optional import numpy as np import tcod +from . import log from .geometry import Direction, Point, Rect, Size from .tile import Empty, Floor, Shroud, Wall -LOG = logging.getLogger('map') - class Map: def __init__(self, size: Size): self.size = size @@ -120,7 +118,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): node_bounds = self.__rect_from_bsp_node(node) if node.children: - LOG.debug(node_bounds) + log.MAP.debug(node_bounds) left_room: RectangularRoom = getattr(node.children[0], room_attrname) right_room: RectangularRoom = getattr(node.children[1], room_attrname) @@ -128,8 +126,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): left_room_bounds = left_room.bounds right_room_bounds = right_room.bounds - LOG.debug(' left: %s, %s', node.children[0], left_room_bounds) - LOG.debug('right: %s, %s', node.children[1], right_room_bounds) + log.MAP.debug(' left: %s, %s', node.children[0], left_room_bounds) + log.MAP.debug('right: %s, %s', node.children[1], right_room_bounds) start_point = left_room_bounds.midpoint end_point = right_room_bounds.midpoint @@ -140,16 +138,16 @@ class RoomsAndCorridorsGenerator(MapGenerator): else: corner = Point(start_point.x, end_point.y) - LOG.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) - LOG.debug('|-> start: %s', left_room_bounds) - LOG.debug('`-> end: %s', right_room_bounds) + log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + log.MAP.debug('|-> start: %s', left_room_bounds) + log.MAP.debug('`-> end: %s', right_room_bounds) for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): tiles[x, y] = Floor for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): tiles[x, y] = Floor else: - LOG.debug('%s (room) %s', node_bounds, node) + log.MAP.debug('%s (room) %s', node_bounds, node) # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of @@ -162,7 +160,7 @@ class RoomsAndCorridorsGenerator(MapGenerator): node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) bounds = Rect(origin, size) - LOG.debug('`-> %s', bounds) + log.MAP.debug('`-> %s', bounds) room = RectangularRoom(bounds) setattr(node, room_attrname, room) diff --git a/logging_config.json b/logging_config.json index a770168..d42487e 100644 --- a/logging_config.json +++ b/logging_config.json @@ -15,30 +15,30 @@ } }, "loggers": { - "ai": { + "erynrl.ai": { "level": "ERROR", "handlers": ["console"], "propagate": false }, - "actions": { + "erynrl.actions": { "level": "INFO", "handlers": ["console"] }, - "actions.movement": { + "erynrl.actions.movement": { "level": "ERROR", "handlers": ["console"] }, - "actions.tree": { + "erynrl.actions.tree": { "level": "INFO", "handlers": ["console"], "propagate": false }, - "events": { + "erynrl.events": { "level": "WARN", "handlers": ["console"], "propagate": false }, - "visible": { + "erynrl.visible": { "level": "DEBUG", "handlers": ["console"] } From ee915bd7c1148884d13d92f349a5f0a66ac61427 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 20:40:40 -0700 Subject: [PATCH 090/234] Add a doc string and a terminal newline --- erynrl/__main__.py | 8 ++++++++ erynrl/ai.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index cee3559..768adec 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -37,6 +37,14 @@ def find_fonts_directory(): return possible_fonts_dir def main(argv): + ''' + Beginning of the game + + Parameters + ---------- + argv : List[str] + A standard argument list, most likely you'll get this from sys.argv + ''' args = parse_args(argv[1:], prog=argv[0]) log.init() diff --git a/erynrl/ai.py b/erynrl/ai.py index 3fc1904..5653ecc 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -118,4 +118,4 @@ class HostileEnemy(AI): path: List[List[int]] = pathfinder.path_to(tuple(point))[1:].tolist() # Convert from List[List[int]] to List[Tuple[int, int]]. - return [Point(index[0], index[1]) for index in path] \ No newline at end of file + return [Point(index[0], index[1]) for index in path] From 2b367c7bb6352e259558c9458eec1b4e4a5eeb1b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 12 May 2022 20:40:46 -0700 Subject: [PATCH 091/234] Quit the game on Escape --- erynrl/events.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erynrl/events.py b/erynrl/events.py index d6d4d2a..f331b21 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -134,6 +134,8 @@ class EventHandler(tcod.event.EventDispatch[Action]): action = BumpAction(hero, Direction.NorthEast) case tcod.event.KeySym.y: action = BumpAction(hero, Direction.NorthWest) + case tcod.event.KeySym.ESCAPE: + action = ExitAction(hero) case tcod.event.KeySym.SPACE: action = RegenerateRoomsAction(hero) case tcod.event.KeySym.PERIOD: From bd5e1bc3c15ab7e9a02230cd29c0023619c61ce4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 13 May 2022 08:18:46 -0700 Subject: [PATCH 092/234] Rename the VS Code Workspace (again) --- lyarlfgg.code-workspace => going_rogue.code-workspace | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lyarlfgg.code-workspace => going_rogue.code-workspace (100%) diff --git a/lyarlfgg.code-workspace b/going_rogue.code-workspace similarity index 100% rename from lyarlfgg.code-workspace rename to going_rogue.code-workspace From c44c4e7bc66c9b4b44b67b8018f981c1f5461c1c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 14 May 2022 23:38:29 -0700 Subject: [PATCH 093/234] Move the event loop to Engine.run_event_loop() --- erynrl/__main__.py | 9 +-------- erynrl/engine.py | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 768adec..484ba64 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -6,7 +6,6 @@ import sys import tcod from . import log from .engine import Configuration, Engine -from .events import EventHandler from .geometry import Size CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50 @@ -64,15 +63,9 @@ def main(argv): configuration = Configuration(map_size=Size(MAP_WIDTH, MAP_HEIGHT)) engine = Engine(configuration) - event_handler = EventHandler(engine) with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: - while True: - console.clear() - engine.print_to_console(console) - context.present(console) - - event_handler.wait_for_events() + engine.run_event_loop(context, console) def run_until_exit(): ''' diff --git a/erynrl/engine.py b/erynrl/engine.py index e168ae5..52663ca 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -4,17 +4,21 @@ import random from dataclasses import dataclass -from typing import MutableSet +from typing import TYPE_CHECKING, MutableSet, NoReturn import tcod from . import log from . import monsters from .ai import HostileEnemy +from .events import MainGameEventHandler from .geometry import Size from .map import Map from .object import Entity, Hero, Monster +if TYPE_CHECKING: + from .events import EventHandler + @dataclass class Configuration: map_size: Size @@ -45,6 +49,8 @@ class Engine: self.map = Map(configuration.map_size) self.hero = Hero(position=self.map.generator.rooms[0].center) + self.event_handler: 'EventHandler' = MainGameEventHandler(self) + self.entities: MutableSet[Entity] = {self.hero} for room in self.map.rooms: should_spawn_monster_chance = random.random() @@ -82,6 +88,15 @@ class Engine: continue ent.print_to_console(console) + def run_event_loop(self, context: tcod.context.Context, console: tcod.Console) -> NoReturn: + '''Run the event loop forever. This method never returns.''' + while True: + console.clear() + self.print_to_console(console) + context.present(console) + + self.event_handler.wait_for_events() + def update_field_of_view(self) -> None: '''Compute visible area of the map based on the player's position and point of view.''' # FIXME: Move this to the Map class From 5b0b33782f73d23b34ab42f0ea91ede75abe265c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 14 May 2022 23:41:43 -0700 Subject: [PATCH 094/234] Move handling hero actions and entity actions to the Engine --- erynrl/engine.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++- erynrl/events.py | 71 ++-------------------------------------------- 2 files changed, 76 insertions(+), 69 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 52663ca..9573973 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -10,11 +10,12 @@ import tcod from . import log from . import monsters +from .actions import Action, ActionResult from .ai import HostileEnemy from .events import MainGameEventHandler from .geometry import Size from .map import Map -from .object import Entity, Hero, Monster +from .object import Actor, Entity, Hero, Monster if TYPE_CHECKING: from .events import EventHandler @@ -97,6 +98,77 @@ class Engine: self.event_handler.wait_for_events() + def process_hero_action(self, action: Action) -> ActionResult: + '''Process an Action for the hero.''' + log.ACTIONS_TREE.info('Processing Hero Actions') + log.ACTIONS_TREE.info('|-> %s', action.actor) + + return self._perform_action_until_done(action) + + def process_entity_actions(self): + hero_position = self.hero.position + + # Copy the list so we only act on the entities that exist at the start of this turn. Sort it by Euclidean + # distance to the Hero, so entities closer to the hero act first. + entities = sorted( + self.entities, + key=lambda e: e.position.euclidean_distance_to(hero_position)) + + log.ACTIONS_TREE.info('Processing Entity Actions') + + for i, ent in enumerate(entities): + if not isinstance(ent, Actor): + continue + + ent_ai = ent.ai + if not ent_ai: + continue + + if self.map.visible[tuple(ent.position)]: + log.ACTIONS_TREE.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent) + + action = ent_ai.act(self) + self._perform_action_until_done(action) + + def _perform_action_until_done(self, action: Action) -> ActionResult: + '''Perform the given action and any alternate follow-up actions until the action chain is done.''' + result = action.perform(self) + + if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.map.visible[tuple(action.actor.position)]: + if result.alternate: + alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' + else: + alternate_string = str(result.alternate) + log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', + '|' if not result.success or not result.done else '`', + action, + result.success, + result.done, + alternate_string) + + while not result.done: + action = result.alternate + assert action is not None, f'Action {result.action} incomplete but no alternate action given' + + result = action.perform(self) + + if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.map.visible[tuple(action.actor.position)]: + if result.alternate: + alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' + else: + alternate_string = str(result.alternate) + log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', + '|' if not result.success or not result.done else '`', + action, + result.success, + result.done, + alternate_string) + + if result.success: + break + + return result + def update_field_of_view(self) -> None: '''Compute visible area of the map based on the player's position and point of view.''' # FIXME: Move this to the Map class diff --git a/erynrl/events.py b/erynrl/events.py index f331b21..bd940ed 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -7,9 +7,8 @@ from typing import Optional, TYPE_CHECKING import tcod from . import log -from .actions import Action, ActionResult, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction +from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .geometry import Direction -from .object import Actor if TYPE_CHECKING: from .engine import Engine @@ -35,79 +34,15 @@ class EventHandler(tcod.event.EventDispatch[Action]): log.EVENTS.debug('Unhandled event: %s', event) return - log.ACTIONS_TREE.info('Processing Hero Actions') - log.ACTIONS_TREE.info('|-> %s', action.actor) - - result = self.perform_action_until_done(action) + result = self.engine.process_hero_action(action) # Player's action failed, don't proceed with turn. if not result.success and result.done: return - # Copy the list so we only act on the entities that exist at the start of this turn. Sort it by Euclidean - # distance to the Hero, so entities closer to the hero act first. - hero_position = self.engine.hero.position - entities = sorted( - self.engine.entities, - key=lambda e: e.position.euclidean_distance_to(hero_position)) - - log.ACTIONS_TREE.info('Processing Entity Actions') - - for i, ent in enumerate(entities): - if not isinstance(ent, Actor): - continue - - ent_ai = ent.ai - if not ent_ai: - continue - - if self.engine.map.visible[tuple(ent.position)]: - log.ACTIONS_TREE.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent) - - action = ent_ai.act(self.engine) - self.perform_action_until_done(action) - + self.engine.process_entity_actions() self.engine.update_field_of_view() - def perform_action_until_done(self, action: Action) -> ActionResult: - '''Perform the given action and any alternate follow-up actions until the action chain is done.''' - result = action.perform(self.engine) - - if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.engine.map.visible[tuple(action.actor.position)]: - if result.alternate: - alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' - else: - alternate_string = str(result.alternate) - log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', - '|' if not result.success or not result.done else '`', - action, - result.success, - result.done, - alternate_string) - - while not result.done: - action = result.alternate - assert action is not None, f'Action {result.action} incomplete but no alternate action given' - - result = action.perform(self.engine) - - if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.engine.map.visible[tuple(action.actor.position)]: - if result.alternate: - alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' - else: - alternate_string = str(result.alternate) - log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', - '|' if not result.success or not result.done else '`', - action, - result.success, - result.done, - alternate_string) - - if result.success: - break - - return result - def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: return ExitAction(self.engine.hero) From e5b3cbd2cdc901389d3e1cf6fc16b61ae6b5b99c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 14 May 2022 23:43:04 -0700 Subject: [PATCH 095/234] Prepare for a GameOver state - Factor a bunch of event handling into a base EventHandler class - Rename the previous event handler MainGameEventHandler - Add a GameOverEventHandler that only responds to Exit actions --- erynrl/events.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/erynrl/events.py b/erynrl/events.py index bd940ed..9c82c9c 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from .engine import Engine class EventHandler(tcod.event.EventDispatch[Action]): - '''Handler of `tcod` events''' + '''Abstract event handler class''' def __init__(self, engine: 'Engine'): super().__init__() @@ -26,7 +26,15 @@ class EventHandler(tcod.event.EventDispatch[Action]): self.handle_event(event) def handle_event(self, event: tcod.event.Event) -> None: - '''Handle the given event. Transform that event into an Action via an EventHandler and perform it.''' + ''' + Handle an event by transforming it into an Action and processing it until it is completed. If the Action + succeeds, also process actions from other Entities. + + Parameters + ---------- + event : tcod.event.Event + The event to handle + ''' action = self.dispatch(event) # Unhandled event. Ignore it. @@ -46,6 +54,14 @@ class EventHandler(tcod.event.EventDispatch[Action]): def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: return ExitAction(self.engine.hero) +class MainGameEventHandler(EventHandler): + ''' + Handler of `tcod` events for the main game. + + Receives input from the player and dispatches actions to the game engine to interat with the hero and other objects + in the game. + ''' + def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: action: Optional[Action] = None @@ -77,3 +93,18 @@ class EventHandler(tcod.event.EventDispatch[Action]): action = WaitAction(hero) return action + +class GameOverEventHandler(EventHandler): + '''When the game is over (the hero dies, the player quits, etc), this event handler takes over.''' + + def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: + action: Optional[Action] = None + + hero = self.engine.hero + + sym = event.sym + match sym: + case tcod.event.KeySym.ESCAPE: + action = ExitAction(hero) + + return action \ No newline at end of file From 388754e5ddb50d0cdb8a05a58a43443a08b1ec76 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 14 May 2022 23:43:38 -0700 Subject: [PATCH 096/234] When the Hero dies, swap MainGameEventHandler for GameOverEventHandler --- erynrl/actions.py | 3 +-- erynrl/engine.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/erynrl/actions.py b/erynrl/actions.py index ca3f447..790acaa 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -221,8 +221,7 @@ class DieAction(Action): '''Kill an Actor''' def perform(self, engine: 'Engine') -> ActionResult: - log.ACTIONS.debug('%s dies', self.actor) - engine.entities.remove(self.actor) + engine.kill_actor(self.actor) if self.actor.yields_corpse_on_death: log.ACTIONS.debug('%s leaves a corpse behind', self.actor) diff --git a/erynrl/engine.py b/erynrl/engine.py index 9573973..4b5125b 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -12,7 +12,7 @@ from . import log from . import monsters from .actions import Action, ActionResult from .ai import HostileEnemy -from .events import MainGameEventHandler +from .events import GameOverEventHandler, MainGameEventHandler from .geometry import Size from .map import Map from .object import Actor, Entity, Hero, Monster @@ -179,3 +179,15 @@ class Engine: # Visible tiles should be added to the explored list self.map.explored |= self.map.visible + + def kill_actor(self, actor: Actor) -> None: + '''Kill an entity. Remove it from the game.''' + + if actor == self.hero: + # When the hero dies, the game is over. + # TODO: Transition to game over state + log.ACTIONS.debug('Time to die.') + self.event_handler = GameOverEventHandler(self) + else: + log.ACTIONS.debug('%s dies', actor) + self.entities.remove(actor) From 5a9df0a3223a7e0a0c6aba1f5287e17f4c8d04c8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 00:11:52 -0700 Subject: [PATCH 097/234] Add my first interface element: a Bar! It renders a bar of a certain width and a percentage full. Use it to render HP. Add a new Python package called interface. The Bar class lives there. Also add a bunch of color defintions to a module called interface.color. --- erynrl/engine.py | 11 +++++++++-- erynrl/interface/__init__.py | 0 erynrl/interface/bar.py | 27 +++++++++++++++++++++++++++ erynrl/interface/color.py | 10 ++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 erynrl/interface/__init__.py create mode 100644 erynrl/interface/bar.py create mode 100644 erynrl/interface/color.py diff --git a/erynrl/engine.py b/erynrl/engine.py index 4b5125b..db237f1 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -13,7 +13,9 @@ from . import monsters from .actions import Action, ActionResult from .ai import HostileEnemy from .events import GameOverEventHandler, MainGameEventHandler -from .geometry import Size +from .geometry import Point, Size +from .interface.bar import Bar +from .interface import color from .map import Map from .object import Actor, Entity, Hero, Monster @@ -76,12 +78,17 @@ class Engine: self.update_field_of_view() + self.hit_points_bar = Bar(position=Point(4, 47), width=20) + def print_to_console(self, console): '''Print the whole game to the given console.''' self.map.print_to_console(console) + console.print(x=1, y=47, string=f'HP:') hp, max_hp = self.hero.fighter.hit_points, self.hero.fighter.maximum_hit_points - console.print(x=1, y=47, string=f'HP: {hp}/{max_hp}') + self.hit_points_bar.percent_filled = hp / max_hp + self.hit_points_bar.render_to_console(console) + console.print(x=6, y=47, string=f'{hp}/{max_hp}', fg=color.WHITE) for ent in sorted(self.entities, key=lambda e: e.render_order.value): # Only print entities that are in the field of view diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erynrl/interface/bar.py b/erynrl/interface/bar.py new file mode 100644 index 0000000..3bc12df --- /dev/null +++ b/erynrl/interface/bar.py @@ -0,0 +1,27 @@ +# Eryn Wells + +from . import color +from ..geometry import Point + +class Bar: + def __init__(self, *, position: Point, width: int): + self.position = position + self.width = width + + self._percent_filled = 1.0 + + @property + def percent_filled(self) -> float: + '''The percentage of this bar that should be filled, as a value between 0.0 and 1.0.''' + return self._percent_filled + + @percent_filled.setter + def percent_filled(self, value): + self._percent_filled = min(1, max(0, value)) + + def render_to_console(self, console): + console.draw_rect(x=self.position.x, y=self.position.y, width=self.width, height=1, ch=1, bg=color.GREY10) + + if self._percent_filled > 0: + filled_width = round(self._percent_filled * self.width) + console.draw_rect(x=self.position.x, y=self.position.y, width=filled_width, height=1, ch=1, bg=color.GREY50) diff --git a/erynrl/interface/color.py b/erynrl/interface/color.py new file mode 100644 index 0000000..3dfd44f --- /dev/null +++ b/erynrl/interface/color.py @@ -0,0 +1,10 @@ +# Eryn Wells + +''' +A bunch of colors. +''' + +BLACK = (0, 0, 0) +WHITE = (255, 255, 255) +GREY10 = (26, 26, 26) +GREY50 = (128, 128, 128) \ No newline at end of file From 090272854d3cceeb11fe7d846055bbfdc96fc088 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 13:13:12 -0700 Subject: [PATCH 098/234] Add a turn count that increments after successfully handling actions for that turn --- erynrl/engine.py | 54 +++++++++++++++++++++++++++++++++++++++++++----- erynrl/events.py | 9 +------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index db237f1..638f635 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -48,12 +48,16 @@ class Engine: def __init__(self, configuration: Configuration): self.configuration = configuration + self.current_turn = 1 + self.did_begin_turn = False + self.did_successfully_process_actions_for_turn = False + self.rng = tcod.random.Random() self.map = Map(configuration.map_size) - self.hero = Hero(position=self.map.generator.rooms[0].center) self.event_handler: 'EventHandler' = MainGameEventHandler(self) + self.hero = Hero(position=self.map.generator.rooms[0].center) self.entities: MutableSet[Entity] = {self.hero} for room in self.map.rooms: should_spawn_monster_chance = random.random() @@ -78,18 +82,21 @@ class Engine: self.update_field_of_view() + # Interface elements self.hit_points_bar = Bar(position=Point(4, 47), width=20) def print_to_console(self, console): '''Print the whole game to the given console.''' self.map.print_to_console(console) - console.print(x=1, y=47, string=f'HP:') + console.print(x=1, y=47, string='HP:') hp, max_hp = self.hero.fighter.hit_points, self.hero.fighter.maximum_hit_points self.hit_points_bar.percent_filled = hp / max_hp self.hit_points_bar.render_to_console(console) console.print(x=6, y=47, string=f'{hp}/{max_hp}', fg=color.WHITE) + console.print(x=1, y=48, string=f'Turn: {self.current_turn}') + for ent in sorted(self.entities, key=lambda e: e.render_order.value): # Only print entities that are in the field of view if not self.map.visible[tuple(ent.position)]: @@ -103,16 +110,29 @@ class Engine: self.print_to_console(console) context.present(console) + self.begin_turn() self.event_handler.wait_for_events() + self.finish_turn() + + def process_input_action(self, action: Action) -> ActionResult: + '''Process an Action from player input''' - def process_hero_action(self, action: Action) -> ActionResult: - '''Process an Action for the hero.''' log.ACTIONS_TREE.info('Processing Hero Actions') log.ACTIONS_TREE.info('|-> %s', action.actor) - return self._perform_action_until_done(action) + result = self._perform_action_until_done(action) + + # Player's action failed, don't proceed with turn. + if not result.success and result.done: + self.did_successfully_process_actions_for_turn = False + return + + self.did_successfully_process_actions_for_turn = True + self.process_entity_actions() + self.update_field_of_view() def process_entity_actions(self): + '''Run AI for entities that have them, and process actions from those AIs''' hero_position = self.hero.position # Copy the list so we only act on the entities that exist at the start of this turn. Sort it by Euclidean @@ -187,6 +207,30 @@ class Engine: # Visible tiles should be added to the explored list self.map.explored |= self.map.visible + def begin_turn(self) -> None: + '''Begin the current turn''' + if self.did_begin_turn: + return + + if log.ROOT.isEnabledFor(log.INFO): + dashes = '-' * 20 + log.ROOT.info('%s Turn %d %s', dashes, self.current_turn, dashes) + + self.did_begin_turn = True + + def finish_turn(self) -> None: + '''Finish the current turn and prepare for the next turn''' + if not self.did_successfully_process_actions_for_turn: + return + + log.ROOT.info('Completed turn %d successfully', self.current_turn) + self._prepare_for_next_turn() + + def _prepare_for_next_turn(self) -> None: + self.current_turn += 1 + self.did_begin_turn = False + self.did_successfully_process_actions_for_turn = False + def kill_actor(self, actor: Actor) -> None: '''Kill an entity. Remove it from the game.''' diff --git a/erynrl/events.py b/erynrl/events.py index 9c82c9c..25ccd4d 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -42,14 +42,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): log.EVENTS.debug('Unhandled event: %s', event) return - result = self.engine.process_hero_action(action) - - # Player's action failed, don't proceed with turn. - if not result.success and result.done: - return - - self.engine.process_entity_actions() - self.engine.update_field_of_view() + self.engine.process_input_action(action) def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: return ExitAction(self.engine.hero) From 8fed219af0e88c9bf6642577817073e1f902984d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 13:13:22 -0700 Subject: [PATCH 099/234] Add erynrl log config --- logging_config.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/logging_config.json b/logging_config.json index d42487e..fe7b5c4 100644 --- a/logging_config.json +++ b/logging_config.json @@ -15,6 +15,11 @@ } }, "loggers": { + "erynrl": { + "level": "INFO", + "handlers": ["console"], + "propagate": false + }, "erynrl.ai": { "level": "ERROR", "handlers": ["console"], From ff6763d3544a472d638814e6e337a19a3c87bf48 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 13:57:32 -0700 Subject: [PATCH 100/234] Add a unique identifier to all entities (a monotonically increasing integer) --- erynrl/object.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/erynrl/object.py b/erynrl/object.py index 70ee0a2..d9437d2 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -41,12 +41,16 @@ class Entity: True if this Entity blocks other Entities from moving through its position ''' + # A monotonically increasing identifier to help differentiate between entities that otherwise look identical + __NEXT_IDENTIFIER = 1 + def __init__(self, symbol: str, *, position: Optional[Point] = None, blocks_movement: Optional[bool] = True, render_order: RenderOrder = RenderOrder.ITEM, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None): + self.identifier = Entity.__NEXT_IDENTIFIER self.position = position if position else Point() self.foreground = fg if fg else (255, 255, 255) self.background = bg @@ -54,12 +58,14 @@ class Entity: self.blocks_movement = blocks_movement self.render_order = render_order + Entity.__NEXT_IDENTIFIER += 1 + def print_to_console(self, console: tcod.Console) -> None: '''Render this Entity to the console''' console.print(x=self.position.x, y=self.position.y, string=self.symbol, fg=self.foreground, bg=self.background) def __str__(self) -> str: - return f'{self.symbol}[{self.position}]' + return f'{self.symbol}!{self.identifier} at {self.position}' def __repr__(self) -> str: return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fg={self.foreground!r}, bg={self.background!r})' @@ -116,7 +122,7 @@ class Hero(Actor): return 8 def __str__(self) -> str: - return f'{self.symbol}[{self.position}][{self.fighter.hit_points}/{self.fighter.maximum_hit_points}]' + return f'Hero!{self.identifier} at {self.position} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp' class Monster(Actor): '''An instance of a Species''' @@ -150,7 +156,7 @@ class Monster(Actor): return True def __str__(self) -> str: - return f'{self.name} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp at {self.position}' + return f'{self.name}!{self.identifier} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp at {self.position}' class Item(Entity): '''An instance of an Item''' From 08ef1af4e469add901bfdebc7c3c05e032bdd1ca Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 13:58:06 -0700 Subject: [PATCH 101/234] Add a MessageLog that keeps a record of in-game events --- erynrl/messages.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 erynrl/messages.py diff --git a/erynrl/messages.py b/erynrl/messages.py new file mode 100644 index 0000000..9a59fde --- /dev/null +++ b/erynrl/messages.py @@ -0,0 +1,72 @@ +# Eryn Wells + +import textwrap +from typing import List, Optional, Reversible, Tuple + +import tcod + +from .geometry import Rect + +class Message: + def __init__(self, text: str, fg: Optional[Tuple[int, int, int]] = None): + self.text = text + self.foreground = fg + self.count = 1 + + @property + def full_text(self) -> str: + '''The full text of the message, including a count of repeats, if present''' + if self.count == 1: + return self.text + else: + return f'{self.text} (x{self.count})' + + def __str__(self) -> str: + return self.text + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({repr(self.text)}, fg={self.foreground})' + +class MessageLog: + '''A buffer of messages sent to the player by the game''' + + def __init__(self): + self.messages: List[Message] = [] + + def add_message(self, text: str, fg: Optional[Tuple[int, int, int]] = None, stack: bool = True): + ''' + Add a message to the buffer + + Parameters + ---------- + text : str + The text of the message + fg : Tuple[int, int, int], optional + A foreground color to render the text + stack : bool + If True and the previous message in the buffer is the same as the text given, increment the count of that + message rather than adding a new message to the buffer + ''' + if stack and self.messages and self.messages[-1].text == text: + self.messages[-1].count += 1 + else: + self.messages.append(Message(text, fg)) + + def render_to_console(self, console: tcod.console.Console, rect: Rect): + self.render_messages(console, rect, self.messages) + + @staticmethod + def render_messages(console: tcod.console.Console, rect: Rect, messages: Reversible[Message]): + y_offset = min(rect.size.height, len(messages)) + + for message in reversed(messages): + wrapped_text = textwrap.wrap(message.full_text, rect.size.width) + for line in wrapped_text: + console.print(x=rect.min_x, y=rect.min_y + y_offset - 1, string=line, fg=message.foreground) + y_offset -= 1 + + if y_offset < 0: + break + + if y_offset < 0: + break \ No newline at end of file From 72cbd15fb05f88fcc5617582c899fa2fbd8036fd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 13:58:26 -0700 Subject: [PATCH 102/234] Render the MessageLog and clean up the interface (lots of math errors here) --- erynrl/engine.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 638f635..e4108b9 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -13,10 +13,11 @@ from . import monsters from .actions import Action, ActionResult from .ai import HostileEnemy from .events import GameOverEventHandler, MainGameEventHandler -from .geometry import Point, Size +from .geometry import Point, Rect, Size from .interface.bar import Bar from .interface import color from .map import Map +from .messages import MessageLog from .object import Actor, Entity, Hero, Monster if TYPE_CHECKING: @@ -54,6 +55,7 @@ class Engine: self.rng = tcod.random.Random() self.map = Map(configuration.map_size) + self.message_log = MessageLog() self.event_handler: 'EventHandler' = MainGameEventHandler(self) @@ -83,19 +85,24 @@ class Engine: self.update_field_of_view() # Interface elements - self.hit_points_bar = Bar(position=Point(4, 47), width=20) + self.hit_points_bar = Bar(position=Point(4, 45), width=20) + + self.message_log.add_message('Greetings adventurer!', fg=(127, 127, 255), stack=False) def print_to_console(self, console): '''Print the whole game to the given console.''' self.map.print_to_console(console) - console.print(x=1, y=47, string='HP:') + console.print(x=1, y=45, string='HP:') hp, max_hp = self.hero.fighter.hit_points, self.hero.fighter.maximum_hit_points self.hit_points_bar.percent_filled = hp / max_hp self.hit_points_bar.render_to_console(console) - console.print(x=6, y=47, string=f'{hp}/{max_hp}', fg=color.WHITE) + console.print(x=6, y=45, string=f'{hp}/{max_hp}', fg=color.WHITE) - console.print(x=1, y=48, string=f'Turn: {self.current_turn}') + console.print(x=1, y=46, string=f'Turn: {self.current_turn}') + + messages_rect = Rect(Point(x=27, y=45), Size(width=40, height=5)) + self.message_log.render_to_console(console, messages_rect) for ent in sorted(self.entities, key=lambda e: e.render_order.value): # Only print entities that are in the field of view From 9f278995729b04cb8e0a92839b4f21398c36f24c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 13:58:37 -0700 Subject: [PATCH 103/234] Print some messages when damage and death happen! --- erynrl/actions.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erynrl/actions.py b/erynrl/actions.py index 790acaa..d4d5fae 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -201,6 +201,11 @@ class MeleeAction(MoveAction): if damage > 0 and self.target: log.ACTIONS.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) self.target.fighter.hit_points -= damage + + if self.actor == engine.hero: + engine.message_log.add_message(f'You attack the {self.target.name} for {damage} damage!', fg=(127, 255, 127)) + elif self.target == engine.hero: + engine.message_log.add_message(f'The {self.actor.name} attacks you for {damage} damage!', fg=(255, 127, 127)) else: log.ACTIONS.debug('%s attacks %s but does no damage!', self.actor, self.target) @@ -223,6 +228,11 @@ class DieAction(Action): def perform(self, engine: 'Engine') -> ActionResult: engine.kill_actor(self.actor) + if self.actor == engine.hero: + engine.message_log.add_message('You die...', fg=(255, 127, 127)) + else: + engine.message_log.add_message(f'The {self.actor.name} dies', fg=(127, 255, 127)) + if self.actor.yields_corpse_on_death: log.ACTIONS.debug('%s leaves a corpse behind', self.actor) corpse = Item(kind=items.Corpse, name=f'{self.actor.name} Corpse', position=self.actor.position) From 8239b22157249974152eaa7e9f6fbba1f7301948 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 16:03:47 -0700 Subject: [PATCH 104/234] Allow fg and bg as variable names --- .pylintrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.pylintrc b/.pylintrc index c795719..1f309f7 100644 --- a/.pylintrc +++ b/.pylintrc @@ -408,8 +408,10 @@ function-naming-style=snake_case # Good variable names which should always be accepted, separated by a comma. good-names=ai, + bg, dx, dy, + fg, hp, i, j, From ccd2e04d0ef824e6bf48c70457d9bb4f8f303ae3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 16:18:42 -0700 Subject: [PATCH 105/234] Doc strings in Messages module --- erynrl/messages.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/erynrl/messages.py b/erynrl/messages.py index 9a59fde..9678473 100644 --- a/erynrl/messages.py +++ b/erynrl/messages.py @@ -8,6 +8,18 @@ import tcod from .geometry import Rect class Message: + '''A message in the message log + + Attributes + ---------- + text : str + The text of the message + foreground : Tuple[int, int, int] + The foreground color to render the message with + count : int + The number of times this message has stacked + ''' + def __init__(self, text: str, fg: Optional[Tuple[int, int, int]] = None): self.text = text self.foreground = fg @@ -53,10 +65,12 @@ class MessageLog: self.messages.append(Message(text, fg)) def render_to_console(self, console: tcod.console.Console, rect: Rect): + '''Render this message log to the given console in the given rect''' self.render_messages(console, rect, self.messages) @staticmethod def render_messages(console: tcod.console.Console, rect: Rect, messages: Reversible[Message]): + '''Render a list of messages to the console in the given rect''' y_offset = min(rect.size.height, len(messages)) for message in reversed(messages): @@ -69,4 +83,4 @@ class MessageLog: break if y_offset < 0: - break \ No newline at end of file + break From 4e585a2650450812e99f20d6b31c9b17092db2f2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 16:19:03 -0700 Subject: [PATCH 106/234] Implement passive healing --- erynrl/actions.py | 23 +++++++++++++++++++++++ erynrl/components.py | 20 +++++++++++++++++++- erynrl/events.py | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/erynrl/actions.py b/erynrl/actions.py index d4d5fae..3e324c4 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -220,6 +220,12 @@ class WaitAction(Action): def perform(self, engine: 'Engine') -> ActionResult: log.ACTIONS.debug('%s is waiting a turn', self.actor) + + if self.actor == engine.hero: + should_recover_hit_points = self.actor.fighter.passively_recover_hit_points() + if should_recover_hit_points: + return ActionResult(self, alternate=HealAction(self.actor, 1)) + return self.success() class DieAction(Action): @@ -250,3 +256,20 @@ class DropItemAction(Action): def perform(self, engine: 'Engine') -> ActionResult: engine.entities.add(self.item) return self.success() + +class HealAction(Action): + '''Heal a target actor some number of hit points''' + + def __init__(self, actor: 'Actor', hit_points_to_recover: int): + super().__init__(actor) + self.hit_points_to_recover = hit_points_to_recover + + def perform(self, engine: 'Engine') -> ActionResult: + fighter = self.actor.fighter + if not fighter: + log.ACTIONS.error('Attempted to heal %s but it has no hit points', self.actor) + return self.failure() + + fighter.hit_points += self.hit_points_to_recover + + return self.success() \ No newline at end of file diff --git a/erynrl/components.py b/erynrl/components.py index 86b266f..ce5abde 100644 --- a/erynrl/components.py +++ b/erynrl/components.py @@ -1,8 +1,9 @@ # Eryn Wells +import random from typing import Optional -# pylint: disable=too-few-public-methods + class Component: '''A base, abstract Component that implement some aspect of an Entity's behavior.''' @@ -29,6 +30,9 @@ class Fighter(Component): self.attack_power = attack_power self.defense = defense + self.turns_since_last_heal = 0 + self.turn_for_next_passive_heal = random.randint(3, 7) + @property def hit_points(self) -> int: '''Number of hit points remaining. When a Fighter reaches 0 hit points, they die.''' @@ -42,3 +46,17 @@ class Fighter(Component): def is_dead(self) -> bool: '''True if the Fighter has died, i.e. reached 0 hit points''' return self.__hit_points == 0 + + def passively_recover_hit_points(self) -> bool: + '''Check the passive healing clock to see if this fighter should recover hit points. If not, increment the + counter.''' + if self.hit_points == self.maximum_hit_points: + self.turns_since_last_heal = 0 + + if self.turns_since_last_heal < self.turn_for_next_passive_heal: + self.turns_since_last_heal += 1 + return False + + self.turns_since_last_heal = 0 + self.turn_for_next_passive_heal = random.randint(3, 7) + return True diff --git a/erynrl/events.py b/erynrl/events.py index 25ccd4d..bb1ee7f 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -100,4 +100,4 @@ class GameOverEventHandler(EventHandler): case tcod.event.KeySym.ESCAPE: action = ExitAction(hero) - return action \ No newline at end of file + return action From d4e46846943e26aed81750daca7caed2e75ef01d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 16:50:24 -0700 Subject: [PATCH 107/234] Show Entity under mouse cursor in a line above the hit points --- erynrl/engine.py | 21 ++++++++++++++------- erynrl/events.py | 11 +++++++++-- erynrl/map.py | 3 +++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index e4108b9..5daa295 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -4,7 +4,7 @@ import random from dataclasses import dataclass -from typing import TYPE_CHECKING, MutableSet, NoReturn +from typing import TYPE_CHECKING, MutableSet, NoReturn, Optional import tcod @@ -58,6 +58,7 @@ class Engine: self.message_log = MessageLog() self.event_handler: 'EventHandler' = MainGameEventHandler(self) + self.current_mouse_point: Optional[Point] = None self.hero = Hero(position=self.map.generator.rooms[0].center) self.entities: MutableSet[Entity] = {self.hero} @@ -104,12 +105,20 @@ class Engine: messages_rect = Rect(Point(x=27, y=45), Size(width=40, height=5)) self.message_log.render_to_console(console, messages_rect) + entities_at_mouse_position = [] for ent in sorted(self.entities, key=lambda e: e.render_order.value): - # Only print entities that are in the field of view + # Only process entities that are in the field of view if not self.map.visible[tuple(ent.position)]: continue + ent.print_to_console(console) + if ent.position == self.current_mouse_point: + entities_at_mouse_position.append(ent) + + if len(entities_at_mouse_position) > 0: + console.print(x=1, y=43, string=', '.join(e.name for e in entities_at_mouse_position)) + def run_event_loop(self, context: tcod.context.Context, console: tcod.Console) -> NoReturn: '''Run the event loop forever. This method never returns.''' while True: @@ -118,7 +127,7 @@ class Engine: context.present(console) self.begin_turn() - self.event_handler.wait_for_events() + self.event_handler.handle_events(context) self.finish_turn() def process_input_action(self, action: Action) -> ActionResult: @@ -240,12 +249,10 @@ class Engine: def kill_actor(self, actor: Actor) -> None: '''Kill an entity. Remove it from the game.''' - if actor == self.hero: # When the hero dies, the game is over. - # TODO: Transition to game over state - log.ACTIONS.debug('Time to die.') + log.ACTIONS.info('Time to die.') self.event_handler = GameOverEventHandler(self) else: - log.ACTIONS.debug('%s dies', actor) + log.ACTIONS.info('%s dies', actor) self.entities.remove(actor) diff --git a/erynrl/events.py b/erynrl/events.py index bb1ee7f..0c736b3 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -8,7 +8,7 @@ import tcod from . import log from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction -from .geometry import Direction +from .geometry import Direction, Point if TYPE_CHECKING: from .engine import Engine @@ -20,9 +20,10 @@ class EventHandler(tcod.event.EventDispatch[Action]): super().__init__() self.engine = engine - def wait_for_events(self): + def handle_events(self, context: tcod.context.Context): '''Wait for events and handle them.''' for event in tcod.event.wait(): + context.convert_event(event) self.handle_event(event) def handle_event(self, event: tcod.event.Event) -> None: @@ -87,6 +88,12 @@ class MainGameEventHandler(EventHandler): return action + def ev_mousemotion(self, event: tcod.event.MouseMotion) -> Optional[Action]: + mouse_point = Point(event.tile.x, event.tile.y) + if not self.engine.map.tile_is_in_bounds(mouse_point): + mouse_point = None + self.engine.current_mouse_point = mouse_point + class GameOverEventHandler(EventHandler): '''When the game is over (the hero dies, the player quits, etc), this event handler takes over.''' diff --git a/erynrl/map.py b/erynrl/map.py index 33b3bbd..18f65f8 100644 --- a/erynrl/map.py +++ b/erynrl/map.py @@ -25,6 +25,7 @@ class Map: @property def rooms(self) -> List['Room']: + '''The list of rooms in the map''' return self.generator.rooms def random_walkable_position(self) -> Point: @@ -35,9 +36,11 @@ class Map: return random_position_in_room def tile_is_in_bounds(self, point: Point) -> bool: + '''Return True if the given point is inside the bounds of the map''' return 0 <= point.x < self.size.width and 0 <= point.y < self.size.height def tile_is_walkable(self, point: Point) -> bool: + '''Return True if the tile at the given point is walkable''' return self.tiles[point.x, point.y]['walkable'] def print_to_console(self, console: tcod.Console) -> None: From 85569595a9b8385627d003dea0953e424181dde0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 15 May 2022 19:58:39 -0700 Subject: [PATCH 108/234] FIX THE COLLISION DETECTION BUG --- erynrl/actions.py | 7 ++++--- erynrl/engine.py | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/erynrl/actions.py b/erynrl/actions.py index 3e324c4..5264fb1 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -149,14 +149,14 @@ class BumpAction(MoveAction): position_is_walkable = engine.map.tile_is_walkable(new_position) for ent in engine.entities: - if new_position != ent.position: + if new_position != ent.position or not ent.blocks_movement: continue entity_occupying_position = ent break else: entity_occupying_position = None - log.ACTIONS.debug('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', + log.ACTIONS.info('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', self.actor, new_position, position_is_in_bounds, @@ -166,7 +166,8 @@ class BumpAction(MoveAction): if not position_is_in_bounds or not position_is_walkable: return self.failure() - if entity_occupying_position and entity_occupying_position.blocks_movement: + if entity_occupying_position: + assert entity_occupying_position.blocks_movement return ActionResult(self, alternate=MeleeAction(self.actor, self.direction, entity_occupying_position)) return ActionResult(self, alternate=WalkAction(self.actor, self.direction)) diff --git a/erynrl/engine.py b/erynrl/engine.py index 5daa295..4a9de14 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -182,12 +182,12 @@ class Engine: alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' else: alternate_string = str(result.alternate) - log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', - '|' if not result.success or not result.done else '`', - action, - result.success, - result.done, - alternate_string) + log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', + '|' if not result.success or not result.done else '`', + action, + result.success, + result.done, + alternate_string) while not result.done: action = result.alternate From 6073454ed33c52f561cd2e8f6380506694f04756 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 16:39:19 -0700 Subject: [PATCH 109/234] Add line 1 comment to interface/__init__.py --- erynrl/interface/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index e69de29..12ba5c3 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -0,0 +1 @@ +# Eryn Wells From 11aee1232054cacdeef4e1baabc31670d7b42fed Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 16:40:04 -0700 Subject: [PATCH 110/234] Add colors attribute to Bar class This list lets you specify a set of colors that the bar should be painted with depending on the percentage the bar is filled --- erynrl/interface/bar.py | 43 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/erynrl/interface/bar.py b/erynrl/interface/bar.py index 3bc12df..10b1ed4 100644 --- a/erynrl/interface/bar.py +++ b/erynrl/interface/bar.py @@ -1,12 +1,37 @@ # Eryn Wells +from typing import List, Optional, Tuple + from . import color from ..geometry import Point class Bar: - def __init__(self, *, position: Point, width: int): + '''A bar that expresses a percentage.''' + + def __init__(self, *, position: Point, width: int, colors: Optional[List[Tuple[float, color.Color]]] = None): + ''' + Instantiate a new Bar + + Arguments + --------- + position : Point + The position within a console to render this bar + width : int + The length of the bar in tiles + colors : List[Tuple[float, color.Color]] + A list of two-tuples specifying a percentage and color to draw the bar. If the bar is less than or equal to + the specified percentage, that color will be chosen. For example, if the bar is 45% filled, and this colors + array is specified: + + ``` + [(0.25, RED), (0.5, ORANGE), (0.75, YELLOW), (1.0, GREEN)] + ``` + + The bar will be painted `ORANGE` because 45% is greater than 25% and less than 50%. + ''' self.position = position self.width = width + self.colors = sorted(colors, key=lambda c: c[0]) if colors is not None else [] self._percent_filled = 1.0 @@ -20,8 +45,18 @@ class Bar: self._percent_filled = min(1, max(0, value)) def render_to_console(self, console): - console.draw_rect(x=self.position.x, y=self.position.y, width=self.width, height=1, ch=1, bg=color.GREY10) + '''Draw this bar to the console''' + # Draw the background first + console.draw_rect(x=self.position.x, y=self.position.y, width=self.width, height=1, ch=1, bg=color.GREY12) + + percent_filled = self._percent_filled + if percent_filled > 0: + for color_spec in self.colors: + if percent_filled <= color_spec[0]: + bar_color = color_spec[1] + break + else: + bar_color = color.GREY50 - if self._percent_filled > 0: filled_width = round(self._percent_filled * self.width) - console.draw_rect(x=self.position.x, y=self.position.y, width=filled_width, height=1, ch=1, bg=color.GREY50) + console.draw_rect(x=self.position.x, y=self.position.y, width=filled_width, height=1, ch=1, bg=bar_color) From 18a068cff63104710c45976421bec5beeb42b064 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 16:40:29 -0700 Subject: [PATCH 111/234] Add some more basic colors and some semantic colors for the Health Bar --- erynrl/engine.py | 2 +- erynrl/interface/color.py | 43 +++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 4a9de14..aa628d4 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -86,7 +86,7 @@ class Engine: self.update_field_of_view() # Interface elements - self.hit_points_bar = Bar(position=Point(4, 45), width=20) + self.hit_points_bar = Bar(position=Point(4, 45), width=20, colors=list(color.HealthBar.bar_colors())) self.message_log.add_message('Greetings adventurer!', fg=(127, 127, 255), stack=False) diff --git a/erynrl/interface/color.py b/erynrl/interface/color.py index 3dfd44f..3e87d46 100644 --- a/erynrl/interface/color.py +++ b/erynrl/interface/color.py @@ -1,10 +1,45 @@ # Eryn Wells +# pylint: disable=too-few-public-methods ''' A bunch of colors. ''' -BLACK = (0, 0, 0) -WHITE = (255, 255, 255) -GREY10 = (26, 26, 26) -GREY50 = (128, 128, 128) \ No newline at end of file +from typing import Iterator, Tuple + +Color = Tuple[int, int, int] + +# Grayscale +BLACK = (0x00, 0x00, 0x00) +GREY12 = (0x20, 0x20, 0x20) +GREY25 = (0x40, 0x40, 0x40) +GREY50 = (0x80, 0x80, 0x80) +GREY75 = (0xC0, 0xC0, 0xC0) +WHITE = (0xFF, 0xFF, 0xFF) + +# Primaries +BLUE = (0x00, 0x00, 0xFF) +CYAN = (0x00, 0xFF, 0xFF) +GREEN = (0x00, 0xFF, 0x00) +MAGENTA = (0xFF, 0x00, 0xFF) +RED = (0xFF, 0x00, 0x00) +YELLOW = (0xFF, 0xFF, 0x00) +ORANGE = (0xFF, 0x77, 0x00) + +# Semantic +class HealthBar: + '''Semantic colors for the health bar''' + FULL = GREEN + GOOD = GREEN + OKAY = YELLOW + LOW = ORANGE + CRITICAL = RED + + @staticmethod + def bar_colors() -> Iterator[Tuple[float, Color]]: + '''Return an iterator of colors that a Bar class can use''' + yield (0.1, HealthBar.CRITICAL) + yield (0.25, HealthBar.LOW) + yield (0.75, HealthBar.OKAY) + yield (0.9, HealthBar.GOOD) + yield (1.0, HealthBar.FULL) \ No newline at end of file From 6e0112fd597b52e691cf5b3bc329ad7631595f8e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 16:40:45 -0700 Subject: [PATCH 112/234] Add some documentation to the things in object.py --- erynrl/object.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/erynrl/object.py b/erynrl/object.py index d9437d2..ba2337a 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -1,5 +1,7 @@ # Eryn Wells +'''Defines a number of high-level game objects. The parent class of all game objects is the Entity class.''' + from enum import Enum from typing import TYPE_CHECKING, Optional, Tuple, Type @@ -27,6 +29,8 @@ class Entity: Attributes ---------- + identifier : int + A numerical value that uniquely identifies this entity across the entire game position : Point The Entity's location on the map foreground : Tuple[int, int, int] @@ -35,10 +39,11 @@ class Entity: The background color used to render this Entity symbol : str A single character string that represents this character on the map - ai : Type[AI], optional - If an entity can act on its own behalf, an instance of an AI class blocks_movement : bool True if this Entity blocks other Entities from moving through its position + render_order : RenderOrder + One of the RenderOrder values that specifies a layer at which this entity will be rendered. Higher values are + rendered on top of lower values. ''' # A monotonically increasing identifier to help differentiate between entities that otherwise look identical @@ -71,6 +76,19 @@ class Entity: return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fg={self.foreground!r}, bg={self.background!r})' class Actor(Entity): + ''' + An actor is an abstract class that defines an object that can act in the game world. Entities that are actors will + be allowed an opportunity to perform an action during each game turn. + + Attributes + ---------- + ai : AI, optional + If an entity can act on its own behalf, an instance of an AI class + fighter : Fighter, optional + If an entity can fight or take damage, an instance of the Fighter class. This is where hit points, attack power, + defense power, etc live. + ''' + def __init__(self, symbol: str, *, position: Optional[Point] = None, blocks_movement: Optional[bool] = True, @@ -87,6 +105,7 @@ class Actor(Entity): @property def name(self) -> str: + '''The name of this actor. This is a player-visible string.''' return 'Actor' @property From e5485300efa6b4be4191923246bc916f36d987a6 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 16:47:21 -0700 Subject: [PATCH 113/234] Rename interface.bar.Bar -> interface.percentage_bar.PercentageBar --- erynrl/engine.py | 4 ++-- erynrl/interface/{bar.py => percentage_bar.py} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename erynrl/interface/{bar.py => percentage_bar.py} (99%) diff --git a/erynrl/engine.py b/erynrl/engine.py index aa628d4..c86e809 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -14,8 +14,8 @@ from .actions import Action, ActionResult from .ai import HostileEnemy from .events import GameOverEventHandler, MainGameEventHandler from .geometry import Point, Rect, Size -from .interface.bar import Bar from .interface import color +from .interface.percentage_bar import PercentageBar from .map import Map from .messages import MessageLog from .object import Actor, Entity, Hero, Monster @@ -86,7 +86,7 @@ class Engine: self.update_field_of_view() # Interface elements - self.hit_points_bar = Bar(position=Point(4, 45), width=20, colors=list(color.HealthBar.bar_colors())) + self.hit_points_bar = PercentageBar(position=Point(4, 45), width=20, colors=list(color.HealthBar.bar_colors())) self.message_log.add_message('Greetings adventurer!', fg=(127, 127, 255), stack=False) diff --git a/erynrl/interface/bar.py b/erynrl/interface/percentage_bar.py similarity index 99% rename from erynrl/interface/bar.py rename to erynrl/interface/percentage_bar.py index 10b1ed4..500ec87 100644 --- a/erynrl/interface/bar.py +++ b/erynrl/interface/percentage_bar.py @@ -5,7 +5,7 @@ from typing import List, Optional, Tuple from . import color from ..geometry import Point -class Bar: +class PercentageBar: '''A bar that expresses a percentage.''' def __init__(self, *, position: Point, width: int, colors: Optional[List[Tuple[float, color.Color]]] = None): From d5f6cbe73a25741a9dc828d76d2406b306314981 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 20:49:01 -0700 Subject: [PATCH 114/234] Clean up some pylint warnings in messages.py Add a module doc string Remove an extraneous else: --- erynrl/messages.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erynrl/messages.py b/erynrl/messages.py index 9678473..3606462 100644 --- a/erynrl/messages.py +++ b/erynrl/messages.py @@ -1,5 +1,11 @@ # Eryn Wells +''' +Defines the classes the support the in-game message log. Messages are recorded to the log as game actions are handled. A +short buffer of messages is displayed in the game's HUD, and a full list of messages can be viewed by the player at any +time. +''' + import textwrap from typing import List, Optional, Reversible, Tuple @@ -30,8 +36,7 @@ class Message: '''The full text of the message, including a count of repeats, if present''' if self.count == 1: return self.text - else: - return f'{self.text} (x{self.count})' + return f'{self.text} (x{self.count})' def __str__(self) -> str: return self.text From 4124d1ae4e59c5c5637e1af0da4f32ef3b51348b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 20:49:28 -0700 Subject: [PATCH 115/234] Clean up some pylint warnings in interface/color.py --- erynrl/interface/color.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/interface/color.py b/erynrl/interface/color.py index 3e87d46..0277921 100644 --- a/erynrl/interface/color.py +++ b/erynrl/interface/color.py @@ -42,4 +42,4 @@ class HealthBar: yield (0.25, HealthBar.LOW) yield (0.75, HealthBar.OKAY) yield (0.9, HealthBar.GOOD) - yield (1.0, HealthBar.FULL) \ No newline at end of file + yield (1.0, HealthBar.FULL) From 31bec25dcf96e1bd0bf94206af50bf33d86b759b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 20:49:42 -0700 Subject: [PATCH 116/234] Add a docstring to engine.Configuration --- erynrl/engine.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erynrl/engine.py b/erynrl/engine.py index c86e809..f24f495 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -25,6 +25,7 @@ if TYPE_CHECKING: @dataclass class Configuration: + '''Configuration of the game engine''' map_size: Size class Engine: From 99838cbd00767678273608923a5ffc89359a3a5f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 20:50:23 -0700 Subject: [PATCH 117/234] Convert the passive healing clock to a more granular tick mechanism Instead of counting turns, count clock ticks. A WaitAction adds 10 ticks to the passive healing clock and a WalkAction adds 5. So, you will heal while walking but at a slower rate. --- erynrl/actions.py | 11 +++++++++-- erynrl/components.py | 36 +++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/erynrl/actions.py b/erynrl/actions.py index 5264fb1..659bc1f 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -182,6 +182,13 @@ class WalkAction(MoveAction): log.ACTIONS.debug('Moving %s to %s', self.actor, new_position) self.actor.position = new_position + try: + should_recover_hit_points = self.actor.fighter.passively_recover_hit_points(5) + if should_recover_hit_points: + return ActionResult(self, alternate=HealAction(self.actor, 1)) + except AttributeError: + pass + return self.success() class MeleeAction(MoveAction): @@ -223,7 +230,7 @@ class WaitAction(Action): log.ACTIONS.debug('%s is waiting a turn', self.actor) if self.actor == engine.hero: - should_recover_hit_points = self.actor.fighter.passively_recover_hit_points() + should_recover_hit_points = self.actor.fighter.passively_recover_hit_points(10) if should_recover_hit_points: return ActionResult(self, alternate=HealAction(self.actor, 1)) @@ -273,4 +280,4 @@ class HealAction(Action): fighter.hit_points += self.hit_points_to_recover - return self.success() \ No newline at end of file + return self.success() diff --git a/erynrl/components.py b/erynrl/components.py index ce5abde..3e7a291 100644 --- a/erynrl/components.py +++ b/erynrl/components.py @@ -26,12 +26,15 @@ class Fighter(Component): def __init__(self, *, maximum_hit_points: int, attack_power: int, defense: int, hit_points: Optional[int] = None): self.maximum_hit_points = maximum_hit_points self.__hit_points = hit_points if hit_points else maximum_hit_points + # TODO: Rename these two attributes something better self.attack_power = attack_power self.defense = defense - self.turns_since_last_heal = 0 - self.turn_for_next_passive_heal = random.randint(3, 7) + # TODO: Factor this out into a dedicated Clock class + self.__ticks_since_last_passive_heal = 0 + self.__ticks_for_next_passive_heal = 0 + self._reset_passive_heal_clock() @property def hit_points(self) -> int: @@ -47,16 +50,27 @@ class Fighter(Component): '''True if the Fighter has died, i.e. reached 0 hit points''' return self.__hit_points == 0 - def passively_recover_hit_points(self) -> bool: - '''Check the passive healing clock to see if this fighter should recover hit points. If not, increment the - counter.''' - if self.hit_points == self.maximum_hit_points: - self.turns_since_last_heal = 0 + def passively_recover_hit_points(self, number_of_ticks: int) -> bool: + ''' + Check the passive healing clock to see if this fighter should recover hit points. If not, increment the + counter. - if self.turns_since_last_heal < self.turn_for_next_passive_heal: - self.turns_since_last_heal += 1 + Arguments + --------- + number_of_ticks : int + The number of ticks to increment the clock + ''' + if self.hit_points == self.maximum_hit_points: + self.__ticks_since_last_passive_heal = 0 + + if self.__ticks_since_last_passive_heal < self.__ticks_for_next_passive_heal: + self.__ticks_since_last_passive_heal += number_of_ticks return False - self.turns_since_last_heal = 0 - self.turn_for_next_passive_heal = random.randint(3, 7) + self._reset_passive_heal_clock() + return True + + def _reset_passive_heal_clock(self) -> None: + self.__ticks_since_last_passive_heal = 0 + self.__ticks_for_next_passive_heal = random.randint(30, 70) From 46af8863b19b18ea8c9e7589090dfeff0c92ca5c Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 16 May 2022 20:51:53 -0700 Subject: [PATCH 118/234] Use a try/catch to check for the presence of actor/target.fighter in MeleeAction --- erynrl/actions.py | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/erynrl/actions.py b/erynrl/actions.py index 659bc1f..c15ea2c 100644 --- a/erynrl/actions.py +++ b/erynrl/actions.py @@ -199,29 +199,26 @@ class MeleeAction(MoveAction): self.target = target def perform(self, engine: 'Engine') -> ActionResult: - if not self.target: + try: + damage = self.actor.fighter.attack_power - self.target.fighter.defense + if damage > 0 and self.target: + log.ACTIONS.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) + self.target.fighter.hit_points -= damage + + if self.actor == engine.hero: + engine.message_log.add_message(f'You attack the {self.target.name} for {damage} damage!', fg=(127, 255, 127)) + elif self.target == engine.hero: + engine.message_log.add_message(f'The {self.actor.name} attacks you for {damage} damage!', fg=(255, 127, 127)) + else: + log.ACTIONS.debug('%s attacks %s but does no damage!', self.actor, self.target) + + if self.target.fighter.is_dead: + log.ACTIONS.info('%s is dead!', self.target) + return ActionResult(self, alternate=DieAction(self.target)) + except AttributeError: return self.failure() - - if not self.actor.fighter or not self.target.fighter: - return self.failure() - - damage = self.actor.fighter.attack_power - self.target.fighter.defense - if damage > 0 and self.target: - log.ACTIONS.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) - self.target.fighter.hit_points -= damage - - if self.actor == engine.hero: - engine.message_log.add_message(f'You attack the {self.target.name} for {damage} damage!', fg=(127, 255, 127)) - elif self.target == engine.hero: - engine.message_log.add_message(f'The {self.actor.name} attacks you for {damage} damage!', fg=(255, 127, 127)) else: - log.ACTIONS.debug('%s attacks %s but does no damage!', self.actor, self.target) - - if self.target.fighter.is_dead: - log.ACTIONS.info('%s is dead!', self.target) - return ActionResult(self, alternate=DieAction(self.target)) - - return self.success() + return self.success() class WaitAction(Action): '''Wait a turn''' From ae1c7f5ce2dec8b5605a0e2f6cdd76f06c712362 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 28 May 2022 08:52:54 -0700 Subject: [PATCH 119/234] Refactor events into their own package Most of the existing actions are game actions (they control the player character) so they live in actions.game. Eventually, there will be modules for different kinds of actions that only apply to, e.g. modal UI. --- erynrl/actions/__init__.py | 4 ++ erynrl/actions/action.py | 45 ++++++++++++ erynrl/{actions.py => actions/game.py} | 94 +++----------------------- erynrl/actions/result.py | 47 +++++++++++++ erynrl/ai.py | 3 +- erynrl/events.py | 11 ++- 6 files changed, 114 insertions(+), 90 deletions(-) create mode 100644 erynrl/actions/__init__.py create mode 100644 erynrl/actions/action.py rename erynrl/{actions.py => actions/game.py} (73%) create mode 100644 erynrl/actions/result.py diff --git a/erynrl/actions/__init__.py b/erynrl/actions/__init__.py new file mode 100644 index 0000000..11a8220 --- /dev/null +++ b/erynrl/actions/__init__.py @@ -0,0 +1,4 @@ +# Eryn Wells + +from .action import Action +from .result import ActionResult diff --git a/erynrl/actions/action.py b/erynrl/actions/action.py new file mode 100644 index 0000000..3dd865a --- /dev/null +++ b/erynrl/actions/action.py @@ -0,0 +1,45 @@ +# Eryn Wells + +from typing import TYPE_CHECKING, Optional + +from ..object import Actor +from .result import ActionResult + +if TYPE_CHECKING: + from ..engine import Engine + +class Action: + '''An action that an Entity should perform.''' + + def __init__(self, actor: Optional[Actor] = None): + self.actor = actor + + def perform(self, engine: 'Engine') -> ActionResult: + '''Perform this action. + + Parameters + ---------- + engine : Engine + The game engine + + Returns + ------- + ActionResult + A result object reflecting how the action was handled, and what follow-up actions, if any, are needed to + complete the action. + ''' + raise NotImplementedError() + + def failure(self) -> ActionResult: + '''Create an ActionResult indicating failure with no follow-up''' + return ActionResult(self, success=False) + + def success(self) -> ActionResult: + '''Create an ActionResult indicating success with no follow-up''' + return ActionResult(self, success=True) + + def __str__(self) -> str: + return f'{self.__class__.__name__} for {self.actor!s}' + + def __repr__(self): + return f'{self.__class__.__name__}({self.actor!r})' diff --git a/erynrl/actions.py b/erynrl/actions/game.py similarity index 73% rename from erynrl/actions.py rename to erynrl/actions/game.py index c15ea2c..3d03df0 100644 --- a/erynrl/actions.py +++ b/erynrl/actions/game.py @@ -18,96 +18,24 @@ Action : Base class of all actions WaitAction ''' -from typing import Optional, TYPE_CHECKING +from typing import TYPE_CHECKING -from . import items -from . import log -from .geometry import Direction -from .object import Actor, Item +from .. import items +from .. import log +from ..geometry import Direction +from ..object import Actor, Item +from .action import Action +from .result import ActionResult if TYPE_CHECKING: - from .engine import Engine - -class ActionResult: - '''The result of an Action. - - `Action.perform()` returns an instance of this class to inform the caller of the result - - Attributes - ---------- - action : Action - The Action that was performed - success : bool, optional - True if the action succeeded - done : bool, optional - True if the action is complete, and no follow-up action is needed - alternate : Action, optional - An alternate action to perform if this action failed - ''' - - def __init__(self, action: 'Action', *, - success: Optional[bool] = None, - done: Optional[bool] = None, - alternate: Optional['Action'] = None): - self.action = action - self.alternate = alternate - - if success is not None: - self.success = success - elif alternate: - self.success = False - else: - self.success = True - - if done is not None: - self.done = done - elif self.success: - self.done = True - else: - self.done = not alternate - - def __repr__(self): - return f'{self.__class__.__name__}({self.action!r}, success={self.success}, done={self.done}, alternate={self.alternate!r})' - -class Action: - '''An action that an Entity should perform.''' - - def __init__(self, actor: Optional[Actor]): - self.actor = actor - - def perform(self, engine: 'Engine') -> ActionResult: - '''Perform this action. - - Parameters - ---------- - engine : Engine - The game engine - - Returns - ------- - ActionResult - A result object reflecting how the action was handled, and what follow-up actions, if any, are needed to - complete the action. - ''' - raise NotImplementedError() - - def failure(self) -> ActionResult: - '''Create an ActionResult indicating failure with no follow-up''' - return ActionResult(self, success=False) - - def success(self) -> ActionResult: - '''Create an ActionResult indicating success with no follow-up''' - return ActionResult(self, success=True) - - def __str__(self) -> str: - return f'{self.__class__.__name__} for {self.actor!s}' - - def __repr__(self): - return f'{self.__class__.__name__}({self.actor!r})' + from ..engine import Engine class ExitAction(Action): '''Exit the game.''' + def __init__(self): + super().__init__(None) + def perform(self, engine: 'Engine') -> ActionResult: raise SystemExit() diff --git a/erynrl/actions/result.py b/erynrl/actions/result.py new file mode 100644 index 0000000..c853bc5 --- /dev/null +++ b/erynrl/actions/result.py @@ -0,0 +1,47 @@ +# Eryn Wells + +from typing import TYPE_CHECKING, Optional + +if TYPE_CHECKING: + from .action import Action + +class ActionResult: + '''The result of an Action. + + `Action.perform()` returns an instance of this class to inform the caller of the result + + Attributes + ---------- + action : Action + The Action that was performed + success : bool, optional + True if the action succeeded + done : bool, optional + True if the action is complete, and no follow-up action is needed + alternate : Action, optional + An alternate action to perform if this action failed + ''' + + def __init__(self, action: 'Action', *, + success: Optional[bool] = None, + done: Optional[bool] = None, + alternate: Optional['Action'] = None): + self.action = action + self.alternate = alternate + + if success is not None: + self.success = success + elif alternate: + self.success = False + else: + self.success = True + + if done is not None: + self.done = done + elif self.success: + self.done = True + else: + self.done = not alternate + + def __repr__(self): + return f'{self.__class__.__name__}({self.action!r}, success={self.success}, done={self.done}, alternate={self.alternate!r})' diff --git a/erynrl/ai.py b/erynrl/ai.py index 5653ecc..2301a74 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -7,7 +7,8 @@ import numpy as np import tcod from . import log -from .actions import Action, BumpAction, WaitAction +from .actions import Action +from .actions.game import BumpAction, WaitAction from .components import Component from .geometry import Direction, Point from .object import Entity diff --git a/erynrl/events.py b/erynrl/events.py index 0c736b3..9d9a69f 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -7,7 +7,8 @@ from typing import Optional, TYPE_CHECKING import tcod from . import log -from .actions import Action, ExitAction, RegenerateRoomsAction, BumpAction, WaitAction +from .actions.action import Action +from .actions.game import ExitAction, RegenerateRoomsAction, BumpAction, WaitAction from .geometry import Direction, Point if TYPE_CHECKING: @@ -46,7 +47,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): self.engine.process_input_action(action) def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: - return ExitAction(self.engine.hero) + return ExitAction() class MainGameEventHandler(EventHandler): ''' @@ -80,7 +81,7 @@ class MainGameEventHandler(EventHandler): case tcod.event.KeySym.y: action = BumpAction(hero, Direction.NorthWest) case tcod.event.KeySym.ESCAPE: - action = ExitAction(hero) + action = ExitAction() case tcod.event.KeySym.SPACE: action = RegenerateRoomsAction(hero) case tcod.event.KeySym.PERIOD: @@ -100,11 +101,9 @@ class GameOverEventHandler(EventHandler): def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: action: Optional[Action] = None - hero = self.engine.hero - sym = event.sym match sym: case tcod.event.KeySym.ESCAPE: - action = ExitAction(hero) + action = ExitAction() return action From 05eb5c4ade122a5634f00aa541dea04a7516db7e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 28 May 2022 09:02:52 -0700 Subject: [PATCH 120/234] Add some docstrings to ai.py --- erynrl/ai.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erynrl/ai.py b/erynrl/ai.py index 2301a74..867b236 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -16,7 +16,10 @@ from .object import Entity if TYPE_CHECKING: from .engine import Engine +# pylint: disable=too-few-public-methods class AI(Component): + '''An abstract class providing AI for an entity.''' + def __init__(self, entity: Entity) -> None: super().__init__() self.entity = entity @@ -26,6 +29,12 @@ class AI(Component): raise NotImplementedError() class HostileEnemy(AI): + '''Entity AI for a hostile enemy. + + The entity will wander around until it sees the hero, at which point it will + beeline for her. + ''' + def act(self, engine: 'Engine') -> Optional[Action]: visible_tiles = tcod.map.compute_fov( engine.map.tiles['transparent'], From 90994cafd73e752e73f13a2547b464f274156a2a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 29 May 2022 21:24:31 -0700 Subject: [PATCH 121/234] Import Action and ActionResult from actions submodules --- erynrl/actions/__init__.py | 3 --- erynrl/engine.py | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/erynrl/actions/__init__.py b/erynrl/actions/__init__.py index 11a8220..12ba5c3 100644 --- a/erynrl/actions/__init__.py +++ b/erynrl/actions/__init__.py @@ -1,4 +1 @@ # Eryn Wells - -from .action import Action -from .result import ActionResult diff --git a/erynrl/engine.py b/erynrl/engine.py index f24f495..08a9261 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -10,7 +10,8 @@ import tcod from . import log from . import monsters -from .actions import Action, ActionResult +from .actions.action import Action +from .actions.result import ActionResult from .ai import HostileEnemy from .events import GameOverEventHandler, MainGameEventHandler from .geometry import Point, Rect, Size From 1b37807710051e1714ea343025730858030e3631 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 8 Feb 2023 08:34:46 -0800 Subject: [PATCH 122/234] Add a pep8 code style configuration file --- .pep8 | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .pep8 diff --git a/.pep8 b/.pep8 new file mode 100644 index 0000000..9d54e0f --- /dev/null +++ b/.pep8 @@ -0,0 +1,2 @@ +[pycodestyle] +max_line_length = 120 From a4f4584ffd29c6307f5c66475ed8304510c4af55 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 8 Feb 2023 08:35:06 -0800 Subject: [PATCH 123/234] Enable pep8 formatting on save --- going_rogue.code-workspace | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/going_rogue.code-workspace b/going_rogue.code-workspace index 876a149..56f8b2b 100644 --- a/going_rogue.code-workspace +++ b/going_rogue.code-workspace @@ -4,5 +4,7 @@ "path": "." } ], - "settings": {} -} \ No newline at end of file + "settings": { + "editor.formatOnSave": true + } +} From 1df7cea2ad06f3661df0736022d14c9ab191a6d3 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 8 Feb 2023 08:36:00 -0800 Subject: [PATCH 124/234] Move map modules to their own directory --- erynrl/{map.py => map/__init__.py} | 64 ++++++++++++++++++++++-------- erynrl/{ => map}/tile.py | 0 2 files changed, 47 insertions(+), 17 deletions(-) rename erynrl/{map.py => map/__init__.py} (83%) rename erynrl/{ => map}/tile.py (100%) diff --git a/erynrl/map.py b/erynrl/map/__init__.py similarity index 83% rename from erynrl/map.py rename to erynrl/map/__init__.py index 18f65f8..a2f0b73 100644 --- a/erynrl/map.py +++ b/erynrl/map/__init__.py @@ -7,21 +7,22 @@ from typing import Iterator, List, Optional import numpy as np import tcod -from . import log -from .geometry import Direction, Point, Rect, Size +from .. import log +from ..geometry import Direction, Point, Rect, Size from .tile import Empty, Floor, Shroud, Wall + class Map: - def __init__(self, size: Size): + def __init__(self, size: Size, room_generator_class=RoomsAndCorridorsGenerator): self.size = size - self.generator = RoomsAndCorridorsGenerator(size=size) + self.generator = room_generator_class(size=size) self.tiles = self.generator.generate() # Map tiles that are currently visible to the player - self.visible = np.full(tuple(self.size), fill_value=False, order='F') + self.visible = np.full(tuple(self.size), fill_value=True, order='F') # Map tiles that the player has explored - self.explored = np.full(tuple(self.size), fill_value=False, order='F') + self.explored = np.full(tuple(self.size), fill_value=True, order='F') @property def rooms(self) -> List['Room']: @@ -54,6 +55,7 @@ class Map: choicelist=[self.tiles['light'], self.tiles['dark']], default=Shroud) + class MapGenerator: def __init__(self, *, size: Size): self.size = size @@ -73,6 +75,7 @@ class MapGenerator: ''' raise NotImplementedError() + class RoomsAndCorridorsGenerator(MapGenerator): '''Generate a rooms-and-corridors style map with BSP.''' @@ -82,8 +85,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): maximum_room_size: Size DefaultConfiguration = Configuration( - minimum_room_size=Size(5, 5), - maximum_room_size=Size(15, 15), + minimum_room_size=Size(7, 7), + maximum_room_size=Size(20, 20), ) def __init__(self, *, size: Size, config: Optional[Configuration] = None): @@ -104,11 +107,16 @@ class RoomsAndCorridorsGenerator(MapGenerator): # Recursively divide the map into squares of various sizes to place rooms in. bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) + + # Add 2 to the minimum width and height to account for walls + gap_for_walls = 2 bsp.split_recursive( depth=4, - # Add 2 to the minimum width and height to account for walls - min_width=minimum_room_size.width + 2, min_height=minimum_room_size.height + 2, - max_horizontal_ratio=3, max_vertical_ratio=3) + min_width=minimum_room_size.width + gap_for_walls, + min_height=minimum_room_size.height + gap_for_walls, + max_horizontal_ratio=1.1, + max_vertical_ratio=1.1 + ) tiles = np.full(tuple(self.size), fill_value=Empty, order='F') @@ -141,7 +149,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): else: corner = Point(start_point.x, end_point.y) - log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + log.MAP.debug( + 'Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) log.MAP.debug('|-> start: %s', left_room_bounds) log.MAP.debug('`-> end: %s', right_room_bounds) @@ -155,10 +164,19 @@ class RoomsAndCorridorsGenerator(MapGenerator): # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of # the node. - width_range = (minimum_room_size.width, min(maximum_room_size.width, max(minimum_room_size.width, node.width - 2))) - height_range = (minimum_room_size.height, min(maximum_room_size.height, max(minimum_room_size.height, node.height - 2))) + width_range = ( + minimum_room_size.width, + min(maximum_room_size.width, max( + minimum_room_size.width, node.width - 2)) + ) + height_range = ( + minimum_room_size.height, + min(maximum_room_size.height, max( + minimum_room_size.height, node.height - 2)) + ) - size = Size(self.rng.randint(*width_range), self.rng.randint(*height_range)) + size = Size(self.rng.randint(*width_range), + self.rng.randint(*height_range)) origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 1)), node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) bounds = Rect(origin, size) @@ -193,7 +211,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): bounds = room.bounds # The range of a numpy array slice is [a, b). floor_rect = bounds.inset_rect(top=1, right=1, bottom=1, left=1) - tiles[floor_rect.min_x:floor_rect.max_x + 1, floor_rect.min_y:floor_rect.max_y + 1] = Floor + tiles[floor_rect.min_x:floor_rect.max_x + 1, + floor_rect.min_y:floor_rect.max_y + 1] = Floor for y in range(self.size.height): for x in range(self.size.width): @@ -215,6 +234,16 @@ class RoomsAndCorridorsGenerator(MapGenerator): '''Create a Rect from the given BSP node object''' return Rect(Point(node.x, node.y), Size(node.width, node.height)) + +class ElbowCorridorGenerator: + ... + + +class NetHackCorridorGenerator: + '''A corridor generator that produces doors and corridors that look like Nethack's Dungeons of Doom levels.''' + ... + + class Room: '''An abstract room. It can be any size or shape.''' @@ -222,6 +251,7 @@ class Room: def walkable_tiles(self) -> Iterator[Point]: raise NotImplementedError() + class RectangularRoom(Room): '''A rectangular room defined by a Rect. @@ -241,7 +271,7 @@ class RectangularRoom(Room): return self.bounds.midpoint @property - def walkable_tiles(self) -> Rect: + def walkable_tiles(self) -> Iterator[Point]: floor_rect = self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) for y in range(floor_rect.min_y, floor_rect.max_y + 1): for x in range(floor_rect.min_x, floor_rect.max_x + 1): diff --git a/erynrl/tile.py b/erynrl/map/tile.py similarity index 100% rename from erynrl/tile.py rename to erynrl/map/tile.py From 843aa2823f6f2fb99ab2855a39bb856531c09fb8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 8 Feb 2023 08:36:44 -0800 Subject: [PATCH 125/234] Some messing around with fonts and BDF files from long ago --- erynrl/__main__.py | 7 +- fonts/ter-u32n.bdf | 52912 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52916 insertions(+), 3 deletions(-) create mode 100644 fonts/ter-u32n.bdf diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 484ba64..74211ab 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -10,7 +10,8 @@ from .geometry import Size CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50 MAP_WIDTH, MAP_HEIGHT = 80, 45 -FONT = 'terminal16x16_gs_ro.png' +FONT_CP437 = 'terminal16x16_gs_ro.png' +FONT_BDF = 'ter-u32n.bdf' def parse_args(argv, *a, **kw): parser = argparse.ArgumentParser(*a, **kw) @@ -53,12 +54,12 @@ def main(argv): log.ROOT.error("Couldn't find a fonts/ directory") return -1 - font = os.path.join(fonts_directory, FONT) + font = os.path.join(fonts_directory, FONT_BDF) if not os.path.isfile(font): log.ROOT.error("Font file %s doesn't exist", font) return -1 - tileset = tcod.tileset.load_tilesheet(font, 16, 16, tcod.tileset.CHARMAP_CP437) + tileset = tcod.tileset.load_bdf(font) console = tcod.Console(CONSOLE_WIDTH, CONSOLE_HEIGHT, order='F') configuration = Configuration(map_size=Size(MAP_WIDTH, MAP_HEIGHT)) diff --git a/fonts/ter-u32n.bdf b/fonts/ter-u32n.bdf new file mode 100644 index 0000000..a758968 --- /dev/null +++ b/fonts/ter-u32n.bdf @@ -0,0 +1,52912 @@ +STARTFONT 2.1 +FONT -xos4-Terminus-Medium-R-Normal--32-320-72-72-C-160-ISO10646-1 +SIZE 32 72 72 +FONTBOUNDINGBOX 16 32 0 -6 +STARTPROPERTIES 20 +FAMILY_NAME "Terminus" +FOUNDRY "xos4" +SETWIDTH_NAME "Normal" +ADD_STYLE_NAME "" +COPYRIGHT "Copyright (C) 2020 Dimitar Toshkov Zhekov" +NOTICE "Licensed under the SIL Open Font License, Version 1.1" +WEIGHT_NAME "Medium" +SLANT "R" +PIXEL_SIZE 32 +POINT_SIZE 320 +RESOLUTION_X 72 +RESOLUTION_Y 72 +SPACING "C" +AVERAGE_WIDTH 160 +CHARSET_REGISTRY "ISO10646" +CHARSET_ENCODING "1" +MIN_SPACE 16 +FONT_ASCENT 26 +FONT_DESCENT 6 +DEFAULT_CHAR 65533 +ENDPROPERTIES +CHARS 1356 +STARTCHAR char0 +ENCODING 0 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3C3C +3C3C +300C +300C +300C +0000 +0000 +0000 +300C +300C +300C +300C +0000 +0000 +0000 +300C +300C +300C +3C3C +3C3C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR space +ENCODING 32 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR exclam +ENCODING 33 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotedbl +ENCODING 34 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR numbersign +ENCODING 35 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0C30 +3FFC +3FFC +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +3FFC +3FFC +0C30 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dollar +ENCODING 36 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0FF0 +1FF8 +399C +318C +3180 +3180 +3180 +3980 +1FF0 +0FF8 +019C +018C +018C +018C +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR percent +ENCODING 37 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1E18 +3F18 +3330 +3330 +3F60 +1E60 +00C0 +00C0 +0180 +0180 +0300 +0300 +0600 +0600 +0CF0 +0DF8 +1998 +1998 +31F8 +30F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ampersand +ENCODING 38 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F80 +1FC0 +38E0 +3060 +3060 +3060 +38E0 +1DC0 +0F80 +0F00 +1F8C +39DC +70F8 +6070 +6030 +6030 +6070 +70F8 +3FDC +1F8C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotesingle +ENCODING 39 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR parenleft +ENCODING 40 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0060 +00E0 +01C0 +0380 +0300 +0700 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0700 +0300 +0380 +01C0 +00E0 +0060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR parenright +ENCODING 41 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0600 +0700 +0380 +01C0 +00C0 +00E0 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +00E0 +00C0 +01C0 +0380 +0700 +0600 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR asterisk +ENCODING 42 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3838 +1C70 +0EE0 +07C0 +0380 +7FFC +7FFC +0380 +07C0 +0EE0 +1C70 +3838 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR plus +ENCODING 43 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR comma +ENCODING 44 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0380 +0300 +0600 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR hyphen +ENCODING 45 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR period +ENCODING 46 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR slash +ENCODING 47 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0030 +0030 +0060 +0060 +00C0 +00C0 +0180 +0180 +0300 +0300 +0600 +0600 +0C00 +0C00 +1800 +1800 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR zero +ENCODING 48 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR one +ENCODING 49 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0380 +0780 +0F80 +0D80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0FF0 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR two +ENCODING 50 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR three +ENCODING 51 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +001C +07F8 +07F8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR four +ENCODING 52 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +000C +001C +003C +007C +00EC +01CC +038C +070C +0E0C +1C0C +380C +300C +300C +3FFC +3FFC +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR five +ENCODING 53 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +001C +000C +000C +000C +000C +000C +300C +380C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR six +ENCODING 54 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF8 +1FF8 +3800 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR seven +ENCODING 55 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +3018 +0018 +0030 +0030 +0060 +0060 +00C0 +00C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR eight +ENCODING 56 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +381C +1FF8 +1FF8 +381C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR nine +ENCODING 57 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +001C +1FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR colon +ENCODING 58 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR semicolon +ENCODING 59 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0380 +0300 +0600 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR less +ENCODING 60 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3800 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +001C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR equal +ENCODING 61 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR greater +ENCODING 62 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3800 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +001C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR question +ENCODING 63 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +001C +0038 +0070 +00E0 +01C0 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR at +ENCODING 64 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +3FF8 +701C +600C +61FC +63FC +670C +660C +660C +660C +660C +660C +660C +671C +63FC +61EC +6000 +7000 +3FFC +1FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR A +ENCODING 65 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR B +ENCODING 66 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +3018 +3FF0 +3FF0 +3038 +301C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR C +ENCODING 67 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR D +ENCODING 68 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FC0 +3FF0 +3038 +3018 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3018 +3038 +3FF0 +3FC0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR E +ENCODING 69 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR F +ENCODING 70 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR G +ENCODING 71 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR H +ENCODING 72 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR I +ENCODING 73 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR J +ENCODING 74 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +007E +007E +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +3018 +3018 +3018 +3838 +1FF0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR K +ENCODING 75 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR L +ENCODING 76 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR M +ENCODING 77 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +701C +783C +6C6C +6C6C +67CC +638C +638C +610C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR N +ENCODING 78 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR O +ENCODING 79 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR P +ENCODING 80 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Q +ENCODING 81 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +31CC +38FC +1FF8 +0FF8 +001C +000E +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR R +ENCODING 82 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR S +ENCODING 83 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR T +ENCODING 84 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR U +ENCODING 85 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR V +ENCODING 86 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +1818 +1818 +1818 +1818 +1818 +0C30 +0C30 +0C30 +0C30 +0660 +0660 +0660 +03C0 +03C0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR W +ENCODING 87 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +610C +638C +638C +67CC +6C6C +6C6C +783C +701C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR X +ENCODING 88 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +1818 +1818 +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Y +ENCODING 89 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Z +ENCODING 90 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR bracketleft +ENCODING 91 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FE0 +0FE0 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0FE0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR backslash +ENCODING 92 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +1800 +1800 +0C00 +0C00 +0600 +0600 +0300 +0300 +0180 +0180 +00C0 +00C0 +0060 +0060 +0030 +0030 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR bracketright +ENCODING 93 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FE0 +0FE0 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0FE0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR asciicircum +ENCODING 94 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +1C38 +381C +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR underscore +ENCODING 95 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +ENDCHAR +STARTCHAR grave +ENCODING 96 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR a +ENCODING 97 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR b +ENCODING 98 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR c +ENCODING 99 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR d +ENCODING 100 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +000C +000C +000C +000C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR e +ENCODING 101 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR f +ENCODING 102 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +007E +00FE +01C0 +0180 +0180 +0180 +1FF8 +1FF8 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR g +ENCODING 103 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR h +ENCODING 104 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR i +ENCODING 105 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR j +ENCODING 106 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR k +ENCODING 107 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +1800 +1800 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR l +ENCODING 108 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR m +ENCODING 109 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +319C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR n +ENCODING 110 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR o +ENCODING 111 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR p +ENCODING 112 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR q +ENCODING 113 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR r +ENCODING 114 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +33FC +37FC +3E00 +3C00 +3800 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR s +ENCODING 115 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR t +ENCODING 116 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +3FF0 +3FF0 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01FC +00FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR u +ENCODING 117 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR v +ENCODING 118 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR w +ENCODING 119 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR x +ENCODING 120 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +381C +1C38 +0E70 +07E0 +03C0 +03C0 +07E0 +0E70 +1C38 +381C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR y +ENCODING 121 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR z +ENCODING 122 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR braceleft +ENCODING 123 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00F0 +01F0 +0380 +0300 +0300 +0300 +0300 +0300 +0300 +1E00 +1E00 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01F0 +00F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR bar +ENCODING 124 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR braceright +ENCODING 125 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1E00 +1F00 +0380 +0180 +0180 +0180 +0180 +0180 +0180 +00F0 +00F0 +0180 +0180 +0180 +0180 +0180 +0180 +0380 +1F00 +1E00 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR asciitilde +ENCODING 126 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0E0C +1F0C +3B8C +31DC +30F8 +3070 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR nbspace +ENCODING 160 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR exclamdown +ENCODING 161 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR cent +ENCODING 162 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0FF0 +1FF8 +399C +318C +3180 +3180 +3180 +3180 +3180 +3180 +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0000 +0000 +0000 +ENDCHAR +STARTCHAR sterling +ENCODING 163 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +03E0 +07F0 +0E38 +0C18 +0C00 +0C00 +0C00 +0C00 +0C00 +3FE0 +3FE0 +0C00 +0C00 +0C00 +0C00 +0C00 +0C0C +0C0C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR currency +ENCODING 164 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +381C +1C38 +0FF0 +0FF0 +1C38 +1818 +1818 +1818 +1818 +1C38 +0FF0 +0FF0 +1C38 +381C +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR yen +ENCODING 165 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +1FF8 +1FF8 +0180 +0180 +1FF8 +1FF8 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR brokenbar +ENCODING 166 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR section +ENCODING 167 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +07C0 +0FE0 +1C70 +1830 +1800 +1C00 +0F80 +0FC0 +18E0 +1870 +1830 +1830 +1830 +1C30 +0E30 +07E0 +03E0 +0070 +0030 +1830 +1C70 +0FE0 +07C0 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dieresis +ENCODING 168 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR copyright +ENCODING 169 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +3FFC +700E +67E6 +6FF6 +6C36 +6C06 +6C06 +6C06 +6C06 +6C36 +6FF6 +67E6 +700E +3FFC +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ordfeminine +ENCODING 170 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FE0 +0FF0 +0038 +0018 +07F8 +0FF8 +1C18 +1818 +1C18 +0FF8 +07F8 +0000 +0000 +1FF8 +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR guillemotleft +ENCODING 171 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01CE +039C +0738 +0E70 +1CE0 +39C0 +7380 +7380 +39C0 +1CE0 +0E70 +0738 +039C +01CE +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR logicalnot +ENCODING 172 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR softhyphen +ENCODING 173 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR registered +ENCODING 174 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +3FFC +700E +6FE6 +6FF6 +6C36 +6C36 +6C36 +6FE6 +6FC6 +6DC6 +6CE6 +6C76 +700E +3FFC +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR macron +ENCODING 175 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR degree +ENCODING 176 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0C30 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR plusminus +ENCODING 177 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR twosuperior +ENCODING 178 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0070 +00E0 +01C0 +0380 +0700 +0FF0 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR threesuperior +ENCODING 179 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0030 +01E0 +01E0 +0030 +0030 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR acute +ENCODING 180 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR mu +ENCODING 181 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +301C +303C +307C +3FEC +3FCC +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR paragraph +ENCODING 182 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FFC +3FFC +718C +618C +618C +618C +618C +618C +718C +3F8C +1F8C +018C +018C +018C +018C +018C +018C +018C +018C +018C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR periodcentered +ENCODING 183 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR cedilla +ENCODING 184 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0380 +0300 +0600 +0000 +ENDCHAR +STARTCHAR onesuperior +ENCODING 185 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0180 +0380 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ordmasculine +ENCODING 186 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +07E0 +0FF0 +1C38 +1818 +1818 +1818 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +0000 +1FF8 +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR guillemotright +ENCODING 187 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7380 +39C0 +1CE0 +0E70 +0738 +039C +01CE +01CE +039C +0738 +0E70 +1CE0 +39C0 +7380 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR onequarter +ENCODING 188 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C00 +1C00 +3C00 +0C00 +0C06 +0C0E +0C1C +0C38 +0C70 +00E0 +01C6 +038E +071E +0E36 +1C66 +38C6 +71FE +61FE +0006 +0006 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR onehalf +ENCODING 189 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1800 +3800 +7806 +180E +181C +1838 +1870 +18E0 +19C0 +0380 +0700 +0E7C +1CFE +38C6 +70C6 +600C +0018 +0030 +007E +00FE +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR threequarters +ENCODING 190 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3F00 +7F80 +6180 +0F00 +0F00 +0186 +618E +7F9C +3F38 +0070 +00E6 +01CE +039E +0736 +0E66 +1CC6 +39FE +71FE +6006 +0006 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR questiondown +ENCODING 191 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0300 +0600 +0C00 +1800 +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Agrave +ENCODING 192 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Aacute +ENCODING 193 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Acircumflex +ENCODING 194 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Atilde +ENCODING 195 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Adieresis +ENCODING 196 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Aring +ENCODING 197 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +03C0 +0660 +0660 +0660 +03C0 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR AE +ENCODING 198 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FFF +3FFF +70C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +7FFE +7FFE +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60FF +60FF +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ccedilla +ENCODING 199 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0180 +0180 +0380 +0300 +0600 +0000 +ENDCHAR +STARTCHAR Egrave +ENCODING 200 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Eacute +ENCODING 201 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ecircumflex +ENCODING 202 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Edieresis +ENCODING 203 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Igrave +ENCODING 204 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Iacute +ENCODING 205 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Icircumflex +ENCODING 206 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Idieresis +ENCODING 207 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Eth +ENCODING 208 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FC0 +3FF0 +3038 +3018 +300C +300C +300C +300C +300C +7F8C +7F8C +300C +300C +300C +300C +300C +3018 +3038 +3FF0 +3FC0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ntilde +ENCODING 209 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ograve +ENCODING 210 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Oacute +ENCODING 211 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ocircumflex +ENCODING 212 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Otilde +ENCODING 213 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Odieresis +ENCODING 214 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR multiply +ENCODING 215 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +701C +3838 +1C70 +0EE0 +07C0 +0380 +07C0 +0EE0 +1C70 +3838 +701C +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Oslash +ENCODING 216 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300E +300E +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +700C +700C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ugrave +ENCODING 217 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Uacute +ENCODING 218 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ucircumflex +ENCODING 219 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Udieresis +ENCODING 220 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Yacute +ENCODING 221 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Thorn +ENCODING 222 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR germandbls +ENCODING 223 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FE0 +3FF0 +3038 +3018 +3018 +3018 +3018 +3030 +3FF0 +3FF0 +3038 +301C +300C +300C +300C +300C +380C +3C1C +37F8 +33F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR agrave +ENCODING 224 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR aacute +ENCODING 225 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR acircumflex +ENCODING 226 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR atilde +ENCODING 227 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR adieresis +ENCODING 228 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR aring +ENCODING 229 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +0660 +0660 +0660 +03C0 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ae +ENCODING 230 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3EF8 +3FFC +018E +0186 +0186 +1F86 +3FFE +71FE +6180 +6180 +6180 +71C6 +3FFE +1F7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ccedilla +ENCODING 231 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0180 +0180 +0380 +0300 +0600 +0000 +ENDCHAR +STARTCHAR egrave +ENCODING 232 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR eacute +ENCODING 233 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ecircumflex +ENCODING 234 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR edieresis +ENCODING 235 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR igrave +ENCODING 236 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1C00 +0E00 +0700 +0380 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR iacute +ENCODING 237 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR icircumflex +ENCODING 238 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR idieresis +ENCODING 239 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR eth +ENCODING 240 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1DC0 +0F80 +1F00 +3B80 +01C0 +00E0 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ntilde +ENCODING 241 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ograve +ENCODING 242 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR oacute +ENCODING 243 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ocircumflex +ENCODING 244 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR otilde +ENCODING 245 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR odieresis +ENCODING 246 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR divide +ENCODING 247 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +3FFC +3FFC +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR oslash +ENCODING 248 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF6 +1FFE +381C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +381C +7FF8 +6FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ugrave +ENCODING 249 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uacute +ENCODING 250 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ucircumflex +ENCODING 251 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR udieresis +ENCODING 252 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR yacute +ENCODING 253 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR thorn +ENCODING 254 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR ydieresis +ENCODING 255 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Amacron +ENCODING 256 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR amacron +ENCODING 257 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Abreve +ENCODING 258 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR abreve +ENCODING 259 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Aogonek +ENCODING 260 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +003C +0070 +0060 +007C +003C +0000 +ENDCHAR +STARTCHAR aogonek +ENCODING 261 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +003C +0070 +0060 +007C +003C +0000 +ENDCHAR +STARTCHAR Cacute +ENCODING 262 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR cacute +ENCODING 263 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ccircumflex +ENCODING 264 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ccircumflex +ENCODING 265 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Cdotaccent +ENCODING 266 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR cdotaccent +ENCODING 267 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ccaron +ENCODING 268 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ccaron +ENCODING 269 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Dcaron +ENCODING 270 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3FC0 +3FF0 +3038 +3018 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3018 +3038 +3FF0 +3FC0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dcaron +ENCODING 271 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +000C +000C +000C +000C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Dcroat +ENCODING 272 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FC0 +3FF0 +3038 +3018 +300C +300C +300C +300C +300C +7F8C +7F8C +300C +300C +300C +300C +300C +3018 +3038 +3FF0 +3FC0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dcroat +ENCODING 273 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +000C +000C +01FF +01FF +000C +000C +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Emacron +ENCODING 274 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR emacron +ENCODING 275 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ebreve +ENCODING 276 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ebreve +ENCODING 277 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Edotaccent +ENCODING 278 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR edotaccent +ENCODING 279 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Eogonek +ENCODING 280 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +003C +0070 +0060 +007C +003C +0000 +ENDCHAR +STARTCHAR eogonek +ENCODING 281 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR Ecaron +ENCODING 282 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ecaron +ENCODING 283 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Gcircumflex +ENCODING 284 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR gcircumflex +ENCODING 285 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Gbreve +ENCODING 286 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR gbreve +ENCODING 287 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Gdotaccent +ENCODING 288 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR gdotaccent +ENCODING 289 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00C0 +00C0 +00C0 +00C0 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Gcommaaccent +ENCODING 290 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR gcommaaccent +ENCODING 291 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0030 +0060 +00E0 +00C0 +00C0 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Hcircumflex +ENCODING 292 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR hcircumflex +ENCODING 293 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Hbar +ENCODING 294 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +7FFE +7FFE +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR hbar +ENCODING 295 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +FF80 +FF80 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Itilde +ENCODING 296 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR itilde +ENCODING 297 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Imacron +ENCODING 298 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR imacron +ENCODING 299 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ibreve +ENCODING 300 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ibreve +ENCODING 301 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Iogonek +ENCODING 302 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0180 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR iogonek +ENCODING 303 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0180 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR Idotaccent +ENCODING 304 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dotlessi +ENCODING 305 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR IJ +ENCODING 306 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +F01E +F01E +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +630C +630C +630C +639C +F1F8 +F0F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ij +ENCODING 307 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3006 +3006 +3006 +3006 +0000 +0000 +701E +701E +3006 +3006 +3006 +3006 +3006 +3006 +3006 +3006 +3006 +3006 +7806 +7806 +0186 +0186 +01CE +00FC +0078 +0000 +ENDCHAR +STARTCHAR Jcircumflex +ENCODING 308 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0018 +003C +007E +00E7 +0000 +007E +007E +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +3018 +3018 +3018 +3838 +1FF0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR jcircumflex +ENCODING 309 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +003C +007E +00E7 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR Kcommaaccent +ENCODING 310 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR kcommaaccent +ENCODING 311 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +1800 +1800 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR kgreenlandic +ENCODING 312 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Lacute +ENCODING 313 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0700 +0E00 +1C00 +3800 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR lacute +ENCODING 314 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Lcommaaccent +ENCODING 315 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR lcommaaccent +ENCODING 316 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR Lcaron +ENCODING 317 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR lcaron +ENCODING 318 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ldot +ENCODING 319 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3030 +3030 +3030 +3030 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ldot +ENCODING 320 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0183 +0183 +0183 +0183 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Lslash +ENCODING 321 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3300 +3600 +3C00 +3800 +7000 +F000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR lslash +ENCODING 322 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +01B0 +01E0 +01C0 +0380 +0780 +0D80 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Nacute +ENCODING 323 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR nacute +ENCODING 324 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ncommaaccent +ENCODING 325 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR ncommaaccent +ENCODING 326 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR Ncaron +ENCODING 327 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ncaron +ENCODING 328 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR napostrophe +ENCODING 329 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +6000 +6000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Eng +ENCODING 330 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +000C +000C +001C +00F8 +00F0 +0000 +ENDCHAR +STARTCHAR eng +ENCODING 331 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +000C +000C +001C +00F8 +00F0 +0000 +ENDCHAR +STARTCHAR Omacron +ENCODING 332 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR omacron +ENCODING 333 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Obreve +ENCODING 334 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR obreve +ENCODING 335 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ohungarumlaut +ENCODING 336 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +01CE +039C +0738 +0E70 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ohungarumlaut +ENCODING 337 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +01CE +039C +0738 +0E70 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR OE +ENCODING 338 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FFF +3FFF +70C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60FE +60FE +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +70C0 +3FFF +1FFF +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR oe +ENCODING 339 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +3FFC +718E +6186 +6186 +6186 +61FE +61FE +6180 +6180 +6180 +7186 +3FFE +1FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Racute +ENCODING 340 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR racute +ENCODING 341 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +33FC +37FC +3E00 +3C00 +3800 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Rcommaaccent +ENCODING 342 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR rcommaaccent +ENCODING 343 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +31FC +33FC +3600 +3C00 +3800 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +3000 +3000 +7000 +6000 +C000 +ENDCHAR +STARTCHAR Rcaron +ENCODING 344 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR rcaron +ENCODING 345 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +33FC +37FC +3E00 +3C00 +3800 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Sacute +ENCODING 346 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR sacute +ENCODING 347 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Scircumflex +ENCODING 348 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR scircumflex +ENCODING 349 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Scedilla +ENCODING 350 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0180 +0180 +0380 +0300 +0600 +0000 +ENDCHAR +STARTCHAR scedilla +ENCODING 351 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0180 +0180 +0380 +0300 +0600 +0000 +ENDCHAR +STARTCHAR Scaron +ENCODING 352 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR scaron +ENCODING 353 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Tcedilla +ENCODING 354 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +00C0 +00C0 +01C0 +0180 +0300 +0000 +ENDCHAR +STARTCHAR tcedilla +ENCODING 355 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +3FF0 +3FF0 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01FC +00FC +0030 +0030 +0070 +0060 +00C0 +0000 +ENDCHAR +STARTCHAR Tcaron +ENCODING 356 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR tcaron +ENCODING 357 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +1CE0 +0FC0 +0780 +0300 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +3FF0 +3FF0 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01FC +00FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Tbar +ENCODING 358 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0FF0 +0FF0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR tbar +ENCODING 359 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +3FF0 +3FF0 +0300 +0300 +0FC0 +0FC0 +0300 +0300 +0300 +0300 +0300 +0380 +01FC +00FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Utilde +ENCODING 360 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR utilde +ENCODING 361 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Umacron +ENCODING 362 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR umacron +ENCODING 363 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ubreve +ENCODING 364 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ubreve +ENCODING 365 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Uring +ENCODING 366 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +03C0 +0660 +0660 +0660 +03C0 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uring +ENCODING 367 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +0660 +0660 +0660 +03C0 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Uhungarumlaut +ENCODING 368 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +01CE +039C +0738 +0E70 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uhungarumlaut +ENCODING 369 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +01CE +039C +0738 +0E70 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Uogonek +ENCODING 370 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR uogonek +ENCODING 371 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +003C +0070 +0060 +007C +003C +0000 +ENDCHAR +STARTCHAR Wcircumflex +ENCODING 372 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +610C +638C +638C +67CC +6C6C +6C6C +783C +701C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR wcircumflex +ENCODING 373 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +300C +300C +300C +300C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ycircumflex +ENCODING 374 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ycircumflex +ENCODING 375 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Ydieresis +ENCODING 376 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Zacute +ENCODING 377 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +3FFC +3FFC +000C +000C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR zacute +ENCODING 378 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Zdotaccent +ENCODING 379 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +3FFC +3FFC +000C +000C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR zdotaccent +ENCODING 380 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Zcaron +ENCODING 381 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3FFC +3FFC +000C +000C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR zcaron +ENCODING 382 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR longs +ENCODING 383 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00FC +01FC +0380 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0186 +ENCODING 390 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni018E +ENCODING 398 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +000C +000C +000C +000C +07FC +07FC +000C +000C +000C +000C +000C +000C +000C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Schwa +ENCODING 399 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +000C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0190 +ENCODING 400 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FE0 +1FE0 +3800 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR florin +ENCODING 402 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00F8 +01FC +018C +018C +0180 +0180 +0180 +0180 +0FF0 +0FF0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +3180 +3180 +3F80 +1F00 +0000 +0000 +ENDCHAR +STARTCHAR uni019D +ENCODING 413 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +3000 +3000 +3000 +7000 +E000 +0000 +ENDCHAR +STARTCHAR uni019E +ENCODING 414 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR uni01B5 +ENCODING 437 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +001C +0038 +0070 +00E0 +3FFC +3FFC +0380 +0700 +0E00 +1C00 +3800 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01B6 +ENCODING 438 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0038 +0070 +00E0 +01C0 +3FF8 +3FF8 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ezh +ENCODING 439 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +07F0 +07F8 +001C +000C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01CD +ENCODING 461 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01CE +ENCODING 462 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01CF +ENCODING 463 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01D0 +ENCODING 464 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01D1 +ENCODING 465 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01D2 +ENCODING 466 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01D3 +ENCODING 467 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01D4 +ENCODING 468 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01E2 +ENCODING 482 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1FFC +1FFC +0000 +0000 +1FFF +3FFF +70C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +7FFE +7FFE +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60FF +60FF +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01E3 +ENCODING 483 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +1FF8 +0000 +0000 +3EF8 +3FFC +018E +0186 +0186 +1F86 +3FFE +71FE +6180 +6180 +6180 +71C6 +3FFE +1F7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01E4 +ENCODING 484 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +30FF +30FF +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01E5 +ENCODING 485 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +30FF +30FF +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR Gcaron +ENCODING 486 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR gcaron +ENCODING 487 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR uni01E8 +ENCODING 488 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01E9 +ENCODING 489 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +1800 +1800 +1800 +1800 +1800 +1800 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01EA +ENCODING 490 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR uni01EB +ENCODING 491 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR uni01EC +ENCODING 492 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR uni01ED +ENCODING 493 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR uni01EE +ENCODING 494 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +07F0 +07F8 +001C +000C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01EF +ENCODING 495 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +07F0 +07F8 +001C +000C +000C +000C +000C +300C +381C +1FF8 +0FF0 +0000 +ENDCHAR +STARTCHAR uni01F0 +ENCODING 496 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00E7 +007E +003C +0018 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR uni01F4 +ENCODING 500 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +30FC +30FC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni01F5 +ENCODING 501 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0038 +0070 +00E0 +01C0 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR AEacute +ENCODING 508 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0038 +0070 +00E0 +01C0 +0000 +1FFF +3FFF +70C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +7FFE +7FFE +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60FF +60FF +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR aeacute +ENCODING 509 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +3EF8 +3FFC +018E +0186 +0186 +1F86 +3FFE +71FE +6180 +6180 +6180 +71C6 +3FFE +1F7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Oslashacute +ENCODING 510 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0FF0 +1FF8 +381C +300E +300E +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +700C +700C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR oslashacute +ENCODING 511 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF6 +1FFE +381C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +381C +7FF8 +6FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Scommaaccent +ENCODING 536 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR scommaaccent +ENCODING 537 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR Tcommaaccent +ENCODING 538 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0180 +0180 +0380 +0300 +0600 +ENDCHAR +STARTCHAR tcommaaccent +ENCODING 539 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +3FF0 +3FF0 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01FC +00FC +0000 +0030 +0030 +0070 +0060 +00C0 +ENDCHAR +STARTCHAR uni0232 +ENCODING 562 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0233 +ENCODING 563 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR dotlessj +ENCODING 567 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR uni0254 +ENCODING 596 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +000C +000C +000C +000C +000C +000C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0258 +ENCODING 600 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +000C +000C +000C +301C +3FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR schwa +ENCODING 601 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +3FF8 +301C +000C +000C +000C +3FFC +3FFC +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni025B +ENCODING 603 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3800 +1FC0 +1FC0 +3800 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0272 +ENCODING 626 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3000 +3000 +3000 +7000 +E000 +0000 +ENDCHAR +STARTCHAR ezh +ENCODING 658 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +001C +0038 +0070 +00E0 +01C0 +0380 +07F0 +07F8 +001C +000C +000C +000C +000C +300C +381C +1FF8 +0FF0 +0000 +ENDCHAR +STARTCHAR commaturnedmod +ENCODING 699 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0060 +00C0 +01C0 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57929 +ENCODING 700 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0380 +0300 +0600 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii64937 +ENCODING 701 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +01C0 +00C0 +0060 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR circumflex +ENCODING 710 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR caron +ENCODING 711 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR breve +ENCODING 728 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dotaccent +ENCODING 729 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ogonek +ENCODING 731 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01C0 +0380 +0300 +03E0 +01E0 +0000 +ENDCHAR +STARTCHAR tilde +ENCODING 732 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR hungarumlaut +ENCODING 733 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +01CE +039C +0738 +0E70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR gravecomb +ENCODING 768 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR acutecomb +ENCODING 769 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0302 +ENCODING 770 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +03C0 +07E0 +0E70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR tildecomb +ENCODING 771 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0304 +ENCODING 772 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0305 +ENCODING 773 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0306 +ENCODING 774 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0307 +ENCODING 775 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0308 +ENCODING 776 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni030A +ENCODING 778 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +03C0 +0660 +0660 +0660 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni030B +ENCODING 779 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +01CE +039C +0738 +0E70 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni030C +ENCODING 780 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0329 +ENCODING 809 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR tonos +ENCODING 900 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dieresistonos +ENCODING 901 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Alphatonos +ENCODING 902 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR anoteleia +ENCODING 903 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Epsilontonos +ENCODING 904 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Etatonos +ENCODING 905 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Iotatonos +ENCODING 906 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Omicrontonos +ENCODING 908 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Upsilontonos +ENCODING 910 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +1806 +1806 +1806 +0C0C +0C0C +0618 +0618 +0330 +0330 +01E0 +01E0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Omegatonos +ENCODING 911 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1C00 +3800 +7000 +E000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1C38 +0E70 +0660 +0660 +3E7C +3E7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR iotadieresistonos +ENCODING 912 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +00E0 +01C0 +0380 +0700 +0000 +1860 +1860 +1860 +1860 +0000 +0000 +0F00 +0F00 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01F0 +00F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Alpha +ENCODING 913 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Beta +ENCODING 914 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +3018 +3FF0 +3FF0 +3038 +301C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Gamma +ENCODING 915 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Delta +ENCODING 916 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +03C0 +03C0 +03C0 +0660 +0660 +0660 +0C30 +0C30 +0C30 +1818 +1818 +1818 +300C +300C +300C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Epsilon +ENCODING 917 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Zeta +ENCODING 918 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Eta +ENCODING 919 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Theta +ENCODING 920 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +37EC +37EC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Iota +ENCODING 921 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Kappa +ENCODING 922 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Lambda +ENCODING 923 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +03C0 +03C0 +03C0 +0660 +0660 +0660 +0C30 +0C30 +0C30 +1818 +1818 +1818 +1818 +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Mu +ENCODING 924 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +701C +783C +6C6C +6C6C +67CC +638C +638C +610C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Nu +ENCODING 925 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Xi +ENCODING 926 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Omicron +ENCODING 927 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Pi +ENCODING 928 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Rho +ENCODING 929 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Sigma +ENCODING 931 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3800 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Tau +ENCODING 932 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Upsilon +ENCODING 933 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Phi +ENCODING 934 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0FF0 +1FF8 +399C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Chi +ENCODING 935 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +1818 +1818 +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Psi +ENCODING 936 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Omega +ENCODING 937 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1C38 +0E70 +0660 +0660 +3E7C +3E7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Iotadieresis +ENCODING 938 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Upsilondieresis +ENCODING 939 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR alphatonos +ENCODING 940 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +1FCC +3FFC +7038 +6030 +6030 +6030 +6030 +6030 +6030 +6030 +6030 +7038 +3FFC +1FCC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR epsilontonos +ENCODING 941 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3800 +1FC0 +1FC0 +3800 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR etatonos +ENCODING 942 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR iotatonos +ENCODING 943 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00E0 +01C0 +0380 +0700 +0000 +0000 +0F00 +0F00 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01F0 +00F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR upsilondieresistonos +ENCODING 944 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR alpha +ENCODING 945 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FCC +3FFC +7038 +6030 +6030 +6030 +6030 +6030 +6030 +6030 +6030 +7038 +3FFC +1FCC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR beta +ENCODING 946 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FE0 +3FF0 +3038 +3018 +3018 +3018 +3018 +3030 +3FF0 +3FF0 +3038 +301C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR gamma +ENCODING 947 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR delta +ENCODING 948 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF0 +0E00 +0700 +0380 +01C0 +07E0 +0FF0 +1818 +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR epsilon +ENCODING 949 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3800 +1FC0 +1FC0 +3800 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR zeta +ENCODING 950 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +1800 +3800 +3000 +3000 +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +001C +0078 +0070 +0000 +ENDCHAR +STARTCHAR eta +ENCODING 951 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR theta +ENCODING 952 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +1C38 +1818 +1818 +1818 +1818 +1818 +1818 +1FF8 +1FF8 +1818 +1818 +1818 +1818 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR iota +ENCODING 953 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0F00 +0F00 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01F0 +00F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR kappa +ENCODING 954 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR lambda +ENCODING 955 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0600 +0600 +0300 +0300 +0180 +0180 +03C0 +03C0 +0660 +0660 +0660 +0C30 +0C30 +0C30 +1818 +1818 +1818 +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR mugreek +ENCODING 956 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +301C +303C +307C +3FEC +3FCC +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR nu +ENCODING 957 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR xi +ENCODING 958 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +3800 +3000 +3000 +3000 +3000 +1800 +0FF0 +0FF0 +1C00 +3800 +3000 +3000 +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +001C +0078 +0070 +0000 +ENDCHAR +STARTCHAR omicron +ENCODING 959 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR pi +ENCODING 960 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR rho +ENCODING 961 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR sigma1 +ENCODING 962 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +001C +0078 +0070 +0000 +ENDCHAR +STARTCHAR sigma +ENCODING 963 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FFE +1FFE +3870 +3038 +301C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR tau +ENCODING 964 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01C0 +00F8 +0078 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR upsilon +ENCODING 965 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR phi +ENCODING 966 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0CF0 +1DF8 +399C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR chi +ENCODING 967 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +1818 +1818 +300C +300C +0000 +ENDCHAR +STARTCHAR psi +ENCODING 968 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR omega +ENCODING 969 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +1C38 +381C +300C +318C +318C +318C +318C +318C +318C +318C +3BDC +1FF8 +0E70 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR iotadieresis +ENCODING 970 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1860 +1860 +1860 +1860 +0000 +0000 +0F00 +0F00 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01F0 +00F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR upsilondieresis +ENCODING 971 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR omicrontonos +ENCODING 972 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR upsilontonos +ENCODING 973 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR omegatonos +ENCODING 974 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +0C30 +1C38 +381C +300C +318C +318C +318C +318C +318C +318C +318C +3BDC +1FF8 +0E70 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR theta1 +ENCODING 977 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +1C38 +1818 +1818 +1818 +1818 +0FFE +07FE +0018 +0018 +0018 +7818 +7818 +1818 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR phi1 +ENCODING 981 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0FF0 +1FF8 +399C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni03F0 +ENCODING 1008 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +780C +7C1C +0E38 +0670 +03E0 +03C0 +0380 +0380 +0780 +0F80 +1CC0 +38E0 +707C +603C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni03F1 +ENCODING 1009 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3800 +1FF8 +0FF8 +0000 +ENDCHAR +STARTCHAR uni03F2 +ENCODING 1010 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni03F3 +ENCODING 1011 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR uni03F4 +ENCODING 1012 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni03F5 +ENCODING 1013 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03FC +0FFC +1C00 +1800 +3000 +3000 +3FF0 +3FF0 +3000 +3000 +1800 +1C00 +0FFC +03FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni03F6 +ENCODING 1014 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FC0 +3FF0 +0038 +0018 +000C +000C +0FFC +0FFC +000C +000C +0018 +0038 +3FF0 +3FC0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0400 +ENCODING 1024 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10023 +ENCODING 1025 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10051 +ENCODING 1026 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +FF00 +FF00 +1800 +1800 +1800 +1800 +1800 +1FF0 +1FF8 +181C +180C +180C +180C +180C +180C +180C +180C +181C +18F8 +18F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10052 +ENCODING 1027 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10053 +ENCODING 1028 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10054 +ENCODING 1029 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10055 +ENCODING 1030 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10056 +ENCODING 1031 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10057 +ENCODING 1032 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +007E +007E +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +3018 +3018 +3018 +3838 +1FF0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10058 +ENCODING 1033 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1F80 +3F80 +7180 +6180 +6180 +6180 +6180 +61F8 +61FC +618E +6186 +6186 +6186 +6186 +6186 +6186 +6186 +618E +E1FC +C1F8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10059 +ENCODING 1034 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +6180 +6180 +6180 +6180 +6180 +6180 +6180 +61F8 +61FC +7F8E +7F86 +6186 +6186 +6186 +6186 +6186 +6186 +618E +61FC +61F8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10060 +ENCODING 1035 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +FF00 +FF00 +1800 +1800 +1800 +1800 +1800 +1FF0 +1FF8 +181C +180C +180C +180C +180C +180C +180C +180C +180C +180C +180C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10061 +ENCODING 1036 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0070 +00E0 +01C0 +0380 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni040D +ENCODING 1037 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0E00 +0700 +0380 +01C0 +0000 +300C +300C +300C +300C +300C +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10062 +ENCODING 1038 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +001C +1FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10145 +ENCODING 1039 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR afii10017 +ENCODING 1040 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10018 +ENCODING 1041 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF8 +3FF8 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10019 +ENCODING 1042 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +3018 +3FF0 +3FF0 +3038 +301C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10020 +ENCODING 1043 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10021 +ENCODING 1044 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07F8 +0FF8 +1C18 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +3FFC +7FFE +6006 +6006 +6006 +6006 +0000 +0000 +ENDCHAR +STARTCHAR afii10022 +ENCODING 1045 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10024 +ENCODING 1046 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10025 +ENCODING 1047 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +001C +07F8 +07F8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10026 +ENCODING 1048 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10027 +ENCODING 1049 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +300C +300C +300C +300C +300C +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10028 +ENCODING 1050 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10029 +ENCODING 1051 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +03FC +07FC +0E0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +1C0C +380C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10030 +ENCODING 1052 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +701C +783C +6C6C +6C6C +67CC +638C +638C +610C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10031 +ENCODING 1053 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10032 +ENCODING 1054 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10033 +ENCODING 1055 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10034 +ENCODING 1056 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10035 +ENCODING 1057 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10036 +ENCODING 1058 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10037 +ENCODING 1059 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +001C +1FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10038 +ENCODING 1060 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0180 +0180 +0FF0 +1FF8 +399C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10039 +ENCODING 1061 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +1818 +1818 +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10040 +ENCODING 1062 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380E +1FFF +0FFF +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR afii10041 +ENCODING 1063 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10042 +ENCODING 1064 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +398C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10043 +ENCODING 1065 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +398C +1FFE +0FFF +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR afii10044 +ENCODING 1066 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +F000 +F000 +3000 +3000 +3000 +3000 +3FE0 +3FF0 +3038 +3018 +3018 +3018 +3018 +3018 +3018 +3018 +3018 +3038 +3FF0 +3FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10045 +ENCODING 1067 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +600C +600C +600C +600C +7F0C +7F8C +61CC +60CC +60CC +60CC +60CC +60CC +60CC +60CC +60CC +61CC +7F8C +7F0C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10046 +ENCODING 1068 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FF0 +3038 +3018 +3018 +3018 +3018 +3018 +3018 +3018 +3018 +3038 +3FF0 +3FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10047 +ENCODING 1069 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +000C +07FC +07FC +000C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10048 +ENCODING 1070 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +60F0 +61F8 +639C +630C +630C +630C +630C +630C +630C +7F0C +7F0C +630C +630C +630C +630C +630C +630C +639C +61F8 +60F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10049 +ENCODING 1071 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +00EC +01CC +038C +070C +0E0C +1C0C +380C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10065 +ENCODING 1072 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10066 +ENCODING 1073 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF0 +3800 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10067 +ENCODING 1074 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FC0 +3FE0 +3070 +3030 +3030 +3030 +3030 +3070 +3FE0 +3FF0 +3038 +301C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10068 +ENCODING 1075 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10069 +ENCODING 1076 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR afii10070 +ENCODING 1077 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10072 +ENCODING 1078 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10073 +ENCODING 1079 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +000C +001C +03F8 +03F8 +001C +000C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10074 +ENCODING 1080 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10075 +ENCODING 1081 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10076 +ENCODING 1082 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10077 +ENCODING 1083 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03FC +07FC +0E0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +0C0C +1C0C +380C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10078 +ENCODING 1084 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +600C +701C +783C +7C7C +6EEC +67CC +638C +610C +600C +600C +600C +600C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10079 +ENCODING 1085 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10080 +ENCODING 1086 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10081 +ENCODING 1087 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10082 +ENCODING 1088 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR afii10083 +ENCODING 1089 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10084 +ENCODING 1090 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10085 +ENCODING 1091 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR afii10086 +ENCODING 1092 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0FF0 +1FF8 +399C +318C +318C +318C +318C +318C +318C +318C +318C +399C +1FF8 +0FF0 +0180 +0180 +0180 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10087 +ENCODING 1093 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +381C +1C38 +0E70 +07E0 +03C0 +03C0 +07E0 +0E70 +1C38 +381C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10088 +ENCODING 1094 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFE +0FFF +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR afii10089 +ENCODING 1095 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10090 +ENCODING 1096 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +398C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10091 +ENCODING 1097 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +398C +1FFE +0FFF +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR afii10092 +ENCODING 1098 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3C00 +3C00 +0C00 +0C00 +0FF0 +0FF8 +0C1C +0C0C +0C0C +0C0C +0C0C +0C1C +0FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10093 +ENCODING 1099 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +600C +600C +7F0C +7F8C +61CC +60CC +60CC +60CC +60CC +61CC +7F8C +7F0C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10094 +ENCODING 1100 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +1FE0 +1FF0 +1838 +1818 +1818 +1818 +1818 +1838 +1FF0 +1FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10095 +ENCODING 1101 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +000C +000C +03FC +03FC +000C +000C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10096 +ENCODING 1102 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +60F0 +61F8 +630C +630C +630C +630C +7F0C +7F0C +630C +630C +630C +630C +61F8 +60F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10097 +ENCODING 1103 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FFC +1FFC +380C +300C +300C +380C +1FFC +0FFC +00EC +01CC +038C +070C +0E0C +1C0C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0450 +ENCODING 1104 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10071 +ENCODING 1105 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10099 +ENCODING 1106 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +FF80 +FF80 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +000C +000C +001C +00F8 +00F0 +0000 +ENDCHAR +STARTCHAR afii10100 +ENCODING 1107 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10101 +ENCODING 1108 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3FC0 +3FC0 +3000 +3000 +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10102 +ENCODING 1109 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +3000 +3000 +3800 +1FF0 +0FF8 +001C +000C +000C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10103 +ENCODING 1110 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10104 +ENCODING 1111 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10105 +ENCODING 1112 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR afii10106 +ENCODING 1113 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0F80 +1F80 +3980 +3180 +31F8 +31FC +318E +3186 +3186 +3186 +3186 +318E +71FC +61F8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10107 +ENCODING 1114 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +6180 +6180 +6180 +6180 +61F8 +61FC +7F8E +7F86 +6186 +6186 +6186 +618E +61FC +61F8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10108 +ENCODING 1115 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +FF80 +FF80 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10109 +ENCODING 1116 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0000 +0000 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni045D +ENCODING 1117 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0E00 +0700 +0380 +01C0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10110 +ENCODING 1118 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR afii10193 +ENCODING 1119 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR afii10146 +ENCODING 1122 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +FF00 +FF00 +3000 +3000 +3FE0 +3FF0 +3038 +3018 +3018 +3018 +3018 +3018 +3018 +3018 +3018 +3038 +3FF0 +3FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10194 +ENCODING 1123 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C00 +0C00 +0C00 +0C00 +3FC0 +3FC0 +0C00 +0C00 +0C00 +0C00 +0FF0 +0FF8 +0C1C +0C0C +0C0C +0C0C +0C0C +0C1C +0FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni046A +ENCODING 1130 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +1818 +1818 +0C30 +0C30 +0660 +03C0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni046B +ENCODING 1131 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +381C +1C38 +0E70 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10050 +ENCODING 1168 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +000C +000C +000C +000C +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10098 +ENCODING 1169 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +000C +000C +000C +000C +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0492 +ENCODING 1170 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +7F80 +7F80 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0493 +ENCODING 1171 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +7F80 +7F80 +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni0494 +ENCODING 1172 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +000C +000C +0018 +0030 +0000 +0000 +ENDCHAR +STARTCHAR uni0495 +ENCODING 1173 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3FC0 +3FE0 +3070 +3030 +3030 +3030 +3030 +0030 +0030 +0060 +00C0 +0000 +0000 +ENDCHAR +STARTCHAR uni0496 +ENCODING 1174 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +318C +318E +318F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni0497 +ENCODING 1175 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318E +318F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni0498 +ENCODING 1176 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +001C +07F8 +07F8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni0499 +ENCODING 1177 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +000C +001C +03F8 +03F8 +001C +000C +300C +381C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni049A +ENCODING 1178 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300E +0006 +0006 +0006 +0006 +0006 +0000 +ENDCHAR +STARTCHAR uni049B +ENCODING 1179 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR uni049C +ENCODING 1180 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +601C +6038 +6C70 +6CE0 +6DC0 +6F80 +6F00 +7E00 +7E00 +6F00 +6F80 +6DC0 +6CE0 +6C70 +6038 +601C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni049D +ENCODING 1181 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3638 +3670 +36E0 +37C0 +3F80 +3F80 +37C0 +36E0 +3670 +3638 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04A0 +ENCODING 1184 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +F00C +F01C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04A1 +ENCODING 1185 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +781C +7838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04A2 +ENCODING 1186 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300E +300F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni04A3 +ENCODING 1187 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300E +300F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni04A4 +ENCODING 1188 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +607F +607F +6060 +6060 +6060 +6060 +6060 +6060 +6060 +7FE0 +7FE0 +6060 +6060 +6060 +6060 +6060 +6060 +6060 +6060 +6060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04A5 +ENCODING 1189 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +607F +607F +6060 +6060 +6060 +6060 +7FE0 +7FE0 +6060 +6060 +6060 +6060 +6060 +6060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04AA +ENCODING 1194 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +300C +300C +381C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni04AB +ENCODING 1195 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +3000 +3000 +3000 +3000 +3000 +3000 +300C +381C +1FF8 +0FF0 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni04AE +ENCODING 1198 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04AF +ENCODING 1199 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni04B0 +ENCODING 1200 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +1FF8 +1FF8 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04B1 +ENCODING 1201 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +1FF8 +1FF8 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni04B2 +ENCODING 1202 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +1818 +1818 +300E +300F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni04B3 +ENCODING 1203 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +381C +1C38 +0E70 +07E0 +03C0 +03C0 +07E0 +0E70 +1C38 +381C +300E +300F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni04B6 +ENCODING 1206 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +000C +000E +000F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni04B7 +ENCODING 1207 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000E +000F +0003 +0003 +0003 +0003 +0003 +0000 +ENDCHAR +STARTCHAR uni04B8 +ENCODING 1208 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +318C +318C +318C +398C +1FFC +0FFC +018C +018C +018C +018C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04B9 +ENCODING 1209 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +318C +318C +398C +1FFC +0FFC +018C +018C +018C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04BA +ENCODING 1210 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04BB +ENCODING 1211 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04C0 +ENCODING 1216 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04C1 +ENCODING 1217 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +318C +318C +318C +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04C2 +ENCODING 1218 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04CF +ENCODING 1231 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D0 +ENCODING 1232 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D1 +ENCODING 1233 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D2 +ENCODING 1234 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D3 +ENCODING 1235 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +1FF0 +1FF8 +001C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D4 +ENCODING 1236 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FFF +3FFF +70C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +7FFE +7FFE +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60C0 +60FF +60FF +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D5 +ENCODING 1237 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3EF8 +3FFC +018E +0186 +0186 +1F86 +3FFE +71FE +6180 +6180 +6180 +71C6 +3FFE +1F7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D6 +ENCODING 1238 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +07E0 +03C0 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D7 +ENCODING 1239 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04D8 +ENCODING 1240 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +000C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii10846 +ENCODING 1241 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +3FF8 +301C +000C +000C +000C +3FFC +3FFC +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04DA +ENCODING 1242 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +000C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04DB +ENCODING 1243 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +1FF0 +3FF8 +301C +000C +000C +000C +3FFC +3FFC +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04DC +ENCODING 1244 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +318C +318C +318C +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04DD +ENCODING 1245 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +318C +318C +318C +399C +1DB8 +0FF0 +07E0 +0FF0 +1DB8 +399C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04DE +ENCODING 1246 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +001C +07F8 +07F8 +001C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04DF +ENCODING 1247 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +000C +001C +03F8 +03F8 +001C +000C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E2 +ENCODING 1250 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E3 +ENCODING 1251 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E4 +ENCODING 1252 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +300C +300C +300C +300C +300C +301C +303C +307C +30EC +31CC +338C +370C +3E0C +3C0C +380C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E5 +ENCODING 1253 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E6 +ENCODING 1254 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E7 +ENCODING 1255 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E8 +ENCODING 1256 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04E9 +ENCODING 1257 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04EA +ENCODING 1258 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04EB +ENCODING 1259 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04EC +ENCODING 1260 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0FF0 +1FF8 +381C +300C +300C +000C +000C +000C +000C +07FC +07FC +000C +000C +000C +000C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04ED +ENCODING 1261 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0FF0 +1FF8 +381C +300C +000C +000C +03FC +03FC +000C +000C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04EE +ENCODING 1262 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +001C +1FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04EF +ENCODING 1263 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR uni04F0 +ENCODING 1264 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +001C +1FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04F1 +ENCODING 1265 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR uni04F2 +ENCODING 1266 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +01CE +039C +0738 +0E70 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +001C +1FF8 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04F3 +ENCODING 1267 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +01CE +039C +0738 +0E70 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR uni04F4 +ENCODING 1268 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04F5 +ENCODING 1269 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04F8 +ENCODING 1272 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0C30 +0C30 +0C30 +0C30 +0000 +600C +600C +600C +600C +600C +600C +7F0C +7F8C +61CC +60CC +60CC +60CC +60CC +60CC +60CC +60CC +60CC +61CC +7F8C +7F0C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni04F9 +ENCODING 1273 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +600C +600C +600C +600C +7F0C +7F8C +61CC +60CC +60CC +60CC +60CC +61CC +7F8C +7F0C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57664 +ENCODING 1488 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +180C +180C +0C0C +0C0C +060C +061C +0338 +07F0 +0FE0 +1CC0 +3860 +3060 +3030 +3030 +3018 +3018 +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57665 +ENCODING 1489 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +7FE0 +7FF0 +0038 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +7FFE +7FFE +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57666 +ENCODING 1490 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1F80 +1FC0 +00E0 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +00F0 +01F0 +0398 +0718 +0E0C +1C0C +3806 +7006 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57667 +ENCODING 1491 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +7FFE +7FFE +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57668 +ENCODING 1492 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +001C +000C +000C +000C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57669 +ENCODING 1493 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1E00 +1F00 +0380 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57670 +ENCODING 1494 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +1FF8 +0070 +00E0 +01C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57671 +ENCODING 1495 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57672 +ENCODING 1496 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +30F0 +30F8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57673 +ENCODING 1497 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1E00 +1F00 +0380 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57674 +ENCODING 1498 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +001C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR afii57675 +ENCODING 1499 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +001C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +001C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57676 +ENCODING 1500 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +3000 +3000 +3000 +3000 +3FFC +3FFC +000C +000C +000C +000C +000C +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0300 +0300 +0300 +0300 +0300 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57677 +ENCODING 1501 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57678 +ENCODING 1502 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +E7C0 +FFF0 +7838 +3018 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +30FC +30FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57679 +ENCODING 1503 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1F80 +1FC0 +00E0 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0000 +ENDCHAR +STARTCHAR afii57680 +ENCODING 1504 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FC0 +0FE0 +0070 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0030 +0FF0 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57681 +ENCODING 1505 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +FFF0 +FFF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57682 +ENCODING 1506 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +180C +180C +180C +0C0C +0C0C +0C0C +0618 +0618 +0618 +0330 +0330 +0330 +01E0 +01C0 +0380 +3F00 +3C00 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57683 +ENCODING 1507 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +380C +1F0C +0F0C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +0000 +ENDCHAR +STARTCHAR afii57684 +ENCODING 1508 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +380C +1F0C +0F0C +000C +000C +000C +000C +000C +000C +001C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57685 +ENCODING 1509 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +180C +180C +0C0C +0C1C +0638 +0670 +03E0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR afii57686 +ENCODING 1510 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +180C +180C +0C0C +0C1C +0638 +0670 +03E0 +03C0 +0180 +0180 +00C0 +00C0 +0060 +0060 +0030 +0030 +3FF8 +3FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57687 +ENCODING 1511 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +000C +300C +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3300 +3300 +3300 +3300 +3300 +3300 +3000 +3000 +3000 +3000 +3000 +0000 +ENDCHAR +STARTCHAR afii57688 +ENCODING 1512 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +001C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +000C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57689 +ENCODING 1513 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +318C +318C +318C +318C +318C +338C +3F0C +3E0C +300C +300C +300C +300C +300C +301C +3FF8 +3FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57690 +ENCODING 1514 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +FFF0 +FFF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +F00C +E00C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni1E0C +ENCODING 7692 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FC0 +3FF0 +3038 +3018 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3018 +3038 +3FF0 +3FC0 +0000 +0300 +0300 +0300 +0300 +0000 +ENDCHAR +STARTCHAR uni1E0D +ENCODING 7693 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +000C +000C +000C +000C +000C +000C +0FFC +1FFC +380C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +00C0 +00C0 +00C0 +00C0 +0000 +ENDCHAR +STARTCHAR Klinebelow +ENCODING 7732 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +301C +3038 +3070 +30E0 +31C0 +3380 +3700 +3E00 +3C00 +3C00 +3E00 +3700 +3380 +31C0 +30E0 +3070 +3038 +301C +300C +0000 +0000 +0FF0 +0FF0 +0000 +0000 +ENDCHAR +STARTCHAR klinebelow +ENCODING 7733 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +1800 +1800 +181C +1838 +1870 +18E0 +19C0 +1B80 +1F00 +1F00 +1B80 +19C0 +18E0 +1870 +1838 +181C +0000 +0000 +07F0 +07F0 +0000 +0000 +ENDCHAR +STARTCHAR uni1E36 +ENCODING 7734 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E37 +ENCODING 7735 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E40 +ENCODING 7744 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +600C +600C +701C +783C +6C6C +6C6C +67CC +638C +638C +610C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni1E41 +ENCODING 7745 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +3FF0 +3FF8 +319C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni1E42 +ENCODING 7746 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +701C +783C +6C6C +6C6C +67CC +638C +638C +610C +600C +600C +600C +600C +600C +600C +600C +600C +600C +600C +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E43 +ENCODING 7747 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +319C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +318C +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E44 +ENCODING 7748 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0180 +0180 +0180 +0180 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni1E45 +ENCODING 7749 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni1E46 +ENCODING 7750 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +380C +3C0C +3E0C +370C +338C +31CC +30EC +307C +303C +301C +300C +300C +300C +300C +300C +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E47 +ENCODING 7751 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E6C +ENCODING 7788 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1E6D +ENCODING 7789 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +3FF0 +3FF0 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0380 +01FC +00FC +0000 +0030 +0030 +0030 +0030 +0000 +ENDCHAR +STARTCHAR Edotbelow +ENCODING 7864 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR edotbelow +ENCODING 7865 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR Etilde +ENCODING 7868 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FE0 +3FE0 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR etilde +ENCODING 7869 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +3FFC +3FFC +3000 +3000 +3000 +380C +1FFC +0FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni1ECA +ENCODING 7882 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +07E0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1ECB +ENCODING 7883 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR Odotbelow +ENCODING 7884 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR odotbelow +ENCODING 7885 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1EE4 +ENCODING 7908 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0000 +0180 +0180 +0180 +0180 +0000 +ENDCHAR +STARTCHAR uni1EE5 +ENCODING 7909 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +0000 +00C0 +00C0 +00C0 +00C0 +0000 +ENDCHAR +STARTCHAR Ytilde +ENCODING 7928 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0F18 +1B98 +19D8 +18F0 +0000 +300C +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ytilde +ENCODING 7929 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0F18 +1B98 +19D8 +18F0 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +380C +1FFC +0FFC +000C +000C +001C +1FF8 +1FF0 +0000 +ENDCHAR +STARTCHAR uni2000 +ENCODING 8192 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2001 +ENCODING 8193 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR enspace +ENCODING 8194 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2003 +ENCODING 8195 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2004 +ENCODING 8196 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2005 +ENCODING 8197 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2006 +ENCODING 8198 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2007 +ENCODING 8199 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2008 +ENCODING 8200 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2009 +ENCODING 8201 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni200A +ENCODING 8202 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni200B +ENCODING 8203 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii61664 +ENCODING 8204 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii301 +ENCODING 8205 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii299 +ENCODING 8206 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii300 +ENCODING 8207 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR hyphentwo +ENCODING 8208 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2011 +ENCODING 8209 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR figuredash +ENCODING 8210 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR endash +ENCODING 8211 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR emdash +ENCODING 8212 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7FFC +7FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii00208 +ENCODING 8213 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7FFC +7FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dblverticalbar +ENCODING 8214 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR underscoredbl +ENCODING 8215 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +3FFC +3FFC +ENDCHAR +STARTCHAR quoteleft +ENCODING 8216 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +00C0 +00C0 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quoteright +ENCODING 8217 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0300 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotesinglbase +ENCODING 8218 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0300 +0300 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotereversed +ENCODING 8219 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +00C0 +00C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotedblleft +ENCODING 8220 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0618 +0618 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotedblright +ENCODING 8221 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0618 +0618 +0618 +0618 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR quotedblbase +ENCODING 8222 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +1860 +1860 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni201F +ENCODING 8223 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +1860 +1860 +1860 +1860 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR dagger +ENCODING 8224 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +1FF8 +1FF8 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR daggerdbl +ENCODING 8225 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +1FF8 +1FF8 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +1FF8 +1FF8 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR bullet +ENCODING 8226 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +07E0 +07E0 +07E0 +07E0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR ellipsis +ENCODING 8230 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +318C +318C +318C +318C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR perthousand +ENCODING 8240 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1C30 +3E30 +3660 +3660 +3EC0 +1CC0 +0180 +0180 +0300 +0300 +0600 +0600 +0C00 +0C00 +19DC +1BFE +3376 +3376 +63FE +61DC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR minute +ENCODING 8242 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +01C0 +01C0 +01C0 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR second +ENCODING 8243 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +1C70 +1C70 +1C70 +1860 +1860 +1860 +1860 +1860 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR guilsinglleft +ENCODING 8249 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR guilsinglright +ENCODING 8250 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR exclamdbl +ENCODING 8252 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0C30 +0C30 +0C30 +0C30 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR overline +ENCODING 8254 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2070 +ENCODING 8304 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +03C0 +07E0 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2071 +ENCODING 8305 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2074 +ENCODING 8308 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0030 +0070 +00F0 +01F0 +03B0 +0730 +0E30 +0FF0 +0FF0 +0030 +0030 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2075 +ENCODING 8309 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0FE0 +0FE0 +0C00 +0C00 +0FE0 +0FF0 +0030 +0030 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2076 +ENCODING 8310 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +03E0 +07E0 +0C00 +0C00 +0FE0 +0FF0 +0C30 +0C30 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2077 +ENCODING 8311 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0FF0 +0FF0 +0C30 +0030 +0060 +0060 +00C0 +00C0 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2078 +ENCODING 8312 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0FF0 +07E0 +0C30 +0C30 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2079 +ENCODING 8313 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0C30 +0FF0 +07F0 +0030 +0030 +07E0 +07C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni207A +ENCODING 8314 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0FF0 +0FF0 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni207B +ENCODING 8315 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni207C +ENCODING 8316 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF0 +0000 +0000 +0000 +1FF0 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni207D +ENCODING 8317 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +00C0 +0180 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0180 +00C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni207E +ENCODING 8318 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0300 +0180 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +0180 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR nsuperior +ENCODING 8319 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +1FC0 +1FE0 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2080 +ENCODING 8320 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +07E0 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +0C30 +07E0 +03C0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2081 +ENCODING 8321 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0380 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2082 +ENCODING 8322 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0070 +00E0 +01C0 +0380 +0700 +0FF0 +0FF0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2083 +ENCODING 8323 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0030 +01E0 +01E0 +0030 +0030 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2084 +ENCODING 8324 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0030 +0070 +00F0 +01F0 +03B0 +0730 +0E30 +0FF0 +0FF0 +0030 +0030 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2085 +ENCODING 8325 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FE0 +0FE0 +0C00 +0C00 +0FE0 +0FF0 +0030 +0030 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2086 +ENCODING 8326 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03E0 +07E0 +0C00 +0C00 +0FE0 +0FF0 +0C30 +0C30 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2087 +ENCODING 8327 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +0FF0 +0C30 +0030 +0060 +0060 +00C0 +00C0 +0180 +0180 +0180 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2088 +ENCODING 8328 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0FF0 +07E0 +0C30 +0C30 +0C30 +0FF0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2089 +ENCODING 8329 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +0C30 +0C30 +0C30 +0FF0 +07F0 +0030 +0030 +07E0 +07C0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni208A +ENCODING 8330 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0FF0 +0FF0 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni208B +ENCODING 8331 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni208C +ENCODING 8332 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF0 +0000 +0000 +0000 +1FF0 +1FF0 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni208D +ENCODING 8333 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +00C0 +0180 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +0180 +00C0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni208E +ENCODING 8334 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0180 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +0180 +0300 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2090 +ENCODING 8336 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FC0 +0FE0 +0030 +0030 +0FF0 +1FF0 +1830 +1830 +1FF0 +0FF0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2091 +ENCODING 8337 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07C0 +0FE0 +1830 +1830 +1FF0 +1FF0 +1800 +1800 +0FF0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2092 +ENCODING 8338 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07C0 +0FE0 +1830 +1830 +1830 +1830 +1830 +1830 +0FE0 +07C0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2093 +ENCODING 8339 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1830 +1C70 +0EE0 +07C0 +0380 +0380 +07C0 +0EE0 +1C70 +1830 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2094 +ENCODING 8340 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0FC0 +1FE0 +0030 +0030 +1FF0 +1FF0 +1830 +1830 +0FE0 +07C0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2095 +ENCODING 8341 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +1FC0 +1FE0 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +1830 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2096 +ENCODING 8342 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0C00 +0C00 +0C00 +0C00 +0C38 +0C70 +0CE0 +0DC0 +0F80 +0F80 +0DC0 +0CE0 +0C70 +0C38 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2097 +ENCODING 8343 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0780 +0780 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2098 +ENCODING 8344 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +318C +318C +318C +318C +318C +318C +318C +318C +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni209A +ENCODING 8346 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FC0 +1FE0 +1830 +1830 +1830 +1830 +1830 +1830 +1FE0 +1FC0 +1800 +1800 +1800 +ENDCHAR +STARTCHAR peseta +ENCODING 8359 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +7F00 +7F80 +61C0 +60C0 +60C0 +60C0 +60C0 +60C0 +61C0 +7FB0 +7F30 +6030 +61FE +61FE +6030 +6030 +6030 +6030 +603E +601E +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii57636 +ENCODING 8362 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +7F86 +7FC6 +60E6 +6066 +6066 +6066 +6666 +6666 +6666 +6666 +6666 +6666 +6666 +6666 +6606 +6606 +6606 +660E +67FC +67F8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Euro +ENCODING 8364 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +03F0 +07F8 +0E1C +1C0E +3800 +3000 +3000 +FFC0 +FFC0 +3000 +3000 +FFC0 +FFC0 +3000 +3000 +3800 +1C0E +0E1C +07F8 +03F0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni20AE +ENCODING 8366 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +01B8 +01F0 +03C0 +0FB8 +1DF0 +03C0 +0F80 +1D80 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2102 +ENCODING 8450 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +3E1C +360C +360C +3600 +3600 +3600 +3600 +3600 +3600 +3600 +3600 +3600 +3600 +360C +360C +3E1C +1FF8 +0FF0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni210E +ENCODING 8462 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni210F +ENCODING 8463 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +FF80 +FF80 +3000 +3000 +3FF0 +3FF8 +301C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2115 +ENCODING 8469 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +380C +3C0C +360C +3B0C +3D8C +36CC +336C +31BC +30DC +306C +303C +301C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR afii61352 +ENCODING 8470 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +C180 +C19C +C1BE +E1B6 +E1B6 +F1BE +F19C +F980 +D980 +DD80 +CD80 +CF80 +C780 +C7BE +C3BE +C380 +C1BE +C1BE +C180 +C180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni211A +ENCODING 8474 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +3E1C +360C +360C +360C +360C +360C +360C +360C +360C +360C +360C +360C +360C +360C +36CC +3EFC +1FF8 +0FF8 +001C +000E +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni211D +ENCODING 8477 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FF0 +3FF8 +361C +360C +360C +360C +360C +360C +360C +361C +37F8 +37F0 +36C0 +3760 +37B0 +36D8 +366C +3636 +361A +3E0E +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR trademark +ENCODING 8482 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +7E82 +7EC6 +18FE +18D6 +18C6 +18C6 +18C6 +18C6 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2124 +ENCODING 8484 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +001C +003C +006C +00D8 +01B0 +0360 +06C0 +0D80 +1B00 +3600 +3C00 +3800 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR Ohm +ENCODING 8486 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +381C +1C38 +0E70 +0660 +0660 +3E7C +3E7C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR aleph +ENCODING 8501 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3030 +3030 +1818 +1818 +0C0C +0C0C +0606 +0E0E +1F1C +3B38 +71F0 +61E0 +60C0 +60C0 +6060 +6060 +6030 +7030 +3818 +1C18 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowleft +ENCODING 8592 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0300 +0700 +0E00 +1C00 +3800 +7FFE +7FFE +3800 +1C00 +0E00 +0700 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowup +ENCODING 8593 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1DB8 +399C +318C +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowright +ENCODING 8594 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +00C0 +00E0 +0070 +0038 +001C +7FFE +7FFE +001C +0038 +0070 +00E0 +00C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowdown +ENCODING 8595 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +318C +399C +1DB8 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowboth +ENCODING 8596 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0660 +0E70 +1C38 +381C +700E +FFFF +FFFF +700E +381C +1C38 +0E70 +0660 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowupdn +ENCODING 8597 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1DB8 +399C +318C +0180 +0180 +0180 +0180 +0180 +0180 +318C +399C +1DB8 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21A4 +ENCODING 8612 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0306 +0706 +0E06 +1C06 +3806 +7FFE +7FFE +3806 +1C06 +0E06 +0706 +0306 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21A6 +ENCODING 8614 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +60C0 +60E0 +6070 +6038 +601C +7FFE +7FFE +601C +6038 +6070 +60E0 +60C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowupdnbse +ENCODING 8616 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1DB8 +399C +318C +0180 +0180 +0180 +0180 +318C +399C +1DB8 +0FF0 +07E0 +03C0 +0180 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21B2 +ENCODING 8626 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +000C +000C +000C +000C +000C +000C +000C +000C +030C +070C +0E0C +1C0C +380C +7FFC +7FFC +3800 +1C00 +0E00 +0700 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21B3 +ENCODING 8627 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6180 +61C0 +60E0 +6070 +6038 +7FFC +7FFC +0038 +0070 +00E0 +01C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR carriagereturn +ENCODING 8629 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +000C +000C +000C +000C +000C +030C +070C +0E0C +1C0C +380C +7FFC +7FFC +3800 +1C00 +0E00 +0700 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21BB +ENCODING 8635 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7F00 +7F00 +1F00 +3B18 +3318 +630C +600C +600C +600C +600C +3018 +3838 +1FF0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21CB +ENCODING 8651 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0800 +1800 +3800 +7000 +FFFE +FFFE +0000 +0000 +FFFE +FFFE +001C +0038 +0030 +0020 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21CC +ENCODING 8652 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0020 +0030 +0038 +001C +FFFE +FFFE +0000 +0000 +FFFE +FFFE +7000 +3800 +1800 +0800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowdblleft +ENCODING 8656 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0600 +0E00 +1C00 +3FFE +7FFE +F000 +F000 +7FFE +3FFE +1C00 +0E00 +0600 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowdblup +ENCODING 8657 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1E78 +3E7C +366C +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowdblright +ENCODING 8658 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +00C0 +00E0 +0070 +FFF8 +FFFC +001E +001E +FFFC +FFF8 +0070 +00E0 +00C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowdbldown +ENCODING 8659 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +366C +3E7C +1E78 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR arrowdblboth +ENCODING 8660 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0660 +0E70 +1C38 +3FFC +7FFE +F00F +F00F +7FFE +3FFC +1C38 +0E70 +0660 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni21D5 +ENCODING 8661 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1E78 +3E7C +366C +0660 +0660 +0660 +0660 +0660 +0660 +366C +3E7C +1E78 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR universal +ENCODING 8704 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +600C +600C +600C +600C +3018 +3018 +3FF8 +3FF8 +1830 +1830 +1830 +0C60 +0C60 +0C60 +06C0 +06C0 +06C0 +0380 +0380 +0380 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR existential +ENCODING 8707 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +000C +000C +000C +000C +000C +000C +000C +3FFC +3FFC +000C +000C +000C +000C +000C +000C +000C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2204 +ENCODING 8708 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +000C +000C +001C +3FFC +3FFC +003C +006C +006C +00CC +00CC +018C +018C +3FFC +3FFC +030C +030C +060C +060C +0C0C +0C0C +180C +3FFC +3FFC +3000 +6000 +6000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR emptyset +ENCODING 8709 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0030 +0030 +0FE0 +1FF0 +30D8 +30D8 +3198 +3198 +3318 +3318 +3618 +3618 +1FF0 +0FE0 +1800 +1800 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR increment +ENCODING 8710 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +03C0 +03C0 +03C0 +0660 +0660 +0660 +0C30 +0C30 +0C30 +1818 +1818 +1818 +300C +300C +300C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR gradient +ENCODING 8711 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0C30 +0660 +0660 +0660 +03C0 +03C0 +03C0 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR element +ENCODING 8712 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +01FC +07FC +0E00 +1800 +1800 +3000 +3000 +3000 +3000 +3FFC +3FFC +3000 +3000 +3000 +3000 +1800 +1800 +0E00 +07FC +01FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR notelement +ENCODING 8713 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0006 +0006 +000C +01FC +07FC +0E18 +1830 +1830 +3060 +3060 +30C0 +30C0 +3FFC +3FFC +3180 +3180 +3300 +3300 +1600 +1E00 +0E00 +0FFC +19FC +1800 +3000 +3000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni220A +ENCODING 8714 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03FC +0FFC +1C00 +1800 +3000 +3000 +3FFC +3FFC +3000 +3000 +1800 +1C00 +0FFC +03FC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR suchthat +ENCODING 8715 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3F80 +3FE0 +0070 +0018 +0018 +000C +000C +000C +000C +3FFC +3FFC +000C +000C +000C +000C +0018 +0018 +0070 +3FE0 +3F80 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni220C +ENCODING 8716 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +6000 +6000 +3000 +3F80 +3FE0 +1870 +0C18 +0C18 +060C +060C +030C +030C +3FFC +3FFC +018C +018C +00CC +00CC +0068 +0078 +0070 +3FF0 +3F98 +0018 +000C +000C +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni220D +ENCODING 8717 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FC0 +3FF0 +0038 +0018 +000C +000C +3FFC +3FFC +000C +000C +0018 +0038 +3FF0 +3FC0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR minus +ENCODING 8722 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2213 +ENCODING 8723 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0180 +0180 +0180 +0180 +0180 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2214 +ENCODING 8724 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +3FFC +3FFC +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2215 +ENCODING 8725 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +000C +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +6000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2216 +ENCODING 8726 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +6000 +7000 +3800 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +001C +000C +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR bulletoperator +ENCODING 8729 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0380 +07C0 +07C0 +07C0 +0380 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR radical +ENCODING 8730 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +001E +001E +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +3018 +3018 +3018 +3818 +1C18 +0E18 +0718 +0398 +01D8 +00F8 +0078 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR infinity +ENCODING 8734 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1E78 +3FFC +73CE +6186 +6186 +6186 +6186 +73CE +3FFC +1E78 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR orthogonal +ENCODING 8735 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2225 +ENCODING 8741 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR logicaland +ENCODING 8743 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +0C30 +1818 +1818 +1818 +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR logicalor +ENCODING 8744 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +1818 +1818 +1818 +0C30 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR intersection +ENCODING 8745 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +1C38 +1818 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR union +ENCODING 8746 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +1818 +1C38 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR approxequal +ENCODING 8776 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1F0C +3F9C +39FC +30F8 +0000 +0000 +1F0C +3F9C +39FC +30F8 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR notequal +ENCODING 8800 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +001C +0038 +7FFC +7FFC +00E0 +01C0 +0380 +0700 +7FFC +7FFC +3800 +7000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR equivalence +ENCODING 8801 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR lessequal +ENCODING 8804 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR greaterequal +ENCODING 8805 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +0000 +0000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni226A +ENCODING 8810 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +00C3 +01C7 +038E +071C +0E38 +1C70 +38E0 +71C0 +E380 +E380 +71C0 +38E0 +1C70 +0E38 +071C +038E +01C7 +00C3 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni226B +ENCODING 8811 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +C300 +E380 +71C0 +38E0 +1C70 +0E38 +071C +038E +01C7 +01C7 +038E +071C +0E38 +1C70 +38E0 +71C0 +E380 +C300 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR propersubset +ENCODING 8834 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07FC +1FFC +3800 +7000 +6000 +6000 +6000 +6000 +6000 +6000 +7000 +3800 +1FFC +07FC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR propersuperset +ENCODING 8835 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7FC0 +7FF0 +0038 +001C +000C +000C +000C +000C +000C +000C +001C +0038 +7FF0 +7FC0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR reflexsubset +ENCODING 8838 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07FC +1FFC +3800 +7000 +6000 +6000 +6000 +6000 +6000 +6000 +7000 +3800 +1FFC +07FC +0000 +0000 +7FFC +7FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR reflexsuperset +ENCODING 8839 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7FC0 +7FF0 +0038 +001C +000C +000C +000C +000C +000C +000C +001C +0038 +7FF0 +7FC0 +0000 +0000 +7FFC +7FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR perpendicular +ENCODING 8869 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni22C2 +ENCODING 8898 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +07E0 +0FF0 +1C38 +1818 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni22C3 +ENCODING 8899 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +1818 +1C38 +0FF0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2300 +ENCODING 8960 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0030 +0030 +0FE0 +1FF0 +30D8 +30D8 +3198 +3198 +3318 +3318 +3618 +3618 +1FF0 +0FE0 +1800 +1800 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR house +ENCODING 8962 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +1C38 +381C +700E +6006 +6006 +6006 +6006 +6006 +6006 +6006 +7FFE +7FFE +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2308 +ENCODING 8968 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FE0 +0FE0 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2309 +ENCODING 8969 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FE0 +0FE0 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni230A +ENCODING 8970 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0C00 +0FE0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni230B +ENCODING 8971 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0FE0 +0FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR revlogicalnot +ENCODING 8976 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3000 +3000 +3000 +3000 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2319 +ENCODING 8985 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3000 +3000 +3000 +3000 +3000 +3000 +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR integraltp +ENCODING 8992 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +00F8 +01FC +018C +018C +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR integralbt +ENCODING 8993 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +3180 +3180 +3F80 +1F00 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni239B +ENCODING 9115 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0018 +0030 +0060 +00C0 +0180 +0180 +0300 +0300 +0600 +0600 +0600 +0C00 +0C00 +0C00 +0C00 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +ENDCHAR +STARTCHAR uni239C +ENCODING 9116 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +ENDCHAR +STARTCHAR uni239D +ENCODING 9117 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +0C00 +0C00 +0C00 +0C00 +0600 +0600 +0600 +0300 +0300 +0180 +0180 +00C0 +0060 +0030 +0018 +ENDCHAR +STARTCHAR uni239E +ENCODING 9118 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1800 +0C00 +0600 +0300 +0180 +0180 +00C0 +00C0 +0060 +0060 +0060 +0030 +0030 +0030 +0030 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +ENDCHAR +STARTCHAR uni239F +ENCODING 9119 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +ENDCHAR +STARTCHAR uni23A0 +ENCODING 9120 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0030 +0030 +0030 +0030 +0060 +0060 +0060 +00C0 +00C0 +0180 +0180 +0300 +0600 +0C00 +1800 +ENDCHAR +STARTCHAR uni23A1 +ENCODING 9121 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1FF8 +1FF8 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +ENDCHAR +STARTCHAR uni23A2 +ENCODING 9122 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +ENDCHAR +STARTCHAR uni23A3 +ENCODING 9123 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1FF8 +1FF8 +ENDCHAR +STARTCHAR uni23A4 +ENCODING 9124 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +1FF8 +1FF8 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +ENDCHAR +STARTCHAR uni23A5 +ENCODING 9125 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +ENDCHAR +STARTCHAR uni23A6 +ENCODING 9126 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1FF8 +1FF8 +ENDCHAR +STARTCHAR uni23A7 +ENCODING 9127 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +007E +01FE +0380 +0300 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +ENDCHAR +STARTCHAR uni23A8 +ENCODING 9128 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0E00 +1C00 +F800 +F800 +1C00 +0E00 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +ENDCHAR +STARTCHAR uni23A9 +ENCODING 9129 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0600 +0300 +0380 +01FE +007E +ENDCHAR +STARTCHAR uni23AB +ENCODING 9131 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FC00 +FF00 +0380 +0180 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +ENDCHAR +STARTCHAR uni23AC +ENCODING 9132 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +0060 +003E +003E +0060 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +ENDCHAR +STARTCHAR uni23AD +ENCODING 9133 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +00C0 +0180 +0380 +FF00 +FC00 +ENDCHAR +STARTCHAR uni23AE +ENCODING 9134 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni23AF +ENCODING 9135 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni23BA +ENCODING 9146 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni23BB +ENCODING 9147 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni23BC +ENCODING 9148 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni23BD +ENCODING 9149 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +ENDCHAR +STARTCHAR uni23D0 +ENCODING 9168 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2409 +ENCODING 9225 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +6180 +6180 +6180 +7F80 +7F80 +6180 +6180 +6180 +6180 +0000 +0000 +03FC +03FC +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni240A +ENCODING 9226 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +7F80 +7F80 +0000 +0000 +03FC +03FC +0300 +0300 +03F0 +03F0 +0300 +0300 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni240B +ENCODING 9227 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +6180 +6180 +6180 +6180 +6180 +6180 +3300 +1E00 +0C00 +0000 +0000 +03FC +03FC +0060 +0060 +0060 +0060 +0060 +0060 +0060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni240C +ENCODING 9228 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +7F80 +7F80 +6000 +6000 +7E00 +7E00 +6000 +6000 +6000 +0000 +0000 +03FC +03FC +0300 +0300 +03F0 +03F0 +0300 +0300 +0300 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni240D +ENCODING 9229 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3F00 +7F80 +6180 +6000 +6000 +6000 +6180 +7F80 +3F00 +0000 +0000 +03F8 +03FC +030C +030C +03F8 +03E0 +0370 +0338 +031C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2424 +ENCODING 9252 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +6180 +6180 +7180 +7980 +6D80 +6780 +6380 +6180 +6180 +0000 +0000 +0300 +0300 +0300 +0300 +0300 +0300 +0300 +03FC +03FC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF100000 +ENCODING 9472 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2501 +ENCODING 9473 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF110000 +ENCODING 9474 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2503 +ENCODING 9475 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2508 +ENCODING 9480 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +F7DE +F7DE +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2509 +ENCODING 9481 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +F7DE +F7DE +F7DE +F7DE +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni250A +ENCODING 9482 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +ENDCHAR +STARTCHAR uni250B +ENCODING 9483 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +0000 +0000 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +0000 +0000 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +0000 +0000 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +0000 +0000 +ENDCHAR +STARTCHAR SF010000 +ENCODING 9484 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni250D +ENCODING 9485 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +01FF +01FF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni250E +ENCODING 9486 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03FF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni250F +ENCODING 9487 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03FF +03FF +03FF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR SF030000 +ENCODING 9488 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2511 +ENCODING 9489 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FF80 +FF80 +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2512 +ENCODING 9490 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFC0 +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2513 +ENCODING 9491 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFC0 +FFC0 +FFC0 +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR SF020000 +ENCODING 9492 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2515 +ENCODING 9493 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +01FF +01FF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2516 +ENCODING 9494 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +03FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2517 +ENCODING 9495 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +03FF +03FF +03FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF040000 +ENCODING 9496 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2519 +ENCODING 9497 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FF80 +FF80 +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni251A +ENCODING 9498 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFC0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni251B +ENCODING 9499 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFC0 +FFC0 +FFC0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF080000 +ENCODING 9500 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni251D +ENCODING 9501 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +01FF +01FF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni251E +ENCODING 9502 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +03FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni251F +ENCODING 9503 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +03FF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2520 +ENCODING 9504 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2521 +ENCODING 9505 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +03FF +03FF +03FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2522 +ENCODING 9506 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +03FF +03FF +03FF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2523 +ENCODING 9507 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +03FF +03FF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR SF090000 +ENCODING 9508 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2525 +ENCODING 9509 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FF80 +FF80 +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2526 +ENCODING 9510 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFC0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2527 +ENCODING 9511 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFC0 +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2528 +ENCODING 9512 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2529 +ENCODING 9513 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFC0 +FFC0 +FFC0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni252A +ENCODING 9514 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFC0 +FFC0 +FFC0 +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni252B +ENCODING 9515 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFC0 +FFC0 +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR SF060000 +ENCODING 9516 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni252D +ENCODING 9517 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FFFF +FFFF +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni252E +ENCODING 9518 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +FFFF +FFFF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni252F +ENCODING 9519 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2530 +ENCODING 9520 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2531 +ENCODING 9521 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFC0 +FFFF +FFFF +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2532 +ENCODING 9522 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03FF +FFFF +FFFF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2533 +ENCODING 9523 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR SF070000 +ENCODING 9524 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2535 +ENCODING 9525 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FFFF +FFFF +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2536 +ENCODING 9526 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +FFFF +FFFF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2537 +ENCODING 9527 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2538 +ENCODING 9528 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2539 +ENCODING 9529 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFFF +FFFF +FFC0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni253A +ENCODING 9530 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +FFFF +FFFF +03FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni253B +ENCODING 9531 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFFF +FFFF +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF050000 +ENCODING 9532 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni253D +ENCODING 9533 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FFFF +FFFF +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni253E +ENCODING 9534 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +FFFF +FFFF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni253F +ENCODING 9535 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2540 +ENCODING 9536 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2541 +ENCODING 9537 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2542 +ENCODING 9538 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFFF +FFFF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2543 +ENCODING 9539 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFFF +FFFF +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2544 +ENCODING 9540 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +FFFF +FFFF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2545 +ENCODING 9541 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FFFF +FFFF +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2546 +ENCODING 9542 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +FFFF +FFFF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2547 +ENCODING 9543 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFFF +FFFF +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2548 +ENCODING 9544 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +FFFF +FFFF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni2549 +ENCODING 9545 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFC0 +FFFF +FFFF +FFC0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni254A +ENCODING 9546 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03FF +FFFF +FFFF +03FF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni254B +ENCODING 9547 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +FFFF +FFFF +FFFF +FFFF +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR SF430000 +ENCODING 9552 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF240000 +ENCODING 9553 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF510000 +ENCODING 9554 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +01FF +0180 +0180 +01FF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR SF520000 +ENCODING 9555 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07FF +07FF +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF390000 +ENCODING 9556 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +07FF +07FF +0600 +0600 +067F +067F +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF220000 +ENCODING 9557 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FF80 +0180 +0180 +FF80 +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR SF210000 +ENCODING 9558 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFE0 +FFE0 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF250000 +ENCODING 9559 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFE0 +FFE0 +0060 +0060 +FE60 +FE60 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF500000 +ENCODING 9560 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +01FF +0180 +0180 +01FF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF490000 +ENCODING 9561 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +07FF +07FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF380000 +ENCODING 9562 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +067F +067F +0600 +0600 +07FF +07FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF280000 +ENCODING 9563 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FF80 +0180 +0180 +FF80 +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF270000 +ENCODING 9564 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FFE0 +FFE0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF260000 +ENCODING 9565 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FE60 +FE60 +0060 +0060 +FFE0 +FFE0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF360000 +ENCODING 9566 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01FF +01FF +0180 +0180 +01FF +01FF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR SF370000 +ENCODING 9567 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +067F +067F +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF420000 +ENCODING 9568 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +067F +067F +0600 +0600 +067F +067F +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF190000 +ENCODING 9569 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FF80 +FF80 +0180 +0180 +FF80 +FF80 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR SF200000 +ENCODING 9570 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FE60 +FE60 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF230000 +ENCODING 9571 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FE60 +FE60 +0060 +0060 +FE60 +FE60 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF470000 +ENCODING 9572 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR SF480000 +ENCODING 9573 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF410000 +ENCODING 9574 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +0000 +0000 +FE7F +FE7F +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF450000 +ENCODING 9575 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF460000 +ENCODING 9576 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF400000 +ENCODING 9577 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FE7F +FE7F +0000 +0000 +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR SF540000 +ENCODING 9578 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +FFFF +FFFF +0180 +0180 +FFFF +FFFF +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR SF530000 +ENCODING 9579 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FFFF +FFFF +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR SF440000 +ENCODING 9580 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +FE7F +FE7F +0000 +0000 +FE7F +FE7F +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +0660 +ENDCHAR +STARTCHAR uni256D +ENCODING 9581 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +000F +003F +0078 +00E0 +00C0 +01C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni256E +ENCODING 9582 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +F000 +FC00 +1E00 +0700 +0300 +0380 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni256F +ENCODING 9583 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0380 +0300 +0700 +1E00 +FC00 +F000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2570 +ENCODING 9584 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +01C0 +00C0 +00E0 +0078 +003F +000F +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2571 +ENCODING 9585 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0001 +0003 +0003 +0006 +0006 +000C +000C +0018 +0018 +0030 +0030 +0060 +0060 +00C0 +00C0 +0180 +0180 +0300 +0300 +0600 +0600 +0C00 +0C00 +1800 +1800 +3000 +3000 +6000 +6000 +C000 +C000 +8000 +ENDCHAR +STARTCHAR uni2572 +ENCODING 9586 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +8000 +C000 +C000 +6000 +6000 +3000 +3000 +1800 +1800 +0C00 +0C00 +0600 +0600 +0300 +0300 +0180 +0180 +00C0 +00C0 +0060 +0060 +0030 +0030 +0018 +0018 +000C +000C +0006 +0006 +0003 +0003 +0001 +ENDCHAR +STARTCHAR uni2573 +ENCODING 9587 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +8001 +C003 +C003 +6006 +6006 +300C +300C +1818 +1818 +0C30 +0C30 +0660 +0660 +03C0 +03C0 +0180 +0180 +03C0 +03C0 +0660 +0660 +0C30 +0C30 +1818 +1818 +300C +300C +6006 +6006 +C003 +C003 +8001 +ENDCHAR +STARTCHAR uni2574 +ENCODING 9588 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2575 +ENCODING 9589 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2576 +ENCODING 9590 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2577 +ENCODING 9591 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR uni2578 +ENCODING 9592 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FF80 +FF80 +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2579 +ENCODING 9593 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni257A +ENCODING 9594 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +01FF +01FF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni257B +ENCODING 9595 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni257C +ENCODING 9596 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +01FF +FFFF +FFFF +01FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni257D +ENCODING 9597 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +ENDCHAR +STARTCHAR uni257E +ENCODING 9598 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF80 +FFFF +FFFF +FF80 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni257F +ENCODING 9599 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +03C0 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +0180 +ENDCHAR +STARTCHAR upblock +ENCODING 9600 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2581 +ENCODING 9601 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni2582 +ENCODING 9602 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni2583 +ENCODING 9603 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR dnblock +ENCODING 9604 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni2585 +ENCODING 9605 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni2586 +ENCODING 9606 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni2587 +ENCODING 9607 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR block +ENCODING 9608 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni2589 +ENCODING 9609 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +FFFC +ENDCHAR +STARTCHAR uni258A +ENCODING 9610 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +FFF0 +ENDCHAR +STARTCHAR uni258B +ENCODING 9611 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +FFC0 +ENDCHAR +STARTCHAR lfblock +ENCODING 9612 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +ENDCHAR +STARTCHAR uni258D +ENCODING 9613 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +FC00 +ENDCHAR +STARTCHAR uni258E +ENCODING 9614 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +F000 +ENDCHAR +STARTCHAR uni258F +ENCODING 9615 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +C000 +ENDCHAR +STARTCHAR rtblock +ENCODING 9616 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +ENDCHAR +STARTCHAR ltshade +ENCODING 9617 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +AAAA +0000 +ENDCHAR +STARTCHAR shade +ENCODING 9618 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +AAAA +5555 +ENDCHAR +STARTCHAR dkshade +ENCODING 9619 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +FFFF +AAAA +ENDCHAR +STARTCHAR uni2596 +ENCODING 9622 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +ENDCHAR +STARTCHAR uni2597 +ENCODING 9623 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +ENDCHAR +STARTCHAR uni2598 +ENCODING 9624 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2599 +ENCODING 9625 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR uni259A +ENCODING 9626 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +ENDCHAR +STARTCHAR uni259B +ENCODING 9627 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +ENDCHAR +STARTCHAR uni259C +ENCODING 9628 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +ENDCHAR +STARTCHAR uni259D +ENCODING 9629 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni259E +ENCODING 9630 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +FF00 +ENDCHAR +STARTCHAR uni259F +ENCODING 9631 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +00FF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR filledbox +ENCODING 9632 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +1FF0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR filledrect +ENCODING 9644 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +7FFC +7FFC +7FFC +7FFC +7FFC +7FFC +7FFC +7FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni25AE +ENCODING 9646 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR triagup +ENCODING 9650 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0100 +0100 +0380 +0380 +07C0 +07C0 +0FE0 +0FE0 +1FF0 +1FF0 +3FF8 +3FF8 +7FFC +7FFC +FFFE +FFFE +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni25B6 +ENCODING 9654 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +C000 +F000 +FC00 +FF00 +FFC0 +FFF0 +FFFC +FFFF +FFFC +FFF0 +FFC0 +FF00 +FC00 +F000 +C000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR triagrt +ENCODING 9658 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +C000 +F000 +FC00 +FF00 +FFC0 +FFF0 +FFFC +FFFF +FFFC +FFF0 +FFC0 +FF00 +FC00 +F000 +C000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR triagdn +ENCODING 9660 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +FFFE +FFFE +7FFC +7FFC +3FF8 +3FF8 +1FF0 +1FF0 +0FE0 +0FE0 +07C0 +07C0 +0380 +0380 +0100 +0100 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni25C0 +ENCODING 9664 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0003 +000F +003F +00FF +03FF +0FFF +3FFF +FFFF +3FFF +0FFF +03FF +00FF +003F +000F +0003 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR triaglf +ENCODING 9668 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0003 +000F +003F +00FF +03FF +0FFF +3FFF +FFFF +3FFF +0FFF +03FF +00FF +003F +000F +0003 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR blackdiamond +ENCODING 9670 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1FF8 +3FFC +7FFE +7FFE +3FFC +1FF8 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR lozenge +ENCODING 9674 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0E70 +1C38 +381C +700E +700E +381C +1C38 +0E70 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR circle +ENCODING 9675 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +07E0 +0660 +0660 +07E0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR H18533 +ENCODING 9679 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +07E0 +07E0 +07E0 +07E0 +03C0 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR invbullet +ENCODING 9688 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FC3F +F81F +F81F +F81F +F81F +FC3F +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR invcircle +ENCODING 9689 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FC3F +F81F +F99F +F99F +F81F +FC3F +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +FFFF +ENDCHAR +STARTCHAR smileface +ENCODING 9786 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +3FFC +700E +6006 +6006 +6006 +6E76 +6E76 +6006 +6006 +6006 +6006 +6FF6 +67E6 +6006 +6006 +6006 +700E +3FFC +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR invsmileface +ENCODING 9787 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FF8 +3FFC +7FFE +7FFE +7FFE +7FFE +718E +718E +7FFE +7FFE +7FFE +7FFE +700E +781E +7C3E +7FFE +7FFE +7FFE +3FFC +1FF8 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR sun +ENCODING 9788 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +0180 +318C +399C +1DB8 +0FF0 +07E0 +3E7C +3E7C +07E0 +0FF0 +1DB8 +399C +318C +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR female +ENCODING 9792 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0FF0 +1FF8 +381C +300C +300C +300C +300C +300C +381C +1FF8 +0FF0 +0180 +0180 +0180 +3FFC +3FFC +0180 +0180 +0180 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR male +ENCODING 9794 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +01FE +01FE +001E +003E +0076 +00E6 +01C6 +0386 +1FE0 +3FF0 +7038 +6018 +6018 +6018 +6018 +6018 +6018 +7038 +3FF0 +1FE0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR spade +ENCODING 9824 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0180 +0180 +03C0 +07E0 +0FF0 +1FF8 +3FFC +3FFC +7FFE +7FFE +7FFE +7FFE +7FFE +3DBC +1DB8 +0180 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR club +ENCODING 9827 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +03C0 +07E0 +07E0 +07E0 +07E0 +07E0 +03C0 +0180 +1DB8 +3FFC +7FFE +7FFE +7FFE +7FFE +3FFC +1DB8 +0180 +0180 +07E0 +07E0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR heart +ENCODING 9829 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +3C3C +7E7E +7E7E +7FFE +7FFE +7FFE +7FFE +7FFE +3FFC +3FFC +1FF8 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR diamond +ENCODING 9830 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0180 +03C0 +07E0 +0FF0 +1FF8 +3FFC +7FFE +7FFE +3FFC +1FF8 +0FF0 +07E0 +03C0 +0180 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR musicalnote +ENCODING 9834 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +1FFC +1FFC +180C +180C +180C +180C +1FFC +1FFC +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +1800 +7800 +7000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR musicalnotedbl +ENCODING 9835 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFE +3FFE +3006 +3006 +3006 +3006 +3FFE +3FFE +3006 +3006 +3006 +3006 +3006 +3006 +3006 +3006 +3006 +301E +F01C +E000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2713 +ENCODING 10003 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0003 +0003 +0006 +0006 +000C +000C +0018 +0018 +C030 +C030 +6060 +6060 +30C0 +30C0 +1980 +1980 +0F00 +0F00 +0600 +0600 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2714 +ENCODING 10004 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0007 +0007 +000E +000E +001C +001C +0038 +0038 +C070 +C070 +E0E0 +E0E0 +71C0 +71C0 +3B80 +3B80 +1F00 +1F00 +0E00 +0E00 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2717 +ENCODING 10007 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0030 +3030 +1860 +0C60 +06C0 +03C0 +0180 +01C0 +0360 +0330 +0618 +060C +0C00 +0C00 +1800 +1800 +3000 +3000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2718 +ENCODING 10008 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0038 +0038 +7070 +3870 +1CE0 +0EE0 +07C0 +03C0 +03C0 +03E0 +0770 +0738 +0E1C +0E0C +1C00 +1C00 +3800 +3800 +7000 +7000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni27E8 +ENCODING 10216 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0060 +0060 +00C0 +00C0 +0180 +0180 +0300 +0300 +0600 +0600 +0600 +0600 +0300 +0300 +0180 +0180 +00C0 +00C0 +0060 +0060 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni27E9 +ENCODING 10217 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0600 +0600 +0300 +0300 +0180 +0180 +00C0 +00C0 +0060 +0060 +0060 +0060 +00C0 +00C0 +0180 +0180 +0300 +0300 +0600 +0600 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni27EA +ENCODING 10218 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +030C +030C +0618 +0618 +0C30 +0C30 +1860 +1860 +30C0 +30C0 +30C0 +30C0 +1860 +1860 +0C30 +0C30 +0618 +0618 +030C +030C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni27EB +ENCODING 10219 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +30C0 +30C0 +1860 +1860 +0C30 +0C30 +0618 +0618 +030C +030C +030C +030C +0618 +0618 +0C30 +0C30 +1860 +1860 +30C0 +30C0 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2800 +ENCODING 10240 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2801 +ENCODING 10241 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2802 +ENCODING 10242 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2803 +ENCODING 10243 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2804 +ENCODING 10244 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2805 +ENCODING 10245 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2806 +ENCODING 10246 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2807 +ENCODING 10247 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2808 +ENCODING 10248 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2809 +ENCODING 10249 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni280A +ENCODING 10250 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni280B +ENCODING 10251 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni280C +ENCODING 10252 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni280D +ENCODING 10253 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni280E +ENCODING 10254 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni280F +ENCODING 10255 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2810 +ENCODING 10256 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2811 +ENCODING 10257 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2812 +ENCODING 10258 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2813 +ENCODING 10259 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2814 +ENCODING 10260 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2815 +ENCODING 10261 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2816 +ENCODING 10262 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2817 +ENCODING 10263 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2818 +ENCODING 10264 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2819 +ENCODING 10265 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni281A +ENCODING 10266 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni281B +ENCODING 10267 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni281C +ENCODING 10268 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni281D +ENCODING 10269 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni281E +ENCODING 10270 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni281F +ENCODING 10271 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2820 +ENCODING 10272 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2821 +ENCODING 10273 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2822 +ENCODING 10274 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2823 +ENCODING 10275 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2824 +ENCODING 10276 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2825 +ENCODING 10277 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2826 +ENCODING 10278 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2827 +ENCODING 10279 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2828 +ENCODING 10280 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2829 +ENCODING 10281 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni282A +ENCODING 10282 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni282B +ENCODING 10283 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni282C +ENCODING 10284 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni282D +ENCODING 10285 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni282E +ENCODING 10286 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni282F +ENCODING 10287 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2830 +ENCODING 10288 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2831 +ENCODING 10289 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2832 +ENCODING 10290 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2833 +ENCODING 10291 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2834 +ENCODING 10292 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2835 +ENCODING 10293 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2836 +ENCODING 10294 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2837 +ENCODING 10295 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2838 +ENCODING 10296 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2839 +ENCODING 10297 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni283A +ENCODING 10298 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni283B +ENCODING 10299 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni283C +ENCODING 10300 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni283D +ENCODING 10301 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni283E +ENCODING 10302 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni283F +ENCODING 10303 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uni2840 +ENCODING 10304 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2841 +ENCODING 10305 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2842 +ENCODING 10306 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2843 +ENCODING 10307 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2844 +ENCODING 10308 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2845 +ENCODING 10309 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2846 +ENCODING 10310 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2847 +ENCODING 10311 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2848 +ENCODING 10312 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2849 +ENCODING 10313 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni284A +ENCODING 10314 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni284B +ENCODING 10315 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni284C +ENCODING 10316 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni284D +ENCODING 10317 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni284E +ENCODING 10318 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni284F +ENCODING 10319 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2850 +ENCODING 10320 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2851 +ENCODING 10321 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2852 +ENCODING 10322 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2853 +ENCODING 10323 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2854 +ENCODING 10324 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2855 +ENCODING 10325 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2856 +ENCODING 10326 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2857 +ENCODING 10327 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2858 +ENCODING 10328 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2859 +ENCODING 10329 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni285A +ENCODING 10330 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni285B +ENCODING 10331 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni285C +ENCODING 10332 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni285D +ENCODING 10333 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni285E +ENCODING 10334 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni285F +ENCODING 10335 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2860 +ENCODING 10336 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2861 +ENCODING 10337 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2862 +ENCODING 10338 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2863 +ENCODING 10339 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2864 +ENCODING 10340 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2865 +ENCODING 10341 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2866 +ENCODING 10342 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2867 +ENCODING 10343 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2868 +ENCODING 10344 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2869 +ENCODING 10345 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni286A +ENCODING 10346 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni286B +ENCODING 10347 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni286C +ENCODING 10348 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni286D +ENCODING 10349 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni286E +ENCODING 10350 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni286F +ENCODING 10351 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2870 +ENCODING 10352 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2871 +ENCODING 10353 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2872 +ENCODING 10354 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2873 +ENCODING 10355 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2874 +ENCODING 10356 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2875 +ENCODING 10357 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2876 +ENCODING 10358 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2877 +ENCODING 10359 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2878 +ENCODING 10360 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2879 +ENCODING 10361 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni287A +ENCODING 10362 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni287B +ENCODING 10363 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni287C +ENCODING 10364 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni287D +ENCODING 10365 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni287E +ENCODING 10366 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni287F +ENCODING 10367 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +ENDCHAR +STARTCHAR uni2880 +ENCODING 10368 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2881 +ENCODING 10369 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2882 +ENCODING 10370 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2883 +ENCODING 10371 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2884 +ENCODING 10372 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2885 +ENCODING 10373 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2886 +ENCODING 10374 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2887 +ENCODING 10375 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2888 +ENCODING 10376 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2889 +ENCODING 10377 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni288A +ENCODING 10378 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni288B +ENCODING 10379 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni288C +ENCODING 10380 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni288D +ENCODING 10381 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni288E +ENCODING 10382 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni288F +ENCODING 10383 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2890 +ENCODING 10384 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2891 +ENCODING 10385 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2892 +ENCODING 10386 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2893 +ENCODING 10387 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2894 +ENCODING 10388 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2895 +ENCODING 10389 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2896 +ENCODING 10390 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2897 +ENCODING 10391 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2898 +ENCODING 10392 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni2899 +ENCODING 10393 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni289A +ENCODING 10394 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni289B +ENCODING 10395 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni289C +ENCODING 10396 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni289D +ENCODING 10397 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni289E +ENCODING 10398 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni289F +ENCODING 10399 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A0 +ENCODING 10400 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A1 +ENCODING 10401 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A2 +ENCODING 10402 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A3 +ENCODING 10403 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A4 +ENCODING 10404 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A5 +ENCODING 10405 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A6 +ENCODING 10406 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A7 +ENCODING 10407 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A8 +ENCODING 10408 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28A9 +ENCODING 10409 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28AA +ENCODING 10410 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28AB +ENCODING 10411 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28AC +ENCODING 10412 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28AD +ENCODING 10413 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28AE +ENCODING 10414 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28AF +ENCODING 10415 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B0 +ENCODING 10416 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B1 +ENCODING 10417 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B2 +ENCODING 10418 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B3 +ENCODING 10419 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B4 +ENCODING 10420 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B5 +ENCODING 10421 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B6 +ENCODING 10422 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B7 +ENCODING 10423 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B8 +ENCODING 10424 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28B9 +ENCODING 10425 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28BA +ENCODING 10426 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28BB +ENCODING 10427 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28BC +ENCODING 10428 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28BD +ENCODING 10429 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28BE +ENCODING 10430 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28BF +ENCODING 10431 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +ENDCHAR +STARTCHAR uni28C0 +ENCODING 10432 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C1 +ENCODING 10433 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C2 +ENCODING 10434 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C3 +ENCODING 10435 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C4 +ENCODING 10436 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C5 +ENCODING 10437 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C6 +ENCODING 10438 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C7 +ENCODING 10439 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C8 +ENCODING 10440 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28C9 +ENCODING 10441 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28CA +ENCODING 10442 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28CB +ENCODING 10443 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28CC +ENCODING 10444 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28CD +ENCODING 10445 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28CE +ENCODING 10446 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28CF +ENCODING 10447 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D0 +ENCODING 10448 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D1 +ENCODING 10449 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D2 +ENCODING 10450 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D3 +ENCODING 10451 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D4 +ENCODING 10452 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D5 +ENCODING 10453 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D6 +ENCODING 10454 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D7 +ENCODING 10455 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D8 +ENCODING 10456 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28D9 +ENCODING 10457 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28DA +ENCODING 10458 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28DB +ENCODING 10459 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28DC +ENCODING 10460 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28DD +ENCODING 10461 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28DE +ENCODING 10462 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28DF +ENCODING 10463 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E0 +ENCODING 10464 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E1 +ENCODING 10465 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E2 +ENCODING 10466 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E3 +ENCODING 10467 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E4 +ENCODING 10468 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E5 +ENCODING 10469 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E6 +ENCODING 10470 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E7 +ENCODING 10471 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E8 +ENCODING 10472 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28E9 +ENCODING 10473 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28EA +ENCODING 10474 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28EB +ENCODING 10475 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28EC +ENCODING 10476 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28ED +ENCODING 10477 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28EE +ENCODING 10478 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28EF +ENCODING 10479 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F0 +ENCODING 10480 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F1 +ENCODING 10481 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F2 +ENCODING 10482 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F3 +ENCODING 10483 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F4 +ENCODING 10484 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F5 +ENCODING 10485 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F6 +ENCODING 10486 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F7 +ENCODING 10487 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1800 +1800 +1800 +1800 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F8 +ENCODING 10488 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28F9 +ENCODING 10489 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28FA +ENCODING 10490 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28FB +ENCODING 10491 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28FC +ENCODING 10492 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28FD +ENCODING 10493 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28FE +ENCODING 10494 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0018 +0018 +0018 +0018 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni28FF +ENCODING 10495 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +0000 +0000 +1818 +1818 +1818 +1818 +0000 +0000 +ENDCHAR +STARTCHAR uni2E2C +ENCODING 11820 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +300C +300C +300C +300C +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uniE0A0 +ENCODING 57504 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +6000 +6000 +6000 +6060 +60F0 +61F8 +63FC +676E +6060 +6060 +6060 +6060 +6060 +6060 +6060 +60C0 +4180 +0300 +0600 +0C00 +1800 +3000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +ENDCHAR +STARTCHAR uniE0A1 +ENCODING 57505 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +6000 +7FC0 +7FC0 +0000 +0000 +060C +060C +070C +078C +06CC +066C +063C +061C +060C +060C +060C +0000 +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uniE0A2 +ENCODING 57506 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +07E0 +0FF0 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +1818 +3FFC +7FFE +7FFE +7FFE +7C3E +781E +781E +781E +7C3E +7FFE +7FFE +7FFE +7FFE +7FFE +0000 +0000 +0000 +0000 +ENDCHAR +STARTCHAR uniE0B0 +ENCODING 57520 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +8000 +C000 +E000 +F000 +F800 +FC00 +FE00 +FF00 +FF80 +FFC0 +FFE0 +FFF0 +FFF8 +FFFC +FFFE +FFFF +FFFF +FFFE +FFFC +FFF8 +FFF0 +FFE0 +FFC0 +FF80 +FF00 +FE00 +FC00 +F800 +F000 +E000 +C000 +8000 +ENDCHAR +STARTCHAR uniE0B1 +ENCODING 57521 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +8000 +C000 +E000 +7000 +3800 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +001C +000E +0007 +0007 +000E +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +E000 +C000 +8000 +ENDCHAR +STARTCHAR uniE0B2 +ENCODING 57522 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0001 +0003 +0007 +000F +001F +003F +007F +00FF +01FF +03FF +07FF +0FFF +1FFF +3FFF +7FFF +FFFF +FFFF +7FFF +3FFF +1FFF +0FFF +07FF +03FF +01FF +00FF +007F +003F +001F +000F +0007 +0003 +0001 +ENDCHAR +STARTCHAR uniE0B3 +ENCODING 57523 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0001 +0003 +0007 +000E +001C +0038 +0070 +00E0 +01C0 +0380 +0700 +0E00 +1C00 +3800 +7000 +E000 +E000 +7000 +3800 +1C00 +0E00 +0700 +0380 +01C0 +00E0 +0070 +0038 +001C +000E +0007 +0003 +0001 +ENDCHAR +STARTCHAR uniF6BE +ENCODING 63166 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0000 +0078 +0078 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +0018 +1818 +1818 +1C38 +0FF0 +07E0 +0000 +ENDCHAR +STARTCHAR uniFFFD +ENCODING 65533 +SWIDTH 500 0 +DWIDTH 16 0 +BBX 16 32 0 -6 +BITMAP +0000 +0000 +0000 +0000 +0000 +0000 +3FFC +3FFC +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +300C +3FFC +3FFC +0000 +0000 +0000 +0000 +0000 +0000 +ENDCHAR +ENDFONT From 9a0469253995c5d0c088382bf62f93f2ee904022 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 16:07:29 -0800 Subject: [PATCH 126/234] Refactor map generator package - Move room generators to map.generators.room - Move corridor generators to map.generators.corridor Generators have a generate() method that generates the things they place, and an apply() method that applies their objects to a grid of tiles. --- erynrl/engine.py | 25 ++- erynrl/geometry.py | 6 + erynrl/map/__init__.py | 269 ++----------------------------- erynrl/map/generator/__init__.py | 29 ++++ erynrl/map/generator/corridor.py | 92 +++++++++++ erynrl/map/generator/room.py | 174 ++++++++++++++++++++ erynrl/map/room.py | 58 +++++++ 7 files changed, 391 insertions(+), 262 deletions(-) create mode 100644 erynrl/map/generator/__init__.py create mode 100644 erynrl/map/generator/corridor.py create mode 100644 erynrl/map/generator/room.py create mode 100644 erynrl/map/room.py diff --git a/erynrl/engine.py b/erynrl/engine.py index 08a9261..897c4d5 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -18,17 +18,22 @@ from .geometry import Point, Rect, Size from .interface import color from .interface.percentage_bar import PercentageBar from .map import Map +from .map.generator import RoomsAndCorridorsGenerator +from .map.generator.room import BSPRoomGenerator +from .map.generator.corridor import ElbowCorridorGenerator from .messages import MessageLog from .object import Actor, Entity, Hero, Monster if TYPE_CHECKING: from .events import EventHandler + @dataclass class Configuration: '''Configuration of the game engine''' map_size: Size + class Engine: '''The main game engine. @@ -56,23 +61,27 @@ class Engine: self.did_successfully_process_actions_for_turn = False self.rng = tcod.random.Random() - self.map = Map(configuration.map_size) self.message_log = MessageLog() + map_size = configuration.map_size + map_generator = RoomsAndCorridorsGenerator(BSPRoomGenerator(size=map_size), ElbowCorridorGenerator()) + self.map = Map(map_size, map_generator) + self.event_handler: 'EventHandler' = MainGameEventHandler(self) self.current_mouse_point: Optional[Point] = None - self.hero = Hero(position=self.map.generator.rooms[0].center) - self.entities: MutableSet[Entity] = {self.hero} - for room in self.map.rooms: + self.entities: MutableSet[Entity] = set() + + self.hero = Hero(position=self.map.random_walkable_position()) + self.entities.add(self.hero) + + while len(self.entities) < 25: should_spawn_monster_chance = random.random() - if should_spawn_monster_chance < 0.4: + if should_spawn_monster_chance < 0.1: continue - floor = list(room.walkable_tiles) - for _ in range(2): while True: - random_start_position = random.choice(floor) + random_start_position = self.map.random_walkable_position() if not any(ent.position == random_start_position for ent in self.entities): break diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 872777d..7990f78 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -13,6 +13,12 @@ class Point: x: int = 0 y: int = 0 + @property + def neighbors(self) -> Iterator['Point']: + '''Iterator over the neighboring points of `self` in all eight directions.''' + for direction in Direction.all(): + yield self + direction + def is_adjacent_to(self, other: 'Point') -> bool: '''Check if this point is adjacent to, but not overlapping the given point diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index a2f0b73..7267d59 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -1,40 +1,38 @@ # Eryn Wells import random -from dataclasses import dataclass -from typing import Iterator, List, Optional +from typing import List, Optional import numpy as np import tcod -from .. import log -from ..geometry import Direction, Point, Rect, Size -from .tile import Empty, Floor, Shroud, Wall +from ..geometry import Point, Size +from .generator import MapGenerator +from .room import Room, RectangularRoom +from .tile import Empty, Shroud class Map: - def __init__(self, size: Size, room_generator_class=RoomsAndCorridorsGenerator): + def __init__(self, size: Size, generator: MapGenerator): self.size = size - self.generator = room_generator_class(size=size) - self.tiles = self.generator.generate() + self.generator = generator + self.tiles = np.full(tuple(size), fill_value=Empty, order='F') + self.generator.generate(self.tiles) # Map tiles that are currently visible to the player self.visible = np.full(tuple(self.size), fill_value=True, order='F') # Map tiles that the player has explored self.explored = np.full(tuple(self.size), fill_value=True, order='F') - @property - def rooms(self) -> List['Room']: - '''The list of rooms in the map''' - return self.generator.rooms + self.__walkable_points = None def random_walkable_position(self) -> Point: - # TODO: Include hallways - random_room: RectangularRoom = random.choice(self.rooms) - floor: List[Point] = list(random_room.walkable_tiles) - random_position_in_room = random.choice(floor) - return random_position_in_room + '''Return a random walkable point on the map.''' + if not self.__walkable_points: + self.__walkable_points = [Point(x, y) for x, y in np.ndindex( + self.tiles.shape) if self.tiles[x, y]['walkable']] + return random.choice(self.__walkable_points) def tile_is_in_bounds(self, point: Point) -> bool: '''Return True if the given point is inside the bounds of the map''' @@ -54,240 +52,3 @@ class Map: condlist=[self.visible, self.explored], choicelist=[self.tiles['light'], self.tiles['dark']], default=Shroud) - - -class MapGenerator: - def __init__(self, *, size: Size): - self.size = size - self.rooms: List['Room'] = [] - - def generate(self) -> np.ndarray: - ''' - Generate a tile grid - - Subclasses should implement this and fill in their specific map - generation algorithm. - - Returns - ------- - np.ndarray - A two-dimensional array of tiles. Dimensions should match the given size. - ''' - raise NotImplementedError() - - -class RoomsAndCorridorsGenerator(MapGenerator): - '''Generate a rooms-and-corridors style map with BSP.''' - - @dataclass - class Configuration: - minimum_room_size: Size - maximum_room_size: Size - - DefaultConfiguration = Configuration( - minimum_room_size=Size(7, 7), - maximum_room_size=Size(20, 20), - ) - - def __init__(self, *, size: Size, config: Optional[Configuration] = None): - super().__init__(size=size) - self.configuration = config if config else RoomsAndCorridorsGenerator.DefaultConfiguration - - self.rng: tcod.random.Random = tcod.random.Random() - - self.rooms: List['RectangularRoom'] = [] - self.tiles: Optional[np.ndarray] = None - - def generate(self) -> np.ndarray: - if self.tiles: - return self.tiles - - minimum_room_size = self.configuration.minimum_room_size - maximum_room_size = self.configuration.maximum_room_size - - # Recursively divide the map into squares of various sizes to place rooms in. - bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) - - # Add 2 to the minimum width and height to account for walls - gap_for_walls = 2 - bsp.split_recursive( - depth=4, - min_width=minimum_room_size.width + gap_for_walls, - min_height=minimum_room_size.height + gap_for_walls, - max_horizontal_ratio=1.1, - max_vertical_ratio=1.1 - ) - - tiles = np.full(tuple(self.size), fill_value=Empty, order='F') - - # Generate the rooms - rooms: List['RectangularRoom'] = [] - - room_attrname = f'{__class__.__name__}.room' - - for node in bsp.post_order(): - node_bounds = self.__rect_from_bsp_node(node) - - if node.children: - log.MAP.debug(node_bounds) - - left_room: RectangularRoom = getattr(node.children[0], room_attrname) - right_room: RectangularRoom = getattr(node.children[1], room_attrname) - - left_room_bounds = left_room.bounds - right_room_bounds = right_room.bounds - - log.MAP.debug(' left: %s, %s', node.children[0], left_room_bounds) - log.MAP.debug('right: %s, %s', node.children[1], right_room_bounds) - - start_point = left_room_bounds.midpoint - end_point = right_room_bounds.midpoint - - # Randomly choose whether to move horizontally then vertically or vice versa - if random.random() < 0.5: - corner = Point(end_point.x, start_point.y) - else: - corner = Point(start_point.x, end_point.y) - - log.MAP.debug( - 'Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) - log.MAP.debug('|-> start: %s', left_room_bounds) - log.MAP.debug('`-> end: %s', right_room_bounds) - - for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): - tiles[x, y] = Floor - for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): - tiles[x, y] = Floor - else: - log.MAP.debug('%s (room) %s', node_bounds, node) - - # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is - # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of - # the node. - width_range = ( - minimum_room_size.width, - min(maximum_room_size.width, max( - minimum_room_size.width, node.width - 2)) - ) - height_range = ( - minimum_room_size.height, - min(maximum_room_size.height, max( - minimum_room_size.height, node.height - 2)) - ) - - size = Size(self.rng.randint(*width_range), - self.rng.randint(*height_range)) - origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 1)), - node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) - bounds = Rect(origin, size) - - log.MAP.debug('`-> %s', bounds) - - room = RectangularRoom(bounds) - setattr(node, room_attrname, room) - rooms.append(room) - - if not hasattr(node.parent, room_attrname): - setattr(node.parent, room_attrname, room) - elif random.random() < 0.5: - setattr(node.parent, room_attrname, room) - - # Pass up a random child room so that parent nodes can connect subtrees to each other. - parent = node.parent - if parent: - node_room = getattr(node, room_attrname) - if not hasattr(node.parent, room_attrname): - setattr(node.parent, room_attrname, node_room) - elif random.random() < 0.5: - setattr(node.parent, room_attrname, node_room) - - self.rooms = rooms - - for room in rooms: - for wall_position in room.walls: - if tiles[wall_position.x, wall_position.y] != Floor: - tiles[wall_position.x, wall_position.y] = Wall - - bounds = room.bounds - # The range of a numpy array slice is [a, b). - floor_rect = bounds.inset_rect(top=1, right=1, bottom=1, left=1) - tiles[floor_rect.min_x:floor_rect.max_x + 1, - floor_rect.min_y:floor_rect.max_y + 1] = Floor - - for y in range(self.size.height): - for x in range(self.size.width): - pos = Point(x, y) - if tiles[x, y] != Floor: - continue - - neighbors = (pos + direction for direction in Direction.all()) - for neighbor in neighbors: - if tiles[neighbor.x, neighbor.y] != Empty: - continue - tiles[neighbor.x, neighbor.y] = Wall - - self.tiles = tiles - - return tiles - - def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: - '''Create a Rect from the given BSP node object''' - return Rect(Point(node.x, node.y), Size(node.width, node.height)) - - -class ElbowCorridorGenerator: - ... - - -class NetHackCorridorGenerator: - '''A corridor generator that produces doors and corridors that look like Nethack's Dungeons of Doom levels.''' - ... - - -class Room: - '''An abstract room. It can be any size or shape.''' - - @property - def walkable_tiles(self) -> Iterator[Point]: - raise NotImplementedError() - - -class RectangularRoom(Room): - '''A rectangular room defined by a Rect. - - Attributes - ---------- - bounds : Rect - A rectangle that defines the room. This rectangle includes the tiles used for the walls, so the floor is 1 tile - inset from the bounds. - ''' - - def __init__(self, bounds: Rect): - self.bounds = bounds - - @property - def center(self) -> Point: - '''The center of the room, truncated according to integer math rules''' - return self.bounds.midpoint - - @property - def walkable_tiles(self) -> Iterator[Point]: - floor_rect = self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) - for y in range(floor_rect.min_y, floor_rect.max_y + 1): - for x in range(floor_rect.min_x, floor_rect.max_x + 1): - yield Point(x, y) - - @property - def walls(self) -> Iterator[Point]: - bounds = self.bounds - min_y = bounds.min_y - max_y = bounds.max_y - min_x = bounds.min_x - max_x = bounds.max_x - for y in range(min_y, max_y + 1): - for x in range(min_x, max_x + 1): - if y == min_y or y == max_y or x == min_x or x == max_x: - yield Point(x, y) - - def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.bounds})' diff --git a/erynrl/map/generator/__init__.py b/erynrl/map/generator/__init__.py new file mode 100644 index 0000000..ff99a64 --- /dev/null +++ b/erynrl/map/generator/__init__.py @@ -0,0 +1,29 @@ +import numpy as np + +from ..tile import Empty +from .corridor import CorridorGenerator +from .room import RoomGenerator + + +class MapGenerator: + '''Abstract base class defining an interface for generating a map and applying it to a set of tiles.''' + + def generate(self, tiles: np.ndarray): + raise NotImplementedError() + + +class RoomsAndCorridorsGenerator(MapGenerator): + ''' + Generates a classic "rooms and corridors" style map with the given room and corridor generators. + ''' + + def __init__(self, room_generator: RoomGenerator, corridor_generator: CorridorGenerator): + self.room_generator = room_generator + self.corridor_generator = corridor_generator + + def generate(self, tiles: np.ndarray): + self.room_generator.generate() + self.room_generator.apply(tiles) + + self.corridor_generator.generate(self.room_generator.rooms) + self.corridor_generator.apply(tiles) diff --git a/erynrl/map/generator/corridor.py b/erynrl/map/generator/corridor.py new file mode 100644 index 0000000..752c5e8 --- /dev/null +++ b/erynrl/map/generator/corridor.py @@ -0,0 +1,92 @@ +''' +Defines an abstract CorridorGenerator and several concrete subclasses. These classes generate corridors between rooms. +''' + +import random +from itertools import pairwise +from typing import List + +import tcod +import numpy as np + +from ... import log +from ...geometry import Point +from ..room import Room +from ..tile import Empty, Floor, Wall + + +class CorridorGenerator: + ''' + Corridor generators produce corridors between rooms. + ''' + + def generate(self, rooms: List[Room]) -> bool: + '''Generate corridors given a list of rooms.''' + raise NotImplementedError() + + def apply(self, tiles: np.ndarray): + '''Apply corridors to a tile grid.''' + raise NotImplementedError() + + +class ElbowCorridorGenerator(CorridorGenerator): + ''' + Generators corridors using a simple "elbow" algorithm: + + ``` + For each pair of rooms: + 1. Find the midpoint of the bounding rect of each room + 2. Calculate an elbow point + 3. Draw a path from the midpoint of the first room to the elbow point + 4. Draw a path from the elbow point to the midpoint of the second room + ``` + ''' + + def __init__(self): + self.corridors: List[List[Point]] = [] + + def generate(self, rooms: List[Room]) -> bool: + for (left_room, right_room) in pairwise(rooms): + left_room_bounds = left_room.bounds + right_room_bounds = right_room.bounds + + log.MAP.debug(' left: %s, %s', left_room, left_room_bounds) + log.MAP.debug('right: %s, %s', right_room, right_room_bounds) + + start_point = left_room_bounds.midpoint + end_point = right_room_bounds.midpoint + + # Randomly choose whether to move horizontally then vertically or vice versa + if random.random() < 0.5: + corner = Point(end_point.x, start_point.y) + else: + corner = Point(start_point.x, end_point.y) + + log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + log.MAP.debug('|-> start: %s', left_room_bounds) + log.MAP.debug('`-> end: %s', right_room_bounds) + + corridor: List[Point] = [] + + for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): + corridor.append(Point(x, y)) + for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): + corridor.append(Point(x, y)) + + self.corridors.append(corridor) + + return True + + def apply(self, tiles): + for corridor in self.corridors: + for pt in corridor: + tiles[pt.x, pt.y] = Floor + for neighbor in pt.neighbors: + if not (0 <= neighbor.x < tiles.shape[0] and 0 <= neighbor.y < tiles.shape[1]): + continue + if tiles[neighbor.x, neighbor.y] == Empty: + tiles[neighbor.x, neighbor.y] = Wall + + +class NetHackCorridorGenerator(CorridorGenerator): + '''A corridor generator that produces doors and corridors that look like Nethack's Dungeons of Doom levels.''' diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py new file mode 100644 index 0000000..2655c56 --- /dev/null +++ b/erynrl/map/generator/room.py @@ -0,0 +1,174 @@ +import random +from dataclasses import dataclass +from typing import List, Optional + +import numpy as np +import tcod + +from ... import log +from ...geometry import Direction, Point, Rect, Size +from ..room import Room, RectangularRoom +from ..tile import Empty, Floor, Wall + + +class RoomGenerator: + '''Abstract room generator class.''' + + def __init__(self, *, size: Size): + self.size = size + self.rooms: List[Room] = [] + + def generate(self) -> bool: + ''' + Generate a list of rooms. + + Subclasses should implement this and fill in their specific map + generation algorithm. + + Returns + ------- + np.ndarray + A two-dimensional array of tiles. Dimensions should match the given size. + ''' + raise NotImplementedError() + + def apply(self, tiles: np.ndarray): + ''' + Apply the generated list of rooms to an array of tiles. Subclasses must implement this. + + Arguments + --------- + tiles: np.ndarray + The array of tiles to update. + ''' + raise NotImplementedError() + + +class BSPRoomGenerator(RoomGenerator): + '''Generate a rooms-and-corridors style map with BSP.''' + + @dataclass + class Configuration: + '''Configuration parameters for BSPRoomGenerator.''' + + minimum_room_size: Size + maximum_room_size: Size + + DefaultConfiguration = Configuration( + minimum_room_size=Size(7, 7), + maximum_room_size=Size(20, 20), + ) + + def __init__(self, *, size: Size, config: Optional[Configuration] = None): + super().__init__(size=size) + self.configuration = config if config else BSPRoomGenerator.DefaultConfiguration + + self.rng: tcod.random.Random = tcod.random.Random() + + self.rooms: List[RectangularRoom] = [] + self.tiles: Optional[np.ndarray] = None + + def generate(self) -> bool: + if self.rooms: + return True + + minimum_room_size = self.configuration.minimum_room_size + maximum_room_size = self.configuration.maximum_room_size + + # Recursively divide the map into squares of various sizes to place rooms in. + bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) + + # Add 2 to the minimum width and height to account for walls + gap_for_walls = 2 + bsp.split_recursive( + depth=4, + min_width=minimum_room_size.width + gap_for_walls, + min_height=minimum_room_size.height + gap_for_walls, + max_horizontal_ratio=1.1, + max_vertical_ratio=1.1 + ) + + # Generate the rooms + rooms: List['RectangularRoom'] = [] + + room_attrname = f'{__class__.__name__}.room' + + for node in bsp.post_order(): + node_bounds = self.__rect_from_bsp_node(node) + + if node.children: + continue + + log.MAP.debug('%s (room) %s', node_bounds, node) + + # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is + # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of + # the node. + width_range = ( + minimum_room_size.width, + min(maximum_room_size.width, max( + minimum_room_size.width, node.width - 2)) + ) + height_range = ( + minimum_room_size.height, + min(maximum_room_size.height, max( + minimum_room_size.height, node.height - 2)) + ) + + size = Size(self.rng.randint(*width_range), + self.rng.randint(*height_range)) + origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 1)), + node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) + bounds = Rect(origin, size) + + log.MAP.debug('`-> %s', bounds) + + room = RectangularRoom(bounds) + setattr(node, room_attrname, room) + rooms.append(room) + + if not hasattr(node.parent, room_attrname): + setattr(node.parent, room_attrname, room) + elif random.random() < 0.5: + setattr(node.parent, room_attrname, room) + + # Pass up a random child room so that parent nodes can connect subtrees to each other. + parent = node.parent + if parent: + node_room = getattr(node, room_attrname) + if not hasattr(node.parent, room_attrname): + setattr(node.parent, room_attrname, node_room) + elif random.random() < 0.5: + setattr(node.parent, room_attrname, node_room) + + self.rooms = rooms + + return True + + def apply(self, tiles: np.ndarray): + for room in self.rooms: + for wall_position in room.walls: + if tiles[wall_position.x, wall_position.y] != Floor: + tiles[wall_position.x, wall_position.y] = Wall + + bounds = room.bounds + # The range of a numpy array slice is [a, b). + floor_rect = bounds.inset_rect(top=1, right=1, bottom=1, left=1) + tiles[floor_rect.min_x:floor_rect.max_x + 1, + floor_rect.min_y:floor_rect.max_y + 1] = Floor + + for y in range(self.size.height): + for x in range(self.size.width): + pos = Point(x, y) + if tiles[x, y] != Floor: + continue + + neighbors = (pos + direction for direction in Direction.all()) + for neighbor in neighbors: + if tiles[neighbor.x, neighbor.y] != Empty: + continue + tiles[neighbor.x, neighbor.y] = Wall + + def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: + '''Create a Rect from the given BSP node object''' + return Rect(Point(node.x, node.y), Size(node.width, node.height)) diff --git a/erynrl/map/room.py b/erynrl/map/room.py new file mode 100644 index 0000000..c4f2f66 --- /dev/null +++ b/erynrl/map/room.py @@ -0,0 +1,58 @@ +from typing import Iterator + +from ..geometry import Point, Rect + + +class Room: + '''An abstract room. It can be any size or shape.''' + + def __init__(self, bounds): + self.bounds = bounds + + @property + def center(self) -> Point: + '''The center of the room, truncated according to integer math rules''' + return self.bounds.midpoint + + @property + def walls(self) -> Iterator[Point]: + '''An iterator over all the wall tiles of this room.''' + raise NotImplementedError() + + @property + def walkable_tiles(self) -> Iterator[Point]: + '''An iterator over all the walkable tiles in this room.''' + raise NotImplementedError() + + +class RectangularRoom(Room): + '''A rectangular room defined by a Rect. + + Attributes + ---------- + bounds : Rect + A rectangle that defines the room. This rectangle includes the tiles used for the walls, so the floor is 1 tile + inset from the bounds. + ''' + + @property + def walkable_tiles(self) -> Iterator[Point]: + floor_rect = self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) + for y in range(floor_rect.min_y, floor_rect.max_y + 1): + for x in range(floor_rect.min_x, floor_rect.max_x + 1): + yield Point(x, y) + + @property + def walls(self) -> Iterator[Point]: + bounds = self.bounds + min_y = bounds.min_y + max_y = bounds.max_y + min_x = bounds.min_x + max_x = bounds.max_x + for y in range(min_y, max_y + 1): + for x in range(min_x, max_x + 1): + if y == min_y or y == max_y or x == min_x or x == max_x: + yield Point(x, y) + + def __repr__(self) -> str: + return f'{self.__class__.__name__}({self.bounds})' From d8cb6b42423f453afe0eab45255466e85c8644da Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 16:07:45 -0800 Subject: [PATCH 127/234] Tell the linter to allow `pt` as a variable name --- .pylintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.pylintrc b/.pylintrc index 1f309f7..cf56258 100644 --- a/.pylintrc +++ b/.pylintrc @@ -413,6 +413,7 @@ good-names=ai, dy, fg, hp, + pt, i, j, k, From 175f94798b7a815b5a7d46c963fab353d9c54242 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 16:09:06 -0800 Subject: [PATCH 128/234] Enable strict type checking in the workspace --- going_rogue.code-workspace | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/going_rogue.code-workspace b/going_rogue.code-workspace index 56f8b2b..29786f7 100644 --- a/going_rogue.code-workspace +++ b/going_rogue.code-workspace @@ -5,6 +5,7 @@ } ], "settings": { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "python.analysis.typeCheckingMode": "strict" } } From 391f84b21b5346f7f7d525702c2b1b5e964bf058 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 16:09:58 -0800 Subject: [PATCH 129/234] Fix up the type annotations for geometry.Direction --- erynrl/geometry.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 7990f78..37ad50c 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -6,6 +6,7 @@ import math from dataclasses import dataclass from typing import Any, Iterator, Optional, overload + @dataclass(frozen=True) class Point: '''A two-dimensional point, with coordinates in X and Y axes''' @@ -32,9 +33,9 @@ class Point: bool True if this point is adjacent to the other point ''' - return (self.x in (other.x - 1, other.x + 1)) and (self.y in (other.y -1, other.y + 1)) + return (self.x in (other.x - 1, other.x + 1)) and (self.y in (other.y - 1, other.y + 1)) - def direction_to_adjacent_point(self, other: 'Point') -> Optional['Direction']: + def direction_to_adjacent_point(self, other: 'Point') -> Optional['Vector']: for direction in Direction.all(): if (self + direction) != other: continue @@ -62,6 +63,7 @@ class Point: def __str__(self): return f'(x:{self.x}, y:{self.y})' + @dataclass(frozen=True) class Vector: '''A two-dimensional vector, representing change in position in X and Y axes''' @@ -76,8 +78,12 @@ class Vector: def __str__(self): return f'(δx:{self.dx}, δy:{self.dy})' + class Direction: - '''A collection of simple uint vectors in each of the eight major compass directions. This is a namespace, not a class.''' + ''' + A collection of simple uint vectors in each of the eight major compass + directions. This is a namespace, not a class. + ''' North = Vector(0, -1) NorthEast = Vector(1, -1) @@ -89,7 +95,7 @@ class Direction: NorthWest = Vector(-1, -1) @classmethod - def all(cls) -> Iterator['Direction']: + def all(cls) -> Iterator[Vector]: '''Iterate through all directions, starting with North and proceeding clockwise''' yield Direction.North yield Direction.NorthEast @@ -100,6 +106,7 @@ class Direction: yield Direction.West yield Direction.NorthWest + @dataclass(frozen=True) class Size: '''A two-dimensional size, representing size in X (width) and Y (height) axes''' @@ -114,6 +121,7 @@ class Size: def __str__(self): return f'(w:{self.width}, h:{self.height})' + @dataclass(frozen=True) class Rect: '''A two-dimensional rectangle, defined by an origin point and size''' From dabc9e70dd9e6936b076e28a4958669b13249f6a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 16:11:33 -0800 Subject: [PATCH 130/234] PEP8 formatter changes --- erynrl/actions/game.py | 21 ++++++++++++++++++--- erynrl/engine.py | 26 ++++++++++++++------------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index 3d03df0..44ddbe8 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -30,6 +30,7 @@ from .result import ActionResult if TYPE_CHECKING: from ..engine import Engine + class ExitAction(Action): '''Exit the game.''' @@ -39,6 +40,7 @@ class ExitAction(Action): def perform(self, engine: 'Engine') -> ActionResult: raise SystemExit() + class RegenerateRoomsAction(Action): '''Regenerate the dungeon map''' @@ -46,6 +48,8 @@ class RegenerateRoomsAction(Action): return ActionResult(self, success=False) # pylint: disable=abstract-method + + class MoveAction(Action): '''An abstract Action that requires a direction to complete.''' @@ -59,6 +63,7 @@ class MoveAction(Action): def __str__(self) -> str: return f'{self.__class__.__name__} toward {self.direction} by {self.actor!s}' + class BumpAction(MoveAction): '''Attempt to perform a movement action in a direction. @@ -84,7 +89,8 @@ class BumpAction(MoveAction): else: entity_occupying_position = None - log.ACTIONS.info('Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', + log.ACTIONS.info( + 'Bumping %s into %s (in_bounds:%s walkable:%s overlaps:%s)', self.actor, new_position, position_is_in_bounds, @@ -119,6 +125,7 @@ class WalkAction(MoveAction): return self.success() + class MeleeAction(MoveAction): '''Perform a melee attack on another Actor''' @@ -134,9 +141,13 @@ class MeleeAction(MoveAction): self.target.fighter.hit_points -= damage if self.actor == engine.hero: - engine.message_log.add_message(f'You attack the {self.target.name} for {damage} damage!', fg=(127, 255, 127)) + engine.message_log.add_message( + f'You attack the {self.target.name} for {damage} damage!', + fg=(127, 255, 127)) elif self.target == engine.hero: - engine.message_log.add_message(f'The {self.actor.name} attacks you for {damage} damage!', fg=(255, 127, 127)) + engine.message_log.add_message( + f'The {self.actor.name} attacks you for {damage} damage!', + fg=(255, 127, 127)) else: log.ACTIONS.debug('%s attacks %s but does no damage!', self.actor, self.target) @@ -148,6 +159,7 @@ class MeleeAction(MoveAction): else: return self.success() + class WaitAction(Action): '''Wait a turn''' @@ -161,6 +173,7 @@ class WaitAction(Action): return self.success() + class DieAction(Action): '''Kill an Actor''' @@ -179,6 +192,7 @@ class DieAction(Action): return self.success() + class DropItemAction(Action): '''Drop an item''' @@ -190,6 +204,7 @@ class DropItemAction(Action): engine.entities.add(self.item) return self.success() + class HealAction(Action): '''Heal a target actor some number of hit points''' diff --git a/erynrl/engine.py b/erynrl/engine.py index 897c4d5..0c1a595 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -80,19 +80,19 @@ class Engine: if should_spawn_monster_chance < 0.1: continue - while True: + while True: random_start_position = self.map.random_walkable_position() - if not any(ent.position == random_start_position for ent in self.entities): - break + if not any(ent.position == random_start_position for ent in self.entities): + break - spawn_monster_chance = random.random() - if spawn_monster_chance > 0.8: - monster = Monster(monsters.Troll, ai_class=HostileEnemy, position=random_start_position) - else: - monster = Monster(monsters.Orc, ai_class=HostileEnemy, position=random_start_position) + spawn_monster_chance = random.random() + if spawn_monster_chance > 0.8: + monster = Monster(monsters.Troll, ai_class=HostileEnemy, position=random_start_position) + else: + monster = Monster(monsters.Orc, ai_class=HostileEnemy, position=random_start_position) - log.ENGINE.info('Spawning %s', monster) - self.entities.add(monster) + log.ENGINE.info('Spawning %s', monster) + self.entities.add(monster) self.update_field_of_view() @@ -193,7 +193,8 @@ class Engine: alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' else: alternate_string = str(result.alternate) - log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', + log.ACTIONS_TREE.info( + '| %s-> %s => success=%s done=%s alternate=%s', '|' if not result.success or not result.done else '`', action, result.success, @@ -211,7 +212,8 @@ class Engine: alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' else: alternate_string = str(result.alternate) - log.ACTIONS_TREE.info('| %s-> %s => success=%s done=%s alternate=%s', + log.ACTIONS_TREE.info( + '| %s-> %s => success=%s done=%s alternate=%s', '|' if not result.success or not result.done else '`', action, result.success, From 8e9b130ba7fba0bc8762598f57335a88772cdd61 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 20:54:40 -0800 Subject: [PATCH 131/234] Fix the ordering of imports in map/__init__.py --- erynrl/map/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 7267d59..9c7cb26 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -1,14 +1,13 @@ # Eryn Wells import random -from typing import List, Optional import numpy as np +import numpy.typing as npt import tcod from ..geometry import Point, Size from .generator import MapGenerator -from .room import Room, RectangularRoom from .tile import Empty, Shroud From aacc5d56ca096b899149a46f6afa5aece497f4bf Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 20:55:03 -0800 Subject: [PATCH 132/234] =?UTF-8?q?"strict"=20type=20checking=20is=20too?= =?UTF-8?q?=20much=20=F0=9F=99=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- going_rogue.code-workspace | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/going_rogue.code-workspace b/going_rogue.code-workspace index 29786f7..de521eb 100644 --- a/going_rogue.code-workspace +++ b/going_rogue.code-workspace @@ -6,6 +6,6 @@ ], "settings": { "editor.formatOnSave": true, - "python.analysis.typeCheckingMode": "strict" + "python.analysis.typeCheckingMode": "basic" } } From ac5efa75187dd31db220c2cff0c4c2edc24095e0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 20:57:37 -0800 Subject: [PATCH 133/234] PEP8 formatting in map.tile --- erynrl/map/tile.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/erynrl/map/tile.py b/erynrl/map/tile.py index 07fa0a3..b20085b 100644 --- a/erynrl/map/tile.py +++ b/erynrl/map/tile.py @@ -1,8 +1,6 @@ -#!/usr/bin/env python3 -# Eryn Wells +from typing import Tuple import numpy as np -from typing import Tuple graphic_datatype = np.dtype([ # Character, a Unicode codepoint represented as an int32 @@ -15,31 +13,36 @@ graphic_datatype = np.dtype([ tile_datatype = np.dtype([ # Bool indicating whether this tile can be traversed - ('walkable', np.bool), + ('walkable', np.bool_), # Bool indicating whether this tile is transparent - ('transparent', np.bool), + ('transparent', np.bool_), # A graphic struct (as above) defining the look of this tile when it's not visible ('dark', graphic_datatype), # A graphic struct (as above) defining the look of this tile when it's visible ('light', graphic_datatype), ]) + def tile(*, walkable: int, transparent: int, - dark: Tuple[int, Tuple[int, int, int], Tuple[int, int ,int]], - light: Tuple[int, Tuple[int, int, int], Tuple[int, int ,int]]) -> np.ndarray: + dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]], + light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]]) -> np.ndarray: return np.array((walkable, transparent, dark, light), dtype=tile_datatype) + # An overlay color for tiles that are not visible and have not been explored Shroud = np.array((ord(' '), (255, 255, 255), (0, 0, 0)), dtype=graphic_datatype) -Empty = tile(walkable=False, transparent=False, - dark=(ord(' '), (255, 255, 255), (0, 0, 0)), - light=(ord(' '), (255, 255, 255), (0, 0, 0))) -Floor = tile(walkable=True, transparent=True, - dark=(ord('·'), (80, 80, 100), (50, 50, 50)), - light=(ord('·'), (100, 100, 120), (80, 80, 100))) -Wall = tile(walkable=False, transparent=False, - dark=(ord(' '), (255, 255, 255), (0, 0, 150)), - light=(ord(' '), (255, 255, 255), (50, 50, 200))) \ No newline at end of file +Empty = tile( + walkable=False, transparent=False, + dark=(ord(' '), (255, 255, 255), (0, 0, 0)), + light=(ord(' '), (255, 255, 255), (0, 0, 0))) +Floor = tile( + walkable=True, transparent=True, + dark=(ord('·'), (80, 80, 100), (50, 50, 50)), + light=(ord('·'), (100, 100, 120), (80, 80, 100))) +Wall = tile( + walkable=False, transparent=False, + dark=(ord(' '), (255, 255, 255), (0, 0, 150)), + light=(ord(' '), (255, 255, 255), (50, 50, 200))) From d9aa8097c5ccb15621b78f854a80a8ec8f4f83b9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 20:57:51 -0800 Subject: [PATCH 134/234] Add StairsUp and StairsDown tile types --- erynrl/map/tile.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/erynrl/map/tile.py b/erynrl/map/tile.py index b20085b..23c3eb7 100644 --- a/erynrl/map/tile.py +++ b/erynrl/map/tile.py @@ -42,6 +42,14 @@ Floor = tile( walkable=True, transparent=True, dark=(ord('·'), (80, 80, 100), (50, 50, 50)), light=(ord('·'), (100, 100, 120), (80, 80, 100))) +StairsUp = tile( + walkable=True, transparent=True, + dark=(ord('<'), (80, 80, 100), (50, 50, 50)), + light=(ord('<'), (100, 100, 120), (80, 80, 100))) +StairsDown = tile( + walkable=True, transparent=True, + dark=(ord('>'), (80, 80, 100), (50, 50, 50)), + light=(ord('>'), (100, 100, 120), (80, 80, 100))) Wall = tile( walkable=False, transparent=False, dark=(ord(' '), (255, 255, 255), (0, 0, 150)), From fff3260d01bb33246cf7a57c7dc71e24663c2d07 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 21:00:51 -0800 Subject: [PATCH 135/234] Give MapGenerator up_stairs and down_stairs properties --- erynrl/map/generator/__init__.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/erynrl/map/generator/__init__.py b/erynrl/map/generator/__init__.py index ff99a64..2d4e361 100644 --- a/erynrl/map/generator/__init__.py +++ b/erynrl/map/generator/__init__.py @@ -1,13 +1,23 @@ +from typing import List + import numpy as np -from ..tile import Empty from .corridor import CorridorGenerator from .room import RoomGenerator +from ...geometry import Point class MapGenerator: '''Abstract base class defining an interface for generating a map and applying it to a set of tiles.''' + @property + def up_stairs(self) -> List[Point]: + raise NotImplementedError() + + @property + def down_stairs(self) -> List[Point]: + raise NotImplementedError() + def generate(self, tiles: np.ndarray): raise NotImplementedError() @@ -21,6 +31,14 @@ class RoomsAndCorridorsGenerator(MapGenerator): self.room_generator = room_generator self.corridor_generator = corridor_generator + @property + def up_stairs(self) -> List[Point]: + return self.room_generator.up_stairs + + @property + def down_stairs(self) -> List[Point]: + return self.room_generator.down_stairs + def generate(self, tiles: np.ndarray): self.room_generator.generate() self.room_generator.apply(tiles) From 771926088c60b18f13cbae2cbd199419cbb7e8eb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 21:04:26 -0800 Subject: [PATCH 136/234] Remove an unused import --- erynrl/map/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/map/room.py b/erynrl/map/room.py index c4f2f66..be78a04 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -1,6 +1,6 @@ from typing import Iterator -from ..geometry import Point, Rect +from ..geometry import Point class Room: From 350876347b7276a2be5f2e0b63b04be460c705e0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 21:05:05 -0800 Subject: [PATCH 137/234] Correct some Direction type annotations They should always have been Vectors. --- erynrl/actions/game.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index 44ddbe8..03f7438 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -22,7 +22,7 @@ from typing import TYPE_CHECKING from .. import items from .. import log -from ..geometry import Direction +from ..geometry import Vector from ..object import Actor, Item from .action import Action from .result import ActionResult @@ -53,7 +53,7 @@ class RegenerateRoomsAction(Action): class MoveAction(Action): '''An abstract Action that requires a direction to complete.''' - def __init__(self, actor: Actor, direction: Direction): + def __init__(self, actor: Actor, direction: Vector): super().__init__(actor) self.direction = direction @@ -129,7 +129,7 @@ class WalkAction(MoveAction): class MeleeAction(MoveAction): '''Perform a melee attack on another Actor''' - def __init__(self, actor: Actor, direction: Direction, target: Actor): + def __init__(self, actor: Actor, direction: Vector, target: Actor): super().__init__(actor, direction) self.target = target From d8dfe5c497059539f7782c365dca20de413eddb4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 21:07:42 -0800 Subject: [PATCH 138/234] Fix some PEP8 formatting issues --- erynrl/object.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/erynrl/object.py b/erynrl/object.py index ba2337a..cc39812 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -15,6 +15,7 @@ from .monsters import Species if TYPE_CHECKING: from .ai import AI + class RenderOrder(Enum): ''' These values indicate the order that an Entity should be rendered. Higher values are rendered later and therefore on @@ -24,6 +25,7 @@ class RenderOrder(Enum): ACTOR = 2000 HERO = 3000 + class Entity: '''A single-tile drawable entity with a symbol and position @@ -47,15 +49,9 @@ class Entity: ''' # A monotonically increasing identifier to help differentiate between entities that otherwise look identical - __NEXT_IDENTIFIER = 1 + __next_identifier = 1 - def __init__(self, symbol: str, *, - position: Optional[Point] = None, - blocks_movement: Optional[bool] = True, - render_order: RenderOrder = RenderOrder.ITEM, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None): - self.identifier = Entity.__NEXT_IDENTIFIER + self.identifier = Entity.__next_identifier self.position = position if position else Point() self.foreground = fg if fg else (255, 255, 255) self.background = bg @@ -63,7 +59,7 @@ class Entity: self.blocks_movement = blocks_movement self.render_order = render_order - Entity.__NEXT_IDENTIFIER += 1 + Entity.__next_identifier += 1 def print_to_console(self, console: tcod.Console) -> None: '''Render this Entity to the console''' @@ -75,6 +71,7 @@ class Entity: def __repr__(self) -> str: return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fg={self.foreground!r}, bg={self.background!r})' + class Actor(Entity): ''' An actor is an abstract class that defines an object that can act in the game world. Entities that are actors will @@ -121,6 +118,7 @@ class Actor(Entity): def __repr__(self) -> str: return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fighter={self.fighter!r}, ai={self.ai!r}, fg={self.foreground!r}, bg={self.background!r})' + class Hero(Actor): '''The hero, the player character''' @@ -143,6 +141,7 @@ class Hero(Actor): def __str__(self) -> str: return f'Hero!{self.identifier} at {self.position} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp' + class Monster(Actor): '''An instance of a Species''' @@ -177,6 +176,7 @@ class Monster(Actor): def __str__(self) -> str: return f'{self.name}!{self.identifier} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp at {self.position}' + class Item(Entity): '''An instance of an Item''' From d4c4b5d87929f4d02f77c359b20cbe9808955f21 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Thu, 9 Feb 2023 21:08:11 -0800 Subject: [PATCH 139/234] Reformat some heckin' long initializers in object.py --- erynrl/object.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/erynrl/object.py b/erynrl/object.py index cc39812..286fb0f 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -51,6 +51,15 @@ class Entity: # A monotonically increasing identifier to help differentiate between entities that otherwise look identical __next_identifier = 1 + def __init__( + self, + symbol: str, + *, + position: Optional[Point] = None, + blocks_movement: Optional[bool] = True, + render_order: RenderOrder = RenderOrder.ITEM, + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None): self.identifier = Entity.__next_identifier self.position = position if position else Point() self.foreground = fg if fg else (255, 255, 255) @@ -86,14 +95,17 @@ class Actor(Entity): defense power, etc live. ''' - def __init__(self, symbol: str, *, - position: Optional[Point] = None, - blocks_movement: Optional[bool] = True, - render_order: RenderOrder = RenderOrder.ACTOR, - ai: Optional[Type['AI']] = None, - fighter: Optional[Fighter] = None, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None): + def __init__( + self, + symbol: str, + *, + position: Optional[Point] = None, + blocks_movement: Optional[bool] = True, + render_order: RenderOrder = RenderOrder.ACTOR, + ai: Optional[Type['AI']] = None, + fighter: Optional[Fighter] = None, + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None): super().__init__(symbol, position=position, blocks_movement=blocks_movement, fg=fg, bg=bg, render_order=render_order) # Components @@ -123,11 +135,12 @@ class Hero(Actor): '''The hero, the player character''' def __init__(self, position: Point): - super().__init__('@', - position=position, - fighter=Fighter(maximum_hit_points=30, attack_power=5, defense=2), - render_order=RenderOrder.HERO, - fg=tuple(tcod.white)) + super().__init__( + '@', + position=position, + fighter=Fighter(maximum_hit_points=30, attack_power=5, defense=2), + render_order=RenderOrder.HERO, + fg=tuple(tcod.white)) @property def name(self) -> str: From c59dc1b907ed5002eea6359872d2f8f57bb81105 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:06:34 -0800 Subject: [PATCH 140/234] Break up room and corridor generation into generate and apply phases - Generate creates rooms and corridors, and apply applies them to a tile grid. - Add up and down stairs generation to the Room Generators. - Clean up Room.wall_points and Room.floor_points to make it easier to write a generic apply() method on RoomGenerator --- erynrl/map/__init__.py | 5 +- erynrl/map/generator/corridor.py | 62 ++++++++++++++---------- erynrl/map/generator/room.py | 82 ++++++++++++++++++-------------- erynrl/map/room.py | 45 +++++++++++++----- 4 files changed, 122 insertions(+), 72 deletions(-) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 9c7cb26..075a915 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -17,7 +17,10 @@ class Map: self.generator = generator self.tiles = np.full(tuple(size), fill_value=Empty, order='F') - self.generator.generate(self.tiles) + generator.generate(self.tiles) + + self.up_stairs = generator.up_stairs + self.down_stairs = generator.down_stairs # Map tiles that are currently visible to the player self.visible = np.full(tuple(self.size), fill_value=True, order='F') diff --git a/erynrl/map/generator/corridor.py b/erynrl/map/generator/corridor.py index 752c5e8..c61b273 100644 --- a/erynrl/map/generator/corridor.py +++ b/erynrl/map/generator/corridor.py @@ -46,37 +46,49 @@ class ElbowCorridorGenerator(CorridorGenerator): self.corridors: List[List[Point]] = [] def generate(self, rooms: List[Room]) -> bool: + if len(rooms) < 2: + return True + for (left_room, right_room) in pairwise(rooms): - left_room_bounds = left_room.bounds - right_room_bounds = right_room.bounds - - log.MAP.debug(' left: %s, %s', left_room, left_room_bounds) - log.MAP.debug('right: %s, %s', right_room, right_room_bounds) - - start_point = left_room_bounds.midpoint - end_point = right_room_bounds.midpoint - - # Randomly choose whether to move horizontally then vertically or vice versa - if random.random() < 0.5: - corner = Point(end_point.x, start_point.y) - else: - corner = Point(start_point.x, end_point.y) - - log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) - log.MAP.debug('|-> start: %s', left_room_bounds) - log.MAP.debug('`-> end: %s', right_room_bounds) - - corridor: List[Point] = [] - - for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): - corridor.append(Point(x, y)) - for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): - corridor.append(Point(x, y)) + corridor = self._generate_corridor_between(left_room, right_room) + self.corridors.append(corridor) + for i in range(len(rooms) - 2): + corridor = self._generate_corridor_between(rooms[i], rooms[i + 2]) self.corridors.append(corridor) return True + def _generate_corridor_between(self, left_room, right_room): + left_room_bounds = left_room.bounds + right_room_bounds = right_room.bounds + + log.MAP.debug(' left: %s, %s', left_room, left_room_bounds) + log.MAP.debug('right: %s, %s', right_room, right_room_bounds) + + start_point = left_room_bounds.midpoint + end_point = right_room_bounds.midpoint + + # Randomly choose whether to move horizontally then vertically or vice versa + if random.random() < 0.5: + corner = Point(end_point.x, start_point.y) + else: + corner = Point(start_point.x, end_point.y) + + log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + log.MAP.debug('|-> start: %s', left_room_bounds) + log.MAP.debug('`-> end: %s', right_room_bounds) + + corridor: List[Point] = [] + + for x, y in tcod.los.bresenham(tuple(start_point), tuple(corner)).tolist(): + corridor.append(Point(x, y)) + + for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): + corridor.append(Point(x, y)) + + return corridor + def apply(self, tiles): for corridor in self.corridors: for pt in corridor: diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 2655c56..bf069d6 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -8,7 +8,7 @@ import tcod from ... import log from ...geometry import Direction, Point, Rect, Size from ..room import Room, RectangularRoom -from ..tile import Empty, Floor, Wall +from ..tile import Empty, Floor, StairsUp, StairsDown, Wall class RoomGenerator: @@ -17,8 +17,17 @@ class RoomGenerator: def __init__(self, *, size: Size): self.size = size self.rooms: List[Room] = [] + self.up_stairs: List[Point] = [] + self.down_stairs: List[Point] = [] - def generate(self) -> bool: + def generate(self): + '''Generate rooms and stairs''' + did_generate_rooms = self._generate() + + if did_generate_rooms: + self._generate_stairs() + + def _generate(self) -> bool: ''' Generate a list of rooms. @@ -33,6 +42,11 @@ class RoomGenerator: raise NotImplementedError() def apply(self, tiles: np.ndarray): + '''Apply the generated rooms to a tile array''' + self._apply(tiles) + self._apply_stairs(tiles) + + def _apply(self, tiles: np.ndarray): ''' Apply the generated list of rooms to an array of tiles. Subclasses must implement this. @@ -41,7 +55,33 @@ class RoomGenerator: tiles: np.ndarray The array of tiles to update. ''' - raise NotImplementedError() + for room in self.rooms: + for pt in room.floor_points: + tiles[pt.x, pt.y] = Floor + + for room in self.rooms: + for pt in room.wall_points: + if tiles[pt.x, pt.y] != Empty: + continue + tiles[pt.x, pt.y] = Wall + + def _generate_stairs(self): + up_stair_room = random.choice(self.rooms) + down_stair_room = None + if len(self.rooms) >= 2: + while down_stair_room is None or down_stair_room == up_stair_room: + down_stair_room = random.choice(self.rooms) + else: + down_stair_room = up_stair_room + + self.up_stairs.append(random.choice(list(up_stair_room.walkable_tiles))) + self.down_stairs.append(random.choice(list(down_stair_room.walkable_tiles))) + + def _apply_stairs(self, tiles): + for pt in self.up_stairs: + tiles[pt.x, pt.y] = StairsUp + for pt in self.down_stairs: + tiles[pt.x, pt.y] = StairsDown class BSPRoomGenerator(RoomGenerator): @@ -59,16 +99,12 @@ class BSPRoomGenerator(RoomGenerator): maximum_room_size=Size(20, 20), ) - def __init__(self, *, size: Size, config: Optional[Configuration] = None): - super().__init__(size=size) - self.configuration = config if config else BSPRoomGenerator.DefaultConfiguration +class BSPRoomGenerator(RoomGenerator): + '''Generate a rooms-and-corridors style map with BSP.''' self.rng: tcod.random.Random = tcod.random.Random() - self.rooms: List[RectangularRoom] = [] - self.tiles: Optional[np.ndarray] = None - - def generate(self) -> bool: + def _generate(self) -> bool: if self.rooms: return True @@ -89,7 +125,7 @@ class BSPRoomGenerator(RoomGenerator): ) # Generate the rooms - rooms: List['RectangularRoom'] = [] + rooms: List[Room] = [] room_attrname = f'{__class__.__name__}.room' @@ -145,30 +181,6 @@ class BSPRoomGenerator(RoomGenerator): return True - def apply(self, tiles: np.ndarray): - for room in self.rooms: - for wall_position in room.walls: - if tiles[wall_position.x, wall_position.y] != Floor: - tiles[wall_position.x, wall_position.y] = Wall - - bounds = room.bounds - # The range of a numpy array slice is [a, b). - floor_rect = bounds.inset_rect(top=1, right=1, bottom=1, left=1) - tiles[floor_rect.min_x:floor_rect.max_x + 1, - floor_rect.min_y:floor_rect.max_y + 1] = Floor - - for y in range(self.size.height): - for x in range(self.size.width): - pos = Point(x, y) - if tiles[x, y] != Floor: - continue - - neighbors = (pos + direction for direction in Direction.all()) - for neighbor in neighbors: - if tiles[neighbor.x, neighbor.y] != Empty: - continue - tiles[neighbor.x, neighbor.y] = Wall - def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: '''Create a Rect from the given BSP node object''' return Rect(Point(node.x, node.y), Size(node.width, node.height)) diff --git a/erynrl/map/room.py b/erynrl/map/room.py index be78a04..9a2e68d 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -1,13 +1,13 @@ from typing import Iterator -from ..geometry import Point +from ..geometry import Point, Rect class Room: '''An abstract room. It can be any size or shape.''' - def __init__(self, bounds): - self.bounds = bounds + def __init__(self, bounds: Rect): + self.bounds: Rect = bounds @property def center(self) -> Point: @@ -15,13 +15,18 @@ class Room: return self.bounds.midpoint @property - def walls(self) -> Iterator[Point]: - '''An iterator over all the wall tiles of this room.''' + def wall_points(self) -> Iterator[Point]: + '''An iterator over all the points that make up the walls of this room.''' + raise NotImplementedError() + + @property + def floor_points(self) -> Iterator[Point]: + '''An iterator over all the points that make of the floor of this room''' raise NotImplementedError() @property def walkable_tiles(self) -> Iterator[Point]: - '''An iterator over all the walkable tiles in this room.''' + '''An iterator over all the points that are walkable in this room.''' raise NotImplementedError() @@ -43,16 +48,34 @@ class RectangularRoom(Room): yield Point(x, y) @property - def walls(self) -> Iterator[Point]: + def wall_points(self) -> Iterator[Point]: bounds = self.bounds + min_y = bounds.min_y max_y = bounds.max_y min_x = bounds.min_x max_x = bounds.max_x - for y in range(min_y, max_y + 1): - for x in range(min_x, max_x + 1): - if y == min_y or y == max_y or x == min_x or x == max_x: - yield Point(x, y) + + for x in range(min_x, max_x + 1): + yield Point(x, min_y) + yield Point(x, max_y) + + for y in range(min_y + 1, max_y): + yield Point(min_x, y) + yield Point(max_x, y) + + @property + def floor_points(self) -> Iterator[Point]: + inset_bounds = self.bounds.inset_rect(1, 1, 1, 1) + + min_y = inset_bounds.min_y + max_y = inset_bounds.max_y + min_x = inset_bounds.min_x + max_x = inset_bounds.max_x + + for x in range(min_x, max_x + 1): + for y in range(min_y, max_y + 1): + yield Point(x, y) def __repr__(self) -> str: return f'{self.__class__.__name__}({self.bounds})' From 643ab0990bfbcb87fdeb08ca92fa21761319b01f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:10:21 -0800 Subject: [PATCH 141/234] logging_config changes Add erynrl.map to the config. Something reformatted the logging config. --- logging_config.json | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/logging_config.json b/logging_config.json index fe7b5c4..2062252 100644 --- a/logging_config.json +++ b/logging_config.json @@ -17,39 +17,62 @@ "loggers": { "erynrl": { "level": "INFO", - "handlers": ["console"], + "handlers": [ + "console" + ], "propagate": false }, "erynrl.ai": { "level": "ERROR", - "handlers": ["console"], + "handlers": [ + "console" + ], "propagate": false }, "erynrl.actions": { "level": "INFO", - "handlers": ["console"] + "handlers": [ + "console" + ] }, "erynrl.actions.movement": { "level": "ERROR", - "handlers": ["console"] + "handlers": [ + "console" + ] }, "erynrl.actions.tree": { "level": "INFO", - "handlers": ["console"], + "handlers": [ + "console" + ], "propagate": false }, "erynrl.events": { "level": "WARN", - "handlers": ["console"], + "handlers": [ + "console" + ], + "propagate": false + }, + "erynrl.map": { + "level": "DEBUG", + "handlers": [ + "console" + ], "propagate": false }, "erynrl.visible": { "level": "DEBUG", - "handlers": ["console"] + "handlers": [ + "console" + ] } }, "root": { "level": "DEBUG", - "handlers": ["console"] + "handlers": [ + "console" + ] } -} \ No newline at end of file +} From 9d00f3b63828b6e71ef527a8f5a74a84a426d7cf Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:12:12 -0800 Subject: [PATCH 142/234] Promote BSPRoomGenerator.Configuration to RoomGenerator.Configuration This configuration object can actually apply to all room generators. Notably: copy the default configuration object before setting it to self.configuration. --- erynrl/map/generator/room.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index bf069d6..8c1a65a 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -1,4 +1,5 @@ import random +from copy import copy from dataclasses import dataclass from typing import List, Optional @@ -6,7 +7,7 @@ import numpy as np import tcod from ... import log -from ...geometry import Direction, Point, Rect, Size +from ...geometry import Point, Rect, Size from ..room import Room, RectangularRoom from ..tile import Empty, Floor, StairsUp, StairsDown, Wall @@ -14,8 +15,20 @@ from ..tile import Empty, Floor, StairsUp, StairsDown, Wall class RoomGenerator: '''Abstract room generator class.''' - def __init__(self, *, size: Size): + @dataclass + class Configuration: + number_of_rooms: int + minimum_room_size: Size + maximum_room_size: Size + + DefaultConfiguration = Configuration( + number_of_rooms=30, + minimum_room_size=Size(7, 7), + maximum_room_size=Size(20, 20)) + + def __init__(self, *, size: Size, config: Optional[Configuration] = None): self.size = size + self.configuration = config if config else copy(RoomGenerator.DefaultConfiguration) self.rooms: List[Room] = [] self.up_stairs: List[Point] = [] self.down_stairs: List[Point] = [] @@ -102,6 +115,8 @@ class BSPRoomGenerator(RoomGenerator): class BSPRoomGenerator(RoomGenerator): '''Generate a rooms-and-corridors style map with BSP.''' + def __init__(self, *, size: Size, config: Optional[RoomGenerator.Configuration] = None): + super().__init__(size=size, config=config) self.rng: tcod.random.Random = tcod.random.Random() def _generate(self) -> bool: From 85b059dbd48f84a2597db2504f42253129b12f35 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:12:42 -0800 Subject: [PATCH 143/234] Add two new room generators - OneBigRoomGenerator - RandomRectRoomGenerator --- erynrl/map/generator/room.py | 52 +++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 8c1a65a..b137c9e 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -97,15 +97,53 @@ class RoomGenerator: tiles[pt.x, pt.y] = StairsDown -class BSPRoomGenerator(RoomGenerator): - '''Generate a rooms-and-corridors style map with BSP.''' +class OneBigRoomGenerator(RoomGenerator): + '''Generates one big room in the center of the map.''' - @dataclass - class Configuration: - '''Configuration parameters for BSPRoomGenerator.''' + def _generate(self) -> bool: + if self.rooms: + return True - minimum_room_size: Size - maximum_room_size: Size + origin = Point(self.size.width // 4, self.size.height // 4) + size = Size(self.size.width // 2, self.size.height // 2) + room = RectangularRoom(Rect(origin, size)) + + self.rooms.append(room) + + return True + + +class RandomRectRoomGenerator(RoomGenerator): + '''Generate rooms by repeatedly attempting to place rects of random size across the map.''' + + NUMBER_OF_ATTEMPTS_PER_ROOM = 5 + + def _generate(self) -> bool: + number_of_attempts = 0 + + minimum_room_size = self.configuration.minimum_room_size + maximum_room_size = self.configuration.maximum_room_size + + width_range = (minimum_room_size.width, maximum_room_size.width) + height_range = (minimum_room_size.height, maximum_room_size.height) + + while len(self.rooms) < self.configuration.number_of_rooms: + size = Size(random.randint(*width_range), random.randint(*height_range)) + origin = Point(random.randint(0, self.size.width - size.width), + random.randint(0, self.size.height - size.height)) + candidate_room_rect = Rect(origin, size) + + overlaps_any_existing_room = any(candidate_room_rect.intersects(room.bounds) for room in self.rooms) + if not overlaps_any_existing_room: + self.rooms.append(RectangularRoom(candidate_room_rect)) + number_of_attempts = 0 + continue + + number_of_attempts += 1 + if number_of_attempts > RandomRectRoomGenerator.NUMBER_OF_ATTEMPTS_PER_ROOM: + break + + return True DefaultConfiguration = Configuration( minimum_room_size=Size(7, 7), From 0d0c5a2b359aed15b6870c98187b5c462a86bc89 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:13:16 -0800 Subject: [PATCH 144/234] Remove this unused DefaultConfiguration class variable --- erynrl/map/generator/room.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index b137c9e..7cf7330 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -145,10 +145,6 @@ class RandomRectRoomGenerator(RoomGenerator): return True - DefaultConfiguration = Configuration( - minimum_room_size=Size(7, 7), - maximum_room_size=Size(20, 20), - ) class BSPRoomGenerator(RoomGenerator): '''Generate a rooms-and-corridors style map with BSP.''' From 7b0b7ff5b65195cea523375ebe5bbf9496d47ddd Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:13:31 -0800 Subject: [PATCH 145/234] Add Rect.intersects --- erynrl/geometry.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 37ad50c..69fa622 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -164,6 +164,22 @@ class Rect: '''A Point in the middle of the Rect''' return Point(self.mid_x, self.mid_y) + def intersects(self, other: 'Rect') -> bool: + '''Returns `True` if `other` intersects this Rect.''' + if other.min_x > self.max_x: + return False + + if other.max_x < self.min_x: + return False + + if other.min_y > self.max_y: + return False + + if other.max_y < self.min_y: + return False + + return True + def inset_rect(self, top: int = 0, right: int = 0, bottom: int = 0, left: int = 0) -> 'Rect': ''' Return a new Rect inset from this rect by the specified values. Arguments are listed in clockwise order around From 727a0737c6ac723907f8c6154a48def1799f0bc0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:13:49 -0800 Subject: [PATCH 146/234] Use integer division for Rect.mid_y and Rect.mid_x --- erynrl/geometry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 69fa622..d5abce6 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -142,12 +142,12 @@ class Rect: @property def mid_x(self) -> int: '''The x-value of the center point of this rectangle.''' - return int(self.origin.x + self.size.width / 2) + return self.origin.x + self.size.width // 2 @property def mid_y(self) -> int: '''The y-value of the center point of this rectangle.''' - return int(self.origin.y + self.size.height / 2) + return self.origin.y + self.size.height // 2 @property def max_x(self) -> int: From f05dfdef559b918df24c81049ce47becde80fbf9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 21:25:00 -0800 Subject: [PATCH 147/234] PEP8 formatter changes --- erynrl/__main__.py | 6 ++++++ erynrl/ai.py | 6 +++++- erynrl/components.py | 1 + erynrl/events.py | 3 +++ erynrl/map/__init__.py | 5 +++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 74211ab..aab55ea 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -13,17 +13,20 @@ MAP_WIDTH, MAP_HEIGHT = 80, 45 FONT_CP437 = 'terminal16x16_gs_ro.png' FONT_BDF = 'ter-u32n.bdf' + def parse_args(argv, *a, **kw): parser = argparse.ArgumentParser(*a, **kw) parser.add_argument('--debug', action='store_true', default=True) args = parser.parse_args(argv) return args + def walk_up_directories_of_path(path): while path and path != '/': path = os.path.dirname(path) yield path + def find_fonts_directory(): '''Walk up the filesystem tree from this script to find a fonts/ directory.''' for parent_dir in walk_up_directories_of_path(__file__): @@ -36,6 +39,7 @@ def find_fonts_directory(): return possible_fonts_dir + def main(argv): ''' Beginning of the game @@ -68,6 +72,7 @@ def main(argv): with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: engine.run_event_loop(context, console) + def run_until_exit(): ''' Run main() and call sys.exit when it finishes. In practice, this function will never return. The game engine has @@ -76,4 +81,5 @@ def run_until_exit(): result = main(sys.argv) sys.exit(0 if not result else result) + run_until_exit() diff --git a/erynrl/ai.py b/erynrl/ai.py index 867b236..710b2b1 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -17,6 +17,8 @@ if TYPE_CHECKING: from .engine import Engine # pylint: disable=too-few-public-methods + + class AI(Component): '''An abstract class providing AI for an entity.''' @@ -28,6 +30,7 @@ class AI(Component): '''Produce an action to perform''' raise NotImplementedError() + class HostileEnemy(AI): '''Entity AI for a hostile enemy. @@ -60,7 +63,8 @@ class HostileEnemy(AI): direction_to_next_position = entity_position.direction_to_adjacent_point(next_position) if engine.map.visible[tuple(self.entity.position)]: - log.AI.info('`-> Hero is visible to %s, bumping %s (%s)', self.entity, direction_to_next_position, next_position) + log.AI.info('`-> Hero is visible to %s, bumping %s (%s)', + self.entity, direction_to_next_position, next_position) return BumpAction(self.entity, direction_to_next_position) else: diff --git a/erynrl/components.py b/erynrl/components.py index 3e7a291..b8810ed 100644 --- a/erynrl/components.py +++ b/erynrl/components.py @@ -7,6 +7,7 @@ from typing import Optional class Component: '''A base, abstract Component that implement some aspect of an Entity's behavior.''' + class Fighter(Component): '''A Fighter is an Entity that can fight. That is, it has hit points (health), attack, and defense. diff --git a/erynrl/events.py b/erynrl/events.py index 9d9a69f..fd3577f 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -14,6 +14,7 @@ from .geometry import Direction, Point if TYPE_CHECKING: from .engine import Engine + class EventHandler(tcod.event.EventDispatch[Action]): '''Abstract event handler class''' @@ -49,6 +50,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: return ExitAction() + class MainGameEventHandler(EventHandler): ''' Handler of `tcod` events for the main game. @@ -95,6 +97,7 @@ class MainGameEventHandler(EventHandler): mouse_point = None self.engine.current_mouse_point = mouse_point + class GameOverEventHandler(EventHandler): '''When the game is over (the hero dies, the player quits, etc), this event handler takes over.''' diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 075a915..9d8c945 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -1,5 +1,10 @@ # Eryn Wells +''' +This module defines the level map, a number of basic building blocks (Rooms, etc), and objects that generate various +parts of a map. +''' + import random import numpy as np From 6c9d01771f2679bb1b981f8e6f3e6dc659fe0718 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 22:35:51 -0800 Subject: [PATCH 148/234] Draw a path from the hero to the current mouse point Add a highlight grid to Map, with locations set to True if that point should be drawn with a "highlight" treatment. Add the highlight graphic_dtype to all Tiles. --- erynrl/engine.py | 45 +++++++++++++++++++++++++++++++++++++----- erynrl/events.py | 4 ++-- erynrl/map/__init__.py | 20 +++++++++++++------ erynrl/map/tile.py | 40 ++++++++++++++++++++++--------------- 4 files changed, 80 insertions(+), 29 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 0c1a595..23ccbec 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -68,7 +68,9 @@ class Engine: self.map = Map(map_size, map_generator) self.event_handler: 'EventHandler' = MainGameEventHandler(self) - self.current_mouse_point: Optional[Point] = None + + self.__current_mouse_point: Optional[Point] = None + self.__mouse_path_points: Optional[List[Point]] = None self.entities: MutableSet[Entity] = set() @@ -103,6 +105,8 @@ class Engine: def print_to_console(self, console): '''Print the whole game to the given console.''' + self.map.highlight_points(self.__mouse_path_points or []) + self.map.print_to_console(console) console.print(x=1, y=45, string='HP:') @@ -124,7 +128,7 @@ class Engine: ent.print_to_console(console) - if ent.position == self.current_mouse_point: + if ent.position == self.__current_mouse_point: entities_at_mouse_position.append(ent) if len(entities_at_mouse_position) > 0: @@ -141,12 +145,15 @@ class Engine: self.event_handler.handle_events(context) self.finish_turn() - def process_input_action(self, action: Action) -> ActionResult: + def process_input_action(self, action: Action): '''Process an Action from player input''' log.ACTIONS_TREE.info('Processing Hero Actions') log.ACTIONS_TREE.info('|-> %s', action.actor) + # Clear the mouse path highlight before handling actions. + self.__mouse_path_points = None + result = self._perform_action_until_done(action) # Player's action failed, don't proceed with turn. @@ -225,7 +232,7 @@ class Engine: return result - def update_field_of_view(self) -> None: + def update_field_of_view(self): '''Compute visible area of the map based on the player's position and point of view.''' # FIXME: Move this to the Map class self.map.visible[:] = tcod.map.compute_fov( @@ -233,9 +240,37 @@ class Engine: tuple(self.hero.position), radius=8) - # Visible tiles should be added to the explored list + # Add visible tiles to the explored grid self.map.explored |= self.map.visible + def update_mouse_point(self, mouse_point: Optional[Point]): + if mouse_point == self.__current_mouse_point: + return + + should_render_mouse_path = ( + mouse_point + and self.map.tile_is_in_bounds(mouse_point) + and self.map.tile_is_walkable(mouse_point)) + + if not should_render_mouse_path: + self.__current_mouse_point = None + self.__mouse_path_points = None + return + + self.__current_mouse_point = mouse_point + + path_from_hero_to_mouse_point = tcod.los.bresenham(tuple(self.hero.position), tuple(self.__current_mouse_point)) + mouse_path_points = [Point(x, y) for x, y in path_from_hero_to_mouse_point.tolist()] + + all_mouse_path_points_are_walkable = all( + self.map.tile_is_walkable(pt) and self.map.point_is_explored(pt) for pt in mouse_path_points) + if not all_mouse_path_points_are_walkable: + self.__current_mouse_point = None + self.__mouse_path_points = None + return + + self.__mouse_path_points = mouse_path_points + def begin_turn(self) -> None: '''Begin the current turn''' if self.did_begin_turn: diff --git a/erynrl/events.py b/erynrl/events.py index fd3577f..dc0d5ac 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -28,7 +28,7 @@ class EventHandler(tcod.event.EventDispatch[Action]): context.convert_event(event) self.handle_event(event) - def handle_event(self, event: tcod.event.Event) -> None: + def handle_event(self, event: tcod.event.Event): ''' Handle an event by transforming it into an Action and processing it until it is completed. If the Action succeeds, also process actions from other Entities. @@ -95,7 +95,7 @@ class MainGameEventHandler(EventHandler): mouse_point = Point(event.tile.x, event.tile.y) if not self.engine.map.tile_is_in_bounds(mouse_point): mouse_point = None - self.engine.current_mouse_point = mouse_point + self.engine.update_mouse_point(mouse_point) class GameOverEventHandler(EventHandler): diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 9d8c945..d19797f 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -6,6 +6,7 @@ parts of a map. ''' import random +from typing import Iterable import numpy as np import numpy.typing as npt @@ -20,17 +21,17 @@ class Map: def __init__(self, size: Size, generator: MapGenerator): self.size = size - self.generator = generator self.tiles = np.full(tuple(size), fill_value=Empty, order='F') generator.generate(self.tiles) self.up_stairs = generator.up_stairs self.down_stairs = generator.down_stairs + self.highlighted = np.full(tuple(self.size), fill_value=False, order='F') # Map tiles that are currently visible to the player - self.visible = np.full(tuple(self.size), fill_value=True, order='F') + self.visible = np.full(tuple(self.size), fill_value=False, order='F') # Map tiles that the player has explored - self.explored = np.full(tuple(self.size), fill_value=True, order='F') + self.explored = np.full(tuple(self.size), fill_value=False, order='F') self.__walkable_points = None @@ -47,7 +48,14 @@ class Map: def tile_is_walkable(self, point: Point) -> bool: '''Return True if the tile at the given point is walkable''' - return self.tiles[point.x, point.y]['walkable'] + return self.tile_is_in_bounds(point) and self.tiles[point.x, point.y]['walkable'] + + def highlight_points(self, points: Iterable[Point]): + '''Update the highlight graph with the list of points to highlight.''' + self.highlighted.fill(False) + + for pt in points if points: + self.highlighted[pt.x, pt.y] = True def print_to_console(self, console: tcod.Console) -> None: '''Render the map to the console.''' @@ -56,6 +64,6 @@ class Map: # If a tile is in the visible array, draw it with the "light" color. If it's not, but it's in the explored # array, draw it with the "dark" color. Otherwise, draw it as Empty. console.tiles_rgb[0:size.width, 0:size.height] = np.select( - condlist=[self.visible, self.explored], - choicelist=[self.tiles['light'], self.tiles['dark']], + condlist=[self.highlighted, self.visible, self.explored], + choicelist=[self.tiles['highlighted'], self.tiles['light'], self.tiles['dark']], default=Shroud) diff --git a/erynrl/map/tile.py b/erynrl/map/tile.py index 23c3eb7..d869920 100644 --- a/erynrl/map/tile.py +++ b/erynrl/map/tile.py @@ -6,9 +6,9 @@ graphic_datatype = np.dtype([ # Character, a Unicode codepoint represented as an int32 ('ch', np.int32), # Foreground color, three bytes - ('fg', '3B'), + ('fg', '4B'), # Background color, three bytes - ('bg', '3B'), + ('bg', '4B'), ]) tile_datatype = np.dtype([ @@ -20,37 +20,45 @@ tile_datatype = np.dtype([ ('dark', graphic_datatype), # A graphic struct (as above) defining the look of this tile when it's visible ('light', graphic_datatype), + # A graphic struct (as above) defining the look of this tile when it's highlighted + ('highlighted', graphic_datatype), ]) def tile(*, walkable: int, transparent: int, - dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]], - light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]]) -> np.ndarray: - return np.array((walkable, transparent, dark, light), dtype=tile_datatype) + dark: Tuple[int, Tuple[int, int, int, int], Tuple[int, int, int, int]], + light: Tuple[int, Tuple[int, int, int, int], Tuple[int, int, int, int]], + highlighted: Tuple[int, Tuple[int, int, int, int], Tuple[int, int, int, int]]) -> np.ndarray: + return np.array((walkable, transparent, dark, light, highlighted), dtype=tile_datatype) # An overlay color for tiles that are not visible and have not been explored -Shroud = np.array((ord(' '), (255, 255, 255), (0, 0, 0)), dtype=graphic_datatype) +Shroud = np.array((ord(' '), (255, 255, 255, 255), (0, 0, 0, 0)), dtype=graphic_datatype) Empty = tile( walkable=False, transparent=False, - dark=(ord(' '), (255, 255, 255), (0, 0, 0)), - light=(ord(' '), (255, 255, 255), (0, 0, 0))) + dark=(ord('#'), (20, 20, 20, 255), (0, 0, 0, 0)), + light=(ord('#'), (20, 20, 20, 255), (0, 0, 0, 0)), + highlighted=(ord('#'), (20, 20, 20, 255), (30, 30, 30, 255))) Floor = tile( walkable=True, transparent=True, - dark=(ord('·'), (80, 80, 100), (50, 50, 50)), - light=(ord('·'), (100, 100, 120), (80, 80, 100))) + dark=(ord('·'), (80, 80, 100, 255), (50, 50, 50, 255)), + light=(ord('·'), (100, 100, 120, 255), (80, 80, 100, 255)), + highlighted=(ord('·'), (100, 100, 120, 255), (80, 80, 150, 255))) StairsUp = tile( walkable=True, transparent=True, - dark=(ord('<'), (80, 80, 100), (50, 50, 50)), - light=(ord('<'), (100, 100, 120), (80, 80, 100))) + dark=(ord('<'), (80, 80, 100, 255), (50, 50, 50, 255)), + light=(ord('<'), (100, 100, 120, 255), (80, 80, 100, 255)), + highlighted=(ord('<'), (100, 100, 120, 255), (80, 80, 150, 255))) StairsDown = tile( walkable=True, transparent=True, - dark=(ord('>'), (80, 80, 100), (50, 50, 50)), - light=(ord('>'), (100, 100, 120), (80, 80, 100))) + dark=(ord('>'), (80, 80, 100, 255), (50, 50, 50, 255)), + light=(ord('>'), (100, 100, 120, 255), (80, 80, 100, 255)), + highlighted=(ord('>'), (100, 100, 120, 255), (80, 80, 150, 255))) Wall = tile( walkable=False, transparent=False, - dark=(ord(' '), (255, 255, 255), (0, 0, 150)), - light=(ord(' '), (255, 255, 255), (50, 50, 200))) + dark=(ord('#'), (80, 80, 80, 255), (0, 0, 0, 255)), + light=(ord('#'), (100, 100, 100, 255), (20, 20, 20, 255)), + highlighted=(ord('#'), (100, 100, 100, 255), (20, 20, 20, 255))) From 2f9864edd825812426c418b1d5db75323cb54633 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 22:36:10 -0800 Subject: [PATCH 149/234] Start the hero on the down stairs of the level --- erynrl/engine.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 23ccbec..26d3d0c 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -74,7 +74,12 @@ class Engine: self.entities: MutableSet[Entity] = set() - self.hero = Hero(position=self.map.random_walkable_position()) + try: + hero_start_position = self.map.up_stairs[0] + except IndexError: + hero_start_position = self.map.random_walkable_position() + self.hero = Hero(position=hero_start_position) + self.entities.add(self.hero) while len(self.entities) < 25: From df4df06013263c483b430152a8cf52c16fb3add4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Feb 2023 22:36:27 -0800 Subject: [PATCH 150/234] Fix this import in ai.py --- erynrl/ai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/ai.py b/erynrl/ai.py index 710b2b1..05c761e 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -7,7 +7,7 @@ import numpy as np import tcod from . import log -from .actions import Action +from .actions.action import Action from .actions.game import BumpAction, WaitAction from .components import Component from .geometry import Direction, Point From 6780b0495c7a14a6ca658209304677154ad4ed48 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Feb 2023 01:21:52 -0800 Subject: [PATCH 151/234] Move all the interface stuff to interface.Interface Draw three windows with frames: - map window - info window (hit point bar; turn count) - message window Clean up the UI code in the Engine. --- erynrl/engine.py | 22 +++------- erynrl/interface/__init__.py | 78 ++++++++++++++++++++++++++++++++++++ erynrl/interface/window.py | 25 ++++++++++++ erynrl/map/__init__.py | 4 +- erynrl/messages.py | 6 ++- 5 files changed, 114 insertions(+), 21 deletions(-) create mode 100644 erynrl/interface/window.py diff --git a/erynrl/engine.py b/erynrl/engine.py index 26d3d0c..6779aef 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -4,7 +4,7 @@ import random from dataclasses import dataclass -from typing import TYPE_CHECKING, MutableSet, NoReturn, Optional +from typing import TYPE_CHECKING, List, MutableSet, NoReturn, Optional import tcod @@ -15,8 +15,7 @@ from .actions.result import ActionResult from .ai import HostileEnemy from .events import GameOverEventHandler, MainGameEventHandler from .geometry import Point, Rect, Size -from .interface import color -from .interface.percentage_bar import PercentageBar +from .interface import Interface from .map import Map from .map.generator import RoomsAndCorridorsGenerator from .map.generator.room import BSPRoomGenerator @@ -104,26 +103,15 @@ class Engine: self.update_field_of_view() # Interface elements - self.hit_points_bar = PercentageBar(position=Point(4, 45), width=20, colors=list(color.HealthBar.bar_colors())) - + self.interface = Interface(Size(80, 50), self.map, self.message_log) self.message_log.add_message('Greetings adventurer!', fg=(127, 127, 255), stack=False) def print_to_console(self, console): '''Print the whole game to the given console.''' self.map.highlight_points(self.__mouse_path_points or []) - self.map.print_to_console(console) - - console.print(x=1, y=45, string='HP:') - hp, max_hp = self.hero.fighter.hit_points, self.hero.fighter.maximum_hit_points - self.hit_points_bar.percent_filled = hp / max_hp - self.hit_points_bar.render_to_console(console) - console.print(x=6, y=45, string=f'{hp}/{max_hp}', fg=color.WHITE) - - console.print(x=1, y=46, string=f'Turn: {self.current_turn}') - - messages_rect = Rect(Point(x=27, y=45), Size(width=40, height=5)) - self.message_log.render_to_console(console, messages_rect) + self.interface.update(self.hero, self.current_turn) + self.interface.draw(console) entities_at_mouse_position = [] for ent in sorted(self.entities, key=lambda e: e.render_order.value): diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 12ba5c3..7df7ba4 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -1 +1,79 @@ # Eryn Wells + +from typing import Optional + +from tcod.console import Console + +from .color import HealthBar +from .percentage_bar import PercentageBar +from .window import Window +from ..geometry import Point, Rect, Size +from ..map import Map +from ..messages import MessageLog +from ..object import Hero + + +class Interface: + def __init__(self, size: Size, map: Map, message_log: MessageLog): + self.map_window = MapWindow(Rect(Point(0, 0), Size(size.width, size.height - 5)), map) + self.info_window = InfoWindow(Rect(Point(0, size.height - 5), Size(28, 5))) + self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log) + + def update(self, hero: Hero, turn_count: int): + self.info_window.turn_count = turn_count + self.info_window.update_hero(hero) + + def draw(self, console: Console): + self.map_window.draw(console) + self.info_window.draw(console) + self.message_window.draw(console) + + +class MapWindow(Window): + def __init__(self, bounds: Rect, map: Map): + super().__init__(bounds) + self.map = map + + def draw(self, console): + super().draw(console) + + # TODO: Get a 2D slice of tiles from the map given a rect based on the window's drawable area + drawable_area = self.drawable_area + self.map.print_to_console(console, drawable_area) + + +class InfoWindow(Window): + def __init__(self, bounds: Rect): + super().__init__(bounds, framed=True) + + self.turn_count: int = 0 + + drawable_area = self.drawable_area + self.hit_points_bar = PercentageBar( + position=Point(drawable_area.min_x + 6, drawable_area.min_y), + width=20, + colors=list(HealthBar.bar_colors())) + + def update_hero(self, hero: Hero): + hp, max_hp = hero.fighter.hit_points, hero.fighter.maximum_hit_points + self.hit_points_bar.percent_filled = hp / max_hp + + def draw(self, console): + super().draw(console) + + drawable_area = self.drawable_area + console.print(x=drawable_area.min_x + 2, y=drawable_area.min_y, string='HP:') + self.hit_points_bar.render_to_console(console) + + if self.turn_count: + console.print(x=drawable_area.min_x, y=drawable_area.min_y + 1, string=f'Turn: {self.turn_count}') + + +class MessageLogWindow(Window): + def __init__(self, bounds: Rect, message_log: MessageLog): + super().__init__(bounds, framed=True) + self.message_log = message_log + + def draw(self, console): + super().draw(console) + self.message_log.render_to_console(console, self.drawable_area) diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py new file mode 100644 index 0000000..0926c3b --- /dev/null +++ b/erynrl/interface/window.py @@ -0,0 +1,25 @@ +# Eryn Wells + +from tcod.console import Console + +from ..geometry import Rect + + +class Window: + def __init__(self, bounds: Rect, *, framed: bool = True): + self.bounds = bounds + self.is_framed = framed + + @property + def drawable_area(self) -> Rect: + if self.is_framed: + return self.bounds.inset_rect(1, 1, 1, 1) + return self.bounds + + def draw(self, console: Console): + if self.is_framed: + console.draw_frame( + self.bounds.origin.x, + self.bounds.origin.y, + self.bounds.size.width, + self.bounds.size.height) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index d19797f..4f19d84 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -12,7 +12,7 @@ import numpy as np import numpy.typing as npt import tcod -from ..geometry import Point, Size +from ..geometry import Point, Rect, Size from .generator import MapGenerator from .tile import Empty, Shroud @@ -57,7 +57,7 @@ class Map: for pt in points if points: self.highlighted[pt.x, pt.y] = True - def print_to_console(self, console: tcod.Console) -> None: + def print_to_console(self, console: tcod.Console, bounds: Rect) -> None: '''Render the map to the console.''' size = self.size diff --git a/erynrl/messages.py b/erynrl/messages.py index 3606462..56179af 100644 --- a/erynrl/messages.py +++ b/erynrl/messages.py @@ -13,6 +13,7 @@ import tcod from .geometry import Rect + class Message: '''A message in the message log @@ -44,6 +45,7 @@ class Message: def __repr__(self) -> str: return f'{self.__class__.__name__}({repr(self.text)}, fg={self.foreground})' + class MessageLog: '''A buffer of messages sent to the player by the game''' @@ -76,12 +78,12 @@ class MessageLog: @staticmethod def render_messages(console: tcod.console.Console, rect: Rect, messages: Reversible[Message]): '''Render a list of messages to the console in the given rect''' - y_offset = min(rect.size.height, len(messages)) + y_offset = min(rect.size.height, len(messages)) - 1 for message in reversed(messages): wrapped_text = textwrap.wrap(message.full_text, rect.size.width) for line in wrapped_text: - console.print(x=rect.min_x, y=rect.min_y + y_offset - 1, string=line, fg=message.foreground) + console.print(x=rect.min_x, y=rect.min_y + y_offset, string=line, fg=message.foreground) y_offset -= 1 if y_offset < 0: From 06ae79ccd0ba480c21d7e9e479f78be0fbf24f51 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:24:36 -0800 Subject: [PATCH 152/234] Redo the configuration metchanism - Allow passing a font on the command line via --font - Move the engine configuration to its own module - Redo entirely the font configuration: move it to the configuration module - Pass the configuration object to the Map in place of the size argument --- erynrl/__main__.py | 61 ++++++-------- erynrl/configuration.py | 175 ++++++++++++++++++++++++++++++++++++++++ erynrl/engine.py | 19 ++--- erynrl/map/__init__.py | 22 +++-- 4 files changed, 220 insertions(+), 57 deletions(-) create mode 100644 erynrl/configuration.py diff --git a/erynrl/__main__.py b/erynrl/__main__.py index aab55ea..19d8985 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -1,45 +1,24 @@ # Eryn Wells +'''Main module''' + import argparse -import os.path import sys import tcod from . import log -from .engine import Configuration, Engine -from .geometry import Size - -CONSOLE_WIDTH, CONSOLE_HEIGHT = 80, 50 -MAP_WIDTH, MAP_HEIGHT = 80, 45 -FONT_CP437 = 'terminal16x16_gs_ro.png' -FONT_BDF = 'ter-u32n.bdf' +from .configuration import Configuration, FontConfiguration, FontConfigurationError, MAP_SIZE, CONSOLE_SIZE +from .engine import Engine def parse_args(argv, *a, **kw): parser = argparse.ArgumentParser(*a, **kw) parser.add_argument('--debug', action='store_true', default=True) + parser.add_argument('--font') + parser.add_argument('--sandbox', action='store_true', default=False) args = parser.parse_args(argv) return args -def walk_up_directories_of_path(path): - while path and path != '/': - path = os.path.dirname(path) - yield path - - -def find_fonts_directory(): - '''Walk up the filesystem tree from this script to find a fonts/ directory.''' - for parent_dir in walk_up_directories_of_path(__file__): - possible_fonts_dir = os.path.join(parent_dir, 'fonts') - if os.path.isdir(possible_fonts_dir): - log.ROOT.info('Found fonts dir %s', possible_fonts_dir) - break - else: - return None - - return possible_fonts_dir - - def main(argv): ''' Beginning of the game @@ -53,25 +32,31 @@ def main(argv): log.init() - fonts_directory = find_fonts_directory() - if not fonts_directory: - log.ROOT.error("Couldn't find a fonts/ directory") + try: + font = args.font + if font: + font_config = FontConfiguration.with_filename(font) + else: + font_config = FontConfiguration.default_configuration() + except FontConfigurationError as error: + log.ROOT.error('Unable to create a default font configuration: %s', error) return -1 - font = os.path.join(fonts_directory, FONT_BDF) - if not os.path.isfile(font): - log.ROOT.error("Font file %s doesn't exist", font) - return -1 + configuration = Configuration( + console_size=CONSOLE_SIZE, + console_font_config=font_config, + map_size=MAP_SIZE, + sandbox=args.sandbox) - tileset = tcod.tileset.load_bdf(font) - console = tcod.Console(CONSOLE_WIDTH, CONSOLE_HEIGHT, order='F') - - configuration = Configuration(map_size=Size(MAP_WIDTH, MAP_HEIGHT)) engine = Engine(configuration) + tileset = configuration.console_font_config.tileset + console = tcod.Console(*configuration.console_size.numpy_shape, order='F') with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: engine.run_event_loop(context, console) + return 0 + def run_until_exit(): ''' diff --git a/erynrl/configuration.py b/erynrl/configuration.py new file mode 100644 index 0000000..384aa93 --- /dev/null +++ b/erynrl/configuration.py @@ -0,0 +1,175 @@ +# Eryn Wells + +''' +Game configuration parameters. +''' + +import os.path as osp +import re +from dataclasses import dataclass +from enum import Enum +from os import PathLike +from typing import Iterable + +import tcod.tileset + +from . import log +from .geometry import Size + + +CONSOLE_SIZE = Size(80, 50) +MAP_SIZE = Size(80, 45) +FONT_CP437 = 'terminal16x16_gs_ro.png' +FONT_BDF = 'ter-u32n.bdf' + + +class FontConfigurationError(Exception): + '''Invalid font configration based on available parameters''' + + +@dataclass +class FontConfiguration: + '''Configuration of the font to use for rendering the game''' + + filename: str | PathLike[str] + + @staticmethod + def __find_fonts_directory(): + '''Walk up the filesystem tree from this file to find a `fonts` directory.''' + + def walk_up_directories_of_path(path): + while path and path != '/': + path = osp.dirname(path) + yield path + + for parent_dir in walk_up_directories_of_path(__file__): + possible_fonts_dir = osp.join(parent_dir, 'fonts') + if osp.isdir(possible_fonts_dir): + log.ROOT.info('Found fonts dir %s', possible_fonts_dir) + break + else: + return None + + return possible_fonts_dir + + @staticmethod + def default_configuration(): + '''Return a default configuration: a tilesheet font configuration using `fonts/terminal16x16_gs_ro.png`.''' + + fonts_directory = FontConfiguration.__find_fonts_directory() + if not fonts_directory: + message = "Couldn't find a fonts directory" + log.ROOT.error('%s', message) + raise FontConfigurationError(message) + + font = osp.join(fonts_directory, 'terminal16x16_gs_ro.png') + if not osp.isfile(font): + message = f"Font file {font} doesn't exist" + log.ROOT.error("%s", message) + raise FontConfigurationError(message) + + return FontConfiguration.with_filename(font) + + @staticmethod + def with_filename(filename: str | PathLike[str]) -> 'FontConfiguration': + '''Return a FontConfig subclass based on the path to the filename''' + _, extension = osp.splitext(filename) + + match extension: + case ".bdf": + return BDFFontConfiguration(filename) + case ".ttf": + return TTFFontConfiguration(filename) + case ".png": + # Attempt to find the tilesheet dimensions in the filename. + try: + match = re.match(r'^.*\(\d+\)x\(\d+\).*$', extension) + if not match: + return TilesheetFontConfiguration(filename) + + rows, columns = int(match.group(1)), int(match.group(2)) + return TilesheetFontConfiguration( + filename=filename, + dimensions=Size(columns, rows)) + except ValueError: + return TilesheetFontConfiguration(filename) + case _: + raise FontConfigurationError(f'Unable to determine font configuration from {filename}') + + @property + def tileset(self) -> tcod.tileset.Tileset: + '''Returns a tcod tileset based on the parameters of this font config''' + raise NotImplementedError() + + +@dataclass +class BDFFontConfiguration(FontConfiguration): + '''A font configuration based on a BDF file.''' + + @property + def tileset(self) -> tcod.tileset.Tileset: + return tcod.tileset.load_bdf(self.filename) + + +@dataclass +class TTFFontConfiguration(FontConfiguration): + ''' + A font configuration based on a TTF file. Since TTFs are variable width, a fixed tile size needs to be specified. + ''' + + tile_size: Size = Size(16, 16) + + @property + def tileset(self) -> tcod.tileset.Tileset: + return tcod.tileset.load_truetype_font(self.filename, *self.tile_size) + + +@dataclass +class TilesheetFontConfiguration(FontConfiguration): + ''' + Configuration for tilesheets. Unlike other font configurations, tilesheets must have their dimensions specified as + the number of sprites per row and number of rows. + ''' + + class Layout(Enum): + '''The layout of the tilesheet''' + CP437 = 1 + TCOD = 2 + + dimensions: Size = Size(16, 16) + layout: Layout | Iterable[int] = Layout.CP437 + + @property + def tilesheet(self) -> Iterable[int]: + '''A tilesheet mapping for the given layout''' + if not self.layout: + return tcod.tileset.CHARMAP_CP437 + + if isinstance(self.layout, Iterable): + return self.layout + + match self.layout: + case TilesheetFontConfiguration.Layout.CP437: + return tcod.tileset.CHARMAP_CP437 + case TilesheetFontConfiguration.Layout.TCOD: + return tcod.tileset.CHARMAP_TCOD + + @property + def tileset(self) -> tcod.tileset.Tileset: + '''A tcod tileset with the given parameters''' + return tcod.tileset.load_tilesheet( + self.filename, + self.dimensions.width, + self.dimensions.height, + self.tilesheet) + + +@dataclass +class Configuration: + '''Configuration of the game engine''' + console_size: Size + console_font_config: FontConfiguration + + map_size: Size + + sandbox: bool = False diff --git a/erynrl/engine.py b/erynrl/engine.py index 6779aef..bf18c53 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -3,7 +3,6 @@ '''Defines the core game engine.''' import random -from dataclasses import dataclass from typing import TYPE_CHECKING, List, MutableSet, NoReturn, Optional import tcod @@ -13,8 +12,9 @@ from . import monsters from .actions.action import Action from .actions.result import ActionResult from .ai import HostileEnemy +from .configuration import Configuration from .events import GameOverEventHandler, MainGameEventHandler -from .geometry import Point, Rect, Size +from .geometry import Point, Size from .interface import Interface from .map import Map from .map.generator import RoomsAndCorridorsGenerator @@ -27,12 +27,6 @@ if TYPE_CHECKING: from .events import EventHandler -@dataclass -class Configuration: - '''Configuration of the game engine''' - map_size: Size - - class Engine: '''The main game engine. @@ -52,8 +46,8 @@ class Engine: A random number generator ''' - def __init__(self, configuration: Configuration): - self.configuration = configuration + def __init__(self, config: Configuration): + self.configuration = config self.current_turn = 1 self.did_begin_turn = False @@ -62,9 +56,8 @@ class Engine: self.rng = tcod.random.Random() self.message_log = MessageLog() - map_size = configuration.map_size - map_generator = RoomsAndCorridorsGenerator(BSPRoomGenerator(size=map_size), ElbowCorridorGenerator()) - self.map = Map(map_size, map_generator) + map_size = config.map_size + self.map = Map(config, map_generator) self.event_handler: 'EventHandler' = MainGameEventHandler(self) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 4f19d84..c98862b 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -12,29 +12,39 @@ import numpy as np import numpy.typing as npt import tcod +from ..engine import Configuration from ..geometry import Point, Rect, Size from .generator import MapGenerator from .tile import Empty, Shroud class Map: - def __init__(self, size: Size, generator: MapGenerator): - self.size = size + def __init__(self, config: Configuration, generator: MapGenerator): + self.configuration = config - self.tiles = np.full(tuple(size), fill_value=Empty, order='F') + map_size = config.map_size + shape = tuple(map_size) + + self.tiles = np.full(shape, fill_value=Empty, order='F') generator.generate(self.tiles) self.up_stairs = generator.up_stairs self.down_stairs = generator.down_stairs - self.highlighted = np.full(tuple(self.size), fill_value=False, order='F') + self.highlighted = np.full(shape, fill_value=False, order='F') # Map tiles that are currently visible to the player - self.visible = np.full(tuple(self.size), fill_value=False, order='F') + self.visible = np.full(shape, fill_value=False, order='F') # Map tiles that the player has explored - self.explored = np.full(tuple(self.size), fill_value=False, order='F') + should_mark_all_tiles_explored = config.sandbox + self.explored = np.full(shape, fill_value=should_mark_all_tiles_explored, order='F') self.__walkable_points = None + @property + def size(self) -> Size: + '''The size of the map''' + return self.configuration.map_size + def random_walkable_position(self) -> Point: '''Return a random walkable point on the map.''' if not self.__walkable_points: From 84f7bdb947f58656a7a0238a997d0413e89d066b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:24:56 -0800 Subject: [PATCH 153/234] Little grammar fix in RenderOrder's doc comment --- erynrl/object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/object.py b/erynrl/object.py index 286fb0f..4d3c813 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: class RenderOrder(Enum): ''' These values indicate the order that an Entity should be rendered. Higher values are rendered later and therefore on - top of items at with lower orderings. + top of items with lower orderings. ''' ITEM = 1000 ACTOR = 2000 From cf31bcc27244baa167c7f56adbfb6f5ca13286fb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:28:14 -0800 Subject: [PATCH 154/234] Create a more semantic Size.numpy_shape property --- erynrl/geometry.py | 7 ++++++- erynrl/map/__init__.py | 9 +++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index d5abce6..623ad3f 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -4,7 +4,7 @@ import math from dataclasses import dataclass -from typing import Any, Iterator, Optional, overload +from typing import Any, Iterator, Optional, overload, Tuple @dataclass(frozen=True) @@ -114,6 +114,11 @@ class Size: width: int = 0 height: int = 0 + @property + def numpy_shape(self) -> Tuple[int, int]: + '''Return a tuple suitable for passing into numpy array initializers for specifying the shape of the array.''' + return (self.width, self.height) + def __iter__(self): yield self.width yield self.height diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index c98862b..fc0f1e0 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -19,11 +19,13 @@ from .tile import Empty, Shroud class Map: + '''A level map''' + def __init__(self, config: Configuration, generator: MapGenerator): self.configuration = config map_size = config.map_size - shape = tuple(map_size) + shape = map_size.numpy_shape self.tiles = np.full(shape, fill_value=Empty, order='F') generator.generate(self.tiles) @@ -60,11 +62,14 @@ class Map: '''Return True if the tile at the given point is walkable''' return self.tile_is_in_bounds(point) and self.tiles[point.x, point.y]['walkable'] + def point_is_explored(self, point: Point) -> bool: + return self.tile_is_in_bounds(point) and self.explored[point.x, point.y] + def highlight_points(self, points: Iterable[Point]): '''Update the highlight graph with the list of points to highlight.''' self.highlighted.fill(False) - for pt in points if points: + for pt in points: self.highlighted[pt.x, pt.y] = True def print_to_console(self, console: tcod.Console, bounds: Rect) -> None: From d8275725b86091b1643c8fbef84c83e7b070cd51 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:28:42 -0800 Subject: [PATCH 155/234] Allow many more (30) attempts at generating a random rect for the RandomRectRoomGenerator --- erynrl/map/generator/room.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 7cf7330..bfa6f19 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -116,7 +116,7 @@ class OneBigRoomGenerator(RoomGenerator): class RandomRectRoomGenerator(RoomGenerator): '''Generate rooms by repeatedly attempting to place rects of random size across the map.''' - NUMBER_OF_ATTEMPTS_PER_ROOM = 5 + NUMBER_OF_ATTEMPTS_PER_ROOM = 30 def _generate(self) -> bool: number_of_attempts = 0 @@ -164,14 +164,12 @@ class BSPRoomGenerator(RoomGenerator): bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) # Add 2 to the minimum width and height to account for walls - gap_for_walls = 2 bsp.split_recursive( depth=4, - min_width=minimum_room_size.width + gap_for_walls, - min_height=minimum_room_size.height + gap_for_walls, + min_width=minimum_room_size.width, + min_height=minimum_room_size.height, max_horizontal_ratio=1.1, - max_vertical_ratio=1.1 - ) + max_vertical_ratio=1.1) # Generate the rooms rooms: List[Room] = [] @@ -200,10 +198,16 @@ class BSPRoomGenerator(RoomGenerator): minimum_room_size.height, node.height - 2)) ) + log.MAP.debug('|-> min room size %s', minimum_room_size) + log.MAP.debug('|-> max room size %s', maximum_room_size) + log.MAP.debug('|-> node size %s x %s', node.width, node.height) + log.MAP.debug('|-> width range %s', width_range) + log.MAP.debug('|-> height range %s', width_range) + size = Size(self.rng.randint(*width_range), self.rng.randint(*height_range)) - origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 1)), - node.y + self.rng.randint(1, max(1, node.height - size.height - 1))) + origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 2)), + node.y + self.rng.randint(1, max(1, node.height - size.height - 2))) bounds = Rect(origin, size) log.MAP.debug('`-> %s', bounds) From 2851ce36a0a2c9f96969394c740b27fbcd9b9174 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:28:57 -0800 Subject: [PATCH 156/234] Update virtualenv requirements --- requirements.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/requirements.txt b/requirements.txt index efa7bd3..c87bcba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,17 @@ +astroid==2.14.1 +autopep8==2.0.1 cffi==1.15.0 +dill==0.3.6 +isort==5.12.0 +lazy-object-proxy==1.9.0 +mccabe==0.7.0 numpy==1.22.3 +platformdirs==3.0.0 +pycodestyle==2.10.0 pycparser==2.21 +pylint==2.16.1 tcod==13.6.1 +tomli==2.0.1 +tomlkit==0.11.6 typing_extensions==4.2.0 +wrapt==1.14.1 From 253db0668301469197e32d2be20b49b6b71ba442 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:29:11 -0800 Subject: [PATCH 157/234] Add the --sandbox argument to the default run config --- .vscode/launch.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7d612ae..8f9589a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,6 +9,9 @@ "type": "python", "request": "launch", "module": "erynrl", + "args": [ + "--sandbox" + ], "justMyCode": true } ] From a709f3fba581ab99c4aa3260500497de1766dc65 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:29:45 -0800 Subject: [PATCH 158/234] Detect when Shift is pressed and don't return a WaitAction when Shift+. is pressed --- erynrl/events.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erynrl/events.py b/erynrl/events.py index dc0d5ac..7c68133 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -64,6 +64,8 @@ class MainGameEventHandler(EventHandler): hero = self.engine.hero + is_shift_pressed = bool(event.mod & tcod.event.Modifier.SHIFT) + sym = event.sym match sym: case tcod.event.KeySym.b: @@ -87,7 +89,8 @@ class MainGameEventHandler(EventHandler): case tcod.event.KeySym.SPACE: action = RegenerateRoomsAction(hero) case tcod.event.KeySym.PERIOD: - action = WaitAction(hero) + if not is_shift_pressed: + action = WaitAction(hero) return action From 1aa6d14540c616143abc7d5623e736ede76fb03d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:30:40 -0800 Subject: [PATCH 159/234] Fix some type checker errors by moving an assert above where the property is accessed --- erynrl/engine.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index bf18c53..de2f2de 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -195,9 +195,9 @@ class Engine: alternate_string) while not result.done: - action = result.alternate - assert action is not None, f'Action {result.action} incomplete but no alternate action given' + assert result.alternate is not None, f'Action {result.action} incomplete but no alternate action given' + action = result.alternate result = action.perform(self) if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.map.visible[tuple(action.actor.position)]: From 8aa329d368b287e4c54e83933be7e5c2ea5e236f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:30:53 -0800 Subject: [PATCH 160/234] Increase sight radius to 30 (from 8) --- erynrl/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index de2f2de..a94eafb 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -224,7 +224,7 @@ class Engine: self.map.visible[:] = tcod.map.compute_fov( self.map.tiles['transparent'], tuple(self.hero.position), - radius=8) + radius=30) # Add visible tiles to the explored grid self.map.explored |= self.map.visible From b0b75f7e76152bea918ddba586ed84aa2da4692e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 14:32:32 -0800 Subject: [PATCH 161/234] Use RandomRect room generator and Elbow corridor generator for the map --- erynrl/engine.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index a94eafb..3e40543 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -18,7 +18,7 @@ from .geometry import Point, Size from .interface import Interface from .map import Map from .map.generator import RoomsAndCorridorsGenerator -from .map.generator.room import BSPRoomGenerator +from .map.generator.room import RandomRectRoomGenerator from .map.generator.corridor import ElbowCorridorGenerator from .messages import MessageLog from .object import Actor, Entity, Hero, Monster @@ -57,6 +57,9 @@ class Engine: self.message_log = MessageLog() map_size = config.map_size + map_generator = RoomsAndCorridorsGenerator( + RandomRectRoomGenerator(size=map_size), + ElbowCorridorGenerator()) self.map = Map(config, map_generator) self.event_handler: 'EventHandler' = MainGameEventHandler(self) From ec28f984dad147204f7b43222c23f8c9e12f6684 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 15:52:26 -0800 Subject: [PATCH 162/234] Add a couple geometry methods - Vector.from_point to convert a point to a vector - Rect.width and Rect.height convenience properties --- erynrl/geometry.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 623ad3f..611660c 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -71,6 +71,11 @@ class Vector: dx: int = 0 dy: int = 0 + @classmethod + def from_point(cls, point: Point) -> 'Vector': + '''Create a Vector from a Point''' + return Vector(point.x, point.y) + def __iter__(self): yield self.dx yield self.dy @@ -164,6 +169,16 @@ class Rect: '''Maximum y-value that is still within the bounds of this rectangle.''' return self.origin.y + self.size.height - 1 + @property + def width(self) -> int: + '''The width of the rectangle. A convenience property for accessing `self.size.width`.''' + return self.size.width + + @property + def height(self) -> int: + '''The height of the rectangle. A convenience property for accessing `self.size.height`.''' + return self.size.height + @property def midpoint(self) -> Point: '''A Point in the middle of the Rect''' From 8efd3ce2073a273b2de0cb10192834a44afc6570 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 15:55:01 -0800 Subject: [PATCH 163/234] Refactor map rendering - Move all map rendering to a new MapWindow class - Clip map rendering to the bounds of the Window - Pass in Entities list from the Engine and render them relative to the window The map doesn't scroll yet, so it'll always be clipped on the bottom and right. --- erynrl/engine.py | 18 ++------- erynrl/interface/__init__.py | 45 +++++++++++----------- erynrl/interface/window.py | 72 +++++++++++++++++++++++++++++++++++- erynrl/map/__init__.py | 14 +++++++ 4 files changed, 109 insertions(+), 40 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 3e40543..c8a44ae 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -106,23 +106,11 @@ class Engine: '''Print the whole game to the given console.''' self.map.highlight_points(self.__mouse_path_points or []) - self.interface.update(self.hero, self.current_turn) + sorted_entities = sorted(self.entities, key=lambda e: e.render_order.value) + self.interface.update(self.current_turn, self.hero, sorted_entities) + self.interface.draw(console) - entities_at_mouse_position = [] - for ent in sorted(self.entities, key=lambda e: e.render_order.value): - # Only process entities that are in the field of view - if not self.map.visible[tuple(ent.position)]: - continue - - ent.print_to_console(console) - - if ent.position == self.__current_mouse_point: - entities_at_mouse_position.append(ent) - - if len(entities_at_mouse_position) > 0: - console.print(x=1, y=43, string=', '.join(e.name for e in entities_at_mouse_position)) - def run_event_loop(self, context: tcod.context.Context, console: tcod.Console) -> NoReturn: '''Run the event loop forever. This method never returns.''' while True: diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 7df7ba4..dac7fac 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -1,27 +1,31 @@ # Eryn Wells -from typing import Optional +from typing import List from tcod.console import Console from .color import HealthBar from .percentage_bar import PercentageBar -from .window import Window +from .window import Window, MapWindow from ..geometry import Point, Rect, Size from ..map import Map from ..messages import MessageLog -from ..object import Hero +from ..object import Entity, Hero class Interface: + '''The game's user interface''' + + # pylint: disable=redefined-builtin def __init__(self, size: Size, map: Map, message_log: MessageLog): self.map_window = MapWindow(Rect(Point(0, 0), Size(size.width, size.height - 5)), map) self.info_window = InfoWindow(Rect(Point(0, size.height - 5), Size(28, 5))) self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log) - def update(self, hero: Hero, turn_count: int): + def update(self, turn_count: int, hero: Hero, entities: List[Entity]): self.info_window.turn_count = turn_count self.info_window.update_hero(hero) + self.map_window.entities = entities def draw(self, console: Console): self.map_window.draw(console) @@ -29,51 +33,46 @@ class Interface: self.message_window.draw(console) -class MapWindow(Window): - def __init__(self, bounds: Rect, map: Map): - super().__init__(bounds) - self.map = map - - def draw(self, console): - super().draw(console) - - # TODO: Get a 2D slice of tiles from the map given a rect based on the window's drawable area - drawable_area = self.drawable_area - self.map.print_to_console(console, drawable_area) - - class InfoWindow(Window): + '''A window that displays information about the player''' + def __init__(self, bounds: Rect): super().__init__(bounds, framed=True) self.turn_count: int = 0 - drawable_area = self.drawable_area + drawable_area = self.drawable_bounds self.hit_points_bar = PercentageBar( position=Point(drawable_area.min_x + 6, drawable_area.min_y), width=20, colors=list(HealthBar.bar_colors())) def update_hero(self, hero: Hero): - hp, max_hp = hero.fighter.hit_points, hero.fighter.maximum_hit_points + assert hero.fighter + + fighter = hero.fighter + hp, max_hp = fighter.hit_points, fighter.maximum_hit_points + self.hit_points_bar.percent_filled = hp / max_hp def draw(self, console): super().draw(console) - drawable_area = self.drawable_area - console.print(x=drawable_area.min_x + 2, y=drawable_area.min_y, string='HP:') + drawable_bounds = self.drawable_bounds + console.print(x=drawable_bounds.min_x + 2, y=drawable_bounds.min_y, string='HP:') self.hit_points_bar.render_to_console(console) if self.turn_count: - console.print(x=drawable_area.min_x, y=drawable_area.min_y + 1, string=f'Turn: {self.turn_count}') + console.print(x=drawable_bounds.min_x, y=drawable_bounds.min_y + 1, string=f'Turn: {self.turn_count}') class MessageLogWindow(Window): + '''A window that displays a list of messages''' + def __init__(self, bounds: Rect, message_log: MessageLog): super().__init__(bounds, framed=True) self.message_log = message_log def draw(self, console): super().draw(console) - self.message_log.render_to_console(console, self.drawable_area) + self.message_log.render_to_console(console, self.drawable_bounds) diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py index 0926c3b..d9a68d1 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window.py @@ -1,17 +1,24 @@ # Eryn Wells +from typing import List + +import numpy as np from tcod.console import Console -from ..geometry import Rect +from ..object import Entity +from ..geometry import Rect, Vector +from ..map import Map class Window: + '''A user interface window. It can be framed.''' + def __init__(self, bounds: Rect, *, framed: bool = True): self.bounds = bounds self.is_framed = framed @property - def drawable_area(self) -> Rect: + def drawable_bounds(self) -> Rect: if self.is_framed: return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds @@ -23,3 +30,64 @@ class Window: self.bounds.origin.y, self.bounds.size.width, self.bounds.size.height) + + +class MapWindow(Window): + '''A Window that displays a game map''' + + def __init__(self, bounds: Rect, map_: Map, **kwargs): + super().__init__(bounds, **kwargs) + self.map = map_ + + self.entities: List[Entity] = [] + + def draw(self, console: Console): + super().draw(console) + self._draw_map(console) + self._draw_entities(console) + + def _draw_map(self, console: Console): + map_ = self.map + map_size = map_.size + + drawable_bounds = self.drawable_bounds + + width = min(map_size.width, drawable_bounds.width) + height = min(map_size.height, drawable_bounds.height) + + # TODO: Adjust the slice according to where the hero is. + map_slice = np.s_[0:width, 0:height] + + min_x = drawable_bounds.min_x + max_x = min_x + width + min_y = drawable_bounds.min_y + max_y = min_y + height + + console.tiles_rgb[min_x:max_x, min_y:max_y] = self.map.composited_tiles[map_slice] + + def _draw_entities(self, console): + drawable_bounds_vector = Vector.from_point(self.drawable_bounds.origin) + + for ent in self.entities: + # Only process entities that are in the field of view + if not self.map.visible[tuple(ent.position)]: + continue + + # Entity positions are 0-based relative to the (0, 0) point of the Map. In order to render them in the + # correct position in the console, we need to offset the position. + entity_position = ent.position + map_tile_at_entity_position = self.map.composited_tiles[entity_position.x, entity_position.y] + + position = ent.position + drawable_bounds_vector + console.print( + x=position.x, + y=position.y, + string=ent.symbol, + fg=ent.foreground, + bg=tuple(map_tile_at_entity_position['bg'][:3])) + + # if ent.position == self.__current_mouse_point: + # entities_at_mouse_position.append(ent) + + # if len(entities_at_mouse_position) > 0: + # console.print(x=1, y=43, string=', '.join(e.name for e in entities_at_mouse_position)) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index fc0f1e0..4c3aba4 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -47,6 +47,20 @@ class Map: '''The size of the map''' return self.configuration.map_size + @property + def composited_tiles(self) -> np.ndarray: + # TODO: Hold onto the result here so that this doen't have to be done every time this property is called. + return np.select( + condlist=[ + self.highlighted, + self.visible, + self.explored], + choicelist=[ + self.tiles['highlighted'], + self.tiles['light'], + self.tiles['dark']], + default=Shroud) + def random_walkable_position(self) -> Point: '''Return a random walkable point on the map.''' if not self.__walkable_points: From 7e00f58a40d851c1bca25b7ac89772f3f3096c8a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 16:34:37 -0800 Subject: [PATCH 164/234] Refactor Action into Action and ActionWithActor The base class Actor doesn't declare a (optional) actor attribute. The ActionWithActor has a non-optional actor attribute. This makes the type checker happier, and means we can have some actions that don't have actors. --- erynrl/actions/action.py | 25 +++++++++++++++----- erynrl/actions/game.py | 51 +++++++++++++++++++++++----------------- erynrl/ai.py | 6 ++--- erynrl/engine.py | 13 ++++++---- erynrl/object.py | 16 +++++++++---- 5 files changed, 72 insertions(+), 39 deletions(-) diff --git a/erynrl/actions/action.py b/erynrl/actions/action.py index 3dd865a..584d102 100644 --- a/erynrl/actions/action.py +++ b/erynrl/actions/action.py @@ -1,6 +1,6 @@ # Eryn Wells -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING from ..object import Actor from .result import ActionResult @@ -8,12 +8,11 @@ from .result import ActionResult if TYPE_CHECKING: from ..engine import Engine + class Action: - '''An action that an Entity should perform.''' - - def __init__(self, actor: Optional[Actor] = None): - self.actor = actor + '''An action with no specific actor''' + # pylint: disable=unused-argument def perform(self, engine: 'Engine') -> ActionResult: '''Perform this action. @@ -28,7 +27,7 @@ class Action: A result object reflecting how the action was handled, and what follow-up actions, if any, are needed to complete the action. ''' - raise NotImplementedError() + return self.success() def failure(self) -> ActionResult: '''Create an ActionResult indicating failure with no follow-up''' @@ -38,6 +37,20 @@ class Action: '''Create an ActionResult indicating success with no follow-up''' return ActionResult(self, success=True) + def __str__(self) -> str: + return self.__class__.__name__ + + def __repr__(self): + return f'{self.__class__.__name__}()' + + +class ActionWithActor(Action): + '''An action that assigned to an actor''' + + def __init__(self, actor: Actor): + super().__init__() + self.actor = actor + def __str__(self) -> str: return f'{self.__class__.__name__} for {self.actor!s}' diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index 03f7438..0e70fbd 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -# Eryn Wells - ''' This module defines all of the actions that can be performed by the game. These actions can come from the player (e.g. via keyboard input), or from non-player entities (e.g. AI deciboard input), or from non-player entities (e.g. AI @@ -18,13 +15,14 @@ Action : Base class of all actions WaitAction ''' +import random from typing import TYPE_CHECKING from .. import items from .. import log from ..geometry import Vector from ..object import Actor, Item -from .action import Action +from .action import Action, ActionWithActor from .result import ActionResult if TYPE_CHECKING: @@ -34,9 +32,6 @@ if TYPE_CHECKING: class ExitAction(Action): '''Exit the game.''' - def __init__(self): - super().__init__(None) - def perform(self, engine: 'Engine') -> ActionResult: raise SystemExit() @@ -45,12 +40,10 @@ class RegenerateRoomsAction(Action): '''Regenerate the dungeon map''' def perform(self, engine: 'Engine') -> ActionResult: - return ActionResult(self, success=False) - -# pylint: disable=abstract-method + return self.failure() -class MoveAction(Action): +class MoveAction(ActionWithActor): '''An abstract Action that requires a direction to complete.''' def __init__(self, actor: Actor, direction: Vector): @@ -100,6 +93,8 @@ class BumpAction(MoveAction): if not position_is_in_bounds or not position_is_walkable: return self.failure() + # TODO: I'm passing entity_occupying_position into the ActionResult below, but the type checker doesn't + # understand that the entity is an Actor. I think I need some additional checks here. if entity_occupying_position: assert entity_occupying_position.blocks_movement return ActionResult(self, alternate=MeleeAction(self.actor, self.direction, entity_occupying_position)) @@ -111,15 +106,19 @@ class WalkAction(MoveAction): '''Walk one step in the given direction.''' def perform(self, engine: 'Engine') -> ActionResult: - new_position = self.actor.position + self.direction + actor = self.actor + + assert actor.fighter + + new_position = actor.position + self.direction log.ACTIONS.debug('Moving %s to %s', self.actor, new_position) - self.actor.position = new_position + actor.position = new_position try: - should_recover_hit_points = self.actor.fighter.passively_recover_hit_points(5) + should_recover_hit_points = actor.fighter.passively_recover_hit_points(5) if should_recover_hit_points: - return ActionResult(self, alternate=HealAction(self.actor, 1)) + return ActionResult(self, alternate=HealAction(actor, random.randint(1, 3))) except AttributeError: pass @@ -134,8 +133,13 @@ class MeleeAction(MoveAction): self.target = target def perform(self, engine: 'Engine') -> ActionResult: + assert self.actor.fighter and self.target.fighter + + fighter = self.actor.fighter + target_fighter = self.target.fighter + try: - damage = self.actor.fighter.attack_power - self.target.fighter.defense + damage = fighter.attack_power - target_fighter.defense if damage > 0 and self.target: log.ACTIONS.debug('%s attacks %s for %d damage!', self.actor, self.target, damage) self.target.fighter.hit_points -= damage @@ -160,21 +164,24 @@ class MeleeAction(MoveAction): return self.success() -class WaitAction(Action): +class WaitAction(ActionWithActor): '''Wait a turn''' def perform(self, engine: 'Engine') -> ActionResult: log.ACTIONS.debug('%s is waiting a turn', self.actor) if self.actor == engine.hero: - should_recover_hit_points = self.actor.fighter.passively_recover_hit_points(10) + assert self.actor.fighter + + fighter = self.actor.fighter + should_recover_hit_points = fighter.passively_recover_hit_points(20) if should_recover_hit_points: - return ActionResult(self, alternate=HealAction(self.actor, 1)) + return ActionResult(self, alternate=HealAction(self.actor, random.randint(1, 3))) return self.success() -class DieAction(Action): +class DieAction(ActionWithActor): '''Kill an Actor''' def perform(self, engine: 'Engine') -> ActionResult: @@ -193,7 +200,7 @@ class DieAction(Action): return self.success() -class DropItemAction(Action): +class DropItemAction(ActionWithActor): '''Drop an item''' def __init__(self, actor: 'Actor', item: 'Item'): @@ -205,7 +212,7 @@ class DropItemAction(Action): return self.success() -class HealAction(Action): +class HealAction(ActionWithActor): '''Heal a target actor some number of hit points''' def __init__(self, actor: 'Actor', hit_points_to_recover: int): diff --git a/erynrl/ai.py b/erynrl/ai.py index 05c761e..ade0b33 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -7,7 +7,7 @@ import numpy as np import tcod from . import log -from .actions.action import Action +from .actions.action import ActionWithActor from .actions.game import BumpAction, WaitAction from .components import Component from .geometry import Direction, Point @@ -26,7 +26,7 @@ class AI(Component): super().__init__() self.entity = entity - def act(self, engine: 'Engine') -> Optional[Action]: + def act(self, engine: 'Engine') -> Optional[ActionWithActor]: '''Produce an action to perform''' raise NotImplementedError() @@ -38,7 +38,7 @@ class HostileEnemy(AI): beeline for her. ''' - def act(self, engine: 'Engine') -> Optional[Action]: + def act(self, engine: 'Engine') -> Optional[ActionWithActor]: visible_tiles = tcod.map.compute_fov( engine.map.tiles['transparent'], pov=tuple(self.entity.position), diff --git a/erynrl/engine.py b/erynrl/engine.py index c8a44ae..86ad536 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -9,7 +9,7 @@ import tcod from . import log from . import monsters -from .actions.action import Action +from .actions.action import Action, ActionWithActor from .actions.result import ActionResult from .ai import HostileEnemy from .configuration import Configuration @@ -125,6 +125,10 @@ class Engine: def process_input_action(self, action: Action): '''Process an Action from player input''' + if not isinstance(action, ActionWithActor): + log.ACTIONS_TREE.error('Attempted to process input action with no actor') + return + log.ACTIONS_TREE.info('Processing Hero Actions') log.ACTIONS_TREE.info('|-> %s', action.actor) @@ -165,10 +169,11 @@ class Engine: if self.map.visible[tuple(ent.position)]: log.ACTIONS_TREE.info('%s-> %s', '|' if i < len(entities) - 1 else '`', ent) - action = ent_ai.act(self) - self._perform_action_until_done(action) + action = ent_ai.act(engine=self) + if action: + self._perform_action_until_done(action) - def _perform_action_until_done(self, action: Action) -> ActionResult: + def _perform_action_until_done(self, action: ActionWithActor) -> ActionResult: '''Perform the given action and any alternate follow-up actions until the action chain is done.''' result = action.perform(self) diff --git a/erynrl/object.py b/erynrl/object.py index 4d3c813..7b2c1b3 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -102,11 +102,17 @@ class Actor(Entity): position: Optional[Point] = None, blocks_movement: Optional[bool] = True, render_order: RenderOrder = RenderOrder.ACTOR, - ai: Optional[Type['AI']] = None, + ai: Optional['AI'] = None, fighter: Optional[Fighter] = None, fg: Optional[Tuple[int, int, int]] = None, bg: Optional[Tuple[int, int, int]] = None): - super().__init__(symbol, position=position, blocks_movement=blocks_movement, fg=fg, bg=bg, render_order=render_order) + super().__init__( + symbol, + position=position, + blocks_movement=blocks_movement, + fg=fg, + bg=bg, + render_order=render_order) # Components self.ai = ai @@ -152,13 +158,14 @@ class Hero(Actor): return 8 def __str__(self) -> str: + assert self.fighter return f'Hero!{self.identifier} at {self.position} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp' class Monster(Actor): '''An instance of a Species''' - def __init__(self, species: Species, ai_class: Type['AI'], position: Point = None): + def __init__(self, species: Species, ai_class: Type['AI'], position: Optional[Point] = None): fighter = Fighter( maximum_hit_points=species.maximum_hit_points, attack_power=species.attack_power, @@ -187,13 +194,14 @@ class Monster(Actor): return True def __str__(self) -> str: + assert self.fighter return f'{self.name}!{self.identifier} with {self.fighter.hit_points}/{self.fighter.maximum_hit_points} hp at {self.position}' class Item(Entity): '''An instance of an Item''' - def __init__(self, kind: items.Item, position: Point = None, name: str = None): + def __init__(self, kind: items.Item, position: Optional[Point] = None, name: Optional[str] = None): super().__init__(kind.symbol, position=position, blocks_movement=False, From 36206b5cc0d73a1b31d7142e599c58e297e6d287 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 19:44:28 -0800 Subject: [PATCH 165/234] Clean up __add__ on Point; add __sub__ The dunder add method only ever had one overload, so you don't need to declare an overload --- erynrl/geometry.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 611660c..424f3b6 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -4,7 +4,7 @@ import math from dataclasses import dataclass -from typing import Any, Iterator, Optional, overload, Tuple +from typing import Iterator, Optional, Tuple @dataclass(frozen=True) @@ -36,6 +36,7 @@ class Point: return (self.x in (other.x - 1, other.x + 1)) and (self.y in (other.y - 1, other.y + 1)) def direction_to_adjacent_point(self, other: 'Point') -> Optional['Vector']: + '''Given a point directly adjacent to `self`''' for direction in Direction.all(): if (self + direction) != other: continue @@ -47,15 +48,16 @@ class Point: '''Compute the Euclidean distance to another Point''' return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) - @overload def __add__(self, other: 'Vector') -> 'Point': - ... - - def __add__(self, other: Any) -> 'Point': if not isinstance(other, Vector): raise TypeError('Only Vector can be added to a Point') return Point(self.x + other.dx, self.y + other.dy) + def __sub__(self, other: 'Vector') -> 'Point': + if not isinstance(other, Vector): + raise TypeError('Only Vector can be added to a Point') + return Point(self.x - other.dx, self.y - other.dy) + def __iter__(self): yield self.x yield self.y From b5f25822dfbbcf1b9ad23a54c1516ba35b435d9a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 19:44:41 -0800 Subject: [PATCH 166/234] Add a UI log --- erynrl/log.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erynrl/log.py b/erynrl/log.py index d61295e..352095b 100644 --- a/erynrl/log.py +++ b/erynrl/log.py @@ -13,9 +13,11 @@ import os.path # pylint: disable=unused-import from logging import CRITICAL, DEBUG, ERROR, FATAL, INFO, NOTSET, WARN, WARNING + def _log_name(*components): return '.'.join(['erynrl'] + list(components)) + ROOT = logging.getLogger(_log_name()) AI = logging.getLogger(_log_name('ai')) ACTIONS = logging.getLogger(_log_name('actions')) @@ -23,6 +25,8 @@ ACTIONS_TREE = logging.getLogger(_log_name('actions', 'tree')) ENGINE = logging.getLogger(_log_name('engine')) EVENTS = logging.getLogger(_log_name('events')) MAP = logging.getLogger(_log_name('map')) +UI = logging.getLogger(_log_name('ui')) + def walk_up_directories_of_path(path): '''Walk up a path, yielding each directory, until the root of the filesystem is found''' @@ -31,6 +35,7 @@ def walk_up_directories_of_path(path): yield path path = os.path.dirname(path) + def find_logging_config(): '''Walk up the filesystem from this script to find a logging_config.json''' for parent_dir in walk_up_directories_of_path(__file__): @@ -43,6 +48,7 @@ def find_logging_config(): return possible_logging_config_file + def init(config_file=None): ''' Set up the logging system by (preferrably) reading a logging configuration file. From 0a6ff23dcdc918e3d8995f1780d53342eb9aacb5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 19:44:52 -0800 Subject: [PATCH 167/234] Bigger maps! --- erynrl/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/configuration.py b/erynrl/configuration.py index 384aa93..5df1721 100644 --- a/erynrl/configuration.py +++ b/erynrl/configuration.py @@ -18,7 +18,7 @@ from .geometry import Size CONSOLE_SIZE = Size(80, 50) -MAP_SIZE = Size(80, 45) +MAP_SIZE = Size(100, 100) FONT_CP437 = 'terminal16x16_gs_ro.png' FONT_BDF = 'ter-u32n.bdf' From 402e910915ffae352c7f232294f2880d885106e5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 19:45:12 -0800 Subject: [PATCH 168/234] Add Map.bounds --- erynrl/map/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 4c3aba4..589c47a 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -42,6 +42,11 @@ class Map: self.__walkable_points = None + @property + def bounds(self) -> Rect: + '''The bounds of the map''' + return Rect(Point(), self.size) + @property def size(self) -> Size: '''The size of the map''' From 356e205f2c30723733fa1c41e57c57734810f443 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 19:47:27 -0800 Subject: [PATCH 169/234] Implement viewport tracking for the MapWindow As the player moves the hero, the MapWindow will try to keep the hero in the middle of the view. --- erynrl/events.py | 2 +- erynrl/interface/__init__.py | 5 +++ erynrl/interface/window.py | 76 ++++++++++++++++++++++++------------ 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/erynrl/events.py b/erynrl/events.py index 7c68133..f06634b 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -87,7 +87,7 @@ class MainGameEventHandler(EventHandler): case tcod.event.KeySym.ESCAPE: action = ExitAction() case tcod.event.KeySym.SPACE: - action = RegenerateRoomsAction(hero) + action = RegenerateRoomsAction() case tcod.event.KeySym.PERIOD: if not is_shift_pressed: action = WaitAction(hero) diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index dac7fac..b8f06d3 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -23,11 +23,15 @@ class Interface: self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log) def update(self, turn_count: int, hero: Hero, entities: List[Entity]): + '''Update game state that the interface needs to render''' self.info_window.turn_count = turn_count self.info_window.update_hero(hero) + + self.map_window.update_drawable_map_bounds(hero) self.map_window.entities = entities def draw(self, console: Console): + '''Draw the UI to the console''' self.map_window.draw(console) self.info_window.draw(console) self.message_window.draw(console) @@ -48,6 +52,7 @@ class InfoWindow(Window): colors=list(HealthBar.bar_colors())) def update_hero(self, hero: Hero): + '''Update internal state for the hero''' assert hero.fighter fighter = hero.fighter diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py index d9a68d1..f707359 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window.py @@ -5,9 +5,10 @@ from typing import List import numpy as np from tcod.console import Console -from ..object import Entity -from ..geometry import Rect, Vector +from .. import log +from ..geometry import Point, Rect, Vector from ..map import Map +from ..object import Entity, Hero class Window: @@ -19,11 +20,13 @@ class Window: @property def drawable_bounds(self) -> Rect: + '''The bounds of the window that is drawable, inset by any frame''' if self.is_framed: return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds def draw(self, console: Console): + '''Draw the window to the conole''' if self.is_framed: console.draw_frame( self.bounds.origin.x, @@ -35,41 +38,67 @@ class Window: class MapWindow(Window): '''A Window that displays a game map''' - def __init__(self, bounds: Rect, map_: Map, **kwargs): + # pylint: disable=redefined-builtin + def __init__(self, bounds: Rect, map: Map, **kwargs): super().__init__(bounds, **kwargs) - self.map = map_ + self.map = map + self.drawable_map_bounds = map.bounds + self.offset = Vector() self.entities: List[Entity] = [] + def update_drawable_map_bounds(self, hero: Hero): + bounds = self.drawable_bounds + map_bounds = self.map.bounds + + if map_bounds.width < bounds.width and map_bounds.height < bounds.height: + # We can draw the whole map in the drawable bounds + self.drawable_map_bounds = map_bounds + + # Attempt to keep the player centered in the viewport. + + hero_point = hero.position + + x = min(max(0, hero_point.x - bounds.mid_x), map_bounds.max_x - bounds.width) + y = min(max(0, hero_point.y - bounds.mid_y), map_bounds.max_y - bounds.height) + origin = Point(x, y) + + self.drawable_map_bounds = Rect(origin, bounds.size) + def draw(self, console: Console): super().draw(console) self._draw_map(console) self._draw_entities(console) def _draw_map(self, console: Console): - map_ = self.map - map_size = map_.size - + drawable_map_bounds = self.drawable_map_bounds drawable_bounds = self.drawable_bounds - width = min(map_size.width, drawable_bounds.width) - height = min(map_size.height, drawable_bounds.height) + log.UI.info('Drawing map') + log.UI.info('|- map bounds: %s', drawable_map_bounds) + log.UI.info('|- window bounds: %s', drawable_bounds) - # TODO: Adjust the slice according to where the hero is. - map_slice = np.s_[0:width, 0:height] + map_slice = np.s_[ + drawable_map_bounds.min_x:drawable_map_bounds.max_x + 1, + drawable_map_bounds.min_y:drawable_map_bounds.max_y + 1] - min_x = drawable_bounds.min_x - max_x = min_x + width - min_y = drawable_bounds.min_y - max_y = min_y + height + console_slice = np.s_[ + drawable_bounds.min_x:drawable_bounds.max_x + 1, + drawable_bounds.min_y:drawable_bounds.max_y + 1] - console.tiles_rgb[min_x:max_x, min_y:max_y] = self.map.composited_tiles[map_slice] + log.UI.info('|- map slice: %s', map_slice) + log.UI.info('`- console slice: %s', console_slice) + + console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice] def _draw_entities(self, console): + map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin) drawable_bounds_vector = Vector.from_point(self.drawable_bounds.origin) + log.UI.info('Drawing entities') + for ent in self.entities: - # Only process entities that are in the field of view + # Only draw entities that are in the field of view if not self.map.visible[tuple(ent.position)]: continue @@ -78,16 +107,15 @@ class MapWindow(Window): entity_position = ent.position map_tile_at_entity_position = self.map.composited_tiles[entity_position.x, entity_position.y] - position = ent.position + drawable_bounds_vector + position = ent.position - map_bounds_vector + drawable_bounds_vector + + if isinstance(ent, Hero): + log.UI.info('|- hero position on map %s', entity_position) + log.UI.info('`- position in window %s', position) + console.print( x=position.x, y=position.y, string=ent.symbol, fg=ent.foreground, bg=tuple(map_tile_at_entity_position['bg'][:3])) - - # if ent.position == self.__current_mouse_point: - # entities_at_mouse_position.append(ent) - - # if len(entities_at_mouse_position) > 0: - # console.print(x=1, y=43, string=', '.join(e.name for e in entities_at_mouse_position)) From 77fc9467bcb2202c9b3f0ce211a69232a1f67a12 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 12 Feb 2023 19:48:45 -0800 Subject: [PATCH 170/234] Fix accidentally breaking QuitAction When I added some type safety to Engine.process_input_action, I just dropped any action without an actor. --- erynrl/engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 86ad536..737f5cd 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -126,7 +126,7 @@ class Engine: '''Process an Action from player input''' if not isinstance(action, ActionWithActor): - log.ACTIONS_TREE.error('Attempted to process input action with no actor') + action.perform(self) return log.ACTIONS_TREE.info('Processing Hero Actions') From e6c4717e808c7658d99fe37a2c96ef264eec99fb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 15 Feb 2023 08:21:49 -0800 Subject: [PATCH 171/234] Let the Hero specify its own sight radius --- erynrl/engine.py | 2 +- erynrl/object.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 737f5cd..d7a5846 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -220,7 +220,7 @@ class Engine: self.map.visible[:] = tcod.map.compute_fov( self.map.tiles['transparent'], tuple(self.hero.position), - radius=30) + radius=self.hero.sight_radius) # Add visible tiles to the explored grid self.map.explored |= self.map.visible diff --git a/erynrl/object.py b/erynrl/object.py index 7b2c1b3..b55f3c0 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -155,7 +155,7 @@ class Hero(Actor): @property def sight_radius(self) -> int: # TODO: Make this configurable - return 8 + return 0 def __str__(self) -> str: assert self.fighter From 633580e27aa56203dd6b892b8c639e828da2343b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 15 Feb 2023 08:22:32 -0800 Subject: [PATCH 172/234] Add Rect.edges Iterates the minimum and maximum x and y edges for the Rect. --- erynrl/geometry.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 424f3b6..5a0aa10 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -186,6 +186,16 @@ class Rect: '''A Point in the middle of the Rect''' return Point(self.mid_x, self.mid_y) + @property + def edges(self) -> Iterator[int]: + ''' + An iterator over the edges of this Rect in the order of: `min_x`, `max_x`, `min_y`, `max_y`. + ''' + yield self.min_x + yield self.max_x + yield self.min_y + yield self.max_y + def intersects(self, other: 'Rect') -> bool: '''Returns `True` if `other` intersects this Rect.''' if other.min_x > self.max_x: From 4050ac5c6f9310943f2595af3772d586fd0659a9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 15 Feb 2023 08:25:40 -0800 Subject: [PATCH 173/234] Add a bunch of doc strings and header comments to files --- erynrl/actions/game.py | 2 +- erynrl/interface/__init__.py | 4 ++++ erynrl/map/__init__.py | 1 + erynrl/map/generator/__init__.py | 3 +++ erynrl/map/generator/corridor.py | 2 ++ erynrl/map/room.py | 7 +++++-- erynrl/map/tile.py | 9 +++++++++ 7 files changed, 25 insertions(+), 3 deletions(-) diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index 0e70fbd..e793005 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -64,7 +64,7 @@ class BumpAction(MoveAction): Attributes ---------- - direction : Direction + direction : Vector The direction to test ''' diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index b8f06d3..bf33a73 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -1,5 +1,9 @@ # Eryn Wells +''' +The game's graphical user interface +''' + from typing import List from tcod.console import Console diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 589c47a..cb07b1f 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -82,6 +82,7 @@ class Map: return self.tile_is_in_bounds(point) and self.tiles[point.x, point.y]['walkable'] def point_is_explored(self, point: Point) -> bool: + '''Return True if the tile at the given point has been explored by the player''' return self.tile_is_in_bounds(point) and self.explored[point.x, point.y] def highlight_points(self, points: Iterable[Point]): diff --git a/erynrl/map/generator/__init__.py b/erynrl/map/generator/__init__.py index 2d4e361..dc11b4b 100644 --- a/erynrl/map/generator/__init__.py +++ b/erynrl/map/generator/__init__.py @@ -12,13 +12,16 @@ class MapGenerator: @property def up_stairs(self) -> List[Point]: + '''The location of any routes to a higher floor of the dungeon.''' raise NotImplementedError() @property def down_stairs(self) -> List[Point]: + '''The location of any routes to a lower floor of the dungeon.''' raise NotImplementedError() def generate(self, tiles: np.ndarray): + '''Generate a map and place it in `tiles`''' raise NotImplementedError() diff --git a/erynrl/map/generator/corridor.py b/erynrl/map/generator/corridor.py index c61b273..aa93b9d 100644 --- a/erynrl/map/generator/corridor.py +++ b/erynrl/map/generator/corridor.py @@ -1,3 +1,5 @@ +# Eryn Wells + ''' Defines an abstract CorridorGenerator and several concrete subclasses. These classes generate corridors between rooms. ''' diff --git a/erynrl/map/room.py b/erynrl/map/room.py index 9a2e68d..3651d6a 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -1,6 +1,9 @@ -from typing import Iterator +# Eryn Wells + +''' +Implements an abstract Room class, and subclasses that implement it. Rooms are basic components of maps. +''' -from ..geometry import Point, Rect class Room: diff --git a/erynrl/map/tile.py b/erynrl/map/tile.py index d869920..91d29ca 100644 --- a/erynrl/map/tile.py +++ b/erynrl/map/tile.py @@ -1,3 +1,12 @@ +# Eryn Wells + +''' +Map tiles and tile related things. + +Maps are represented with 2-dimensional numpy arrays with the `dtype`s defined +here. Tiles are instances of those dtypes. +''' + from typing import Tuple import numpy as np From e377b3d7b61ac002b5d720b98ea964b94d68ae5b Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 15 Feb 2023 08:26:01 -0800 Subject: [PATCH 174/234] Change the name of the Sandbox scheme to indicate it's the Sandbox scheme --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f9589a..a588f9e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "ErynRL", + "name": "ErynRL (Sandbox)", "type": "python", "request": "launch", "module": "erynrl", From d5e8891545109e2ad23f97b1090c2540a4962cff Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 15 Feb 2023 08:36:43 -0800 Subject: [PATCH 175/234] Add back some imports to map/room.py --- erynrl/map/room.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/erynrl/map/room.py b/erynrl/map/room.py index 3651d6a..b4c27c1 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -4,6 +4,9 @@ Implements an abstract Room class, and subclasses that implement it. Rooms are basic components of maps. ''' +from typing import Iterator + +from ..geometry import Rect class Room: From 292fa852f9d119f44c8ede0ec0d22ae647fb4d67 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 10:40:36 -0800 Subject: [PATCH 176/234] Add non-Sandbox launch target --- .vscode/launch.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index a588f9e..40b6cd8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "ErynRL", + "type": "python", + "request": "launch", + "module": "erynrl", + "args": [], + "justMyCode": true + }, { "name": "ErynRL (Sandbox)", "type": "python", From 8fc5206e95f2ced412c99c91cda0fad4790c50e4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 10:40:55 -0800 Subject: [PATCH 177/234] Ignore .venv directory in the VSCode workspace --- going_rogue.code-workspace | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/going_rogue.code-workspace b/going_rogue.code-workspace index de521eb..2c32b9c 100644 --- a/going_rogue.code-workspace +++ b/going_rogue.code-workspace @@ -6,6 +6,9 @@ ], "settings": { "editor.formatOnSave": true, - "python.analysis.typeCheckingMode": "basic" + "python.analysis.typeCheckingMode": "basic", + "files.exclude": { + ".venv/": true + } } } From 306d6fd13f0c98d6877efa2910d52bd0fc2bf9bb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 10:49:35 -0800 Subject: [PATCH 178/234] Add pytest unit tests --- .vscode/settings.json | 9 +++++++-- requirements.txt | 6 ++++++ test/__init__.py | 1 + test/test_geometry_point.py | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/__init__.py create mode 100644 test/test_geometry_point.py diff --git a/.vscode/settings.json b/.vscode/settings.json index cc67606..e63fc33 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { "python.linting.pylintEnabled": true, - "python.linting.enabled": true -} \ No newline at end of file + "python.linting.enabled": true, + "python.testing.pytestArgs": [ + "test" + ], + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true +} diff --git a/requirements.txt b/requirements.txt index c87bcba..4a12b8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,21 @@ astroid==2.14.1 +attrs==22.2.0 autopep8==2.0.1 cffi==1.15.0 dill==0.3.6 +exceptiongroup==1.1.0 +iniconfig==2.0.0 isort==5.12.0 lazy-object-proxy==1.9.0 mccabe==0.7.0 numpy==1.22.3 +packaging==23.0 platformdirs==3.0.0 +pluggy==1.0.0 pycodestyle==2.10.0 pycparser==2.21 pylint==2.16.1 +pytest==7.2.1 tcod==13.6.1 tomli==2.0.1 tomlkit==0.11.6 diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..12ba5c3 --- /dev/null +++ b/test/__init__.py @@ -0,0 +1 @@ +# Eryn Wells diff --git a/test/test_geometry_point.py b/test/test_geometry_point.py new file mode 100644 index 0000000..96ec355 --- /dev/null +++ b/test/test_geometry_point.py @@ -0,0 +1,35 @@ +# Eryn Wells + +from erynrl.geometry import Point + + +def test_point_neighbors(): + '''Check that Point.neighbors returns all neighbors''' + test_point = Point(5, 5) + + expected_neighbors = set([ + Point(4, 4), + Point(5, 4), + Point(6, 4), + Point(4, 5), + # Point(5, 5), + Point(6, 5), + Point(4, 6), + Point(5, 6), + Point(6, 6), + ]) + + neighbors = set(test_point.neighbors) + for pt in expected_neighbors: + assert pt in neighbors + + assert expected_neighbors - neighbors == set(), \ + f"Found some points that didn't belong in the set of neighbors of {test_point}" + + +def test_point_manhattan_distance(): + '''Check that the Manhattan Distance calculation on Points is correct''' + point_a = Point(3, 2) + point_b = Point(8, 5) + + assert point_a.manhattan_distance_to(point_b) == 8 From 462eebd95c220e5e0d22f8b5b9859c0970c17de1 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 22:51:19 -0800 Subject: [PATCH 179/234] Add Rect.corners An iterator over the corners of the rectangle --- erynrl/geometry.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 5a0aa10..cbc156e 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -186,6 +186,13 @@ class Rect: '''A Point in the middle of the Rect''' return Point(self.mid_x, self.mid_y) + @property + def corners(self) -> Iterator[Point]: + yield self.origin + yield self.origin + Vector(self.max_x, 0) + yield self.origin + Vector(self.max_x, self.max_y) + yield self.origin + Vector(0, self.max_y) + @property def edges(self) -> Iterator[int]: ''' From 06d34a527b608c1fd35d20038ee2038ab07b08a0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 22:51:42 -0800 Subject: [PATCH 180/234] Add Point.manhattan_distance_to Returns the manhattan distance to another Point. --- erynrl/geometry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index cbc156e..aab95b8 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -48,6 +48,10 @@ class Point: '''Compute the Euclidean distance to another Point''' return math.sqrt((self.x - other.x) ** 2 + (self.y - other.y) ** 2) + def manhattan_distance_to(self, other: 'Point') -> int: + '''Compute the Manhattan distance to another Point''' + return abs(self.x - other.x) + abs(self.y - other.y) + def __add__(self, other: 'Vector') -> 'Point': if not isinstance(other, Vector): raise TypeError('Only Vector can be added to a Point') From 7428d95126ed66af83206036eb36712f85e562a2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 22:51:58 -0800 Subject: [PATCH 181/234] Fix the implementation of Point.is_adjacent_to --- erynrl/geometry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index aab95b8..f567ec5 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -33,7 +33,10 @@ class Point: bool True if this point is adjacent to the other point ''' - return (self.x in (other.x - 1, other.x + 1)) and (self.y in (other.y - 1, other.y + 1)) + if self == other: + return False + + return (self.x - 1 <= other.x <= self.x + 1) and (self.y - 1 <= other.y <= self.y + 1) def direction_to_adjacent_point(self, other: 'Point') -> Optional['Vector']: '''Given a point directly adjacent to `self`''' From 37ffa423b68373d631f8bd50141fb291a9f6a6ab Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 22:55:20 -0800 Subject: [PATCH 182/234] Pass the whole Map into MapGenerator.generate --- erynrl/map/__init__.py | 5 +++-- erynrl/map/generator/__init__.py | 10 ++++++---- erynrl/map/generator/room.py | 28 ++++++++++++++++++---------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index cb07b1f..2002545 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -12,7 +12,7 @@ import numpy as np import numpy.typing as npt import tcod -from ..engine import Configuration +from ..configuration import Configuration from ..geometry import Point, Rect, Size from .generator import MapGenerator from .tile import Empty, Shroud @@ -28,7 +28,6 @@ class Map: shape = map_size.numpy_shape self.tiles = np.full(shape, fill_value=Empty, order='F') - generator.generate(self.tiles) self.up_stairs = generator.up_stairs self.down_stairs = generator.down_stairs @@ -42,6 +41,8 @@ class Map: self.__walkable_points = None + generator.generate(self) + @property def bounds(self) -> Rect: '''The bounds of the map''' diff --git a/erynrl/map/generator/__init__.py b/erynrl/map/generator/__init__.py index dc11b4b..9f3cd82 100644 --- a/erynrl/map/generator/__init__.py +++ b/erynrl/map/generator/__init__.py @@ -20,7 +20,8 @@ class MapGenerator: '''The location of any routes to a lower floor of the dungeon.''' raise NotImplementedError() - def generate(self, tiles: np.ndarray): + # pylint: disable=redefined-builtin + def generate(self, map: 'Map'): '''Generate a map and place it in `tiles`''' raise NotImplementedError() @@ -42,9 +43,10 @@ class RoomsAndCorridorsGenerator(MapGenerator): def down_stairs(self) -> List[Point]: return self.room_generator.down_stairs - def generate(self, tiles: np.ndarray): + # pylint: disable=redefined-builtin + def generate(self, map: 'Map'): self.room_generator.generate() - self.room_generator.apply(tiles) + self.room_generator.apply(map) self.corridor_generator.generate(self.room_generator.rooms) - self.corridor_generator.apply(tiles) + self.corridor_generator.apply(map.tiles) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index bfa6f19..5a039b8 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -1,15 +1,17 @@ +# Eryn Wells + import random from copy import copy from dataclasses import dataclass -from typing import List, Optional +from typing import List, Optional, TYPE_CHECKING -import numpy as np import tcod from ... import log from ...geometry import Point, Rect, Size -from ..room import Room, RectangularRoom -from ..tile import Empty, Floor, StairsUp, StairsDown, Wall + +if TYPE_CHECKING: + from .. import Map class RoomGenerator: @@ -29,7 +31,9 @@ class RoomGenerator: def __init__(self, *, size: Size, config: Optional[Configuration] = None): self.size = size self.configuration = config if config else copy(RoomGenerator.DefaultConfiguration) + self.rooms: List[Room] = [] + self.up_stairs: List[Point] = [] self.down_stairs: List[Point] = [] @@ -54,20 +58,23 @@ class RoomGenerator: ''' raise NotImplementedError() - def apply(self, tiles: np.ndarray): + # pylint: disable=redefined-builtin + def apply(self, map: 'Map'): '''Apply the generated rooms to a tile array''' - self._apply(tiles) - self._apply_stairs(tiles) + self._apply(map) + self._apply_stairs(map.tiles) - def _apply(self, tiles: np.ndarray): + def _apply(self, map: 'Map'): ''' Apply the generated list of rooms to an array of tiles. Subclasses must implement this. Arguments --------- - tiles: np.ndarray - The array of tiles to update. + map: Map + The game map to apply the generated room to ''' + tiles = map.tiles + for room in self.rooms: for pt in room.floor_points: tiles[pt.x, pt.y] = Floor @@ -76,6 +83,7 @@ class RoomGenerator: for pt in room.wall_points: if tiles[pt.x, pt.y] != Empty: continue + tiles[pt.x, pt.y] = Wall def _generate_stairs(self): From 30727ccac1fee6f64b8709b3a7585f249e05ec7f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 22:55:41 -0800 Subject: [PATCH 183/234] Add a test for Point.is_adjacent_to --- test/test_geometry_point.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/test_geometry_point.py b/test/test_geometry_point.py index 96ec355..8cd95c8 100644 --- a/test/test_geometry_point.py +++ b/test/test_geometry_point.py @@ -33,3 +33,20 @@ def test_point_manhattan_distance(): point_b = Point(8, 5) assert point_a.manhattan_distance_to(point_b) == 8 + + +def test_point_is_adjacent_to(): + '''Check that Point.is_adjacent_to correctly computes adjacency''' + test_point = Point(5, 5) + + assert not test_point.is_adjacent_to(test_point), \ + f"{test_point!s} should not be considered adjacent to itself" + + for neighbor in test_point.neighbors: + assert test_point.is_adjacent_to(neighbor), \ + f"Neighbor {neighbor!s} that was not considered adjacent to {test_point!s}" + + assert not test_point.is_adjacent_to(Point(3, 5)) + assert not test_point.is_adjacent_to(Point(7, 5)) + assert not test_point.is_adjacent_to(Point(5, 3)) + assert not test_point.is_adjacent_to(Point(5, 7)) From 64844d124b2c79957dad8c17156efd369e250aae Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Feb 2023 22:57:37 -0800 Subject: [PATCH 184/234] A couple fixes in items.py Reformat the items in items.py Fix the type declaration of Item.background_color --- erynrl/items.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/erynrl/items.py b/erynrl/items.py index eafd248..4c76f0b 100644 --- a/erynrl/items.py +++ b/erynrl/items.py @@ -1,7 +1,8 @@ # Eryn Wells from dataclasses import dataclass -from typing import Tuple +from typing import Optional, Tuple + @dataclass(frozen=True) class Item: @@ -27,9 +28,11 @@ class Item: name: str description: str foreground_color: Tuple[int, int, int] - background_color: Tuple[int, int, int] = None + background_color: Optional[Tuple[int, int, int]] = None -Corpse = Item('%', - name="Corpse", - description="The corpse of a once-living being", - foreground_color=(128, 128, 255)) + +Corpse = Item( + '%', + name="Corpse", + description="The corpse of a once-living being", + foreground_color=(128, 128, 255)) From 5470ea697c320addc3ba612b5d57a518edffb574 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 18:18:09 -0800 Subject: [PATCH 185/234] Add a test for Rect.corners --- test/test_geometry_rect.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/test_geometry_rect.py diff --git a/test/test_geometry_rect.py b/test/test_geometry_rect.py new file mode 100644 index 0000000..39c4219 --- /dev/null +++ b/test/test_geometry_rect.py @@ -0,0 +1,18 @@ +# Eryn Wells + +from erynrl.geometry import Point, Rect, Size + + +def test_rect_corners(): + rect = Rect(Point(5, 5), Size(5, 5)) + + corners = set(rect.corners) + + expected_corners = set([ + Point(5, 5), + Point(9, 5), + Point(5, 9), + Point(9, 9) + ]) + + assert corners == expected_corners From 885868f39ef9755c428b97c142e22e7d91742b66 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 18:18:40 -0800 Subject: [PATCH 186/234] Add a test for RectangularRoom.wall_points --- test/test_map_room.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/test_map_room.py diff --git a/test/test_map_room.py b/test/test_map_room.py new file mode 100644 index 0000000..0467e27 --- /dev/null +++ b/test/test_map_room.py @@ -0,0 +1,34 @@ +# Eryn Wells + +from erynrl.geometry import Point, Rect, Size +from erynrl.map.room import RectangularRoom + + +def test_rectangular_room_wall_points(): + '''Check that RectangularRoom.wall_points returns the correct set of points''' + rect = Rect(Point(5, 5), Size(5, 5)) + room = RectangularRoom(rect) + + expected_points = set([ + Point(5, 5), + Point(6, 5), + Point(7, 5), + Point(8, 5), + Point(9, 5), + Point(9, 6), + Point(9, 7), + Point(9, 8), + Point(9, 9), + Point(8, 9), + Point(7, 9), + Point(6, 9), + Point(5, 9), + Point(5, 8), + Point(5, 7), + Point(5, 6), + ]) + + for pt in room.wall_points: + expected_points.remove(pt) + + assert len(expected_points) == 0 From 21c3b5d94f2d485c8e3e4785c955600c61665552 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 18:22:45 -0800 Subject: [PATCH 187/234] Small bit of reformatting and type checking --- erynrl/map/generator/__init__.py | 7 ++++++- erynrl/map/generator/room.py | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/erynrl/map/generator/__init__.py b/erynrl/map/generator/__init__.py index 9f3cd82..d82d44e 100644 --- a/erynrl/map/generator/__init__.py +++ b/erynrl/map/generator/__init__.py @@ -1,4 +1,6 @@ -from typing import List +# Eryn Wells + +from typing import List, TYPE_CHECKING import numpy as np @@ -6,6 +8,9 @@ from .corridor import CorridorGenerator from .room import RoomGenerator from ...geometry import Point +if TYPE_CHECKING: + from .. import Map + class MapGenerator: '''Abstract base class defining an interface for generating a map and applying it to a set of tiles.''' diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 5a039b8..9a3423d 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -41,8 +41,10 @@ class RoomGenerator: '''Generate rooms and stairs''' did_generate_rooms = self._generate() - if did_generate_rooms: - self._generate_stairs() + if not did_generate_rooms: + return + + self._generate_stairs() def _generate(self) -> bool: ''' From 9bd287dc9fe3616730f2203841c7e1b4569e6d78 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 18:34:59 -0800 Subject: [PATCH 188/234] Add Map.__str__ --- erynrl/map/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 2002545..a47a75e 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -103,3 +103,12 @@ class Map: condlist=[self.highlighted, self.visible, self.explored], choicelist=[self.tiles['highlighted'], self.tiles['light'], self.tiles['dark']], default=Shroud) + + def __str__(self): + string = '' + + tiles = self.tiles['light']['ch'] + for row in tiles: + string += ''.join(chr(n) for n in row) + '\n' + + return string From 47014d4e6eaf16ac395e01aeee137e056d412f25 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 18:37:14 -0800 Subject: [PATCH 189/234] Move field of view updates to Map.update_field_of_view --- erynrl/engine.py | 6 +----- erynrl/map/__init__.py | 7 +++++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index d7a5846..0255073 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -216,11 +216,7 @@ class Engine: def update_field_of_view(self): '''Compute visible area of the map based on the player's position and point of view.''' - # FIXME: Move this to the Map class - self.map.visible[:] = tcod.map.compute_fov( - self.map.tiles['transparent'], - tuple(self.hero.position), - radius=self.hero.sight_radius) + self.map.update_visible_tiles(self.hero.position, self.hero.sight_radius) # Add visible tiles to the explored grid self.map.explored |= self.map.visible diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index a47a75e..550a25b 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -33,6 +33,7 @@ class Map: self.down_stairs = generator.down_stairs self.highlighted = np.full(shape, fill_value=False, order='F') + # Map tiles that are currently visible to the player self.visible = np.full(shape, fill_value=False, order='F') # Map tiles that the player has explored @@ -67,6 +68,12 @@ class Map: self.tiles['dark']], default=Shroud) + def update_visible_tiles(self, point: Point, radius: int): + field_of_view = tcod.map.compute_fov(self.tiles['transparent'], tuple(point), radius=radius) + + # The player's computed field of view + self.visible[:] = field_of_view + def random_walkable_position(self) -> Point: '''Return a random walkable point on the map.''' if not self.__walkable_points: From 22ad73af0be417203b187fccc0c205d210a6be4d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 19:06:24 -0800 Subject: [PATCH 190/234] Fix Rect.corners based on the unit test --- erynrl/geometry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index f567ec5..f9e2ca5 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -196,9 +196,9 @@ class Rect: @property def corners(self) -> Iterator[Point]: yield self.origin - yield self.origin + Vector(self.max_x, 0) - yield self.origin + Vector(self.max_x, self.max_y) - yield self.origin + Vector(0, self.max_y) + yield Point(self.max_x, self.min_y) + yield Point(self.min_x, self.max_y) + yield Point(self.max_x, self.max_y) @property def edges(self) -> Iterator[int]: From 445c600bf929f0d863d00f3f4fd2e789acbf6a14 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 19:08:32 -0800 Subject: [PATCH 191/234] Add a doc string to Rect.corners --- erynrl/geometry.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index f9e2ca5..67adcc0 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -195,6 +195,7 @@ class Rect: @property def corners(self) -> Iterator[Point]: + '''An iterator over the corners of this rectangle''' yield self.origin yield Point(self.max_x, self.min_y) yield Point(self.min_x, self.max_y) From 706a2443829b04419c7be5266ea9074b7b4531ff Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 19:13:19 -0800 Subject: [PATCH 192/234] Fix up some imports in map.generator.room --- erynrl/map/generator/room.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 9a3423d..d3a90c5 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -9,6 +9,8 @@ import tcod from ... import log from ...geometry import Point, Rect, Size +from ..room import RectangularRoom +from ..tile import Empty, Floor, StairsDown, StairsUp, Wall if TYPE_CHECKING: from .. import Map From 09480e7499e27c234319d5ef93df7be8b5242899 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 19 Feb 2023 19:13:31 -0800 Subject: [PATCH 193/234] Fix up some imports in map.room --- erynrl/map/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/map/room.py b/erynrl/map/room.py index b4c27c1..0e8ddce 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -6,7 +6,7 @@ Implements an abstract Room class, and subclasses that implement it. Rooms are b from typing import Iterator -from ..geometry import Rect +from ..geometry import Point, Rect class Room: From b8e7e3d0595002020467abeeeee6b179acd587ab Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 20 Feb 2023 17:11:55 -0800 Subject: [PATCH 194/234] Clean up log module documentation --- erynrl/log.py | 46 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/erynrl/log.py b/erynrl/log.py index 352095b..af8ccdb 100644 --- a/erynrl/log.py +++ b/erynrl/log.py @@ -1,13 +1,14 @@ # Eryn Wells ''' -Initializes and sets up +Initializes and sets up logging for the game. ''' import json import logging import logging.config import os.path +from typing import Iterator, Optional # These are re-imports so clients of this module don't have to also import logging # pylint: disable=unused-import @@ -24,20 +25,38 @@ ACTIONS = logging.getLogger(_log_name('actions')) ACTIONS_TREE = logging.getLogger(_log_name('actions', 'tree')) ENGINE = logging.getLogger(_log_name('engine')) EVENTS = logging.getLogger(_log_name('events')) -MAP = logging.getLogger(_log_name('map')) UI = logging.getLogger(_log_name('ui')) +MAP = logging.getLogger(_log_name('map')) +MAP_CELL_ATOM = logging.getLogger(_log_name('map', 'cellular')) -def walk_up_directories_of_path(path): - '''Walk up a path, yielding each directory, until the root of the filesystem is found''' + +def walk_up_directories_of_path(path: str) -> Iterator[str]: + ''' + Walk up a path, yielding each directory, until the root of the filesystem is + found. + + ### Parameters + `path`: `str` + The starting path + + ### Returns + Yields each ancestor directory until the root directory of the filesystem is + reached. + ''' while path and path != '/': if os.path.isdir(path): yield path path = os.path.dirname(path) -def find_logging_config(): - '''Walk up the filesystem from this script to find a logging_config.json''' +def find_logging_config() -> Optional[str]: + ''' + Walk up the filesystem from this script to find a logging_config.json + + ### Returns + The path to a logging configuration file, or `None` if no such file was found + ''' for parent_dir in walk_up_directories_of_path(__file__): possible_logging_config_file = os.path.join(parent_dir, 'logging_config.json') if os.path.isfile(possible_logging_config_file): @@ -49,23 +68,26 @@ def find_logging_config(): return possible_logging_config_file -def init(config_file=None): +def init(config_file: Optional[str] = None): ''' Set up the logging system by (preferrably) reading a logging configuration file. - Parameters - ---------- - config_file : str + ### Parameters + `config_file`: Optional[str] Path to a file containing a Python logging configuration in JSON ''' logging_config_path = config_file if config_file else find_logging_config() - if os.path.isfile(logging_config_path): + if logging_config_path and os.path.isfile(logging_config_path): + ROOT.info('Found logging configuration at %s', logging_config_path) with open(logging_config_path, encoding='utf-8') as logging_config_file: logging_config = json.load(logging_config_file) logging.config.dictConfig(logging_config) - ROOT.info('Found logging configuration at %s', logging_config_path) else: + ROOT.info( + "Couldn't find logging configuration at %s; using default configuration", + logging_config_path) + root_logger = logging.getLogger('') root_logger.setLevel(logging.DEBUG) From 6aefff838d7122d8beda083a1392f969a50ba754 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 20 Feb 2023 18:00:29 -0800 Subject: [PATCH 195/234] Implement Rect.__contains__ on a Point Returns True if the Rect contains the point. --- erynrl/geometry.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 67adcc0..2ec834a 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -252,6 +252,15 @@ class Rect: return Rect(Point(self.origin.x + left, self.origin.y + top), Size(self.size.width - right - left, self.size.height - top - bottom)) + def __contains__(self, pt: Point) -> bool: + if pt.x < self.min_x or pt.x > self.max_x: + return False + + if pt.y < self.min_y or pt.y > self.max_y: + return False + + return True + def __iter__(self): yield tuple(self.origin) yield tuple(self.size) From be7198b16dce4b8569c64fc77183722ac124d246 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 20 Feb 2023 18:00:41 -0800 Subject: [PATCH 196/234] Update the doc string of Rect.inset_rect --- erynrl/geometry.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 2ec834a..b2b6cf4 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -229,23 +229,25 @@ class Rect: def inset_rect(self, top: int = 0, right: int = 0, bottom: int = 0, left: int = 0) -> 'Rect': ''' - Return a new Rect inset from this rect by the specified values. Arguments are listed in clockwise order around - the permeter. This method doesn't validate the returned Rect, or transform it to a canonical representation with - the origin at the top-left. + Create a new Rect inset from this rect by the specified values. - Parameters - ---------- - top : int + Arguments are listed in clockwise order around the permeter. This method + doesn't validate the returned Rect, or transform it to a canonical + representation with the origin at the top-left. + + ### Parameters + + `top`: int Amount to inset from the top - right : int + `right`: int Amount to inset from the right - bottom : int + `bottom`: int Amount to inset from the bottom - left : int + `left`: int Amount to inset from the left - Returns - ------- + ### Returns + Rect A new Rect, inset from `self` by the given amount on each side ''' From a542bb956a965de55001af7bee407dca2b5ea0d7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 20 Feb 2023 18:02:01 -0800 Subject: [PATCH 197/234] Add CellularAtomataMapGenerator First pass at a cellular atomata map generator. Add map.grid and a make_grid function to make it easier to make numpy arrays for Map purposes. Add ca.py to test the generator. --- ca.py | 44 +++++++++ erynrl/map/generator/cellular_atomata.py | 120 +++++++++++++++++++++++ erynrl/map/grid.py | 10 ++ 3 files changed, 174 insertions(+) create mode 100644 ca.py create mode 100644 erynrl/map/generator/cellular_atomata.py create mode 100644 erynrl/map/grid.py diff --git a/ca.py b/ca.py new file mode 100644 index 0000000..3d2ff6e --- /dev/null +++ b/ca.py @@ -0,0 +1,44 @@ +# Eryn Wells + +''' +Run the cellular atomaton from ErynRL and print the results. +''' + +import argparse + +from erynrl import log +from erynrl.geometry import Point, Rect, Size +from erynrl.map.generator.cellular_atomata import CellularAtomataMapGenerator + + +def parse_args(argv, *a, **kw): + '''Parse command line arguments''' + parser = argparse.ArgumentParser(*a, **kw) + parser.add_argument('--rounds', type=int, default=5) + args = parser.parse_args(argv) + return args + + +def main(argv): + '''The script''' + + args = parse_args(argv[1:], prog=argv[0]) + + log.init() + + bounds = Rect(Point(), Size(20, 20)) + + config = CellularAtomataMapGenerator.Configuration() + config.number_of_rounds = args.rounds + + gen = CellularAtomataMapGenerator(bounds, config) + + gen.generate() + + print(gen) + + +if __name__ == '__main__': + import sys + result = main(sys.argv) + sys.exit(0 if not result else result) diff --git a/erynrl/map/generator/cellular_atomata.py b/erynrl/map/generator/cellular_atomata.py new file mode 100644 index 0000000..c1e2f0b --- /dev/null +++ b/erynrl/map/generator/cellular_atomata.py @@ -0,0 +1,120 @@ +# Eryn Wells + +import random +from dataclasses import dataclass +from typing import Optional + +import numpy as np + +from ... import log +from ...geometry import Point, Rect +from ..grid import make_grid +from ..tile import Floor, Wall + + +class CellularAtomataMapGenerator: + ''' + A map generator that utilizes a cellular atomaton to place floors and walls. + ''' + + @dataclass + class Configuration: + ''' + Configuration of a cellular atomaton map generator. + + ### Attributes + `fill_percentage` : `float` + The percentage of tiles to fill with Floor when the map is seeded. + `number_of_rounds` : `int` + The number of rounds to run the atomaton. 5 is a good default. More + rounds results in smoother output; fewer rounds results in more + jagged, random output. + ''' + fill_percentage: float = 0.5 + number_of_rounds: int = 5 + + def __init__(self, bounds: Rect, config: Optional[Configuration] = None): + ''' + Initializer + + ### Parameters + `bounds` : `Rect` + A rectangle representing the bounds of the cellular atomaton + `config` : `Optional[Configuration]` + A configuration object specifying parameters for the atomaton. If + None, the instance will use a default configuration. + ''' + self.bounds = bounds + self.configuration = config if config else CellularAtomataMapGenerator.Configuration() + self.tiles = make_grid(bounds.size) + + def generate(self): + ''' + Run the cellular atomaton on a grid of `self.bounds.size` shape. + + First fill the grid with random Floor and Wall tiles according to + `self.configuration.fill_percentage`, then run the simulation for + `self.configuration.number_of_rounds` rounds. + ''' + self._fill() + self._run_atomaton() + + def _fill(self): + fill_percentage = self.configuration.fill_percentage + + for y, x in np.ndindex(self.tiles.shape): + self.tiles[x, y] = Floor if random.random() < fill_percentage else Wall + + def _run_atomaton(self): + alternate_tiles = make_grid(self.bounds.size) + + number_of_rounds = self.configuration.number_of_rounds + if number_of_rounds < 1: + raise ValueError('Refusing to run cellular atomaton for less than 1 round') + + log.MAP_CELL_ATOM.info( + 'Running cellular atomaton over %s for %d round%s', + self.bounds, + number_of_rounds, + '' if number_of_rounds == 1 else 's') + + for i in range(number_of_rounds): + if i % 2 == 0: + from_tiles = self.tiles + to_tiles = alternate_tiles + else: + from_tiles = alternate_tiles + to_tiles = self.tiles + + self._do_round(from_tiles, to_tiles) + + # If we ended on a round where alternate_tiles was the "to" tile grid + # above, save it back to self.tiles. + if number_of_rounds % 2 == 0: + self.tiles = alternate_tiles + + def _do_round(self, from_tiles: np.ndarray, to_tiles: np.ndarray): + for y, x in np.ndindex(from_tiles.shape): + pt = Point(x, y) + + # Start with 1 because the point is its own neighbor + number_of_neighbors = 1 + for neighbor in pt.neighbors: + if neighbor not in self.bounds: + continue + + if from_tiles[neighbor.x, neighbor.y] == Floor: + number_of_neighbors += 1 + + tile_is_alive = from_tiles[pt.x, pt.y] == Floor + if tile_is_alive and number_of_neighbors >= 5: + # Survival + to_tiles[pt.x, pt.y] = Floor + elif not tile_is_alive and number_of_neighbors >= 5: + # Birth + to_tiles[pt.x, pt.y] = Floor + else: + to_tiles[pt.x, pt.y] = Wall + + def __str__(self): + return '\n'.join(''.join(chr(i['light']['ch']) for i in row) for row in self.tiles) diff --git a/erynrl/map/grid.py b/erynrl/map/grid.py new file mode 100644 index 0000000..b86f8af --- /dev/null +++ b/erynrl/map/grid.py @@ -0,0 +1,10 @@ +# Eryn Wells + +import numpy as np + +from .tile import Empty +from ..geometry import Size + + +def make_grid(size: Size): + return np.full(size.numpy_shape, fill_value=Empty, order='F') From a83b85b7a04f45a1637fd2b3a868c55a7c565c94 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 20 Feb 2023 18:02:21 -0800 Subject: [PATCH 198/234] Add one stray import to map.generator.room --- erynrl/map/generator/room.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index d3a90c5..3fdf8c3 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -9,7 +9,7 @@ import tcod from ... import log from ...geometry import Point, Rect, Size -from ..room import RectangularRoom +from ..room import RectangularRoom, Room from ..tile import Empty, Floor, StairsDown, StairsUp, Wall if TYPE_CHECKING: From b9c45f44b2fe5b101804f264099160e37cadba67 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Mar 2023 11:07:32 -0800 Subject: [PATCH 199/234] Clean up the doc comment on Entity --- erynrl/object.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/erynrl/object.py b/erynrl/object.py index b55f3c0..34d2f2e 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -29,10 +29,11 @@ class RenderOrder(Enum): class Entity: '''A single-tile drawable entity with a symbol and position - Attributes - ---------- + ### Attributes + identifier : int - A numerical value that uniquely identifies this entity across the entire game + A numerical value that uniquely identifies this entity across the entire + game position : Point The Entity's location on the map foreground : Tuple[int, int, int] @@ -42,13 +43,16 @@ class Entity: symbol : str A single character string that represents this character on the map blocks_movement : bool - True if this Entity blocks other Entities from moving through its position + True if this Entity blocks other Entities from moving through its + position render_order : RenderOrder - One of the RenderOrder values that specifies a layer at which this entity will be rendered. Higher values are - rendered on top of lower values. + One of the RenderOrder values that specifies a layer at which this + entity will be rendered. Higher values are rendered on top of lower + values. ''' - # A monotonically increasing identifier to help differentiate between entities that otherwise look identical + # A monotonically increasing identifier to help differentiate between + # entities that otherwise look identical __next_identifier = 1 def __init__( From e2553cca3bb5a6fc65006f86497af2096544a435 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 4 Mar 2023 11:08:11 -0800 Subject: [PATCH 200/234] Clean up RoomGenerator.Configuration --- erynrl/map/generator/room.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 3fdf8c3..744586c 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -1,7 +1,6 @@ # Eryn Wells import random -from copy import copy from dataclasses import dataclass from typing import List, Optional, TYPE_CHECKING @@ -21,18 +20,13 @@ class RoomGenerator: @dataclass class Configuration: - number_of_rooms: int - minimum_room_size: Size - maximum_room_size: Size - - DefaultConfiguration = Configuration( - number_of_rooms=30, - minimum_room_size=Size(7, 7), - maximum_room_size=Size(20, 20)) + number_of_rooms: int = 30 + minimum_room_size: Size = Size(7, 7) + maximum_room_size: Size = Size(20, 20) def __init__(self, *, size: Size, config: Optional[Configuration] = None): self.size = size - self.configuration = config if config else copy(RoomGenerator.DefaultConfiguration) + self.configuration = config if config else RoomGenerator.Configuration() self.rooms: List[Room] = [] From c488ef9c2b8133e5f776a76f9c7f69ce3738986a Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 10:49:02 -0800 Subject: [PATCH 201/234] Add doc string for Configuration attributes --- erynrl/__main__.py | 8 +++----- erynrl/configuration.py | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 19d8985..f801654 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -39,18 +39,16 @@ def main(argv): else: font_config = FontConfiguration.default_configuration() except FontConfigurationError as error: - log.ROOT.error('Unable to create a default font configuration: %s', error) + log.ROOT.error('Unable to create a font configuration: %s', error) return -1 configuration = Configuration( - console_size=CONSOLE_SIZE, - console_font_config=font_config, - map_size=MAP_SIZE, + console_font_configuration=font_config, sandbox=args.sandbox) engine = Engine(configuration) - tileset = configuration.console_font_config.tileset + tileset = configuration.console_font_configuration.tileset console = tcod.Console(*configuration.console_size.numpy_shape, order='F') with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: engine.run_event_loop(context, console) diff --git a/erynrl/configuration.py b/erynrl/configuration.py index 5df1721..f0f2c72 100644 --- a/erynrl/configuration.py +++ b/erynrl/configuration.py @@ -166,10 +166,23 @@ class TilesheetFontConfiguration(FontConfiguration): @dataclass class Configuration: - '''Configuration of the game engine''' - console_size: Size - console_font_config: FontConfiguration + ''' + Configuration of the game engine - map_size: Size + ### Attributes + + console_font_configuration : FontConfiguration + A configuration object that defines the font to use for rendering the console + console_size : Size + The size of the console in tiles + map_size : Size + The size of the map in tiles + sandbox : bool + If this flag is toggled on, the map is rendered with no shroud + ''' + console_font_configuration: FontConfiguration + + console_size: Size = CONSOLE_SIZE + map_size: Size = MAP_SIZE sandbox: bool = False From 85928a938d8d49d0cc7eb5cd64a55bc5704fe715 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 13:35:25 -0800 Subject: [PATCH 202/234] Some geometry updates - Add Point.numpy_index to simplify converting Points to indexes - Update the doc string of Point.direction_to_adjacent_point - Add a Rect.__contains__ implementation for another Rect - Refactor the contains implementations above into helper methods --- erynrl/geometry.py | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index b2b6cf4..97aee52 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -4,7 +4,7 @@ import math from dataclasses import dataclass -from typing import Iterator, Optional, Tuple +from typing import Iterator, Optional, overload, Tuple @dataclass(frozen=True) @@ -14,6 +14,11 @@ class Point: x: int = 0 y: int = 0 + @property + def numpy_index(self) -> Tuple[int, int]: + '''Convert this Point into a tuple suitable for indexing into a numpy map array''' + return (self.x, self.y) + @property def neighbors(self) -> Iterator['Point']: '''Iterator over the neighboring points of `self` in all eight directions.''' @@ -39,7 +44,10 @@ class Point: return (self.x - 1 <= other.x <= self.x + 1) and (self.y - 1 <= other.y <= self.y + 1) def direction_to_adjacent_point(self, other: 'Point') -> Optional['Vector']: - '''Given a point directly adjacent to `self`''' + ''' + Given a point directly adjacent to `self`, return a Vector indicating in + which direction it is adjacent. + ''' for direction in Direction.all(): if (self + direction) != other: continue @@ -254,14 +262,32 @@ class Rect: return Rect(Point(self.origin.x + left, self.origin.y + top), Size(self.size.width - right - left, self.size.height - top - bottom)) - def __contains__(self, pt: Point) -> bool: - if pt.x < self.min_x or pt.x > self.max_x: - return False + @overload + def __contains__(self, other: Point) -> bool: + ... - if pt.y < self.min_y or pt.y > self.max_y: - return False + @overload + def __contains__(self, other: 'Rect') -> bool: + ... - return True + def __contains__(self, other: 'Point | Rect') -> bool: + if isinstance(other, Point): + return self.__contains_point(other) + + if isinstance(other, Rect): + return self.__contains_rect(other) + + raise TypeError(f'{self.__class__.__name__} cannot contain value of type {other.__class__.__name__}') + + def __contains_point(self, pt: Point) -> bool: + return (pt.x >= self.min_x and pt.x <= self.max_x + and pt.y >= self.min_x and pt.y <= self.max_y) + + def __contains_rect(self, other: 'Rect') -> bool: + return (self.min_x <= other.min_x + and self.max_x >= other.max_x + and self.min_y <= other.min_y + and self.max_y >= other.max_y) def __iter__(self): yield tuple(self.origin) From 368f780fcdd5a5d40e1f9df654410694485b8a9e Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 13:35:35 -0800 Subject: [PATCH 203/234] Enable UI debug logging --- logging_config.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/logging_config.json b/logging_config.json index 2062252..41026d4 100644 --- a/logging_config.json +++ b/logging_config.json @@ -62,6 +62,12 @@ ], "propagate": false }, + "erynrl.ui": { + "level": "DEBUG", + "handlers": [ + "console" + ] + }, "erynrl.visible": { "level": "DEBUG", "handlers": [ From 84e51a17ff774e6d0fa0436a9102f585b93f6689 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 13:35:47 -0800 Subject: [PATCH 204/234] Specify map size of 80x24 --- erynrl/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index f801654..84d8dfc 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -6,8 +6,9 @@ import argparse import sys import tcod from . import log -from .configuration import Configuration, FontConfiguration, FontConfigurationError, MAP_SIZE, CONSOLE_SIZE +from .configuration import Configuration, FontConfiguration, FontConfigurationError from .engine import Engine +from .geometry import Size def parse_args(argv, *a, **kw): @@ -44,6 +45,7 @@ def main(argv): configuration = Configuration( console_font_configuration=font_config, + map_size=Size(80, 24), sandbox=args.sandbox) engine = Engine(configuration) From 4b09d467d145f057b9e566c08b5a43851962f6ec Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 13:36:04 -0800 Subject: [PATCH 205/234] Document map.grid --- erynrl/map/grid.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/erynrl/map/grid.py b/erynrl/map/grid.py index b86f8af..4e495d5 100644 --- a/erynrl/map/grid.py +++ b/erynrl/map/grid.py @@ -1,10 +1,15 @@ # Eryn Wells +''' +Utilities for maps. +''' + import numpy as np from .tile import Empty from ..geometry import Size -def make_grid(size: Size): - return np.full(size.numpy_shape, fill_value=Empty, order='F') +def make_grid(size: Size, fill: np.ndarray = Empty) -> np.ndarray: + '''Make a numpy array of the given size filled with `fill` tiles.''' + return np.full(size.numpy_shape, fill_value=fill, order='F') From 42cfb78ba30b600a223dd96947ee762aa3fac5ba Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 13:36:32 -0800 Subject: [PATCH 206/234] Raise exceptions for out-of-bounds points that are passed into Map helper methods --- erynrl/map/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 550a25b..c7c71bd 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -25,8 +25,9 @@ class Map: self.configuration = config map_size = config.map_size - shape = map_size.numpy_shape + self._bounds = Rect(Point(), map_size) + shape = map_size.numpy_shape self.tiles = np.full(shape, fill_value=Empty, order='F') self.up_stairs = generator.up_stairs @@ -47,7 +48,7 @@ class Map: @property def bounds(self) -> Rect: '''The bounds of the map''' - return Rect(Point(), self.size) + return self._bounds @property def size(self) -> Size: @@ -87,11 +88,21 @@ class Map: def tile_is_walkable(self, point: Point) -> bool: '''Return True if the tile at the given point is walkable''' - return self.tile_is_in_bounds(point) and self.tiles[point.x, point.y]['walkable'] + if not self.tile_is_in_bounds(point): + raise ValueError(f'Point {point!s} is not in bounds') + return self.tiles[point.numpy_index]['walkable'] + + def point_is_visible(self, point: Point) -> bool: + '''Return True if the point is visible to the player''' + if not self.tile_is_in_bounds(point): + raise ValueError(f'Point {point!s} is not in bounds') + return self.visible[point.numpy_index] def point_is_explored(self, point: Point) -> bool: '''Return True if the tile at the given point has been explored by the player''' - return self.tile_is_in_bounds(point) and self.explored[point.x, point.y] + if not self.tile_is_in_bounds(point): + raise ValueError(f'Point {point!s} is not in bounds') + return self.explored[point.numpy_index] def highlight_points(self, points: Iterable[Point]): '''Update the highlight graph with the list of points to highlight.''' From af3d93ba116520bd981f9d09b610289040902be2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 13:37:51 -0800 Subject: [PATCH 207/234] Fix up how Maps are rendered in MapWindows There was a bug in how MapWindow was calculating the numpy array slices when drawing the map. Redo how this works so that MapWindow can draw maps of arbitrary size and center maps that are smaller than the window's drawable area. --- erynrl/interface/window.py | 112 ++++++++++++++++++++++++++++--------- 1 file changed, 86 insertions(+), 26 deletions(-) diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py index f707359..9f67113 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window.py @@ -6,7 +6,7 @@ import numpy as np from tcod.console import Console from .. import log -from ..geometry import Point, Rect, Vector +from ..geometry import Point, Rect, Size, Vector from ..map import Map from ..object import Entity, Hero @@ -20,7 +20,10 @@ class Window: @property def drawable_bounds(self) -> Rect: - '''The bounds of the window that is drawable, inset by any frame''' + ''' + The bounds of the window that is drawable, inset by its frame if + `is_framed` is `True`. + ''' if self.is_framed: return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds @@ -34,6 +37,11 @@ class Window: self.bounds.size.width, self.bounds.size.height) + drawable_bounds = self.drawable_bounds + console.draw_rect(drawable_bounds.min_x, drawable_bounds.min_y, + drawable_bounds.width, drawable_bounds.height, + ord(' '), (255, 255, 255), (0, 0, 0)) + class MapWindow(Window): '''A Window that displays a game map''' @@ -44,29 +52,78 @@ class MapWindow(Window): self.map = map self.drawable_map_bounds = map.bounds - self.offset = Vector() self.entities: List[Entity] = [] + self._draw_bounds = self.drawable_bounds + def update_drawable_map_bounds(self, hero: Hero): + ''' + Figure out what portion of the map is drawable and update + `self.drawable_map_bounds`. This method attempts to keep the hero + centered in the map viewport, while not overscrolling the map in either + direction. + ''' bounds = self.drawable_bounds map_bounds = self.map.bounds - if map_bounds.width < bounds.width and map_bounds.height < bounds.height: - # We can draw the whole map in the drawable bounds + viewport_is_wider_than_map = bounds.width > map_bounds.width + viewport_is_taller_than_map = bounds.height > map_bounds.height + + if viewport_is_wider_than_map and viewport_is_taller_than_map: + # The whole map fits within the window's drawable bounds self.drawable_map_bounds = map_bounds + return # Attempt to keep the player centered in the viewport. - hero_point = hero.position - x = min(max(0, hero_point.x - bounds.mid_x), map_bounds.max_x - bounds.width) - y = min(max(0, hero_point.y - bounds.mid_y), map_bounds.max_y - bounds.height) - origin = Point(x, y) + if viewport_is_wider_than_map: + x = 0 + else: + x = min(max(0, hero_point.x - bounds.mid_x), map_bounds.max_x - bounds.width) - self.drawable_map_bounds = Rect(origin, bounds.size) + if viewport_is_taller_than_map: + y = 0 + else: + y = min(max(0, hero_point.y - bounds.mid_y), map_bounds.max_y - bounds.height) + + origin = Point(x, y) + size = Size(min(bounds.width, map_bounds.width), min(bounds.height, map_bounds.height)) + + self.drawable_map_bounds = Rect(origin, size) + + def _update_draw_bounds(self): + ''' + The area where the map should actually be drawn, accounting for the size + of the viewport (`drawable_bounds`)and the size of the map (`self.map.bounds`). + ''' + drawable_map_bounds = self.drawable_map_bounds + drawable_bounds = self.drawable_bounds + + viewport_is_wider_than_map = drawable_bounds.width >= drawable_map_bounds.width + viewport_is_taller_than_map = drawable_bounds.height >= drawable_map_bounds.height + + if viewport_is_wider_than_map: + # Center the map horizontally in the viewport + origin_x = drawable_bounds.min_x + (drawable_bounds.width - drawable_map_bounds.width) // 2 + width = drawable_map_bounds.width + else: + origin_x = drawable_bounds.min_x + width = drawable_bounds.width + + if viewport_is_taller_than_map: + # Center the map vertically in the viewport + origin_y = drawable_bounds.min_y + (drawable_bounds.height - drawable_map_bounds.height) // 2 + height = drawable_map_bounds.height + else: + origin_y = drawable_bounds.min_y + height = drawable_bounds.height + + self._draw_bounds = Rect(Point(origin_x, origin_y), Size(width, height)) def draw(self, console: Console): super().draw(console) + self._update_draw_bounds() self._draw_map(console) self._draw_entities(console) @@ -75,43 +132,44 @@ class MapWindow(Window): drawable_bounds = self.drawable_bounds log.UI.info('Drawing map') - log.UI.info('|- map bounds: %s', drawable_map_bounds) - log.UI.info('|- window bounds: %s', drawable_bounds) map_slice = np.s_[ - drawable_map_bounds.min_x:drawable_map_bounds.max_x + 1, - drawable_map_bounds.min_y:drawable_map_bounds.max_y + 1] + drawable_map_bounds.min_x: drawable_map_bounds.max_x + 1, + drawable_map_bounds.min_y: drawable_map_bounds.max_y + 1] + console_draw_bounds = self._draw_bounds console_slice = np.s_[ - drawable_bounds.min_x:drawable_bounds.max_x + 1, - drawable_bounds.min_y:drawable_bounds.max_y + 1] + console_draw_bounds.min_x: console_draw_bounds.max_x + 1, + console_draw_bounds.min_y: console_draw_bounds.max_y + 1] - log.UI.info('|- map slice: %s', map_slice) - log.UI.info('`- console slice: %s', console_slice) + log.UI.debug('Map bounds=%s, slice=%s', drawable_map_bounds, map_slice) + log.UI.debug('Console bounds=%s, slice=%s', drawable_bounds, console_slice) console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice] + log.UI.info('Done drawing map') + def _draw_entities(self, console): map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin) - drawable_bounds_vector = Vector.from_point(self.drawable_bounds.origin) + draw_bounds_vector = Vector.from_point(self._draw_bounds.origin) log.UI.info('Drawing entities') for ent in self.entities: # Only draw entities that are in the field of view - if not self.map.visible[tuple(ent.position)]: + if not self.map.point_is_visible(ent.position): continue - # Entity positions are 0-based relative to the (0, 0) point of the Map. In order to render them in the - # correct position in the console, we need to offset the position. + # Entity positions are relative to the (0, 0) point of the Map. In + # order to render them in the correct position in the console, we + # need to transform them into viewport-relative coordinates. entity_position = ent.position - map_tile_at_entity_position = self.map.composited_tiles[entity_position.x, entity_position.y] + map_tile_at_entity_position = self.map.composited_tiles[entity_position.numpy_index] - position = ent.position - map_bounds_vector + drawable_bounds_vector + position = ent.position - map_bounds_vector + draw_bounds_vector if isinstance(ent, Hero): - log.UI.info('|- hero position on map %s', entity_position) - log.UI.info('`- position in window %s', position) + log.UI.debug('Hero position: map=%s, window=%s', entity_position, position) console.print( x=position.x, @@ -119,3 +177,5 @@ class MapWindow(Window): string=ent.symbol, fg=ent.foreground, bg=tuple(map_tile_at_entity_position['bg'][:3])) + + log.UI.info('Done drawing entities') From e3864d8468a0d0d4e6491126cfe26b0e88372a58 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 14:24:44 -0800 Subject: [PATCH 208/234] Implement LE, LT, GE, GT on Point --- erynrl/geometry.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 97aee52..0001d56 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -73,6 +73,18 @@ class Point: raise TypeError('Only Vector can be added to a Point') return Point(self.x - other.dx, self.y - other.dy) + def __lt__(self, other: 'Point') -> bool: + return self.x < other.x and self.y < other.y + + def __le__(self, other: 'Point') -> bool: + return self.x <= other.x and self.y <= other.y + + def __gt__(self, other: 'Point') -> bool: + return self.x > other.x and self.y > other.y + + def __ge__(self, other: 'Point') -> bool: + return self.x >= other.x and self.y >= other.y + def __iter__(self): yield self.x yield self.y From 744c63bc8557c2951b93fb795e9d89f69c55a6c5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 14:26:20 -0800 Subject: [PATCH 209/234] Sort rooms before generating corridors between them --- erynrl/map/generator/corridor.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/erynrl/map/generator/corridor.py b/erynrl/map/generator/corridor.py index aa93b9d..13ce2ba 100644 --- a/erynrl/map/generator/corridor.py +++ b/erynrl/map/generator/corridor.py @@ -38,9 +38,12 @@ class ElbowCorridorGenerator(CorridorGenerator): ``` For each pair of rooms: 1. Find the midpoint of the bounding rect of each room - 2. Calculate an elbow point - 3. Draw a path from the midpoint of the first room to the elbow point - 4. Draw a path from the elbow point to the midpoint of the second room + 2. For each pair of rooms: + 1. Calculate an elbow point by taking the x coordinate of one room + and the y coordinate of the other room, choosing which x and which + y at random. + 2. Draw a path from the midpoint of the first room to the elbow point + 3. Draw a path from the elbow point to the midpoint of the second room ``` ''' @@ -51,7 +54,9 @@ class ElbowCorridorGenerator(CorridorGenerator): if len(rooms) < 2: return True - for (left_room, right_room) in pairwise(rooms): + sorted_rooms = sorted(rooms, key=lambda r: r.bounds.origin) + + for (left_room, right_room) in pairwise(sorted_rooms): corridor = self._generate_corridor_between(left_room, right_room) self.corridors.append(corridor) From c17258bd7377c88c94da6f4bbd5ca1f16c1a2f29 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 14:26:52 -0800 Subject: [PATCH 210/234] Some x/y -> numpy_index changes --- erynrl/map/generator/room.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 744586c..bd8b9c9 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -75,14 +75,16 @@ class RoomGenerator: for room in self.rooms: for pt in room.floor_points: - tiles[pt.x, pt.y] = Floor + tiles[pt.numpy_index] = Floor for room in self.rooms: for pt in room.wall_points: - if tiles[pt.x, pt.y] != Empty: + idx = pt.numpy_index + + if tiles[idx] != Empty: continue - tiles[pt.x, pt.y] = Wall + tiles[idx] = Wall def _generate_stairs(self): up_stair_room = random.choice(self.rooms) @@ -98,9 +100,9 @@ class RoomGenerator: def _apply_stairs(self, tiles): for pt in self.up_stairs: - tiles[pt.x, pt.y] = StairsUp + tiles[pt.numpy_index] = StairsUp for pt in self.down_stairs: - tiles[pt.x, pt.y] = StairsDown + tiles[pt.numpy_index] = StairsDown class OneBigRoomGenerator(RoomGenerator): From dd8b0364e0885beaf7eba4b82786c6b0133ecb47 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 16:53:00 -0800 Subject: [PATCH 211/234] Break RoomGenerators into Rect and Room methods A RoomGenerator is now made up of two "method" classes that do separate things: 1. A RectMethod takes the size of the area to generate and creates an iterable stream of Rects to fill that area. 2. A RoomMethod takes a Rect and creates a room inside of it. These two components are composable in interesting ways, and allow a more data-driven approach to map generation, though I don't yet have the ability to make this mechansim entirely data-driven. --- erynrl/engine.py | 11 ++- erynrl/map/generator/room.py | 161 ++++++++++++++++++++++++++++------- 2 files changed, 138 insertions(+), 34 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 0255073..5cc2a92 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -18,7 +18,7 @@ from .geometry import Point, Size from .interface import Interface from .map import Map from .map.generator import RoomsAndCorridorsGenerator -from .map.generator.room import RandomRectRoomGenerator +from .map.generator.room import RoomGenerator, RandomRectMethod, RectangularRoomMethod from .map.generator.corridor import ElbowCorridorGenerator from .messages import MessageLog from .object import Actor, Entity, Hero, Monster @@ -58,7 +58,14 @@ class Engine: map_size = config.map_size map_generator = RoomsAndCorridorsGenerator( - RandomRectRoomGenerator(size=map_size), + RoomGenerator( + size=map_size, + config=RoomGenerator.Configuration( + rect_method=RandomRectMethod( + size=map_size, + config=RandomRectMethod.Configuration(number_of_rooms=4)), + room_method=RectangularRoomMethod()) + ), ElbowCorridorGenerator()) self.map = Map(config, map_generator) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index bd8b9c9..3cc534b 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -1,8 +1,9 @@ # Eryn Wells +import math import random from dataclasses import dataclass -from typing import List, Optional, TYPE_CHECKING +from typing import Iterable, Iterator, List, Optional, Tuple, TYPE_CHECKING import tcod @@ -20,13 +21,12 @@ class RoomGenerator: @dataclass class Configuration: - number_of_rooms: int = 30 - minimum_room_size: Size = Size(7, 7) - maximum_room_size: Size = Size(20, 20) + rect_method: 'RectMethod' + room_method: 'RoomMethod' - def __init__(self, *, size: Size, config: Optional[Configuration] = None): + def __init__(self, *, size: Size, config: Configuration): self.size = size - self.configuration = config if config else RoomGenerator.Configuration() + self.configuration = config self.rooms: List[Room] = [] @@ -35,27 +35,20 @@ class RoomGenerator: def generate(self): '''Generate rooms and stairs''' - did_generate_rooms = self._generate() + rect_method = self.configuration.rect_method + room_method = self.configuration.room_method - if not did_generate_rooms: + for rect in rect_method.generate(): + room = room_method.room_in_rect(rect) + if not room: + break + self.rooms.append(room) + + if len(self.rooms) == 0: return self._generate_stairs() - def _generate(self) -> bool: - ''' - Generate a list of rooms. - - Subclasses should implement this and fill in their specific map - generation algorithm. - - Returns - ------- - np.ndarray - A two-dimensional array of tiles. Dimensions should match the given size. - ''' - raise NotImplementedError() - # pylint: disable=redefined-builtin def apply(self, map: 'Map'): '''Apply the generated rooms to a tile array''' @@ -105,20 +98,124 @@ class RoomGenerator: tiles[pt.numpy_index] = StairsDown -class OneBigRoomGenerator(RoomGenerator): - '''Generates one big room in the center of the map.''' +class RectMethod: + '''An abstract class defining a method for generating rooms.''' - def _generate(self) -> bool: - if self.rooms: - return True + def __init__(self, *, size: Size): + self.size = size - origin = Point(self.size.width // 4, self.size.height // 4) - size = Size(self.size.width // 2, self.size.height // 2) - room = RectangularRoom(Rect(origin, size)) + def generate(self) -> Iterator[Rect]: + '''Generate rects to place rooms in until there are no more.''' + raise NotImplementedError() - self.rooms.append(room) - return True +class OneBigRoomRectMethod(RectMethod): + ''' + A room generator method that yields one large rectangle centered in the + bounds defined by the zero origin and `self.size`. + ''' + + @dataclass + class Configuration: + ''' + Configuration for a OneBigRoom room generator method. + + ### Attributes + + width_percentage : float + The percentage of overall width to make the room + height_percentage : float + The percentage of overall height to make the room + ''' + width_percentage: float = 0.5 + height_percentage: float = 0.5 + + def __init__(self, *, size: Size, config: Optional[Configuration] = None): + super().__init__(size=size) + self.configuration = config or self.__class__.Configuration() + + def generate(self) -> Iterator[Rect]: + width = self.size.width + height = self.size.height + + size = Size(math.floor(width * self.configuration.width_percentage), + math.floor(height * self.configuration.height_percentage)) + origin = Point((width - size.width) // 2, (height - size.height) // 2) + + yield Rect(origin, size) + + +class RandomRectMethod(RectMethod): + NUMBER_OF_ATTEMPTS_PER_RECT = 30 + + @dataclass + class Configuration: + number_of_rooms: int = 30 + minimum_room_size: Size = Size(7, 7) + maximum_room_size: Size = Size(20, 20) + + def __init__(self, *, size: Size, config: Optional[Configuration] = None): + super().__init__(size=size) + self.configuration = config or self.__class__.Configuration() + self._rects: List[Rect] = [] + + def generate(self) -> Iterator[Rect]: + minimum_room_size = self.configuration.minimum_room_size + maximum_room_size = self.configuration.maximum_room_size + + width_range = (minimum_room_size.width, maximum_room_size.width) + height_range = (minimum_room_size.height, maximum_room_size.height) + + while len(self._rects) < self.configuration.number_of_rooms: + for _ in range(self.__class__.NUMBER_OF_ATTEMPTS_PER_RECT): + size = Size(random.randint(*width_range), random.randint(*height_range)) + origin = Point(random.randint(0, self.size.width - size.width), + random.randint(0, self.size.height - size.height)) + candidate_rect = Rect(origin, size) + + overlaps_any_existing_room = any(candidate_rect.intersects(r) for r in self._rects) + if not overlaps_any_existing_room: + break + else: + return + + self._rects.append(candidate_rect) + yield candidate_rect + + +class RoomMethod: + '''An abstract class defining a method for generating rooms.''' + + def room_in_rect(self, rect: Rect) -> Optional[Room]: + '''Create a Room inside the given Rect.''' + raise NotImplementedError() + + +class RectangularRoomMethod(RoomMethod): + def room_in_rect(self, rect: Rect) -> Optional[Room]: + return RectangularRoom(rect) + + +class OrRoomMethod(RoomMethod): + ''' + A room generator method that picks between several RoomMethods at random + based on a set of probabilities. + ''' + + def __init__(self, methods: Iterable[Tuple[float, RoomMethod]]): + assert sum(m[0] for m in methods) == 1.0 + self.methods = methods + + def room_in_rect(self, rect: Rect) -> Optional[Room]: + factor = random.random() + + threshold = 0 + for method in self.methods: + threshold += method[0] + if factor <= threshold: + return method[1].room_in_rect(rect) + + return None class RandomRectRoomGenerator(RoomGenerator): From e6327deeefe31d8f4304e3e0146d7fa29be4d41d Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 16:53:27 -0800 Subject: [PATCH 212/234] Geometry classes are no longer frozen --- erynrl/geometry.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 0001d56..acb1584 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -7,7 +7,7 @@ from dataclasses import dataclass from typing import Iterator, Optional, overload, Tuple -@dataclass(frozen=True) +@dataclass class Point: '''A two-dimensional point, with coordinates in X and Y axes''' @@ -93,7 +93,7 @@ class Point: return f'(x:{self.x}, y:{self.y})' -@dataclass(frozen=True) +@dataclass class Vector: '''A two-dimensional vector, representing change in position in X and Y axes''' @@ -141,7 +141,7 @@ class Direction: yield Direction.NorthWest -@dataclass(frozen=True) +@dataclass class Size: '''A two-dimensional size, representing size in X (width) and Y (height) axes''' @@ -161,7 +161,7 @@ class Size: return f'(w:{self.width}, h:{self.height})' -@dataclass(frozen=True) +@dataclass class Rect: '''A two-dimensional rectangle, defined by an origin point and size''' From 635aea5e3b592bc75f67a034114dcb255c6c2745 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 18:40:02 -0800 Subject: [PATCH 213/234] Add cellular atomata to the map generator finally! Use the new map generator mechanism to generate rooms via cellular atomata. Create a new CellularAtomatonRoomMethod class that uses the Cellular Atomton class to create a room. Add a FreefromRoom class that draws a room based on an ndarray of tiles. Along the way I discovered I have misunderstood how numpy arrays organize rows and columns. The numpy array creation routines take an 'order' argument that specifies whether arrays should be in C order (row major) or Fortran order (column major). Fortran order lets you index arrays with a more natural [x, y] coordinate order, and that's what the tutorials I've read have shown. So I've been using that. When I was developing the Cellular Atomaton, I wrote some code that assumed row- major order. I think I want to move everything to row-major / C-style, but that will take a bit more time. --- ca.py | 2 +- erynrl/map/generator/cellular_atomata.py | 45 ++++++++++++++--------- erynrl/map/generator/room.py | 45 +++++++++++++++++++++-- erynrl/map/room.py | 46 +++++++++++++++++++----- 4 files changed, 111 insertions(+), 27 deletions(-) diff --git a/ca.py b/ca.py index 3d2ff6e..8035e53 100644 --- a/ca.py +++ b/ca.py @@ -26,7 +26,7 @@ def main(argv): log.init() - bounds = Rect(Point(), Size(20, 20)) + bounds = Rect(Point(), Size(60, 20)) config = CellularAtomataMapGenerator.Configuration() config.number_of_rounds = args.rounds diff --git a/erynrl/map/generator/cellular_atomata.py b/erynrl/map/generator/cellular_atomata.py index c1e2f0b..1fad789 100644 --- a/erynrl/map/generator/cellular_atomata.py +++ b/erynrl/map/generator/cellular_atomata.py @@ -2,14 +2,16 @@ import random from dataclasses import dataclass -from typing import Optional +from typing import Optional, TYPE_CHECKING import numpy as np from ... import log -from ...geometry import Point, Rect -from ..grid import make_grid -from ..tile import Floor, Wall +from ...geometry import Point, Rect, Vector +from ..tile import Empty, Floor, Wall, tile_datatype + +if TYPE_CHECKING: + from .. import Map class CellularAtomataMapGenerator: @@ -38,6 +40,7 @@ class CellularAtomataMapGenerator: Initializer ### Parameters + `bounds` : `Rect` A rectangle representing the bounds of the cellular atomaton `config` : `Optional[Configuration]` @@ -46,7 +49,7 @@ class CellularAtomataMapGenerator: ''' self.bounds = bounds self.configuration = config if config else CellularAtomataMapGenerator.Configuration() - self.tiles = make_grid(bounds.size) + self.tiles = np.full((bounds.size.height, bounds.size.width), fill_value=Empty, dtype=tile_datatype, order='C') def generate(self): ''' @@ -59,14 +62,23 @@ class CellularAtomataMapGenerator: self._fill() self._run_atomaton() + def apply(self, map: 'Map'): + origin = self.bounds.origin + for y, x in np.ndindex(self.tiles.shape): + map_pt = origin + Vector(x, y) + tile = self.tiles[y, x] + if tile == Floor: + map.tiles[map_pt.numpy_index] = tile + def _fill(self): fill_percentage = self.configuration.fill_percentage for y, x in np.ndindex(self.tiles.shape): - self.tiles[x, y] = Floor if random.random() < fill_percentage else Wall + self.tiles[y, x] = Floor if random.random() < fill_percentage else Empty def _run_atomaton(self): - alternate_tiles = make_grid(self.bounds.size) + alternate_tiles = np.full((self.bounds.size.height, self.bounds.size.width), + fill_value=Empty, dtype=tile_datatype, order='C') number_of_rounds = self.configuration.number_of_rounds if number_of_rounds < 1: @@ -100,21 +112,22 @@ class CellularAtomataMapGenerator: # Start with 1 because the point is its own neighbor number_of_neighbors = 1 for neighbor in pt.neighbors: - if neighbor not in self.bounds: - continue + try: + if from_tiles[neighbor.y, neighbor.x] == Floor: + number_of_neighbors += 1 + except IndexError: + pass - if from_tiles[neighbor.x, neighbor.y] == Floor: - number_of_neighbors += 1 - - tile_is_alive = from_tiles[pt.x, pt.y] == Floor + idx = (pt.y, pt.x) + tile_is_alive = from_tiles[idx] == Floor if tile_is_alive and number_of_neighbors >= 5: # Survival - to_tiles[pt.x, pt.y] = Floor + to_tiles[idx] = Floor elif not tile_is_alive and number_of_neighbors >= 5: # Birth - to_tiles[pt.x, pt.y] = Floor + to_tiles[idx] = Floor else: - to_tiles[pt.x, pt.y] = Wall + to_tiles[idx] = Empty def __str__(self): return '\n'.join(''.join(chr(i['light']['ch']) for i in row) for row in self.tiles) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 3cc534b..d0ee4e9 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -5,12 +5,14 @@ import random from dataclasses import dataclass from typing import Iterable, Iterator, List, Optional, Tuple, TYPE_CHECKING +import numpy as np import tcod from ... import log from ...geometry import Point, Rect, Size -from ..room import RectangularRoom, Room -from ..tile import Empty, Floor, StairsDown, StairsUp, Wall +from ..room import FreeformRoom, RectangularRoom, Room +from ..tile import Empty, Floor, StairsDown, StairsUp, Wall, tile_datatype +from .cellular_atomata import CellularAtomataMapGenerator if TYPE_CHECKING: from .. import Map @@ -196,6 +198,45 @@ class RectangularRoomMethod(RoomMethod): return RectangularRoom(rect) +class CellularAtomatonRoomMethod(RoomMethod): + + def __init__(self, cellular_atomaton_config: CellularAtomataMapGenerator.Configuration): + self.cellular_atomaton_configuration = cellular_atomaton_config + + def room_in_rect(self, rect: Rect) -> Optional[Room]: + # The cellular atomaton doesn't generate any walls, just floors and + # emptiness. Inset it by 1 all the way around so that we can draw walls + # around it. + + atomaton_rect = rect.inset_rect(1, 1, 1, 1) + room_generator = CellularAtomataMapGenerator(atomaton_rect, self.cellular_atomaton_configuration) + room_generator.generate() + + # Create a new tile array and copy the result of the atomaton into it, + # then draw walls everywhere that neighbors a floor tile. + + width = rect.width + height = rect.height + + room_tiles = np.full((height, width), fill_value=Empty, dtype=tile_datatype, order='C') + room_tiles[1:height - 1, 1:width - 1] = room_generator.tiles + + for y, x in np.ndindex(room_tiles.shape): + if room_tiles[y, x] == Floor: + continue + + for neighbor in Point(x, y).neighbors: + try: + if room_tiles[neighbor.y, neighbor.x] != Floor: + continue + room_tiles[y, x] = Wall + break + except IndexError: + pass + + return FreeformRoom(rect, room_tiles) + + class OrRoomMethod(RoomMethod): ''' A room generator method that picks between several RoomMethods at random diff --git a/erynrl/map/room.py b/erynrl/map/room.py index 0e8ddce..5228620 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -4,9 +4,12 @@ Implements an abstract Room class, and subclasses that implement it. Rooms are basic components of maps. ''' -from typing import Iterator +from typing import Iterable -from ..geometry import Point, Rect +import numpy as np + +from ..geometry import Point, Rect, Vector +from .tile import Floor, Wall class Room: @@ -21,17 +24,17 @@ class Room: return self.bounds.midpoint @property - def wall_points(self) -> Iterator[Point]: + def wall_points(self) -> Iterable[Point]: '''An iterator over all the points that make up the walls of this room.''' raise NotImplementedError() @property - def floor_points(self) -> Iterator[Point]: + def floor_points(self) -> Iterable[Point]: '''An iterator over all the points that make of the floor of this room''' raise NotImplementedError() @property - def walkable_tiles(self) -> Iterator[Point]: + def walkable_tiles(self) -> Iterable[Point]: '''An iterator over all the points that are walkable in this room.''' raise NotImplementedError() @@ -47,14 +50,14 @@ class RectangularRoom(Room): ''' @property - def walkable_tiles(self) -> Iterator[Point]: + def walkable_tiles(self) -> Iterable[Point]: floor_rect = self.bounds.inset_rect(top=1, right=1, bottom=1, left=1) for y in range(floor_rect.min_y, floor_rect.max_y + 1): for x in range(floor_rect.min_x, floor_rect.max_x + 1): yield Point(x, y) @property - def wall_points(self) -> Iterator[Point]: + def wall_points(self) -> Iterable[Point]: bounds = self.bounds min_y = bounds.min_y @@ -71,7 +74,7 @@ class RectangularRoom(Room): yield Point(max_x, y) @property - def floor_points(self) -> Iterator[Point]: + def floor_points(self) -> Iterable[Point]: inset_bounds = self.bounds.inset_rect(1, 1, 1, 1) min_y = inset_bounds.min_y @@ -85,3 +88,30 @@ class RectangularRoom(Room): def __repr__(self) -> str: return f'{self.__class__.__name__}({self.bounds})' + + +class FreeformRoom(Room): + def __init__(self, bounds: Rect, tiles: np.ndarray): + super().__init__(bounds) + self.tiles = tiles + + @property + def floor_points(self) -> Iterable[Point]: + room_origin_vector = Vector.from_point(self.bounds.origin) + for y, x in np.ndindex(self.tiles.shape): + if self.tiles[y, x] == Floor: + yield Point(x, y) + room_origin_vector + + @property + def wall_points(self) -> Iterable[Point]: + room_origin_vector = Vector.from_point(self.bounds.origin) + for y, x in np.ndindex(self.tiles.shape): + if self.tiles[y, x] == Wall: + yield Point(x, y) + room_origin_vector + + @property + def walkable_tiles(self) -> Iterable[Point]: + room_origin_vector = Vector.from_point(self.bounds.origin) + for y, x in np.ndindex(self.tiles.shape): + if self.tiles[y, x]['walkable']: + yield Point(x, y) + room_origin_vector From edbc76d2ff24fe0a3918dff1a605188b766a57e7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sun, 5 Mar 2023 18:48:51 -0800 Subject: [PATCH 214/234] Catch the ValueError when an AI asks if an out-of-bounds tile is walkable --- erynrl/ai.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erynrl/ai.py b/erynrl/ai.py index ade0b33..f7a30c1 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -77,7 +77,10 @@ class HostileEnemy(AI): directions.remove(direction) new_position = self.entity.position + direction overlaps_existing_entity = any(new_position == ent.position for ent in engine.entities) - tile_is_walkable = engine.map.tile_is_walkable(new_position) + try: + tile_is_walkable = engine.map.tile_is_walkable(new_position) + except ValueError: + tile_is_walkable = False if not overlaps_existing_entity and tile_is_walkable: if engine.map.visible[tuple(self.entity.position)]: log.AI.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) From 040803fe619a8f141718bd2960c74eb341270ed9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 6 Mar 2023 19:23:43 -0800 Subject: [PATCH 215/234] Add Rect.from_raw_values --- erynrl/geometry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index acb1584..050222f 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -163,11 +163,18 @@ class Size: @dataclass class Rect: - '''A two-dimensional rectangle, defined by an origin point and size''' + ''' + A two-dimensional rectangle defined by an origin point and size + ''' origin: Point size: Size + @staticmethod + def from_raw_values(x: int, y: int, width: int, height: int): + '''Create a rect from raw (unpacked from their struct) values''' + return Rect(Point(x, y), Size(width, height)) + @property def min_x(self) -> int: '''Minimum x-value that is still within the bounds of this rectangle. This is the origin's x-value.''' From fd068268f516eab0387dc9107080b61b35ae9490 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 6 Mar 2023 19:33:34 -0800 Subject: [PATCH 216/234] Move the BSP implementation to BSPRectMethod --- erynrl/engine.py | 15 ++- erynrl/log.py | 1 + erynrl/map/generator/room.py | 194 +++++++++++++++++++---------------- erynrl/map/room.py | 3 + 4 files changed, 118 insertions(+), 95 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 5cc2a92..4c50099 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -18,7 +18,8 @@ from .geometry import Point, Size from .interface import Interface from .map import Map from .map.generator import RoomsAndCorridorsGenerator -from .map.generator.room import RoomGenerator, RandomRectMethod, RectangularRoomMethod +from .map.generator.cellular_atomata import CellularAtomataMapGenerator +from .map.generator.room import BSPRectMethod, CellularAtomatonRoomMethod, OrRoomMethod, RoomGenerator, RandomRectMethod, RectangularRoomMethod from .map.generator.corridor import ElbowCorridorGenerator from .messages import MessageLog from .object import Actor, Entity, Hero, Monster @@ -61,10 +62,16 @@ class Engine: RoomGenerator( size=map_size, config=RoomGenerator.Configuration( - rect_method=RandomRectMethod( + rect_method=BSPRectMethod( size=map_size, - config=RandomRectMethod.Configuration(number_of_rooms=4)), - room_method=RectangularRoomMethod()) + config=BSPRectMethod.Configuration(number_of_rooms=30)), + room_method=OrRoomMethod( + methods=[ + (0.2, CellularAtomatonRoomMethod(CellularAtomataMapGenerator.Configuration())), + (0.8, RectangularRoomMethod()) + ] + ) + ) ), ElbowCorridorGenerator()) self.map = Map(config, map_generator) diff --git a/erynrl/log.py b/erynrl/log.py index af8ccdb..8f7e4d1 100644 --- a/erynrl/log.py +++ b/erynrl/log.py @@ -28,6 +28,7 @@ EVENTS = logging.getLogger(_log_name('events')) UI = logging.getLogger(_log_name('ui')) MAP = logging.getLogger(_log_name('map')) +MAP_BSP = logging.getLogger(_log_name('map', 'bsp')) MAP_CELL_ATOM = logging.getLogger(_log_name('map', 'cellular')) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index d0ee4e9..1b7cbee 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -185,6 +185,109 @@ class RandomRectMethod(RectMethod): yield candidate_rect +class BSPRectMethod(RectMethod): + @dataclass + class Configuration: + ''' + Configuration for the binary space partitioning (BSP) Rect method. + + ### Attributes + + number_of_rooms : int + The maximum number of rooms to produce + maximum_room_size : Size + The maximum size of any room + minimum_room_size : Size + The minimum size of any room + room_size_ratio : Tuple[float, float] + A pair of floats indicating the maximum proportion the sides of a + BSP node can have to each other. + + The first value is the horizontal ratio. BSP nodes will never have a + horizontal size (width) bigger than `room_size_ratio[0]` times the + vertical size. + + The second value is the vertical ratio. BSP nodes will never have a + vertical size (height) larger than `room_size_ratio[1]` times the + horizontal size. + + The closer these values are to 1.0, the more square the BSP nodes + will be. + ''' + number_of_rooms: int = 30 + minimum_room_size: Size = Size(7, 7) + maximum_room_size: Size = Size(20, 20) + room_size_ratio: Tuple[float, float] = (1.1, 1.1) + + def __init__(self, *, size: Size, config: Optional[Configuration] = None): + super().__init__(size=size) + self.configuration = config or self.__class__.Configuration() + + def generate(self) -> Iterator[Rect]: + nodes_with_rooms = set() + + minimum_room_size = self.configuration.minimum_room_size + maximum_room_size = self.configuration.maximum_room_size + + # Recursively divide the map into squares of various sizes to place rooms in. + bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) + + # Add 2 to the minimum width and height to account for walls + bsp.split_recursive( + depth=6, + min_width=minimum_room_size.width, + min_height=minimum_room_size.height, + max_horizontal_ratio=self.configuration.room_size_ratio[0], + max_vertical_ratio=self.configuration.room_size_ratio[1]) + + log.MAP_BSP.info('Generating room rects via BSP') + + # Visit all nodes in a level before visiting any of their children + for bsp_node in bsp.level_order(): + node_width = bsp_node.w + node_height = bsp_node.h + + if node_width > maximum_room_size.width or node_height > maximum_room_size.height: + log.MAP_BSP.debug('Node with size (%s, %s) exceeds maximum size %s', + node_width, node_height, maximum_room_size) + continue + + if len(nodes_with_rooms) >= self.configuration.number_of_rooms: + # Made as many rooms as we're allowed. We're done. + log.MAP_BSP.debug("Generated enough rooms (more than %d); we're done", + self.configuration.number_of_rooms) + return + + if any(node in nodes_with_rooms for node in self.__all_parents_of_node(bsp_node)): + # Already made a room for one of this node's parents + log.MAP_BSP.debug('Already made a room for parent of %s', bsp_node) + continue + + try: + probability_of_room = max( + 1.0 / (node_width - minimum_room_size.width), + 1.0 / (node_height - minimum_room_size.height)) + except ZeroDivisionError: + probability_of_room = 1.0 + + log.MAP_BSP.info('Probability of generating room for %s: %f', bsp_node, probability_of_room) + + if random.random() <= probability_of_room: + log.MAP_BSP.info('Yielding room for node %s', bsp_node) + nodes_with_rooms.add(bsp_node) + yield self.__rect_from_bsp_node(bsp_node) + + log.MAP_BSP.info('Finished BSP room rect generation, yielded %d rooms', len(nodes_with_rooms)) + + def __rect_from_bsp_node(self, bsp_node: tcod.bsp.BSP) -> Rect: + return Rect.from_raw_values(bsp_node.x, bsp_node.y, bsp_node.w, bsp_node.h) + + def __all_parents_of_node(self, node: tcod.bsp.BSP | None) -> Iterable[tcod.bsp.BSP]: + while node: + yield node + node = node.parent + + class RoomMethod: '''An abstract class defining a method for generating rooms.''' @@ -292,94 +395,3 @@ class RandomRectRoomGenerator(RoomGenerator): return True -class BSPRoomGenerator(RoomGenerator): - '''Generate a rooms-and-corridors style map with BSP.''' - - def __init__(self, *, size: Size, config: Optional[RoomGenerator.Configuration] = None): - super().__init__(size=size, config=config) - self.rng: tcod.random.Random = tcod.random.Random() - - def _generate(self) -> bool: - if self.rooms: - return True - - minimum_room_size = self.configuration.minimum_room_size - maximum_room_size = self.configuration.maximum_room_size - - # Recursively divide the map into squares of various sizes to place rooms in. - bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) - - # Add 2 to the minimum width and height to account for walls - bsp.split_recursive( - depth=4, - min_width=minimum_room_size.width, - min_height=minimum_room_size.height, - max_horizontal_ratio=1.1, - max_vertical_ratio=1.1) - - # Generate the rooms - rooms: List[Room] = [] - - room_attrname = f'{__class__.__name__}.room' - - for node in bsp.post_order(): - node_bounds = self.__rect_from_bsp_node(node) - - if node.children: - continue - - log.MAP.debug('%s (room) %s', node_bounds, node) - - # Generate a room size between minimum_room_size and maximum_room_size. The minimum value is - # straight-forward, but the maximum value needs to be clamped between minimum_room_size and the size of - # the node. - width_range = ( - minimum_room_size.width, - min(maximum_room_size.width, max( - minimum_room_size.width, node.width - 2)) - ) - height_range = ( - minimum_room_size.height, - min(maximum_room_size.height, max( - minimum_room_size.height, node.height - 2)) - ) - - log.MAP.debug('|-> min room size %s', minimum_room_size) - log.MAP.debug('|-> max room size %s', maximum_room_size) - log.MAP.debug('|-> node size %s x %s', node.width, node.height) - log.MAP.debug('|-> width range %s', width_range) - log.MAP.debug('|-> height range %s', width_range) - - size = Size(self.rng.randint(*width_range), - self.rng.randint(*height_range)) - origin = Point(node.x + self.rng.randint(1, max(1, node.width - size.width - 2)), - node.y + self.rng.randint(1, max(1, node.height - size.height - 2))) - bounds = Rect(origin, size) - - log.MAP.debug('`-> %s', bounds) - - room = RectangularRoom(bounds) - setattr(node, room_attrname, room) - rooms.append(room) - - if not hasattr(node.parent, room_attrname): - setattr(node.parent, room_attrname, room) - elif random.random() < 0.5: - setattr(node.parent, room_attrname, room) - - # Pass up a random child room so that parent nodes can connect subtrees to each other. - parent = node.parent - if parent: - node_room = getattr(node, room_attrname) - if not hasattr(node.parent, room_attrname): - setattr(node.parent, room_attrname, node_room) - elif random.random() < 0.5: - setattr(node.parent, room_attrname, node_room) - - self.rooms = rooms - - return True - - def __rect_from_bsp_node(self, node: tcod.bsp.BSP) -> Rect: - '''Create a Rect from the given BSP node object''' - return Rect(Point(node.x, node.y), Size(node.width, node.height)) diff --git a/erynrl/map/room.py b/erynrl/map/room.py index 5228620..60a3510 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -115,3 +115,6 @@ class FreeformRoom(Room): for y, x in np.ndindex(self.tiles.shape): if self.tiles[y, x]['walkable']: yield Point(x, y) + room_origin_vector + + def __str__(self): + return '\n'.join(''.join(chr(i['light']['ch']) for i in row) for row in self.tiles) From ee1c6f22226b7d6744e42c3de7cb5c05e97d94c7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 6 Mar 2023 19:33:50 -0800 Subject: [PATCH 217/234] Remove the RandomRectRoomGenerator --- erynrl/map/generator/room.py | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 1b7cbee..8872436 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -360,38 +360,3 @@ class OrRoomMethod(RoomMethod): return method[1].room_in_rect(rect) return None - - -class RandomRectRoomGenerator(RoomGenerator): - '''Generate rooms by repeatedly attempting to place rects of random size across the map.''' - - NUMBER_OF_ATTEMPTS_PER_ROOM = 30 - - def _generate(self) -> bool: - number_of_attempts = 0 - - minimum_room_size = self.configuration.minimum_room_size - maximum_room_size = self.configuration.maximum_room_size - - width_range = (minimum_room_size.width, maximum_room_size.width) - height_range = (minimum_room_size.height, maximum_room_size.height) - - while len(self.rooms) < self.configuration.number_of_rooms: - size = Size(random.randint(*width_range), random.randint(*height_range)) - origin = Point(random.randint(0, self.size.width - size.width), - random.randint(0, self.size.height - size.height)) - candidate_room_rect = Rect(origin, size) - - overlaps_any_existing_room = any(candidate_room_rect.intersects(room.bounds) for room in self.rooms) - if not overlaps_any_existing_room: - self.rooms.append(RectangularRoom(candidate_room_rect)) - number_of_attempts = 0 - continue - - number_of_attempts += 1 - if number_of_attempts > RandomRectRoomGenerator.NUMBER_OF_ATTEMPTS_PER_ROOM: - break - - return True - - From 003aedf30e498263697fcc81aa528894f861d8f5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 7 Mar 2023 21:29:05 -0800 Subject: [PATCH 218/234] Restructure event handling Events start in the Interface. The interface gets first crack at any incoming events. If the interface doesn't handle the event, it is given to the engine. The engine has an EngineEventHandler that yields actions just like the event handler prior to this change. The interface's event handler passes events to each window in the interface. Windows can choose to handle events however they like, and they return a bool indicating whether the event was fully handled. --- erynrl/__main__.py | 11 +++--- erynrl/engine.py | 46 +++++++--------------- erynrl/events.py | 75 +++++++----------------------------- erynrl/interface/__init__.py | 67 ++++++++++++++++++++++++-------- erynrl/interface/events.py | 50 ++++++++++++++++++++++++ erynrl/interface/window.py | 64 ++++++++++++++++++++++++++++-- 6 files changed, 197 insertions(+), 116 deletions(-) create mode 100644 erynrl/interface/events.py diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 84d8dfc..9b96c14 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -9,6 +9,7 @@ from . import log from .configuration import Configuration, FontConfiguration, FontConfigurationError from .engine import Engine from .geometry import Size +from .interface import Interface def parse_args(argv, *a, **kw): @@ -45,15 +46,15 @@ def main(argv): configuration = Configuration( console_font_configuration=font_config, - map_size=Size(80, 24), + map_size=Size(80, 40), sandbox=args.sandbox) engine = Engine(configuration) - + interface = Interface(configuration.console_size, engine) tileset = configuration.console_font_configuration.tileset - console = tcod.Console(*configuration.console_size.numpy_shape, order='F') - with tcod.context.new(columns=console.width, rows=console.height, tileset=tileset) as context: - engine.run_event_loop(context, console) + + with tcod.context.new(columns=interface.console.width, rows=interface.console.height, tileset=tileset) as context: + interface.run_event_loop(context) return 0 diff --git a/erynrl/engine.py b/erynrl/engine.py index 4c50099..0477383 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -3,7 +3,7 @@ '''Defines the core game engine.''' import random -from typing import TYPE_CHECKING, List, MutableSet, NoReturn, Optional +from typing import TYPE_CHECKING, List, MutableSet, Optional import tcod @@ -13,20 +13,21 @@ from .actions.action import Action, ActionWithActor from .actions.result import ActionResult from .ai import HostileEnemy from .configuration import Configuration -from .events import GameOverEventHandler, MainGameEventHandler -from .geometry import Point, Size -from .interface import Interface +from .events import EngineEventHandler, GameOverEventHandler +from .geometry import Point from .map import Map from .map.generator import RoomsAndCorridorsGenerator from .map.generator.cellular_atomata import CellularAtomataMapGenerator -from .map.generator.room import BSPRectMethod, CellularAtomatonRoomMethod, OrRoomMethod, RoomGenerator, RandomRectMethod, RectangularRoomMethod +from .map.generator.room import ( + BSPRectMethod, + CellularAtomatonRoomMethod, + OrRoomMethod, + RoomGenerator, + RectangularRoomMethod) from .map.generator.corridor import ElbowCorridorGenerator from .messages import MessageLog from .object import Actor, Entity, Hero, Monster -if TYPE_CHECKING: - from .events import EventHandler - class Engine: '''The main game engine. @@ -76,7 +77,7 @@ class Engine: ElbowCorridorGenerator()) self.map = Map(config, map_generator) - self.event_handler: 'EventHandler' = MainGameEventHandler(self) + self.event_handler = EngineEventHandler(self) self.__current_mouse_point: Optional[Point] = None self.__mouse_path_points: Optional[List[Point]] = None @@ -112,37 +113,16 @@ class Engine: self.update_field_of_view() - # Interface elements - self.interface = Interface(Size(80, 50), self.map, self.message_log) self.message_log.add_message('Greetings adventurer!', fg=(127, 127, 255), stack=False) - def print_to_console(self, console): - '''Print the whole game to the given console.''' - self.map.highlight_points(self.__mouse_path_points or []) - - sorted_entities = sorted(self.entities, key=lambda e: e.render_order.value) - self.interface.update(self.current_turn, self.hero, sorted_entities) - - self.interface.draw(console) - - def run_event_loop(self, context: tcod.context.Context, console: tcod.Console) -> NoReturn: - '''Run the event loop forever. This method never returns.''' - while True: - console.clear() - self.print_to_console(console) - context.present(console) - - self.begin_turn() - self.event_handler.handle_events(context) - self.finish_turn() - def process_input_action(self, action: Action): '''Process an Action from player input''' - if not isinstance(action, ActionWithActor): action.perform(self) return + self.begin_turn() + log.ACTIONS_TREE.info('Processing Hero Actions') log.ACTIONS_TREE.info('|-> %s', action.actor) @@ -160,6 +140,8 @@ class Engine: self.process_entity_actions() self.update_field_of_view() + self.finish_turn() + def process_entity_actions(self): '''Run AI for entities that have them, and process actions from those AIs''' hero_position = self.hero.position diff --git a/erynrl/events.py b/erynrl/events.py index f06634b..9507a11 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -1,65 +1,26 @@ # Eryn Wells -'''Defines event handling mechanisms.''' - from typing import Optional, TYPE_CHECKING import tcod +import tcod.event as tev -from . import log from .actions.action import Action -from .actions.game import ExitAction, RegenerateRoomsAction, BumpAction, WaitAction -from .geometry import Direction, Point +from .actions.game import BumpAction, ExitAction, RegenerateRoomsAction, WaitAction +from .geometry import Direction if TYPE_CHECKING: from .engine import Engine -class EventHandler(tcod.event.EventDispatch[Action]): - '''Abstract event handler class''' +class EngineEventHandler(tev.EventDispatch[Action]): + '''Handles event on behalf of the game engine, dispatching Actions back to the engine.''' def __init__(self, engine: 'Engine'): super().__init__() self.engine = engine - def handle_events(self, context: tcod.context.Context): - '''Wait for events and handle them.''' - for event in tcod.event.wait(): - context.convert_event(event) - self.handle_event(event) - - def handle_event(self, event: tcod.event.Event): - ''' - Handle an event by transforming it into an Action and processing it until it is completed. If the Action - succeeds, also process actions from other Entities. - - Parameters - ---------- - event : tcod.event.Event - The event to handle - ''' - action = self.dispatch(event) - - # Unhandled event. Ignore it. - if not action: - log.EVENTS.debug('Unhandled event: %s', event) - return - - self.engine.process_input_action(action) - - def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: - return ExitAction() - - -class MainGameEventHandler(EventHandler): - ''' - Handler of `tcod` events for the main game. - - Receives input from the player and dispatches actions to the game engine to interat with the hero and other objects - in the game. - ''' - - def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: + def ev_keydown(self, event: tev.KeyDown) -> Optional[Action]: action: Optional[Action] = None hero = self.engine.hero @@ -84,8 +45,6 @@ class MainGameEventHandler(EventHandler): action = BumpAction(hero, Direction.NorthEast) case tcod.event.KeySym.y: action = BumpAction(hero, Direction.NorthWest) - case tcod.event.KeySym.ESCAPE: - action = ExitAction() case tcod.event.KeySym.SPACE: action = RegenerateRoomsAction() case tcod.event.KeySym.PERIOD: @@ -94,22 +53,16 @@ class MainGameEventHandler(EventHandler): return action - def ev_mousemotion(self, event: tcod.event.MouseMotion) -> Optional[Action]: - mouse_point = Point(event.tile.x, event.tile.y) - if not self.engine.map.tile_is_in_bounds(mouse_point): - mouse_point = None - self.engine.update_mouse_point(mouse_point) + def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: + return ExitAction() -class GameOverEventHandler(EventHandler): +class GameOverEventHandler(tev.EventDispatch[Action]): '''When the game is over (the hero dies, the player quits, etc), this event handler takes over.''' - def ev_keydown(self, event: tcod.event.KeyDown) -> Optional[Action]: - action: Optional[Action] = None + def __init__(self, engine: 'Engine'): + super().__init__() + self.engine = engine - sym = event.sym - match sym: - case tcod.event.KeySym.ESCAPE: - action = ExitAction() - - return action + def ev_quit(self, event: tev.Quit) -> Optional[Action]: + return ExitAction() diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index bf33a73..26b151e 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -4,15 +4,18 @@ The game's graphical user interface ''' -from typing import List +from typing import NoReturn +from tcod import event as tev from tcod.console import Console +from tcod.context import Context from .color import HealthBar +from .events import InterfaceEventHandler from .percentage_bar import PercentageBar from .window import Window, MapWindow +from ..engine import Engine from ..geometry import Point, Rect, Size -from ..map import Map from ..messages import MessageLog from ..object import Entity, Hero @@ -20,25 +23,59 @@ from ..object import Entity, Hero class Interface: '''The game's user interface''' - # pylint: disable=redefined-builtin - def __init__(self, size: Size, map: Map, message_log: MessageLog): - self.map_window = MapWindow(Rect(Point(0, 0), Size(size.width, size.height - 5)), map) - self.info_window = InfoWindow(Rect(Point(0, size.height - 5), Size(28, 5))) - self.message_window = MessageLogWindow(Rect(Point(28, size.height - 5), Size(size.width - 28, 5)), message_log) + def __init__(self, size: Size, engine: Engine): + self.engine = engine - def update(self, turn_count: int, hero: Hero, entities: List[Entity]): + self.console = Console(*size.numpy_shape, order='F') + + self.map_window = MapWindow( + Rect.from_raw_values(0, 0, size.width, size.height - 5), + engine.map) + self.info_window = InfoWindow( + Rect.from_raw_values(0, size.height - 5, 28, 5)) + self.message_window = MessageLogWindow( + Rect.from_raw_values(28, size.height - 5, size.width - 28, 5), + engine.message_log) + + self.event_handler = InterfaceEventHandler(self) + + def update(self): '''Update game state that the interface needs to render''' - self.info_window.turn_count = turn_count + self.info_window.turn_count = self.engine.current_turn + + hero = self.engine.hero self.info_window.update_hero(hero) - self.map_window.update_drawable_map_bounds(hero) - self.map_window.entities = entities - def draw(self, console: Console): + sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value) + self.map_window.entities = sorted_entities + + def draw(self): '''Draw the UI to the console''' - self.map_window.draw(console) - self.info_window.draw(console) - self.message_window.draw(console) + self.map_window.draw(self.console) + self.info_window.draw(self.console) + self.message_window.draw(self.console) + + def run_event_loop(self, context: Context) -> NoReturn: + '''Run the event loop forever. This method never returns.''' + while True: + self.update() + + self.console.clear() + self.draw() + context.present(self.console) + + for event in tev.wait(): + did_handle = self.event_handler.dispatch(event) + if did_handle: + continue + + action = self.engine.event_handler.dispatch(event) + if not action: + # The engine didn't handle the event, so just drop it. + continue + + self.engine.process_input_action(action) class InfoWindow(Window): diff --git a/erynrl/interface/events.py b/erynrl/interface/events.py new file mode 100644 index 0000000..bd8e5fc --- /dev/null +++ b/erynrl/interface/events.py @@ -0,0 +1,50 @@ +# Eryn Wells + +'''Defines event handling mechanisms.''' + +from typing import TYPE_CHECKING + +from tcod import event as tev + +if TYPE_CHECKING: + from . import Interface + + +class InterfaceEventHandler(tev.EventDispatch[bool]): + '''The event handler for the user interface.''' + + def __init__(self, interface: 'Interface'): + super().__init__() + self.interface = interface + + self._handlers = [] + self._refresh_handlers() + + def _refresh_handlers(self): + self._handlers = [ + self.interface.map_window.event_handler, + self.interface.message_window.event_handler, + self.interface.info_window.event_handler, + ] + + def ev_keydown(self, event: tev.KeyDown) -> bool: + return self._handle_event(event) + + def ev_keyup(self, event: tev.KeyUp) -> bool: + return self._handle_event(event) + + def ev_mousemotion(self, event: tev.MouseMotion) -> bool: + return self._handle_event(event) + + def ev_mousebuttondown(self, event: tev.MouseButtonDown) -> bool: + return self._handle_event(event) + + def ev_mousebuttonup(self, event: tev.MouseButtonUp) -> bool: + return self._handle_event(event) + + def _handle_event(self, event: tev.Event) -> bool: + for handler in self._handlers: + if handler and handler.dispatch(event): + return True + + return False diff --git a/erynrl/interface/window.py b/erynrl/interface/window.py index 9f67113..61d0e5e 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window.py @@ -1,8 +1,9 @@ # Eryn Wells -from typing import List +from typing import List, Optional import numpy as np +from tcod import event as tev from tcod.console import Console from .. import log @@ -12,11 +13,46 @@ from ..object import Entity, Hero class Window: - '''A user interface window. It can be framed.''' + '''A user interface window. It can be framed and it can handle events.''' - def __init__(self, bounds: Rect, *, framed: bool = True): + class EventHandler(tev.EventDispatch[bool]): + ''' + Handles events for a Window. Event dispatch methods return True if the event + was handled and no further action is needed. + ''' + + def __init__(self, window: 'Window'): + super().__init__() + self.window = window + + def mouse_point_for_event(self, event: tev.MouseState) -> Point: + ''' + Return the mouse point in tiles for a window event. Raises a ValueError + if the event is not a mouse event. + ''' + if not isinstance(event, tev.MouseState): + raise ValueError("Can't get mouse point for non-mouse event") + + return Point(event.tile.x, event.tile.y) + + def ev_keydown(self, event: tev.KeyDown) -> bool: + return False + + def ev_keyup(self, event: tev.KeyUp) -> bool: + return False + + def ev_mousemotion(self, event: tev.MouseMotion) -> bool: + mouse_point = self.mouse_point_for_event(event) + + if mouse_point not in self.window.bounds: + return False + + return False + + def __init__(self, bounds: Rect, *, framed: bool = True, event_handler: Optional['EventHandler'] = None): self.bounds = bounds self.is_framed = framed + self.event_handler = event_handler or self.__class__.EventHandler(self) @property def drawable_bounds(self) -> Rect: @@ -28,6 +64,14 @@ class Window: return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds + def convert_console_point(self, point: Point) -> Optional[Point]: + ''' + Converts a point in console coordinates to window-relative coordinates. + If the point is out of bounds of the window, return None. + ''' + converted_point = point - Vector.from_point(self.bounds.origin) + return converted_point if converted_point in self.bounds else None + def draw(self, console: Console): '''Draw the window to the conole''' if self.is_framed: @@ -46,6 +90,20 @@ class Window: class MapWindow(Window): '''A Window that displays a game map''' + class EventHandler(Window.EventHandler): + '''An event handler for the MapWindow.''' + + def ev_mousemotion(self, event: tev.MouseMotion) -> bool: + mouse_point = self.window.convert_console_point(self.mouse_point_for_event(event)) + if not mouse_point: + return False + + # TODO: Convert window point to map point + # TODO: Perform a path finding operation from the hero to the mouse point + # TODO: Highlight those points on the map + + return False + # pylint: disable=redefined-builtin def __init__(self, bounds: Rect, map: Map, **kwargs): super().__init__(bounds, **kwargs) From 1e678ff47d59b9882b0a7bb98efcd132a492a2c9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 7 Mar 2023 21:30:04 -0800 Subject: [PATCH 219/234] Promote .ui and .visible logging to WARN --- logging_config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logging_config.json b/logging_config.json index 41026d4..acf206a 100644 --- a/logging_config.json +++ b/logging_config.json @@ -63,13 +63,13 @@ "propagate": false }, "erynrl.ui": { - "level": "DEBUG", + "level": "WARN", "handlers": [ "console" ] }, "erynrl.visible": { - "level": "DEBUG", + "level": "WARN", "handlers": [ "console" ] From a8bbc47668ef5517e3ed0e54a93a68fdc7f6b4fb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 7 Mar 2023 21:44:01 -0800 Subject: [PATCH 220/234] Move all interface Windows to their own modules in interface.window --- erynrl/interface/__init__.py | 56 +----------- erynrl/interface/window/__init__.py | 83 +++++++++++++++++ erynrl/interface/window/info.py | 45 ++++++++++ erynrl/interface/{window.py => window/map.py} | 88 ++----------------- erynrl/interface/window/message_log.py | 21 +++++ 5 files changed, 160 insertions(+), 133 deletions(-) create mode 100644 erynrl/interface/window/__init__.py create mode 100644 erynrl/interface/window/info.py rename erynrl/interface/{window.py => window/map.py} (67%) create mode 100644 erynrl/interface/window/message_log.py diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 26b151e..d843d44 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -10,14 +10,12 @@ from tcod import event as tev from tcod.console import Console from tcod.context import Context -from .color import HealthBar from .events import InterfaceEventHandler -from .percentage_bar import PercentageBar -from .window import Window, MapWindow +from .window.info import InfoWindow +from .window.map import MapWindow +from .window.message_log import MessageLogWindow from ..engine import Engine -from ..geometry import Point, Rect, Size -from ..messages import MessageLog -from ..object import Entity, Hero +from ..geometry import Rect, Size class Interface: @@ -76,49 +74,3 @@ class Interface: continue self.engine.process_input_action(action) - - -class InfoWindow(Window): - '''A window that displays information about the player''' - - def __init__(self, bounds: Rect): - super().__init__(bounds, framed=True) - - self.turn_count: int = 0 - - drawable_area = self.drawable_bounds - self.hit_points_bar = PercentageBar( - position=Point(drawable_area.min_x + 6, drawable_area.min_y), - width=20, - colors=list(HealthBar.bar_colors())) - - def update_hero(self, hero: Hero): - '''Update internal state for the hero''' - assert hero.fighter - - fighter = hero.fighter - hp, max_hp = fighter.hit_points, fighter.maximum_hit_points - - self.hit_points_bar.percent_filled = hp / max_hp - - def draw(self, console): - super().draw(console) - - drawable_bounds = self.drawable_bounds - console.print(x=drawable_bounds.min_x + 2, y=drawable_bounds.min_y, string='HP:') - self.hit_points_bar.render_to_console(console) - - if self.turn_count: - console.print(x=drawable_bounds.min_x, y=drawable_bounds.min_y + 1, string=f'Turn: {self.turn_count}') - - -class MessageLogWindow(Window): - '''A window that displays a list of messages''' - - def __init__(self, bounds: Rect, message_log: MessageLog): - super().__init__(bounds, framed=True) - self.message_log = message_log - - def draw(self, console): - super().draw(console) - self.message_log.render_to_console(console, self.drawable_bounds) diff --git a/erynrl/interface/window/__init__.py b/erynrl/interface/window/__init__.py new file mode 100644 index 0000000..bde8a08 --- /dev/null +++ b/erynrl/interface/window/__init__.py @@ -0,0 +1,83 @@ +# Eryn Wells + +from typing import Optional + +from tcod import event as tev +from tcod.console import Console + +from ...geometry import Point, Rect, Vector + + +class Window: + '''A user interface window. It can be framed and it can handle events.''' + + class EventHandler(tev.EventDispatch[bool]): + ''' + Handles events for a Window. Event dispatch methods return True if the event + was handled and no further action is needed. + ''' + + def __init__(self, window: 'Window'): + super().__init__() + self.window = window + + def mouse_point_for_event(self, event: tev.MouseState) -> Point: + ''' + Return the mouse point in tiles for a window event. Raises a ValueError + if the event is not a mouse event. + ''' + if not isinstance(event, tev.MouseState): + raise ValueError("Can't get mouse point for non-mouse event") + + return Point(event.tile.x, event.tile.y) + + def ev_keydown(self, event: tev.KeyDown) -> bool: + return False + + def ev_keyup(self, event: tev.KeyUp) -> bool: + return False + + def ev_mousemotion(self, event: tev.MouseMotion) -> bool: + mouse_point = self.mouse_point_for_event(event) + + if mouse_point not in self.window.bounds: + return False + + return False + + def __init__(self, bounds: Rect, *, framed: bool = True, event_handler: Optional['EventHandler'] = None): + self.bounds = bounds + self.is_framed = framed + self.event_handler = event_handler or self.__class__.EventHandler(self) + + @property + def drawable_bounds(self) -> Rect: + ''' + The bounds of the window that is drawable, inset by its frame if + `is_framed` is `True`. + ''' + if self.is_framed: + return self.bounds.inset_rect(1, 1, 1, 1) + return self.bounds + + def convert_console_point(self, point: Point) -> Optional[Point]: + ''' + Converts a point in console coordinates to window-relative coordinates. + If the point is out of bounds of the window, return None. + ''' + converted_point = point - Vector.from_point(self.bounds.origin) + return converted_point if converted_point in self.bounds else None + + def draw(self, console: Console): + '''Draw the window to the conole''' + if self.is_framed: + console.draw_frame( + self.bounds.origin.x, + self.bounds.origin.y, + self.bounds.size.width, + self.bounds.size.height) + + drawable_bounds = self.drawable_bounds + console.draw_rect(drawable_bounds.min_x, drawable_bounds.min_y, + drawable_bounds.width, drawable_bounds.height, + ord(' '), (255, 255, 255), (0, 0, 0)) diff --git a/erynrl/interface/window/info.py b/erynrl/interface/window/info.py new file mode 100644 index 0000000..6252fdc --- /dev/null +++ b/erynrl/interface/window/info.py @@ -0,0 +1,45 @@ +# Eryn Wells + +''' +Declares the InfoWindow. +''' + +from . import Window +from ..color import HealthBar +from ..percentage_bar import PercentageBar +from ...geometry import Point, Rect +from ...object import Hero + + +class InfoWindow(Window): + '''A window that displays information about the player''' + + def __init__(self, bounds: Rect): + super().__init__(bounds, framed=True) + + self.turn_count: int = 0 + + drawable_area = self.drawable_bounds + self.hit_points_bar = PercentageBar( + position=Point(drawable_area.min_x + 6, drawable_area.min_y), + width=20, + colors=list(HealthBar.bar_colors())) + + def update_hero(self, hero: Hero): + '''Update internal state for the hero''' + assert hero.fighter + + fighter = hero.fighter + hp, max_hp = fighter.hit_points, fighter.maximum_hit_points + + self.hit_points_bar.percent_filled = hp / max_hp + + def draw(self, console): + super().draw(console) + + drawable_bounds = self.drawable_bounds + console.print(x=drawable_bounds.min_x + 2, y=drawable_bounds.min_y, string='HP:') + self.hit_points_bar.render_to_console(console) + + if self.turn_count: + console.print(x=drawable_bounds.min_x, y=drawable_bounds.min_y + 1, string=f'Turn: {self.turn_count}') diff --git a/erynrl/interface/window.py b/erynrl/interface/window/map.py similarity index 67% rename from erynrl/interface/window.py rename to erynrl/interface/window/map.py index 61d0e5e..118b0f6 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window/map.py @@ -1,90 +1,16 @@ # Eryn Wells -from typing import List, Optional +from typing import List import numpy as np -from tcod import event as tev +import tcod.event as tev from tcod.console import Console -from .. import log -from ..geometry import Point, Rect, Size, Vector -from ..map import Map -from ..object import Entity, Hero - - -class Window: - '''A user interface window. It can be framed and it can handle events.''' - - class EventHandler(tev.EventDispatch[bool]): - ''' - Handles events for a Window. Event dispatch methods return True if the event - was handled and no further action is needed. - ''' - - def __init__(self, window: 'Window'): - super().__init__() - self.window = window - - def mouse_point_for_event(self, event: tev.MouseState) -> Point: - ''' - Return the mouse point in tiles for a window event. Raises a ValueError - if the event is not a mouse event. - ''' - if not isinstance(event, tev.MouseState): - raise ValueError("Can't get mouse point for non-mouse event") - - return Point(event.tile.x, event.tile.y) - - def ev_keydown(self, event: tev.KeyDown) -> bool: - return False - - def ev_keyup(self, event: tev.KeyUp) -> bool: - return False - - def ev_mousemotion(self, event: tev.MouseMotion) -> bool: - mouse_point = self.mouse_point_for_event(event) - - if mouse_point not in self.window.bounds: - return False - - return False - - def __init__(self, bounds: Rect, *, framed: bool = True, event_handler: Optional['EventHandler'] = None): - self.bounds = bounds - self.is_framed = framed - self.event_handler = event_handler or self.__class__.EventHandler(self) - - @property - def drawable_bounds(self) -> Rect: - ''' - The bounds of the window that is drawable, inset by its frame if - `is_framed` is `True`. - ''' - if self.is_framed: - return self.bounds.inset_rect(1, 1, 1, 1) - return self.bounds - - def convert_console_point(self, point: Point) -> Optional[Point]: - ''' - Converts a point in console coordinates to window-relative coordinates. - If the point is out of bounds of the window, return None. - ''' - converted_point = point - Vector.from_point(self.bounds.origin) - return converted_point if converted_point in self.bounds else None - - def draw(self, console: Console): - '''Draw the window to the conole''' - if self.is_framed: - console.draw_frame( - self.bounds.origin.x, - self.bounds.origin.y, - self.bounds.size.width, - self.bounds.size.height) - - drawable_bounds = self.drawable_bounds - console.draw_rect(drawable_bounds.min_x, drawable_bounds.min_y, - drawable_bounds.width, drawable_bounds.height, - ord(' '), (255, 255, 255), (0, 0, 0)) +from . import Window +from ... import log +from ...geometry import Point, Rect, Size, Vector +from ...map import Map +from ...object import Entity, Hero class MapWindow(Window): diff --git a/erynrl/interface/window/message_log.py b/erynrl/interface/window/message_log.py new file mode 100644 index 0000000..13d6091 --- /dev/null +++ b/erynrl/interface/window/message_log.py @@ -0,0 +1,21 @@ +# Eryn Wells + +''' +Declares the MessageLogWindow. +''' + +from . import Window +from ...geometry import Rect +from ...messages import MessageLog + + +class MessageLogWindow(Window): + '''A window that displays a list of messages''' + + def __init__(self, bounds: Rect, message_log: MessageLog): + super().__init__(bounds, framed=True) + self.message_log = message_log + + def draw(self, console): + super().draw(console) + self.message_log.render_to_console(console, self.drawable_bounds) From 2d82d9834fc18766fc2e80092d5b98234d9064d0 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 7 Mar 2023 22:18:31 -0800 Subject: [PATCH 221/234] Do pathfinding from the hero to the mouse point It works finally! And uses A*! --- erynrl/interface/__init__.py | 6 ++-- erynrl/interface/window/__init__.py | 8 +++-- erynrl/interface/window/map.py | 46 ++++++++++++++++------------- erynrl/map/__init__.py | 17 +++++------ 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index d843d44..3375cd7 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -28,7 +28,8 @@ class Interface: self.map_window = MapWindow( Rect.from_raw_values(0, 0, size.width, size.height - 5), - engine.map) + engine.map, + engine.hero) self.info_window = InfoWindow( Rect.from_raw_values(0, size.height - 5, 28, 5)) self.message_window = MessageLogWindow( @@ -43,7 +44,7 @@ class Interface: hero = self.engine.hero self.info_window.update_hero(hero) - self.map_window.update_drawable_map_bounds(hero) + self.map_window.update_drawable_map_bounds() sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value) self.map_window.entities = sorted_entities @@ -64,6 +65,7 @@ class Interface: context.present(self.console) for event in tev.wait(): + context.convert_event(event) did_handle = self.event_handler.dispatch(event) if did_handle: continue diff --git a/erynrl/interface/window/__init__.py b/erynrl/interface/window/__init__.py index bde8a08..3119246 100644 --- a/erynrl/interface/window/__init__.py +++ b/erynrl/interface/window/__init__.py @@ -1,23 +1,25 @@ # Eryn Wells -from typing import Optional +from typing import Generic, Optional, TypeVar from tcod import event as tev from tcod.console import Console from ...geometry import Point, Rect, Vector +WindowT = TypeVar('WindowT', bound='Window') + class Window: '''A user interface window. It can be framed and it can handle events.''' - class EventHandler(tev.EventDispatch[bool]): + class EventHandler(tev.EventDispatch[bool], Generic[WindowT]): ''' Handles events for a Window. Event dispatch methods return True if the event was handled and no further action is needed. ''' - def __init__(self, window: 'Window'): + def __init__(self, window: WindowT): super().__init__() self.window = window diff --git a/erynrl/interface/window/map.py b/erynrl/interface/window/map.py index 118b0f6..a7ab6ce 100644 --- a/erynrl/interface/window/map.py +++ b/erynrl/interface/window/map.py @@ -1,6 +1,6 @@ # Eryn Wells -from typing import List +from typing import List, Optional import numpy as np import tcod.event as tev @@ -16,7 +16,7 @@ from ...object import Entity, Hero class MapWindow(Window): '''A Window that displays a game map''' - class EventHandler(Window.EventHandler): + class EventHandler(Window.EventHandler['MapWindow']): '''An event handler for the MapWindow.''' def ev_mousemotion(self, event: tev.MouseMotion) -> bool: @@ -24,23 +24,40 @@ class MapWindow(Window): if not mouse_point: return False - # TODO: Convert window point to map point - # TODO: Perform a path finding operation from the hero to the mouse point - # TODO: Highlight those points on the map + log.UI.info('Mouse point in window %s', mouse_point) + + hero = self.window.hero + if not hero: + return False + + map_point = self.window.convert_window_point_to_map(mouse_point) + log.UI.info('Mouse point in map %s', map_point) + + map_ = self.window.map + path = map_.find_walkable_path_from_point_to_point(hero.position, map_point) + map_.highlight_points(path) return False # pylint: disable=redefined-builtin - def __init__(self, bounds: Rect, map: Map, **kwargs): - super().__init__(bounds, **kwargs) + def __init__(self, bounds: Rect, map: Map, hero: Hero, **kwargs): + super().__init__(bounds, event_handler=self.__class__.EventHandler(self), **kwargs) self.map = map self.drawable_map_bounds = map.bounds + self.hero = hero self.entities: List[Entity] = [] self._draw_bounds = self.drawable_bounds - def update_drawable_map_bounds(self, hero: Hero): + def convert_window_point_to_map(self, point: Point) -> Point: + ''' + Convert a point in window coordinates to a point relative to the map's + origin point. + ''' + return point - Vector.from_point(self._draw_bounds.origin) + + def update_drawable_map_bounds(self): ''' Figure out what portion of the map is drawable and update `self.drawable_map_bounds`. This method attempts to keep the hero @@ -59,7 +76,7 @@ class MapWindow(Window): return # Attempt to keep the player centered in the viewport. - hero_point = hero.position + hero_point = self.hero.position if viewport_is_wider_than_map: x = 0 @@ -115,8 +132,6 @@ class MapWindow(Window): drawable_map_bounds = self.drawable_map_bounds drawable_bounds = self.drawable_bounds - log.UI.info('Drawing map') - map_slice = np.s_[ drawable_map_bounds.min_x: drawable_map_bounds.max_x + 1, drawable_map_bounds.min_y: drawable_map_bounds.max_y + 1] @@ -126,19 +141,12 @@ class MapWindow(Window): console_draw_bounds.min_x: console_draw_bounds.max_x + 1, console_draw_bounds.min_y: console_draw_bounds.max_y + 1] - log.UI.debug('Map bounds=%s, slice=%s', drawable_map_bounds, map_slice) - log.UI.debug('Console bounds=%s, slice=%s', drawable_bounds, console_slice) - console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice] - log.UI.info('Done drawing map') - def _draw_entities(self, console): map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin) draw_bounds_vector = Vector.from_point(self._draw_bounds.origin) - log.UI.info('Drawing entities') - for ent in self.entities: # Only draw entities that are in the field of view if not self.map.point_is_visible(ent.position): @@ -161,5 +169,3 @@ class MapWindow(Window): string=ent.symbol, fg=ent.foreground, bg=tuple(map_tile_at_entity_position['bg'][:3])) - - log.UI.info('Done drawing entities') diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index c7c71bd..7eb80f2 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -111,16 +111,13 @@ class Map: for pt in points: self.highlighted[pt.x, pt.y] = True - def print_to_console(self, console: tcod.Console, bounds: Rect) -> None: - '''Render the map to the console.''' - size = self.size - - # If a tile is in the visible array, draw it with the "light" color. If it's not, but it's in the explored - # array, draw it with the "dark" color. Otherwise, draw it as Empty. - console.tiles_rgb[0:size.width, 0:size.height] = np.select( - condlist=[self.highlighted, self.visible, self.explored], - choicelist=[self.tiles['highlighted'], self.tiles['light'], self.tiles['dark']], - default=Shroud) + def find_walkable_path_from_point_to_point(self, point_a: Point, point_b: Point) -> Iterable[Point]: + ''' + Find a path between point A and point B using tcod's A* implementation. + ''' + a_star = tcod.path.AStar(self.tiles['walkable']) + path = a_star.get_path(point_a.x, point_a.y, point_b.x, point_b.y) + return map(lambda t: Point(t[0], t[1]), path) def __str__(self): string = '' From 7ee790e25e8554c3f171c9929618983767cad9ad Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 7 Mar 2023 22:27:21 -0800 Subject: [PATCH 222/234] Remove all the old mouse point stuff from Engine --- erynrl/engine.py | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 0477383..b28424a 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -3,7 +3,7 @@ '''Defines the core game engine.''' import random -from typing import TYPE_CHECKING, List, MutableSet, Optional +from typing import MutableSet import tcod @@ -14,7 +14,6 @@ from .actions.result import ActionResult from .ai import HostileEnemy from .configuration import Configuration from .events import EngineEventHandler, GameOverEventHandler -from .geometry import Point from .map import Map from .map.generator import RoomsAndCorridorsGenerator from .map.generator.cellular_atomata import CellularAtomataMapGenerator @@ -79,9 +78,6 @@ class Engine: self.event_handler = EngineEventHandler(self) - self.__current_mouse_point: Optional[Point] = None - self.__mouse_path_points: Optional[List[Point]] = None - self.entities: MutableSet[Entity] = set() try: @@ -126,9 +122,6 @@ class Engine: log.ACTIONS_TREE.info('Processing Hero Actions') log.ACTIONS_TREE.info('|-> %s', action.actor) - # Clear the mouse path highlight before handling actions. - self.__mouse_path_points = None - result = self._perform_action_until_done(action) # Player's action failed, don't proceed with turn. @@ -217,34 +210,6 @@ class Engine: # Add visible tiles to the explored grid self.map.explored |= self.map.visible - def update_mouse_point(self, mouse_point: Optional[Point]): - if mouse_point == self.__current_mouse_point: - return - - should_render_mouse_path = ( - mouse_point - and self.map.tile_is_in_bounds(mouse_point) - and self.map.tile_is_walkable(mouse_point)) - - if not should_render_mouse_path: - self.__current_mouse_point = None - self.__mouse_path_points = None - return - - self.__current_mouse_point = mouse_point - - path_from_hero_to_mouse_point = tcod.los.bresenham(tuple(self.hero.position), tuple(self.__current_mouse_point)) - mouse_path_points = [Point(x, y) for x, y in path_from_hero_to_mouse_point.tolist()] - - all_mouse_path_points_are_walkable = all( - self.map.tile_is_walkable(pt) and self.map.point_is_explored(pt) for pt in mouse_path_points) - if not all_mouse_path_points_are_walkable: - self.__current_mouse_point = None - self.__mouse_path_points = None - return - - self.__mouse_path_points = mouse_path_points - def begin_turn(self) -> None: '''Begin the current turn''' if self.did_begin_turn: From eda44a879201459e07c459bf4e5e8afbe0253ce4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Wed, 8 Mar 2023 08:57:20 -0800 Subject: [PATCH 223/234] Rename the Map's Point question methods to use "point" instead of "tile" --- erynrl/actions/game.py | 4 ++-- erynrl/ai.py | 6 +++--- erynrl/engine.py | 2 +- erynrl/map/__init__.py | 13 ++++++------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index e793005..e014c1f 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -71,8 +71,8 @@ class BumpAction(MoveAction): def perform(self, engine: 'Engine') -> ActionResult: new_position = self.actor.position + self.direction - position_is_in_bounds = engine.map.tile_is_in_bounds(new_position) - position_is_walkable = engine.map.tile_is_walkable(new_position) + position_is_in_bounds = engine.map.point_is_in_bounds(new_position) + position_is_walkable = engine.map.point_is_walkable(new_position) for ent in engine.entities: if new_position != ent.position or not ent.blocks_movement: diff --git a/erynrl/ai.py b/erynrl/ai.py index f7a30c1..cca9331 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -78,10 +78,10 @@ class HostileEnemy(AI): new_position = self.entity.position + direction overlaps_existing_entity = any(new_position == ent.position for ent in engine.entities) try: - tile_is_walkable = engine.map.tile_is_walkable(new_position) + point_is_walkable = engine.map.point_is_walkable(new_position) except ValueError: - tile_is_walkable = False - if not overlaps_existing_entity and tile_is_walkable: + point_is_walkable = False + if not overlaps_existing_entity and point_is_walkable: if engine.map.visible[tuple(self.entity.position)]: log.AI.info('Hero is NOT visible to %s, bumping %s randomly', self.entity, direction) action = BumpAction(self.entity, direction) diff --git a/erynrl/engine.py b/erynrl/engine.py index b28424a..0bdeca0 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -17,13 +17,13 @@ from .events import EngineEventHandler, GameOverEventHandler from .map import Map from .map.generator import RoomsAndCorridorsGenerator from .map.generator.cellular_atomata import CellularAtomataMapGenerator +from .map.generator.corridor import ElbowCorridorGenerator from .map.generator.room import ( BSPRectMethod, CellularAtomatonRoomMethod, OrRoomMethod, RoomGenerator, RectangularRoomMethod) -from .map.generator.corridor import ElbowCorridorGenerator from .messages import MessageLog from .object import Actor, Entity, Hero, Monster diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index 7eb80f2..fb1ae32 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -9,7 +9,6 @@ import random from typing import Iterable import numpy as np -import numpy.typing as npt import tcod from ..configuration import Configuration @@ -82,25 +81,25 @@ class Map: self.tiles.shape) if self.tiles[x, y]['walkable']] return random.choice(self.__walkable_points) - def tile_is_in_bounds(self, point: Point) -> bool: + def point_is_in_bounds(self, point: Point) -> bool: '''Return True if the given point is inside the bounds of the map''' return 0 <= point.x < self.size.width and 0 <= point.y < self.size.height - def tile_is_walkable(self, point: Point) -> bool: + def point_is_walkable(self, point: Point) -> bool: '''Return True if the tile at the given point is walkable''' - if not self.tile_is_in_bounds(point): + if not self.point_is_in_bounds(point): raise ValueError(f'Point {point!s} is not in bounds') return self.tiles[point.numpy_index]['walkable'] def point_is_visible(self, point: Point) -> bool: '''Return True if the point is visible to the player''' - if not self.tile_is_in_bounds(point): + if not self.point_is_in_bounds(point): raise ValueError(f'Point {point!s} is not in bounds') return self.visible[point.numpy_index] def point_is_explored(self, point: Point) -> bool: '''Return True if the tile at the given point has been explored by the player''' - if not self.tile_is_in_bounds(point): + if not self.point_is_in_bounds(point): raise ValueError(f'Point {point!s} is not in bounds') return self.explored[point.numpy_index] @@ -109,7 +108,7 @@ class Map: self.highlighted.fill(False) for pt in points: - self.highlighted[pt.x, pt.y] = True + self.highlighted[pt.numpy_index] = True def find_walkable_path_from_point_to_point(self, point_a: Point, point_b: Point) -> Iterable[Point]: ''' From 879e0c680dfaca6c09eb17b89bc2f273aaee4fb4 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Mar 2023 22:56:00 -0800 Subject: [PATCH 224/234] Pylint: allow masking the 'map' builtin --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index cf56258..16d5c06 100644 --- a/.pylintrc +++ b/.pylintrc @@ -251,7 +251,7 @@ additional-builtins= allow-global-unused-variables=yes # List of names allowed to shadow builtins -allowed-redefined-builtins= +allowed-redefined-builtins=map # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. From 1018febeab433b7b9a9e4dde59de374d978a4ac7 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Mar 2023 22:56:50 -0800 Subject: [PATCH 225/234] Fix a bug in Rect.__contains_point! It was comparing point.y with the Rect's min_x. --- erynrl/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index 050222f..adc9b90 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -300,7 +300,7 @@ class Rect: def __contains_point(self, pt: Point) -> bool: return (pt.x >= self.min_x and pt.x <= self.max_x - and pt.y >= self.min_x and pt.y <= self.max_y) + and pt.y >= self.min_y and pt.y <= self.max_y) def __contains_rect(self, other: 'Rect') -> bool: return (self.min_x <= other.min_x From 078520678d8723d935d0b40970aa3bef777123c2 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Mar 2023 23:53:08 -0800 Subject: [PATCH 226/234] Figure out (finally!) the mouse coordinates in the MapWindow --- erynrl/geometry.py | 13 ++- erynrl/interface/__init__.py | 1 - erynrl/interface/window/__init__.py | 26 ++++-- erynrl/interface/window/map.py | 130 ++++++++++++++++++---------- 4 files changed, 112 insertions(+), 58 deletions(-) diff --git a/erynrl/geometry.py b/erynrl/geometry.py index adc9b90..ada08fc 100644 --- a/erynrl/geometry.py +++ b/erynrl/geometry.py @@ -205,6 +205,16 @@ class Rect: '''Maximum y-value that is still within the bounds of this rectangle.''' return self.origin.y + self.size.height - 1 + @property + def end_x(self) -> int: + '''X-value beyond the end of the rectangle.''' + return self.origin.x + self.size.width + + @property + def end_y(self) -> int: + '''Y-value beyond the end of the rectangle.''' + return self.origin.y + self.size.height + @property def width(self) -> int: '''The width of the rectangle. A convenience property for accessing `self.size.width`.''' @@ -299,8 +309,7 @@ class Rect: raise TypeError(f'{self.__class__.__name__} cannot contain value of type {other.__class__.__name__}') def __contains_point(self, pt: Point) -> bool: - return (pt.x >= self.min_x and pt.x <= self.max_x - and pt.y >= self.min_y and pt.y <= self.max_y) + return self.min_x <= pt.x <= self.max_x and self.min_y <= pt.y <= self.max_y def __contains_rect(self, other: 'Rect') -> bool: return (self.min_x <= other.min_x diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 3375cd7..096f2f9 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -44,7 +44,6 @@ class Interface: hero = self.engine.hero self.info_window.update_hero(hero) - self.map_window.update_drawable_map_bounds() sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value) self.map_window.entities = sorted_entities diff --git a/erynrl/interface/window/__init__.py b/erynrl/interface/window/__init__.py index 3119246..aedd067 100644 --- a/erynrl/interface/window/__init__.py +++ b/erynrl/interface/window/__init__.py @@ -1,5 +1,9 @@ # Eryn Wells +''' +Declares the Window class. +''' + from typing import Generic, Optional, TypeVar from tcod import event as tev @@ -49,26 +53,34 @@ class Window: def __init__(self, bounds: Rect, *, framed: bool = True, event_handler: Optional['EventHandler'] = None): self.bounds = bounds + '''The window's bounds in console coordinates''' + self.is_framed = framed + '''A `bool` indicating whether the window has a frame''' + self.event_handler = event_handler or self.__class__.EventHandler(self) + '''The window's event handler''' @property def drawable_bounds(self) -> Rect: ''' - The bounds of the window that is drawable, inset by its frame if - `is_framed` is `True`. + A rectangle in console coordinates defining the area of the window that + is drawable, inset by the window's frame if it has one. ''' if self.is_framed: return self.bounds.inset_rect(1, 1, 1, 1) return self.bounds - def convert_console_point(self, point: Point) -> Optional[Point]: + def convert_console_point_to_window(self, point: Point, *, use_drawable_bounds: bool = False) -> Optional[Point]: ''' - Converts a point in console coordinates to window-relative coordinates. - If the point is out of bounds of the window, return None. + Converts a point in console coordinates to window coordinates. If the + point is out of bounds of the window, return None. ''' - converted_point = point - Vector.from_point(self.bounds.origin) - return converted_point if converted_point in self.bounds else None + bounds = self.drawable_bounds if use_drawable_bounds else self.bounds + if point in bounds: + return point - Vector.from_point(bounds.origin) + + return None def draw(self, console: Console): '''Draw the window to the conole''' diff --git a/erynrl/interface/window/map.py b/erynrl/interface/window/map.py index a7ab6ce..d8ac561 100644 --- a/erynrl/interface/window/map.py +++ b/erynrl/interface/window/map.py @@ -1,6 +1,10 @@ # Eryn Wells -from typing import List, Optional +''' +Declares the MapWindow class. +''' + +from typing import List import numpy as np import tcod.event as tev @@ -8,7 +12,7 @@ from tcod.console import Console from . import Window from ... import log -from ...geometry import Point, Rect, Size, Vector +from ...geometry import Point, Rect, Vector from ...map import Map from ...object import Entity, Hero @@ -20,18 +24,18 @@ class MapWindow(Window): '''An event handler for the MapWindow.''' def ev_mousemotion(self, event: tev.MouseMotion) -> bool: - mouse_point = self.window.convert_console_point(self.mouse_point_for_event(event)) - if not mouse_point: - return False + mouse_point = self.mouse_point_for_event(event) - log.UI.info('Mouse point in window %s', mouse_point) + converted_point = self.window.convert_console_point_to_window(mouse_point, use_drawable_bounds=True) + if not converted_point: + return False hero = self.window.hero if not hero: return False - map_point = self.window.convert_window_point_to_map(mouse_point) - log.UI.info('Mouse point in map %s', map_point) + map_point = self.window.convert_console_point_to_map(mouse_point) + log.UI.info('Mouse moved; finding path from hero to %s', map_point) map_ = self.window.map path = map_.find_walkable_path_from_point_to_point(hero.position, map_point) @@ -39,30 +43,51 @@ class MapWindow(Window): return False - # pylint: disable=redefined-builtin + def ev_mousebuttondown(self, event: tev.MouseButtonDown) -> bool: + mouse_point = self.mouse_point_for_event(event) + + converted_point = self.window.convert_console_point_to_window(mouse_point, use_drawable_bounds=True) + if not converted_point: + return False + + map_point = self.window.convert_console_point_to_map(mouse_point) + log.UI.info('Mouse button down at %s', map_point) + + return False + def __init__(self, bounds: Rect, map: Map, hero: Hero, **kwargs): super().__init__(bounds, event_handler=self.__class__.EventHandler(self), **kwargs) self.map = map + '''The game map''' + + self.visible_map_bounds = map.bounds + '''A rectangle in map coordinates defining the visible area of the map in the window''' - self.drawable_map_bounds = map.bounds self.hero = hero + '''The hero entity''' + self.entities: List[Entity] = [] + '''A list of all game entities to render on the map''' self._draw_bounds = self.drawable_bounds - - def convert_window_point_to_map(self, point: Point) -> Point: ''' - Convert a point in window coordinates to a point relative to the map's + A rectangle in console coordinates where the map will actually be drawn. + This area should always be entirely contained within the window's + drawable bounds. + ''' + + def convert_console_point_to_map(self, point: Point) -> Point: + ''' + Convert a point in console coordinates to a point relative to the map's origin point. ''' - return point - Vector.from_point(self._draw_bounds.origin) + return point - Vector.from_point(self._draw_bounds.origin) + Vector.from_point(self.visible_map_bounds.origin) - def update_drawable_map_bounds(self): + def _update_visible_map_bounds(self) -> Rect: ''' - Figure out what portion of the map is drawable and update - `self.drawable_map_bounds`. This method attempts to keep the hero - centered in the map viewport, while not overscrolling the map in either - direction. + Figure out what portion of the map is visible. This method attempts to + keep the hero centered in the map viewport, while not overscrolling the + map in either direction. ''' bounds = self.drawable_bounds map_bounds = self.map.bounds @@ -72,65 +97,71 @@ class MapWindow(Window): if viewport_is_wider_than_map and viewport_is_taller_than_map: # The whole map fits within the window's drawable bounds - self.drawable_map_bounds = map_bounds - return + return map_bounds # Attempt to keep the player centered in the viewport. hero_point = self.hero.position if viewport_is_wider_than_map: x = 0 + width = map_bounds.width else: - x = min(max(0, hero_point.x - bounds.mid_x), map_bounds.max_x - bounds.width) + half_width = bounds.width // 2 + x = min(max(0, hero_point.x - half_width), map_bounds.end_x - bounds.width) + width = bounds.width if viewport_is_taller_than_map: y = 0 + height = map_bounds.height else: - y = min(max(0, hero_point.y - bounds.mid_y), map_bounds.max_y - bounds.height) + half_height = bounds.height // 2 + y = min(max(0, hero_point.y - half_height), map_bounds.end_y - bounds.height) + height = bounds.height - origin = Point(x, y) - size = Size(min(bounds.width, map_bounds.width), min(bounds.height, map_bounds.height)) - - self.drawable_map_bounds = Rect(origin, size) + return Rect.from_raw_values(x, y, width, height) def _update_draw_bounds(self): ''' The area where the map should actually be drawn, accounting for the size - of the viewport (`drawable_bounds`)and the size of the map (`self.map.bounds`). + of the viewport (`drawable_bounds`) and the size of the map (`self.map.bounds`). ''' - drawable_map_bounds = self.drawable_map_bounds + visible_map_bounds = self.visible_map_bounds drawable_bounds = self.drawable_bounds - viewport_is_wider_than_map = drawable_bounds.width >= drawable_map_bounds.width - viewport_is_taller_than_map = drawable_bounds.height >= drawable_map_bounds.height + viewport_is_wider_than_map = drawable_bounds.width >= visible_map_bounds.width + viewport_is_taller_than_map = drawable_bounds.height >= visible_map_bounds.height if viewport_is_wider_than_map: # Center the map horizontally in the viewport - origin_x = drawable_bounds.min_x + (drawable_bounds.width - drawable_map_bounds.width) // 2 - width = drawable_map_bounds.width + x = drawable_bounds.min_x + (drawable_bounds.width - visible_map_bounds.width) // 2 + width = visible_map_bounds.width else: - origin_x = drawable_bounds.min_x + x = drawable_bounds.min_x width = drawable_bounds.width if viewport_is_taller_than_map: # Center the map vertically in the viewport - origin_y = drawable_bounds.min_y + (drawable_bounds.height - drawable_map_bounds.height) // 2 - height = drawable_map_bounds.height + y = drawable_bounds.min_y + (drawable_bounds.height - visible_map_bounds.height) // 2 + height = visible_map_bounds.height else: - origin_y = drawable_bounds.min_y + y = drawable_bounds.min_y height = drawable_bounds.height - self._draw_bounds = Rect(Point(origin_x, origin_y), Size(width, height)) + draw_bounds = Rect.from_raw_values(x, y, width, height) + assert draw_bounds in self.drawable_bounds + + return draw_bounds def draw(self, console: Console): super().draw(console) - self._update_draw_bounds() + + self.visible_map_bounds = self._update_visible_map_bounds() + self._draw_bounds = self._update_draw_bounds() self._draw_map(console) self._draw_entities(console) def _draw_map(self, console: Console): - drawable_map_bounds = self.drawable_map_bounds - drawable_bounds = self.drawable_bounds + drawable_map_bounds = self.visible_map_bounds map_slice = np.s_[ drawable_map_bounds.min_x: drawable_map_bounds.max_x + 1, @@ -143,26 +174,29 @@ class MapWindow(Window): console.tiles_rgb[console_slice] = self.map.composited_tiles[map_slice] - def _draw_entities(self, console): - map_bounds_vector = Vector.from_point(self.drawable_map_bounds.origin) + def _draw_entities(self, console: Console): + visible_map_bounds = self.visible_map_bounds + map_bounds_vector = Vector.from_point(self.visible_map_bounds.origin) draw_bounds_vector = Vector.from_point(self._draw_bounds.origin) for ent in self.entities: + entity_position = ent.position + + # Only draw entities that are within the visible map bounds + if entity_position not in visible_map_bounds: + continue + # Only draw entities that are in the field of view - if not self.map.point_is_visible(ent.position): + if not self.map.point_is_visible(entity_position): continue # Entity positions are relative to the (0, 0) point of the Map. In # order to render them in the correct position in the console, we # need to transform them into viewport-relative coordinates. - entity_position = ent.position map_tile_at_entity_position = self.map.composited_tiles[entity_position.numpy_index] position = ent.position - map_bounds_vector + draw_bounds_vector - if isinstance(ent, Hero): - log.UI.debug('Hero position: map=%s, window=%s', entity_position, position) - console.print( x=position.x, y=position.y, From e1523cd9c0a57c4968f576dfc5b00b7b5a44c226 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Fri, 10 Mar 2023 23:54:36 -0800 Subject: [PATCH 227/234] Some logging level adjustments Move some loggers up to INFO. Disable propagation on erynrl.ui. --- logging_config.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/logging_config.json b/logging_config.json index acf206a..58cb9c6 100644 --- a/logging_config.json +++ b/logging_config.json @@ -30,7 +30,7 @@ "propagate": false }, "erynrl.actions": { - "level": "INFO", + "level": "ERROR", "handlers": [ "console" ] @@ -42,7 +42,7 @@ ] }, "erynrl.actions.tree": { - "level": "INFO", + "level": "ERROR", "handlers": [ "console" ], @@ -63,10 +63,11 @@ "propagate": false }, "erynrl.ui": { - "level": "WARN", + "level": "INFO", "handlers": [ "console" - ] + ], + "propagate": false }, "erynrl.visible": { "level": "WARN", From b0d91c9c5d71ab5f81ecd7a89d5fad55ce0b2f07 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 00:06:47 -0800 Subject: [PATCH 228/234] Refactor how maps, rooms, and corridors are generated - Rect and Room method objects no longer need to know the map size up front - The Map object has lists of interesting map features (I don't like this) - Room and corridor generators take the map itself as an argument to their generate and apply methods - Create a Corridor object to hold a list of points - Add a bunch of documentation here and there --- erynrl/engine.py | 7 +--- erynrl/map/__init__.py | 12 ++++-- erynrl/map/generator/__init__.py | 15 ++++--- erynrl/map/generator/corridor.py | 49 ++++++++++++++-------- erynrl/map/generator/room.py | 70 ++++++++++++++++++++------------ erynrl/map/room.py | 20 ++++++++- 6 files changed, 111 insertions(+), 62 deletions(-) diff --git a/erynrl/engine.py b/erynrl/engine.py index 0bdeca0..c81ec4f 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -57,14 +57,11 @@ class Engine: self.rng = tcod.random.Random() self.message_log = MessageLog() - map_size = config.map_size map_generator = RoomsAndCorridorsGenerator( RoomGenerator( - size=map_size, - config=RoomGenerator.Configuration( + RoomGenerator.Configuration( rect_method=BSPRectMethod( - size=map_size, - config=BSPRectMethod.Configuration(number_of_rooms=30)), + BSPRectMethod.Configuration(number_of_rooms=30)), room_method=OrRoomMethod( methods=[ (0.2, CellularAtomatonRoomMethod(CellularAtomataMapGenerator.Configuration())), diff --git a/erynrl/map/__init__.py b/erynrl/map/__init__.py index fb1ae32..a4216b7 100644 --- a/erynrl/map/__init__.py +++ b/erynrl/map/__init__.py @@ -6,7 +6,7 @@ parts of a map. ''' import random -from typing import Iterable +from typing import Iterable, List import numpy as np import tcod @@ -14,6 +14,7 @@ import tcod from ..configuration import Configuration from ..geometry import Point, Rect, Size from .generator import MapGenerator +from .room import Corridor, Room from .tile import Empty, Shroud @@ -29,9 +30,6 @@ class Map: shape = map_size.numpy_shape self.tiles = np.full(shape, fill_value=Empty, order='F') - self.up_stairs = generator.up_stairs - self.down_stairs = generator.down_stairs - self.highlighted = np.full(shape, fill_value=False, order='F') # Map tiles that are currently visible to the player @@ -44,6 +42,12 @@ class Map: generator.generate(self) + # Map Features + self.rooms: List[Room] = [] + self.corridors: List[Corridor] = [] + self.up_stairs = generator.up_stairs + self.down_stairs = generator.down_stairs + @property def bounds(self) -> Rect: '''The bounds of the map''' diff --git a/erynrl/map/generator/__init__.py b/erynrl/map/generator/__init__.py index d82d44e..1fc1d8d 100644 --- a/erynrl/map/generator/__init__.py +++ b/erynrl/map/generator/__init__.py @@ -1,8 +1,10 @@ # Eryn Wells -from typing import List, TYPE_CHECKING +''' +This module defines a bunch of mechanisms for generating maps. +''' -import numpy as np +from typing import List, TYPE_CHECKING from .corridor import CorridorGenerator from .room import RoomGenerator @@ -25,7 +27,6 @@ class MapGenerator: '''The location of any routes to a lower floor of the dungeon.''' raise NotImplementedError() - # pylint: disable=redefined-builtin def generate(self, map: 'Map'): '''Generate a map and place it in `tiles`''' raise NotImplementedError() @@ -48,10 +49,8 @@ class RoomsAndCorridorsGenerator(MapGenerator): def down_stairs(self) -> List[Point]: return self.room_generator.down_stairs - # pylint: disable=redefined-builtin def generate(self, map: 'Map'): - self.room_generator.generate() + self.room_generator.generate(map) self.room_generator.apply(map) - - self.corridor_generator.generate(self.room_generator.rooms) - self.corridor_generator.apply(map.tiles) + self.corridor_generator.generate(map) + self.corridor_generator.apply(map) diff --git a/erynrl/map/generator/corridor.py b/erynrl/map/generator/corridor.py index 13ce2ba..b57a3ad 100644 --- a/erynrl/map/generator/corridor.py +++ b/erynrl/map/generator/corridor.py @@ -6,30 +6,35 @@ Defines an abstract CorridorGenerator and several concrete subclasses. These cla import random from itertools import pairwise -from typing import List +from typing import List, TYPE_CHECKING import tcod -import numpy as np from ... import log from ...geometry import Point -from ..room import Room +from ..room import Corridor, Room from ..tile import Empty, Floor, Wall +if TYPE_CHECKING: + from .. import Map + class CorridorGenerator: ''' Corridor generators produce corridors between rooms. ''' - def generate(self, rooms: List[Room]) -> bool: + def generate(self, map: 'Map') -> bool: '''Generate corridors given a list of rooms.''' raise NotImplementedError() - def apply(self, tiles: np.ndarray): + def apply(self, map: 'Map'): '''Apply corridors to a tile grid.''' raise NotImplementedError() + def _sorted_rooms(self, rooms: List[Room]) -> List[Room]: + return sorted(rooms, key=lambda r: r.bounds.origin) + class ElbowCorridorGenerator(CorridorGenerator): ''' @@ -48,25 +53,23 @@ class ElbowCorridorGenerator(CorridorGenerator): ''' def __init__(self): - self.corridors: List[List[Point]] = [] + self.corridors: List[Corridor] = [] + + def generate(self, map: 'Map') -> bool: + rooms = map.rooms - def generate(self, rooms: List[Room]) -> bool: if len(rooms) < 2: return True - sorted_rooms = sorted(rooms, key=lambda r: r.bounds.origin) + sorted_rooms = self._sorted_rooms(rooms) - for (left_room, right_room) in pairwise(sorted_rooms): + for left_room, right_room in pairwise(sorted_rooms): corridor = self._generate_corridor_between(left_room, right_room) self.corridors.append(corridor) - for i in range(len(rooms) - 2): - corridor = self._generate_corridor_between(rooms[i], rooms[i + 2]) - self.corridors.append(corridor) - return True - def _generate_corridor_between(self, left_room, right_room): + def _generate_corridor_between(self, left_room, right_room) -> Corridor: left_room_bounds = left_room.bounds right_room_bounds = right_room.bounds @@ -77,12 +80,18 @@ class ElbowCorridorGenerator(CorridorGenerator): end_point = right_room_bounds.midpoint # Randomly choose whether to move horizontally then vertically or vice versa - if random.random() < 0.5: + horizontal_first = random.random() < 0.5 + if horizontal_first: corner = Point(end_point.x, start_point.y) else: corner = Point(start_point.x, end_point.y) - log.MAP.debug('Digging a tunnel between %s and %s with corner %s', start_point, end_point, corner) + log.MAP.debug( + 'Digging a tunnel between %s and %s with corner %s (%s)', + start_point, + end_point, + corner, + 'horizontal' if horizontal_first else 'vertical') log.MAP.debug('|-> start: %s', left_room_bounds) log.MAP.debug('`-> end: %s', right_room_bounds) @@ -94,9 +103,13 @@ class ElbowCorridorGenerator(CorridorGenerator): for x, y in tcod.los.bresenham(tuple(corner), tuple(end_point)).tolist(): corridor.append(Point(x, y)) - return corridor + return Corridor(points=corridor) + + def apply(self, map: 'Map'): + tiles = map.tiles + + map.corridors = self.corridors - def apply(self, tiles): for corridor in self.corridors: for pt in corridor: tiles[pt.x, pt.y] = Floor diff --git a/erynrl/map/generator/room.py b/erynrl/map/generator/room.py index 8872436..727bfe2 100644 --- a/erynrl/map/generator/room.py +++ b/erynrl/map/generator/room.py @@ -23,24 +23,32 @@ class RoomGenerator: @dataclass class Configuration: + ''' + Configuration of a RoomGenerator + + ### Attributes + + rect_method : RectMethod + A RectMethod object to produce rectangles + room_method : RoomMethod + A RoomMethod object to produce rooms from rectangles + ''' rect_method: 'RectMethod' room_method: 'RoomMethod' - def __init__(self, *, size: Size, config: Configuration): - self.size = size + def __init__(self, config: Configuration): self.configuration = config self.rooms: List[Room] = [] - self.up_stairs: List[Point] = [] self.down_stairs: List[Point] = [] - def generate(self): + def generate(self, map: 'Map'): '''Generate rooms and stairs''' rect_method = self.configuration.rect_method room_method = self.configuration.room_method - for rect in rect_method.generate(): + for rect in rect_method.generate(map): room = room_method.room_in_rect(rect) if not room: break @@ -51,11 +59,10 @@ class RoomGenerator: self._generate_stairs() - # pylint: disable=redefined-builtin def apply(self, map: 'Map'): '''Apply the generated rooms to a tile array''' self._apply(map) - self._apply_stairs(map.tiles) + self._apply_stairs(map) def _apply(self, map: 'Map'): ''' @@ -68,6 +75,8 @@ class RoomGenerator: ''' tiles = map.tiles + map.rooms = self.rooms + for room in self.rooms: for pt in room.floor_points: tiles[pt.numpy_index] = Floor @@ -93,7 +102,12 @@ class RoomGenerator: self.up_stairs.append(random.choice(list(up_stair_room.walkable_tiles))) self.down_stairs.append(random.choice(list(down_stair_room.walkable_tiles))) - def _apply_stairs(self, tiles): + def _apply_stairs(self, map: 'Map'): + tiles = map.tiles + + map.up_stairs = self.up_stairs + map.down_stairs = self.down_stairs + for pt in self.up_stairs: tiles[pt.numpy_index] = StairsUp for pt in self.down_stairs: @@ -103,10 +117,7 @@ class RoomGenerator: class RectMethod: '''An abstract class defining a method for generating rooms.''' - def __init__(self, *, size: Size): - self.size = size - - def generate(self) -> Iterator[Rect]: + def generate(self, map: 'Map') -> Iterator[Rect]: '''Generate rects to place rooms in until there are no more.''' raise NotImplementedError() @@ -132,13 +143,14 @@ class OneBigRoomRectMethod(RectMethod): width_percentage: float = 0.5 height_percentage: float = 0.5 - def __init__(self, *, size: Size, config: Optional[Configuration] = None): - super().__init__(size=size) + def __init__(self, config: Optional[Configuration] = None): + super().__init__() self.configuration = config or self.__class__.Configuration() - def generate(self) -> Iterator[Rect]: - width = self.size.width - height = self.size.height + def generate(self, map: 'Map') -> Iterator[Rect]: + map_size = map.bounds.size + width = map_size.width + height = map_size.height size = Size(math.floor(width * self.configuration.width_percentage), math.floor(height * self.configuration.height_percentage)) @@ -156,23 +168,24 @@ class RandomRectMethod(RectMethod): minimum_room_size: Size = Size(7, 7) maximum_room_size: Size = Size(20, 20) - def __init__(self, *, size: Size, config: Optional[Configuration] = None): - super().__init__(size=size) + def __init__(self, config: Optional[Configuration] = None): self.configuration = config or self.__class__.Configuration() self._rects: List[Rect] = [] - def generate(self) -> Iterator[Rect]: + def generate(self, map: 'Map') -> Iterator[Rect]: minimum_room_size = self.configuration.minimum_room_size maximum_room_size = self.configuration.maximum_room_size width_range = (minimum_room_size.width, maximum_room_size.width) height_range = (minimum_room_size.height, maximum_room_size.height) + map_size = map.size + while len(self._rects) < self.configuration.number_of_rooms: for _ in range(self.__class__.NUMBER_OF_ATTEMPTS_PER_RECT): size = Size(random.randint(*width_range), random.randint(*height_range)) - origin = Point(random.randint(0, self.size.width - size.width), - random.randint(0, self.size.height - size.height)) + origin = Point(random.randint(0, map_size.width - size.width), + random.randint(0, map_size.height - size.height)) candidate_rect = Rect(origin, size) overlaps_any_existing_room = any(candidate_rect.intersects(r) for r in self._rects) @@ -186,6 +199,10 @@ class RandomRectMethod(RectMethod): class BSPRectMethod(RectMethod): + ''' + Generate rectangles with Binary Space Partitioning. + ''' + @dataclass class Configuration: ''' @@ -219,18 +236,19 @@ class BSPRectMethod(RectMethod): maximum_room_size: Size = Size(20, 20) room_size_ratio: Tuple[float, float] = (1.1, 1.1) - def __init__(self, *, size: Size, config: Optional[Configuration] = None): - super().__init__(size=size) + def __init__(self, config: Optional[Configuration] = None): self.configuration = config or self.__class__.Configuration() - def generate(self) -> Iterator[Rect]: + def generate(self, map: 'Map') -> Iterator[Rect]: nodes_with_rooms = set() minimum_room_size = self.configuration.minimum_room_size maximum_room_size = self.configuration.maximum_room_size + map_size = map.size + # Recursively divide the map into squares of various sizes to place rooms in. - bsp = tcod.bsp.BSP(x=0, y=0, width=self.size.width, height=self.size.height) + bsp = tcod.bsp.BSP(x=0, y=0, width=map_size.width, height=map_size.height) # Add 2 to the minimum width and height to account for walls bsp.split_recursive( diff --git a/erynrl/map/room.py b/erynrl/map/room.py index 60a3510..95b34f3 100644 --- a/erynrl/map/room.py +++ b/erynrl/map/room.py @@ -4,7 +4,7 @@ Implements an abstract Room class, and subclasses that implement it. Rooms are basic components of maps. ''' -from typing import Iterable +from typing import Iterable, Iterator, List, Optional import numpy as np @@ -118,3 +118,21 @@ class FreeformRoom(Room): def __str__(self): return '\n'.join(''.join(chr(i['light']['ch']) for i in row) for row in self.tiles) + + +class Corridor: + ''' + A corridor is a list of points connecting two endpoints + ''' + + def __init__(self, points: Optional[List[Point]] = None): + self.points: List[Point] = points or [] + + @property + def length(self) -> int: + '''The length of this corridor''' + return len(self.points) + + def __iter__(self) -> Iterator[Point]: + for pt in self.points: + yield pt From f78bc39e3b25cb9e3db3095059b96a26ff0548b8 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 00:57:15 -0800 Subject: [PATCH 229/234] Clean up a few type checker things --- erynrl/interface/window/info.py | 4 +++- erynrl/interface/window/message_log.py | 4 +++- erynrl/monsters.py | 7 +++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erynrl/interface/window/info.py b/erynrl/interface/window/info.py index 6252fdc..1b21940 100644 --- a/erynrl/interface/window/info.py +++ b/erynrl/interface/window/info.py @@ -4,6 +4,8 @@ Declares the InfoWindow. ''' +from tcod.console import Console + from . import Window from ..color import HealthBar from ..percentage_bar import PercentageBar @@ -34,7 +36,7 @@ class InfoWindow(Window): self.hit_points_bar.percent_filled = hp / max_hp - def draw(self, console): + def draw(self, console: Console): super().draw(console) drawable_bounds = self.drawable_bounds diff --git a/erynrl/interface/window/message_log.py b/erynrl/interface/window/message_log.py index 13d6091..df7d6ea 100644 --- a/erynrl/interface/window/message_log.py +++ b/erynrl/interface/window/message_log.py @@ -4,6 +4,8 @@ Declares the MessageLogWindow. ''' +from tcod.console import Console + from . import Window from ...geometry import Rect from ...messages import MessageLog @@ -16,6 +18,6 @@ class MessageLogWindow(Window): super().__init__(bounds, framed=True) self.message_log = message_log - def draw(self, console): + def draw(self, console: Console): super().draw(console) self.message_log.render_to_console(console, self.drawable_bounds) diff --git a/erynrl/monsters.py b/erynrl/monsters.py index 2333ba8..06bbdcb 100644 --- a/erynrl/monsters.py +++ b/erynrl/monsters.py @@ -4,9 +4,11 @@ the dungeon.''' from dataclasses import dataclass -from typing import Tuple +from typing import Optional, Tuple # pylint: disable=too-many-instance-attributes + + @dataclass(frozen=True) class Species: '''A kind of monster. @@ -35,7 +37,8 @@ class Species: attack_power: int defense: int foreground_color: Tuple[int, int, int] - background_color: Tuple[int, int, int] = None + background_color: Optional[Tuple[int, int, int]] = None + Orc = Species(name='Orc', symbol='o', foreground_color=(63, 127, 63), From 01b549bc6ee64d8804f9e8ba2b12734cb601f6a5 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 00:57:48 -0800 Subject: [PATCH 230/234] Remove the RegenerateRoomsAction --- erynrl/actions/game.py | 7 ------- erynrl/events.py | 4 +--- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index e014c1f..ad960fa 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -36,13 +36,6 @@ class ExitAction(Action): raise SystemExit() -class RegenerateRoomsAction(Action): - '''Regenerate the dungeon map''' - - def perform(self, engine: 'Engine') -> ActionResult: - return self.failure() - - class MoveAction(ActionWithActor): '''An abstract Action that requires a direction to complete.''' diff --git a/erynrl/events.py b/erynrl/events.py index 9507a11..cc6928b 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -6,7 +6,7 @@ import tcod import tcod.event as tev from .actions.action import Action -from .actions.game import BumpAction, ExitAction, RegenerateRoomsAction, WaitAction +from .actions.game import BumpAction, ExitAction, WaitAction from .geometry import Direction if TYPE_CHECKING: @@ -45,8 +45,6 @@ class EngineEventHandler(tev.EventDispatch[Action]): action = BumpAction(hero, Direction.NorthEast) case tcod.event.KeySym.y: action = BumpAction(hero, Direction.NorthWest) - case tcod.event.KeySym.SPACE: - action = RegenerateRoomsAction() case tcod.event.KeySym.PERIOD: if not is_shift_pressed: action = WaitAction(hero) From def79386d862e56c8ad8ddfdbc5dde3561622b20 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 00:58:58 -0800 Subject: [PATCH 231/234] Move the renderable part of Entity to a Renderable component Move symbol, render order, foreground, and background properties on Entity to a new Component called Renderable. --- erynrl/components.py | 42 ++++++++++++++- erynrl/engine.py | 2 +- erynrl/interface/__init__.py | 3 +- erynrl/interface/window/map.py | 8 ++- erynrl/object.py | 96 +++++++++++----------------------- 5 files changed, 80 insertions(+), 71 deletions(-) diff --git a/erynrl/components.py b/erynrl/components.py index b8810ed..c59cb7c 100644 --- a/erynrl/components.py +++ b/erynrl/components.py @@ -1,7 +1,8 @@ # Eryn Wells import random -from typing import Optional +from enum import Enum +from typing import Optional, Tuple class Component: @@ -75,3 +76,42 @@ class Fighter(Component): def _reset_passive_heal_clock(self) -> None: self.__ticks_since_last_passive_heal = 0 self.__ticks_for_next_passive_heal = random.randint(30, 70) + + +class Renderable(Component): + class Order(Enum): + ''' + These values indicate the order that an entity with a Renderable + component should be rendered. Higher values are rendered later and + therefore on top of items with lower orderings. + ''' + ITEM = 1000 + ACTOR = 2000 + HERO = 3000 + + def __init__( + self, + symbol: str, + order: Order = Order.ACTOR, + fg: Optional[Tuple[int, int, int]] = None, + bg: Optional[Tuple[int, int, int]] = None): + if len(symbol) != 1: + raise ValueError(f'Symbol string "{symbol}" must be of length 1') + + self.symbol = symbol + '''The symbol that represents this renderable on the map''' + + self.order = order + ''' + Specifies the layer at which this entity is rendered. Higher values are + rendered later, and thus on top of lower values. + ''' + + self.foreground = fg + '''The foreground color of the entity''' + + self.background = bg + '''The background color of the entity''' + + def __repr__(self): + return f'{self.__class__.__name__}("{self.symbol}", {self.order}, {self.foreground}, {self.background})' diff --git a/erynrl/engine.py b/erynrl/engine.py index c81ec4f..d394852 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -165,7 +165,7 @@ class Engine: if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.map.visible[tuple(action.actor.position)]: if result.alternate: - alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' + alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor}]' else: alternate_string = str(result.alternate) log.ACTIONS_TREE.info( diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 096f2f9..ddc06e0 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -45,7 +45,8 @@ class Interface: hero = self.engine.hero self.info_window.update_hero(hero) - sorted_entities = sorted(self.engine.entities, key=lambda e: e.render_order.value) + sorted_entities = sorted(filter(lambda e: e.renderable is not None, self.engine.entities), + key=lambda e: e.renderable.order.value) self.map_window.entities = sorted_entities def draw(self): diff --git a/erynrl/interface/window/map.py b/erynrl/interface/window/map.py index d8ac561..90b70b4 100644 --- a/erynrl/interface/window/map.py +++ b/erynrl/interface/window/map.py @@ -190,6 +190,10 @@ class MapWindow(Window): if not self.map.point_is_visible(entity_position): continue + renderable = ent.renderable + if not renderable: + continue + # Entity positions are relative to the (0, 0) point of the Map. In # order to render them in the correct position in the console, we # need to transform them into viewport-relative coordinates. @@ -200,6 +204,6 @@ class MapWindow(Window): console.print( x=position.x, y=position.y, - string=ent.symbol, - fg=ent.foreground, + string=renderable.symbol, + fg=renderable.foreground, bg=tuple(map_tile_at_entity_position['bg'][:3])) diff --git a/erynrl/object.py b/erynrl/object.py index 34d2f2e..4da1d0c 100644 --- a/erynrl/object.py +++ b/erynrl/object.py @@ -2,13 +2,12 @@ '''Defines a number of high-level game objects. The parent class of all game objects is the Entity class.''' -from enum import Enum -from typing import TYPE_CHECKING, Optional, Tuple, Type +from typing import TYPE_CHECKING, Optional, Type import tcod from . import items -from .components import Fighter +from .components import Fighter, Renderable from .geometry import Point from .monsters import Species @@ -16,16 +15,6 @@ if TYPE_CHECKING: from .ai import AI -class RenderOrder(Enum): - ''' - These values indicate the order that an Entity should be rendered. Higher values are rendered later and therefore on - top of items with lower orderings. - ''' - ITEM = 1000 - ACTOR = 2000 - HERO = 3000 - - class Entity: '''A single-tile drawable entity with a symbol and position @@ -36,19 +25,9 @@ class Entity: game position : Point The Entity's location on the map - foreground : Tuple[int, int, int] - The foreground color used to render this Entity - background : Tuple[int, int, int], optional - The background color used to render this Entity - symbol : str - A single character string that represents this character on the map blocks_movement : bool True if this Entity blocks other Entities from moving through its position - render_order : RenderOrder - One of the RenderOrder values that specifies a layer at which this - entity will be rendered. Higher values are rendered on top of lower - values. ''' # A monotonically increasing identifier to help differentiate between @@ -57,66 +36,51 @@ class Entity: def __init__( self, - symbol: str, *, position: Optional[Point] = None, blocks_movement: Optional[bool] = True, - render_order: RenderOrder = RenderOrder.ITEM, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None): + renderable: Optional[Renderable] = None): self.identifier = Entity.__next_identifier self.position = position if position else Point() - self.foreground = fg if fg else (255, 255, 255) - self.background = bg - self.symbol = symbol + self.renderable = renderable self.blocks_movement = blocks_movement - self.render_order = render_order Entity.__next_identifier += 1 - def print_to_console(self, console: tcod.Console) -> None: - '''Render this Entity to the console''' - console.print(x=self.position.x, y=self.position.y, string=self.symbol, fg=self.foreground, bg=self.background) + def __str__(self): + return f'{self.__class__.__name__}!{self.identifier}' - def __str__(self) -> str: - return f'{self.symbol}!{self.identifier} at {self.position}' - - def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fg={self.foreground!r}, bg={self.background!r})' + def __repr__(self): + return f'{self.__class__.__name__}(position={self.position!r}, blocks_movement={self.blocks_movement}, renderable={self.renderable!r})' class Actor(Entity): ''' - An actor is an abstract class that defines an object that can act in the game world. Entities that are actors will - be allowed an opportunity to perform an action during each game turn. + An actor is an abstract class that defines an object that can act in the + game world. Entities that are actors will be allowed an opportunity to + perform an action during each game turn. + + ### Attributes - Attributes - ---------- ai : AI, optional If an entity can act on its own behalf, an instance of an AI class fighter : Fighter, optional - If an entity can fight or take damage, an instance of the Fighter class. This is where hit points, attack power, - defense power, etc live. + If an entity can fight or take damage, an instance of the Fighter class. + This is where hit points, attack power, defense power, etc live. ''' def __init__( self, - symbol: str, *, position: Optional[Point] = None, blocks_movement: Optional[bool] = True, - render_order: RenderOrder = RenderOrder.ACTOR, + renderable: Optional[Renderable] = None, ai: Optional['AI'] = None, - fighter: Optional[Fighter] = None, - fg: Optional[Tuple[int, int, int]] = None, - bg: Optional[Tuple[int, int, int]] = None): + fighter: Optional[Fighter] = None): super().__init__( - symbol, position=position, blocks_movement=blocks_movement, - fg=fg, - bg=bg, - render_order=render_order) + renderable=renderable) # Components self.ai = ai @@ -138,7 +102,7 @@ class Actor(Entity): return False def __repr__(self) -> str: - return f'{self.__class__.__name__}({self.symbol!r}, position={self.position!r}, fighter={self.fighter!r}, ai={self.ai!r}, fg={self.foreground!r}, bg={self.background!r})' + return f'{self.__class__.__name__}(position={self.position!r}, fighter={self.fighter!r}, ai={self.ai!r}, renderable={self.renderable!r})' class Hero(Actor): @@ -146,11 +110,9 @@ class Hero(Actor): def __init__(self, position: Point): super().__init__( - '@', position=position, fighter=Fighter(maximum_hit_points=30, attack_power=5, defense=2), - render_order=RenderOrder.HERO, - fg=tuple(tcod.white)) + renderable=Renderable('@', Renderable.Order.HERO, tuple(tcod.white))) @property def name(self) -> str: @@ -176,12 +138,13 @@ class Monster(Actor): defense=species.defense) super().__init__( - species.symbol, ai=ai_class(self), position=position, fighter=fighter, - fg=species.foreground_color, - bg=species.background_color) + renderable=Renderable( + symbol=species.symbol, + fg=species.foreground_color, + bg=species.background_color)) self.species = species @@ -206,12 +169,13 @@ class Item(Entity): '''An instance of an Item''' def __init__(self, kind: items.Item, position: Optional[Point] = None, name: Optional[str] = None): - super().__init__(kind.symbol, - position=position, + super().__init__(position=position, blocks_movement=False, - render_order=RenderOrder.ITEM, - fg=kind.foreground_color, - bg=kind.background_color) + renderable=Renderable( + symbol=kind.symbol, + order=Renderable.Order.ITEM, + fg=kind.foreground_color, + bg=kind.background_color)) self.kind = kind self._name = name From 02ed3d1e4a53089f2e29ac40cd7c6e05f5ff2194 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 01:00:09 -0800 Subject: [PATCH 232/234] Add a title to the window :) --- erynrl/__main__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 9b96c14..2a79e44 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -12,6 +12,9 @@ from .geometry import Size from .interface import Interface +TITLE = 'ErynRL' + + def parse_args(argv, *a, **kw): parser = argparse.ArgumentParser(*a, **kw) parser.add_argument('--debug', action='store_true', default=True) @@ -53,7 +56,7 @@ def main(argv): interface = Interface(configuration.console_size, engine) tileset = configuration.console_font_configuration.tileset - with tcod.context.new(columns=interface.console.width, rows=interface.console.height, tileset=tileset) as context: + with tcod.context.new(columns=interface.console.width, rows=interface.console.height, tileset=tileset, title=TITLE) as context: interface.run_event_loop(context) return 0 From 327cc90b2e182d00bf77beb7a33da9b6a7de78b9 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 01:09:53 -0800 Subject: [PATCH 233/234] Remove QuitAction and ActionWithActor! Move quit event handling to the interface and flatten the Action class hierarchy. There are no longer any actions that don't take an Actor. This has the happy side effect of resolving some pylint errors too. :) --- erynrl/actions/action.py | 18 ++++-------------- erynrl/actions/game.py | 20 ++++++-------------- erynrl/ai.py | 6 +++--- erynrl/engine.py | 8 ++++---- erynrl/events.py | 8 +------- erynrl/interface/events.py | 7 ++++++- 6 files changed, 24 insertions(+), 43 deletions(-) diff --git a/erynrl/actions/action.py b/erynrl/actions/action.py index 584d102..c2a9660 100644 --- a/erynrl/actions/action.py +++ b/erynrl/actions/action.py @@ -12,6 +12,10 @@ if TYPE_CHECKING: class Action: '''An action with no specific actor''' + def __init__(self, actor: Actor): + super().__init__() + self.actor = actor + # pylint: disable=unused-argument def perform(self, engine: 'Engine') -> ActionResult: '''Perform this action. @@ -42,17 +46,3 @@ class Action: def __repr__(self): return f'{self.__class__.__name__}()' - - -class ActionWithActor(Action): - '''An action that assigned to an actor''' - - def __init__(self, actor: Actor): - super().__init__() - self.actor = actor - - def __str__(self) -> str: - return f'{self.__class__.__name__} for {self.actor!s}' - - def __repr__(self): - return f'{self.__class__.__name__}({self.actor!r})' diff --git a/erynrl/actions/game.py b/erynrl/actions/game.py index ad960fa..71d7485 100644 --- a/erynrl/actions/game.py +++ b/erynrl/actions/game.py @@ -11,7 +11,6 @@ Action : Base class of all actions BumpAction WalkAction MeleeAction - ExitAction WaitAction ''' @@ -22,21 +21,14 @@ from .. import items from .. import log from ..geometry import Vector from ..object import Actor, Item -from .action import Action, ActionWithActor +from .action import Action from .result import ActionResult if TYPE_CHECKING: from ..engine import Engine -class ExitAction(Action): - '''Exit the game.''' - - def perform(self, engine: 'Engine') -> ActionResult: - raise SystemExit() - - -class MoveAction(ActionWithActor): +class MoveAction(Action): '''An abstract Action that requires a direction to complete.''' def __init__(self, actor: Actor, direction: Vector): @@ -157,7 +149,7 @@ class MeleeAction(MoveAction): return self.success() -class WaitAction(ActionWithActor): +class WaitAction(Action): '''Wait a turn''' def perform(self, engine: 'Engine') -> ActionResult: @@ -174,7 +166,7 @@ class WaitAction(ActionWithActor): return self.success() -class DieAction(ActionWithActor): +class DieAction(Action): '''Kill an Actor''' def perform(self, engine: 'Engine') -> ActionResult: @@ -193,7 +185,7 @@ class DieAction(ActionWithActor): return self.success() -class DropItemAction(ActionWithActor): +class DropItemAction(Action): '''Drop an item''' def __init__(self, actor: 'Actor', item: 'Item'): @@ -205,7 +197,7 @@ class DropItemAction(ActionWithActor): return self.success() -class HealAction(ActionWithActor): +class HealAction(Action): '''Heal a target actor some number of hit points''' def __init__(self, actor: 'Actor', hit_points_to_recover: int): diff --git a/erynrl/ai.py b/erynrl/ai.py index cca9331..c37b186 100644 --- a/erynrl/ai.py +++ b/erynrl/ai.py @@ -7,7 +7,7 @@ import numpy as np import tcod from . import log -from .actions.action import ActionWithActor +from .actions.action import Action from .actions.game import BumpAction, WaitAction from .components import Component from .geometry import Direction, Point @@ -26,7 +26,7 @@ class AI(Component): super().__init__() self.entity = entity - def act(self, engine: 'Engine') -> Optional[ActionWithActor]: + def act(self, engine: 'Engine') -> Optional[Action]: '''Produce an action to perform''' raise NotImplementedError() @@ -38,7 +38,7 @@ class HostileEnemy(AI): beeline for her. ''' - def act(self, engine: 'Engine') -> Optional[ActionWithActor]: + def act(self, engine: 'Engine') -> Optional[Action]: visible_tiles = tcod.map.compute_fov( engine.map.tiles['transparent'], pov=tuple(self.entity.position), diff --git a/erynrl/engine.py b/erynrl/engine.py index d394852..8b13350 100644 --- a/erynrl/engine.py +++ b/erynrl/engine.py @@ -9,7 +9,7 @@ import tcod from . import log from . import monsters -from .actions.action import Action, ActionWithActor +from .actions.action import Action from .actions.result import ActionResult from .ai import HostileEnemy from .configuration import Configuration @@ -110,7 +110,7 @@ class Engine: def process_input_action(self, action: Action): '''Process an Action from player input''' - if not isinstance(action, ActionWithActor): + if not isinstance(action, Action): action.perform(self) return @@ -159,7 +159,7 @@ class Engine: if action: self._perform_action_until_done(action) - def _perform_action_until_done(self, action: ActionWithActor) -> ActionResult: + def _perform_action_until_done(self, action: Action) -> ActionResult: '''Perform the given action and any alternate follow-up actions until the action chain is done.''' result = action.perform(self) @@ -184,7 +184,7 @@ class Engine: if log.ACTIONS_TREE.isEnabledFor(log.INFO) and self.map.visible[tuple(action.actor.position)]: if result.alternate: - alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor.symbol}]' + alternate_string = f'{result.alternate.__class__.__name__}[{result.alternate.actor}]' else: alternate_string = str(result.alternate) log.ACTIONS_TREE.info( diff --git a/erynrl/events.py b/erynrl/events.py index cc6928b..3a9bcf0 100644 --- a/erynrl/events.py +++ b/erynrl/events.py @@ -6,7 +6,7 @@ import tcod import tcod.event as tev from .actions.action import Action -from .actions.game import BumpAction, ExitAction, WaitAction +from .actions.game import BumpAction, WaitAction from .geometry import Direction if TYPE_CHECKING: @@ -51,9 +51,6 @@ class EngineEventHandler(tev.EventDispatch[Action]): return action - def ev_quit(self, event: tcod.event.Quit) -> Optional[Action]: - return ExitAction() - class GameOverEventHandler(tev.EventDispatch[Action]): '''When the game is over (the hero dies, the player quits, etc), this event handler takes over.''' @@ -61,6 +58,3 @@ class GameOverEventHandler(tev.EventDispatch[Action]): def __init__(self, engine: 'Engine'): super().__init__() self.engine = engine - - def ev_quit(self, event: tev.Quit) -> Optional[Action]: - return ExitAction() diff --git a/erynrl/interface/events.py b/erynrl/interface/events.py index bd8e5fc..09d0447 100644 --- a/erynrl/interface/events.py +++ b/erynrl/interface/events.py @@ -2,7 +2,7 @@ '''Defines event handling mechanisms.''' -from typing import TYPE_CHECKING +from typing import NoReturn, TYPE_CHECKING from tcod import event as tev @@ -42,6 +42,11 @@ class InterfaceEventHandler(tev.EventDispatch[bool]): def ev_mousebuttonup(self, event: tev.MouseButtonUp) -> bool: return self._handle_event(event) + def ev_quit(self, event: tev.Quit) -> NoReturn: + # TODO: Maybe show a "do you want to quit?" alert? + # TODO: Probably inform the engine that we're shutting down. + raise SystemExit() + def _handle_event(self, event: tev.Event) -> bool: for handler in self._handlers: if handler and handler.dispatch(event): From a650e1db492174036aa853b267119f373243270f Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 11 Mar 2023 01:10:10 -0800 Subject: [PATCH 234/234] Clean up the context.new call in __main__ --- erynrl/__main__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/erynrl/__main__.py b/erynrl/__main__.py index 2a79e44..0d18787 100644 --- a/erynrl/__main__.py +++ b/erynrl/__main__.py @@ -56,7 +56,11 @@ def main(argv): interface = Interface(configuration.console_size, engine) tileset = configuration.console_font_configuration.tileset - with tcod.context.new(columns=interface.console.width, rows=interface.console.height, tileset=tileset, title=TITLE) as context: + with tcod.context.new( + columns=interface.console.width, + rows=interface.console.height, + tileset=tileset, + title=TITLE) as context: interface.run_event_loop(context) return 0