erkennt jetzt ob das spiel fertig ist
[darcs-mirror-ozeanien.git] / cairoboard.py
1 #!/usr/bin/python
2 # encoding:utf8
3
4
5 import pygtk
6 pygtk.require('2.0')
7 import gtk
8 import gobject
9 import cairo
10 import math
11
12 import engine
13
14 # Constants
15
16 GRIDWIDTH = 70
17 SCALE = GRIDWIDTH/2
18 GRIDPAD = 30
19
20 PILE_X = 2 * GRIDPAD + 10 * GRIDWIDTH
21 PILE_Y = 3 * GRIDWIDTH
22
23 BH_X = 2 * GRIDPAD + 10*GRIDWIDTH
24 BH_Y = 5 * GRIDWIDTH
25
26 class CairoBoard(gtk.DrawingArea):
27
28     def __init__(self,fabric, board):
29         assert board
30         self.board = board
31         self.fabric = fabric
32
33         self.base_surface = None
34
35         self.quit_rect = None
36         self.reset_rect = None
37         
38         self.highlighted_slot = None
39
40         gtk.DrawingArea.__init__(self)
41         self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
42                 gtk.gdk.BUTTON_RELEASE_MASK |
43                 gtk.gdk.LEAVE_NOTIFY_MASK |
44                 gtk.gdk.EXPOSURE_MASK |
45                 gtk.gdk.POINTER_MOTION_MASK)
46         self.connect_after("expose-event",self.do_expose_event)
47         self.connect("motion-notify-event",self.hover)
48         self.connect("button-press-event",   self.press)
49
50         self.set_size_request(
51             2 * GRIDPAD + 9 * GRIDWIDTH + 2 * GRIDWIDTH,
52             2 * GRIDPAD + 6 * GRIDWIDTH
53         )
54         
55         self.reset()
56
57         # Creatings Drawing Things
58         for col in self.board.slots:
59             for slot in col:
60                 self.fabric.setup(slot)
61         self.fabric.setup(self.board.bh)
62         self.fabric.setup(self.board.pile)
63         self.fabric.setup(self.board.hand)
64
65     def reset(self):
66         self.queue_draw()
67
68     def press(self, widget, event):
69         # Doppelclick ignorieren
70         if event.type != gtk.gdk.BUTTON_PRESS:
71             return
72
73         if self.event2quit(event):
74             self.emit("quit-clicked")
75
76         if self.event2reset(event):
77             self.emit("reset-clicked")
78
79         if self.pileclick(event):
80             self.emit("card-flipped")
81
82         rot = self.pilerotclick(event)
83         if rot == 1:
84             self.board.pile.rotate_right()
85         elif rot == -1:
86             self.board.pile.rotate_left()
87
88         # Schaded nicht...
89         self.queue_draw()
90
91
92     def do_expose_event(self,widget,event):
93         gc = self.window.new_gc()
94         gc.set_clip_rectangle(event.area)
95         cr = self.window.cairo_create()
96         # set a clip region for the expose event
97         cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
98         cr.clip()
99
100         self.draw(self.window)
101
102     def hover(self, widget, event):
103         self.board.hand.guiinfo.move_to(event.x, event.y)
104         if not self.board.hand.is_empty():
105             self.queue_draw()
106
107         slot = self.event2slot(event)
108         if self.highlighted_slot and self.highlighted_slot != slot: 
109             self.highlighted_slot.highlighted = False
110             self.highlighted_slot = None
111             self.queue_draw()
112         if slot:
113             slot.highlighted = True
114             self.highlighted_slot = slot
115             self.queue_draw()
116
117     def pileclick(self, event):
118         return (2*abs(event.x - PILE_X) < GRIDWIDTH and
119                 2*abs(event.y - PILE_Y + 3*SCALE) < GRIDWIDTH)
120
121     def pilerotclick(self, event):
122         if   PILE_Y + GRIDWIDTH/2 <= event.y <= PILE_Y + GRIDWIDTH and\
123                 PILE_X + GRIDWIDTH/4 <= event.x <= PILE_X + GRIDWIDTH:
124             return 1
125         elif PILE_Y + GRIDWIDTH/2 <= event.y <= PILE_Y + GRIDWIDTH and\
126                 PILE_X - GRIDWIDTH/4 >= event.x >= PILE_X - GRIDWIDTH:
127             return -1
128         else:
129             return 0
130
131     def event2quit(self, event):
132         if self.quit_rect:
133             (x,y,w,h) = self.quit_rect
134             return (x <= event.x <= x + w) and (y <= event.y <= y + h)
135         else:
136             return False
137
138     def event2reset(self, event):
139         if self.reset_rect:
140             (x,y,w,h) = self.reset_rect
141             return (x <= event.x <= x + w) and (y <= event.y <= y + h)
142         else:
143             return False
144
145     def event2slot(self, event):
146         x = int((event.x - GRIDPAD) // GRIDWIDTH)
147         y = int((event.y - GRIDPAD) // GRIDWIDTH)
148         if 0 <= x < len(self.board.slots) and 0 <= y < len(self.board.slots[x]):
149             return self.board.slots[x][y]
150         elif (2*abs(event.x - PILE_X) < GRIDWIDTH and
151               2*abs(event.y - PILE_Y) < GRIDWIDTH):
152             return self.board.pile
153         elif (2*abs(event.x - BH_X) < GRIDWIDTH and
154               2*abs(event.y - BH_Y) < GRIDWIDTH):
155             return self.board.bh
156         else:
157             return None
158
159     def draw(self, window):
160         self.draw_cached_base(window)
161
162         self.draw_points(self.board.points, window)
163
164         for col in self.board.slots:
165             for slot in col:
166                 slot.guiinfo.draw(window)
167         self.board.bh.guiinfo.draw(window)
168         self.board.pile.guiinfo.draw(window)
169         self.board.hand.guiinfo.draw(window)
170     
171     def draw_points(self, points, window):
172         cr = window.cairo_create()
173         cr.translate(2 * GRIDPAD + 10*GRIDWIDTH, 6 * GRIDWIDTH)
174         cr.scale(SCALE, SCALE)
175
176         txt = "%ip" % points
177
178         cr.set_font_size(1)
179         cr.set_source_rgb(0,0,0)
180         (_,_,w,h,_,_) = cr.text_extents(txt)
181
182         cr.move_to(-w/2, h/2)
183         cr.show_text(txt)
184         cr.fill()
185
186     def draw_cached_base(self,window):
187         ocr = window.cairo_create()
188         if not self.base_surface:
189             self.base_surface = ocr.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA, 
190                     2*GRIDPAD + 11*GRIDWIDTH, 2*GRIDPAD + 7*GRIDWIDTH)
191
192             cr = cairo.Context(self.base_surface)
193             self.draw_base(cr)
194             cr = cairo.Context(self.base_surface)
195             self.draw_buttons(cr)
196
197         ocr.set_source_surface(self.base_surface,0,0)
198         ocr.rectangle(0,0,
199                 2*GRIDPAD + 11*GRIDWIDTH, 2*GRIDPAD + 7*GRIDWIDTH)
200         ocr.fill()
201
202     def draw_buttons(self,cr):
203         cr.set_font_size(25)
204         cr.set_source_rgb(0,0,0)
205
206         (_,_,w,h,ax,_) = cr.text_extents("QUIT")
207         cr.move_to(20, 1*GRIDPAD + 6*GRIDWIDTH + 10)
208         cr.show_text("QUIT")
209         self.quit_rect = (20, 1*GRIDPAD + 6*GRIDWIDTH + 10 - h, w, h)
210
211         (_,_,w,h,_,_) = cr.text_extents("RESET")
212         cr.move_to(20 + ax + 20, 1*GRIDPAD + 6*GRIDWIDTH + 10)
213         cr.show_text("RESET")
214         self.reset_rect = (20 + ax + 20, 1*GRIDPAD + 6*GRIDWIDTH + 10 - h, w, h)
215
216 # GObject signals
217 gobject.signal_new("quit-clicked", CairoBoard, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
218 gobject.signal_new("reset-clicked", CairoBoard, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
219 gobject.signal_new("card-flipped", CairoBoard, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
220
221
222 class CairoSlot:
223     def __init__(self, fabric, slot):
224         self.fabric = fabric
225         self.slot = slot
226
227     def translate(self, cr):
228         raise "Abstract Method of Object" + str(self)
229
230     def draw(self, window):
231         if self.slot.card or self.slot.ship:
232             cr = window.cairo_create()
233             self.translate(cr)
234             cr.scale(SCALE, SCALE)
235
236             matrix = cr.get_matrix()
237             if self.slot.card:
238                 self.fabric.setup(self.slot.card)
239                 self.slot.card.guiinfo.draw(cr)
240                 cr.set_matrix(matrix)
241             if self.slot.ship:
242                 self.fabric.setup(self.slot.ship)
243                 self.slot.ship.guiinfo.draw(cr)
244                 cr.set_matrix(matrix)
245             if self.slot.highlighted:
246                 cr.set_source_rgba(1, 1, 1, 0.5)
247                 cr.rectangle(-1, -1, 2, 2)
248                 cr.fill()
249
250 class CairoBoardSlot:
251     def translate(self, cr):
252         cr.translate(
253                  GRIDPAD + (self.slot.x+0.5)*GRIDWIDTH,
254                  GRIDPAD + (self.slot.y+0.5)*GRIDWIDTH
255         )
256
257 class CairoHandSlot:
258     def __init__(self):
259         self.x = 0
260         self.y = 0
261
262     def move_to(self, x,y):
263         self.x = x
264         self.y = y
265
266     def translate(self, cr):
267         cr.translate(self.x, self.y)
268
269 class CairoFabric:
270     def get_widget(self, board):
271         self.board_draw = self.get_drawer(board)
272         board.guiinfo = self.board_draw
273         return self.board_draw
274
275     def redraw(self):
276         self.board_draw.queue_draw()
277
278     def get_drawer(self, object):
279         for c, drawer in self.classes:
280             if isinstance(object, c):
281                 return drawer(self, object)
282         raise "No Drawing Class for "+str(object)
283
284     def setup(self, object):
285         if not object.guiinfo:
286             object.guiinfo = self.get_drawer(object)
287
288 class CairoShip:
289     def __init__(self, fabric, ship):
290         self.fabric = fabric
291         self.ship = ship
292         self.cache = None
293     
294     def draw_cache(self, cr):
295         raise "Abstrac Method: "+str(self)
296
297     def draw(self, cr):
298         cr.rotate(-(self.ship.rotation * math.pi / 2))
299         cr.rotate(math.pi / 2) # Koordinaten unten passen sonst nicht
300
301         if not self.cache:
302             self.cache = cr.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA, 
303                     GRIDWIDTH + GRIDPAD, GRIDWIDTH + GRIDPAD)
304             scr = cairo.Context(self.cache)
305             scr.translate((GRIDWIDTH + GRIDPAD)/2, (GRIDWIDTH+GRIDPAD)/2)
306             scr.scale(SCALE, SCALE)
307
308             self.draw_cache(scr)
309
310         cr.scale(1.0/SCALE, 1.0/SCALE)
311         cr.set_source_surface(self.cache,-(GRIDWIDTH + GRIDPAD)/2,-(GRIDWIDTH + GRIDPAD)/2)
312         cr.paint()
313
314 class CairoPile:
315     def __init__(self, fabric, slot):
316         self.pile = self.slot
317         self.flip_d = None # How far the card has been flipped
318         self.last_rot = 0
319
320     def card_flipped(self):
321         def update():
322             self.flip_d += 0.06
323             if self.flip_d > 1:
324                 self.flip_d = 1
325             self.fabric.redraw()
326             return self.flip_d < 1
327
328         self.flip_d = 0
329         self.last_rot = 0
330         gobject.timeout_add(40, update)
331
332     def start_rotation(self, dir):
333         def update():
334             if dir != self.rot_dir:
335                 return False
336             self.last_rot -= dir*5
337             if dir*self.last_rot < 0:
338                 self.last_rot = 0
339             self.fabric.redraw()
340             return self.last_rot
341
342         self.last_rot += dir * 100
343         self.rot_dir = dir
344
345         gobject.timeout_add(40, update)
346
347     def translate(self, cr):
348         cr.translate(
349                 PILE_X,
350                 PILE_Y,
351         )
352     
353     def draw_arrow(self, cr):
354         cr.arc(1, 1, 0.5, 0, math.pi/2)
355         cr.set_source_rgb(0, 0, 0)
356         cr.set_line_width(0.1)
357         cr.set_line_cap(cairo.LINE_CAP_ROUND)
358         cr.move_to(1,    1.5)
359         cr.line_to(0.75, 1.5)
360         cr.move_to(0.75, 1.5)
361         cr.line_to(1   , 1.75)
362         cr.move_to(0.75, 1.5)
363         cr.line_to(1   , 1.25)
364         cr.stroke()
365
366     def draw(self, window):
367         cr = window.cairo_create()
368         cr.translate(PILE_X, PILE_Y)
369         cr.scale(SCALE, SCALE)
370
371         cr.set_source_rgb(0.8, 0.8, 0.8)
372         cr.rectangle(-1, -1, 2, 2)
373         cr.fill()
374
375         matrix = cr.get_matrix()
376         self.draw_arrow(cr)
377         cr.scale(-1,1)
378         self.draw_arrow(cr)
379         cr.set_matrix(matrix)
380
381         cr.set_line_cap(cairo.LINE_CAP_SQUARE)
382         cr.translate(0,-3)
383         if self.pile.cards:
384             self.fabric.setup(self.pile.cards[0])
385             self.pile.cards[0].guiinfo.draw_back(cr)
386         else:
387             cr.set_source_rgb(0, 0, 0)
388             cr.set_line_width(0.05)
389             cr.rectangle(-1, -1, 2, 2)
390             cr.stroke()
391
392         cr.set_font_size(1)
393         cr.set_source_rgb(1,1,1)
394         (_,_,w,h,_,_) = cr.text_extents(str(len(self.pile.cards)))
395
396         cr.move_to(-w/2, h/2)
397         cr.show_text(str(len(self.pile.cards)))
398         cr.fill()
399         cr.set_matrix(matrix)
400
401         if self.flip_d == 1 and self.last_rot == 0:
402             self.super.draw(self, window)
403         elif self.slot.card:
404             cr = window.cairo_create()
405             cr.translate( PILE_X, PILE_Y )
406             cr.scale(SCALE, SCALE)
407
408             if self.flip_d != 0.5: # Da gibts nichts zu malen
409                 cr.translate(0, (1-self.flip_d) * (-3))
410                 cr.scale(1, (2 * self.flip_d - 1) )
411
412             if self.last_rot:
413                 cr.rotate(self.last_rot/100.0 * math.pi/2)
414
415             self.fabric.setup(self.slot.card)
416             if self.flip_d < 0.5: # back side
417                 self.slot.card.guiinfo.draw_back(cr)
418             else:
419                 self.slot.card.guiinfo.draw(cr)
420              
421 class CairoCard:
422     def __init__(self, fabric, card):
423         self.fabric = fabric
424         self.card = card
425         self.cache = None
426
427     def draw_back(self, cr):
428         matrix = cr.get_matrix()
429         cr.rotate(-(self.card.rotation * math.pi / 2))
430
431         cr.rectangle(-1,-1, 2,2)
432         cr.set_source_rgb(0.2, 0.2, 0.8)
433         cr.fill_preserve()
434         cr.set_source_rgb(0.2, 0.2, 0.2)
435         cr.set_line_width(0.05)
436         cr.stroke()
437         cr.set_matrix(matrix)
438
439     def draw(self, cr):
440         cr.rotate(-(self.card.rotation * math.pi / 2))
441
442         if not self.cache:
443             self.cache = cr.get_target().create_similar(cairo.CONTENT_COLOR_ALPHA, 
444                     GRIDWIDTH + GRIDPAD, GRIDWIDTH + GRIDPAD)
445             scr = cairo.Context(self.cache)
446             scr.translate((GRIDWIDTH + GRIDPAD)/2, (GRIDWIDTH+GRIDPAD)/2)
447             scr.scale(SCALE, SCALE)
448
449             self.draw_cache(scr)
450
451         cr.scale(1.0/SCALE, 1.0/SCALE)
452         cr.set_source_surface(self.cache,-(GRIDWIDTH + GRIDPAD)/2,-(GRIDWIDTH + GRIDPAD)/2)
453         cr.paint()
454
455 class CairoBlackHole:
456     def __init__(self, fabric, bh):
457         self.fabric = fabric
458         self.bh = bh
459         self.falling_card = None
460         self.falling_d = 0
461
462     def fall_card(self, card):
463         def update():
464             self.falling_d += 0.03
465             if self.falling_d > 1:
466                 self.falling_d = 1
467                 self.falling_card = None
468             self.fabric.redraw()
469             return self.falling_d < 1
470
471         self.falling_d = 0
472         self.falling_card = card
473         gobject.timeout_add(40, update)
474
475     def draw(self, window):
476         cr = window.cairo_create()
477         cr.translate(BH_X, BH_Y)
478         cr.scale(SCALE, SCALE)
479
480         cr.set_source_rgb(0,0,0)
481         cr.arc(0, 0, 0.8, 0, 2*math.pi)
482         cr.fill()
483         
484         cr.set_font_size(1)
485         cr.set_source_rgb(1,1,1)
486         (_,_,w,h,_,_) = cr.text_extents(str(self.bh.count))
487
488         cr.move_to(-w/2, h/2)
489         cr.show_text(str(self.bh.count))
490         cr.fill()
491
492         if self.falling_card:
493             matrix = cr.get_matrix()
494             cr.scale((1-self.falling_d * self.falling_d),(1-self.falling_d*self.falling_d))
495             cr.rotate(4*(self.falling_d * self.falling_d))
496             self.falling_card.guiinfo.draw(cr)
497             cr.set_matrix(matrix)
498
499
500
501 # vim:ts=4:sw=4:sts=4:et