Started to build Mapview-class. It acts as an observer for the Map.
[otium.git] / world.py
blob7f6891353d4f73cff41f81aa1aa7e626b6dc303d
1 import math
3 class Position:
4 """
5 Simple class for position information. Implements 'comparable
6 interface' by implementing __cmp__ -method. Comparing is based purely
7 on distances between two Position-instances.
8 """
9 def __init__(self, x, y):
10 self.__x__ = x
11 self.__y__ = y
13 def x(self):
14 """
15 >>> pos = Position(3,4)
16 >>> pos.x()
18 """
19 return self.__x__
21 def y(self):
22 """
23 >>> pos = Position(5,6)
24 >>> pos.y()
26 """
27 return self.__y__
29 def __add__(self, other):
30 return Position(self.x() + other.x(), self.y() + other.y())
32 def __sub__(self, other):
33 return Position(self.x() - other.x(), self.y() - other.y())
35 def distance(self, other):
36 """
37 Counts the distance between self and other.
39 Examples:
40 >>> origin = Position(0,0)
41 >>> origin.distance(Position(0,0))
42 0.0
43 >>> origin.distance(Position(5,7))
44 8.6023252670426267
45 >>> origin.distance(origin)
46 0.0
47 >>> pos = Position(2,4)
48 >>> pos.distance(Position(6,7))
49 5.0
50 """
51 delta_x = self.x() - other.x()
52 delta_y = self.y() - other.y()
53 return math.sqrt(math.pow(delta_x, 2) + math.pow(delta_y, 2))
55 def __cmp__(self, other):
56 """
57 >>> origin = Position(0,0)
58 >>> origin == origin
59 True
60 >>> origin == Position(0,0)
61 True
62 >>> origin <= (origin)
63 True
64 >>> origin >= (origin)
65 True
66 >>> origin != (origin)
67 False
68 >>> origin < Position(3,4)
69 True
70 >>> Position(5,5) > Position(4,1)
71 True
72 """
73 origin = Position(0,0)
74 return self.distance(origin) - other.distance(origin)
76 class Symbol:
77 """
78 Symbolic representation for the use of different objects. Every symbol
79 contains char+color -combination the be represented in the maps of the
80 game. Every symbol can be also referenced by an unique_char. At the
81 moment, it's up to the developer to take care of uniqueness of these
82 unique_chars. It's also up to the developer to take care of the
83 relationship between unique_char and char+color -combination.
84 """
86 def __init__(self, char, color, unique_char):
87 self.__char__ = char
88 self.__color__ = color
89 self.__unique_char__ = unique_char
91 def char(self):
92 return self.__char__
94 def color(self):
95 return self.__color__
97 def unique_char(self):
98 return self.__unique_char__
100 def __eq__(self, other):
102 >>> Symbol('^', 7, 'a') == Symbol('!', 3, 'a')
103 True
104 >>> Symbol('=', 1, 'b') == Symbol('=', 2, 'c')
105 False
106 >>> Symbol('=', 1, 'b') == Symbol('=', 1, 'c')
107 False
109 return self.unique_char() == other.unique_char()
111 def __str__(self):
112 return self.char()
114 class Object:
116 Abstract baseclass for every object in the game. At minimum, every
117 object has a position information, a symbolic (color + char)
118 representation and a description which reveals it's deepest essence to
119 the players.
121 def __init__(self, position, symbol, description):
122 self.__pos__ = position
123 self.__sym__ = symbol
124 self.__desc__ = description
126 def move(self, direction):
128 Parameter direction is a kind of direction vector, still being
129 instance of Position-class. It represents the new relative
130 position of this object.
132 Example:
133 >>> o = Object(Position(4,5),None, None)
134 >>> o.move(Position(0,1)) # North
135 >>> Position(4,6) == o.position() # Assume 'speed' == 1
136 True
138 self.__pos__ = self.position() + direction
140 def position(self):
141 return self.__pos__
143 def symbol(self):
144 return self.__sym__
146 def description(self):
147 return self.__desc__
149 def __str__(self):
150 return self.symbol().__str__()
153 class Tile(Object):
154 def __init__(self, position, symbol, description=""):
155 Object.__init__(self, position, symbol, description)
158 class WorldException(Exception):
160 Baseclass for every exception that Map-class may throw.
162 pass
165 class RectangleArea:
166 def __init__(self, width, height):
167 self.__height__ = height
168 self.__width__ = width
170 def surfacearea(self):
172 >>> Map(4,5,None).surfacearea()
175 return self.width() * self.height()
177 def has_position(self, pos):
179 >>> m = Map(5,5, None)
180 >>> m.has_position(Position(3,3))
181 True
182 >>> m.has_position(Position(6,1))
183 False
184 >>> m.has_position(Position(5,5))
185 False
187 return pos.x() < self.width() and pos.y() < self.height()
189 def height(self):
190 return self.__height__
192 def width(self):
193 return self.__width__
195 def __position__(self, i):
196 y = i / self.width()
197 x = i % self.width()
198 return Position(x,y)
200 def __index__(self, pos):
201 return pos.y() * self.width() + pos.x()
203 class StoringRectangleArea(RectangleArea):
204 def __init__(self, width, height, empty_symbol):
205 RectangleArea.__init__(self, width, height)
206 self.__empty_symbol__ = empty_symbol
207 self.__items__ = []
208 self.__init_items__()
210 def __init_items__(self):
211 for i in range(self.surfacearea()):
212 pos = self.__position__(i)
213 t = Tile(pos, self.empty_symbol(), "Space. Cold.")
214 self.__items__.append(t)
217 def is_complete(self):
219 Returns a boolean value that indicates whether every position
220 in the area is populated by a item. If there is even one
221 empty_symbol, this method returns false.
223 Examples:
225 >>> import world
226 >>> WATER = world.Symbol('=', 1, 'a')
227 >>> DESERT = world.Symbol('.', 3, 'b')
228 >>> area = Map(2,2,None)
231 for item in self:
232 if item.symbol() == self.empty_symbol():
233 return False
234 return True
236 def reserved(self, pos):
237 item = self.get(pos)
238 if item.symbol() != self.empty_symbol():
239 return True
240 return False
242 def empty_symbol(self):
243 return self.__empty_symbol__
245 def get(self, pos):
246 i = self.__index__(pos)
247 return self.__items__[i]
249 def __iter__(self):
250 return self.__items__.__iter__()
252 # def fromlist(self, tiles, replace=False):
253 # """
254 # Inserts tiles from the given list.
255 # """
256 # if len(tiles) != self.surfacearea():
257 # raise WorldException("Data does not fit into the map.")
258 # raise WorldException("Not yet implemented.")
260 def insert(self, tile, replace=False):
262 Public interface for inserting tiles into the map.
263 Overrides Map's __insert-method and adds handling for replacing
264 inserts. Throws WorldException if there is already a tile in
265 position (x,y) and replacing is not allowed. By default,
266 replacing is forbidden.
268 pos = tile.position()
269 if self.reserved(pos) and not replace:
270 raise WorldException("Position %s is reserved." % pos)
271 i = self.__index__(pos)
272 self.__items__[i] = tile
275 class MapEvent:
276 BEFORE_INSERT = 0
277 AFTER_INSERT = 1
279 def __init__(self, type, object):
280 self.__type__ = type
281 self.__object__ = object
283 def type(self):
284 return self.__type__
286 def object(self):
287 return self.__object__
290 class Map(StoringRectangleArea):
292 Class to represent rectangle-shaped areas populated by different kinds
293 of tiles. Tiles are accessed using coordinate-like attributes.
295 def __init__(self, width, height, empty_symbol):
296 StoringRectangleArea.__init__(self, width, height, empty_symbol)
297 self.__observers__ = set()
299 def add_observer(self, observer):
300 self.__observers__.add(observer)
302 def remove_observer(self, observer):
303 self.__observers__.discard(observer)
305 def notify_observers(self, event):
306 for observer in self.__observers__:
307 observer.map_event(event)
309 def insert(self, tile, replace=False):
311 Public interface for inserting tiles into the map.
312 Overrides Map's __insert-method and adds handling for replacing
313 inserts. Throws WorldException if there is already a tile in
314 position (x,y) and replacing is not allowed. By default,
315 replacing is forbidden.
317 self.notify_observers(MapEvent(MapEvent.BEFORE_INSERT, tile))
318 StoringRectangleArea.insert(self, tile, replace)
319 self.notify_observers(MapEvent(MapEvent.AFTER_INSERT, tile))
321 def _test():
322 import doctest
323 doctest.testmod()
325 if __name__ == "__main__":
326 _test()