Ticket #4427: game.py

File game.py, 13.0 KB (added by Rach4d, 11 years ago)

Replace the game.py file in CookieSearch.activity with this game.py for new functionality.

Line 
1# -*- coding: utf-8 -*-
2#Copyright (c) 2011 Walter Bender
3
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 3 of the License, or
7# (at your option) any later version.
8#
9# You should have received a copy of the GNU General Public License
10# along with this library; if not, write to the Free Software
11# Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
12
13from gi.repository import Gtk, GObject, GdkPixbuf, Gdk
14import cairo
15import os
16from random import uniform
17import easygui
18
19
20from gettext import gettext as _
21
22import logging
23_logger = logging.getLogger('cookie-search-activity')
24
25try:
26    from sugar3.graphics import style
27    GRID_CELL_SIZE = style.GRID_CELL_SIZE
28except ImportError:
29    GRID_CELL_SIZE = 0
30
31from sprites import Sprites, Sprite
32
33# Grid dimensions must be even
34TEN = 10
35SEVEN = 7
36DOT_SIZE = 40
37PATHS = [False, 'turtle-monster.jpg', 'cookie.jpg', 'cookie.jpg',
38         'bitten-cookie.jpg']
39
40
41class Game():
42
43    def __init__(self, canvas, parent=None, path=None,
44                 colors=['#A0FFA0', '#FF8080']):
45        self._canvas = canvas
46        self._parent = parent
47        self._parent.show_all()
48        self._path = path
49
50        self._colors = ['#FFFFFF']
51        self._colors.append(colors[0])
52        self._colors.append(colors[1])
53        self._colors.append(colors[0])
54        self._colors.append('#FF0000')
55
56        self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK)
57        self._canvas.connect("draw", self.__draw_cb)
58        self._canvas.connect("button-press-event", self._button_press_cb)
59
60        self._width = Gdk.Screen.width()
61        self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5)
62        self._scale = self._width / (10 * DOT_SIZE * 1.2)
63        self._dot_size = int(DOT_SIZE * self._scale)
64        self._space = int(self._dot_size / 5.)
65        self.we_are_sharing = False
66
67        self._start_time = 0
68        self._timeout_id = None
69
70        # Generate the sprites we'll need...
71        self._sprites = Sprites(self._canvas)
72        self._dots = []
73        for y in range(SEVEN):
74            for x in range(TEN):
75                xoffset = int((self._width - TEN * self._dot_size - \
76                                   (TEN - 1) * self._space) / 2.)
77                self._dots.append(
78                    Sprite(self._sprites,
79                           xoffset + x * (self._dot_size + self._space),
80                           y * (self._dot_size + self._space),
81                           self._new_dot(self._colors[0])))
82                self._dots[-1].type = 0  # not set
83                self._dots[-1].set_label_attributes(40)
84
85        self._all_clear()
86    def __draw_cb(self, canvas, cr):
87                self._sprites.redraw_sprites(cr=cr)
88    def _all_clear(self):
89        ''' Things to reinitialize when starting up a new game. '''
90        for dot in self._dots:
91            if dot.type != 1:
92                dot.type = 1
93                dot.set_shape(self._new_dot(self._colors[dot.type]))
94            dot.set_label('')
95        self._stop_timer()
96
97    def new_game(self):
98        ''' Start a new game. '''
99        self._all_clear()
100
101        # Fill in a few dots to start
102        for i in range(int(TEN)):
103            n = int(uniform(0, TEN * SEVEN))
104            while True:
105                if self._dots[n].type == 1:
106                    self._dots[n].type = 2
107                    self._dots[n].set_shape(self._new_dot(self._colors[1]))
108                    break
109                else:
110                    n = int(uniform(0, TEN * SEVEN))
111
112        if self.we_are_sharing:
113            _logger.debug('sending a new game')
114            self._parent.send_new_game()
115
116        self._start_timer()
117
118    def restore_game(self, dot_list):
119        ''' Restore a game from the Journal or share '''
120        for i, dot in enumerate(dot_list):
121            self._dots[i].type = dot
122            self._dots[i].set_shape(self._new_dot(
123                    self._colors[self._dots[i].type]))
124
125    def save_game(self):
126        ''' Return dot list for saving to Journal or
127        sharing '''
128        dot_list = []
129        for dot in self._dots:
130            dot_list.append(dot.type)
131        return dot_list
132
133    def _set_label(self, string):
134        ''' Set the label in the toolbar or the window frame. '''
135        self._parent.status.set_label(string)
136
137    def _neighbors(self, spr):
138        ''' Return the list of surrounding dots '''
139        neighbors = []
140        x, y = self._dot_to_grid(self._dots.index(spr))
141        if x > 0 and y > 0:
142            neighbors.append(self._dots[self._grid_to_dot((x - 1, y - 1))])
143        if x > 0:
144            neighbors.append(self._dots[self._grid_to_dot((x - 1, y))])
145        if x > 0 and y < SEVEN - 1:
146            neighbors.append(self._dots[self._grid_to_dot((x - 1, y + 1))])
147        if y > 0:
148            neighbors.append(self._dots[self._grid_to_dot((x, y - 1))])
149        if y < SEVEN - 1:
150            neighbors.append(self._dots[self._grid_to_dot((x, y + 1))])
151        if x < TEN - 1 and y > 0:
152            neighbors.append(self._dots[self._grid_to_dot((x + 1, y - 1))])
153        if x < TEN - 1:
154            neighbors.append(self._dots[self._grid_to_dot((x + 1, y))])
155        if x < TEN - 1 and y < SEVEN - 1:
156            neighbors.append(self._dots[self._grid_to_dot((x + 1, y + 1))])
157        return neighbors
158
159    def _count(self, count_type, spr):
160        ''' Count the number of surrounding dots of type count_type '''
161        counter = 0
162        for dot in self._neighbors(spr):
163            if dot.type in count_type:
164                counter += 1
165        return counter
166
167    def _floodfill(self, old_type, spr):
168        if spr.type not in old_type:
169            return
170
171        spr.type = 0
172        spr.set_shape(self._new_dot(self._colors[spr.type]))
173        if self.we_are_sharing:
174            _logger.debug('sending a click to the share')
175            self._parent.send_dot_click(self._dots.index(spr), spr.type)
176
177        counter = self._count([2, 4], spr)
178        if counter > 0:
179            spr.set_label(str(counter))
180        else:
181            spr.set_label('')
182            for dot in self._neighbors(spr):
183                self._floodfill(old_type, dot)
184
185    def _button_press_cb(self, win, event):
186        win.grab_focus()
187        x, y = map(int, event.get_coords())
188
189        spr = self._sprites.find_sprite((x, y))
190        if spr == None:
191            return
192
193        if event.button > 1:  # right click
194            if spr.type != 0:
195                self._flip_the_cookie(spr)
196            return True
197        else:
198            if spr.type != 0:
199                red, green, blue, alpha = spr.get_pixel((x, y))
200                if red > 190 and red < 215:  # clicked the cookie
201                    self._flip_the_cookie(spr)
202                    return True
203
204        if spr.type in [2, 4]:
205            spr.set_shape(self._new_dot(self._colors[4]))
206            self._frown()
207            return True
208           
209        if spr.type is not None:
210            self._floodfill([1, 3], spr)
211            self._test_game_over()
212
213        return True
214
215    def _flip_the_cookie(self, spr):
216        if spr.type in [1, 2]:
217            spr.set_shape(self._new_dot(self._colors[2]))
218            spr.type += 2
219        else:  # elif spr.type in [3, 4]:
220            spr.set_shape(self._new_dot(self._colors[1]))
221            spr.type -= 2
222        self._test_game_over()
223
224    def remote_button_press(self, dot, color):
225        ''' Receive a button press from a sharer '''
226        self._dots[dot].type = color
227        self._dots[dot].set_shape(self._new_dot(self._colors[color]))
228
229    def set_sharing(self, share=True):
230        _logger.debug('enabling sharing')
231        self.we_are_sharing = share
232
233    def _counter(self):
234        ''' Display of seconds since start_time. '''
235        self._set_label(
236            str(int(GObject.get_current_time() - self._start_time)))
237        self._timeout_id = GObject.timeout_add(1000, self._counter)
238
239    def _start_timer(self):
240        ''' Start/reset the timer '''
241        self._start_time = GObject.get_current_time()
242        self._timeout_id = None
243        self._counter()
244
245    def _stop_timer(self):
246         if self._timeout_id is not None:
247            GObject.source_remove(self._timeout_id)
248
249    def _smile(self):
250        for dot in self._dots:
251            if dot.type == 0:
252                dot.set_label('☻')
253        self._end_prompt()
254
255    def _frown(self):
256        for dot in self._dots:
257            if dot.type == 0:
258                dot.set_label('☹')
259        self._end_prompt()
260
261    def _test_game_over(self):
262        ''' Check to see if game is over '''
263        for dot in self._dots:
264            if dot.type == 1 or dot.type == 2:
265                return False
266        self._parent.all_scores.append(
267            str(int(GObject.get_current_time() - self._start_time)))
268        _logger.debug(self._parent.all_scores)
269        self._smile()
270        self._stop_timer()
271        return True
272
273    def _end_prompt(self):
274        title="End of Game"
275        easygui.msgbox("The game is OVER!", title)
276        msg = "Would you like to restart?"
277        choices = ["New Game", "Exit"]
278        choice = easygui.choicebox(msg, title, choices)
279       
280        if choice == "New Game":
281                self.new_game()
282        if choice == "Exit":
283                Gtk.main_quit()
284
285    def _grid_to_dot(self, pos):
286        ''' calculate the dot index from a column and row in the grid '''
287        return pos[0] + pos[1] * TEN
288
289    def _dot_to_grid(self, dot):
290        ''' calculate the grid column and row for a dot '''
291        return [dot % TEN, int(dot / TEN)]
292
293    def _expose_cb(self, win, event):
294        self.do_expose_event(event)
295
296    def do_expose_event(self, event):
297        ''' Handle the expose-event by drawing '''
298        # Restrict Cairo to the exposed area
299        cr = self._canvas.window.cairo_create()
300        cr.rectangle(event.area.x, event.area.y,
301                event.area.width, event.area.height)
302        cr.clip()
303        # Refresh sprite list
304        self._sprites.redraw_sprites(cr=cr)
305
306    def _destroy_cb(self, win, event):
307        Gtk.main_quit()
308
309    def _new_dot(self, color):
310        ''' generate a dot of a color color '''
311        self._dot_cache = {}
312        if not color in self._dot_cache:
313            self._stroke = color
314            self._fill = color
315            self._svg_width = self._dot_size
316            self._svg_height = self._dot_size
317
318            i = self._colors.index(color)
319            if PATHS[i] is False:
320                pixbuf = svg_str_to_pixbuf(
321                    self._header() + \
322                    self._circle(self._dot_size / 2., self._dot_size / 2.,
323                                 self._dot_size / 2.) + \
324                    self._footer())
325            else:
326                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
327                    os.path.join(self._path, PATHS[i]),
328                    self._svg_width, self._svg_height)
329
330            surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
331                                         self._svg_width, self._svg_height)
332            context = cairo.Context(surface)
333            Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0)
334            context.rectangle(0, 0, self._svg_width, self._svg_height)
335            context.fill()
336            self._dot_cache[color] = surface
337
338        return self._dot_cache[color]
339
340    def _line(self, vertical=True):
341        ''' Generate a center line '''
342        if vertical:
343            self._svg_width = 3
344            self._svg_height = self._height
345            return svg_str_to_pixbuf(
346                self._header() + \
347                self._rect(3, self._height, 0, 0) + \
348                self._footer())
349        else:
350            self._svg_width = self._width
351            self._svg_height = 3
352            return svg_str_to_pixbuf(
353                self._header() + \
354                self._rect(self._width, 3, 0, 0) + \
355                self._footer())
356
357    def _header(self):
358        return '<svg\n' + 'xmlns:svg="http://www.w3.org/2000/svg"\n' + \
359            'xmlns="http://www.w3.org/2000/svg"\n' + \
360            'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \
361            'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \
362            'height="' + str(self._svg_height) + '">\n'
363
364    def _rect(self, w, h, x, y):
365        svg_string = '       <rect\n'
366        svg_string += '          width="%f"\n' % (w)
367        svg_string += '          height="%f"\n' % (h)
368        svg_string += '          rx="%f"\n' % (0)
369        svg_string += '          ry="%f"\n' % (0)
370        svg_string += '          x="%f"\n' % (x)
371        svg_string += '          y="%f"\n' % (y)
372        svg_string += 'style="fill:#000000;stroke:#000000;"/>\n'
373        return svg_string
374
375    def _circle(self, r, cx, cy):
376        return '<circle style="fill:' + str(self._fill) + ';stroke:' + \
377            str(self._stroke) + ';" r="' + str(r - 0.5) + '" cx="' + \
378            str(cx) + '" cy="' + str(cy) + '" />\n'
379
380    def _footer(self):
381        return '</svg>\n'
382
383
384def svg_str_to_pixbuf(svg_string):
385    """ Load pixbuf from SVG string """
386    pl = GdkPixbuf.PixbufLoader.new_with_type('svg')
387    pl.write(svg_string)
388    pl.close()
389    pixbuf = pl.get_pixbuf()
390    return pixbuf