class Spirolaterals: def __init__(self, canvas, colors, parent, score=0, delay=500, pattern=1, last=None): self._canvas = canvas self._colors = colors self._parent = parent self.delay = delay self.score = score self.pattern = pattern self.last_pattern = last self._running = False self._turtle_canvas = None self._user_numbers = [1, 1, 1, 3, 2] self._active_index = 0 self._sprites = Sprites(self._canvas) self._sprites.set_delay(True) size = max(Gdk.Screen.width(), Gdk.Screen.height()) cr = self._canvas.get_property('window').cairo_create() self._turtle_canvas = cr.get_target().create_similar( cairo.CONTENT_COLOR, size, size) self._canvas.connect('draw', self.__draw_cb) self._cr = cairo.Context(self._turtle_canvas) self._cr.set_line_cap(1) # Set the line cap to be round self._sprites.set_cairo_context(self._cr) self._canvas.set_can_focus(True) self._canvas.grab_focus() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect('button-press-event', self._button_press_cb) self._canvas.connect('key_press_event', self._keypress_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - style.GRID_CELL_SIZE if self._width < self._height: self.i = 1 else: self.i = 0 self._calculate_scale_and_offset() self._numbers = [] self._glownumbers = [] self._create_number_sprites() self._create_turtle_sprites() self._create_results_sprites() self._set_color(colors[0]) self._set_pen_size(4) self.reset_level() def _calculate_scale_and_offset(self): self.offset = 0 if self.i == 0: self.scale = self._height / (900. - style.GRID_CELL_SIZE) * 1.25 self.offset = ( self._width - (self.sx(X1[self.i] + X2[self.i]) + self.ss(BS[self.i]))) / 2. else: self.scale = self._width / 900. self.offset = (self._width - (self.sx(X1[self.i]) + self.ss(BS[self.i]))) / 2. def reset_level(self): self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - style.GRID_CELL_SIZE if self._width < self._height: self.i = 1 else: self.i = 0 self._calculate_scale_and_offset() self._show_background_graphics() self._show_user_numbers() self._get_goal() self._draw_goal() self._reset_sprites() if self.score > 0: self._parent.update_score(int(self.score)) def _reset_sprites(self): x = self.sx(TX[self.i] - TS[self.i] / 2) y = self.sy(TY[self.i]) self._target_turtle.move((x, y)) x = self.sx(UX[self.i] - US[self.i] / 2) y = self.sy(UY[self.i]) self._user_turtles[0].move((x, y)) for i in range(5): for j in range(5): if self.i == 0: x = self.sx( NX[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) y = self.sy(NY[self.i]) else: x = self.sx(NX[self.i]) y = self.sy( NY[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) self._numbers[i][j].move((x, y)) self._glownumbers[i][j].move((x, y)) x = 0 y = self.sy(GY[self.i]) self._success.move((x, y)) self._success.hide() self._failure.move((x, y)) self._failure.hide() self._splot.hide() if self.last_pattern == self.pattern: self._parent.cyan.set_sensitive(True) def _keypress_cb(self, area, event): ''' Keypress: moving the slides with the arrow keys ''' k = Gdk.keyval_name(event.keyval) if k in ['1', '2', '3', '4', '5']: self.do_stop() i = self._active_index j = int(k) - 1 self._numbers[i][self._user_numbers[i] - 1].set_layer(HIDDEN_LAYER) self._numbers[i][j].set_layer(NUMBER_LAYER) self._user_numbers[i] = j + 1 self.inval(self._numbers[i][j].rect) elif k in ['KP_Up', 'j', 'Up']: self.do_stop() i = self._active_index j = self._user_numbers[i] if j < 5: j += 1 self._numbers[i][self._user_numbers[i] - 1].set_layer(HIDDEN_LAYER) self._numbers[i][j - 1].set_layer(NUMBER_LAYER) self._user_numbers[i] = j self.inval(self._numbers[i][j].rect) elif k in ['KP_Down', 'k', 'Down']: self.do_stop() i = self._active_index j = self._user_numbers[i] if j > 0: j -= 1 self._numbers[i][self._user_numbers[i] - 1].set_layer(HIDDEN_LAYER) self._numbers[i][j - 1].set_layer(NUMBER_LAYER) self._user_numbers[i] = j self.inval(self._numbers[i][j].rect) elif k in ['KP_Left', 'h', 'Left']: self.do_stop() self._active_index -= 1 self._active_index %= 5 elif k in ['KP_Right', 'l', 'Right']: self.do_stop() self._active_index += 1 self._active_index %= 5 elif k in ['Return', 'KP_Page_Up', 'KP_End']: self.do_run() elif k in ['space', 'Esc', 'KP_Page_Down', 'KP_Home']: self.do_stop() else: logging.debug(k) self._canvas.grab_focus() def _button_press_cb(self, win, event): ''' Callback to handle the button presses ''' win.grab_focus() x, y = map(int, event.get_coords()) self.press = self._sprites.find_sprite((x, y)) if self.press is not None and self.press.type == 'number': self.do_stop() i = int(self.press.name.split(',')[0]) self._active_index = i j = int(self.press.name.split(',')[1]) j1 = (j + 1) % 5 self._numbers[i][j1].set_layer(NUMBER_LAYER) self._numbers[i][j].set_layer(HIDDEN_LAYER) self._user_numbers[i] = j1 + 1 self.inval(self._numbers[i][j].rect) def _create_results_sprites(self): x = 0 y = self.sy(GY[self.i]) self._success = Sprite(self._sprites, x, y, self._parent.good_job_pixbuf()) self._success.hide() self._failure = Sprite(self._sprites, x, y, self._parent.try_again_pixbuf()) self._failure.hide() def _create_turtle_sprites(self): x = self.sx(TX[self.i] - TS[self.i] / 2) y = self.sy(TY[self.i]) pixbuf = self._parent.turtle_pixbuf() self._target_turtle = Sprite(self._sprites, x, y, pixbuf) self._user_turtles = [] x = self.sx(UX[self.i] - US[self.i] / 2) y = self.sy(UY[self.i]) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) pixbuf = pixbuf.rotate_simple(270) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) pixbuf = pixbuf.rotate_simple(270) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) pixbuf = pixbuf.rotate_simple(270) self._user_turtles.append(Sprite(self._sprites, x, y, pixbuf)) self._show_turtle(0) self._splot = Sprite(self._sprites, 0, 0, self._parent.splot_pixbuf()) self._splot.hide() def _show_splot(self, x, y, dd, h): for i in range(4): self._user_turtles[i].hide() if h == 0: self._splot.move((x - int(dd / 2), y)) elif h == 1: self._splot.move((x - dd, y - int(dd / 2))) elif h == 2: self._splot.move((x - int(dd / 2), y - dd)) elif h == 3: self._splot.move((x, y - int(dd / 2))) self._splot.set_layer(SUCCESS_LAYER) self._failure.set_layer(SUCCESS_LAYER) def _show_turtle(self, t): for i in range(4): if i == t: self._user_turtles[i].set_layer(TURTLE_LAYER) else: self._user_turtles[i].hide() def _reset_user_turtle(self): x = self.sx(UX[self.i] - US[self.i] / 2) y = self.sy(UY[self.i]) self._user_turtles[0].move((x, y)) self._show_turtle(0) def _create_number_sprites(self): for i in range(5): self._numbers.append([]) self._glownumbers.append([]) for j in range(5): if self.i == 0: x = self.sx( NX[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) y = self.sy(NY[self.i]) else: x = self.sx(NX[self.i]) y = self.sy( NY[self.i]) + i * (self.ss(NS[self.i] + NO[self.i])) number = Sprite( self._sprites, x, y, self._parent.number_pixbuf(self.ss(NS[self.i]), j + 1, self._parent.sugarcolors[1])) number.type = 'number' number.name = '%d,%d' % (i, j) self._numbers[i].append(number) number = Sprite( self._sprites, x, y, self._parent.number_pixbuf(self.ss(NS[self.i]), j + 1, '#FFFFFF')) number.type = 'number' number.name = '%d,%d' % (i, j) self._glownumbers[i].append(number) def _show_user_numbers(self): # Hide the numbers for i in range(5): for j in range(5): self._numbers[i][j].set_layer(HIDDEN_LAYER) self._glownumbers[i][j].set_layer(HIDDEN_LAYER) # Show user numbers self._numbers[0][self._user_numbers[0] - 1].set_layer(NUMBER_LAYER) self._numbers[1][self._user_numbers[1] - 1].set_layer(NUMBER_LAYER) self._numbers[2][self._user_numbers[2] - 1].set_layer(NUMBER_LAYER) self._numbers[3][self._user_numbers[3] - 1].set_layer(NUMBER_LAYER) self._numbers[4][self._user_numbers[4] - 1].set_layer(NUMBER_LAYER) def _show_background_graphics(self): self._draw_pixbuf(self._parent.background_pixbuf(), 0, 0, self._width, self._height) self._draw_pixbuf(self._parent.box_pixbuf(self.ss(BS[self.i])), self.sx(X1[self.i]), self.sy(Y1[self.i]), self.ss(BS[self.i]), self.ss(BS[self.i])) self._draw_pixbuf(self._parent.box_pixbuf(self.ss(BS[self.i])), self.sx(X2[self.i]), self.sy(Y2[self.i]), self.ss(BS[self.i]), self.ss(BS[self.i])) self._draw_text(self.pattern, self.sx(X1[self.i]), self.sy(Y1[self.i]), self.ss(LS[self.i])) def _set_pen_size(self, ps): self._cr.set_line_width(ps) def _set_color(self, color): r = color[0] / 255. g = color[1] / 255. b = color[2] / 255. self._cr.set_source_rgb(r, g, b) def _draw_line(self, x1, y1, x2, y2): self._cr.move_to(x1, y1) self._cr.line_to(x2, y2) self._cr.stroke() def ss(self, f): # scale size function return int(f * self.scale) def sx(self, f): # scale x function return int(f * self.scale + self.offset) def sy(self, f): # scale y function return int(f * self.scale) def _draw_pixbuf(self, pixbuf, x, y, w, h): self._cr.save() self._cr.translate(x + w / 2., y + h / 2.) self._cr.translate(-x - w / 2., -y - h / 2.) Gdk.cairo_set_source_pixbuf(self._cr, pixbuf, x, y) self._cr.rectangle(x, y, w, h) self._cr.fill() self._cr.restore() def _draw_text(self, label, x, y, size): pl = PangoCairo.create_layout(self._cr) fd = Pango.FontDescription('Sans') fd.set_size(int(size) * Pango.SCALE) pl.set_font_description(fd) if type(label) == str or type(label) == unicode: pl.set_text(label.replace('\0', ' '), -1) elif type(label) == float or type(label) == int: pl.set_text(str(label), -1) else: pl.set_text(str(label), -1) self._cr.save() self._cr.translate(x, y) self._cr.set_source_rgb(1, 1, 1) PangoCairo.update_layout(self._cr, pl) PangoCairo.show_layout(self._cr, pl) self._cr.restore() def inval(self, r): self._canvas.queue_draw_area(r[0], r[1], r[2], r[3]) def inval_all(self): self._canvas.queue_draw_area(0, 0, self._width, self._height) def __draw_cb(self, canvas, cr): cr.set_source_surface(self._turtle_canvas) cr.paint() self._sprites.redraw_sprites(cr=cr) def do_stop(self): self._parent.green.set_sensitive(True) self._running = False def do_run(self): self._show_background_graphics() # TODO: Add turtle graphics self._success.hide() self._failure.hide() self._splot.hide() self._get_goal() self._draw_goal() self.inval_all() self._running = True self.loop = 0 self._active_index = 0 self.step = 0 self._set_pen_size(4) self._set_color(self._colors[0]) x1 = self.sx(UX[self.i]) y1 = self.sy(UY[self.i]) dd = self.ss(US[self.i]) self._numbers[0][self._user_numbers[0] - 1].set_layer(HIDDEN_LAYER) self._glownumbers[0][self._user_numbers[0] - 1].set_layer(NUMBER_LAYER) self._user_turtles[0].move((int(x1 - dd / 2), y1)) self._show_turtle(0) if self._running: GObject.timeout_add(self.delay, self._do_step, x1, y1, dd, 0) def _do_step(self, x1, y1, dd, h): if not self._running: return if self.loop > 3: return if h == 0: # up x2 = x1 y2 = y1 - dd self._user_turtles[h].move((int(x2 - dd / 2), int(y2 - dd))) elif h == 1: # right x2 = x1 + dd y2 = y1 self._user_turtles[h].move((int(x2), int(y2 - dd / 2))) elif h == 2: # down x2 = x1 y2 = y1 + dd self._user_turtles[h].move((int(x2 - dd / 2), int(y2))) elif h == 3: # left x2 = x1 - dd y2 = y1 self._user_turtles[h].move((int(x2 - dd), int(y2 - dd / 2))) self._show_turtle(h) if x2 < self.sx(X2[self.i]) or \ x2 > self.sx(X2[self.i] + BS[self.i]) or \ y2 < self.sy(Y2[self.i]) or \ y2 > self.sy(Y2[self.i] + BS[self.i]): self.do_stop() self._show_splot(x2, y2, dd, h) self._draw_line(x1, y1, x2, y2) self.inval_all() self.step += 1 i = self._active_index if self.step == self._user_numbers[i]: number = self._user_numbers[i] - 1 self._numbers[i][number].set_layer(NUMBER_LAYER) self._glownumbers[i][number].set_layer(HIDDEN_LAYER) h += 1 h %= 4 self.step = 0 self._active_index += 1 if self._active_index == 5: self.loop += 1 self._active_index = 0 else: i = self._active_index number = self._user_numbers[i] - 1 self._numbers[i][number].set_layer(HIDDEN_LAYER) self._glownumbers[i][number].set_layer(NUMBER_LAYER) if self.loop < 4 and self._running: GObject.timeout_add(self.delay, self._do_step, x2, y2, dd, h) elif self.loop == 4: # Test to see if we win self._running = False self._parent.green.set_sensitive(True) self._reset_user_turtle() self._show_user_numbers() self._test_level() def _test_level(self): success = True for i in range(5): if self._user_numbers[i] != self._goal[i]: success = False break if success: self._do_success() else: self._do_fail() def _do_success(self): self._success.set_layer(SUCCESS_LAYER) self._parent.cyan.set_sensitive(True) if self.last_pattern != self.pattern: self.score += 6 self.last_pattern = self.pattern self._parent.update_score(int(self.score)) def _do_fail(self): self._failure.set_layer(SUCCESS_LAYER) self._parent.cyan.set_sensitive(False) def do_slider(self, value): self.delay = int(value) def do_button(self, bu): self._success.hide() self._failure.hide() if bu == 'cyan': # Next level self.do_stop() self._splot.hide() self.pattern += 1 if self.pattern == 123: self.pattern = 1 self._get_goal() self._show_background_graphics() self._draw_goal() self._reset_user_turtle() self.inval_all() self._parent.cyan.set_sensitive(False) elif bu == 'green': # Run level self._parent.green.set_sensitive(False) self.do_run() elif bu == 'red': # Stop level self.do_stop() def _draw_goal(self): # draws the left hand pattern x1 = self.sx(TX[self.i]) y1 = self.sy(TY[self.i]) dd = self.ss(TS[self.i]) dx = 0 dy = -dd for i in range(4): for j in self._goal: for k in range(j): x2 = x1 + dx y2 = y1 + dy self._set_pen_size(4) self._set_color(self._colors[0]) self._draw_line(x1, y1, x2, y2) x1 = x2 y1 = y2 if dy == -dd: dx = dd dy = 0 elif dx == dd: dx = 0 dy = dd elif dy == dd: dx = -dd dy = 0 else: dx = 0 dy = -dd def _get_goal(self): fname = os.path.join('data', 'patterns.dat') try: f = open(fname, 'r') for n in range(0, self.pattern): s = f.readline() s = s[0:5] except: s = 11132 self.pattern = 1 f.close l = [int(c) for c in str(s)] self._goal = l
class Game(): ''' OLPC XO man color changer designed in memory of Nat Jacobson ''' def __init__(self, canvas, parent=None, mycolors=['#A0FFA0', '#FF8080']): self._activity = parent self.colors = [mycolors[0]] self.colors.append(mycolors[1]) self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.connect("draw", self.__draw_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.connect('button-release-event', self._button_release_cb) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE self._scale = self._width / 1200. self.press = None self.dragpos = [0, 0] self.startpos = [0, 0] self._dot_cache = {} self._xo_cache = {} self._radius = 22.5 self._stroke_width = 9.5 # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._sprites.set_delay(True) self._dots = [] self._xo_man = None self._generate_bg('#FFF') # First dot, starting angle self._cxy = [self._width / 2, self._height / 2] self._xy = [self._width / 2 + 120 * self._scale, self._height / 2 - self._radius * self._scale] self._angle = 0 self._dot_size_plus = self._radius * 3 * self._scale self._min = -self._dot_size_plus / 3 self._max = self._height - (self._dot_size_plus / 2.2) self._zones = [] self._calc_zones() self._generate_spiral() self._sprites.draw_all() def _calc_zones(self): for color in colors: rgb1 = _from_hex(color[0]) rgb2 = _from_hex(color[1]) dv = _contrast(rgb1, rgb2) dh = _delta_hue(rgb1, rgb2) self._zones.append(_zone(dv, dh)) def _calc_next_dot_position(self): ''' calculate spiral coordinates ''' dx = self._xy[0] - self._cxy[0] dy = self._xy[1] - self._cxy[1] r = sqrt(dx * dx + dy * dy) c = 2 * r * pi a = atan2(dy, dx) da = (self._dot_size_plus / c) * 2 * pi a += da r += self._dot_size_plus / (c / self._dot_size_plus) self._xy[0] = r * cos(a) + self._cxy[0] self._xy[1] = r * sin(a) + self._cxy[1] if self._xy[1] < self._min or self._xy[1] > self._max: self._calc_next_dot_position() def _generate_spiral(self): ''' Make a new set of dots for a sprial ''' for z in range(4): for i in range(len(colors)): if self._zones[i] == z: self._dots.append( Sprite(self._sprites, self._xy[0], self._xy[1], self._new_dot(colors[i]))) self._dots[-1].type = i self._calc_next_dot_position() if self._xo_man is None: x = 510 * self._scale y = 280 * self._scale self._xo_man = Sprite(self._sprites, x, y, self._new_xo_man(self.colors)) self._xo_man.type = None def move_dot(self, i, x, y): self._dots[i].move((x, y)) def get_dot_xy(self, i): return self._dots[i].get_xy() def move_xo_man(self, x, y): self._xo_man.move((x, y)) def get_xo_man_xy(self): return self._xo_man.get_xy() def rotate(self): x, y = self._dots[0].get_xy() for i in range(len(colors) - 1): self._dots[i].move(self._dots[i + 1].get_xy()) self._dots[-1].move((x, y)) def _generate_bg(self, color): ''' a background color ''' self._bg = Sprite(self._sprites, 0, 0, self._new_background(color)) self._bg.set_layer(0) self._bg.type = None def adj_background(self, color): ''' Change background ''' self._bg.set_image(self._new_background(color)) self._bg.set_layer(0) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self.dragpos = [x, y] spr = self._sprites.find_sprite((x, y)) if spr == None or spr == self._bg: return self.startpos = spr.get_xy() self.press = spr def _mouse_move_cb(self, win, event): """ Drag a rule with the mouse. """ if self.press is None: self.dragpos = [0, 0] return True win.grab_focus() x, y = map(int, event.get_coords()) dx = x - self.dragpos[0] dy = y - self.dragpos[1] self.press.move_relative((dx, dy)) self.dragpos = [x, y] def _button_release_cb(self, win, event): if self.press == None: return True if _distance(self.press.get_xy(), self.startpos) < 20: if type(self.press.type) == int: self.i = self.press.type self._new_surface() self.press.move(self.startpos) self.press = None def _new_surface(self): self.colors[0] = colors[self.i][0] self.colors[1] = colors[self.i][1] self._xo_man.set_image(self._new_xo_man(colors[self.i])) self._xo_man.set_layer(100) def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _new_dot(self, color): ''' generate a dot of a color color ''' if True: # not color in self._dot_cache: self._stroke = color[0] self._fill = color[1] self._svg_width = int(60 * self._scale) self._svg_height = int(60 * self._scale) pixbuf = svg_str_to_pixbuf( self._header() + \ '<circle cx="%f" cy="%f" r="%f" stroke="%s" fill="%s" \ stroke-width="%f" visibility="visible" />' % ( 30 * self._scale, 30 * self._scale, self._radius * self._scale, self._stroke, self._fill, self._stroke_width * self._scale) + \ self._footer()) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() # self._dot_cache[color] = surface return surface # self._dot_cache[color] def _new_background(self, color): ''' Background color ''' self._svg_width = int(self._width) self._svg_height = int(self._height) string = \ self._header() + \ '<rect width="%f" height="%f" x="%f" \ y="%f" fill="%s" stroke="none" visibility="visible" />' % ( self._width, self._height, 0, 0, color) + \ self._footer() pixbuf = svg_str_to_pixbuf(string) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() return surface def _new_xo_man(self, color): ''' generate a xo-man of a color color ''' if True: # not color in self._xo_cache: self._stroke = color[0] self._fill = color[1] self._svg_width = int(240. * self._scale) self._svg_height = int(260. * self._scale) string = \ self._header() + \ '<g>' + \ '<g id="XO">' + \ '<path id="Line1" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 97 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 188 * self._scale, self._stroke, 37 * self._scale) + \ '<path id="Line2" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 188 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 97 * self._scale, self._stroke, 37 * self._scale) + \ '<path id="Fill1" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 97 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 188 * self._scale, self._fill, 17 * self._scale) + \ '<path id="Fill2" d="M%f,%f C%f,%f %f,%f %f,%f" stroke="%s" \ stroke-width="%f" stroke-linecap="round" fill="none" visibility="visible" />' \ % ( 165.5 * self._scale, 188 * self._scale, 120 * self._scale, 140.5 * self._scale, 120 * self._scale, 140.5 * self._scale, 74.5 * self._scale, 97 * self._scale, self._fill, 17 * self._scale) + \ '<circle id="Circle" cx="%f" cy="%f" r="%f" \ fill="%s" stroke="%s" stroke-width="%f" visibility="visible" />' % ( 120 * self._scale, 61.5 * self._scale, 27.5 * self._scale, self._fill, self._stroke, 11 * self._scale) + \ '</g></g>' + \ self._footer() pixbuf = svg_str_to_pixbuf(string) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self._svg_width, self._svg_height) context = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(context, pixbuf, 0, 0) context.rectangle(0, 0, self._svg_width, self._svg_height) context.fill() # self._xo_cache[color] = surface return surface # self._xo_cache[color] def _header(self): return '<svg\n' + 'xmlns:svg="http:#www.w3.org/2000/svg"\n' + \ 'xmlns="http://www.w3.org/2000/svg"\n' + \ 'xmlns:xlink="http://www.w3.org/1999/xlink"\n' + \ 'version="1.1"\n' + 'width="' + str(self._svg_width) + '"\n' + \ 'height="' + str(self._svg_height) + '">\n' def _footer(self): return '</svg>\n'
class Game(): ''' The game play -- called from within Sugar or GNOME ''' def __init__(self, canvas, parent=None): ''' Initialize the playing surface ''' self.activity = parent if parent is None: # Starting from command line self._sugar = False self._canvas = canvas else: # Starting from Sugar self._sugar = True self._canvas = canvas parent.show_all() self._canvas.set_can_focus(True) self._canvas.add_events(Gdk.EventMask.TOUCH_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_MOTION_MASK) self._canvas.connect('event', self.__event_cb) self._canvas.connect('draw', self.__draw_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE * 2 if self._width < self._height: self.portrait = True self._scale = 0.8 * self._width / (CARD_HEIGHT * 5.5) else: self.portrait = False self._scale = 0.8 * self._height / (CARD_HEIGHT * 5.5) self._card_width = CARD_WIDTH * self._scale self._card_height = CARD_HEIGHT * self._scale self.custom_paths = [None, None, None, None, None, None, None, None, None] self._sprites = Sprites(self._canvas) self._sprites.set_delay(True) self._press = None self.matches = 0 self.robot_matches = 0 self._match_area = [] self._matches_on_display = False self._smiley = [] self._frowny = [] self._help = [] self._stop_help = True self._failure = None self.clicked = [] self.last_click = None self._drag_pos = [0, 0] self._start_pos = [0, 0] self.low_score = [-1, -1, -1] self.all_scores = [] self.robot = False self.robot_time = 0 self.total_time = 0 self.numberC = 0 self.numberO = 0 self.word_lists = None self.editing_word_list = False self.editing_custom_cards = False self._edit_card = None self._dead_key = None self._found_a_match = False self.level = 0 self.card_type = 'pattern' self.buddies = [] self._dealing = False self._the_game_is_over = False self.grid = Grid(self._width, self._height, self._card_width, self._card_height) self._cards = [] for i in range(DECKSIZE): self._cards.append(Card(scale=self._scale)) self.deck = Deck(self._cards, scale=self._scale) for i in range(CARDS_IN_A_MATCH): self.clicked.append(Click()) self._match_area.append(Card(scale=self._scale)) self._match_area[-1].create( generate_match_card(self._scale), sprites=self._sprites) self._match_area[-1].spr.move(self.grid.match_to_xy(i)) for i in range((ROW - 1) * COL): self._smiley.append(Card(scale=self._scale)) self._smiley[-1].create( generate_smiley(self._scale), sprites=self._sprites) self._smiley[-1].spr.move(self.grid.grid_to_xy(i)) self._smiley.append(Card(scale=self._scale)) self._smiley[-1].create( generate_smiley(self._scale), sprites=self._sprites) self._smiley[-1].spr.move(self.grid.match_to_xy(3)) self._smiley[-1].spr.hide() # A different frowny face for each type of error self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_shape(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_color(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_texture(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._frowny.append(Card(self._scale)) self._frowny[-1].create( generate_frowny_number(self._scale), sprites=self._sprites) self._frowny[-1].spr.move(self.grid.match_to_xy(3)) self._label = Card() self._label.create(generate_label(min(self._width, self._height), LABELH), sprites=self._sprites) self._label.spr.move((0, 0)) self._label.spr.set_label_attributes(24, horiz_align="left") self._labels = {'deck': '', 'match': '', 'clock': '', 'status': ''} Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _configure_cb(self, event): self.grid.stop_animation = True self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - GRID_CELL_SIZE * 2 if self._width < self._height: self.portrait = True else: self.portrait = False self.grid.rotate(self._width, self._height) for i in range(CARDS_IN_A_MATCH): self._match_area[i].spr.move(self.grid.match_to_xy(i)) self._smiley[-1].spr.move(self.grid.match_to_xy(3)) for c in self._frowny: c.spr.move(self.grid.match_to_xy(3)) for i, c in enumerate(self.clicked): if c.spr is not None: c.spr.move(self.grid.match_to_xy(i)) def new_game(self, saved_state=None, deck_index=0): ''' Start a new game ''' # If we were editing the word list, time to stop self.grid.stop_animation = True self.editing_word_list = False self.editing_custom_cards = False self._edit_card = None self._saved_state = saved_state self._deck_index = deck_index # Wait for any animations to stop before starting new game timeout = GObject.timeout_add(200, self._prepare_new_game) def _prepare_new_game(self): # If there is already a deck, hide it. if hasattr(self, 'deck'): self.deck.hide() self._dealing = False self._hide_clicked() self._matches_on_display = False self._failure = None self._hide_frowny() self._smiley[-1].spr.hide() if self._saved_state is not None: _logger.debug('Restoring state: %s' % (str(self._saved_state))) if self.card_type == 'custom': self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL[self.level]) else: self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.word_lists, DIFFICULTY_LEVEL[self.level]) self.deck.hide() self.deck.index = self._deck_index deck_start = ROW * COL + 3 deck_stop = deck_start + self.deck.count() self._restore_word_list(self._saved_state[deck_stop + 3 * self.matches:]) if self._saved_state[deck_start] is not None: self.deck.restore(self._saved_state[deck_start: deck_stop]) self.grid.restore(self.deck, self._saved_state[0: ROW * COL]) self._restore_matches( self._saved_state[deck_stop: deck_stop + 3 * self.matches]) self._restore_clicked( self._saved_state[ROW * COL: ROW * COL + 3]) else: self.deck.hide() self.deck.shuffle() self.grid.deal(self.deck) if not self._find_a_match(): self.grid.deal_extra_cards(self.deck) self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 elif not self.joiner(): _logger.debug('Starting new game.') if self.card_type == 'custom': self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL[self.level]) else: self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.word_lists, DIFFICULTY_LEVEL[self.level]) self.deck.hide() self.deck.shuffle() self.grid.deal(self.deck) if not self._find_a_match(): self.grid.deal_extra_cards(self.deck) self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 # When sharer starts a new game, joiners should be notified. if self.sharer(): self.activity._send_event('J') self._update_labels() self._the_game_is_over = False if self._game_over(): if hasattr(self, 'timeout_id') and self.timeout_id is not None: GObject.source_remove(self.timeout_id) else: if hasattr(self, 'match_timeout_id') and \ self.match_timeout_id is not None: GObject.source_remove(self.match_timeout_id) self._timer_reset() for i in range((ROW - 1) * COL): self._smiley[i].hide_card() self._sprites.draw_all() def _sharing(self): ''' Are we sharing? ''' if self._sugar and hasattr(self.activity, 'chattube') and \ self.activity.chattube is not None: return True return False def joiner(self): ''' Are you the one joining? ''' if self._sharing() and not self.activity.initiating: return True return False def sharer(self): ''' Are you the one sharing? ''' if self._sharing() and self.activity.initiating: return True return False def edit_custom_card(self): ''' Update the custom cards from the Journal ''' if not self.editing_custom_cards: return # Set the card type to custom, and generate a new deck. self._hide_clicked() self.deck.hide() self.card_type = 'custom' if len(self.custom_paths) < 3: for i in range(len(self.custom_paths), 81): self.custom_paths.append(None) self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL.index(HIGH)) self.deck.hide() self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 self._edit_card = None self._dead_key = None if hasattr(self, 'timeout_id') and self.timeout_id is not None: GObject.source_remove(self.timeout_id) # Fill the grid with custom cards. self.grid.restore(self.deck, CUSTOM_CARD_INDICIES) self.set_label('deck', '') self.set_label('match', '') self.set_label('clock', '') self.set_label('status', _('Edit the custom cards.')) def edit_word_list(self): ''' Update the word cards ''' if not self.editing_word_list: if hasattr(self, 'text_entry'): self.text_entry.hide() self.text_entry.disconnect(self.text_event_id) return # Set the card type to words, and generate a new deck. self._hide_clicked() self.deck.hide() self.card_type = 'word' self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.word_lists, DIFFICULTY_LEVEL.index(HIGH)) self.deck.hide() self.matches = 0 self.robot_matches = 0 self.match_list = [] self.total_time = 0 self._edit_card = None self._dead_key = None if hasattr(self, 'timeout_id') and self.timeout_id is not None: GObject.source_remove(self.timeout_id) # Fill the grid with word cards. self.grid.restore(self.deck, WORD_CARD_INDICIES) self.set_label('deck', '') self.set_label('match', '') self.set_label('clock', '') self.set_label('status', _('Edit the word cards.')) if not hasattr(self, 'text_entry'): self.text_entry = Gtk.TextView() self.text_entry.set_wrap_mode(Gtk.WrapMode.WORD) self.text_entry.set_pixels_above_lines(0) self.text_entry.set_size_request(self._card_width, self._card_height) ''' rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = rgb(self._colors[1]) rgba.alpha = 1. self.text_entry.override_background_color( Gtk.StateFlags.NORMAL, rgba) ''' font_text = Pango.font_description_from_string('24') self.text_entry.modify_font(font_text) self.activity.fixed.put(self.text_entry, 0, 0) def _text_focus_out_cb(self, widget=None, event=None): if self._edit_card is None: self.text_entry.hide() self.text_entry.disconnect(self.text_event_id) self._update_word_card() self.text_entry.hide() def _update_word_card(self): bounds = self.text_buffer.get_bounds() text = self.text_buffer.get_text(bounds[0], bounds[1], True) self._edit_card.spr.set_label(text) (i, j) = WORD_CARD_MAP[self._edit_card.index] self.word_lists[i][j] = text self._edit_card = None def __event_cb(self, widget, event): ''' Handle touch events ''' if event.type in (Gdk.EventType.TOUCH_BEGIN, Gdk.EventType.TOUCH_END, Gdk.EventType.TOUCH_UPDATE, Gdk.EventType.BUTTON_PRESS, Gdk.EventType.BUTTON_RELEASE, Gdk.EventType.MOTION_NOTIFY): x = event.get_coords()[1] y = event.get_coords()[2] if event.type == Gdk.EventType.TOUCH_BEGIN or \ event.type == Gdk.EventType.BUTTON_PRESS: self._button_press(x, y) elif event.type == Gdk.EventType.TOUCH_UPDATE or \ event.type == Gdk.EventType.MOTION_NOTIFY: self._drag_event(x, y) elif event.type == Gdk.EventType.TOUCH_END or \ event.type == Gdk.EventType.BUTTON_RELEASE: self._button_release(x, y) def _button_press_cb(self, win, event): ''' Look for a card under the button press and save its position. ''' win.grab_focus() x, y = map(int, event.get_coords()) self._button_press(x, y) def _button_press(self, x, y): # Turn off help animation if not self._stop_help: self._stop_help = True return True # Don't do anything if the game is over if self._the_game_is_over: return True # Don't do anything during a deal if self._dealing: return True # Find the sprite under the mouse. spr = self._sprites.find_sprite((x, y)) # If there is a match showing, hide it. if self._matches_on_display: self.clean_up_match(share=True) # Nothing else to do. if spr is None: return True # Don't grab cards in the match pile. if spr in self.match_list: return True # Don't grab a card being animated. if True in self.grid.animation_lock: return True # Don't do anything if a card is already in motion if self._in_motion(spr): return True # Keep track of starting drag position. self._drag_pos = [x, y] self._start_pos = [x, y] # If the match area is full, we need to move a card back to the grid if self._failure is not None: if not self.grid.xy_in_match(spr.get_xy()): return True # We are only interested in cards in the deck. if self.deck.spr_to_card(spr) is not None: self._press = spr # Save its starting position so we can restore it if necessary if self._where_in_clicked(spr) is None: i = self._none_in_clicked() if i is None: self._press = None else: self.clicked[i].spr = spr self.clicked[i].pos = spr.get_xy() self.last_click = i else: self._press = None return True def clean_up_match(self, share=False): ''' Unselect clicked cards that are now in the match pile ''' self._matches_on_display = False self._hide_clicked() self._smiley[-1].spr.hide() if share and self._sharing(): self.activity._send_event('r:') def clean_up_no_match(self, spr, share=False): ''' Return last card played to grid ''' if self.clicked[2].spr is not None and self.clicked[2].spr != spr: self.return_card_to_grid(2) self.last_click = 2 if share and self._sharing(): self.activity._send_event('R:2') self._hide_frowny() self._failure = None def _mouse_move_cb(self, win, event): ''' Drag the card with the mouse. ''' win.grab_focus() x, y = map(int, event.get_coords()) self._drag_event(x, y) def _drag_event(self, x, y): if self._press is None or self.editing_word_list or \ self.editing_custom_cards: self._drag_pos = [0, 0] return True dx = x - self._drag_pos[0] dy = y - self._drag_pos[1] self._press.set_layer(5000) self._press.move_relative((dx, dy)) self._drag_pos = [x, y] def _button_release_cb(self, win, event): ''' Lots of possibilities here between clicks and drags ''' win.grab_focus() x, y = map(int, event.get_coords()) self._button_release(x, y) def _button_release(self, x, y): # Maybe there is nothing to do. if self._press is None: if self.editing_word_list: self._text_focus_out_cb() self._drag_pos = [0, 0] return True self._press.set_layer(2000) # Determine if it was a click, a drag, or an aborted drag d = _distance((x, y), (self._start_pos[0], self._start_pos[1])) if d < self._card_width / 10: # click move = 'click' elif d < self._card_width / 2: # aborted drag move = 'abort' else: move = 'drag' # Determine status of card status = self.grid.spr_to_grid(self._press) if move == 'click': if self.editing_word_list: # Only edit one card at a time, so unselect other cards for i, c in enumerate(self.clicked): if c.spr is not None and c.spr != self._press: c.spr.set_label( c.spr.labels[0].replace(CURSOR, '')) c.spr = None # Unselect elif self.editing_custom_cards: pass else: self.process_click(self._press) elif move == 'abort': i = self._where_in_clicked(self._press) self._press.move(self.clicked[i].pos) else: # move == 'drag' move = self._process_drag(self._press, x, y) if move == 'abort': self._press = None return if self._sharing(): if self.deck.spr_to_card(self._press) is not None: # Tell everyone about the card we just clicked self.activity._send_event( 'B:%d' % (self.deck.spr_to_card(self._press).index)) i = self._where_in_clicked(self._press) if i is not None: self.activity._send_event('S:%d' % (i)) elif self.last_click is not None: self.activity._send_event('S:%d' % (self.last_click)) else: _logger.error('WARNING: Cannot find last click') self.last_click = None self.process_selection(self._press) self._press = None return def process_click(self, spr): ''' Either move the card to the match area or back to the grid.''' if self.grid.spr_to_grid(spr) is None: # Return card to grid i = self._where_in_clicked(spr) if i is not None: self.return_card_to_grid(i) self.last_click = i self._hide_frowny() self._failure = None else: i = self._where_in_clicked(spr) if i is None: spr.move((self._start_pos)) else: spr.set_layer(5000) self.grid.grid[self.grid.spr_to_grid(spr)] = None self.grid.display_match(spr, i) def _process_drag(self, spr, x, y): ''' Either drag to the match area, back to the grid, or to a new slot. ''' move = 'drag' if self.grid.spr_to_grid(spr) is None: # Returning a card to the grid if (self.portrait and y < self.grid.bottom) or \ (not self.portrait and x > self.grid.left): i = self.grid.xy_to_grid((x, y)) if self.grid.grid[i] is not None: i = self.grid.find_an_empty_slot() spr.move(self.grid.grid_to_xy(i)) self.grid.grid[i] = self.deck.spr_to_card(spr) i = self._where_in_clicked(spr) self.last_click = i self.clicked[i].reset() self._hide_frowny() self._failure = None # Move a click to a different match slot else: i = self._where_in_clicked(spr) j = self.grid.xy_to_match((x, y)) if i == j: spr.move(self.clicked[i].pos) else: temp_spr = self.clicked[i].spr self.clicked[i].spr = self.clicked[j].spr self.clicked[j].spr = temp_spr if self.clicked[i].spr is not None: self.clicked[i].spr.move(self.grid.match_to_xy(i)) if self.clicked[j].spr is not None: self.clicked[j].spr.move(self.grid.match_to_xy(j)) move = 'abort' else: i = self._where_in_clicked(spr) if i is None: move = 'abort' # Moving a card to the match area elif (self.portrait and y > self.grid.bottom) or \ (not self.portrait and x < self.grid.left): self.grid.grid[self.grid.spr_to_grid(spr)] = None spr.move(self._match_area[i].spr.get_xy()) else: # Shuffle positions in match area j = self.grid.xy_to_grid((x, y)) k = self.grid.xy_to_grid(self.clicked[i].pos) if j < 0 or k < 0 or j > 15 or k > 15 or j == k: spr.move(self.clicked[i].pos) else: tmp_card = self.grid.grid[k] if self.grid.grid[j] is not None: self.grid.grid[j].spr.move(self.grid.grid_to_xy(k)) spr.move(self.grid.grid_to_xy(j)) self.grid.grid[k] = self.grid.grid[j] self.grid.grid[j] = tmp_card else: spr.move(self.grid.grid_to_xy(j)) self.grid.grid[j] = self.grid.grid[k] self.grid.grid[k] = None move = 'abort' self.clicked[i].reset() self._consistency_check() return move def _consistency_check(self): ''' Make sure that the cards in the grid are really in the grid ''' # Root cause: a race condition? for i in range(3): spr = self.clicked[i].spr if spr is not None: if not self.grid.xy_in_match(spr.get_xy()): _logger.debug('card in both the grid and ' 'match area (%d)' % (i)) spr.move(self.grid.match_to_xy(i)) def process_selection(self, spr): ''' After a card has been selected... ''' if self.editing_word_list: # Edit label of selected card x, y = spr.get_xy() if self._edit_card is not None: self._update_word_card() self._edit_card = self.deck.spr_to_card(spr) self.text_buffer = self.text_entry.get_buffer() self.text_entry.show() self.text_buffer.set_text(self._edit_card.spr.labels[0]) self.activity.fixed.move(self.text_entry, x, y) self.text_event_id = self.text_entry.connect( 'focus-out-event', self._text_focus_out_cb) self.text_entry.grab_focus() elif self.editing_custom_cards: # Only edit one card at a time, so unselect other cards for i, c in enumerate(self.clicked): if c.spr is not None and c.spr != spr: c.spr = None # Choose an image from the Journal for a card self._edit_card = self.deck.spr_to_card(spr) self._choose_custom_card() # Regenerate the deck with the new card definitions self.deck.create(self._sprites, self.card_type, [self.numberO, self.numberC], self.custom_paths, DIFFICULTY_LEVEL[1]) self.deck.hide() self.grid.restore(self.deck, CUSTOM_CARD_INDICIES) elif self._none_in_clicked() == None: # If we have three cards selected, test for a match. self._test_for_a_match() if self._matches_on_display: self._smiley[-1].spr.set_layer(100) elif not self._the_game_is_over and self._failure is not None: self._frowny[self._failure].spr.set_layer(100) return def _none_in_clicked(self): ''' Look for room on the click list ''' for i, c in enumerate(self.clicked): if c.spr is None: return i return None def _where_in_clicked(self, spr): ''' Is the card already selected? ''' for i, c in enumerate(self.clicked): if c.spr == spr: return i return None def add_to_clicked(self, spr, pos=[0, 0]): ''' Add a card to the selected list ''' i = self._where_in_clicked(spr) if i is not None: self.last_click = i else: i = self._none_in_clicked() if i is None: _logger.error('WARNING: No room in clicked') self.last_click = None return self.clicked[i].spr = spr self.clicked[i].pos = pos self.last_click = i def _hide_clicked(self): ''' Hide the clicked cards ''' for c in self.clicked: if c is not None: c.hide() def _hide_frowny(self): ''' Hide the frowny cards ''' for c in self._frowny: c.spr.hide() def return_card_to_grid(self, i): ''' "Unclick" ''' j = self.grid.find_an_empty_slot() if j is not None: self.grid.return_to_grid(self.clicked[i].spr, j, i) self.grid.grid[j] = self.deck.spr_to_card(self.clicked[i].spr) self.clicked[i].reset() def _game_over(self): ''' Game is over when the deck is empty and no more matches. ''' if self.deck.empty() and not self._find_a_match(): self._hide_frowny() self.set_label('deck', '') self.set_label('clock', '') self.set_label('status', '%s (%d:%02d)' % (_('Game over'), int(self.total_time / 60), int(self.total_time % 60))) for i in range((ROW - 1) * COL): if self.grid.grid[i] == None: self._smiley[i].show_card() self.match_timeout_id = GObject.timeout_add( 2000, self._show_matches, 0) self._the_game_is_over = True elif self.grid.cards_in_grid() == DEAL + 3 \ and not self._find_a_match(): self._hide_frowny() self.set_label('deck', '') self.set_label('clock', '') self.set_label('status', _('unsolvable')) self._the_game_is_over = True return self._the_game_is_over def _test_for_a_match(self): ''' If we have a match, then we have work to do. ''' if self._match_check([self.deck.spr_to_card(self.clicked[0].spr), self.deck.spr_to_card(self.clicked[1].spr), self.deck.spr_to_card(self.clicked[2].spr)], self.card_type): # Stop the timer. if hasattr(self, 'timeout_id'): if self.timeout_id is not None: GObject.source_remove(self.timeout_id) self.total_time += GObject.get_current_time() - self.start_time # Increment the match counter and add the match to the match list. self.matches += 1 for c in self.clicked: self.match_list.append(c.spr) self._matches_on_display = True # Test to see if the game is over. if self._game_over(): if hasattr(self, 'timeout_id'): GObject.source_remove(self.timeout_id) if self.low_score[self.level] == -1: self.low_score[self.level] = self.total_time elif self.total_time < self.low_score[self.level]: self.low_score[self.level] = self.total_time self.set_label('status', '%s (%d:%02d)' % (_('New record'), int(self.total_time / 60), int(self.total_time % 60))) # Round to nearest second self.all_scores.append(int(self.total_time + 0.5)) if not self._sugar: self.activity.save_score() else: self._auto_increase_difficulty() return True else: # Wait a few seconds before dealing new cards. self._dealing = True GObject.timeout_add(2000, self._deal_new_cards) # Keep playing. self._update_labels() self._timer_reset() else: self._matches_on_display = False def _auto_increase_difficulty(self): ''' Auto advance levels ''' if self.level == 2 and len(self.all_scores) > 3: sum = 0 for i in range(3): sum += self.all_scores[-i - 1] if sum < 120: self.level = 0 self.activity.intermediate_button.set_active(True) elif self.level == 0 and len(self.all_scores) > 8: sum = 0 for i in range(3): sum += self.all_scores[-i - 1] if sum < 240: self.level = 1 self.activity.expert_button.set_active(True) def _deal_new_cards(self): ''' Deal three new cards. ''' self.grid.replace(self.deck) self.set_label('deck', '%d %s' % (self.deck.cards_remaining(), _('cards'))) # Consolidate the grid. self.grid.consolidate() # Test to see if we need to deal extra cards. if not self._find_a_match(): self.grid.deal_extra_cards(self.deck) self._failure = None self._dealing = False def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def _expose_cb(self, win, event): ''' Callback to handle window expose events ''' self.do_expose_event(event) return True # Handle the expose-event by drawing def do_expose_event(self, event): # Create the cairo context cr = self._canvas.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list if cr is not None: self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): ''' This is the end ''' Gtk.main_quit() def _update_labels(self): ''' Write strings to a label in the toolbar. ''' self.set_label('deck', '%d %s' % (self.deck.cards_remaining(), _('cards'))) self.set_label('status', '') if self.matches == 1: if self.robot_matches > 0: self.set_label('match', '%d (%d) %s' % ( self.matches - self.robot_matches, self.robot_matches, _('match'))) else: self.set_label('match', '%d %s' % (self.matches, _('match'))) else: if self.robot_matches > 0: self.set_label('match', '%d (%d) %s' % ( self.matches - self.robot_matches, self.robot_matches, _('matches'))) else: self.set_label('match', '%d %s' % (self.matches, _('matches'))) def set_label(self, label, s): ''' Update the toolbar labels ''' if label in self._labels: self._labels[label] = s msg = "%s - %s - %s - %s" % (self._labels['deck'], self._labels['match'], self._labels['clock'], self._labels['status']) self._label.spr.set_label(msg) def _restore_clicked(self, saved_selected_indices): ''' Restore the selected cards upon resume or share. ''' j = 0 for i in saved_selected_indices: if i is None: self.clicked[j].reset() else: self.clicked[j].spr = self.deck.index_to_card(i).spr k = self.grid.spr_to_grid(self.clicked[j].spr) self.clicked[j].spr.move(self.grid.match_to_xy(j)) self.clicked[j].pos = self.grid.match_to_xy(j) self.clicked[j].spr.set_layer(2000) j += 1 self.process_selection(None) def _restore_matches(self, saved_match_list_indices): ''' Restore the match list upon resume or share. ''' j = 0 self.match_list = [] for i in saved_match_list_indices: if i is not None: try: self.match_list.append(self.deck.index_to_card(i).spr) except AttributeError: _logger.debug('index %s was not found in deck' % (str(i))) def _restore_word_list(self, saved_word_list): ''' Restore the word list upon resume or share. ''' if len(saved_word_list) == 9: for i in range(3): for j in range(3): self.word_lists[i][j] = saved_word_list[i * 3 + j] def _counter(self): ''' Display of seconds since start_time. ''' seconds = int(GObject.get_current_time() - self.start_time) self.set_label('clock', str(seconds)) if self.robot and self.robot_time < seconds: self._find_a_match(robot_match=True) else: self.timeout_id = GObject.timeout_add(1000, self._counter) def _timer_reset(self): ''' Reset the timer for the robot ''' self.start_time = GObject.get_current_time() self.timeout_id = None self._counter() def _show_matches(self, i): ''' Show all the matches as a simple animation. ''' if i < self.matches and \ i * CARDS_IN_A_MATCH < len(self.match_list): for j in range(CARDS_IN_A_MATCH): self.grid.display_match( self.match_list[i * CARDS_IN_A_MATCH + j], j) self.match_timeout_id = GObject.timeout_add( 2000, self._show_matches, i + 1) def _find_a_match(self, robot_match=False): ''' Check to see whether there are any matches on the board. ''' # Before finding a match, return any cards from the match area if self._matches_on_display: if not self.deck.empty(): self._matches_on_display = False GObject.timeout_add(1000, self.clean_up_match) else: for c in self.clicked: if c.spr is not None: i = self.grid.find_an_empty_slot() if i is not None: c.spr.move(self.grid.grid_to_xy(i)) self.grid.grid[i] = self.deck.spr_to_card(c.spr) c.reset() a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] for i in Permutation(a): # TODO: really should be combination cardarray = [self.grid.grid[i[0]], self.grid.grid[i[1]], self.grid.grid[i[2]]] if self._match_check(cardarray, self.card_type): if robot_match: # Stop animations before moving robot match self.grid.stop_animation = True self._robot_match(i) return True return False def _robot_match(self, i): ''' Robot finds a match ''' for j in range(CARDS_IN_A_MATCH): # WARNING: Sometimes there is a race condition between the # robot delay and shoing a robot match. if self.grid.grid[i[j]] is not None: self.clicked[j].spr = self.grid.grid[i[j]].spr self.grid.grid[i[j]].spr.move(self.grid.match_to_xy(j)) else: _logger.error('in robot match, grid[%d] is None' % (i[j])) self.grid.grid[i[j]] = None self.robot_matches += 1 self._test_for_a_match() self._smiley[-1].spr.set_layer(100) self._matches_on_display = True def _match_check(self, cardarray, card_type): ''' For each attribute, either it is the same or different. ''' for a in cardarray: if a is None: return False if (cardarray[0].shape + cardarray[1].shape + cardarray[2].shape) % 3 != 0: self._failure = 0 return False if (cardarray[0].color + cardarray[1].color + cardarray[2].color) % 3 != 0: self._failure = 1 return False if (cardarray[0].fill + cardarray[1].fill + cardarray[2].fill) % 3 != 0: self._failure = 2 return False # Special case: only check number when shapes are the same if card_type == 'word': if cardarray[0].shape == cardarray[1].shape and \ cardarray[0].shape == cardarray[2].shape and \ (cardarray[0].num + cardarray[1].num + cardarray[2].num) % 3\ != 0: return False else: if (cardarray[0].num + cardarray[1].num + cardarray[2].num) % 3 != 0: self._failure = 3 return False self._failure = None return True def _choose_custom_card(self): ''' Select a custom card from the Journal ''' chooser = None name = None if hasattr(mime, 'GENERIC_TYPE_IMAGE'): # See #2398 if 'image/svg+xml' not in \ mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types: mime.get_generic_type( mime.GENERIC_TYPE_IMAGE).mime_types.append('image/svg+xml') chooser = ObjectChooser(parent=self.activity, what_filter=mime.GENERIC_TYPE_IMAGE) else: try: chooser = ObjectChooser(parent=self, what_filter=None) except TypeError: chooser = ObjectChooser( None, self.activity, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() if jobject and jobject.file_path: name = jobject.metadata['title'] mime_type = jobject.metadata['mime_type'] _logger.debug('result of choose: %s (%s)' % (name, str(mime_type))) finally: chooser.destroy() del chooser if name is not None: self._find_custom_paths(jobject) def _find_custom_paths(self, jobject): ''' Associate a Journal object with a card ''' found_a_sequence = False if self.custom_paths[0] is None: basename, suffix, i = _find_the_number_in_the_name( jobject.metadata['title']) ''' If this is the first card, try to find paths for other custom cards based on the name; else just load the card. ''' if i >= 0: dsobjects, nobjects = datastore.find( {'mime_type': [str(jobject.metadata['mime_type'])]}) self.custom_paths = [] if nobjects > 0: for j in range(DECKSIZE): for i in range(nobjects): if dsobjects[i].metadata['title'] == \ _construct_a_name(basename, j + 1, suffix): self.custom_paths.append(dsobjects[i]) break if len(self.custom_paths) < 9: for i in range(3, 81): self.custom_paths.append( self.custom_paths[int(i / 27)]) elif len(self.custom_paths) < 27: for i in range(9, 81): self.custom_paths.append( self.custom_paths[int(i / 9)]) elif len(self.custom_paths) < 81: for i in range(9, 81): self.custom_paths.append( self.custom_paths[int(i / 3)]) found_a_sequence = True self.activity.metadata['custom_object'] = jobject.object_id self.activity.metadata['custom_mime_type'] = \ jobject.metadata['mime_type'] if not found_a_sequence: grid_index = self.grid.spr_to_grid(self._edit_card.spr) self.custom_paths[grid_index] = jobject self.activity.metadata['custom_' + str(grid_index)] = \ jobject.object_id self.card_type = 'custom' self.activity.button_custom.set_icon('new-custom-game') self.activity.button_custom.set_tooltip(_('New custom game')) return def _in_motion(self, spr): ''' Is the sprite in a grid or match position or in motion? ''' x, y = spr.get_xy() if self.grid.xy_in_match((x, y)): return False if self.grid.xy_in_grid((x, y)): return False return True def help_animation(self): ''' Simple explanatory animation at start of play ''' for i in range(22): path = os.path.join(activity.get_bundle_path(), 'images', 'help-%d.svg' % i) svg_str = svg_from_file(path) pixbuf = svg_str_to_pixbuf(svg_str, int(self._width / 2), int(self._height / 2)) self._help.append(Sprite(self._sprites, int(self._width / 4), int(self._height / 4), pixbuf)) self._help[-1].hide() self._help_index = 0 self._stop_help = False self._help[self._help_index].set_layer(5000) self._help_timeout_id = GObject.timeout_add(2000, self._help_next) def _help_next(self): ''' Load the next frame in the animation ''' self._help[self._help_index].hide() if self._stop_help: self._help = [] return self._help_index += 1 self._help_index %= len(self._help) self._help[self._help_index].set_layer(5000) if self._help_index in [0, 9, 10, 20, 21]: self._help_timeout_id = GObject.timeout_add(2000, self._help_next) else: self._help_timeout_id = GObject.timeout_add(1000, self._help_next)