From b148f9923dd40e35c972c93ef1ac03e431a4b65c Mon Sep 17 00:00:00 2001 From: Simon Schampijer Date: Wed, 04 Jul 2007 20:24:56 +0000 Subject: - Moved things in a cleaner MVC. - switched in the collaboration code to elect a leader and do total ordering - Added the NEWS file --- diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/NEWS diff --git a/controller.py b/controller.py new file mode 100644 index 0000000..dc446aa --- /dev/null +++ b/controller.py @@ -0,0 +1,206 @@ +import logging + +import gobject +import gtk +import os + +from dbus import Interface +from dbus.service import method, signal +from dbus.gobject_service import ExportedGObject + +from model import Model +from gamestate import GameState + +# XXX: I'm not convinced this is in the right namespace +SERVICE = "org.freedesktop.Telepathy.Tube.Memosono" +IFACE = SERVICE +PATH = "/org/freedesktop/Telepathy/Tube/Memosono" + + + +GAME_PATH = os.path.join(os.path.dirname(__file__),'games/drumgit') + +_logger = logging.getLogger('memosono-activity.game') + + +class Controller(ExportedGObject): + ''' Networked Controller which is the core component of the activity. It + handles the communication with the components (model, view) and with the + other players over the network. + ''' + def __init__(self, tube, playview, is_initiator, buddies_panel, info_panel, + owner, get_buddy, activity): + super(Controller, self).__init__(tube, PATH) + self.tube = tube + self.pv = playview + self.is_initiator = is_initiator + self.entered = False + self.buddies_panel = buddies_panel + self.info_panel = info_panel + self.owner = owner + self._get_buddy = get_buddy + self.activity = activity + + self.model = None + self.playerid = None + self.turn = 0 + # index 0 is the master + self.players = [] + self.started = 0 + self.count = 0 + if self.is_initiator: + self.gs = GameState() + for tile in self.pv.tiles: + tile.connect('button-press-event', self._button_press_cb, self.pv.tiles.index(tile)) + + self.tube.watch_participants(self.participant_change_cb) + + def participant_change_cb(self, added, removed): + # Initiator is player 0, other player is player 1. + + _logger.debug('adding participants: %r', added) + _logger.debug('removing participants: %r', removed) + + for handle, bus_name in added: + buddy = self._get_buddy(handle) + _logger.debug('Buddy %r was added', buddy) + if buddy is not None: + if len(self.players) < 2: + self.buddies_panel.add_player(buddy) + self.players.append(self.tube.participants[handle]) + if self.is_initiator: + self.gs.points[self.tube.participants[handle]] = 0 + _logger.debug('MA: points of players: %s', self.gs.points) + _logger.debug('MA: list of players: %s', self.players) + else: + self.buddies_panel.add_watcher(buddy) + + for handle in removed: + buddy = self._get_buddy(handle) + _logger.debug('Buddy %r was removed', buddy) + if buddy is not None: + self.buddies_panel.remove_watcher(buddy) + try: + self.players.remove(self.tube.participants[handle]) + except ValueError: + # already absent + pass + + if not self.entered: + if self.is_initiator: + _logger.debug('I am the initiator, so making myself the leader of the game.') + self.init_game() + self.playerid = self.tube.get_unique_name() + self.tube.add_signal_receiver(self.info_cb, 'Info', IFACE, + path=PATH, sender_keyword='sender') + self.tube.add_signal_receiver(self.turn_cb, 'Turn', IFACE, + path=PATH, sender_keyword='sender') + self.tube.add_signal_receiver(self.flip_cb, 'Flip', IFACE, + path=PATH, sender_keyword='sender') + self.tube.add_signal_receiver(self.points_cb, 'Points', IFACE, + path=PATH, sender_keyword='sender') + self.entered = True + + if self.is_initiator: + if len(self.players) == 2 and self.started == 0: + _logger.debug('Start the game.') + self.Info('Start the game') + self.started = 1 + self.Turn(self.players[self.gs.player_active]) + + def init_game(self): + self.model = Model(GAME_PATH, os.path.dirname(__file__)) + self.model.read('drumgit.mson') + self.model.def_grid() + + self.tube.add_signal_receiver(self.selected_cb, 'Selected', IFACE, + path=PATH, sender_keyword='sender') + + @signal(dbus_interface=IFACE, signature='n') + def Selected(self, tilenum): + """Signal that a tile has been selected""" + + def selected_cb(self, tilenum, sender=None): + _logger.debug('MA: %s flipped tile %d', sender, tilenum) + obj, color = self.model.gettile(tilenum) + self.Flip(tilenum, obj, color) + + self.count+=1 + if self.count == 1: + self.gs.selected = tilenum + if self.count == 2: + self.count = 0 + # evaluate + if( self.model.same(tilenum, self.gs.selected) == 1): + _logger.debug('MA: Tile(%d) and (%d) are the same', tilenum, self.gs.selected) + self.gs.points[sender]+=1 + self.Points(sender, self.gs.points[sender]) + self.info_panel.show('Open another one') + else: + gobject.timeout_add(2000, self._turn_back, tilenum, self.gs.selected) + _logger.debug('Tile(%d) and (%d) are NOT the same', tilenum, self.gs.selected) + # next player + self.change_turn() + + def _turn_back(self, tilenuma, tilenumb): + self.Flip(tilenuma, 'images/black.png', 100) + self.Flip(tilenumb, 'images/black.png', 100) + return False + + def change_turn(self): + if self.gs.player_active == 0: + self.gs.player_active = 1 + else: + self.gs.player_active = 0 + + self.Turn(self.players[self.gs.player_active]) + + + @signal(dbus_interface=IFACE, signature='nsn') + def Flip(self, tilenum, obj, color): + """Signal that a tile will be flipped""" + + def flip_cb(self, tilenum, obj, color, sender=None): + handle = self.tube.bus_name_to_handle[sender] + _logger.debug('Flipped tile(%d) from %s', tilenum, sender) + self.pv.flip(tilenum, os.path.join(os.path.dirname(__file__), obj), color) + + + @signal(dbus_interface=IFACE, signature='s') + def Turn(self, playerid): + """Signal that it is the players turn""" + + def turn_cb(self, playerid, sender=None): + if self.playerid == playerid: + self.turn = 1 + self.info_panel.show('It is my turn') + else: + self.turn = 0 + + + @signal(dbus_interface=IFACE, signature='sn') + def Points(self, player, points): + """Signal to update the points""" + + def points_cb(self, player, points, sender=None): + handle = self.tube.bus_name_to_handle[player] + buddy = self._get_buddy(handle) + self.buddies_panel.set_count(buddy, points) + + + @signal(dbus_interface=IFACE, signature='s') + def Info(self, msg): + """Signal that there is some game information""" + + def info_cb(self, msg, sender=None): + self.info_panel.show(msg) + + + def _button_press_cb(self, tile, event, tilenum=None): + if self.turn == 1: + self.Selected(tilenum) + else: + _logger.debug('Not my turn') + self.info_panel.show('Not my turn') + + diff --git a/game.py b/game.py deleted file mode 100644 index 236b908..0000000 --- a/game.py +++ /dev/null @@ -1,217 +0,0 @@ -import logging - -import gobject -import gtk -import os - -from dbus import Interface -from dbus.service import method, signal -from dbus.gobject_service import ExportedGObject - - -# XXX: I'm not convinced this is in the right namespace -SERVICE = "org.freedesktop.Telepathy.Tube.Memosono" -IFACE = SERVICE -PATH = "/org/freedesktop/Telepathy/Tube/Memosono" - - -_logger = logging.getLogger('memosono-activity.game') - - -class ConnectGame(ExportedGObject): - - def __init__(self, tube, playview, model, is_initiator, buddies_panel, info_panel, - owner, get_buddy, activity): - super(ConnectGame, self).__init__(tube, PATH) - self.tube = tube - self.pv = playview - self.model = model - self.is_initiator = is_initiator - self.entered = False - self.player_id = None - self.buddies_panel = buddies_panel - self.info_panel = info_panel - self.owner = owner - self._get_buddy = get_buddy - self.activity = activity - - self.active_player = 1 - self.count = 0 - self.points = {} - - # list indexed by player ID - # 0, 1 are players 0, 1 - # 2+ are the spectator queue, 2 is to play next - self.ordered_bus_names = [] - - for tile in self.pv.tiles: - tile.connect('button-press-event', self._button_press_cb, self.pv.tiles.index(tile)) - - self.tube.watch_participants(self.participant_change_cb) - - def participant_change_cb(self, added, removed): - # Initiator is player 0, other player is player 1. - - _logger.debug('adding participants: %r', added) - _logger.debug('removing participants: %r', removed) - - for handle, bus_name in added: - buddy = self._get_buddy(handle) - _logger.debug('Buddy %r was added', buddy) - if buddy is not None: - self.buddies_panel.add_watcher(buddy) - - for handle in removed: - buddy = self._get_buddy(handle) - _logger.debug('Buddy %r was removed', buddy) - if buddy is not None: - self.buddies_panel.remove_watcher(buddy) - try: - self.ordered_bus_names.remove(self.tube.participants[handle]) - except ValueError: - # already absent - pass - - if not self.entered: - self.tube.add_signal_receiver(self.flip_cb, 'Flip', IFACE, - path=PATH, sender_keyword='sender') - if self.is_initiator: - _logger.debug('I am the initiator, so making myself player 0') - self.add_hello_handler() - self.ordered_bus_names = [self.tube.get_unique_name()] - self.player_id = 0 - self.points[self.player_id] = 0 - self.buddies_panel.add_player(self.owner) - else: - _logger.debug('Hello, everyone! What did I miss?') - self.Hello() - self.entered = True - - @signal(dbus_interface=IFACE, signature='') - def Hello(self): - """Request that this player's Welcome method is called to bring it - up to date with the game state. - """ - - @method(dbus_interface=IFACE, in_signature='aanas', out_signature='') - def Welcome(self, grid, bus_names): - """To be called on the incoming player by the other players to - inform them of the game state. - - FIXME: nominate a "referee" (initially the initiator) responsible - for saying Welcome, elect a new referee when the current referee - leaves? This could also be used to make the protocol robust against - cheating/bugs - """ - if self.player_id is None: - _logger.debug('Welcomed to the game. Player bus names are %r', - bus_names) - _logger.debug('Received the grid: %s', str(grid)) - self.model.grid = grid - - self.ordered_bus_names = bus_names - self.player_id = bus_names.index(self.tube.get_unique_name()) - self.points[self.player_id] = 0 - # OK, now I'm synched with the game, I can welcome others - self.add_hello_handler() - - buddy = self._get_buddy(self.tube.bus_name_to_handle[bus_names[0]]) - self.buddies_panel.add_player(buddy) - buddy = self._get_buddy(self.tube.bus_name_to_handle[bus_names[1]]) - self.buddies_panel.add_player(buddy) - - if self.active_player == self.player_id: - _logger.debug("It's my turn already!") - self.change_turn() - else: - _logger.debug("I've already been welcomed, doing nothing") - - def add_hello_handler(self): - self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE, - path=PATH, sender_keyword='sender') - - @signal(dbus_interface=IFACE, signature='nsn') - def Flip(self, tilenum, obj, color): - """Signal that the local player has flipped a tile.""" - - def hello_cb(self, sender=None): - """Tell the newcomer what's going on.""" - _logger.debug('Newcomer %s has joined', sender) - self.ordered_bus_names.append(sender) - if len(self.ordered_bus_names) == 2: - buddy = self._get_buddy(self.tube.bus_name_to_handle[sender]) - self.buddies_panel.add_player(buddy) - _logger.debug('Bus names are now: %r', self.ordered_bus_names) - _logger.debug('Welcoming newcomer and sending them the game state') - grid = 0 - self.tube.get_object(sender, PATH).Welcome(self.model.grid, - self.ordered_bus_names, - dbus_interface=IFACE) - _logger.debug('--- After welcome') - if (self.player_id == 0 and len(self.ordered_bus_names) == 2): - _logger.debug("This is my game and an opponent has joined. " - "I go first") - self.change_turn() - - def flip_cb(self, tilenum, obj, color, sender=None): - handle = self.tube.bus_name_to_handle[sender] - _logger.debug('Flipped tile(%d) from %s', tilenum, sender) - - self.pv.flip(tilenum, obj, color) - - self.count+=1 - if self.count == 1: - self.comp = tilenum - if self.count == 2: - self.count = 0 - # evaluate - if( self.model.same(tilenum, self.comp) == 1): - _logger.debug('Tile(%d) and (%d) are the same', tilenum, self.comp) - buddy = self._get_buddy(handle) - self.points[self.active_player]+=1 - self.buddies_panel.set_count(buddy, self.points[self.active_player]) - self.info_panel.show('Open another one') - else: - gobject.timeout_add(2000, self._turn_back, tilenum, self.comp) - _logger.debug('Tile(%d) and (%d) are NOT the same', tilenum, self.comp) - # next player - self.change_turn() - - def _turn_back(self, tilenuma, tilenumb): - self.pv.flip(tilenuma, os.path.join(os.path.dirname(__file__), 'images/black.png'), 100) - self.pv.flip(tilenumb, os.path.join(os.path.dirname(__file__), 'images/black.png'), 100) - return False - - def change_turn(self): - self.set_active_player() - try: - bus_name = self.ordered_bus_names[self.active_player] - buddy = self._get_buddy(self.tube.bus_name_to_handle[bus_name]) - self.buddies_panel.set_is_playing(buddy) - except: - _logger.error('argh!', exc_info=1) - raise - - if self.active_player == self.player_id: - _logger.debug('It\'s my turn now') - self.info_panel.show('Your turn') - self.activity.grab_focus() - else: - _logger.debug('It\'s not my turn') - self.info_panel.show('Other player\'s turn') - - - def set_active_player(self): - if self.active_player == 0: - self.active_player = 1 - else: - self.active_player = 0 - - def _button_press_cb(self, tile, event, tilenum=None): - if self.active_player != self.player_id: - _logger.debug('Ignoring flip - not my turn') - else: - _logger.debug('selected tile=%s'%str(tilenum)) - obj, color = self.model.gettile(tilenum) - self.Flip(tilenum, obj, color) - diff --git a/gamestate.py b/gamestate.py new file mode 100644 index 0000000..a3e40c9 --- /dev/null +++ b/gamestate.py @@ -0,0 +1,13 @@ + + +class GameState(object): + ''' Used by the leader of the game to keep track of the game state + ''' + + def __init__(self): + self.player_active = 0 + self.points = {} + self.turn = 0 + self.selected = 0 + + diff --git a/memosonoactivity.py b/memosonoactivity.py index 416c3aa..3bdc63f 100755 --- a/memosonoactivity.py +++ b/memosonoactivity.py @@ -36,11 +36,9 @@ from tubeconn import TubeConnection from playview import PlayView from buddiespanel import BuddiesPanel from infopanel import InfoPanel -from model import Model -from game import ConnectGame +from controller import Controller -GAME_PATH = os.path.join(os.path.dirname(__file__),'games/drumgit') class MemosonoActivity(Activity): def __init__(self, handle): @@ -49,12 +47,8 @@ class MemosonoActivity(Activity): logging.debug('Starting Memosono activity...') self.set_title(_('Memsosono Activity')) - - self.model = Model(GAME_PATH, os.path.dirname(__file__)) - self.model.read('drumgit.mson') - self.model.def_grid() - self.pv = PlayView( len(self.model.grid) ) + self.pv = PlayView( 6 ) self.buddies_panel = BuddiesPanel() @@ -90,7 +84,7 @@ class MemosonoActivity(Activity): self.conn = telepathy.client.Connection(name, path) self.initiating = None - self.game = None + self.ctrl = None toolbox = ActivityToolbox(self) self.set_toolbox(toolbox) @@ -214,7 +208,7 @@ class MemosonoActivity(Activity): logging.error('ListTubes() failed: %s', e) def _joined_cb(self, activity): - if self.game is not None: + if self.ctrl is not None: return if not self._shared_activity: @@ -238,7 +232,7 @@ class MemosonoActivity(Activity): 'params=%r state=%d', id, initiator, type, service, params, state) - if (self.game is None and type == telepathy.TUBE_TYPE_DBUS and + if (self.ctrl is None and type == telepathy.TUBE_TYPE_DBUS and service == 'org.fredektop.Telepathy.Tube.Memosono'): if state == telepathy.TUBE_STATE_LOCAL_PENDING: self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptTube(id) @@ -246,7 +240,7 @@ class MemosonoActivity(Activity): tube_conn = TubeConnection(self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) - self.game = ConnectGame(tube_conn, self.pv, self.model, self.initiating, + self.ctrl = Controller(tube_conn, self.pv, self.initiating, self.buddies_panel, self.info_panel, self.owner, self._get_buddy, self) diff --git a/model.py b/model.py index 510c9c4..581be15 100644 --- a/model.py +++ b/model.py @@ -3,9 +3,13 @@ import os import logging import random -IMAGES_PATH = os.path.join(os.path.dirname(__file__),'games/drumgit/images') +IMAGES_PATH = 'games/drumgit/images' class Model(object): + ''' The model of the activity. Contains methods to read and save + the configuration for a game from xml. Stores the pairs and grid + information. + ''' def __init__(self, gamepath, dtdpath, name='noname'): self.name = name self.pairs = {} @@ -95,6 +99,7 @@ class Model(object): pairkeya, moch = self.grid[a] pairkeyb, moch = self.grid[b] return (pairkeya == pairkeyb) + if __name__ == '__main__': -- cgit v0.9.1