Commit before breaking everything
[matches/honours.git] / research / transmission_spectroscopy / simulator / pgu-0.18 / pgu / vid.py
1 """Sprite and tile engine.
2
3 tilevid, isovid, hexvid are all subclasses of this interface.
4
5 Includes support for:
6
7 * Foreground Tiles
8 * Background Tiles
9 * Sprites
10 * Sprite-Sprite Collision handling
11 * Sprite-Tile Collision handling
12 * Scrolling 
13 * Loading from PGU tile and sprite formats (optional)
14 * Set rate FPS (optional)
15
16 This code was previously known as the King James Version (named after the
17 Bible of the same name for historical reasons.)
18
19 """
20
21 import pygame
22 from pygame.rect import Rect
23 from pygame.locals import *
24 import math
25
26 class Sprite:
27     """The object used for Sprites.
28
29     Arguments:
30         ishape -- an image, or an image, rectstyle.  The rectstyle will
31             describe the shape of the image, used for collision
32             detection.
33         pos -- initial (x,y) position of the Sprite.
34     
35     Attributes:
36         rect -- the current position of the Sprite
37         _rect -- the previous position of the Sprite
38         groups -- the groups the Sprite is in
39         agroups -- the groups the Sprite can hit in a collision
40         hit -- the handler for hits -- hit(g,s,a)
41         loop -- the loop handler, called once a frame
42
43     """
44     def __init__(self,ishape,pos):
45         if not isinstance(ishape, tuple):
46             ishape = ishape,None
47         image,shape = ishape
48         if shape == None:
49             shape = pygame.Rect(0,0,image.get_width(),image.get_height())
50         if isinstance(shape, tuple): shape = pygame.Rect(shape)
51         self.image = image
52         self._image = self.image
53         self.shape = shape
54         self.rect = pygame.Rect(pos[0],pos[1],shape.w,shape.h)
55         self._rect = pygame.Rect(self.rect)
56         self.irect = pygame.Rect(pos[0]-self.shape.x,pos[1]-self.shape.y,
57             image.get_width(),image.get_height())
58         self._irect = pygame.Rect(self.irect)
59         self.groups = 0
60         self.agroups = 0
61         self.updated = 1
62         
63     def setimage(self,ishape):
64         """Set the image of the Sprite.
65         
66         Arguments:
67             ishape -- an image, or an image, rectstyle.  The rectstyle will
68                       describe the shape of the image, used for collision detection.
69
70         """        
71         if not isinstance(ishape, tuple):
72             ishape = ishape,None
73         image,shape = ishape
74         if shape == None:
75             shape = pygame.Rect(0,0,image.get_width(),image.get_height())
76         if isinstance(shape, tuple):
77             shape = pygame.Rect(shape)
78         self.image = image
79         self.shape = shape
80         self.rect.w,self.rect.h = shape.w,shape.h
81         self.irect.w,self.irect.h = image.get_width(),image.get_height()
82         self.updated = 1
83
84         
85 class Tile:
86     """Tile Object used by TileCollide.
87     
88     Arguments:
89         image -- an image for the Tile.
90     
91     Attributes:
92         agroups -- the groups the Tile can hit in a collision
93         hit -- the handler for hits -- hit(g,t,a)
94
95     """
96     def __init__(self,image=None):
97         self.image = image
98         self.agroups = 0
99         
100     def __setattr__(self,k,v):
101         if k == 'image' and v != None:
102             self.image_h = v.get_height()
103             self.image_w = v.get_width()
104         self.__dict__[k] = v
105
106 class _Sprites(list):
107     def __init__(self):
108         list.__init__(self)
109         self.removed = []
110         
111     def append(self,v):
112         list.append(self,v)
113         v.updated = 1
114         
115     def remove(self,v):
116         list.remove(self,v)
117         v.updated = 1
118         self.removed.append(v)
119         
120 class Vid:
121     """An engine for rendering Sprites and Tiles.
122     
123     Attributes:
124         sprites -- a list of the Sprites to be displayed.  You may append and
125                    remove Sprites from it.
126         images  -- a dict for images to be put in.  
127         size    -- the width, height in Tiles of the layers.  Do not modify.
128         view    -- a pygame.Rect of the viewed area.  You may change .x, .y,
129                     etc to move the viewed area around.
130         bounds  -- a pygame.Rect (set to None by default) that sets the bounds
131                     of the viewable area.  Useful for setting certain borders
132                     as not viewable.
133         tlayer  -- the foreground tiles layer
134         clayer  -- the code layer (optional)
135         blayer  -- the background tiles layer (optional)
136         groups  -- a hash of group names to group values (32 groups max, as a tile/sprites 
137                 membership in a group is determined by the bits in an integer)
138
139     """
140     
141     def __init__(self):
142         self.tiles = [None for x in xrange(0,256)]
143         self.sprites = _Sprites()
144         self.images = {} #just a store for images.
145         self.layers = None
146         self.size = None
147         self.view = pygame.Rect(0,0,0,0)
148         self._view = pygame.Rect(self.view)
149         self.bounds = None
150         self.updates = []
151         self.groups = {}
152     
153         
154     def resize(self,size,bg=0):
155         """Resize the layers.
156         
157         Arguments:
158             size -- w,h in Tiles of the layers
159             bg   -- set to 1 if you wish to use both a foreground layer and a
160                     background layer
161
162         """
163         self.size = size
164         w,h = size
165         self.layers = [[[0 for x in xrange(0,w)] for y in xrange(0,h)]
166             for z in xrange(0,4)]
167         self.tlayer = self.layers[0]
168         self.blayer = self.layers[1]
169         if not bg: self.blayer = None
170         self.clayer = self.layers[2]
171         self.alayer = self.layers[3]
172         
173         self.view.x, self.view.y = 0,0
174         self._view.x, self.view.y = 0,0
175         self.bounds = None
176         
177         self.updates = []
178     
179     def set(self,pos,v):
180         """Set a tile in the foreground to a value.
181         
182         Use this method to set tiles in the foreground, as it will make
183         sure the screen is updated with the change.  Directly changing
184         the tlayer will not guarantee updates unless you are using .paint()
185         
186         Arguments:
187             pos -- (x,y) of tile
188             v -- value
189
190         """
191         if self.tlayer[pos[1]][pos[0]] == v: return
192         self.tlayer[pos[1]][pos[0]] = v
193         self.alayer[pos[1]][pos[0]] = 1
194         self.updates.append(pos)
195         
196     def get(self,pos):
197         """Get the tlayer at pos.
198         
199         Arguments:
200             pos -- (x,y) of tile
201
202         """
203         return self.tlayer[pos[1]][pos[0]]
204     
205     def paint(self,s):
206         """Paint the screen.
207         
208         Arguments:
209             screen -- a pygame.Surface to paint to
210
211         Returns the updated portion of the screen (all of it)
212
213         """
214         return []
215                 
216     def update(self,s):
217         """Update the screen.
218         
219         Arguments:
220             screen -- a pygame.Rect to update
221         
222         Returns a list of updated rectangles.
223
224         """
225         self.updates = []
226         return []
227
228     def tga_load_level(self,fname,bg=0):
229         """Load a TGA level.  
230         
231         Arguments:
232             g        -- a Tilevid instance
233             fname    -- tga image to load
234             bg        -- set to 1 if you wish to load the background layer
235
236         """
237         if type(fname) == str: img = pygame.image.load(fname)
238         else: img = fname
239         w,h = img.get_width(),img.get_height()
240         self.resize((w,h),bg)
241         for y in range(0,h):
242             for x in range(0,w):
243                 t,b,c,_a = img.get_at((x,y))
244                 self.tlayer[y][x] = t
245                 if bg: self.blayer[y][x] = b
246                 self.clayer[y][x] = c
247                 
248     def tga_save_level(self,fname):
249         """Save a TGA level.
250
251         Arguments:
252             fname -- tga image to save to
253
254         """
255         w,h = self.size
256         img = pygame.Surface((w,h),SWSURFACE,32)
257         img.fill((0,0,0,0))
258         for y in range(0,h):
259             for x in range(0,w):
260                 t = self.tlayer[y][x]
261                 b = 0
262                 if self.blayer:
263                     b = self.blayer[y][x]
264                 c = self.clayer[y][x]
265                 _a = 0
266                 img.set_at((x,y),(t,b,c,_a))
267         pygame.image.save(img,fname)
268                 
269                 
270
271     def tga_load_tiles(self,fname,size,tdata={}):
272         """Load a TGA tileset.
273         
274         Arguments:
275             g       -- a Tilevid instance
276             fname    -- tga image to load
277             size    -- (w,h) size of tiles in pixels
278             tdata    -- tile data, a dict of tile:(agroups, hit handler, config)
279
280         """
281         TW,TH = size
282         if type(fname) == str: img = pygame.image.load(fname).convert_alpha()
283         else: img = fname
284         w,h = img.get_width(),img.get_height()
285         
286         n = 0
287         for y in range(0,h,TH):
288             for x in range(0,w,TW):
289                 i = img.subsurface((x,y,TW,TH))
290                 tile = Tile(i)
291                 self.tiles[n] = tile
292                 if n in tdata:
293                     agroups,hit,config = tdata[n]
294                     tile.agroups = self.string2groups(agroups)
295                     tile.hit = hit
296                     tile.config = config
297                 n += 1
298
299
300     def load_images(self,idata):
301         """Load images.
302         
303         Arguments:
304             idata -- a list of (name, fname, shape)
305
306         """
307         for name,fname,shape in idata:
308             self.images[name] = pygame.image.load(fname).convert_alpha(),shape
309
310     def run_codes(self,cdata,rect):
311         """Run codes.
312         
313         Arguments:
314             cdata -- a dict of code:(handler function, value)
315             rect -- a tile rect of the parts of the layer that should have
316                  their codes run
317
318         """
319         tw,th = self.tiles[0].image.get_width(),self.tiles[0].image.get_height()
320
321         x1,y1,w,h = rect
322         clayer = self.clayer
323         t = Tile()
324         for y in range(y1,y1+h):
325             for x in range(x1,x1+w):
326                 n = clayer[y][x]
327                 if n in cdata:
328                     fnc,value = cdata[n]
329                     t.tx,t.ty = x,y
330                     t.rect = pygame.Rect(x*tw,y*th,tw,th)
331                     fnc(self,t,value)
332
333         
334     def string2groups(self,str):
335         """Convert a string to groups."""
336         if str == None: return 0
337         return self.list2groups(str.split(","))
338
339     def list2groups(self,igroups):
340         """Convert a list to groups."""
341         for s in igroups:
342             if not s in self.groups:
343                 self.groups[s] = 2**len(self.groups)
344         v = 0
345         for s,n in self.groups.items():
346             if s in igroups: v|=n
347         return v
348
349     def groups2list(self,groups):
350         """Convert a groups to a list."""
351         v = []
352         for s,n in self.groups.items():
353             if (n&groups)!=0: v.append(s)
354         return v
355
356     def hit(self,x,y,t,s):
357         tiles = self.tiles
358         tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
359         t.tx = x
360         t.ty = y
361         t.rect = Rect(x*tw,y*th,tw,th)
362         t._rect = t.rect
363         if hasattr(t,'hit'):
364             t.hit(self,t,s)
365
366     def loop(self):
367         """Update and hit testing loop.  Run this once per frame."""
368         self.loop_sprites() #sprites may move
369         self.loop_tilehits() #sprites move
370         self.loop_spritehits() #no sprites should move
371         for s in self.sprites:
372             s._rect = pygame.Rect(s.rect)
373         
374     def loop_sprites(self):
375         as_ = self.sprites[:]
376         for s in as_:
377             if hasattr(s,'loop'):
378                 s.loop(self,s)
379
380     def loop_tilehits(self):
381         tiles = self.tiles
382         tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
383
384         layer = self.layers[0]
385
386         as_ = self.sprites[:]
387         for s in as_:
388             self._tilehits(s)
389     
390     def _tilehits(self,s):
391         tiles = self.tiles
392         tw,th = tiles[0].image.get_width(),tiles[0].image.get_height()
393         layer = self.layers[0]
394         
395         for _z in (0,): 
396             if s.groups != 0:
397
398                 _rect = s._rect
399                 rect = s.rect
400
401                 _rectx = _rect.x
402                 _recty = _rect.y
403                 _rectw = _rect.w
404                 _recth = _rect.h
405
406                 rectx = rect.x
407                 recty = rect.y
408                 rectw = rect.w
409                 recth = rect.h
410
411                 rect.y = _rect.y
412                 rect.h = _rect.h
413
414                 hits = []
415                 ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
416                 #nasty ol loops
417                 y = ct/th*th
418                 while y < cb:
419                     x = cl/tw*tw
420                     yy = y/th
421                     while x < cr:
422                         xx = x/tw
423                         t = tiles[layer[yy][xx]]
424                         if (s.groups & t.agroups)!=0:
425                             #self.hit(xx,yy,t,s)
426                             d = math.hypot(rect.centerx-(xx*tw+tw/2),
427                                 rect.centery-(yy*th+th/2))
428                             hits.append((d,t,xx,yy))
429
430                         x += tw
431                     y += th
432                 
433                 hits.sort()
434                 #if len(hits) > 0: print self.frame,hits
435                 for d,t,xx,yy in hits:
436                     self.hit(xx,yy,t,s)
437                 
438                 #switching directions...
439                 _rect.x = rect.x
440                 _rect.w = rect.w
441                 rect.y = recty
442                 rect.h = recth
443
444                 hits = []
445                 ct,cb,cl,cr = rect.top,rect.bottom,rect.left,rect.right
446                 #nasty ol loops
447                 y = ct/th*th
448                 while y < cb:
449                     x = cl/tw*tw
450                     yy = y/th
451                     while x < cr:
452                         xx = x/tw
453                         t = tiles[layer[yy][xx]]
454                         if (s.groups & t.agroups)!=0:
455                             d = math.hypot(rect.centerx-(xx*tw+tw/2),
456                                 rect.centery-(yy*th+th/2))
457                             hits.append((d,t,xx,yy))
458                             #self.hit(xx,yy,t,s)
459                         x += tw
460                     y += th
461                 
462                 hits.sort()    
463                 #if len(hits) > 0: print self.frame,hits
464                 for d,t,xx,yy in hits:
465                     self.hit(xx,yy,t,s)
466
467                 #done with loops
468                 _rect.x = _rectx
469                 _rect.y = _recty
470
471
472     def loop_spritehits(self):
473         as_ = self.sprites[:]
474         
475         groups = {}
476         for n in range(0,31):
477             groups[1<<n] = []
478         for s in as_:
479             g = s.groups
480             n = 1
481             while g:
482                 if (g&1)!=0: groups[n].append(s)
483                 g >>= 1
484                 n <<= 1
485                 
486         for s in as_:
487             if s.agroups!=0:
488                 rect1,rect2 = s.rect,Rect(s.rect)
489                 #if rect1.centerx < 320: rect2.x += 640
490                 #else: rect2.x -= 640
491                 g = s.agroups
492                 n = 1
493                 while g:
494                     if (g&1)!=0:
495                         for b in groups[n]:    
496                             if (s != b and (s.agroups & b.groups)!=0
497                                     and s.rect.colliderect(b.rect)):
498                                 s.hit(self,s,b)
499
500                     g >>= 1
501                     n <<= 1
502
503
504     def screen_to_tile(self,pos):
505         """Convert a screen position to a tile position."""
506         return pos
507         
508     def tile_to_screen(self,pos):
509         """Convert a tile position to a screen position."""
510         return pos
511                     
512

UCC git Repository :: git.ucc.asn.au