44If you are developing your own map format, please use this 
55as a template.  Just fill in values that work for your game. 
66""" 
7+ 
78from  __future__ import  annotations 
89
910import  time 
11+ from  collections .abc  import  Iterable 
1012from  heapq  import  heappop , heappush 
1113from  itertools  import  product 
14+ from  typing  import  Any , Optional 
1215
1316import  pygame 
14- from  pygame  import  Surface 
17+ from  pygame  import  Rect ,  Surface 
1518
1619try :
1720    # optional pytmx support 
@@ -43,22 +46,29 @@ class PyscrollDataAdapter:
4346    # or properties.  they are listed here as class 
4447    # instances, but use as properties is fine, too. 
4548
46-     tile_size  =  None   # (int, int): size of each tile in pixels 
47-     map_size  =  None   # (int, int): size of map in tiles 
48-     visible_tile_layers  =  None   # list of visible layer integers 
49+     # (int, int): size of each tile in pixels 
50+     tile_size : Vector2DInt  =  (0 , 0 )
51+     # (int, int): size of map in tiles 
52+     map_size : Vector2DInt  =  (0 , 0 )
53+     # list of visible layer integers 
54+     visible_tile_layers : list [int ] =  []
4955
5056    def  __init__ (self ) ->  None :
51-         self ._last_time  =  None   # last time map animations were updated 
52-         self ._animation_queue  =  list ()  # list of animation tokens 
53-         self ._animated_tile  =  dict ()  # mapping of tile substitutions when animated 
54-         self ._tracked_tiles  =  set ()  # track the tiles on screen with animations 
57+         # last time map animations were updated 
58+         self ._last_time : float  =  0.0 
59+         # list of animation tokens 
60+         self ._animation_queue : list [AnimationToken ] =  []
61+         # mapping of tile substitutions when animated 
62+         self ._animated_tile : dict [tuple [int , int , int ], Surface ] =  {}
63+         # track the tiles on screen with animations 
64+         self ._tracked_tiles  =  set ()
5565
5666    def  reload_data (self ) ->  None :
5767        raise  NotImplementedError 
5868
5969    def  process_animation_queue (
6070        self ,
61-         tile_view : RectLike ,
71+         tile_view : Rect ,
6272    ) ->  list [tuple [int , int , int , Surface ]]:
6373        """ 
6474        Given the time and the tile view, process tile changes and return them 
@@ -67,7 +77,7 @@ def process_animation_queue(
6777            tile_view: Rect representing tiles on the screen 
6878
6979        """ 
70-         new_tiles  =  list () 
80+         new_tiles  =  [] 
7181
7282        # verify that there are tile substitutions ready 
7383        self ._update_time ()
@@ -131,7 +141,7 @@ def _update_time(self) -> None:
131141        """ 
132142        self ._last_time  =  time .time () *  1000 
133143
134-     def  prepare_tiles (self , tiles : RectLike ):
144+     def  prepare_tiles (self , tiles : RectLike )  ->   None :
135145        """ 
136146        Somewhat experimental: The renderer will advise data layer of its view 
137147
@@ -155,14 +165,13 @@ def reload_animations(self) -> None:
155165
156166        """ 
157167        self ._update_time ()
158-         self ._animation_queue  =  list ()
159-         self ._tracked_gids  =  set ()
160-         self ._animation_map  =  dict ()
168+         self ._tracked_gids : set [int ] =  set ()
169+         self ._animation_map : dict [int , AnimationToken ] =  {}
161170
162171        for  gid , frame_data  in  self .get_animations ():
163172            self ._tracked_gids .add (gid )
164173
165-             frames   =   list () 
174+             frames :  list [ AnimationFrame ]  =  [] 
166175            for  frame_gid , frame_duration  in  frame_data :
167176                image  =  self ._get_tile_image_by_id (frame_gid )
168177                frames .append (AnimationFrame (image , frame_duration ))
@@ -174,7 +183,7 @@ def reload_animations(self) -> None:
174183            # locations of an animation, but searching for their locations 
175184            # is slow. so it will be updated as the map is drawn. 
176185
177-             positions  =  set ()
186+             positions :  set [ tuple [ int ,  int ,  int ]]  =  set ()
178187            ani  =  AnimationToken (positions , frames , self ._last_time )
179188            self ._animation_map [gid ] =  ani 
180189            heappush (self ._animation_queue , ani )
@@ -221,7 +230,7 @@ def _get_tile_image(self, x: int, y: int, l: int) -> Surface:
221230        """ 
222231        raise  NotImplementedError 
223232
224-     def  _get_tile_image_by_id (self , id ) :
233+     def  _get_tile_image_by_id (self , id :  int )  ->   Any :
225234        """ 
226235        Return Image by a custom ID. 
227236
@@ -245,6 +254,9 @@ def get_animations(self) -> None:
245254          Where Frames is: 
246255          [ (ID, Duration), ... ] 
247256
257+           [tuple[int, tuple[int, float]]] 
258+           [tuple[gid, tuple[frame_gid, frame_duration]]] 
259+ 
248260          And ID is a reference to a tile image. 
249261          This will be something accessible using _get_tile_image_by_id 
250262
@@ -289,7 +301,7 @@ class TiledMapData(PyscrollDataAdapter):
289301
290302    """ 
291303
292-     def  __init__ (self , tmx ) ->  None :
304+     def  __init__ (self , tmx :  pytmx . TiledMap ) ->  None :
293305        super (TiledMapData , self ).__init__ ()
294306        self .tmx  =  tmx 
295307        self .reload_animations ()
@@ -308,7 +320,7 @@ def get_animations(self):
308320                yield  gid , frames 
309321
310322    def  convert_surfaces (self , parent : Surface , alpha : bool  =  False ) ->  None :
311-         images  =  list () 
323+         images  =  [] 
312324        for  i  in  self .tmx .images :
313325            try :
314326                if  alpha :
@@ -320,19 +332,19 @@ def convert_surfaces(self, parent: Surface, alpha: bool = False) -> None:
320332        self .tmx .images  =  images 
321333
322334    @property  
323-     def  tile_size (self ):
335+     def  tile_size (self )  ->   Vector2DInt :
324336        return  self .tmx .tilewidth , self .tmx .tileheight 
325337
326338    @property  
327-     def  map_size (self ):
339+     def  map_size (self )  ->   Vector2DInt :
328340        return  self .tmx .width , self .tmx .height 
329341
330342    @property  
331343    def  visible_tile_layers (self ):
332344        return  self .tmx .visible_tile_layers 
333345
334346    @property  
335-     def  visible_object_layers (self ):
347+     def  visible_object_layers (self )  ->   Iterable [ pytmx . TiledObjectGroup ] :
336348        return  (
337349            layer 
338350            for  layer  in  self .tmx .visible_layers 
@@ -345,11 +357,11 @@ def _get_tile_image(self, x: int, y: int, l: int):
345357        except  ValueError :
346358            return  None 
347359
348-     def  _get_tile_image_by_id (self , id ) ->  Surface :
360+     def  _get_tile_image_by_id (self , id :  int ) ->  Surface :
349361        return  self .tmx .images [id ]
350362
351363    def  get_tile_images_by_rect (self , rect : RectLike ):
352-         def  rev (seq , start , stop ) :
364+         def  rev (seq :  list [ int ] , start :  int , stop :  int )  ->   enumerate [ int ] :
353365            if  start  <  0 :
354366                start  =  0 
355367            return  enumerate (seq [start  : stop  +  1 ], start )
@@ -393,92 +405,124 @@ class MapAggregator(PyscrollDataAdapter):
393405
394406    """ 
395407
396-     def  __init__ (self , tile_size ) ->  None :
408+     def  __init__ (self , tile_size :  Vector2DInt ) ->  None :
397409        super ().__init__ ()
398410        self .tile_size  =  tile_size 
399411        self .map_size  =  0 , 0 
400-         self .maps   =   list () 
412+         self .maps :  list [ tuple [ PyscrollDataAdapter ,  Rect ,  int ]]  =  [] 
401413        self ._min_x  =  0 
402414        self ._min_y  =  0 
403415
404-     def  _get_tile_image (self , x : int , y : int , l : int ) ->  Surface :
416+     def  _get_tile_image (self , x : int , y : int , l : int ) ->  Optional [ Surface ] :
405417        """ 
406-         Required for sprite collation - not implemented 
407- 
418+         Required for sprite collation - not implemented. 
408419        """ 
409420        pass 
410421
411-     def  _get_tile_image_by_id (self , id ) ->  None :
422+     def  _get_tile_image_by_id (self , id :  int ) ->  None :
412423        """ 
413-         Required for sprite collation - not implemented 
414- 
424+         Required for sprite collation - not implemented. 
415425        """ 
416426        pass 
417427
418-     def  add_map (self , data : PyscrollDataAdapter , offset : Vector2DInt ) ->  None :
428+     def  add_map (
429+         self , data : PyscrollDataAdapter , offset : Vector2DInt , layer : int  =  0 
430+     ) ->  None :
419431        """ 
420-         Add map data and position it with an offset 
432+         Add map data and position it with an offset.  
421433
422434        Args: 
423-             data: Data Adapater , such as TiledMapData 
435+             data: Data Adapter , such as TiledMapData 
424436            offset: Where the upper-left corner is, in tiles 
425- 
437+             layer: The layer of the map 
426438        """ 
427-         assert  data .tile_size  ==  self .tile_size 
439+         if  data .tile_size  !=  self .tile_size :
440+             raise  ValueError ("Tile sizes must be the same for all maps." )
441+ 
428442        rect  =  pygame .Rect (offset , data .map_size )
429-         ox  =  self ._min_x  -  offset [0 ]
430-         oy  =  self ._min_y  -  offset [1 ]
431443        self ._min_x  =  min (self ._min_x , offset [0 ])
432444        self ._min_y  =  min (self ._min_y , offset [1 ])
433-         mx  =  0 
434-         my  =  0 
445+ 
435446        # the renderer cannot deal with negative tile coordinates, 
436447        # so we must move all the offsets if there is a negative so 
437448        # that all the tile coordinates are >= (0, 0) 
438-         self .maps .append ((data , rect ))
449+         self .maps .append ((data , rect , layer ))
450+         self ._adjust_map_positions (offset )
451+         self ._update_map_size ()
452+ 
453+     def  remove_map (self , data : PyscrollDataAdapter ) ->  None :
454+         """ 
455+         Removes a map from the aggregator. 
456+ 
457+         Args: 
458+             data: The data adapter of the map to remove. 
459+         """ 
460+         if  data  not  in   [m [0 ] for  m  in  self .maps ]:
461+             raise  ValueError ("Map is not in the aggregator" )
462+         self .maps  =  [m  for  m  in  self .maps  if  m [0 ] !=  data ]
463+         self ._update_map_size ()
464+ 
465+     def  _adjust_map_positions (self , offset : Vector2DInt ) ->  None :
466+         """Adjusts map positions based on negative offsets.""" 
467+         ox  =  self ._min_x  -  offset [0 ]
468+         oy  =  self ._min_y  -  offset [1 ]
439469        if  ox  >  0  or  oy  >  0 :
440-             for  data , rect  in  self .maps :
470+             for  _ , rect ,  _  in  self .maps :
441471                rect .move_ip ((ox , oy ))
442-                 mx  =  max (mx , rect .right )
443-                 my  =  max (my , rect .bottom )
444472        else :
445-             rect .move_ip (- self ._min_x , - self ._min_y )
473+             for  _ , rect , _  in  self .maps :
474+                 rect .move_ip (- self ._min_x , - self ._min_y )
475+ 
476+     def  _update_map_size (self ) ->  None :
477+         """Updates the overall map size.""" 
478+         mx  =  0 
479+         my  =  0 
480+         for  _ , rect , _  in  self .maps :
446481            mx  =  max (mx , rect .right )
447482            my  =  max (my , rect .bottom )
448483        self .map_size  =  mx , my 
449484
450-     def  remove_map (self , data : PyscrollDataAdapter ):
451-         """ 
452-         Remove map - not implemented 
453- 
454-         """ 
455-         raise  NotImplementedError 
456- 
457485    def  get_animations (self ) ->  None :
458486        """ 
459487        Get animations - not implemented 
460- 
461488        """ 
462489        pass 
463490
464491    def  reload_data (self ) ->  None :
465492        """ 
466493        Reload the tiles - not implemented 
467- 
468494        """ 
469495        pass 
470496
471497    @property  
472-     def  visible_tile_layers (self ):
498+     def  visible_tile_layers (self ) ->  list [int ]:
499+         """ 
500+         Returns a sorted list of all visible tile layers from all added maps. 
501+         """ 
473502        layers  =  set ()
474-         for  data , offset  in  self .maps :
503+         for  data , rect ,  z  in  self .maps :
475504            layers .update (list (data .visible_tile_layers ))
476505        return  sorted (layers )
477506
478-     def  get_tile_images_by_rect (self , view : RectLike ):
507+     def  get_tile_images_by_rect (
508+         self , view : pygame .Rect 
509+     ) ->  Iterable [tuple [int , int , int , Surface ]]:
510+         """ 
511+         Yields tile images within the given view rectangle from all added maps. 
512+ 
513+         Args: 
514+             view: Rect-like object defining the tile view. 
515+         """ 
479516        view  =  pygame .Rect (view )
480-         for  data , rect  in  self .maps :
517+         for  data , rect ,  z  in  self .maps :
481518            ox , oy  =  rect .topleft 
482519            clipped  =  rect .clip (view ).move (- ox , - oy )
483-             for  x , y , l , image  in  data .get_tile_images_by_rect (clipped ):
484-                 yield  x  +  ox , y  +  oy , l , image 
520+             if  clipped .width  >  0  and  clipped .height  >  0 :
521+                 for  x , y , l , image  in  data .get_tile_images_by_rect (clipped ):
522+                     yield  x  +  ox , y  +  oy , l , image 
523+ 
524+     def  __repr__ (self ) ->  str :
525+         return  f"MapAggregator(tile_size={ self .tile_size }  , maps={ self .maps }  )" 
526+ 
527+     def  __len__ (self ) ->  int :
528+         return  len (self .maps )
0 commit comments