# -*- coding: utf-8 -*- #Copyright (c) 2011-12 Walter Bender #Copyright (c) 2012 Ignacio Rodríguez # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # You should have received a copy of the GNU General Public License # along with this library; if not, write to the Free Software # Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA from gi.repository import Gtk, GdkPixbuf, GObject, Gdk import cairo from random import uniform from gettext import gettext as _ import logging _logger = logging.getLogger('reflection-activity') try: from sugar3.graphics import style GRID_CELL_SIZE = style.GRID_CELL_SIZE except ImportError: GRID_CELL_SIZE = 0 from sprites import Sprites, Sprite # Grid dimensions must be even TEN = 10 SIX = 6 DOT_SIZE = 40 class Game(): def __init__(self, canvas, parent=None, colors=['#A0FFA0', '#FF8080']): self._activity = parent self._colors = [colors[0]] self._colors.append(colors[1]) self._colors.append('#FFFFFF') self._colors.append('#000000') self._colors.append('#FF0000') self._colors.append('#FF8000') self._colors.append('#FFFF00') self._colors.append('#00FF00') self._colors.append('#00FFFF') self._colors.append('#0000FF') self._colors.append('#FF00FF') self._canvas = canvas if parent is not None: parent.show_all() self._parent = parent self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("button-press-event", self._button_press_cb) self._canvas.connect("button-release-event", self._button_release_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) self._width = Gdk.Screen.width() self._height = Gdk.Screen.height() - (GRID_CELL_SIZE * 1.5) self._scale = self._width / (10 * DOT_SIZE * 1.2) self._dot_size = int(DOT_SIZE * self._scale) self._space = int(self._dot_size / 5.) self._orientation = 'horizontal' self.we_are_sharing = False self.playing_with_robot = False self._press = False self.last_spr = None self._timer = None self.roygbiv = False # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._dots = [] for y in range(SIX): for x in range(TEN): xoffset = int((self._width - TEN * self._dot_size - \ (TEN - 1) * self._space) / 2.) self._dots.append( Sprite(self._sprites, xoffset + x * (self._dot_size + self._space), y * (self._dot_size + self._space), self._new_dot(self._colors[2]))) self._dots[-1].type = 2 # not set self._dots[-1].set_label_attributes(40) self.vline = Sprite(self._sprites, int(self._width / 2.) - 1, 0, self._line(vertical=True)) n = SIX / 2. self.hline = Sprite( self._sprites, 0, int(self._dot_size * n + self._space * (n - 0.5)) - 1, self._line(vertical=False)) self.hline.hide() # and initialize a few variables we'll need. self._all_clear() def _all_clear(self): ''' Things to reinitialize when starting up a new game. ''' for dot in self._dots: dot.type = 2 dot.set_shape(self._new_dot(self._colors[2])) dot.set_label('') self._set_orientation() def _set_orientation(self): ''' Set bar and message for current orientation ''' if self._orientation == 'horizontal': self.hline.hide() self.vline.set_layer(1000) elif self._orientation == 'vertical': self.hline.set_layer(1000) self.vline.hide() else: self.hline.set_layer(1000) self.vline.set_layer(1000) ''' if self._orientation == 'horizontal': self._set_label( _('Click on the dots to make a horizontal reflection.')) elif self._orientation == 'vertical': self._set_label( _('Click on the dots to make a vertical reflection.')) else: self._set_label( _('Click on the dots to make a bilateral reflection.')) ''' def _initiating(self): return self._activity.initiating def new_game(self, orientation='horizontal'): ''' Start a new game. ''' self._orientation = orientation self._all_clear() # Fill in a few dots to start for i in range(int(TEN * SIX / 2)): n = int(uniform(0, TEN * SIX)) if self.roygbiv: self._dots[n].type = int(uniform(2, len(self._colors))) else: self._dots[n].type = int(uniform(0, 4)) self._dots[n].set_shape(self._new_dot( self._colors[self._dots[n].type])) if self.we_are_sharing: _logger.debug('sending a new game') self._parent.send_new_game() def restore_game(self, dot_list, orientation): ''' Restore a game from the Journal or share ''' for i, dot in enumerate(dot_list): self._dots[i].type = dot self._dots[i].set_shape(self._new_dot( self._colors[self._dots[i].type])) self._orientation = orientation self._set_orientation() def save_game(self): ''' Return dot list and orientation for saving to Journal or sharing ''' dot_list = [] for dot in self._dots: dot_list.append(dot.type) return [dot_list, self._orientation] def _set_label(self, string): ''' Set the label in the toolbar or the window frame. ''' self._activity.status.set_label(string) def _button_press_cb(self, win, event): win.grab_focus() x, y = map(int, event.get_coords()) self._press = True spr = self._sprites.find_sprite((x, y)) if spr == None: return True self.last_spr = spr if spr.type is not None: if not self._timer is None: GObject.source_remove(self._timer) self._increment_dot(spr) return True def _button_release_cb(self, win, event): self._press = False if not self._timer is None: GObject.source_remove(self._timer) def _increment_dot(self, spr): spr.type += 1 if self.roygbiv: if spr.type >= len(self._colors): spr.type = 2 else: spr.type %= 4 spr.set_shape(self._new_dot(self._colors[spr.type])) if self.playing_with_robot: self._robot_play(spr) self._test_game_over() if self.we_are_sharing: _logger.debug('sending a click to the share') self._parent.send_dot_click(self._dots.index(spr), spr.type) self._timer = GObject.timeout_add(1000, self._increment_dot, spr) def _mouse_move_cb(self, win, event): """ Drag a tile with the mouse. """ if not self._press: return x, y = map(int, event.get_coords()) spr = self._sprites.find_sprite((x, y)) if spr == self.last_spr: return True if spr is None: return True if spr.type is not None: self.last_spr = spr if not self._timer is None: GObject.source_remove(self._timer) self._increment_dot(spr) def _robot_play(self, dot): ''' Robot reflects dot clicked. ''' x, y = self._dot_to_grid(self._dots.index(dot)) if self._orientation == 'horizontal': x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) elif self._orientation == 'vertical': y = SIX - y - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) else: x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) y = SIX - y - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) x = TEN - x - 1 i = self._grid_to_dot((x, y)) self._dots[i].type = dot.type self._dots[i].set_shape(self._new_dot(self._colors[dot.type])) if self.we_are_sharing: _logger.debug('sending a robot click to the share') self._parent.send_dot_click(i, dot.type) def remote_button_press(self, dot, color): ''' Receive a button press from a sharer ''' self._dots[dot].type = color self._dots[dot].set_shape(self._new_dot(self._colors[color])) def set_sharing(self, share=True): _logger.debug('enabling sharing') self.we_are_sharing = share def _smile(self): for dot in self._dots: dot.set_label(':)') def _test_game_over(self): ''' Check to see if game is over ''' if self._orientation == 'horizontal': for y in range(SIX): for x in range(SIX): if self._dots[y * TEN + x].type != \ self._dots[y * TEN + TEN - x - 1].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() return True if self._orientation == 'vertical': for y in range(int(SIX / 2)): for x in range(TEN): if self._dots[y * TEN + x].type != \ self._dots[(SIX - y - 1) * TEN + x].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) else: for y in range(SIX): for x in range(SIX): if self._dots[y * TEN + x].type != \ self._dots[y * TEN + TEN - x - 1].type: self._set_label(_('keep trying')) return False for y in range(int(SIX / 2)): for x in range(TEN): if self._dots[y * TEN + x].type != \ self._dots[(SIX - y - 1) * TEN + x].type: self._set_label(_('keep trying')) return False self._set_label(_('good work')) self._smile() return True def __draw_cb(self,canvas,cr): self._sprites.redraw_sprites(cr=cr) def _grid_to_dot(self, pos): ''' calculate the dot index from a column and row in the grid ''' return pos[0] + pos[1] * TEN def _dot_to_grid(self, dot): ''' calculate the grid column and row for a dot ''' return [dot % TEN, int(dot / TEN)] def _expose_cb(self, win, event): self.do_expose_event(event) 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 ''' self._dot_cache = {} if not color in self._dot_cache: self._stroke = color self._fill = color self._svg_width = self._dot_size self._svg_height = self._dot_size pixbuf = svg_str_to_pixbuf( self._header() + \ self._circle(self._dot_size / 2., self._dot_size / 2., self._dot_size / 2.) + \ 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 self._dot_cache[color] def _line(self, vertical=True): ''' Generate a center line ''' if vertical: self._svg_width = 3 self._svg_height = self._height return svg_str_to_pixbuf( self._header() + \ self._rect(3, self._height, 0, 0) + \ self._footer()) else: self._svg_width = self._width self._svg_height = 3 return svg_str_to_pixbuf( self._header() + \ self._rect(self._width, 3, 0, 0) + \ self._footer()) def _header(self): return '\n' def _rect(self, w, h, x, y): svg_string = ' \n' def _footer(self): return '\n' def svg_str_to_pixbuf(svg_string): try: pl = GdkPixbuf.PixbufLoader.new_with_type('svg') pl.write(svg_string) pl.close() pixbuf = pl.get_pixbuf() return pixbuf except: print svg_string return None