Mockup: psp2d.py

Line 
1 #psp2d.py
2 #pygame-wrapper to fake psp2d module as found on http://fraca7.free.fr/pspwiki/doku.php?id=psp2d
3 #meant merely to allow pypsp games to be made on a computer
4
5 #This is under a BSD License, copyright Kousu <kousue@gmail.com> 2006
6
7 __author__ = 'Kousu <kousue@gmail.com>'
8 __copyright__ = 'BSD License, 2006'
9
10 #Note to fraca: psp2d is not especially pythonic yet. This is sad. In particular, Controller should
11
12 """
13 Todo:
14 Dump dir() on this module and all classes in the real module and such and implement everything exactly like it should be
15 Fixup controller mappings to be easier to work with
16 Protect the display from being reinitialized somehow?
17 make it wipe the mouse trail every time instead of relying on the user code to wipe the screen. Would have to cache stuff.
18 """
19
20 from __future__ import division
21
22 import sys
23 import pygame, SFont
24 from pygame.locals import *
25
26 class Controller:
27         """This class gives access to the state of the pad and buttons. The state is read upon instantiation and is accessible through read-only properties.
28         """
29        
30         def __init__(self):
31                 """Read the current state of the 'controller'.
32                 This should be read-only to follow fraca's idiot way, but oh well.
33                 See the code for what each PSP control is mapped to
34                 
35                 While the controller has a mousepos property the mouse is being dragged which is mapped to the joystick being moved. mousepos is used to calculate a distance to pretend the joystick is moving.
36                 """
37                
38                 #The mouse motion in the X direction, hard limited and normalized to the range -127 and 128.
39                 #normalize and hardlimit:
40                 #we'll say 100px = 127 (i.e. the highest is 127)
41                 #px = 1.27j
42                 #Npx = n*1.27j
43                 
44                 #self.analogX, self.analogY = [1.27*c for c in pygame.mouse.get_rel()]
45                 self.analogX, self.analogY = 0,0
46                 if hasattr(self, 'mousepos'):
47                         newpos = pygame.mouse.get_pos()
48                         pygame.draw.line(pygame.display.get_surface(), (0,0,0), self.mousepos, newpos)
49                         pygame.display.flip()
50                         self.analogX, self.analogY = (b-a for a,b in zip(self.mousepos, newpos))
51                         #if __debug__: print self.analogX, self.analogY
52                         if -127>self.analogX: self.analogX=-127
53                         elif self.analogX>128: self.analogX=128
54                         if -127>self.analogY: self.analogY=-127 #XXX stupid ugly shit code
55                         elif self.analogY>128: self.analogY=128
56                
57                 #if __debug__: print (self.analogX, self.analogY)
58                 pygame.event.pump() #update the in-memory keystate
59                 keys = pygame.key.get_pressed()
60                 keys = [i for i,pressed in enumerate(keys) if pressed] #make a list of all the IDs of the currently pressed keys
61                 #if __debug__ and keys: print keys
62                 
63                 self.start = K_F1 in keys
64                 self.select = K_F2 in keys
65                                
66                 #self.square = K_a in keys
67                 #self.triangle = K_w in keys
68                 #self.circle = K_d in keys
69                 #self.cross = K_s in keys
70                 #self.up = K_UP in keys or K_i in keys
71                 #self.down = K_DOWN in keys or K_k in keys
72                 #self.left = K_LEFT in keys or K_j in keys
73                 #self.right = K_RIGHT in keys or K_l in keys
74
75
76                 self.triangle = K_i in keys
77                 self.square = K_j in keys
78
79                 self.circle = K_l in keys
80
81                 self.cross = K_k in keys
82                
83
84                 self.up = K_w in keys or K_UP in keys
85
86                 self.left = K_a in keys or K_LEFT in keys
87
88                 self.down = K_s in keys or K_DOWN in keys
89
90                 self.right = K_d in keys or K_RIGHT in keys
91
92                
93                 #self.l = K_BACKQUOTE in keys
94                 #self.r = K_BACKSPACE in keys
95                 
96                 self.l = K_LSHIFT in keys
97                 self.r = K_RSHIFT in keys
98                
99                
100                 for e in pygame.event.get():
101                         if e.type == QUIT: sys.exit(0) #this is a little hook to make the program nice to the rest of the system. Under the assumption that the app will often be querying the controller, this here serves to kill the app if the user clicks close or something
102                         if e.type == MOUSEBUTTONDOWN:
103                                 Controller.mousepos = e.pos #cache the position, used in calculating how much to give to the joystick
104
105                         if e.type == MOUSEBUTTONUP:
106                                 del Controller.mousepos
107                
108 #GRAPHICS
109 class Color:
110         """
111         Represents a colour.
112         In pygame, this would be:
113         (r,g,b) or (r,g,b,a)
114         """
115         def __init__(self, r, g, b, a = 0):
116                 self.red = r
117                 self.green = g
118                 self.blue = b
119                 self.alpha = a
120         def tuple(self):
121                 """Turns this colour into a pygame-style tuple
122                 this is a non-standard extension. NOT PART OS PYPSP!"""
123                 return (self.red, self.green, self.blue, self.alpha)
124                 #XXX should this skip the alpha in certain cases? e.g. if the alpha is fully opqaue?
125         
126 #A pygame wart is that there is no docs on colours!!! I never knew you could write (r,g,b,a) I thought it was always (r,g,b)
127
128
129 #XXX Look up what these are in the real implementation
130 IMG_PNG = 0
131 IMG_JPEG = 1
132
133 class Image:
134         #self.surface is the pygame surface we are wrapping
135         #we don't subclass pygame.Surface to avoid people accidentally calling on pygame methods without realizing it
136         def __init__(self, *args):
137                 """
138                 3 different constructors:
139                 __init__(self, filename)
140                 __init__(self, w, h)
141                 __init__(self, img)
142                 """
143                
144                 if len(args)==1:
145                         arg = args[0]
146                         if type(arg)==type(""): #filename
147                                 self.surface = pygame.image.load(arg)
148                         elif type(arg)==type(self): #copy an image
149                                 #self.surface = pygame.Surface((surf.get_width(), surf.get_height()), 0, surf)
150                                 self.surface = arg.surface.copy()
151                         #XXX todo: for compatibility, allow passing in pygame surfaces directly to this
152                 elif len(args)==2: #
153                         self.surface = pygame.Surface(args, pygame.SRCALPHA, 32) #args is a double (width, height), just like Surface() wants
154                 else: raise TypeError("__init__() takes either 1 or 2 arguments (%d given)" % len(args))
155        
156         def clear(self, color):
157                 self.surface.fill(color.tuple())
158        
159         def blit(self, source, sx=0, sy=0, w=-1, h=-1, dx=0, dy=0, blend=False, dw=-1, dh=-1):
160                 #appearantly this function will resize the surface while blitting
161                 #XXX this completely ignores alphas. Don't know what to do about that.
162                 if w == -1: w = source.width
163                 if h == -1: h = source.height
164                
165                 if dw == -1: dw = source.width
166                 if dh == -1: dh = source.height
167                
168                 r = source.surface.subsurface((sx,sy,w,h))
169                 r = pygame.transform.scale(r, (dw,dh))
170                
171                 self.surface.blit(r, (dx,dy))   
172        
173         def fillRect(self, x, y, w, h, color):
174                 self.surface.fill((x,y,w,h), color.tuple())
175        
176         def saveToFile(self, filename, type=IMG_PNG):
177                 #This isn't exactly right, fraca's implementation (is broken because it lets you pick the extension and the filetype separately) does PNG and JPEG, whereas pygame (just pulls format info direct from the filename) only does BMP and TGA
178                 pygame.image.save(self.surface, filename)
179
180         def putPixel(self, x, y, color):
181                 self.surface.set_at((x,y), color.tuple())
182        
183         def getPixel(self, x, y):
184                 color = self.surface.get_at((x,y))
185                 return Color(*color)   
186         width = property(lambda self: self.surface.get_width())
187         height = property(lambda self: self.surface.get_height())
188
189
190 class Screen(Image):
191         surface = pygame.display.set_mode((480,272)) #this makes the surface get cached when the module is imported. This isn't always what you want, of course, but it works for the mock up. PSP screen res is 480x272.
192         #XXX not quite like fraca. He randomly makes the two classes distinct instead of making one inherit from the other. Oh well.
193         
194         def __init__(self):
195                 """Override the constructor to kill it so
196                         i) you don't get to pick the info. HAH.
197                         ii) Screen.surface isn't overriden by self.surface.
198                 """
199                 pass
200         def swap(self):
201                 pygame.display.flip()
202
203
204 class Font(SFont.Font):
205         """Wrap an SFont to make it look like PSP Python fonts"""
206         def textWidth(self, text):
207                 return self.size(text)[0]
208         def textHeight(self, text):
209                 return self.size(text)[1]
210         def drawText(self, image, x, y, text):
211                 self.write(image.surface, (x,y), text)
212                 #image.surface.blit(self.render(text), (x,y)) #XXX should this be touching the internals of the image like this??)
213
214 class BlitBatch:
215         def __init__(self): raise NotImplementedError("This class allows the user to batch several blitting operations. It was written for optimization purposes but it doesn\u2019t seem to really make a difference.")
216
217
218 #MISCELLANEOUS
219 class Mask:
220         def __init__(self, img, x, y, w, h, threshold):
221                 raise NotImplementedError
222         def collide(self, msk):
223                 pass
224         def union(self, msk): pass
225         def isIn(self, x, y): pass
226
227 #XXX Check these
228 TR_PLUS = 0
229 TR_MULT = 1
230 class Transform:
231         def __init__(self, *args):
232                 """
233                 __init__(self, type, param)
234                 __init__(self, cb)
235                 """
236                 if len(args)==2:
237                         pass
238                 elif len(args)==1:
239                         pass
240                 else: raise TypeError("__init__() takes either 1 or 2 arguments (%d given)" % len(args))
241
242         def apply(self, img):
243                 pass
244
245 class Timer:
246         """Fraca says: 'A timer class that doesn\u2019t run in its own thread (unlike the standard threading.Timer class).'
247         Since I really really really don't know how to manage that I'm just going to use threading.Timer
248         This timer dies after one run, if you want it to go again you should, as the last thing in fire(), make a new one
249         and start it. I think. Hopefully that will still allow the old thread to die.
250         """
251         def __init__(self, timeout): #timeout is in milliseconds
252                 self.t=threading.Timer(timeout/1000, self.fire)
253        
254         def fire(self): raise NotImplementedError("Timer.fire() must be overridden to be any use, idiot")
255        
256         def run(self):
257                 self.t.start()