class ImplodeActivity(Activity): def __init__(self, handle): Activity.__init__(self, handle) self._joining_hide = False self._game = ImplodeGame() self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) game_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) game_box.pack_start(self._game, True, True, 0) self._stuck_strip = _StuckStrip() self._configure_toolbars() self.set_canvas(game_box) # Show everything except the stuck strip. self.show_all() self._configure_cb() game_box.pack_end(self._stuck_strip, expand=False, fill=False, padding=0) self._game.connect('show-stuck', self._show_stuck_cb) self._game.connect('piece-selected', self._piece_selected_cb) self._game.connect('undo-key-pressed', self._undo_key_pressed_cb) self._game.connect('redo-key-pressed', self._redo_key_pressed_cb) self._game.connect('new-key-pressed', self._new_key_pressed_cb) self._stuck_strip.connect('undo-clicked', self._stuck_undo_cb) game_box.connect('key-press-event', self._key_press_event_cb) self._game.grab_focus() last_game_path = self._get_last_game_path() if os.path.exists(last_game_path): self.read_file(last_game_path) self._collab.setup() # Hide the canvas when joining a shared activity if self.shared_activity: if not self.get_shared(): self.get_canvas().hide() self.busy() self._joining_hide = True def _get_last_game_path(self): return os.path.join(self.get_activity_root(), 'data', 'last_game') def get_data(self): return self._game.get_game_state() def set_data(self, data): if not data['win_draw_flag']: self._game.set_game_state(data) # Ensure that the visual display matches the game state. self._levels_buttons[data['difficulty']].props.active = True # Release the cork if self._joining_hide: self.get_canvas().show() self.unbusy() def read_file(self, file_path): # Loads the game state from a file. f = open(file_path, 'r') file_data = json.loads(f.read()) f.close() (file_type, version, game_data) = file_data if file_type == 'Implode save game' and version <= [1, 0]: self.set_data(game_data) def write_file(self, file_path): # Writes the game state to a file. data = self.get_data() file_data = ['Implode save game', [1, 0], data] last_game_path = self._get_last_game_path() for path in (file_path, last_game_path): f = open(path, 'w') f.write(json.dumps(file_data)) f.close() def _show_stuck_cb(self, state, data=None): if self.shared_activity: return if self.metadata: share_scope = self.metadata.get('share-scope', SCOPE_PRIVATE) if share_scope != SCOPE_PRIVATE: return if data: self._stuck_strip.show_all() else: if self._stuck_strip.get_focus_child(): self._game.grab_focus() self._stuck_strip.hide() def _stuck_undo_cb(self, state, data=None): self._game.undo_to_solvable_state() def _key_press_event_cb(self, source, event): # Make the game navigable by keypad controls. action = KEY_MAP.get(event.keyval, None) if action is None: return False if not self._stuck_strip.get_state_flags() & Gtk.AccelFlags.VISIBLE: return True if self._game.get_focus_child(): if action == 'down': self._stuck_strip.button.grab_focus() return True elif self._stuck_strip.get_focus_child(): if action == 'up': self._game.grab_focus() elif action == 'select': self._stuck_strip.button.activate() return True return True def _configure_toolbars(self): """Create, set, and show a toolbar box with an activity button, game controls, difficulty selector, help button, and stop button. All callbacks are locally defined.""" self._seps = [] toolbar_box = ToolbarBox() toolbar = toolbar_box.toolbar activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() self._add_separator(toolbar) def add_button(icon_name, tooltip, callback): button = ToolButton(icon_name) toolbar.add(button) button.connect('clicked', callback) button.set_tooltip(tooltip) return button add_button('new-game', _("New"), self._new_game_cb) add_button('replay-game', _("Replay"), self._replay_game_cb) self._add_separator(toolbar) add_button('edit-undo', _("Undo"), self._undo_cb) add_button('edit-redo', _("Redo"), self._redo_cb) self._add_separator(toolbar) self._levels_buttons = [] def add_level_button(icon_name, tooltip, numeric_level): if self._levels_buttons: button = RadioToolButton(icon_name=icon_name, group=self._levels_buttons[0]) else: button = RadioToolButton(icon_name=icon_name) self._levels_buttons.append(button) toolbar.add(button) def callback(source): if source.get_active(): self._collab.post({'action': icon_name}) self._game.set_level(numeric_level) self._game.new_game() button.connect('toggled', callback) button.set_tooltip(tooltip) add_level_button('easy-level', _("Easy"), 0) add_level_button('medium-level', _("Medium"), 1) add_level_button('hard-level', _("Hard"), 2) self._add_separator(toolbar) def _help_clicked_cb(button): help_window = _HelpWindow() help_window.set_transient_for(self.get_toplevel()) help_window.show_all() help_button = add_button('toolbar-help', _("Help"), _help_clicked_cb) def _help_disable_cb(collab, buddy): if help_button.props.sensitive: help_button.props.sensitive = False self._collab.connect('buddy-joined', _help_disable_cb) self._add_expander(toolbar) stop_button = StopButton(self) toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbar_box) toolbar_box.show() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _add_separator(self, toolbar): self._seps.append(Gtk.SeparatorToolItem()) toolbar.add(self._seps[-1]) self._seps[-1].show() def _add_expander(self, toolbar, expand=True): """Insert a toolbar item which will expand to fill the available space.""" self._seps.append(Gtk.SeparatorToolItem()) self._seps[-1].props.draw = False self._seps[-1].set_expand(expand) toolbar.insert(self._seps[-1], -1) self._seps[-1].show() def _configure_cb(self, event=None): if Gdk.Screen.width() < Gdk.Screen.height(): hide = True else: hide = False for sep in self._seps: if hide: sep.hide() else: sep.show() def _new_game_cb(self, button): self._game.reseed() self._collab.post({ 'action': 'new-game', 'seed': self._game.get_seed() }) self._game.new_game() def _replay_game_cb(self, button): self._collab.post({'action': 'replay-game'}) self._game.replay_game() def _undo_cb(self, button): self._collab.post({'action': 'edit-undo'}) self._game.undo() def _redo_cb(self, button): self._collab.post({'action': 'edit-redo'}) self._game.redo() def _message_cb(self, collab, buddy, msg): action = msg.get('action') if action == 'new-game': self._game.set_seed(msg.get('seed')) self._game.new_game() elif action == 'replay-game': self._game.replay_game() elif action == 'edit-undo': self._game.undo() elif action == 'edit-redo': self._game.redo() elif action == 'easy-level': self._game.set_level(0) self._game.new_game() elif action == 'medium-level': self._game.set_level(1) self._game.new_game() elif action == 'hard-level': self._game.set_level(2) self._game.new_game() elif action == 'piece-selected': x = msg.get('x') y = msg.get('y') self._game.piece_selected(x, y) def _piece_selected_cb(self, game, x, y): self._collab.post({'action': 'piece-selected', 'x': x, 'y': y}) def _undo_key_pressed_cb(self, game, dummy): self._collab.post({'action': 'edit-undo'}) def _redo_key_pressed_cb(self, game, dummy): self._collab.post({'action': 'edit-redo'}) def _new_key_pressed_cb(self, game, seed): self._collab.post({'action': 'new-game', 'seed': seed})
class FlipActivity(activity.Activity): """ Flip puzzle game """ def __init__(self, handle): """ Initialize the toolbars and the game board """ super(FlipActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self._setup_toolbars() self._setup_dispatch_table() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() if 'dotlist' in self.metadata: self._restore() else: self._game.new_game() def _setup_toolbars(self): """ Setup the toolbars. """ self.max_participants = 4 toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_game_button_h = button_factory('new-game', self.toolbar, self._new_game_cb, tooltip=_('Start a game.')) self.status = label_factory(self.toolbar, '') separator_factory(toolbox.toolbar, True, False) self.solver = button_factory('help-toolbar', self.toolbar, self._solve_cb, tooltip=_('Solve the puzzle')) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _new_game_cb(self, button=None): ''' Start a new game. ''' self._game.new_game() def _solve_cb(self, button=None): ''' Solve the puzzle ''' self._game.solve() def write_file(self, file_path): """ Write the grid status to the Journal """ (dot_list, move_list) = self._game.save_game() self.metadata['dotlist'] = '' for dot in dot_list: self.metadata['dotlist'] += str(dot) if dot_list.index(dot) < len(dot_list) - 1: self.metadata['dotlist'] += ' ' self.metadata['movelist'] = '' for move in move_list: self.metadata['movelist'] += str(move) if move_list.index(move) < len(move_list) - 1: self.metadata['movelist'] += ' ' _logger.debug(self.metadata['movelist']) def _restore(self): """ Restore the game state from metadata """ dot_list = [] dots = self.metadata['dotlist'].split() for dot in dots: dot_list.append(int(dot)) if 'movelist' in self.metadata: move_list = [] moves = self.metadata['movelist'].split() for move in moves: move_list.append(int(move)) else: move_list = None _logger.debug(move_list) self._game.restore_game(dot_list, move_list) # Collaboration-related methods def set_data(self, data): pass def get_data(self): return None def _shared_cb(self, activity): """ Either set up initial share...""" self.after_share_join(True) def _joined_cb(self, activity): """ ...or join an exisiting share. """ self.after_share_join(False) def after_share_join(self, sharer): self.waiting_for_hand = not sharer self._game.set_sharing(True) def _setup_dispatch_table(self): ''' Associate tokens with commands. ''' self._processing_methods = { 'n': [self._receive_new_game, 'get a new game grid'], 'p': [self._receive_dot_click, 'get a dot click'], } def _message_cb(self, collab, buddy, msg): ''' Data from a tube has arrived. ''' command = msg.get('command') payload = msg.get('payload') self._processing_methods[command][0](payload) def send_new_game(self): ''' Send a new grid to all players ''' self.send_event('n', self._game.save_game()) def _receive_new_game(self, payload): ''' Sharer can start a new game. ''' (dot_list, move_list) = payload self._game.restore_game(dot_list, move_list) def send_dot_click(self, dot): ''' Send a dot click to all the players ''' self.send_event('p', dot) def _receive_dot_click(self, payload): ''' When a dot is clicked, everyone should change its color. ''' dot = payload self._game.remote_button_press(dot) def send_event(self, command, payload): """ Send event through the tube. """ self._collab.post({'command': command, 'payload': payload})
class FractionBounceActivity(activity.Activity): def __init__(self, handle): ''' Initiate activity. ''' super(FractionBounceActivity, self).__init__(handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self._colors = profile.get_color().to_string().split(',') else: self._colors = ['#A0FFA0', '#FF8080'] self.max_participants = 4 # sharing self._playing = True self._setup_toolbars() canvas = self._setup_canvas() # Read any custom fractions from the project metadata if 'custom' in self.metadata: custom = self.metadata['custom'] else: custom = None self._current_ball = 'soccerball' self._toolbar_was_expanded = False # Initialize the canvas self._bounce_window = Bounce(canvas, activity.get_bundle_path(), self) Gdk.Screen.get_default().connect('size-changed', self._configure_cb) # Restore any custom fractions if custom is not None: fractions = custom.split(',') for f in fractions: self._bounce_window.add_fraction(f) self._bounce_window.buddies.append(self.nick) self._player_colors = [self._colors] self._player_pixbufs = [ svg_str_to_pixbuf(generate_xo_svg(scale=0.8, colors=self._colors)) ] def on_activity_joined_cb(me): logging.debug('activity joined') self._player.set_from_pixbuf(self._player_pixbufs[0]) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self._player.set_from_pixbuf(self._player_pixbufs[0]) self._label.set_label(_('Wait for others to join.')) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) if self.shared_activity: # We're joining if not self.get_shared(): self._label.set_label(_('Wait for the sharer to start.')) actions = { 'j': self._new_joiner, 'b': self._buddy_list, 'f': self._receive_a_fraction, 't': self._take_a_turn, 'l': self._buddy_left, } def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) if self._playing: actions[msg.get('action')](msg.get('data')) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self.send_event('j', [self.nick, self._colors]) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r' % (buddy.props.nick)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._collab.setup() def set_data(self, blob): pass def get_data(self): return None def close(self, **kwargs): aplay.close() activity.Activity.close(self, **kwargs) def _configure_cb(self, event): if Gdk.Screen.width() < 1024: self._label.set_size_request(275, -1) self._label.set_label('') self._separator.set_expand(False) else: self._label.set_size_request(500, -1) self._separator.set_expand(True) self._bounce_window.configure_cb(event) if self._toolbar_expanded(): self._bounce_window.bar.bump_bars('up') self._bounce_window.ball.ball.move_relative( (0, -style.GRID_CELL_SIZE)) def _toolbar_expanded(self): if self._activity_button.is_expanded(): return True elif self._custom_toolbar_button.is_expanded(): return True return False def _update_graphics(self, widget): # We need to catch opening and closing of toolbars and ignore # switching between open toolbars. if self._toolbar_expanded(): if not self._toolbar_was_expanded: self._bounce_window.bar.bump_bars('up') self._bounce_window.ball.ball.move_relative( (0, -style.GRID_CELL_SIZE)) self._toolbar_was_expanded = True else: if self._toolbar_was_expanded: self._bounce_window.bar.bump_bars('down') self._bounce_window.ball.ball.move_relative( (0, style.GRID_CELL_SIZE)) self._toolbar_was_expanded = False def _setup_toolbars(self): custom_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() self._toolbar = toolbox.toolbar self._activity_button = ActivityToolbarButton(self) self._activity_button.connect('clicked', self._update_graphics) self._toolbar.insert(self._activity_button, 0) self._activity_button.show() self._custom_toolbar_button = ToolbarButton(label=_('Custom'), page=custom_toolbar, icon_name='view-source') self._custom_toolbar_button.connect('clicked', self._update_graphics) custom_toolbar.show() self._toolbar.insert(self._custom_toolbar_button, -1) self._custom_toolbar_button.show() self._load_standard_buttons(self._toolbar) self._separator = Gtk.SeparatorToolItem() self._separator.props.draw = False self._separator.set_expand(True) self._toolbar.insert(self._separator, -1) self._separator.show() stop_button = StopButton(self) stop_button.props.accelerator = _('<Ctrl>Q') self._toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbox) toolbox.show() self._load_custom_buttons(custom_toolbar) def _load_standard_buttons(self, toolbar): fraction_button = RadioToolButton(group=None) fraction_button.set_icon_name('fraction') fraction_button.set_tooltip(_('fractions')) fraction_button.connect('clicked', self._fraction_cb) toolbar.insert(fraction_button, -1) fraction_button.show() sector_button = RadioToolButton(group=fraction_button) sector_button.set_icon_name('sector') sector_button.set_tooltip(_('sectors')) sector_button.connect('clicked', self._sector_cb) toolbar.insert(sector_button, -1) sector_button.show() percent_button = RadioToolButton(group=fraction_button) percent_button.set_icon_name('percent') percent_button.set_tooltip(_('percents')) percent_button.connect('clicked', self._percent_cb) toolbar.insert(percent_button, -1) percent_button.show() self._player = Gtk.Image() self._player.set_from_pixbuf( svg_str_to_pixbuf( generate_xo_svg(scale=0.8, colors=['#282828', '#282828']))) self._player.set_tooltip_text(self.nick) toolitem = Gtk.ToolItem() toolitem.add(self._player) self._player.show() toolbar.insert(toolitem, -1) toolitem.show() self._label = Gtk.Label(_("Click the ball to start.")) self._label.set_line_wrap(True) if Gdk.Screen.width() < 1024: self._label.set_size_request(275, -1) else: self._label.set_size_request(500, -1) self.toolitem = Gtk.ToolItem() self.toolitem.add(self._label) self._label.show() toolbar.insert(self.toolitem, -1) self.toolitem.show() def _load_custom_buttons(self, toolbar): self.numerator = Gtk.Entry() self.numerator.set_text('') self.numerator.set_tooltip_text(_('numerator')) self.numerator.set_width_chars(3) toolitem = Gtk.ToolItem() toolitem.add(self.numerator) self.numerator.show() toolbar.insert(toolitem, -1) toolitem.show() label = Gtk.Label(' / ') toolitem = Gtk.ToolItem() toolitem.add(label) label.show() toolbar.insert(toolitem, -1) toolitem.show() self.denominator = Gtk.Entry() self.denominator.set_text('') self.denominator.set_tooltip_text(_('denominator')) self.denominator.set_width_chars(3) toolitem = Gtk.ToolItem() toolitem.add(self.denominator) self.denominator.show() toolbar.insert(toolitem, -1) toolitem.show() button = ToolButton('list-add') button.set_tooltip(_('add new fraction')) button.props.sensitive = True button.props.accelerator = 'Return' button.connect('clicked', self._add_fraction_cb) toolbar.insert(button, -1) button.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(False) toolbar.insert(separator, -1) separator.show() button = ToolButton('soccerball') button.set_tooltip(_('choose a ball')) button.props.sensitive = True button.connect('clicked', self._button_palette_cb) toolbar.insert(button, -1) button.show() self._ball_palette = button.get_palette() button_grid = Gtk.Grid() row = 0 for ball in BALLDICT.keys(): if ball == 'custom': button = ToolButton('view-source') else: button = ToolButton(ball) button.connect('clicked', self._load_ball_cb, None, ball) eventbox = Gtk.EventBox() eventbox.connect('button_press_event', self._load_ball_cb, ball) label = Gtk.Label(BALLDICT[ball][0]) eventbox.add(label) label.show() button_grid.attach(button, 0, row, 1, 1) button.show() button_grid.attach(eventbox, 1, row, 1, 1) eventbox.show() row += 1 self._ball_palette.set_content(button_grid) button_grid.show() button = ToolButton('insert-picture') button.set_tooltip(_('choose a background')) button.props.sensitive = True button.connect('clicked', self._button_palette_cb) toolbar.insert(button, -1) button.show() self._bg_palette = button.get_palette() button_grid = Gtk.Grid() row = 0 for bg in BGDICT.keys(): if bg == 'custom': button = ToolButton('view-source') else: button = ToolButton(bg) button.connect('clicked', self._load_bg_cb, None, bg) eventbox = Gtk.EventBox() eventbox.connect('button_press_event', self._load_bg_cb, bg) label = Gtk.Label(BGDICT[bg][0]) eventbox.add(label) label.show() button_grid.attach(button, 0, row, 1, 1) button.show() button_grid.attach(eventbox, 1, row, 1, 1) eventbox.show() row += 1 self._bg_palette.set_content(button_grid) button_grid.show() def _button_palette_cb(self, button): palette = button.get_palette() if palette: if not palette.is_up(): palette.popup(immediate=True) else: palette.popdown(immediate=True) def can_close(self): # Let everyone know we are leaving... if hasattr(self, '_bounce_window') and \ self._bounce_window.we_are_sharing(): self._playing = False self.send_event('l', self.nick) return True def _setup_canvas(self): canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() return canvas def _load_bg_cb(self, widget, event, bg): if bg == 'custom': chooser(self, 'Image', self._new_background_from_journal) else: self._bounce_window.set_background(BGDICT[bg][1]) def _load_ball_cb(self, widget, event, ball): if ball == 'custom': chooser(self, 'Image', self._new_ball_from_journal) else: self._bounce_window.ball.new_ball( os.path.join(activity.get_bundle_path(), 'images', ball + '.svg')) self._bounce_window.set_background(BGDICT[BALLDICT[ball][1]][1]) self._current_ball = ball def _reset_ball(self): ''' If we switch back from sector mode, we need to restore the ball ''' if self._bounce_window.mode != 'sectors': return if self._current_ball == 'custom': # TODO: Reload custom ball self._current_ball = 'soccerball' self._bounce_window.ball.new_ball( os.path.join(activity.get_bundle_path(), 'images', self._current_ball + '.svg')) def _new_ball_from_journal(self, dsobject): ''' Load an image from the Journal. ''' self._bounce_window.ball.new_ball_from_image( dsobject.file_path, os.path.join(activity.get_activity_root(), 'tmp', 'custom.png')) def _new_background_from_journal(self, dsobject): ''' Load an image from the Journal. ''' self._bounce_window.new_background_from_image(None, dsobject=dsobject) def _fraction_cb(self, arg=None): ''' Set fraction mode ''' self._reset_ball() self._bounce_window.mode = 'fractions' def _percent_cb(self, arg=None): ''' Set percent mode ''' self._reset_ball() self._bounce_window.mode = 'percents' def _sector_cb(self, arg=None): ''' Set sector mode ''' self._bounce_window.mode = 'sectors' def _add_fraction_cb(self, arg=None): ''' Read entries and add a fraction to the list ''' try: numerator = int(self.numerator.get_text().strip()) except ValueError: self.numerator.set_text('NAN') numerator = 0 try: denominator = int(self.denominator.get_text().strip()) except ValueError: self.denominator.set_text('NAN') denominator = 1 if denominator == 0: self.denominator.set_text('ZDE') if numerator > denominator: numerator = 0 if numerator > 0 and denominator > 1: fraction = '%d/%d' % (numerator, denominator) self._bounce_window.add_fraction(fraction) if 'custom' in self.metadata: # Save to Journal self.metadata['custom'] = '%s,%s' % (self.metadata['custom'], fraction) else: self.metadata['custom'] = fraction self.alert( _('New fraction'), _('Your fraction, %s, has been added to the program' % (fraction))) def reset_label(self, label): ''' update the challenge label ''' self._label.set_label(label) def alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) # Collaboration-related methods def _buddy_left(self, nick): self._label.set_label(nick + ' ' + _('has left.')) if self._collab.props.leader: self._remove_player(nick) self.send_event('b', [self._bounce_window.buddies, self._player_colors]) # Restart from sharer's turn self._bounce_window.its_my_turn() def _new_joiner(self, payload): ''' Someone has joined; sharer adds them to the buddy list. ''' [nick, colors] = payload self._label.set_label(nick + ' ' + _('has joined.')) if self._collab.props.leader: self._append_player(nick, colors) self.send_event('b', [self._bounce_window.buddies, self._player_colors]) if self._bounce_window.count == 0: # Haven't started yet... self._bounce_window.its_my_turn() def _remove_player(self, nick): if nick in self._bounce_window.buddies: i = self._bounce_window.buddies.index(nick) self._bounce_window.buddies.remove(nick) self._player_colors.remove(self._player_colors[i]) self._player_pixbufs.remove(self._player_pixbufs[i]) def _append_player(self, nick, colors): ''' Keep a list of players, their colors, and an XO pixbuf ''' if nick not in self._bounce_window.buddies: _logger.debug('appending %s to the buddy list', nick) self._bounce_window.buddies.append(nick) self._player_colors.append([str(colors[0]), str(colors[1])]) self._player_pixbufs.append( svg_str_to_pixbuf( generate_xo_svg(scale=0.8, colors=self._player_colors[-1]))) def _buddy_list(self, payload): '''Sharer sent the updated buddy list, so regenerate internal lists''' if not self._collab.props.leader: [buddies, colors] = payload self._bounce_window.buddies = buddies[:] self._player_colors = colors[:] self._player_pixbufs = [] for colors in self._player_colors: self._player_pixbufs.append( svg_str_to_pixbuf( generate_xo_svg( scale=0.8, colors=[str(colors[0]), str(colors[1])]))) def send_a_fraction(self, fraction): ''' Send a fraction to other players. ''' self.send_event('f', fraction) def _receive_a_fraction(self, payload): ''' Receive a fraction from another player. ''' self._bounce_window.play_a_fraction(payload) def _take_a_turn(self, nick): ''' If it is your turn, take it, otherwise, wait. ''' if nick == self.nick: # TODO: disambiguate self._bounce_window.its_my_turn() else: self._bounce_window.its_their_turn(nick) def send_event(self, action, data): ''' Send event through the tube. ''' _logger.debug('send_event action=%r data=%r' % (action, data)) self._collab.post({'action': action, 'data': data}) def set_player_on_toolbar(self, nick): ''' Display the XO icon of the player whose turn it is. ''' self._player.set_from_pixbuf( self._player_pixbufs[self._bounce_window.buddies.index(nick)]) self._player.set_tooltip_text(nick)
class CollabWrapperTestActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._make_toolbar_box() self._make_canvas() self._make_collaboration() self._make_automatic_restart() def set_data(self, data): if data is not 'no specific data': logging.error('get_data, set_data: unexpected data %r' % data) def get_data(self): return 'no specific data' def _make_toolbar_box(self): toolbar_box = ToolbarBox() def tool(callable, pos): widget = callable(self) toolbar_box.toolbar.insert(widget, pos) widget.show() return widget def bar(): separator = Gtk.SeparatorToolItem() toolbar_box.toolbar.insert(separator, -1) separator.show() def gap(): separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() tool(ActivityButton, 0) tool(TitleEntry, -1) tool(DescriptionItem, -1) tool(ShareButton, -1) bar() button = tool(TestButton, -1) button.props.accelerator = '<ctrl>p' button.connect('clicked', self._test_clicked_cb) button = tool(SendButton, -1) button.connect('clicked', self._send_clicked_cb) button = tool(ClearButton, -1) button.connect('clicked', self._clear_clicked_cb) gap() tool(StopButton, -1) toolbar_box.show() self.set_toolbar_box(toolbar_box) def _test_clicked_cb(self, widget): now = time.time() self._collab.post({'action': 'echo-request', 'text': now}) self._say('%.6f send echo-request %r\n' % (now, now)) def _send_clicked_cb(self, widget): now = time.time() data = 'One Two Three' desc = 'Test Data' self._collab.send_file_memory(self._last_buddy, data, desc) self._say('%.6f send data %r\n' % (now, (data, desc))) def _clear_clicked_cb(self, widget): self._textbuffer.props.text = '' def _make_canvas(self): self._textview = Gtk.TextView() self._textview.props.editable = False self._textbuffer = self._textview.get_buffer() sw = Gtk.ScrolledWindow() sw.add(self._textview) entry = Gtk.Entry() entry.connect('activate', self._entry_activate_cb) def focus_timer_cb(): entry.grab_focus() return False GLib.timeout_add(1500, focus_timer_cb) box = Gtk.VBox() box.pack_start(sw, True, True, 10) box.pack_end(entry, False, False, 10) box.show_all() self.set_canvas(box) def _say(self, string): self._textbuffer.begin_user_action() self._textbuffer.insert(self._textbuffer.get_end_iter(), string, len(string)) self._textview.scroll_mark_onscreen( self._textbuffer.create_mark(None, self._textbuffer.get_end_iter())) self._textbuffer.end_user_action() def _entry_activate_cb(self, widget): text = widget.props.text widget.props.text = '' self._collab.post({'action': 'chat', 'text': text}) self._say('%.6f send chat %r\n' % (time.time(), text)) def _make_collaboration(self): def on_activity_joined_cb(me): self._say('%.6f activity joined\n' % (time.time())) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): self._say('%.6f activity shared\n' % (time.time())) self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) def on_joined_cb(collab, msg): self._say('%.6f joined\n' % (time.time())) self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): self._say('%.6f buddy-joined %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): self._say('%.6f buddy-left %s@%s\n' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') def on_incoming_file_cb(collab, ft, desc): self._say('%.6f incoming-file %r\n' % (time.time(), desc)) def on_ready_cb(ft, stream): stream.close(None) gbytes = stream.steal_as_bytes() data = gbytes.get_data() self._say('%.6f data %r\n' % (time.time(), data)) ft.connect('ready', on_ready_cb) ft.accept_to_memory() self._collab.connect('incoming_file', on_incoming_file_cb) self._collab.setup() self._last_buddy = None def _message_cb(self, collab, buddy, msg): def say(string): self._say('%.6f recv from %s@%s ' % (time.time(), buddy.props.nick, buddy.props.ip4_address)) self._say(string) self._last_buddy = buddy action = msg.get('action') if action == 'echo-reply': text = msg.get('text') latency = (time.time() - float(text)) * 1000.0 say('%s %r latency=%.3f ms\n' % (action, text, latency)) return if action == 'echo-request': text = msg.get('text') self._collab.post({'action': 'echo-reply', 'text': text}) say('%s %r\n' % (action, text)) return if action == 'chat': text = msg.get('text') say('%s %r\n' % (action, text)) return say('%s\n' % (action)) def _make_automatic_restart(self): ct = os.stat('activity.py').st_ctime def restarter(): if os.stat('activity.py').st_ctime != ct: GLib.timeout_add(233, self.close) logging.error('-- restart --') return False return True GLib.timeout_add(233, restarter)
class DeductoActivity(activity.Activity): """ Logic puzzle game """ def __init__(self, handle): """ Initialize the toolbars and the game board """ activity.Activity.__init__(self, handle) self.nick = profile.get_nick_name() if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self.level = 0 self._correct = 0 self._playing = True self._game_over = False self._python_code = None self._setup_toolbars() self._setup_dispatch_table() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._game = Game(canvas, parent=self, colors=self.colors) self._sharing = False self._initiating = False self.connect('shared', self._shared_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() if 'level' in self.metadata: self.level = int(self.metadata['level']) self.status.set_label(_('Resuming level %d') % (self.level + 1)) self._game.show_random() else: self._game.new_game() def _setup_toolbars(self): """ Setup the toolbars. """ self.max_participants = 4 toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_game_button = button_factory('new-game', self.toolbar, self._new_game_cb, tooltip=_('Start a new game.')) separator_factory(toolbox.toolbar, False, True) self._true_button = button_factory( 'true', self.toolbar, self._true_cb, tooltip=_('The pattern matches the rule.')) self._false_button = button_factory( 'false', self.toolbar, self._false_cb, tooltip=_('The pattern does not match the rule.')) separator_factory(toolbox.toolbar, False, True) self._example_button = button_factory( 'example', self.toolbar, self._example_cb, tooltip=_('Explore some examples.')) self.status = label_factory(self.toolbar, '', width=300) separator_factory(toolbox.toolbar, True, False) self._gear_button = button_factory('view-source', self.toolbar, self._gear_cb, tooltip=_('Load a custom level.')) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _new_game_cb(self, button=None): ''' Start a new game. ''' self._game_over = False self._correct = 0 self.level = 0 if not self._playing: self._example_cb() self._game.new_game() if self._initiating: _logger.debug('sending new game and new grid') self._send_new_game() self._send_new_grid() self.status.set_label(_('Playing level %d') % (self.level + 1)) def _test_for_game_over(self): ''' If we are at maximum levels, the game is over ''' if self.level == self._game.max_levels: self.level = 0 self._game_over = True self.status.set_label(_('Game over.')) else: self.status.set_label(_('Playing level %d') % (self.level + 1)) self._correct = 0 if (not self._sharing) or self._initiating: self._game.show_random() if self._initiating: self._send_new_grid() def _true_cb(self, button=None): ''' Declare pattern true or show an example of a true pattern. ''' if self._game_over: if (not self._sharing) or self._initiating: self.status.set_label(_('Click on new game button to begin.')) else: self.status.set_label( _('Wait for sharer to start ' + 'a new game.')) return if self._playing: if self._game.this_pattern: self._correct += 1 if self._correct == 5: self.level += 1 self._test_for_game_over() self.metadata['level'] = str(self.level) else: self.status.set_label( _('%d correct answers.') % (self._correct)) if (not self._sharing) or self._initiating: self._game.show_random() if self._initiating: self._send_new_grid() else: self.status.set_label(_('Pattern was false.')) self._correct = 0 if (button is not None) and self._sharing: self._send_true_button_click() else: self._game.show_true() def _false_cb(self, button=None): ''' Declare pattern false or show an example of a false pattern. ''' if self._game_over: if (not self._sharing) or self._initiating: self.status.set_label(_('Click on new game button to begin.')) else: self.status.set_label( _('Wait for sharer to start ' + 'a new game.')) return if self._playing: if not self._game.this_pattern: self._correct += 1 if self._correct == 5: self.level += 1 self._test_for_game_over() else: self.status.set_label( _('%d correct answers.') % (self._correct)) if (not self._sharing) or self._initiating: self._game.show_random() if self._initiating: self._send_new_grid() else: self.status.set_label(_('Pattern was true.')) self._correct = 0 if (button is not None) and self._sharing: self._send_false_button_click() else: self._game.show_false() def _example_cb(self, button=None): ''' Show examples or resume play of current level. ''' if self._playing: self._example_button.set_icon_name('resume-play') self._example_button.set_tooltip(_('Resume play')) self._true_button.set_tooltip( _('Show a pattern that matches the rule.')) self._false_button.set_tooltip( _('Show a pattern that does not match the rule.')) Button1 = '☑' Button2 = '☒' self.status.set_label( _("Explore patterns with the {} and {} buttons.".format( Button1, Button2))) self._playing = False else: self._example_button.set_icon_name('example') self._example_button.set_tooltip(_('Explore some examples.')) self._true_button.set_tooltip(_('The pattern matches the rule.')) self._false_button.set_tooltip( _('The pattern does not match the rule.')) self.status.set_label(_('Playing level %d') % (self.level + 1)) self._playing = True self._correct = 0 def _gear_cb(self, button=None): ''' Load a custom level. ''' self.status.set_text( _('Load a "True" pattern generator from the journal')) self._chooser('org.laptop.Pippy', self._load_python_code_from_journal) if self._python_code is None: return LEVELS_TRUE.append(self._python_code) self.status.set_text( _('Load a "False" pattern generator from the journal')) self._chooser('org.laptop.Pippy', self._load_python_code_from_journal) LEVELS_FALSE.append(self._python_code) if self._python_code is None: return self.status.set_text(_('New level added')) self._game.max_levels += 1 def _load_python_code_from_journal(self, dsobject): ''' Read the Python code from the Journal object ''' self._python_code = None try: _logger.debug("opening %s " % dsobject.file_path) file_handle = open(dsobject.file_path, "r") self._python_code = file_handle.read() file_handle.close() except IOError: _logger.debug("couldn't open %s" % dsobject.file_path) def _chooser(self, filter, action): ''' Choose an object from the datastore and take some action ''' chooser = None try: chooser = ObjectChooser(parent=self, what_filter=filter) except TypeError: chooser = ObjectChooser( None, self, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: dsobject = chooser.get_selected_object() action(dsobject) dsobject.destroy() finally: chooser.destroy() del chooser # Collaboration-related methods # The sharer sends patterns and everyone shares whatever vote is # cast first among all the sharer and joiners. def set_data(self, data): pass def get_data(self): return None def _shared_cb(self, activity): ''' Either set up initial share...''' self.after_share_join(True) def _joined_cb(self, activity): ''' ...or join an exisiting share. ''' self.after_share_join(True) def after_share_join(self, sharer): self.waiting_for_hand = not sharer self._initiating = sharer self._sharing = True def _setup_dispatch_table(self): ''' Associate tokens with commands. ''' self._processing_methods = { 'new_game': [self._receive_new_game, 'new game'], 'new_grid': [self._receive_new_grid, 'get a new grid'], 'true': [self._receive_true_button_click, 'get a true button press'], 'false': [ self._receive_false_button_click, 'get a false ' + 'button press' ], } def _message_cb(self, collab, buddy, msg): command = msg.get('command') payload = msg.get('payload') self._processing_methods[command][0](payload) def _send_new_game(self): ''' Send a new game message to all players ''' self._send_event('new_game', ' ') def _receive_new_game(self, payload): ''' Receive a new game notification from the sharer. ''' self._game_over = False self._correct = 0 self.level = 0 if not self._playing: self._example_cb() self.status.set_label(_('Playing level %d') % (self.level + 1)) def _send_new_grid(self): ''' Send a new grid to all players ''' self._send_event('new_grid', self._game.save_grid()) def _receive_new_grid(self, payload): ''' Receive a grid from the sharer. ''' (dot_list, boolean, colors) = payload self._game.restore_grid(dot_list, boolean, colors) def _send_true_button_click(self): ''' Send a true click to all the players ''' self._send_event('true') def _receive_true_button_click(self, payload): ''' When a button is clicked, everyone should react. ''' self._playing = True self._true_cb() def _send_false_button_click(self): ''' Send a false click to all the players ''' self._send_event('false') def _receive_false_button_click(self, payload): ''' When a button is clicked, everyone should react. ''' self._playing = True self._false_cb() def _send_event(self, command, payload=None): self._collab.post({'command': command, 'payload': payload})
class YupanaActivity(activity.Activity): """ Yupana counting device """ def __init__(self, handle): """ Initialize the toolbars and the yupana """ activity.Activity.__init__(self, handle) self.nick = profile.get_nick_name() self._reload_custom = False if profile.get_color() is not None: self.colors = profile.get_color().to_string().split(',') else: self.colors = ['#A0FFA0', '#FF8080'] self._setup_toolbars() # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), \ Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() self._yupana = Yupana(canvas, parent=self, colors=self.colors) self._setup_collab() if 'dotlist' in self.metadata: self._restore() else: self._yupana.new_yupana(mode='ten') self._make_custom_toolbar() if self._reload_custom: self._custom_cb() def _setup_toolbars(self): """ Setup the toolbars. """ self.max_participants = 4 yupana_toolbar = Gtk.Toolbar() self.custom_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() yupana_toolbar_button = ToolbarButton(label=_("Mode"), page=yupana_toolbar, icon_name='preferences-system') yupana_toolbar.show() toolbox.toolbar.insert(yupana_toolbar_button, -1) yupana_toolbar_button.show() custom_toolbar_button = ToolbarButton(label=_("Custom"), page=self.custom_toolbar, icon_name='view-source') self.custom_toolbar.show() toolbox.toolbar.insert(custom_toolbar_button, -1) custom_toolbar_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_yupana_button = button_factory( 'edit-delete', self.toolbar, self._new_yupana_cb, tooltip=_('Clear the yupana.')) separator_factory(yupana_toolbar, False, True) self.ten_button = radio_factory('ten', yupana_toolbar, self._ten_cb, tooltip=_('decimal mode'), group=None) self.twenty_button = radio_factory('twenty', yupana_toolbar, self._twenty_cb, tooltip=_('base-twenty mode'), group=self.ten_button) self.factor_button = radio_factory('factor', yupana_toolbar, self._factor_cb, tooltip=_('prime-factor mode'), group=self.ten_button) self.fibonacci_button = radio_factory('fibonacci', yupana_toolbar, self._fibonacci_cb, tooltip=_('Fibonacci mode'), group=self.ten_button) self.custom_button = radio_factory('view-source', yupana_toolbar, self._custom_cb, tooltip=_('custom mode'), group=self.ten_button) separator_factory(self.toolbar, False, False) self.status = label_factory(self.toolbar, '', width=200) self.status.set_label(_('decimal mode')) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _make_custom_toolbar(self): self._ones = entry_factory(str(self._yupana.custom[0]), self.custom_toolbar, tooltip=_('one row')) self._twos = entry_factory(str(self._yupana.custom[1]), self.custom_toolbar, tooltip=_('two row')) self._threes = entry_factory(str(self._yupana.custom[2]), self.custom_toolbar, tooltip=_('three row')) self._fives = entry_factory(str(self._yupana.custom[3]), self.custom_toolbar, tooltip=_('five row')) separator_factory(self.custom_toolbar, False, True) self._base = entry_factory(str(self._yupana.custom[4]), self.custom_toolbar, tooltip=_('base')) separator_factory(self.custom_toolbar, False, True) button_factory('view-refresh', self.custom_toolbar, self._custom_cb, tooltip=_('Reload custom values.')) def _new_yupana_cb(self, button=None): ''' Start a new yupana. ''' self._yupana.new_yupana() def _ten_cb(self, button=None): self._yupana.new_yupana(mode='ten') self.status.set_label(_('decimal mode')) def _twenty_cb(self, button=None): self._yupana.new_yupana(mode='twenty') self.status.set_label(_('base-twenty mode')) def _factor_cb(self, button=None): self._yupana.new_yupana(mode='factor') self.status.set_label(_('prime-factor mode')) def _fibonacci_cb(self, button=None): self._yupana.new_yupana(mode='fibonacci') self.status.set_label(_('Fibonacci mode')) def _custom_cb(self, button=None): if hasattr(self, '_ones'): self._yupana.custom[0] = int(self._ones.get_text()) self._yupana.custom[1] = int(self._twos.get_text()) self._yupana.custom[2] = int(self._threes.get_text()) self._yupana.custom[3] = int(self._fives.get_text()) self._yupana.custom[4] = int(self._base.get_text()) self._reload_custom = False else: self._reload_custom = True self._yupana.new_yupana(mode='custom') self.status.set_label(_('custom mode')) def write_file(self, file_path): """ Write the grid status to the Journal """ [mode, dot_list] = self._yupana.save_yupana() self.metadata['mode'] = mode self.metadata['custom'] = '' for i in range(5): self.metadata['custom'] += str(self._yupana.custom[i]) self.metadata['custom'] += ' ' self.metadata['dotlist'] = '' for dot in dot_list: self.metadata['dotlist'] += str(dot) if dot_list.index(dot) < len(dot_list) - 1: self.metadata['dotlist'] += ' ' self.metadata['label'] = self._yupana.get_label() def _restore(self): """ Restore the yupana state from metadata """ if 'custom' in self.metadata: values = self.metadata['custom'].split() for i in range(5): self._yupana.custom[i] = int(values[i]) if 'mode' in self.metadata: if self.metadata['mode'] == 'ten': self.ten_button.set_active(True) elif self.metadata['mode'] == 'twenty': self.twenty_button.set_active(True) elif self.metadata['mode'] == 'factor': self.factor_button.set_active(True) elif self.metadata['mode'] == 'fibonacci': self.fibonacci_button.set_active(True) else: self.custom_button.set_active(True) if 'dotlist' in self.metadata: dot_list = [] dots = self.metadata['dotlist'].split() for dot in dots: dot_list.append(int(dot)) self._yupana.restore_yupana(self.metadata['mode'], dot_list) self._yupana.set_label(self.metadata['label']) # Collaboration-related methods def _setup_collab(self): """ Setup the Presence Service. """ self.initiating = None # sharing (True) or joining (False) self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) self._collab = CollabWrapper(self) self._collab.connect('message', self._message_cb) self._collab.connect('joined', self._joined_cb) self._collab.setup() def set_data(self, data): pass def get_data(self): return None def _shared_cb(self, activity): """ Either set up initial share...""" self.after_share_join(True) def _joined_cb(self, activity): """ ...or join an exisiting share. """ self.after_share_join(False) def after_share_join(self, sharer): """ Joining and sharing are mostly the same... """ self.initiating = sharer self.waiting_for_hand = not sharer self._yupana.set_sharing(True) def _message_cb(self, collab, buddy, msg): command = msg.get('command') payload = msg.get('payload') if command == 'new_game': '''Get a new yupana grid''' self._receive_new_yupana(payload) elif command == 'played': '''Get a dot click''' self._receive_dot_click(payload) elif command == 'label': self._yupana.set_label(payload) def send_new_yupana(self): ''' Send a new orientation, grid to all players ''' self._collab.post( dict(command='new_game', payload=json_dump(self._yupana.save_yupana()))) def _receive_new_yupana(self, payload): ''' Sharer can start a new yupana. ''' mode, dot_list = json_load(payload) self._yupana.restore_yupana(mode, dot_list) def send_dot_click(self, dot, color): ''' Send a dot click to all the players ''' self._collab.post( dict(command='played', payload=json_dump([dot, color]))) def _receive_dot_click(self, payload): ''' When a dot is clicked, everyone should change its color. ''' (dot, color) = json_load(payload) self._yupana.remote_button_press(dot, color) def send_label(self, label): self._collab.post(dict(command='label', payload=label))
class MemorizeActivity(Activity): def __init__(self, handle): Activity.__init__(self, handle) self.play_mode = None toolbar_box = ToolbarBox() self.set_toolbar_box(toolbar_box) self.activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(self.activity_button, -1) self._memorizeToolbarBuilder = \ memorizetoolbar.MemorizeToolbarBuilder(self) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) self._edit_button = ToggleToolButton('view-source') self._edit_button.set_tooltip(_('Edit game')) self._edit_button.set_active(False) toolbar_box.toolbar.insert(self._edit_button, -1) self._createToolbarBuilder = \ createtoolbar.CreateToolbarBuilder(self) separator = Gtk.SeparatorToolItem() separator.set_expand(True) separator.set_draw(False) separator.set_size_request(0, -1) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) self.game = game.MemorizeGame() # Play game mode self.table = cardtable.CardTable() self.scoreboard = scoreboard.Scoreboard() self.cardlist = cardlist.CardList() self.createcardpanel = createcardpanel.CreateCardPanel(self.game) self.cardlist.connect('pair-selected', self.createcardpanel.pair_selected) self.cardlist.connect('update-create-toolbar', self._createToolbarBuilder.update_create_toolbar) self.createcardpanel.connect('add-pair', self.cardlist.add_pair) self.createcardpanel.connect('update-pair', self.cardlist.update_selected) self.createcardpanel.connect('change-font', self.cardlist.change_font) self.createcardpanel.connect('pair-closed', self.cardlist.rem_current_pair) self._createToolbarBuilder.connect('create_new_game', self.cardlist.clean_list) self._createToolbarBuilder.connect('create_new_game', self.createcardpanel.clean) self._createToolbarBuilder.connect('create_new_game', self._memorizeToolbarBuilder.reset) self._createToolbarBuilder.connect('create_equal_pairs', self.change_equal_pairs) self._edit_button.connect('toggled', self._change_mode_bt) self.connect('key-press-event', self.table.key_press_event) self.table.connect('card-flipped', self.game.card_flipped) self.table.connect('card-highlighted', self.game.card_highlighted) self.game.connect('set-border', self.table.set_border) self.game.connect('flop-card', self.table.flop_card) self.game.connect('flip-card', self.table.flip_card) self.game.connect('cement-card', self.table.cement_card) self.game.connect('highlight-card', self.table.highlight_card) self.game.connect('load_mode', self.table.load_msg) self.game.connect('msg_buddy', self.scoreboard.set_buddy_message) self.game.connect('add_buddy', self.scoreboard.add_buddy) self.game.connect('rem_buddy', self.scoreboard.rem_buddy) self.game.connect('increase-score', self.scoreboard.increase_score) self.game.connect('wait_mode_buddy', self.scoreboard.set_wait_mode) self.game.connect('change-turn', self.scoreboard.set_selected) self.game.connect('change_game', self.scoreboard.change_game) self.game.connect('reset_scoreboard', self.scoreboard.reset) self.game.connect('reset_table', self.table.reset) self.game.connect('load_game', self.table.load_game) self.game.connect('change_game', self.table.change_game) self.game.connect('load_game', self._memorizeToolbarBuilder.update_toolbar) self.game.connect('change_game', self._memorizeToolbarBuilder.update_toolbar) self.game.connect('change_game', self.createcardpanel.update_font_combos) self._memorizeToolbarBuilder.connect('game_changed', self.change_game) self.box = Gtk.HBox(orientation=Gtk.Orientation.VERTICAL, homogeneous=False) width = Gdk.Screen.width() height = Gdk.Screen.height() - style.GRID_CELL_SIZE self.table.resize(width, height - style.GRID_CELL_SIZE) self.scoreboard.set_size_request(-1, style.GRID_CELL_SIZE) self.set_canvas(self.box) # connect to the in/out events of the memorize activity self.connect('focus_in_event', self._focus_in) self.connect('focus_out_event', self._focus_out) self.connect('destroy', self._cleanup_cb) self.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self.connect('motion_notify_event', lambda widget, event: face.look_at()) Gdk.Screen.get_default().connect('size-changed', self.__configure_cb) # start on the game toolbar, might change this # to the create toolbar later self._change_mode(_MODE_PLAY) def on_activity_joined_cb(me): logging.debug('activity joined') self.game.add_buddy(self._collab.props.owner) self.connect('joined', on_activity_joined_cb) def on_activity_shared_cb(me): logging.debug('activity shared') self.connect('shared', on_activity_shared_cb) self._collab = CollabWrapper(self) self.game.set_myself(self._collab.props.owner) def on_message_cb(collab, buddy, msg): logging.debug('on_message_cb buddy %r msg %r' % (buddy, msg)) action = msg.get('action') if action == 'flip': n = msg.get('n') self.game.card_flipped(None, n, True) elif action == 'change': self.get_canvas().hide() def momentary_blank_timeout_cb(): self.set_data(msg) self.get_canvas().show() GLib.timeout_add(100, momentary_blank_timeout_cb) self._collab.connect('message', on_message_cb) def on_joined_cb(collab, msg): logging.debug('joined') self._collab.connect('joined', on_joined_cb, 'joined') def on_buddy_joined_cb(collab, buddy, msg): logging.debug('on_buddy_joined_cb buddy %r msg %r' % (buddy, msg)) self.game.add_buddy(buddy) self._collab.connect('buddy_joined', on_buddy_joined_cb, 'buddy_joined') def on_buddy_left_cb(collab, buddy, msg): logging.debug('on_buddy_left_cb buddy %r msg %r' % (buddy, msg)) self.game.rem_buddy(buddy) self._collab.connect('buddy_left', on_buddy_left_cb, 'buddy_left') self._files = {} # local temporary copies of shared games self._collab.setup() def on_flip_card_cb(game, n): logging.debug('on_flip_card_cb n %r' % (n)) self._collab.post({'action': 'flip', 'n': n}) self.game.connect('flip-card-signal', on_flip_card_cb) def on_change_game_cb(sender, mode, grid, data, waiting_list, zip): logging.debug('on_change_game_cb') blob = self.get_data() blob['action'] = 'change' self._collab.post(blob) self.game.connect('change_game_signal', on_change_game_cb) if self._collab.props.leader: logging.debug('is leader') game_file = os.path.join(os.path.dirname(__file__), 'demos', 'addition.zip') self.game.load_game(game_file, 4, 'demo') self.game.add_buddy(self._collab.props.owner) self.show_all() def __configure_cb(self, event): ''' Screen size has changed ''' width = Gdk.Screen.width() height = Gdk.Screen.height() - style.GRID_CELL_SIZE self.box.set_size_request(width, height) self.scoreboard.set_size_request(-1, style.GRID_CELL_SIZE) self.table.resize(width, height - style.GRID_CELL_SIZE) self.show_all() def _change_mode_bt(self, button): if button.get_active(): self._change_mode(_MODE_CREATE) button.set_icon_name('player_play') button.set_tooltip(_('Play game')) else: self._change_mode(_MODE_PLAY) button.set_icon_name('view-source') button.set_tooltip(_('Edit game')) def _change_game_receiver(self, mode, grid, data, path): if mode == 'demo': game_name = os.path.basename(data.get('game_file', 'debug-demo')) game_file = os.path.join(os.path.dirname(__file__), 'demos', game_name).encode('ascii') self.game.model.read(game_file) if mode == 'art4apps': game_file = data['game_file'] category = game_file[:game_file.find('_')] language = data['language'] self.game.model.is_demo = True self.game.model.read_art4apps(category, language) if mode == 'file': self.game.model.read(self._files[path]) if 'path' in self.game.model.data: data['path'] = self.game.model.data['path'] data['pathimg'] = self.game.model.data['pathimg'] data['pathsnd'] = self.game.model.data['pathsnd'] self.game.load_remote(grid, data, mode, True) def set_data(self, blob): logging.debug("set_data %r" % list(blob.keys())) grid = blob['grid'] data = blob['data'] current_player = blob['current'] path = blob['path'] if 'zip' in blob: tmp_root = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'instance') temp_dir = tempfile.mkdtemp(dir=tmp_root) os.chmod(temp_dir, 0o777) temp_file = os.path.join(temp_dir, 'game.zip') self._files[path] = temp_file f = open(temp_file, 'wb') f.write(base64.b64decode(blob['zip'])) f.close() self._change_game_receiver(data['mode'], grid, data, path) for i in range(len(self.game.players)): self.game.increase_point(self.game.players[i], int(data.get(str(i), '0'))) self.game.current_player = self.game.players[current_player] self.game.update_turn() def get_data(self): data = self.game.collect_data() path = data['game_file'] blob = { "grid": self.game.get_grid(), "data": data, "current": self.game.players.index(self.game.current_player), "path": path } if data['mode'] == 'file': blob['zip'] = base64.b64encode(open(path, 'rb').read()).decode() logging.debug("get_data %r" % list(blob.keys())) return blob def read_file(self, file_path): if 'icon-color' in self.metadata: color = self.metadata['icon-color'] else: color = profile.get_color().to_string() self.change_game(None, file_path, 4, 'file', self.metadata['title'], color) def write_file(self, file_path): logging.debug('WRITE_FILE is_demo %s', self.game.model.is_demo) if self.game.model.is_demo: # if is a demo game only want keep the metadata self._jobject.set_file_path(None) raise NotImplementedError return if self.cardlist.pair_list_modified: self.cardlist.update_model(self.game.model) temp_img_folder = self.game.model.data['pathimg'] temp_snd_folder = self.game.model.data['pathsnd'] self.game.model.create_temp_directories() game_zip = zipfile.ZipFile(file_path, 'w') save_image_and_sound = True if 'origin' in self.game.model.data: if self.game.model.data['origin'] == 'art4apps': # we don't need save images and audio files # for art4apps games save_image_and_sound = False if save_image_and_sound: for pair in self.game.model.pairs: # aimg aimg = self.game.model.pairs[pair].get_property('aimg') if aimg is not None: game_zip.write(os.path.join(temp_img_folder, aimg), os.path.join('images', aimg)) # bimg bimg = self.game.model.pairs[pair].get_property('bimg') if bimg is not None: game_zip.write(os.path.join(temp_img_folder, bimg), os.path.join('images', bimg)) # asnd asnd = self.game.model.pairs[pair].get_property('asnd') if asnd is not None: if os.path.exists(os.path.join(temp_snd_folder, asnd)): game_zip.write(os.path.join(temp_snd_folder, asnd), os.path.join('sounds', asnd)) # bsnd bsnd = self.game.model.pairs[pair].get_property('bsnd') if bsnd is not None: if os.path.exists(os.path.join(temp_snd_folder, bsnd)): game_zip.write(os.path.join(temp_snd_folder, bsnd), os.path.join('sounds', bsnd)) self.game.model.game_path = self.game.model.temp_folder self.game.model.data['name'] = str(self.get_title()) self.game.model.write() game_zip.write(os.path.join(self.game.model.temp_folder, 'game.xml'), 'game.xml') game_zip.close() self.metadata['mime_type'] = 'application/x-memorize-project' def _complete_close(self): self._remove_temp_files() Activity._complete_close(self) def _remove_temp_files(self): tmp_root = os.path.join(os.environ['SUGAR_ACTIVITY_ROOT'], 'instance') for root, dirs, files in os.walk(tmp_root, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) def _change_mode(self, mode): logging.debug("Change mode %s" % mode) if mode == _MODE_CREATE: if self.play_mode: self.box.remove(self.scoreboard) self.box.remove(self.table) self.createcardpanel.update_orientation() self.box.pack_start(self.createcardpanel, True, True, 0) self.box.pack_start(self.cardlist, False, False, 0) self.cardlist.load_game(self.game) self.game.model.create_temp_directories() self.createcardpanel.set_temp_folder( self.game.model.temp_folder) self.play_mode = False else: if self.game.model.modified: self.cardlist.update_model(self.game.model) self.save() self.game.reset_game() self.table.change_game(None, self.game.model.data, self.game.model.grid) self.game.model.modified = False if not self.play_mode: self.box.remove(self.createcardpanel) self.box.remove(self.cardlist) if self.play_mode in (False, None): self.box.pack_start(self.table, True, True, 0) self.box.pack_start(self.scoreboard, False, False, 0) self.play_mode = True self._memorizeToolbarBuilder.update_controls(mode == _MODE_PLAY) self._createToolbarBuilder.update_controls(mode == _MODE_CREATE) def change_game(self, widget, game_name, size, mode, title=None, color=None): logging.debug('Change game %s', game_name) self.game.change_game(widget, game_name, size, mode, title, color) self.cardlist.game_loaded = False def change_equal_pairs(self, widget, state): self.cardlist.update_model(self.game.model) self.createcardpanel.change_equal_pairs(widget, state) def _focus_in(self, event, data=None): self.game.audio.play() def _focus_out(self, event, data=None): self.game.audio.pause() def _cleanup_cb(self, data=None): self.game.audio.stop()