From: <umg...@us...> - 2006-08-15 15:12:04
|
Revision: 338 Author: umgangee Date: 2006-08-15 08:11:51 -0700 (Tue, 15 Aug 2006) ViewCVS: http://svn.sourceforge.net/pybridge/?rev=338&view=rev Log Message: ----------- Revised environment class to differentiate between installed paths (/usr/bin/, /usr/share/pybridge/glade/ etc) and source dist paths (pybridge/bin/, pybridge/glade/ etc). Modified Paths: -------------- trunk/pybridge/pybridge/environment.py trunk/pybridge/pybridge/server/database.py trunk/pybridge/pybridge/ui/__init__.py trunk/pybridge/pybridge/ui/utils.py Modified: trunk/pybridge/pybridge/environment.py =================================================================== --- trunk/pybridge/pybridge/environment.py 2006-08-15 15:02:10 UTC (rev 337) +++ trunk/pybridge/pybridge/environment.py 2006-08-15 15:11:51 UTC (rev 338) @@ -16,50 +16,80 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import os, sys +import os +import sys -HOME = os.path.expanduser('~') -if hasattr(sys, 'frozen'): # For py2exe distribution. - CURRENTDIR = os.path.dirname(sys.executable) - BASEDIR = os.path.abspath(CURRENTDIR) -else: - CURRENTDIR = os.path.dirname(os.path.abspath(sys.argv[0])) - BASEDIR = os.path.abspath(os.path.join(CURRENTDIR, '..')) +class Environment: + """This module provides path location services for PyBridge.""" -if os.path.exists(os.path.join(BASEDIR, 'share', 'pybridge')): - DATADIR = os.path.join(BASEDIR, 'share', 'pybridge') -else: - DATADIR = BASEDIR -CONFIG_DIR = os.path.join(HOME, '.pybridge') -CLIENT_SETTINGS_PATH = os.path.join(CONFIG_DIR, 'client.cfg') -USER_DB = os.path.join(CONFIG_DIR, 'users.db') + def __init__(self): + # Locate base directory. + if hasattr(sys, 'frozen'): # If py2exe distribution. + currentdir = os.path.dirname(sys.executable) + self.basedir = os.path.abspath(currentdir) + else: # Typically /usr/ or root of source distribution. + currentdir = os.path.dirname(os.path.abspath(sys.argv[0])) + self.basedir = os.path.normpath(os.path.join(currentdir, '..')) + + # Locate shared resources directory, typically /usr/share/. + if os.path.exists(os.path.join(self.basedir, 'share')): + self.sharedir = os.path.join(self.basedir, 'share') + else: # Root of source distribution. + self.sharedir = self.basedir + + # Locate config directory. + self.configdir = os.path.join(os.path.expanduser('~'), '.pybridge') + if not os.path.exists(self.configdir): + os.mkdir(self.configdir) # Create directory. -if not os.path.isdir(CONFIG_DIR): - os.makedirs(CONFIG_DIR) -LOCALE_DIR = os.path.join(BASEDIR, 'locale') + def find_configfile(self, name): + """A config file is located in <configdir>/""" + return os.path.join(self.configdir, name) -DOCS_DIR = '.' -GLADE_DIR = 'glade' -PIXMAPS_DIR = 'pixmaps' + def find_doc(self, name): + """A documentation file may be located in: + + <sharedir>/doc/pybridge/ (installed) + <basedir>/ (source) + """ + if self.sharedir == self.basedir: + return os.path.join(self.basedir, name) + else: + return os.path.join(self.sharedir, 'doc', 'pybridge', name) -class Environment: - def find_datafile(self, name): - return os.path.join(CONFIG_DIR, name) + def find_glade(self, name): + """A Glade interface file may be located in: + + <sharedir>/pybridge/glade/ (installed) + <basedir>/glade/ (source) + """ + if self.sharedir == self.basedir: + return os.path.join(self.basedir, 'glade', name) + else: + return os.path.join(self.sharedir, 'pybridge', 'glade', name) - def find_doc(self, name): - return os.path.join(DATADIR, DOCS_DIR, name) - def find_glade(self, name): - return os.path.join(DATADIR, GLADE_DIR, name) - def find_pixmap(self, name): - return os.path.join(DATADIR, PIXMAPS_DIR, name) + """A pixmap file may be located in: + + <sharedir>/pybridge/pixmaps/ (installed) + <basedir>/pixmaps/ (source) + """ + if self.sharedir == self.basedir: + return os.path.join(self.basedir, 'pixmaps', name) + else: + return os.path.join(self.sharedir, 'pybridge', 'pixmaps', name) + def get_localedir(self): + """Returns the path of the locale directory.""" + return os.path.join(self.sharedir, 'locale') + + environment = Environment() Modified: trunk/pybridge/pybridge/server/database.py =================================================================== --- trunk/pybridge/pybridge/server/database.py 2006-08-15 15:02:10 UTC (rev 337) +++ trunk/pybridge/pybridge/server/database.py 2006-08-15 15:11:51 UTC (rev 338) @@ -20,7 +20,7 @@ from twisted.internet import defer from twisted.python import failure -from pybridge.environment import USER_DB +from pybridge.environment import environment class DuplicateError(Exception): @@ -36,7 +36,8 @@ def __init__(self): # Open the database file. - self.accounts = shelve.open(USER_DB, 'c', writeback=True) + dbfile = environment.find_configfile('users.db') + self.accounts = shelve.open(dbfile, 'c', writeback=True) def addUser(self, username, **attrs): Modified: trunk/pybridge/pybridge/ui/__init__.py =================================================================== --- trunk/pybridge/pybridge/ui/__init__.py 2006-08-15 15:02:10 UTC (rev 337) +++ trunk/pybridge/pybridge/ui/__init__.py 2006-08-15 15:11:51 UTC (rev 338) @@ -16,7 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from pybridge.environment import LOCALE_DIR +from pybridge.environment import environment def run(): """""" @@ -29,7 +29,7 @@ import locale import gettext locale.setlocale(locale.LC_ALL, '') - gettext.bindtextdomain('pybridge', LOCALE_DIR) + gettext.bindtextdomain('pybridge', environment.get_localedir()) gettext.textdomain('pybridge') gettext.install('pybridge') Modified: trunk/pybridge/pybridge/ui/utils.py =================================================================== --- trunk/pybridge/pybridge/ui/utils.py 2006-08-15 15:02:10 UTC (rev 337) +++ trunk/pybridge/pybridge/ui/utils.py 2006-08-15 15:11:51 UTC (rev 338) @@ -16,7 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from pybridge.environment import CLIENT_SETTINGS_PATH +from pybridge.environment import environment # Set up client with UI event handler. from pybridge.network.client import client @@ -38,7 +38,7 @@ general = {} - def __init__(self, filename=CLIENT_SETTINGS_PATH): + def __init__(self, filename): self.config = ConfigParser.SafeConfigParser() self.filename = filename self.read() @@ -63,7 +63,7 @@ self.config.write(file(self.filename, 'w')) -settings = Settings() +settings = Settings(environment.find_configfile('client.cfg')) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-02-28 18:29:54
|
Revision: 356 http://svn.sourceforge.net/pybridge/?rev=356&view=rev Author: umgangee Date: 2007-02-28 10:29:54 -0800 (Wed, 28 Feb 2007) Log Message: ----------- Fold the pybridge.environment.Environment class into pybridge.environment module. Make appropriate changes to UI code. Modified Paths: -------------- trunk/pybridge/pybridge/environment.py trunk/pybridge/pybridge/ui/__init__.py trunk/pybridge/pybridge/ui/canvas.py trunk/pybridge/pybridge/ui/cardarea.py trunk/pybridge/pybridge/ui/dialog_preferences.py trunk/pybridge/pybridge/ui/utils.py trunk/pybridge/pybridge/ui/window_main.py trunk/pybridge/pybridge/ui/wrapper.py Modified: trunk/pybridge/pybridge/environment.py =================================================================== --- trunk/pybridge/pybridge/environment.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/environment.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -20,76 +20,95 @@ import sys -class Environment: - """This module provides path location services for PyBridge.""" +""" +This module provides path location services for PyBridge. +Note to PyBridge packagers: - def __init__(self): - # Locate base directory. - if hasattr(sys, 'frozen'): # If py2exe distribution. - currentdir = os.path.dirname(sys.executable) - self.basedir = os.path.abspath(currentdir) - else: # Typically /usr/ or root of source distribution. - currentdir = os.path.dirname(os.path.abspath(sys.argv[0])) - self.basedir = os.path.normpath(os.path.join(currentdir, '..')) - - # Locate shared resources directory, typically /usr/share/. - if os.path.exists(os.path.join(self.basedir, 'share')): - self.sharedir = os.path.join(self.basedir, 'share') - else: # Root of source distribution. - self.sharedir = self.basedir - - # Locate config directory. - self.configdir = os.path.join(os.path.expanduser('~'), '.pybridge') - if not os.path.exists(self.configdir): - os.mkdir(self.configdir) # Create directory. +The packaging policy of your distribution may specify a filesystem organisation +standard, which conflicts with the directory structure defined in this module. +This is the only module that you should need to modify to make PyBridge +compliant with distribution policy. +""" - def find_configfile(self, name): - """A config file is located in <configdir>/""" - return os.path.join(self.configdir, name) +# Locate base directory. +if hasattr(sys, 'frozen'): # If py2exe distribution. + currentdir = os.path.dirname(sys.executable) + basedir = os.path.abspath(currentdir) +else: # Typically /usr/ (if installed) or root of source distribution. + currentdir = os.path.dirname(os.path.abspath(sys.argv[0])) + basedir = os.path.normpath(os.path.join(currentdir, '..')) - def find_doc(self, name): - """A documentation file may be located in: - - <sharedir>/doc/pybridge/ (installed) - <basedir>/ (source) - """ - if self.sharedir == self.basedir: - return os.path.join(self.basedir, name) - else: - return os.path.join(self.sharedir, 'doc', 'pybridge', name) +# Locate shared resources directory, typically /usr/share/. +if os.path.exists(os.path.join(basedir, 'share')): + sharedir = os.path.join(basedir, 'share') +else: # Root of source distribution. + sharedir = basedir +# Locate client configuration directory, typically ~/.pybridge/. +clientconfigdir = os.path.join(os.path.expanduser('~'), '.pybridge') +if not os.path.exists(clientconfigdir): + os.mkdir(clientconfigdir) # Create directory. - def find_glade(self, name): - """A Glade interface file may be located in: - - <sharedir>/pybridge/glade/ (installed) - <basedir>/glade/ (source) - """ - if self.sharedir == self.basedir: - return os.path.join(self.basedir, 'glade', name) - else: - return os.path.join(self.sharedir, 'pybridge', 'glade', name) +# Locate server configuration directory. +serverconfigdir = clientconfigdir - def find_pixmap(self, name): - """A pixmap file may be located in: - - <sharedir>/pybridge/pixmaps/ (installed) - <basedir>/pixmaps/ (source) - """ - if self.sharedir == self.basedir: - return os.path.join(self.basedir, 'pixmaps', name) - else: - return os.path.join(self.sharedir, 'pybridge', 'pixmaps', name) +def find_config_client(name): + """A client configuration file is located in: + + <clientconfigdir>/ + """ + return os.path.join(clientconfigdir, name) - def get_localedir(self): - """Returns the path of the locale directory.""" - return os.path.join(self.sharedir, 'locale') +def find_config_server(name): + """A server configuration file is located in: + <serverconfigdir>/ + """ + return os.path.join(serverconfigdir, name) -environment = Environment() +def find_doc(name): + """A documentation file may be located in: + + <sharedir>/doc/pybridge/ (installed) + <basedir>/ (source) + """ + if sharedir == basedir: + return os.path.join(basedir, name) + else: + return os.path.join(sharedir, 'doc', 'pybridge', name) + + +def find_glade(name): + """A Glade interface file may be located in: + + <sharedir>/pybridge/glade/ (installed) + <basedir>/glade/ (source) + """ + if sharedir == basedir: + return os.path.join(basedir, 'glade', name) + else: + return os.path.join(sharedir, 'pybridge', 'glade', name) + + +def find_pixmap(name): + """A pixmap file may be located in: + + <sharedir>/pybridge/pixmaps/ (installed) + <basedir>/pixmaps/ (source) + """ + if sharedir == basedir: + return os.path.join(basedir, 'pixmaps', name) + else: + return os.path.join(sharedir, 'pybridge', 'pixmaps', name) + + +def get_localedir(): + """Returns the path of the locale directory.""" + return os.path.join(sharedir, 'locale') + Modified: trunk/pybridge/pybridge/ui/__init__.py =================================================================== --- trunk/pybridge/pybridge/ui/__init__.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/__init__.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,14 +10,12 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from pybridge.environment import environment - def run(): """""" from twisted.internet import gtk2reactor @@ -28,8 +26,9 @@ import locale import gettext + import pybridge.environment as env locale.setlocale(locale.LC_ALL, '') - gettext.bindtextdomain('pybridge', environment.get_localedir()) + gettext.bindtextdomain('pybridge', env.get_localedir()) gettext.textdomain('pybridge') gettext.install('pybridge') Modified: trunk/pybridge/pybridge/ui/canvas.py =================================================================== --- trunk/pybridge/pybridge/ui/canvas.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/canvas.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -19,7 +19,7 @@ import gtk import cairo -from pybridge.environment import environment +import pybridge.environment as env class CairoCanvas(gtk.DrawingArea): @@ -28,7 +28,7 @@ Overlapping items. """ - background_path = environment.find_pixmap('baize.png') + background_path = env.find_pixmap('baize.png') background = cairo.ImageSurface.create_from_png(background_path) pattern = cairo.SurfacePattern(background) pattern.set_extend(cairo.EXTEND_REPEAT) Modified: trunk/pybridge/pybridge/ui/cardarea.py =================================================================== --- trunk/pybridge/pybridge/ui/cardarea.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/cardarea.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,7 +21,7 @@ import pango import pangocairo -from pybridge.environment import environment +import pybridge.environment as env from canvas import CairoCanvas from pybridge.bridge.card import Card, Rank, Suit @@ -44,7 +44,7 @@ """ # Load card mask. - card_mask_path = environment.find_pixmap('bonded.png') + card_mask_path = env.find_pixmap('bonded.png') card_mask = cairo.ImageSurface.create_from_png(card_mask_path) font_description = pango.FontDescription('Sans Bold 10') Modified: trunk/pybridge/pybridge/ui/dialog_preferences.py =================================================================== --- trunk/pybridge/pybridge/ui/dialog_preferences.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/dialog_preferences.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -20,7 +20,7 @@ import os from wrapper import GladeWrapper -from pybridge.environment import environment +import pybridge.environment as env import utils @@ -36,7 +36,7 @@ self.carddeck.pack_start(cell, True) self.carddeck.add_attribute(cell, 'text', 0) # Populate list of card decks. - path = environment.find_pixmap('') + path = env.find_pixmap('') for filename in os.listdir(path): if filename.endswith('.png'): iter = model.append((filename,)) Modified: trunk/pybridge/pybridge/ui/utils.py =================================================================== --- trunk/pybridge/pybridge/ui/utils.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/utils.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,13 +10,13 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from pybridge.environment import environment +import pybridge.environment as env # Set up client with UI event handler. from pybridge.network.client import client @@ -49,21 +49,27 @@ self.config.read(self.filename) # Create sections if they do not exist. - if not self.config.has_section('Connection'): - self.config.add_section('Connection') - self.write() + for section in ('Connection', 'General'): + if not self.config.has_section(section): + self.config.add_section(section) + self.write() + for key, value in self.config.items('Connection'): self.connection[key] = value + for key, value in self.config.items('General'): + self.general[key] = value def write(self): """""" for key, value in self.connection.items(): self.config.set('Connection', key, value) + for key, value in self.general.items(): + self.config.set('General', key, value) self.config.write(file(self.filename, 'w')) -settings = Settings(environment.find_configfile('client.cfg')) +settings = Settings(env.find_config_client('client.cfg')) Modified: trunk/pybridge/pybridge/ui/window_main.py =================================================================== --- trunk/pybridge/pybridge/ui/window_main.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/window_main.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,15 +21,15 @@ import webbrowser -from pybridge import __version__ -from pybridge.environment import environment +from pybridge import __version__ as version +import pybridge.environment as env from pybridge.network.client import client from eventhandler import eventhandler import utils -TABLE_ICON = environment.find_pixmap("table.png") -USER_ICON = environment.find_pixmap("user.png") +TABLE_ICON = env.find_pixmap("table.png") +USER_ICON = env.find_pixmap("user.png") class WindowMain(GladeWrapper): @@ -209,16 +209,16 @@ def on_about_activate(self, widget, *args): about = gtk.AboutDialog() about.set_name('PyBridge') - about.set_version(__version__) - about.set_copyright('Copyright (C) 2004-2006 Michael Banks') + about.set_version(version) + about.set_copyright('Copyright (C) 2004-2007 Michael Banks') about.set_comments(_('A free online bridge game.')) about.set_website('http://pybridge.sourceforge.net/') - license = file(environment.find_doc('COPYING')).read() + license = file(env.find_doc('COPYING')).read() about.set_license(license) - authorsfile = file(environment.find_doc('AUTHORS')) + authorsfile = file(env.find_doc('AUTHORS')) authors = [author.strip() for author in authorsfile] about.set_authors(authors) - logo_path = environment.find_pixmap('pybridge.png') + logo_path = env.find_pixmap('pybridge.png') logo = gtk.gdk.pixbuf_new_from_file(logo_path) about.set_logo(logo) Modified: trunk/pybridge/pybridge/ui/wrapper.py =================================================================== --- trunk/pybridge/pybridge/ui/wrapper.py 2007-02-28 18:21:26 UTC (rev 355) +++ trunk/pybridge/pybridge/ui/wrapper.py 2007-02-28 18:29:54 UTC (rev 356) @@ -1,5 +1,5 @@ # PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2006 PyBridge Project. +# Copyright (C) 2004-2007 PyBridge Project. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -10,7 +10,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. - +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. @@ -21,13 +21,13 @@ import gtk.glade import sys -from pybridge.environment import environment +import pybridge.environment as env -GLADE_PATH = environment.find_glade("pybridge.glade") +GLADE_PATH = env.find_glade("pybridge.glade") if sys.platform == 'win32': # Win32 should use the ICO icon. - ICON_PATH = environment.find_pixmap("pybridge.ico") + ICON_PATH = env.find_pixmap("pybridge.ico") else: # All other platforms should use the PNG icon. - ICON_PATH = environment.find_pixmap("pybridge.png") + ICON_PATH = env.find_pixmap("pybridge.png") class GladeWrapper: This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-02-28 21:37:21
|
Revision: 358 http://svn.sourceforge.net/pybridge/?rev=358&view=rev Author: umgangee Date: 2007-02-28 13:37:21 -0800 (Wed, 28 Feb 2007) Log Message: ----------- Move Settings class out of UI, to enable reuse. Modified Paths: -------------- trunk/pybridge/pybridge/ui/utils.py Added Paths: ----------- trunk/pybridge/pybridge/settings.py Added: trunk/pybridge/pybridge/settings.py =================================================================== --- trunk/pybridge/pybridge/settings.py (rev 0) +++ trunk/pybridge/pybridge/settings.py 2007-02-28 21:37:21 UTC (rev 358) @@ -0,0 +1,53 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# 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 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import ConfigParser + + +class Settings: + """A wrapper for ConfigParser.""" + + + def __init__(self, path, sections): + """Make section key/value pairs into attributes of this object. + + @param path: the location of configuration file to load. + @param sections: a list of section names to be made available. + """ + self._path = path + self._config = ConfigParser.SafeConfigParser() + self._config.read(path) + + for section in sections: + # Create sections if they do not exist. + if not self._config.has_section(section): + self._config.add_section(section) + # Make items in section available as + items = {} + for key, value in self._config.items(section): + items[key] = value + setattr(self, section.lower(), items) # self.<section> = items + + + def save(self): + """Writes contents of section/item dicts back to file.""" + for section in self._config.sections(): + for key, value in getattr(self, section.lower()).items(): + self._config.set(section, key, value) + self._config.write(file(self._path, 'w')) + Modified: trunk/pybridge/pybridge/ui/utils.py =================================================================== --- trunk/pybridge/pybridge/ui/utils.py 2007-02-28 18:33:16 UTC (rev 357) +++ trunk/pybridge/pybridge/ui/utils.py 2007-02-28 21:37:21 UTC (rev 358) @@ -16,8 +16,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -import pybridge.environment as env - # Set up client with UI event handler. from pybridge.network.client import client from eventhandler import eventhandler @@ -26,54 +24,13 @@ PORT = 5040 # Default port for PyBridge. +import pybridge.environment as env +from pybridge.settings import Settings +file = env.find_config_client('client.cfg') +settings = Settings(file, ['Connection', 'General']) -import ConfigParser - -class Settings: - """""" - - connection = {} - general = {} - - - def __init__(self, filename): - self.config = ConfigParser.SafeConfigParser() - self.filename = filename - self.read() - - - def read(self): - """""" - self.config.read(self.filename) - - # Create sections if they do not exist. - for section in ('Connection', 'General'): - if not self.config.has_section(section): - self.config.add_section(section) - self.write() - - for key, value in self.config.items('Connection'): - self.connection[key] = value - for key, value in self.config.items('General'): - self.general[key] = value - - - def write(self): - """""" - for key, value in self.connection.items(): - self.config.set('Connection', key, value) - for key, value in self.general.items(): - self.config.set('General', key, value) - self.config.write(file(self.filename, 'w')) - - -settings = Settings(env.find_config_client('client.cfg')) - - - - import imp from UserDict import UserDict @@ -123,7 +80,7 @@ def quit(): """Shutdown gracefully.""" client.disconnect() - settings.write() # Save settings. + settings.save() # Save settings. reactor.stop() gtk.main_quit() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 15:11:41
|
Revision: 368 http://svn.sourceforge.net/pybridge/?rev=368&view=rev Author: umgangee Date: 2007-03-27 08:11:40 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Move game-centric code out of Table classes and into Game classes. Modified Paths: -------------- trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotetable.py trunk/pybridge/pybridge/server/server.py Removed Paths: ------------- trunk/pybridge/pybridge/network/localbridge.py trunk/pybridge/pybridge/network/remotebridge.py Deleted: trunk/pybridge/pybridge/network/localbridge.py =================================================================== --- trunk/pybridge/pybridge/network/localbridge.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/localbridge.py 2007-03-27 15:11:40 UTC (rev 368) @@ -1,220 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# 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 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -from twisted.internet import reactor -from twisted.spread import pb -from zope.interface import implements - -from pybridge.interfaces.bridgetable import IBridgeTable -from pybridge.network.error import DeniedRequest, IllegalRequest -from pybridge.network.localtable import LocalTable, LocalTableViewable - -# Set up reconstruction of objects from clients. -from pybridge.bridge.call import Bid, Pass, Double, Redouble -from pybridge.bridge.card import Card -pb.setUnjellyableForClass(Bid, Bid) -pb.setUnjellyableForClass(Pass, Pass) -pb.setUnjellyableForClass(Double, Double) -pb.setUnjellyableForClass(Redouble, Redouble) -pb.setUnjellyableForClass(Card, Card) - -# Bridge game. -from pybridge.bridge.deck import Deck -from pybridge.bridge.game import Game, GameError -from pybridge.bridge.scoring import scoreDuplicate -from pybridge.bridge.symbols import Player - - -class LocalBridgeTable(LocalTable): - """A bridge table, implementing the IBridgeTable interface. - - """ - - implements(IBridgeTable) # Also ITable, from parent Table. - - - def __init__(self, id): - LocalTable.__init__(self, id) - self.view = LocalBridgeTableViewable(self) # For clients. - - self.dealer = None - self.deck = Deck() - self.game = None - self.players = dict.fromkeys(Player, None) - self.scoring = scoreDuplicate - - self.handsSeen = {} - for player in Player: - self.handsSeen[player] = [] - - self.pendingDeals = [] # Queue of deals for successive games. - - # A time delay between a finished game and starting the next game. - self.config['gameWaitInterval'] = 5 - - - def getState(self): - state = LocalTable.getState(self) - - if self.game: - state['game'] = {} - state['game']['dealer'] = self.dealer.key - state['game']['vulnNS'] = self.game.vulnNS - state['game']['vulnEW'] = self.game.vulnEW - if self.game.bidding: - state['game']['calls'] = self.game.bidding.calls - if self.game.playing: - state['game']['declarer'] = self.game.playing.declarer.key - state['game']['played'] = {} - for player, played in self.game.playing.played.items(): - state['game']['played'][player.key] = played - # Add visible hands. - state['game']['deal'] = {} - for player, hand in self.game.deal.items(): - if self.game.isHandVisible(player, viewer=None): - state['game']['deal'][player.key] = hand - else: - state['game'] = None - - return state - - - def addPlayer(self, position, player): - # Overrides LocalTable, to provision revealHands() and testStartGame(). - LocalTable.addPlayer(self, position, player) - - self.handsSeen[position] = [] # Clear list of hands seen by player. - if self.game: # If game in progress... - self.revealHands() # ... provide player with visible hands. - # Test if game is ready to start. - self.testStartGame() - - - -# Methods implementing IBridgeTable. - - - def gameMakeCall(self, call, position): - if self.game is None or self.game.isComplete(): - raise DeniedRequest, 'Game not running' - elif position is None: - raise DeniedRequest, 'Not a player' - - try: - self.game.makeCall(call=call, player=position) - except GameError, error: - raise DeniedRequest, error - - self.updateObservers('gameCallMade', call=call, position=position.key) - self.testEndGame() - - - def gamePlayCard(self, card, position): - if self.game is None or self.game.isComplete(): - raise DeniedRequest, 'Game not running' - elif position is None: - raise DeniedRequest, 'Not a player' - elif self.game.playing is None: - raise DeniedRequest, 'Play not running' - - # Declarer can play dummy's cards, dummy cannot play own cards. - if self.game.whoseTurn() == self.game.playing.dummy: - if position == self.game.playing.declarer: # Declarer commands dummy. - position = self.game.playing.dummy - elif position == self.game.playing.dummy: - raise DeniedRequest, 'Dummy cannot play cards' - - try: - self.game.playCard(card=card, player=position) - except GameError, error: - raise DeniedRequest, error - - self.updateObservers('gameCardPlayed', card=card, position=position.key) - self.revealHands() - self.testEndGame() - - - def requestNextGame(self, player, ready=True): - self.observers[player]['ready'] = (ready == True) - self.testStartGame() # Check to start game. - - -# Utility methods. - - - def testStartGame(self, dealer=None, deal=None): - """If no game is active and all players are ready, start a game.""" - if (self.game is None or self.game.isComplete()) \ - and len([p for p in self.players.values() if p is None]) == 0: - - deal = deal or self.deck.randomDeal() - vulnNS, vulnEW = False, False - self.dealer = dealer or (self.dealer and Player[(self.dealer.index + 1) % 4]) or Player.North - self.game = Game(self.dealer, deal, self.scoring, vulnNS, vulnEW) - self.updateObservers('gameStarted', dealer=self.dealer.key, vulnNS=vulnNS, vulnEW=vulnEW) - - for position in self.handsSeen: - self.handsSeen[position] = [] # Clear lists of hands seen. - self.revealHands() # Provide players with their hands. - return True - return False - - - def testEndGame(self): - """If game is finished, end game.""" - if self.game and self.game.isComplete(): - self.updateObservers('gameFinished') - self.revealHands() # Make all hands visible. - - # Set up time delay before next game starts. - wait = self.config.get('gameWaitInterval', 0) - reactor.callLater(wait, self.testStartGame) - - return True - return False - - - def revealHands(self): - """ - - Each hand is transmitted only once to each player. - """ - # TODO: what about observers? - for viewer, player in self.players.items(): - for seat in Player: - if seat not in self.handsSeen[viewer] and self.game.isHandVisible(seat, viewer): - self.handsSeen[viewer].append(seat) - hand = self.game.deal[seat] - self.informObserver(self.observers[player], 'gameHandRevealed', - hand=hand, position=seat.key) - - - - -class LocalBridgeTableViewable(LocalTableViewable): - - - def view_gameMakeCall(self, user, call, position=None): - position = self.table.getPositionOfPlayer(user) - self.table.gameMakeCall(call, position) - - - def view_gamePlayCard(self, user, card, position=None): - position = self.table.getPositionOfPlayer(user) - self.table.gamePlayCard(card, position) - Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/localtable.py 2007-03-27 15:11:40 UTC (rev 368) @@ -21,22 +21,33 @@ from twisted.spread import pb from zope.interface import implements +from pybridge.interfaces.observer import ISubject, IListener from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest -from pybridge.bridge.symbols import Player # XX TODO: Try to avoid this. class LocalTable(pb.Cacheable): - """""" + """An implementation of ITable suitable for server-side table instances. + + A LocalTable maintains the "master" game object and provides synchronisation + services for remote tables to mirror the game state. + """ - implements(ITable) + implements(ITable, ISubject, IListener) - def __init__(self, id): + def __init__(self, id, gametype): + self.listeners = [] + self.id = id + self.gametype = gametype + self.game = gametype() # Initialise game. + self.game.attach(self) # Listen for game events. + self.config = {} - self.observers = {} # For each perspective, a remote ITableEvents observer. - self.players = {} # For each position, the player's perspective. + self.observers = {} # For each user perspective, a remote ITableEvents. + self.players = {} # Positions mapped to perspectives of game players. + self.view = LocalTableViewable(self) # For remote clients. # Configuration variables. self.config['closeWhenEmpty'] = True @@ -44,65 +55,112 @@ def getStateToCacheAndObserveFor(self, perspective, observer): - self.updateObservers('observerAdded', observer=perspective.name) + # Inform existing observers that a new user has joined. + self.notify('addObserver', observer=perspective.name) self.observers[perspective] = observer - - return self.getState() # For observer. - - def getState(self): - """Build a dict of public information about the table.""" + # Build a dict of public information about the table. state = {} - state['id'] = self.id state['observers'] = [p.name for p in self.observers.keys()] - state['players'] = {} - for position, perspective in self.players.items(): - state['players'][position.key] = getattr(perspective, 'name', None) -# state['timeCreated'] = self.config['timeCreated'] - - return state + state['players'] = dict([(pos, p.name) + for pos, p in self.players.items()]) + state['gametype'] = self.gametype.__name__ + state['gamestate'] = self.game.getState() + return state # To observer. + def stoppedObserving(self, perspective, observer): + # If user was playing, then remove their player(s) from game. + for position, user in self.players.items(): + if perspective == user: + self.leaveGame(perspective, position) + del self.observers[perspective] - - # If user was a player, then remove player. - if self.getPositionOfPlayer(perspective): - self.removePlayer(perspective) - - self.updateObservers('observerRemoved', observer=perspective.name) - + self.notify('removeObserver', observer=perspective.name) + # If there are no remaining observers, close table. - if self.config.get('closeWhenEmpty') and len(self.observers) == 0: + if self.config.get('closeWhenEmpty') and not self.observers: self.server.tables.closeTable(self) -# Methods implementing ITable. +# Implementation of ISubject. - def addPlayer(self, position, player): - # Check that player is not already playing at table. - if self.getPositionOfPlayer(player): - raise DeniedRequest('already playing at table') - # Check that position is not occupied by another player. - if self.players.get(position) is not None: - raise DeniedRequest('position occupied by another player') - - self.players[position] = player - self.updateObservers('playerAdded', player=player.name, position=position.key) + def attach(self, listener): + self.listeners.append(listener) - def removePlayer(self, player): - position = self.getPositionOfPlayer(player) - # Check that player is playing at table: - if not position: - raise DeniedRequest('not playing at table') + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + # For all observers, calls event handler with provided arguments. + for observer in self.observers.values(): + self.notifyObserver(observer, event, *args, **kwargs) + + + def notifyObserver(self, obs, event, *args, **kwargs): + """Calls observer's event handler with provided arguments. - self.players[position] = None - self.updateObservers('playerRemoved', player=player.name, position=position.key) + @param obs: an observer object. + @type obs: RemoteCacheObserver + @param event: the name of the event. + @type event: str + """ + # Event handlers are called on the next iteration of the reactor, + # to allow the caller of this method to return a result. + reactor.callLater(0, obs.callRemote, event, *args, **kwargs) +# Implementation of IListener. + + + def update(self, event, *args, **kwargs): + # Expected to be called only by methods of self.game. + for observer in self.observers.values(): + self.notifyObserver(observer, 'gameUpdate', event, *args, **kwargs) + + +# Implementation of ITable. + + + def joinGame(self, user, position): + if position not in self.game.positions: + raise IllegalRequest, "Invalid position type" + # Check that user is not already playing at table. + if not self.config.get('allowUserMultiplePlayers'): + if user in self.players.values(): + raise DeniedRequest, "Already playing in game" + + player = self.game.addPlayer(position) # May raise GameError. + self.players[position] = user + self.notify('joinGame', player=user.name, position=position) + + # If no game is active and all players are ready, start game. + if not self.game.inProgress(): + if len(self.players) == len(self.game.positions): + self.game.start() + + return player + + + def leaveGame(self, user, position): + if position not in self.game.positions: + raise IllegalRequest, "Invalid position type" + # Ensure that user is playing at specified position. + if self.players.get(position) != user: + raise DeniedRequest, "Not playing at position" + + self.game.removePlayer(position) # May raise GameError. + del self.players[position] + self.notify('leaveGame', player=user.name, position=position) + + def sendMessage(self, message, sender, recipients): names = [perspective.name for perspective in self.observers.keys()] if recipients: # Translate user names to their observer objects. @@ -112,49 +170,19 @@ else: # Broadcast message to all observers. recipients = names sendTo = self.observers.values() - + for observer in sendTo: - observer.callRemote('messageReceived', message, sender.name, recipients) + self.notifyObserver(observer, 'sendMessage', message=message, + sender=sender.name, recipients=recipients) -# Utility methods. - def getPositionOfPlayer(self, user): - """If observer is playing, returns position of player. - Otherwise, returns False. - - @param user: observer identifier. - @return: position of player, or False. - """ - for position, player in self.players.items(): - if player == user: - return position - return False - - - def informObserver(self, obs, event, *args, **kwargs): - """Calls observer's event handler with provided args and kwargs. - - Event handlers are called on the next iteration of the reactor, - to allow the caller of this method to return a result. - """ - reactor.callLater(0, obs.callRemote, event, *args, **kwargs) - - - def updateObservers(self, event, *args, **kwargs): - """For each observer, calls event handler with provided kwargs.""" - for observer in self.observers.values(): - self.informObserver(observer, event, *args, **kwargs) - - - - class LocalTableViewable(pb.Viewable): - """ + """Provides a public front-end to an instantiated LocalTable. Serialization flavors are mutually exclusive and cannot be mixed, - so this class provides a pb.Viewable front-end to LocalTable. + so this class is a subclass of pb.Viewable. """ @@ -166,15 +194,14 @@ self.table = table - def view_addPlayer(self, user, position, player=None): - position = getattr(Player, position) # XX - self.table.addPlayer(position, user) + def view_joinGame(self, user, position): + return self.table.joinGame(user, position) - def view_removePlayer(self, user, player=None): - self.table.removePlayer(user) + def view_leaveGame(self, user, position): + return self.table.leaveGame(user, position) def view_sendMessage(self, user, message, sender=None, recipients=[]): - self.table.sendMessage(message, user, recipients) + return self.table.sendMessage(message, sender=user, recipients=recipients) Deleted: trunk/pybridge/pybridge/network/remotebridge.py =================================================================== --- trunk/pybridge/pybridge/network/remotebridge.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/remotebridge.py 2007-03-27 15:11:40 UTC (rev 368) @@ -1,140 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# 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 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -from twisted.spread import pb -from zope.interface import implements - -from pybridge.interfaces.bridgetable import IBridgeTable -from pybridge.network.remotetable import RemoteTable -from pybridge.network.error import DeniedRequest, IllegalRequest - -# Set up reconstruction of objects from server. -from pybridge.bridge.call import Bid, Pass, Double, Redouble -from pybridge.bridge.card import Card -pb.setUnjellyableForClass(Bid, Bid) -pb.setUnjellyableForClass(Pass, Pass) -pb.setUnjellyableForClass(Double, Double) -pb.setUnjellyableForClass(Redouble, Redouble) -pb.setUnjellyableForClass(Card, Card) - -# Bridge game. -from pybridge.bridge.game import Game, GameError -from pybridge.bridge.scoring import scoreDuplicate -from pybridge.bridge.symbols import Player - - -class RemoteBridgeTable(RemoteTable): - """A bridge table, implementing the IBridgeTable interface. - - This is a cache of a remote MasterBridgeTable. - """ - - implements(IBridgeTable) - - - def __init__(self): - RemoteTable.__init__(self) - - self.dealer = None - self.game = None - self.players = dict.fromkeys(Player, None) - self.scoring = scoreDuplicate - - - def setCopyableState(self, state): - RemoteTable.setCopyableState(self, state) - - # Convert seat strings to Player enumeration values. - players = {} - for seat, player in self.players.items(): - players[getattr(Player, seat)] = player - self.players = players - - if state.get('game'): - self.dealer = getattr(Player, state['game']['dealer']) # XX - deal = {} - for player in Player: - deal[player] = state['game']['deal'].get(player, []) - vulnNS, vulnEW = state['game']['vulnNS'], state['game']['vulnEW'] - self.game = Game(self.dealer, deal, self.scoring, vulnNS, vulnEW) - if state['game'].get('calls'): - for call in state['game']['calls']: - player = self.game.whoseTurn() - self.game.makeCall(call=call, player=player) - if state['game'].get('played'): - played = state['game']['played'] - while sum([len(cards) for cards in played.values()]) > 0: - player = self.game.whoseTurn() - card = played[player.key].pop(0) - self.game.playCard(card=card, player=player) - - - def gameMakeCall(self, call, position=None): - d = self.master.callRemote('gameMakeCall', call) - return d - -# # Check that game is running and we are playing. -# if self.game and self.game.whoseTurn() == self.position \ -# and self.game.bidding.validCall(call): -# # TODO: bidding.isValidCall() should check turn to bid. -# d = self.master.callRemote('gameMakeCall', call) -# return d - - - def gamePlayCard(self, card, position): - d = self.master.callRemote('gamePlayCard', card) - return d - - - def requestNextGame(self, ready=True, player=None): - d = self.master.callRemote('requestNextGame', ready) - return d - - -# Remote update methods. - - - def observe_gameStarted(self, dealer, vulnNS, vulnEW): - dealer = getattr(Player, dealer) # XX - self.dealer = dealer - deal = dict.fromkeys(Player, []) # Unknown hands. - self.game = Game(dealer, deal, self.scoring, vulnNS, vulnEW) - self.eventHandler.gameStarted(self, dealer, vulnNS, vulnEW) - - - def observe_gameFinished(self): - self.eventHandler.gameFinished(self) - - - def observe_gameCallMade(self, call, position): - position = getattr(Player, position) # XX - self.game.makeCall(call=call, player=position) - self.eventHandler.gameCallMade(self, call, position) - - - def observe_gameCardPlayed(self, card, position): - position = getattr(Player, position) # XX - self.game.playCard(card=card, player=position) - self.eventHandler.gameCardPlayed(self, card, position) - - - def observe_gameHandRevealed(self, hand, position): - position = getattr(Player, position) # XX - self.game.deal[position] = hand - self.eventHandler.gameHandRevealed(self, hand, position) - Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-03-27 15:11:40 UTC (rev 368) @@ -19,27 +19,31 @@ from twisted.spread import pb from zope.interface import implements +from pybridge.interfaces.observer import ISubject from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest -from pybridge.bridge.symbols import Player # XX TODO: Try to avoid this. class RemoteTable(pb.RemoteCache): - """ + """A client-side implementation of ITable providing a "front-end" to a + remote server-side LocalTable. + RemoteTable mirrors the state of LocalTable as a local cache. External code + may, therefore, read the table state without network communication. Actions + which change the table state are forwarded to the LocalTable. """ - implements(ITable) + implements(ITable, ISubject) def __init__(self): self.master = None # Server-side ITable object. - self.eventHandler = None # Expected to be set. - + self.listeners = [] + self.id = None - self.observers = [] - self.players = {} - self.seated = None # If user is playing at table. + self.game = None + self.observers = [] # Observers of master table. + self.players = {} # Positions mapped to player identifiers. def setCopyableState(self, state): @@ -47,23 +51,31 @@ self.observers = state['observers'] self.players = state['players'] + # TODO: do this by magic. + if state['gametype'] in ['BridgeGame']: + from pybridge.bridge.game import BridgeGame + self.gametype = BridgeGame + else: + raise NameError, "Unknown game type %s" % state['gametype'] - def setEventHandler(self, handler): - self.eventHandler = handler + self.game = self.gametype() + self.game.setState(state['gamestate']) -# Methods implementing ITable. +# Implementation of ITable. - def addPlayer(self, position, player=None): - d = self.master.callRemote('addPlayer', position.key) - d.addCallback(lambda _: setattr(self, 'seated', position)) + def setEventHandler(self, e): + print "called event handler - remove this!" + + + def joinGame(self, position, user=None): + d = self.master.callRemote('joinGame', position.key) return d - def removePlayer(self, player=None): - d = self.master.callRemote('removePlayer') - d.addCallback(lambda _: setattr(self, 'seated', None)) + def leaveGame(self, position, user=None): + d = self.master.callRemote('leaveGame', position.key) return d @@ -72,46 +84,52 @@ return d +# Implementation of ISubject. + + + def attach(self, listener): + self.listeners.append(listener) + + + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + + # Remote update methods. - def observe_observerAdded(self, observer): + def observe_addObserver(self, observer): self.observers.append(observer) - self.eventHandler.observerAdded(self, observer) + self.notify('addObserver', observer) - def observe_observerRemoved(self, observer): + def observe_removeObserver(self, observer): self.observers.remove(observer) - self.eventHandler.observerRemoved(self, observer) + self.notify('removeObserver', observer) - def observe_playerAdded(self, player, position): - position = getattr(Player, position) # XX + def observe_joinGame(self, player, position): + position = getattr(self.game.positions, position) self.players[position] = player - self.eventHandler.playerAdded(self, player, position) + self.notify('joinGame', player, position) - def observe_playerRemoved(self, player, position): - position = getattr(Player, position) # XX - self.players[position] = None - self.eventHandler.playerRemoved(self, player, position) + def observe_leaveGame(self, player, position): + position = getattr(self.game.positions, position) + del self.players[position] + self.notify('leaveGame', player, position) - def observe_messageReceived(self, message, sender, recipients): - self.eventHandler.messageReceived(self, message, sender, recipients) + def observe_sendMessage(self, message, sender, recipients): + # TODO: add to message log? + self.notify('sendMessage', message, sender, recipients) -# Utility methods. + def observe_gameUpdate(self, event, *args, **kwargs): + self.game.updateState(event, *args, **kwargs) - - def getPositionOfPlayer(self, user=None): - """If user is playing, returns position of player, otherwise False. - - @param user: observer identifier. - @return: position of player, or False. - """ - for position, player in self.players.items(): - if player == user: - return position - return False - Modified: trunk/pybridge/pybridge/server/server.py =================================================================== --- trunk/pybridge/pybridge/server/server.py 2007-03-27 14:55:26 UTC (rev 367) +++ trunk/pybridge/pybridge/server/server.py 2007-03-27 15:11:40 UTC (rev 368) @@ -26,7 +26,8 @@ from pybridge.network.tablemanager import LocalTableManager from pybridge.network.usermanager import LocalUserManager -from pybridge.network.localbridge import LocalBridgeTable +from pybridge.network.localtable import LocalTable +from pybridge.bridge.game import BridgeGame class Server: @@ -80,7 +81,7 @@ def createTable(self, tableid, tabletype): # Ignore specified tabletype, for now. if tableid not in self.tables: - table = LocalBridgeTable(tableid) + table = LocalTable(tableid, BridgeGame) table.id = tableid table.server = self self.tables.openTable(table) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-03-27 15:40:52
|
Revision: 370 http://svn.sourceforge.net/pybridge/?rev=370&view=rev Author: umgangee Date: 2007-03-27 08:39:04 -0700 (Tue, 27 Mar 2007) Log Message: ----------- Refactor BridgeGame class to support new LocalTable and RemoteTable. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/game.py trunk/pybridge/pybridge/network/error.py Modified: trunk/pybridge/pybridge/bridge/game.py =================================================================== --- trunk/pybridge/pybridge/bridge/game.py 2007-03-27 15:33:14 UTC (rev 369) +++ trunk/pybridge/pybridge/bridge/game.py 2007-03-27 15:39:04 UTC (rev 370) @@ -16,107 +16,301 @@ # Foundation Inc. 51 Franklin Street Fifth Floor Boston MA 02110-1301 USA. +from twisted.spread import pb +from zope.interface import implements + +from pybridge.interfaces.game import ICardGame +from pybridge.interfaces.observer import ISubject +from pybridge.network.error import GameError + from bidding import Bidding +from board import Board from playing import Playing -from symbols import Player, Suit +from symbols import Direction, Suit, Strain -class GameError(Exception): - pass +class BridgeGame(object): + """A bridge game models the bidding and play sequence. + + The methods of this class comprise the interface of a state machine. + Clients should only use the class methods to interact with the game state. + Modifications to the state are typically made through BridgePlayer objects. + + Methods which change the game state (makeCall, playCard) require a player + argument as "authentication". + """ -class Game: - """A bridge game models the bidding, playing, scoring sequence.""" + implements(ICardGame, ISubject) - def __init__(self, dealer, deal, scoring, vulnNS, vulnEW): - """Initialises game. - - scoring: instance of scoring class. - """ - self.vulnNS, self.vulnEW = vulnNS, vulnEW - self.deal = deal - self.playing = None - self.scoring = scoring - - # Start bidding. - self.bidding = Bidding(dealer) + # Valid positions. + positions = Direction + # Mapping from Strain symbols (in bidding) to Suit symbols (in play). + trumpMap = {Strain.Club: Suit.Club, Strain.Diamond: Suit.Diamond, + Strain.Heart: Suit.Heart, Strain.Spade: Suit.Spade, + Strain.NoTrump: None} - def isComplete(self): - """Returns True if game is complete, False otherwise.""" - if self.playing: - return self.playing.isComplete() + + def __init__(self): + self.listeners = [] + + self.board = None + self.bidding = None + self.contract = None + self.play = None + + self.boardQueue = [] # Boards for successive games. + self.players = {} # One-to-one mapping from Direction to BridgePlayer. + + +# Implementation of ICardGame. + + + def start(self, board=None): + if board: # Use specified board. + self.board = board + elif self.board: # Advance to next deal. + self.board.nextDeal(result=999) + else: # Create a board. + self.board = Board() + self.board.nextDeal() + self.bidding = Bidding(self.board['dealer']) # Start bidding. + self.contract = None + self.play = None + + # Remove deal from board, so it does not appear to players. + visibleBoard = self.board.copy() + del visibleBoard['deal'] + self.notify('start', board=visibleBoard) + + + def inProgress(self): + if self.play: + return not self.play.isComplete() + elif self.bidding: + return not self.bidding.isPassedOut() else: - return self.bidding.isPassedOut() + return False - def isHandVisible(self, player, viewer): - """A hand is visible if one of the following conditions is met: + def getState(self): + # TODO: all flag? + state = {} + + if self.inProgress(): + # Remove deal from board, so it does not appear to players. + visibleBoard = self.board.copy() + del visibleBoard['deal'] + state['board'] = visibleBoard + + if self.bidding: + state['calls'] = self.bidding.calls + if self.play: + state['played'] = [] + trickcount = max([len(s) for s in self.play.played.values()]) + for index in range(trickcount): + leader, cards = self.play.getTrick(index) + for pos in Direction[leader.index:] + Direction[:leader.index]: + if pos in cards: + state['played'].append(cards[pos]) + + return state + + + def setState(self, state): + if state.get('board'): + self.start(state['board']) + + # Comprehensive error checking is suppressed. + for call in state.get('calls', []): + turn = self.getTurn() + self.makeCall(call, self.players[turn]) +# self.bidding.makeCall(call, player=turn) + + # If a contract has been reached, start the play. +# contract = self.bidding.getContract() +# if contract: +# self.contract = contract +# trumpSuit = self.trumpMap[self.contract['bid'].strain] +# self.play = Playing(contract['declarer'], trumpSuit) + + # Comprehensive error checking is suppressed. + for card in state.get('played', []): + turn = self.getTurn() + self.playCard(card, self.players[turn]) + #self.play.playCard(card, player=turn) + + + def updateState(self, event, *args, **kwargs): + allowed = ['start', 'makeCall', 'playCard'] + if event in allowed: + handler = getattr(self, event) + handler(*args, **kwargs) +# else: +# print "updateState unknown attempted", event + + + def addPlayer(self, position): + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) + if position in self.players: + raise GameError, "Position %s is taken" % position + + player = BridgePlayer(self) + self.players[position] = player + self.notify('addPlayer', position=position) + + return player + + + def removePlayer(self, position): + if position not in Direction: + raise TypeError, "Expected Direction, got %s" % type(position) + if position not in self.players: + raise GameError, "Position %s is vacant" % position + + player = self.players[position] + del self.players[position] + self.notify('removePlayer', position=position) + + +# Implementation of ISubject. + + + def attach(self, listener): + self.listeners.append(listener) + + + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + + +# Bridge-specific methods. + + + def makeCall(self, call, player): + """Make a call in the current bidding session. - 1. The hand is the viewer's own hand. - 2. Game is complete. - 3. Bidding complete and hand is dummy's, and first card of - first trick has been played. + @param call: the call. + @type call: Bid or Pass or Double or Redouble + @param player: a player identifier. + @type player: BridgePlayer """ - return player == viewer \ - or self.isComplete() \ - or (self.bidding.isComplete() and player == self.playing.dummy and \ - len(self.playing.getTrick(0)[1]) >= 1) + position = self.getPositionOfPlayer(player) + if position is None: + raise GameError, "Invalid player reference" + # Validate call according to game state. + if not self.bidding or self.bidding.isComplete(): + raise GameError, "Game not running or bidding complete" + if self.getTurn() is not position: + raise GameError, "Call made out of turn" + if not self.bidding.isValidCall(call, position): + raise GameError, "Call cannot be made" - def makeCall(self, player, call): - """Makes call from player.""" + self.bidding.makeCall(call, position) + self.notify('makeCall', call=call, position=position) + if self.bidding.isComplete(): - raise GameError('not in bidding') - - if self.bidding.whoseTurn() is not player: - raise GameError('out of turn') - elif not self.bidding.isValidCall(call, player): - raise GameError('invalid call') - - self.bidding.makeCall(call, player) - - # If bidding is complete, start playing. - if self.bidding.isComplete() and not self.bidding.isPassedOut(): + # If a contract has been reached, start the play. contract = self.bidding.getContract() - # Convert from bidding's Strain type to playing's Suit type. - # Note that No Trumps implies a None trump suit. - trumpSuit = getattr(Suit, str(contract['bid'].strain), None) - self.playing = Playing(contract['declarer'], trumpSuit) + if contract: + self.contract = contract + trumpSuit = self.trumpMap[self.contract['bid'].strain] + self.play = Playing(contract['declarer'], trumpSuit) + elif self.bidding.isPassedOut(): + self.notify('gameOver') - def playCard(self, player, card): - """Plays card from player.""" - if not self.bidding.isComplete() or self.bidding.isPassedOut(): - raise GameError('not in play') - elif self.playing.isComplete(): - raise GameError('not in play') + def signalAlert(self, alert, player): + pass # TODO + + + def playCard(self, card, player): + """Play a card in the current play session. - hand = self.deal[player] - if self.playing.whoseTurn() is not player: - raise GameError('out of turn') - elif not self.playing.isValidPlay(card, player, hand): - raise GameError('invalid card') - - self.playing.playCard(card) + @param card: a Card object. + @param player: a BridgePlayer object. + """ + position = self.getPositionOfPlayer(player) + if position is None: + raise GameError, "Invalid player reference" + if not self.play or self.play.isComplete(): + raise GameError, "Game not running or play complete" + if self.getTurn() is self.play.dummy: # Dummy's turn. + if position is self.play.declarer: + position = self.play.dummy # Declarer can play dummy's cards. + elif position is self.play.dummy: + raise GameError, "Dummy cannot play cards" + elif self.getTurn() is not position: + raise GameError, "Card played out of turn" - def getHand(self, player, viewer=None): - """Returns the hand of specified player. + hand = self.board['deal'][position] # May be empty, if hand unknown. + if not self.play.isValidPlay(card, position, hand): + raise GameError, "Card cannot be played from hand" + + self.play.playCard(card) + self.notify('playCard', card=card, position=position) + + + def getHand(self, position, player): + """If specified hand is visible, returns the list of cards in hand. - If viewer player is specified, then the ability of viewer - to "see" the hand will be examined. + A hand is visible if one of the following conditions is met: + + 1. The hand is the player's own hand. + 2. The game is finished. + 3. The bidding is complete and the hand is dummy's, and first card of + first trick has been played. + + @param position: the hand identifier. + @type position: Direction + @param player: a player identifier. + @type player: BridgePlayer """ - if viewer and not self.isHandVisible(player, viewer): - raise GameError('hand not visible') - return self.deal[player] + viewer = self.getPositionOfPlayer(player) + if viewer is None: + raise GameError, "Invalid player reference" + if not self.inProgress() and self.bidding is None: + raise GameError, "No game in progress" + if player == viewer or not self.inProgress(): + return self.board.deal[position] + if self.bidding.isComplete() and position == self.play.dummy: + leader, cards = self.play.getTrick(0) + if len(cards) >= 1: + return self.board.deal[position] + # At this point, checks have been exhausted. + raise GameError, "Hand is not visible" + + + def getTurn(self): + if self.inProgress(): + if self.play: # Currently in the play. + return self.play.whoseTurn() + else: # Currently in the bidding. + return self.bidding.whoseTurn() + else: # Not in game. + raise GameError, "No game in progress" + + def getTrickCount(self): - """ + """Returns various - @return['declarerWon']: number of tricks won by declarer/dummy. + @return: a dictionary of result information. + @rtype: dict + + + ['declarerWon']: number of tricks won by declarer/dummy. @return['defenceWon']: number of tricks won by defenders. @return['declarerNeeds']: number of extra tricks required by declarer to make contract. @@ -125,16 +319,16 @@ @return['required']: number of tricks required from contract level. """ - if self.playing is None: - raise GameError('not in play') + if self.play is None: + raise GameError, "Not in play" count = dict.fromkeys(('declarerWon', 'declarerNeeds', 'defenceWon', 'defenceNeeds'), 0) - for index in range(len(self.playing.winners)): - trick = self.playing.getTrick(index) - winner = self.playing.whoPlayed(self.playing.winningCard(trick)) - if winner in (self.playing.declarer, self.playing.dummy): + for index in range(len(self.play.winners)): + trick = self.play.getTrick(index) + winner = self.play.whoPlayed(self.play.winningCard(trick)) + if winner in (self.play.declarer, self.play.dummy): count['declarerWon'] += 1 else: # Trick won by defenders. count ['defenceWon'] += 1 @@ -148,43 +342,78 @@ return count - def score(self): + def getScore(self): """Returns the integer score value for declarer/dummy if: - bidding stage has been passed out, with no bids made. - play stage is complete. """ - if not self.isComplete(): - raise GameError('game not complete') - elif self.bidding.isPassedOut(): + if self.inProgress() or self.bidding is None: + raise GameError, "Game not complete" + if self.bidding.isPassedOut(): return 0 # A passed out deal does not score. - else: - contract = self.bidding.getContract() - declarer = contract['declarer'] - dummy = Player[(declarer.index + 2) % 4] - vulnerable = (self.vulnNS and declarer in (Player.North, Player.South)) + \ - (self.vulnEW and declarer in (Player.West, Player.East)) - - tricksMade = 0 # Count of tricks won by declarer or dummy. - for index in range(len(self.playing.winners)): - trick = self.playing.getTrick(index) - winningCard = self.playing.winningCard(trick) - winner = self.playing.whoPlayed(winningCard) - tricksMade += winner in (declarer, dummy) - - result = {'contract' : contract, - 'tricksMade' : tricksMade, - 'vulnerable' : vulnerable, } - return self.scoring(result) + contract = self.bidding.getContract() + declarer = contract['declarer'] + dummy = Seat[(declarer.index + 2) % 4] + vulnerable = (self.vulnNS and declarer in (Seat.North, Seat.South)) + \ + (self.vulnEW and declarer in (Seat.West, Seat.East)) - def whoseTurn(self): - """Returns the player that is next to call or play card.""" - if not self.isComplete(): - if self.bidding.isComplete(): - return self.playing.whoseTurn() - else: - return self.bidding.whoseTurn() - else: - raise GameError('game complete') + tricksMade = 0 # Count of tricks won by declarer or dummy. + for index in range(len(self.play.winners)): + trick = self.play.getTrick(index) + winningCard = self.play.winningCard(trick) + winner = self.play.whoPlayed(winningCard) + tricksMade += winner in (declarer, dummy) + result = {'contract' : contract, + 'tricksMade' : tricksMade, + 'vulnerable' : vulnerable, } + return self.scoring(result) + + def getPositionOfPlayer(self, player): + """If player is playing, returns position of player, otherwise None. + + @param player: a BridgePlayer object. + @type player: BridgePlayer + @return: the position of player. + @rtype: Direction or None + """ + for position, p in self.players.items(): + if p == player: + return position + return None + + + + +class BridgePlayer(pb.Referenceable): + """Actor representing a player's view of a BridgeGame object.""" + + + def __init__(self, game): + self.__game = game # Provide access to game only through this object. + + + def makeCall(self, call): + self.__game.makeCall(call, player=self) + + + def playCard(self, card): + self.__game.playCard(card, player=self) + + + def nextGame(self): + pass + + +# Aliases for remote-callable methods. + + + def remote_makeCall(self, call): + self.makeCall(call) + + + def remote_playCard(self, card): + self.playCard(card) + Modified: trunk/pybridge/pybridge/network/error.py =================================================================== --- trunk/pybridge/pybridge/network/error.py 2007-03-27 15:33:14 UTC (rev 369) +++ trunk/pybridge/pybridge/network/error.py 2007-03-27 15:39:04 UTC (rev 370) @@ -33,3 +33,7 @@ Please report any bugs which you discover in PyBridge! """ + +class GameError(pb.Error): + """Raised by game in response to an unsatisfiable or erroneous request.""" + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-06-13 15:11:44
|
Revision: 415 http://svn.sourceforge.net/pybridge/?rev=415&view=rev Author: umgangee Date: 2007-06-13 08:11:39 -0700 (Wed, 13 Jun 2007) Log Message: ----------- Add a isNextGameReady() method to BridgeGame, add startNextGame() to BridgePlayer. Modified Paths: -------------- trunk/pybridge/pybridge/bridge/game.py trunk/pybridge/pybridge/interfaces/game.py Modified: trunk/pybridge/pybridge/bridge/game.py =================================================================== --- trunk/pybridge/pybridge/bridge/game.py 2007-06-11 13:10:16 UTC (rev 414) +++ trunk/pybridge/pybridge/bridge/game.py 2007-06-13 15:11:39 UTC (rev 415) @@ -26,6 +26,7 @@ from bidding import Bidding from board import Board from playing import Playing +from scoring import scoreDuplicate from call import Bid, Pass, Double, Redouble from card import Card @@ -72,8 +73,8 @@ def start(self, board=None): - if self.inProgress(): - raise GameError, "Game in progress" + if not self.isNextGameReady(): + raise GameError, "Not ready to start game" if board: # Use specified board. self.board = board @@ -103,6 +104,10 @@ return False + def isNextGameReady(self): + return (not self.inProgress()) and len(self.players) == 4 + + def getState(self): state = {} @@ -374,7 +379,7 @@ result = {'contract' : contract, 'tricksMade' : tricksMade, 'vulnerable' : vulnerable, } - return self.scoring(result) + return scoreDuplicate(result) @@ -384,7 +389,7 @@ def __init__(self, game): - self.__game = game # Provide access to game only through this object. + self.__game = game # Access to game is private to this object. def getHand(self): @@ -393,16 +398,21 @@ def makeCall(self, call): - return self.__game.makeCall(call, player=self) + try: + return self.__game.makeCall(call, player=self) + except TypeError, e: + raise GameError, e def playCard(self, card): - # TODO: need try/except block on each. - return self.__game.playCard(card, player=self) + try: + return self.__game.playCard(card, player=self) + except TypeError, e: + raise GameError, e - def nextGame(self): - pass + def startNextGame(self): + self.__game.start() # Raises GameError if not ready to start next game. # Aliases for remote-callable methods. @@ -410,4 +420,5 @@ remote_getHand = getHand remote_makeCall = makeCall remote_playCard = playCard + remote_startNextGame = startNextGame Modified: trunk/pybridge/pybridge/interfaces/game.py =================================================================== --- trunk/pybridge/pybridge/interfaces/game.py 2007-06-11 13:10:16 UTC (rev 414) +++ trunk/pybridge/pybridge/interfaces/game.py 2007-06-13 15:11:39 UTC (rev 415) @@ -84,6 +84,13 @@ """ + def isNextGameReady(self): + """Indicates whether the next game is ready to start. + + @return: True if next game is ready to start, False otherwise. + """ + + class ICardGame(IGame): """ICardGame defines methods specific to card games. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-06-13 15:14:21
|
Revision: 416 http://svn.sourceforge.net/pybridge/?rev=416&view=rev Author: umgangee Date: 2007-06-13 08:14:20 -0700 (Wed, 13 Jun 2007) Log Message: ----------- Move responsibility for starting games from LocalTable to BridgeGame, add appropriate functionality to WindowBridgetable. Modified Paths: -------------- trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotetable.py trunk/pybridge/pybridge/ui/window_bridgetable.py Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-06-13 15:11:39 UTC (rev 415) +++ trunk/pybridge/pybridge/network/localtable.py 2007-06-13 15:14:20 UTC (rev 416) @@ -145,11 +145,6 @@ self.players[position] = user self.notify('joinGame', player=user.name, position=position) - # If no game is active and all players are ready, start game. - if not self.game.inProgress(): - if len(self.players) == len(self.game.positions): - self.game.start() - return player Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-06-13 15:11:39 UTC (rev 415) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-06-13 15:14:20 UTC (rev 416) @@ -65,6 +65,8 @@ self.observers = state['observers'] self.players = state['players'] + for position in self.players: + self.game.addPlayer(position) # Implementation of ITable. @@ -115,11 +117,13 @@ def observe_joinGame(self, player, position): + self.game.addPlayer(position) self.players[position] = player self.notify('joinGame', player, position) def observe_leaveGame(self, player, position): + self.game.removePlayer(position) del self.players[position] self.notify('leaveGame', player, position) Modified: trunk/pybridge/pybridge/ui/window_bridgetable.py =================================================================== --- trunk/pybridge/pybridge/ui/window_bridgetable.py 2007-06-13 15:11:39 UTC (rev 415) +++ trunk/pybridge/pybridge/ui/window_bridgetable.py 2007-06-13 15:14:20 UTC (rev 416) @@ -297,7 +297,11 @@ dialog.run() dialog.destroy() + if self.player and self.table.game.isNextGameReady(): + d = self.player.callRemote('startNextGame') + d.addErrback(self.errback) + def redrawHand(self, position, all=False): """Redraws cards making up the hand at position. @@ -431,23 +435,21 @@ # Disable menu item corresponding to position. widget = self.takeseat_items[position] widget.set_property('sensitive', False) - # Set player label. - label = getattr(self, 'label_%s' % position.key.lower()) - label.set_markup('<b>%s</b>' % player) # If all positions occupied, disable Take Seat. if len(self.table.players.values()) == len(Direction): self.takeseat.set_property('sensitive', False) + if self.player and self.table.game.isNextGameReady(): + d = self.player.callRemote('startNextGame') + d.addErrback(self.errback) + def event_leaveGame(self, player, position): self.cardarea.set_player_name(position, None) # Enable menu item corresponding to position. widget = self.takeseat_items[position] widget.set_property('sensitive', True) - # Reset player label. - label = getattr(self, 'label_%s' % position.key.lower()) - label.set_markup('<i>%s</i>' % _('Position vacant')) # If we are not seated, ensure Take Seat is enabled. if self.position is None: @@ -504,7 +506,7 @@ self.redrawHand(position) - def event_messageReceived(self, message, sender, recipients): + def event_sendMessage(self, message, sender, recipients): buffer = self.chat_messagehistory.get_buffer() iter = buffer.get_end_iter() buffer.insert(iter, '%s: %s\n' % (sender, message)) @@ -562,8 +564,8 @@ self.player = player # RemoteReference to BridgePlayer object. self.position = position - self.takeseat.set_property('visible', False) - self.leaveseat.set_property('visible', True) + self.takeseat.set_property('sensitive', False) + self.leaveseat.set_property('sensitive', True) self.cardarea.set_player_mapping(self.position) @@ -576,7 +578,7 @@ if not self.table.game.bidding.isComplete(): bidbox = self.children.open(WindowBidbox, parent=self) bidbox.monitor(self.table.game, self.position, self.on_call_selected) - + d = self.table.joinGame(position) d.addCallbacks(success, self.errback) @@ -594,8 +596,8 @@ def success(r): self.player = None self.position = None - self.takeseat.set_property('visible', True) - self.leaveseat.set_property('visible', False) + self.takeseat.set_property('sensitive', True) + self.leaveseat.set_property('sensitive', False) if self.children.get(WindowBidbox): self.children.close(self.children[WindowBidbox]) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-06-22 15:33:14
|
Revision: 440 http://svn.sourceforge.net/pybridge/?rev=440&view=rev Author: umgangee Date: 2007-06-22 08:33:12 -0700 (Fri, 22 Jun 2007) Log Message: ----------- Use ConfigObj to manage database connection configuration, remove homebrew ConfigParser wrapper. Modified Paths: -------------- trunk/pybridge/pybridge/server/__init__.py trunk/pybridge/pybridge/server/database.py Added Paths: ----------- trunk/pybridge/pybridge/server/config.py Removed Paths: ------------- trunk/pybridge/pybridge/settings.py Modified: trunk/pybridge/pybridge/server/__init__.py =================================================================== --- trunk/pybridge/pybridge/server/__init__.py 2007-06-22 15:30:41 UTC (rev 439) +++ trunk/pybridge/pybridge/server/__init__.py 2007-06-22 15:33:12 UTC (rev 440) @@ -19,11 +19,13 @@ from twisted.cred import checkers, credentials, portal from twisted.spread import pb +import config +config.load() + from pybridge.server.checker import Checker from pybridge.server.realm import Realm from pybridge.server.server import Server - server = Server() realm = Realm() checker = Checker() Added: trunk/pybridge/pybridge/server/config.py =================================================================== --- trunk/pybridge/pybridge/server/config.py (rev 0) +++ trunk/pybridge/pybridge/server/config.py 2007-06-22 15:33:12 UTC (rev 440) @@ -0,0 +1,55 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# 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 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +""" +Manages PyBridge server configuration file. +""" + +from StringIO import StringIO +from configobj import ConfigObj +from validate import Validator + +import pybridge.environment as env + +# Config spec +spec = StringIO("""# PyBridge server configuration file + +[Database] + Engine = option('sqlite', 'sapdb', 'postgresql', 'firebird', 'maxdb', 'sybase', 'interbase', 'psycopg', 'mysql', 'mssql', 'postgres', default='sqlite') + DatabaseName = string # Or path to database file if using sqlite. + User = string # Not used with sqlite. + Password = string # Not used with sqlite. + Host = string # Leave empty for localhost. + Port = integer # Leave empty for default. +""") + + +config = None +val = Validator() + +def load(): + global config + filename = env.find_config_server('server.cfg') + config = ConfigObj(filename, create_empty=True, configspec=spec) + config.validate(val, copy=True) + +def save(): + global config + config.validate(val, copy=True) + config.write() + Modified: trunk/pybridge/pybridge/server/database.py =================================================================== --- trunk/pybridge/pybridge/server/database.py 2007-06-22 15:30:41 UTC (rev 439) +++ trunk/pybridge/pybridge/server/database.py 2007-06-22 15:33:12 UTC (rev 440) @@ -22,48 +22,51 @@ from sqlobject.inheritance import InheritableSQLObject from twisted.python import log -import pybridge.environment as env -from pybridge.settings import Settings +from config import config +from pybridge import environment as env -# TODO: when fields are not empty, avoid writing default values to fields. - -configfile = env.find_config_server('server.cfg') -settings = Settings(configfile, ['database']) - - # Initiate connection to the appropriate database backend. # See http://sqlobject.org/SQLObject.html#declaring-the-class # This code has been tested with the SQLite database backend. If you experience # problems with databases supported by SQLObject, please file a bug report. -backend = settings.database.get('backend', 'sqlite') # Default to SQLite. -settings.database['backend'] = backend +engine = config['Database'].get('Engine', 'sqlite') # Default to SQLite. -if backend == 'sqlite': - dbfile = settings.database.get('dbfile') - if dbfile is None: - dbfile = env.find_config_server('pybridge-server.db') - settings.database['dbfile'] = dbfile +if engine == 'sqlite': + dbfile = config['Database'].get('DatabaseName', + env.find_config_server('pybridge-server.db')) connection_string = "sqlite://" + dbfile + else: - username = settings.database.get('username', '') - password = settings.database.get('password', '') - hostname = settings.database.get('hostname', 'localhost') - dbname = settings.database.get('dbname', 'pybridge') - connection_string = "%s://%s:%s/%s" % (username, password, hostname, dbname) + username = config['Database'].get('Username', '') + password = config['Database'].get('Password', '') + host = config['Database'].get('Host', 'localhost') + port = config['Database'].get('Port', '') + dbname = config['Database'].get('DatabaseName', 'pybridge') -settings.save() + # Standard URI syntax (from http://sqlobject.org/SQLObject.html): + # scheme://[user[:password]@]host[:port]/database[?parameters] + connection_string = engine + '://' + if username: + connection_string += username + if password: + connection_string += ':' + password + connection_string += '@' + connection_string += host + if port: + connection_string += ':' + str(port) + connection_string += '/' + dbname try: connection = connectionForURI(connection_string) # TODO: fix for Win32. - log.msg("Connection to %s database succeeded" % backend) + log.msg("Connection to %s database succeeded" % engine) except Exception, e: log.err(e) log.msg("Could not connect to %s database with URI: %s" - % (backend, connection_string)) - log.msg("Please check configuration file %s" % configfile) + % (engine, connection_string)) + log.msg("Please check configuration file.") raise SystemExit # Database connection is required for server operation. sqlhub.processConnection = connection # Set all SQLObjects to use connection. Deleted: trunk/pybridge/pybridge/settings.py =================================================================== --- trunk/pybridge/pybridge/settings.py 2007-06-22 15:30:41 UTC (rev 439) +++ trunk/pybridge/pybridge/settings.py 2007-06-22 15:33:12 UTC (rev 440) @@ -1,53 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# 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 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -import ConfigParser - - -class Settings: - """A wrapper for ConfigParser.""" - - - def __init__(self, path, sections): - """Make section key/value pairs into attributes of this object. - - @param path: the location of configuration file to load. - @param sections: a list of section names to be made available. - """ - self._path = path - self._config = ConfigParser.SafeConfigParser() - self._config.read(path) - - for section in sections: - # Create sections if they do not exist. - if not self._config.has_section(section): - self._config.add_section(section) - # Make items in section available as - items = {} - for key, value in self._config.items(section): - items[key] = value - setattr(self, section.lower(), items) # self.<section> = items - - - def save(self): - """Writes contents of section/item dicts back to file.""" - for section in self._config.sections(): - for key, value in getattr(self, section.lower()).items(): - self._config.set(section, key, value) - self._config.write(file(self._path, 'w')) - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-09 11:47:33
|
Revision: 460 http://svn.sourceforge.net/pybridge/?rev=460&view=rev Author: umgangee Date: 2007-07-09 04:47:30 -0700 (Mon, 09 Jul 2007) Log Message: ----------- New Chat messaging class, providing generic chat services and replacing table-specific sendMessage(...) functionality. Undecided whether to retain LocalTableViewable.view_sendMessage(...), maintaining backwards-compatability with PyBridge 0.3. Modified Paths: -------------- trunk/pybridge/pybridge/interfaces/table.py trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotetable.py Added Paths: ----------- trunk/pybridge/pybridge/network/chat.py Modified: trunk/pybridge/pybridge/interfaces/table.py =================================================================== --- trunk/pybridge/pybridge/interfaces/table.py 2007-07-04 14:34:35 UTC (rev 459) +++ trunk/pybridge/pybridge/interfaces/table.py 2007-07-09 11:47:30 UTC (rev 460) @@ -60,12 +60,12 @@ """ - def sendMessage(self, message, sender, recipients): - """Issues message from sender to all named recipients, - or to all observers. - - @param message: message text string. - @param sender: user identifier of sender. - @param recipients: user identifiers of recipient observers. - """ +# def sendMessage(self, message, sender, recipients): +# """Issues message from sender to all named recipients, +# or to all observers. +# +# @param message: message text string. +# @param sender: user identifier of sender. +# @param recipients: user identifiers of recipient observers. +# """ Added: trunk/pybridge/pybridge/network/chat.py =================================================================== --- trunk/pybridge/pybridge/network/chat.py (rev 0) +++ trunk/pybridge/pybridge/network/chat.py 2007-07-09 11:47:30 UTC (rev 460) @@ -0,0 +1,213 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# 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 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import time +from twisted.internet import reactor +from twisted.spread import pb +from zope.interface import implements + +from pybridge.interfaces.observer import ISubject, IListener +from pybridge.network.error import DeniedRequest, IllegalRequest + + + +class Message(pb.Copyable, pb.RemoteCopy): + """A textual message, from a sender, to any number of recipients.""" + + + def __init__(self, text, sender, recipients): + self.text = text + self.sender = sender + self.recipients = recipients + self.time = time.localtime() + + + def getStateToCopy(self): + state = {} + state['text'] = self.text + state['sender'] = self.sender.name + state['recipients'] = [r.name for r in self.recipients] + state['time'] = tuple(self.time) + return state + + + def setCopyableState(self, state): + self.text = state['text'] + self.sender = state['sender'] + self.recipients = state.get('recipients', []) + self.time = time.struct_time(state['time']) + + +pb.setUnjellyableForClass(Message, Message) + + + + +class Chat: # TODO subclass observable? + """A simple chat facility, facilitating communication between users.""" + + implements(ISubject) + + + def __init__(self): + self.listeners = [] + + self.messages = [] # Ordered from oldest to newest. + self.moderators = [] + self.observers = [] # User identifiers. + + + def attach(self, listener): + self.listeners.append(listener) + + + def detach(self, listener): + self.listeners.remove(listener) + + + def notify(self, event, *args, **kwargs): + for listener in self.listeners: + listener.update(event, *args, **kwargs) + + + + +class LocalChat(Chat, pb.Cacheable): + + messageMaxLength = 1000 # An upper limit on the length of messages. + + + class ChatClient(pb.Viewable): + """A front-end for client users to interact with a LocalChat object.""" + + + def __init__(self, chat): + self.__chat = chat + + + def view_sendMessage(self, user, text, recipients=[]): + """Relays message to other users in chat. + + If the recipients argument is specified, only named recipients will + receive the message as a 'whisper'. Otherwise, all users in the chat + will recieve the 'shouted' message. + """ + if type(text) is not str: + raise IllegalRequest, "Expected str, got %s" % type(text) + # The user cannot be trusted to provide a valid Message object. + message = Message(text, user, recipients) + return self.__chat.send(message) + + + def view_censor(self, user, censorUser): + pass + + + def view_kickUser(self, user, kickUser): + """Eject a user from the chat and prevent them from re-joining. + + The user must be a moderator in the Chat object. + """ + pass + + + def __init__(self): + Chat.__init__(self) + self.observers = {} # User avatars mapped to RemoteChat references. + self.view = self.ChatClient(self) + + + def send(self, message): + # Validate message. + if message.text == '': + raise DeniedRequest, "Message must contain text" + if len(message.text) > self.messageMaxLength: + raise DeniedRequest, "Message text longer than character limit" + # TODO: filter message for spam, obscenity, ... + + if message.recipients: # Specified recipients. + sendTo = [p for p in self.observers if p.name in message.recipients] # O(n^2) + if sendTo == []: + raise DeniedRequest, "No named recipient present at table." + else: + sendTo = self.observers.keys() + + self.messages.append(message) + self.notify('gotMessage', message=message) + # return defer.succeed(message) + + + def notify(self, event, *args, **kwargs): + Chat.notify(self, event, *args, **kwargs) + # For all observers, calls event handler with provided arguments. + for obs in self.observers.values(): + # Event handlers are called on the next iteration of the reactor, + # to allow the caller of this method to return a result. + reactor.callLater(0, obs.callRemote, event, *args, **kwargs) + + + def getStateToCacheAndObserveFor(self, perspective, observer): + self.notify('addObserver', perspective.name) + self.observers[perspective] = observer + + state = {} + state['messages'] = [] # TODO: supply all messages? + state['observers'] = [p.name for p in self.observers] + state['view'] = self.view + + return state + + + def stoppedObserving(self, perspective, observer): + del self.observers[perspective] + self.notify('removeObserver', perspective.name) + + + + +class RemoteChat(Chat, pb.RemoteCache): + + + def setCopyableState(self, state): + self.messages = state['messages'] + self.observers = state['observers'] + self.__view = state['view'] + + + def send(self, text): + d = self.__view.callRemote('sendMessage', text) + return d + + + def observe_gotMessage(self, message): + self.messages.append(message) + self.notify('gotMessage', message) + + + def observe_addObserver(self, observer): + self.observers.append(observer) + self.notify('addObserver', observer) + + + def observe_removeObserver(self, observer): + self.observers.remove(observer) + self.notify('removeObserver', observer) + + +pb.setUnjellyableForClass(LocalChat, RemoteChat) + Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-07-04 14:34:35 UTC (rev 459) +++ trunk/pybridge/pybridge/network/localtable.py 2007-07-09 11:47:30 UTC (rev 460) @@ -25,7 +25,9 @@ from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest +from chat import LocalChat + class LocalTable(pb.Cacheable): """An implementation of ITable suitable for server-side table instances. @@ -45,6 +47,7 @@ self.gametype = gametype self.game = gametype() # Initialise game. self.game.attach(self) # Listen for game events. + self.chat = LocalChat() self.observers = {} # For each user perspective, a remote ITableEvents. self.players = {} # Positions mapped to perspectives of game players. @@ -66,6 +69,7 @@ # Build a dict of public information about the table. state = {} state['id'] = self.id + state['chat'] = self.chat state['gametype'] = self.gametype.__name__ state['gamestate'] = self.game.getState() state['observers'] = [p.name for p in self.observers.keys()] @@ -160,23 +164,8 @@ self.notify('leaveGame', player=user.name, position=position) - def sendMessage(self, message, sender, recipients): - names = [perspective.name for perspective in self.observers.keys()] - if recipients: # Translate user names to their observer objects. - # Remove user names without a perspective object observing table. - recipients = [name for name in recipients if name in names] - sendTo = [o for p, o in self.observers.items() if p.name in recipients] - else: # Broadcast message to all observers. - recipients = names - sendTo = self.observers.values() - for observer in sendTo: - self.notifyObserver(observer, 'sendMessage', message=message, - sender=sender.name, recipients=recipients) - - - class LocalTableViewable(pb.Viewable): """Provides a public front-end to an instantiated LocalTable. @@ -190,19 +179,14 @@ @param table: a instantiated LocalTable. """ - self.table = table + self.__table = table def view_joinGame(self, user, position): # TODO: return a deferred? - return self.table.joinGame(user, position) + return self.__table.joinGame(user, position) def view_leaveGame(self, user, position): - return self.table.leaveGame(user, position) + return self.__table.leaveGame(user, position) - - def view_sendMessage(self, user, message, sender=None, recipients=[]): - return self.table.sendMessage(message, sender=user, - recipients=recipients) - Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-07-04 14:34:35 UTC (rev 459) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-07-09 11:47:30 UTC (rev 460) @@ -43,11 +43,13 @@ info = property(lambda self: {'game': self.gametype.__name__}) + # TODO: is there any need for this initialisation? def __init__(self): self.master = None # Server-side ITable object. self.listeners = [] self.id = None + self.chat = None self.game = None self.gametype = None self.observers = [] # Observers of master table. @@ -63,6 +65,7 @@ else: raise NameError, "Unknown game type %s" % state['gametype'] + self.chat = state['chat'] self.observers = state['observers'] self.players = state['players'] for position in self.players: @@ -82,11 +85,6 @@ return d - def sendMessage(self, message, sender=None, recipients=[]): - d = self.master.callRemote('sendMessage', message, recipients) - return d - - # Implementation of ISubject. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-09 14:17:46
|
Revision: 464 http://svn.sourceforge.net/pybridge/?rev=464&view=rev Author: umgangee Date: 2007-07-09 07:17:49 -0700 (Mon, 09 Jul 2007) Log Message: ----------- Move bridge-specific UI components to bridge/ui/. Added Paths: ----------- trunk/pybridge/pybridge/bridge/ui/window_bidbox.py trunk/pybridge/pybridge/bridge/ui/window_bridgetable.py Removed Paths: ------------- trunk/pybridge/pybridge/ui/window_bidbox.py trunk/pybridge/pybridge/ui/window_bridgetable.py Copied: trunk/pybridge/pybridge/bridge/ui/window_bidbox.py (from rev 463, trunk/pybridge/pybridge/ui/window_bidbox.py) =================================================================== --- trunk/pybridge/pybridge/bridge/ui/window_bidbox.py (rev 0) +++ trunk/pybridge/pybridge/bridge/ui/window_bidbox.py 2007-07-09 14:17:49 UTC (rev 464) @@ -0,0 +1,153 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# 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 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import gtk + +from pybridge.ui.config import config +from pybridge.ui.eventhandler import SimpleEventHandler +from pybridge.ui.vocabulary import * + +import pybridge.bridge.call as Call + + +class WindowBidbox(object): + """The bidding box is presented to a player, during bidding. + + Each call (bid, pass, double or redouble) is displayed as a button. + When it is the player's turn to bid, a call is made by clicking the + corresponding button. Unavailable calls are shown greyed-out. + """ + + + def __init__(self, parent=None): + self.window = gtk.Window() + if parent: + self.window.set_transient_for(parent.window) + self.window.set_title(_('Bidding Box')) + self.window.connect('delete_event', self.on_delete_event) + self.window.set_resizable(False) + + self.callButtons = {} + self.callSelectHandler = None # A method to invoke when a call is clicked. + self.eventHandler = SimpleEventHandler(self) + self.table = None + self.position = None + + def buildButtonFromCall(call, markup): + button = gtk.Button() + button.set_relief(gtk.RELIEF_NONE) + button.connect('clicked', self.on_call_clicked, call) + # A separate label is required for marked-up text. + label = gtk.Label() + label.set_markup(markup) + label.set_use_markup(True) + button.add(label) + self.callButtons[call] = button + return button + + vbox = gtk.VBox() + + bidtable = gtk.Table(rows=7, columns=5, homogeneous=True) + vbox.pack_start(bidtable) + # Build buttons for all bids. + for y, level in enumerate(Call.Level): + for x, strain in enumerate(Call.Strain): + bid = Call.Bid(level, strain) + markup = render_call(bid) + xy = (x, x+1, y, y+1) + bidtable.attach(buildButtonFromCall(bid, markup), *xy) + + vbox.pack_start(gtk.HSeparator()) + + otherbox = gtk.HBox() + vbox.pack_start(otherbox) + # Build buttons for other calls. + othercalls = [(Call.Pass(), render_call_name, True), + (Call.Double(), render_call, False), + (Call.Redouble(), render_call, False)] + for call, renderer, expand in othercalls: + markup = renderer(call) + otherbox.pack_start(buildButtonFromCall(call, markup), expand) + + self.window.add(vbox) + self.window.show_all() + + + def tearDown(self): + if self.table: + self.table.game.detach(self.eventHandler) + self.table = None # Dereference table. + + + def setCallSelectHandler(self, handler): + """Provide a method to invoke when user selects a call. + + @param handler: a method accepting a call argument. + @type handler: function + """ + self.callSelectHandler = handler + + + def setTable(self, table, position): + """Monitor the state of bidding in game at specified table. + + @param table: the BridgeGame for which to observe bidding session. + @param: + """ + if self.table: + self.table.game.detach(self.eventHandler) + + self.table = table + self.table.game.attach(self.eventHandler) + self.position = position + + self.enableCalls() + + +# Event handlers. + + + def event_makeCall(self, call, position): + self.enableCalls() + + +# Utility methods. + + + def enableCalls(self): + """Enables buttons representing the calls available to player.""" + if self.position == self.table.game.getTurn(): + self.window.set_property('sensitive', True) + for call, button in self.callButtons.items(): + isvalid = self.table.game.bidding.isValidCall(call) + button.set_property('sensitive', isvalid) + else: + self.window.set_property('sensitive', False) + + +# Signal handlers. + + + def on_call_clicked(self, widget, call): + if self.callSelectHandler: + self.callSelectHandler(call) # Invoke external handler. + + + def on_delete_event(self, widget, *args): + return True # Stops window deletion taking place. + Copied: trunk/pybridge/pybridge/bridge/ui/window_bridgetable.py (from rev 463, trunk/pybridge/pybridge/ui/window_bridgetable.py) =================================================================== --- trunk/pybridge/pybridge/bridge/ui/window_bridgetable.py (rev 0) +++ trunk/pybridge/pybridge/bridge/ui/window_bridgetable.py 2007-07-09 14:17:49 UTC (rev 464) @@ -0,0 +1,605 @@ +# PyBridge -- online contract bridge made easy. +# Copyright (C) 2004-2007 PyBridge Project. +# +# 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 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + +import gtk + +from pybridge.network.client import client +from pybridge.network.error import GameError + +from pybridge.ui.cardarea import CardArea +from pybridge.ui.config import config +from pybridge.ui.eventhandler import SimpleEventHandler +from pybridge.ui.manager import WindowManager +from pybridge.ui.vocabulary import * + +from pybridge.ui.window_gametable import WindowGameTable +from window_bidbox import WindowBidbox + + +class BiddingView(gtk.TreeView): + """A view of all calls made in an auction.""" + + + def __init__(self): + gtk.TreeView.__init__(self) + self.set_rules_hint(True) + + self.store = gtk.ListStore(str, str, str, str) + self.set_model(self.store) + self.clear = self.store.clear + renderer = gtk.CellRendererText() + renderer.set_property('size-points', 12) + renderer.set_property('xalign', 0.5) + + # Set up columns, each corresponding to a position. + for index, position in enumerate(Direction): + title = DIRECTION_NAMES[position] + column = gtk.TreeViewColumn(title, renderer, markup=index) + self.append_column(column) + + + def add_call(self, call, position): + """Adds call from specified position.""" + column = position.index + if column == 0 or self.store.get_iter_first() == None: + iter = self.store.append() + else: # Get bottom row. There must be a better way than this... + iter = self.store.get_iter_first() + while self.store.iter_next(iter) != None: + iter = self.store.iter_next(iter) + + format = render_call(call) + self.store.set(iter, column, format) + + + + +class ScoreView(gtk.TreeView): + """A display of contracts bid, their results and their scores.""" + + + def __init__(self): + gtk.TreeView.__init__(self) + self.set_rules_hint(True) + + self.store = gtk.ListStore(str, str, str, str) + self.set_model(self.store) + self.clear = self.store.clear + renderer = gtk.CellRendererText() + + for index, title in enumerate([_('Contract'), _('Made'), _('N/S'), _('E/W')]): + column = gtk.TreeViewColumn(title, renderer, markup=index) + self.append_column(column) + + + def add_score(self, game): + declarerWon, defenceWon = game.play.getTrickCount() + score = game.getScore() + + textContract = render_contract(game.contract) + textMade = str(declarerWon) + if game.contract['declarer'] in (Direction.North, Direction.South) and score > 0 \ + or game.contract['declarer'] in (Direction.East, Direction.West) and score < 0: + textNS, textEW = str(abs(score)), '' + else: + textNS, textEW = '', str(abs(score)) + + self.store.prepend([textContract, textMade, textNS, textEW]) + + + + +class BridgeDashboard(gtk.VBox): + """An at-a-glance display of the state of a bridge game.""" + + + def __init__(self): + gtk.VBox.__init__(self) + self.set_spacing(4) + + self.contract = gtk.Label() + self.pack_start(self.contract) + + hbox = gtk.HBox() + hbox.set_homogeneous(True) + hbox.set_spacing(6) + self.declarer_tricks = gtk.Label() + frame = gtk.Frame() + frame.set_label(_('Declarer')) + frame.set_label_align(0.5, 0.5) + frame.add(self.declarer_tricks) + hbox.pack_start(frame) + self.defence_tricks = gtk.Label() + frame = gtk.Frame() + frame.set_label(_('Defence')) + frame.set_label_align(0.5, 0.5) + frame.add(self.defence_tricks) + hbox.pack_start(frame) + self.pack_start(hbox) + + hbox = gtk.HBox() + hbox.set_homogeneous(True) + hbox.set_spacing(6) + # TODO: display board number? + self.dealer = gtk.Label() + self.dealer.set_alignment(0, 0.5) + hbox.pack_start(self.dealer) + self.vulnerability = gtk.Label() + self.vulnerability.set_alignment(0, 0.5) + hbox.pack_start(self.vulnerability) + self.pack_start(hbox) + + + def set_contract(self, game): + if game.contract: + text = render_contract(game.contract) + else: + text = _('No contract') + self.contract.set_markup("<span size='x-large'>%s</span>" % text) + + + def set_trickcount(self, game): + if game.play: + declarer, defence = game.play.getTrickCount() + required = game.contract['bid'].level.index + 7 + declarerNeeds = max(0, required - declarer) + defenceNeeds = max(0, 13 + 1 - required - defence) + else: + declarer, defence, declarerNeeds, defenceNeeds = 0, 0, 0, 0 + format = "<span size='x-large'><b>%s</b> (%s)</span>" + self.declarer_tricks.set_markup(format % (declarer, declarerNeeds)) + self.defence_tricks.set_markup(format % (defence, defenceNeeds)) + + + def set_dealer(self, game): + if game.inProgress(): + dealertext = "<b>%s</b>" % DIRECTION_NAMES[game.board['dealer']] + else: + dealertext = '' + self.dealer.set_markup(_('Dealer') + ': ' + dealertext) + + + def set_vulnerability(self, game): + if game.inProgress(): + vulntext = "<b>%s</b>" % VULN_SYMBOLS[game.board['vuln']] + else: + vulntext = '' + self.vulnerability.set_markup(_('Vuln') + ': ' + vulntext) + + + + +class WindowBridgeTable(WindowGameTable): + + gametype = _('Contract Bridge') + + stockdirections = [gtk.STOCK_GO_UP, gtk.STOCK_GO_FORWARD, + gtk.STOCK_GO_DOWN, gtk.STOCK_GO_BACK] + + + def setUp(self): + super(WindowBridgeTable, self).setUp() + + # Set up menu attached to 'Take Seat' toolbar button. + self.takeseat_menuitems = {} + menu = gtk.Menu() + for position, stock in zip(Direction, self.stockdirections): + item = gtk.ImageMenuItem(DIRECTION_NAMES[position], True) + item.set_image(gtk.image_new_from_stock(stock, gtk.ICON_SIZE_MENU)) + item.connect('activate', self.on_takeseat_clicked, position) + item.show() + menu.append(item) + self.takeseat_menuitems[position] = item + self.takeseat.set_menu(menu) + + # Set up CardArea widget. + self.cardarea = CardArea() + self.cardarea.on_card_clicked = self.on_card_clicked + self.cardarea.on_hand_clicked = self.on_hand_clicked + self.cardarea.set_size_request(640, 480) + self.gamearea.add(self.cardarea) + + # Set up sidebar. + self.dashboard = BridgeDashboard() + self.sidebar.pack_start(self.dashboard, expand=False) + + self.biddingview = BiddingView() + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + sw.add(self.biddingview) + frame = gtk.Frame() + frame.add(sw) + exp = gtk.Expander(_('Bidding')) + exp.set_expanded(True) + exp.add(frame) + self.sidebar.pack_start(exp) + +# self.lasttrick = CardArea() +# frame = gtk.Frame() +# frame.add(self.lasttrick) +# exp = gtk.Expander(_('Last Trick')) +# exp.set_expanded(True) +# exp.add(frame) +# self.sidebar.pack_start(exp) + + self.scoreview = ScoreView() + sw = gtk.ScrolledWindow() + sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + sw.add(self.scoreview) + frame = gtk.Frame() + frame.add(sw) + exp = gtk.Expander(_('Score Sheet')) + exp.set_expanded(False) + exp.add(frame) + self.sidebar.pack_start(exp) + + + def errback(self, failure): + print "Error: %s" % failure.getErrorMessage() + #print failure.getBriefTraceback() + + + def setTable(self, table): + """Changes display to match the table specified. + + @param table: the (now) focal table. + """ + super(WindowBridgeTable, self).setTable(table) + + self.table.game.attach(self.eventHandler) + self.resetGame() + + for position in Direction: + self.redrawHand(position) # Redraw all hands. + + if self.table.game.inProgress(): + # If trick play in progress, redraw trick. + if self.table.game.play: + self.redrawTrick() + self.setTurnIndicator() + + for call in self.table.game.bidding.calls: + position = self.table.game.bidding.whoCalled(call) + self.biddingview.add_call(call, position) + + # If playing, set trick counts. + if self.table.game.play: + for position, cards in self.table.game.play.played.items(): + for card in cards: + self.addCard(card, position) + + # If user is a player and bidding in progress, open bidding box. + if self.player and not self.table.game.bidding.isComplete(): + bidbox = self.children.open(WindowBidbox, parent=self) + bidbox.setCallSelectHandler(self.on_call_selected) + bidbox.setTable(self.table, self.position) + + + # Initialise seat menu and player labels. + for position in Direction: + player = self.table.players.get(position) # Player name or None. + + avail = player is None or position == self.position + self.takeseat_menuitems[position].set_property('sensitive', avail) + # If player == None, this unsets player name. + self.cardarea.set_player_name(position, player) + + + def resetGame(self): + """Clear bidding history, contract, trick counts.""" + self.cardarea.clear() + self.biddingview.clear() # Reset bidding history. + + self.dashboard.set_contract(self.table.game) + self.dashboard.set_trickcount(self.table.game) + self.dashboard.set_dealer(self.table.game) + self.dashboard.set_vulnerability(self.table.game) + + + def addCard(self, card, position): + """""" +# position = self.table.game.play.whoPlayed(card) +# column = position.index +# row = self.table.game.play.played[position].index(card) +# +# if self.trick_store.get_iter_first() == None: +# self.trick_store.append() +# iter = self.trick_store.get_iter_first() +# for i in range(row): +# iter = self.trick_store.iter_next(iter) +# if iter is None: +# iter = self.trick_store.append() +# +# format = render_card(card) +# self.trick_store.set(iter, column, format) + + + def gameComplete(self): + # Display all previously revealed hands - the server will reveal the others. + for position in self.table.game.visibleHands: + self.redrawHand(position, all=True) + + self.setTurnIndicator() + + dialog = gtk.MessageDialog(parent=self.window, type=gtk.MESSAGE_INFO) + dialog.set_title(_('Game result')) + + # Determine and display score in dialog box and score sheet. + if self.table.game.contract: + self.scoreview.add_score(self.table.game) + + declarerWon, defenceWon = self.table.game.play.getTrickCount() + required = self.table.game.contract['bid'].level.index + 7 + offset = declarerWon - required + score = self.table.game.getScore() + + fields = {'contract': render_contract(self.table.game.contract), + 'offset': abs(offset) } + if offset > 0: + if offset == 1: + resultText = _('Contract %(contract)s made by 1 trick.') % fields + else: + resultText = _('Contract %(contract)s made by %(offset)s tricks.') % fields + elif offset < 0: + if offset == -1: + resultText = _('Contract %(contract)s failed by 1 trick.') % fields + else: + resultText = _('Contract %(contract)s failed by %(offset)s tricks.') % fields + else: + resultText = _('Contract %(contract)s made exactly.') % fields + + pair = (score >= 0 and _('declarer')) or _('defence') + scoreText = _('Score %(points)s points for %(pair)s.') % {'points': abs(score), 'pair': pair} + + dialog.set_markup(resultText + '\n' + scoreText) + + else: + dialog.set_markup(_('Bidding passed out.')) + dialog.format_secondary_text(_('No score.')) + + if self.player: + dialog.add_button(_('Leave Seat'), gtk.RESPONSE_CANCEL) + dialog.format_secondary_text(_('Click OK to start next game.')) + dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + dialog.set_default_response(gtk.RESPONSE_OK) + # If user leaves table (ie. closes window), close dialog as well. + dialog.set_transient_for(self.window) + dialog.set_destroy_with_parent(True) + + def dialog_response_cb(dialog, response_id): + dialog.destroy() + if self.player: + if response_id == gtk.RESPONSE_OK and self.table.game.isNextGameReady(): + d = self.player.callRemote('startNextGame') + d.addErrback(self.errback) + elif response_id == gtk.RESPONSE_CANCEL: + self.on_leaveseat_clicked(dialog) + + dialog.connect('response', dialog_response_cb) + dialog.show() + + + def redrawHand(self, position, all=False): + """Redraws cards making up the hand at position. + + Cards played are filtered out and omitted from display. + Unknown cards are displayed face down. + + @param position: + @param all: If True, do not filter out cards played. + """ + try: + hand = self.table.game.getHand(position) + facedown = False + except GameError: # Unknown hand. + hand = range(13) + facedown = True + + if all is True or self.table.game.play is None: + available = hand + else: + played = self.table.game.play.played[position] + if facedown: # Draw cards face down for unknown hand. + available = range(13 - len(played)) + else: + available = [card for card in hand if card not in played] + + self.cardarea.set_hand(hand, position, facedown, visible=available) + + + def redrawTrick(self): + """Redraws trick. + + @param table: + @param trick: + """ + trick = None + if self.table.game.play: + trick = self.table.game.play.getCurrentTrick() + self.cardarea.set_trick(trick) + + + def setTurnIndicator(self): + """Sets the statusbar text to indicate which player is on turn.""" + context = self.statusbar.get_context_id('turn') + self.statusbar.pop(context) + + try: + turn = self.table.game.getTurn() + + if self.table.game.play: + declarer, dummy = self.table.game.play.declarer, self.table.game.play.dummy + if self.position and self.position == turn != dummy: + text = _("Play a card from your hand.") + elif self.position and self.position == declarer and turn == dummy: + text = _("Play a card from dummy's hand.") + else: + text = _("It is %s's turn to play a card.") % DIRECTION_NAMES[turn] + + else: # Bidding. + if self.position and self.position == turn: + text = _("Make a call from the bidding box.") + else: + text = _("It is %s's turn to make a call.") % DIRECTION_NAMES[turn] + + except GameError: # Game not in progress. + text = _("Waiting for next game to start.") + + self.statusbar.push(context, text) + + +# Registered event handlers. + + + def event_joinGame(self, player, position): + self.cardarea.set_player_name(position, player) + # Disable menu item corresponding to position. + widget = self.takeseat_menuitems[position] + widget.set_property('sensitive', False) + + # If all positions occupied, disable Take Seat. + if len(self.table.players.values()) == len(Direction): + self.takeseat.set_property('sensitive', False) + + if self.player and self.table.game.isNextGameReady(): + d = self.player.callRemote('startNextGame') + d.addErrback(self.errback) + + + def event_leaveGame(self, player, position): + self.cardarea.set_player_name(position, None) + # Enable menu item corresponding to position. + widget = self.takeseat_menuitems[position] + widget.set_property('sensitive', True) + + # If we are not seated, ensure Take Seat is enabled. + if self.position is None: + self.takeseat.set_property('sensitive', True) + + + def event_start(self, board): + self.resetGame() + + # Re-initialise player labels. + # TODO: shouldn't need to do this. + for position in Direction: + player = self.table.players.get(position) # Player name or None. + self.cardarea.set_player_name(position, player) + + self.redrawTrick() # Clear trick. + for position in Direction: + self.redrawHand(position) + + self.setTurnIndicator() + self.dashboard.set_dealer(self.table.game) + self.dashboard.set_vulnerability(self.table.game) + + if self.player: + d = self.player.callRemote('getHand') + # When user's hand is returned, reveal it to client-side Game. + d.addCallbacks(self.table.game.revealHand, self.errback, + callbackKeywords={'position': self.position}) + bidbox = self.children.open(WindowBidbox, parent=self) + bidbox.setCallSelectHandler(self.on_call_selected) + bidbox.setTable(self.table, self.position) + + + def event_makeCall(self, call, position): + self.biddingview.add_call(call, position) + self.setTurnIndicator() + + if self.table.game.bidding.isComplete(): + self.dashboard.set_contract(self.table.game) + if self.children.get(WindowBidbox): # If a player. + self.children.close(self.children[WindowBidbox]) + + if not self.table.game.inProgress(): + self.gameComplete() + + + def event_playCard(self, card, position): + # Determine the position of the hand from which card was played. + playfrom = self.table.game.play.whoPlayed(card) + self.addCard(card, playfrom) + self.setTurnIndicator() + self.dashboard.set_trickcount(self.table.game) + self.redrawTrick() + self.redrawHand(playfrom) + + if not self.table.game.inProgress(): + self.gameComplete() + + + def event_revealHand(self, hand, position): + all = not self.table.game.inProgress() + self.redrawHand(position, all) # Show all cards if game has finished. + + +# Signal handlers. + + + def on_call_selected(self, call): + if self.player: + d = self.player.callRemote('makeCall', call) + d.addErrback(self.errback) + + + def on_hand_clicked(self, position): + if not self.player and not self.table.players.get(position): + # Join game at position. + self.on_takeseat_clicked(self.cardarea, position) + + + def on_card_clicked(self, card, position): + if self.player: + if self.table.game.inProgress() and self.table.game.play: + d = self.player.callRemote('playCard', card) + d.addErrback(self.errback) + + + def on_takeseat_clicked(self, widget, position=None): + + def success(r): + self.cardarea.set_player_mapping(self.position) + if self.table.game.inProgress(): + d = self.player.callRemote('getHand') + d.addCallbacks(self.table.game.revealHand, self.errback, + callbackKeywords={'position' : self.position}) + # If game is running and bidding is active, open bidding box. + if not self.table.game.bidding.isComplete(): + bidbox = self.children.open(WindowBidbox, parent=self) + bidbox.setCallSelectHandler(self.on_call_selected) + bidbox.setTable(self.table, self.position) + + # TODO: match user up with preferred partner. + if position is None: + # No position specified by user: choose an arbitary position. + position = [p for p in Direction if p not in self.table.players][0] + d = super(WindowBridgeTable, self).on_takeseat_clicked(widget, position) + d.addCallback(success) + + + def on_leaveseat_clicked(self, widget, *args): + + def success(r): + if self.children.get(WindowBidbox): + self.children.close(self.children[WindowBidbox]) + + d = super(WindowBridgeTable, self).on_leaveseat_clicked(widget, *args) + d.addCallback(success) + Deleted: trunk/pybridge/pybridge/ui/window_bidbox.py =================================================================== --- trunk/pybridge/pybridge/ui/window_bidbox.py 2007-07-09 14:10:29 UTC (rev 463) +++ trunk/pybridge/pybridge/ui/window_bidbox.py 2007-07-09 14:17:49 UTC (rev 464) @@ -1,153 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# 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 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -import gtk - -from pybridge.ui.config import config -from pybridge.ui.eventhandler import SimpleEventHandler -from pybridge.ui.vocabulary import * - -import pybridge.bridge.call as Call - - -class WindowBidbox(object): - """The bidding box is presented to a player, during bidding. - - Each call (bid, pass, double or redouble) is displayed as a button. - When it is the player's turn to bid, a call is made by clicking the - corresponding button. Unavailable calls are shown greyed-out. - """ - - - def __init__(self, parent=None): - self.window = gtk.Window() - if parent: - self.window.set_transient_for(parent.window) - self.window.set_title(_('Bidding Box')) - self.window.connect('delete_event', self.on_delete_event) - self.window.set_resizable(False) - - self.callButtons = {} - self.callSelectHandler = None # A method to invoke when a call is clicked. - self.eventHandler = SimpleEventHandler(self) - self.table = None - self.position = None - - def buildButtonFromCall(call, markup): - button = gtk.Button() - button.set_relief(gtk.RELIEF_NONE) - button.connect('clicked', self.on_call_clicked, call) - # A separate label is required for marked-up text. - label = gtk.Label() - label.set_markup(markup) - label.set_use_markup(True) - button.add(label) - self.callButtons[call] = button - return button - - vbox = gtk.VBox() - - bidtable = gtk.Table(rows=7, columns=5, homogeneous=True) - vbox.pack_start(bidtable) - # Build buttons for all bids. - for y, level in enumerate(Call.Level): - for x, strain in enumerate(Call.Strain): - bid = Call.Bid(level, strain) - markup = render_call(bid) - xy = (x, x+1, y, y+1) - bidtable.attach(buildButtonFromCall(bid, markup), *xy) - - vbox.pack_start(gtk.HSeparator()) - - otherbox = gtk.HBox() - vbox.pack_start(otherbox) - # Build buttons for other calls. - othercalls = [(Call.Pass(), render_call_name, True), - (Call.Double(), render_call, False), - (Call.Redouble(), render_call, False)] - for call, renderer, expand in othercalls: - markup = renderer(call) - otherbox.pack_start(buildButtonFromCall(call, markup), expand) - - self.window.add(vbox) - self.window.show_all() - - - def tearDown(self): - if self.table: - self.table.game.detach(self.eventHandler) - self.table = None # Dereference table. - - - def setCallSelectHandler(self, handler): - """Provide a method to invoke when user selects a call. - - @param handler: a method accepting a call argument. - @type handler: function - """ - self.callSelectHandler = handler - - - def setTable(self, table, position): - """Monitor the state of bidding in game at specified table. - - @param table: the BridgeGame for which to observe bidding session. - @param: - """ - if self.table: - self.table.game.detach(self.eventHandler) - - self.table = table - self.table.game.attach(self.eventHandler) - self.position = position - - self.enableCalls() - - -# Event handlers. - - - def event_makeCall(self, call, position): - self.enableCalls() - - -# Utility methods. - - - def enableCalls(self): - """Enables buttons representing the calls available to player.""" - if self.position == self.table.game.getTurn(): - self.window.set_property('sensitive', True) - for call, button in self.callButtons.items(): - isvalid = self.table.game.bidding.isValidCall(call) - button.set_property('sensitive', isvalid) - else: - self.window.set_property('sensitive', False) - - -# Signal handlers. - - - def on_call_clicked(self, widget, call): - if self.callSelectHandler: - self.callSelectHandler(call) # Invoke external handler. - - - def on_delete_event(self, widget, *args): - return True # Stops window deletion taking place. - Deleted: trunk/pybridge/pybridge/ui/window_bridgetable.py =================================================================== --- trunk/pybridge/pybridge/ui/window_bridgetable.py 2007-07-09 14:10:29 UTC (rev 463) +++ trunk/pybridge/pybridge/ui/window_bridgetable.py 2007-07-09 14:17:49 UTC (rev 464) @@ -1,605 +0,0 @@ -# PyBridge -- online contract bridge made easy. -# Copyright (C) 2004-2007 PyBridge Project. -# -# 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 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - -import gtk - -from pybridge.network.client import client -from pybridge.network.error import GameError - -from pybridge.ui.cardarea import CardArea -from pybridge.ui.config import config -from pybridge.ui.eventhandler import SimpleEventHandler -from pybridge.ui.manager import WindowManager -from pybridge.ui.vocabulary import * - -from pybridge.ui.window_gametable import WindowGameTable -from window_bidbox import WindowBidbox - - -class BiddingView(gtk.TreeView): - """A view of all calls made in an auction.""" - - - def __init__(self): - gtk.TreeView.__init__(self) - self.set_rules_hint(True) - - self.store = gtk.ListStore(str, str, str, str) - self.set_model(self.store) - self.clear = self.store.clear - renderer = gtk.CellRendererText() - renderer.set_property('size-points', 12) - renderer.set_property('xalign', 0.5) - - # Set up columns, each corresponding to a position. - for index, position in enumerate(Direction): - title = DIRECTION_NAMES[position] - column = gtk.TreeViewColumn(title, renderer, markup=index) - self.append_column(column) - - - def add_call(self, call, position): - """Adds call from specified position.""" - column = position.index - if column == 0 or self.store.get_iter_first() == None: - iter = self.store.append() - else: # Get bottom row. There must be a better way than this... - iter = self.store.get_iter_first() - while self.store.iter_next(iter) != None: - iter = self.store.iter_next(iter) - - format = render_call(call) - self.store.set(iter, column, format) - - - - -class ScoreView(gtk.TreeView): - """A display of contracts bid, their results and their scores.""" - - - def __init__(self): - gtk.TreeView.__init__(self) - self.set_rules_hint(True) - - self.store = gtk.ListStore(str, str, str, str) - self.set_model(self.store) - self.clear = self.store.clear - renderer = gtk.CellRendererText() - - for index, title in enumerate([_('Contract'), _('Made'), _('N/S'), _('E/W')]): - column = gtk.TreeViewColumn(title, renderer, markup=index) - self.append_column(column) - - - def add_score(self, game): - declarerWon, defenceWon = game.play.getTrickCount() - score = game.getScore() - - textContract = render_contract(game.contract) - textMade = str(declarerWon) - if game.contract['declarer'] in (Direction.North, Direction.South) and score > 0 \ - or game.contract['declarer'] in (Direction.East, Direction.West) and score < 0: - textNS, textEW = str(abs(score)), '' - else: - textNS, textEW = '', str(abs(score)) - - self.store.prepend([textContract, textMade, textNS, textEW]) - - - - -class BridgeDashboard(gtk.VBox): - """An at-a-glance display of the state of a bridge game.""" - - - def __init__(self): - gtk.VBox.__init__(self) - self.set_spacing(4) - - self.contract = gtk.Label() - self.pack_start(self.contract) - - hbox = gtk.HBox() - hbox.set_homogeneous(True) - hbox.set_spacing(6) - self.declarer_tricks = gtk.Label() - frame = gtk.Frame() - frame.set_label(_('Declarer')) - frame.set_label_align(0.5, 0.5) - frame.add(self.declarer_tricks) - hbox.pack_start(frame) - self.defence_tricks = gtk.Label() - frame = gtk.Frame() - frame.set_label(_('Defence')) - frame.set_label_align(0.5, 0.5) - frame.add(self.defence_tricks) - hbox.pack_start(frame) - self.pack_start(hbox) - - hbox = gtk.HBox() - hbox.set_homogeneous(True) - hbox.set_spacing(6) - # TODO: display board number? - self.dealer = gtk.Label() - self.dealer.set_alignment(0, 0.5) - hbox.pack_start(self.dealer) - self.vulnerability = gtk.Label() - self.vulnerability.set_alignment(0, 0.5) - hbox.pack_start(self.vulnerability) - self.pack_start(hbox) - - - def set_contract(self, game): - if game.contract: - text = render_contract(game.contract) - else: - text = _('No contract') - self.contract.set_markup("<span size='x-large'>%s</span>" % text) - - - def set_trickcount(self, game): - if game.play: - declarer, defence = game.play.getTrickCount() - required = game.contract['bid'].level.index + 7 - declarerNeeds = max(0, required - declarer) - defenceNeeds = max(0, 13 + 1 - required - defence) - else: - declarer, defence, declarerNeeds, defenceNeeds = 0, 0, 0, 0 - format = "<span size='x-large'><b>%s</b> (%s)</span>" - self.declarer_tricks.set_markup(format % (declarer, declarerNeeds)) - self.defence_tricks.set_markup(format % (defence, defenceNeeds)) - - - def set_dealer(self, game): - if game.inProgress(): - dealertext = "<b>%s</b>" % DIRECTION_NAMES[game.board['dealer']] - else: - dealertext = '' - self.dealer.set_markup(_('Dealer') + ': ' + dealertext) - - - def set_vulnerability(self, game): - if game.inProgress(): - vulntext = "<b>%s</b>" % VULN_SYMBOLS[game.board['vuln']] - else: - vulntext = '' - self.vulnerability.set_markup(_('Vuln') + ': ' + vulntext) - - - - -class WindowBridgeTable(WindowGameTable): - - gametype = _('Contract Bridge') - - stockdirections = [gtk.STOCK_GO_UP, gtk.STOCK_GO_FORWARD, - gtk.STOCK_GO_DOWN, gtk.STOCK_GO_BACK] - - - def setUp(self): - super(WindowBridgeTable, self).setUp() - - # Set up menu attached to 'Take Seat' toolbar button. - self.takeseat_menuitems = {} - menu = gtk.Menu() - for position, stock in zip(Direction, self.stockdirections): - item = gtk.ImageMenuItem(DIRECTION_NAMES[position], True) - item.set_image(gtk.image_new_from_stock(stock, gtk.ICON_SIZE_MENU)) - item.connect('activate', self.on_takeseat_clicked, position) - item.show() - menu.append(item) - self.takeseat_menuitems[position] = item - self.takeseat.set_menu(menu) - - # Set up CardArea widget. - self.cardarea = CardArea() - self.cardarea.on_card_clicked = self.on_card_clicked - self.cardarea.on_hand_clicked = self.on_hand_clicked - self.cardarea.set_size_request(640, 480) - self.gamearea.add(self.cardarea) - - # Set up sidebar. - self.dashboard = BridgeDashboard() - self.sidebar.pack_start(self.dashboard, expand=False) - - self.biddingview = BiddingView() - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) - sw.add(self.biddingview) - frame = gtk.Frame() - frame.add(sw) - exp = gtk.Expander(_('Bidding')) - exp.set_expanded(True) - exp.add(frame) - self.sidebar.pack_start(exp) - -# self.lasttrick = CardArea() -# frame = gtk.Frame() -# frame.add(self.lasttrick) -# exp = gtk.Expander(_('Last Trick')) -# exp.set_expanded(True) -# exp.add(frame) -# self.sidebar.pack_start(exp) - - self.scoreview = ScoreView() - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.add(self.scoreview) - frame = gtk.Frame() - frame.add(sw) - exp = gtk.Expander(_('Score Sheet')) - exp.set_expanded(False) - exp.add(frame) - self.sidebar.pack_start(exp) - - - def errback(self, failure): - print "Error: %s" % failure.getErrorMessage() - #print failure.getBriefTraceback() - - - def setTable(self, table): - """Changes display to match the table specified. - - @param table: the (now) focal table. - """ - super(WindowBridgeTable, self).setTable(table) - - self.table.game.attach(self.eventHandler) - self.resetGame() - - for position in Direction: - self.redrawHand(position) # Redraw all hands. - - if self.table.game.inProgress(): - # If trick play in progress, redraw trick. - if self.table.game.play: - self.redrawTrick() - self.setTurnIndicator() - - for call in self.table.game.bidding.calls: - position = self.table.game.bidding.whoCalled(call) - self.biddingview.add_call(call, position) - - # If playing, set trick counts. - if self.table.game.play: - for position, cards in self.table.game.play.played.items(): - for card in cards: - self.addCard(card, position) - - # If user is a player and bidding in progress, open bidding box. - if self.player and not self.table.game.bidding.isComplete(): - bidbox = self.children.open(WindowBidbox, parent=self) - bidbox.setCallSelectHandler(self.on_call_selected) - bidbox.setTable(self.table, self.position) - - - # Initialise seat menu and player labels. - for position in Direction: - player = self.table.players.get(position) # Player name or None. - - avail = player is None or position == self.position - self.takeseat_menuitems[position].set_property('sensitive', avail) - # If player == None, this unsets player name. - self.cardarea.set_player_name(position, player) - - - def resetGame(self): - """Clear bidding history, contract, trick counts.""" - self.cardarea.clear() - self.biddingview.clear() # Reset bidding history. - - self.dashboard.set_contract(self.table.game) - self.dashboard.set_trickcount(self.table.game) - self.dashboard.set_dealer(self.table.game) - self.dashboard.set_vulnerability(self.table.game) - - - def addCard(self, card, position): - """""" -# position = self.table.game.play.whoPlayed(card) -# column = position.index -# row = self.table.game.play.played[position].index(card) -# -# if self.trick_store.get_iter_first() == None: -# self.trick_store.append() -# iter = self.trick_store.get_iter_first() -# for i in range(row): -# iter = self.trick_store.iter_next(iter) -# if iter is None: -# iter = self.trick_store.append() -# -# format = render_card(card) -# self.trick_store.set(iter, column, format) - - - def gameComplete(self): - # Display all previously revealed hands - the server will reveal the others. - for position in self.table.game.visibleHands: - self.redrawHand(position, all=True) - - self.setTurnIndicator() - - dialog = gtk.MessageDialog(parent=self.window, type=gtk.MESSAGE_INFO) - dialog.set_title(_('Game result')) - - # Determine and display score in dialog box and score sheet. - if self.table.game.contract: - self.scoreview.add_score(self.table.game) - - declarerWon, defenceWon = self.table.game.play.getTrickCount() - required = self.table.game.contract['bid'].level.index + 7 - offset = declarerWon - required - score = self.table.game.getScore() - - fields = {'contract': render_contract(self.table.game.contract), - 'offset': abs(offset) } - if offset > 0: - if offset == 1: - resultText = _('Contract %(contract)s made by 1 trick.') % fields - else: - resultText = _('Contract %(contract)s made by %(offset)s tricks.') % fields - elif offset < 0: - if offset == -1: - resultText = _('Contract %(contract)s failed by 1 trick.') % fields - else: - resultText = _('Contract %(contract)s failed by %(offset)s tricks.') % fields - else: - resultText = _('Contract %(contract)s made exactly.') % fields - - pair = (score >= 0 and _('declarer')) or _('defence') - scoreText = _('Score %(points)s points for %(pair)s.') % {'points': abs(score), 'pair': pair} - - dialog.set_markup(resultText + '\n' + scoreText) - - else: - dialog.set_markup(_('Bidding passed out.')) - dialog.format_secondary_text(_('No score.')) - - if self.player: - dialog.add_button(_('Leave Seat'), gtk.RESPONSE_CANCEL) - dialog.format_secondary_text(_('Click OK to start next game.')) - dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - dialog.set_default_response(gtk.RESPONSE_OK) - # If user leaves table (ie. closes window), close dialog as well. - dialog.set_transient_for(self.window) - dialog.set_destroy_with_parent(True) - - def dialog_response_cb(dialog, response_id): - dialog.destroy() - if self.player: - if response_id == gtk.RESPONSE_OK and self.table.game.isNextGameReady(): - d = self.player.callRemote('startNextGame') - d.addErrback(self.errback) - elif response_id == gtk.RESPONSE_CANCEL: - self.on_leaveseat_clicked(dialog) - - dialog.connect('response', dialog_response_cb) - dialog.show() - - - def redrawHand(self, position, all=False): - """Redraws cards making up the hand at position. - - Cards played are filtered out and omitted from display. - Unknown cards are displayed face down. - - @param position: - @param all: If True, do not filter out cards played. - """ - try: - hand = self.table.game.getHand(position) - facedown = False - except GameError: # Unknown hand. - hand = range(13) - facedown = True - - if all is True or self.table.game.play is None: - available = hand - else: - played = self.table.game.play.played[position] - if facedown: # Draw cards face down for unknown hand. - available = range(13 - len(played)) - else: - available = [card for card in hand if card not in played] - - self.cardarea.set_hand(hand, position, facedown, visible=available) - - - def redrawTrick(self): - """Redraws trick. - - @param table: - @param trick: - """ - trick = None - if self.table.game.play: - trick = self.table.game.play.getCurrentTrick() - self.cardarea.set_trick(trick) - - - def setTurnIndicator(self): - """Sets the statusbar text to indicate which player is on turn.""" - context = self.statusbar.get_context_id('turn') - self.statusbar.pop(context) - - try: - turn = self.table.game.getTurn() - - if self.table.game.play: - declarer, dummy = self.table.game.play.declarer, self.table.game.play.dummy - if self.position and self.position == turn != dummy: - text = _("Play a card from your hand.") - elif self.position and self.position == declarer and turn == dummy: - text = _("Play a card from dummy's hand.") - else: - text = _("It is %s's turn to play a card.") % DIRECTION_NAMES[turn] - - else: # Bidding. - if self.position and self.position == turn: - text = _("Make a call from the bidding box.") - else: - text = _("It is %s's turn to make a call.") % DIRECTION_NAMES[turn] - - except GameError: # Game not in progress. - text = _("Waiting for next game to start.") - - self.statusbar.push(context, text) - - -# Registered event handlers. - - - def event_joinGame(self, player, position): - self.cardarea.set_player_name(position, player) - # Disable menu item corresponding to position. - widget = self.takeseat_menuitems[position] - widget.set_property('sensitive', False) - - # If all positions occupied, disable Take Seat. - if len(self.table.players.values()) == len(Direction): - self.takeseat.set_property('sensitive', False) - - if self.player and self.table.game.isNextGameReady(): - d = self.player.callRemote('startNextGame') - d.addErrback(self.errback) - - - def event_leaveGame(self, player, position): - self.cardarea.set_player_name(position, None) - # Enable menu item corresponding to position. - widget = self.takeseat_menuitems[position] - widget.set_property('sensitive', True) - - # If we are not seated, ensure Take Seat is enabled. - if self.position is None: - self.takeseat.set_property('sensitive', True) - - - def event_start(self, board): - self.resetGame() - - # Re-initialise player labels. - # TODO: shouldn't need to do this. - for position in Direction: - player = self.table.players.get(position) # Player name or None. - self.cardarea.set_player_name(position, player) - - self.redrawTrick() # Clear trick. - for position in Direction: - self.redrawHand(position) - - self.setTurnIndicator() - self.dashboard.set_dealer(self.table.game) - self.dashboard.set_vulnerability(self.table.game) - - if self.player: - d = self.player.callRemote('getHand') - # When user's hand is returned, reveal it to client-side Game. - d.addCallbacks(self.table.game.revealHand, self.errback, - callbackKeywords={'position': self.position}) - bidbox = self.children.open(WindowBidbox, parent=self) - bidbox.setCallSelectHandler(self.on_call_selected) - bidbox.setTable(self.table, self.position) - - - def event_makeCall(self, call, position): - self.biddingview.add_call(call, position) - self.setTurnIndicator() - - if self.table.game.bidding.isComplete(): - self.dashboard.set_contract(self.table.game) - if self.children.get(WindowBidbox): # If a player. - self.children.close(self.children[WindowBidbox]) - - if not self.table.game.inProgress(): - self.gameComplete() - - - def event_playCard(self, card, position): - # Determine the position of the hand from which card was played. - playfrom = self.table.game.play.whoPlayed(card) - self.addCard(card, playfrom) - self.setTurnIndicator() - self.dashboard.set_trickcount(self.table.game) - self.redrawTrick() - self.redrawHand(playfrom) - - if not self.table.game.inProgress(): - self.gameComplete() - - - def event_revealHand(self, hand, position): - all = not self.table.game.inProgress() - self.redrawHand(position, all) # Show all cards if game has finished. - - -# Signal handlers. - - - def on_call_selected(self, call): - if self.player: - d = self.player.callRemote('makeCall', call) - d.addErrback(self.errback) - - - def on_hand_clicked(self, position): - if not self.player and not self.table.players.get(position): - # Join game at position. - self.on_takeseat_clicked(self.cardarea, position) - - - def on_card_clicked(self, card, position): - if self.player: - if self.table.game.inProgress() and self.table.game.play: - d = self.player.callRemote('playCard', card) - d.addErrback(self.errback) - - - def on_takeseat_clicked(self, widget, position=None): - - def success(r): - self.cardarea.set_player_mapping(self.position) - if self.table.game.inProgress(): - d = self.player.callRemote('getHand') - d.addCallbacks(self.table.game.revealHand, self.errback, - callbackKeywords={'position' : self.position}) - # If game is running and bidding is active, open bidding box. - if not self.table.game.bidding.isComplete(): - bidbox = self.children.open(WindowBidbox, parent=self) - bidbox.setCallSelectHandler(self.on_call_selected) - bidbox.setTable(self.table, self.position) - - # TODO: match user up with preferred partner. - if position is None: - # No position specified by user: choose an arbitary position. - position = [p for p in Direction if p not in self.table.players][0] - d = super(WindowBridgeTable, self).on_takeseat_clicked(widget, position) - d.addCallback(success) - - - def on_leaveseat_clicked(self, widget, *args): - - def success(r): - if self.children.get(WindowBidbox): - self.children.close(self.children[WindowBidbox]) - - d = super(WindowBridgeTable, self).on_leaveseat_clicked(widget, *args) - d.addCallback(success) - This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-10 13:53:29
|
Revision: 466 http://svn.sourceforge.net/pybridge/?rev=466&view=rev Author: umgangee Date: 2007-07-10 06:53:31 -0700 (Tue, 10 Jul 2007) Log Message: ----------- Encapsulate TableClient view reference within the state dict provided by LocalTable:getStateToCacheAndObserveFor() and consumed by RemoteTable:setCopyableState(), instead of passing it alongside the table reference. Modified Paths: -------------- trunk/pybridge/pybridge/network/client.py trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotetable.py trunk/pybridge/pybridge/server/user.py Modified: trunk/pybridge/pybridge/network/client.py =================================================================== --- trunk/pybridge/pybridge/network/client.py 2007-07-09 15:40:59 UTC (rev 465) +++ trunk/pybridge/pybridge/network/client.py 2007-07-10 13:53:31 UTC (rev 466) @@ -173,8 +173,7 @@ def joinTable(self, tableid, host=False): - def success((table, remote)): - table.master = remote # Set RemoteReference for RemoteBridgeTable. + def success(table): self.tables[tableid] = table self.notify('joinTable', tableid=tableid, table=table) return table Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-07-09 15:40:59 UTC (rev 465) +++ trunk/pybridge/pybridge/network/localtable.py 2007-07-10 13:53:31 UTC (rev 466) @@ -40,6 +40,27 @@ info = property(lambda self: {'game': self.gametype.__name__}) + class TableClient(pb.Viewable): + """Provides a public front-end to an instantiated LocalTable. + + Serialization flavors are mutually exclusive and cannot be mixed, + so this class is a subclass of pb.Viewable. + """ + + + def __init__(self, table): + self.__table = table + + + def view_joinGame(self, user, position): + # TODO: return a deferred? + return self.__table.joinGame(user, position) + + + def view_leaveGame(self, user, position): + return self.__table.leaveGame(user, position) + + def __init__(self, id, gametype, config={}): self.listeners = [] @@ -51,7 +72,7 @@ self.observers = {} # For each user perspective, a remote ITableEvents. self.players = {} # Positions mapped to perspectives of game players. - self.view = LocalTableViewable(self) # For remote clients. + self.view = self.TableClient(self) # For remote clients. # Configuration variables. self.config = {} @@ -75,6 +96,7 @@ state['observers'] = [p.name for p in self.observers.keys()] state['players'] = dict([(pos, p.name) for pos, p in self.players.items()]) + state['view'] = self.view return state # To observer. @@ -163,30 +185,3 @@ del self.players[position] self.notify('leaveGame', player=user.name, position=position) - - - -class LocalTableViewable(pb.Viewable): - """Provides a public front-end to an instantiated LocalTable. - - Serialization flavors are mutually exclusive and cannot be mixed, - so this class is a subclass of pb.Viewable. - """ - - - def __init__(self, table): - """ - - @param table: a instantiated LocalTable. - """ - self.__table = table - - - def view_joinGame(self, user, position): - # TODO: return a deferred? - return self.__table.joinGame(user, position) - - - def view_leaveGame(self, user, position): - return self.__table.leaveGame(user, position) - Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-07-09 15:40:59 UTC (rev 465) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-07-10 13:53:31 UTC (rev 466) @@ -43,19 +43,10 @@ info = property(lambda self: {'game': self.gametype.__name__}) - # TODO: is there any need for this initialisation? def __init__(self): - self.master = None # Server-side ITable object. self.listeners = [] - self.id = None - self.chat = None - self.game = None - self.gametype = None - self.observers = [] # Observers of master table. - self.players = {} # Positions mapped to player identifiers. - def setCopyableState(self, state): self.id = state['id'] if state['gametype'] in GAMETYPES: @@ -70,18 +61,19 @@ self.players = state['players'] for position in self.players: self.game.addPlayer(position) + self.__view = state['view'] # Implementation of ITable. def joinGame(self, position, user=None): - d = self.master.callRemote('joinGame', position) + d = self.__view.callRemote('joinGame', position) return d def leaveGame(self, position, user=None): - d = self.master.callRemote('leaveGame', position) + d = self.__view.callRemote('leaveGame', position) return d Modified: trunk/pybridge/pybridge/server/user.py =================================================================== --- trunk/pybridge/pybridge/server/user.py 2007-07-09 15:40:59 UTC (rev 465) +++ trunk/pybridge/pybridge/server/user.py 2007-07-10 13:53:31 UTC (rev 466) @@ -100,8 +100,7 @@ table = self.server.tables[tableid] self.tables[tableid] = table - # Returning table reference creates a RemoteTable object on client. - return table, table.view + return table def perspective_leaveTable(self, tableid): This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-10 20:01:37
|
Revision: 472 http://svn.sourceforge.net/pybridge/?rev=472&view=rev Author: umgangee Date: 2007-07-10 12:27:08 -0700 (Tue, 10 Jul 2007) Log Message: ----------- Add change password functionality; some reorganisation of server-side validation of data received from client. Modified Paths: -------------- trunk/pybridge/pybridge/network/client.py trunk/pybridge/pybridge/server/server.py trunk/pybridge/pybridge/server/user.py Modified: trunk/pybridge/pybridge/network/client.py =================================================================== --- trunk/pybridge/pybridge/network/client.py 2007-07-10 17:25:05 UTC (rev 471) +++ trunk/pybridge/pybridge/network/client.py 2007-07-10 19:27:08 UTC (rev 472) @@ -171,6 +171,12 @@ # Client request methods. + def changePassword(self, password): + hash = sha.new(password).hexdigest() + d = self.avatar.callRemote('changePassword', hash) + return d + + def joinTable(self, tableid, host=False): def success(table): @@ -179,10 +185,9 @@ return table if host: - d = self.avatar.callRemote('hostTable', tableid=tableid, - tabletype='bridge') + d = self.avatar.callRemote('hostTable', tableid, 'bridge') else: - d = self.avatar.callRemote('joinTable', tableid=tableid) + d = self.avatar.callRemote('joinTable', tableid) d.addCallback(success) return d @@ -193,7 +198,7 @@ del self.tables[tableid] self.notify('leaveTable', tableid=tableid) - d = self.avatar.callRemote('leaveTable', tableid=tableid) + d = self.avatar.callRemote('leaveTable', tableid) d.addCallback(success) return d Modified: trunk/pybridge/pybridge/server/server.py =================================================================== --- trunk/pybridge/pybridge/server/server.py 2007-07-10 17:25:05 UTC (rev 471) +++ trunk/pybridge/pybridge/server/server.py 2007-07-10 19:27:08 UTC (rev 472) @@ -16,7 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from datetime import datetime +import re from twisted.python import log import database as db @@ -35,13 +35,11 @@ def getServerInfo(): - return {'supported': (version, version), # minimum, maximum + return {'compatibleVersions': (version, version), # minimum, maximum + 'supportedGames': 'bridge', # TODO 'version': version} -# Methods invoked by user perspectives. - - def registerUser(username, password): """Registers a new user account in the database. @@ -51,21 +49,23 @@ # Check that username has not already been registered. if db.UserAccount.selectBy(username=username).count() > 0: raise DeniedRequest, "Username already registered" - try: - # Create user account. - db.UserAccount(username=username, password=password, allowLogin=True) - log.msg("New user %s registered" % username) - except ValueError, err: - raise IllegalRequest, err + # Create user account - may raise ValueError. + db.UserAccount(username=username, password=password, allowLogin=True) + log.msg("New user %s registered" % username) -def changePasswordOfUser(username, password): - """Sets the password of user to specified password. + +def changeUserPassword(username, password): + """Sets the password of user's account. @param username: the user identifier. @param password: the new password for user. """ - pass # TODO implement + try: + user = db.UserAccount.selectBy(username=username)[0] + user.set(password=password) # May raise ValueError. + except IndexError: + raise DeniedRequest, "User account does not exist" def createTable(tableid, gametype): @@ -75,9 +75,17 @@ @param gametype: a game identifier. """ # TODO: convert gametype string to corresponding class. - if tableid not in availableTables: - table = LocalTable(tableid, BridgeGame) # Ignore gametype for now. - # Provide table instance with a means of closing itself. - table.close = lambda: availableTables.closeTable(table) - availableTables.openTable(table) + if not 0 < len(tableid) <= 20 or re.search("[^A-Za-z0-9_ ]", tableid): + raise IllegalRequest, "Invalid table identifier format" + if tableid in availableTables: + raise DeniedRequest, "Table name exists" +# if tabletype not in supported: +# raise DeniedRequest, "Table type not suppported by this server" + + table = LocalTable(tableid, BridgeGame) # Ignore gametype for now. + # Provide table instance with a means of closing itself. + table.close = lambda: availableTables.closeTable(table) + availableTables.openTable(table) + return table + Modified: trunk/pybridge/pybridge/server/user.py =================================================================== --- trunk/pybridge/pybridge/server/user.py 2007-07-10 17:25:05 UTC (rev 471) +++ trunk/pybridge/pybridge/server/user.py 2007-07-10 19:27:08 UTC (rev 472) @@ -17,7 +17,6 @@ from datetime import datetime -import re from twisted.python import log from twisted.spread import pb @@ -29,7 +28,7 @@ class RegisteredUser(pb.Avatar): - info = property(lambda self: {}) + info = property(lambda self: {}) # TODO: Send profile data? def __init__(self, name): @@ -72,19 +71,32 @@ raise DeniedRequest, "Unknown roster name \'%s\'" % name - def perspective_hostTable(self, tableid, tabletype): + def perspective_getUserProfile(self, username): + """Provides profile information for user with specified username.""" + pass + + + def perspective_changePassword(self, password): + """Sets avatar's user account password to that specified.""" + if not isinstance(password, str): + raise IllegalRequest, "Invalid parameter for password" + + try: + server.changeUserPassword(self.name, password) + except ValueError, err: # Password validation failed. + raise DeniedRequest, err + + + def perspective_hostTable(self, tableid, gametype): """Creates a new table.""" if not isinstance(tableid, str): raise IllegalRequest, "Invalid parameter for table identifier" - elif not(0 < len(tableid) < 21) or re.search("[^A-Za-z0-9_ ]", tableid): - raise IllegalRequest, "Invalid table identifier format" - elif tableid in server.availableTables: - raise DeniedRequest, "Table name exists" -# elif tabletype not in server.supported: -# raise DeniedRequest, "Table type not suppported by this server" + if not isinstance(gametype, str): + raise IllegalRequest, "Invalid parameter for game type" - server.createTable(tableid, tabletype) - return self.perspective_joinTable(tableid) # Force join to table. + table = server.createTable(tableid, gametype) + # Force client to join table. + return self.perspective_joinTable(tableid) def perspective_joinTable(self, tableid): @@ -119,5 +131,7 @@ def perspective_register(self, username, password): """Create a user account with specified username and password.""" # TODO: consider defer.succeed, defer.fail, failure.Failure - server.registerUser(username, password) - + try: + server.registerUser(username, password) + except ValueError, err: # Username/password validation failed. + raise DeniedRequest, err This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-11 11:58:48
|
Revision: 473 http://svn.sourceforge.net/pybridge/?rev=473&view=rev Author: umgangee Date: 2007-07-11 04:58:50 -0700 (Wed, 11 Jul 2007) Log Message: ----------- Relocate pybridge/bridge/ to pybridge/games/bridge/. To patch breakage, create a symbolic link "pybridge/bridge" pointing to "pybridge/games/bridge". Added Paths: ----------- trunk/pybridge/pybridge/games/ trunk/pybridge/pybridge/games/bridge/ Removed Paths: ------------- trunk/pybridge/pybridge/bridge/ Copied: trunk/pybridge/pybridge/games/bridge (from rev 465, trunk/pybridge/pybridge/bridge) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-11 13:54:15
|
Revision: 475 http://svn.sourceforge.net/pybridge/?rev=475&view=rev Author: umgangee Date: 2007-07-11 06:54:14 -0700 (Wed, 11 Jul 2007) Log Message: ----------- Change client/server code to work with any game in pybridge.games.SUPPORTED_GAMES, not just Bridge. Modified Paths: -------------- trunk/pybridge/pybridge/network/client.py trunk/pybridge/pybridge/network/localtable.py trunk/pybridge/pybridge/network/remotetable.py trunk/pybridge/pybridge/network/tablemanager.py trunk/pybridge/pybridge/server/server.py Modified: trunk/pybridge/pybridge/network/client.py =================================================================== --- trunk/pybridge/pybridge/network/client.py 2007-07-11 13:47:03 UTC (rev 474) +++ trunk/pybridge/pybridge/network/client.py 2007-07-11 13:54:14 UTC (rev 475) @@ -172,12 +172,16 @@ def changePassword(self, password): + """Change password of user account. + + @param password: the new password. + """ hash = sha.new(password).hexdigest() d = self.avatar.callRemote('changePassword', hash) return d - def joinTable(self, tableid, host=False): + def joinTable(self, tableid, gameclass=None, host=False): def success(table): self.tables[tableid] = table @@ -185,7 +189,9 @@ return table if host: - d = self.avatar.callRemote('hostTable', tableid, 'bridge') + # TODO: why not just joinTable, host=True? + gamename = gameclass.__name__ + d = self.avatar.callRemote('hostTable', tableid, gamename) else: d = self.avatar.callRemote('joinTable', tableid) d.addCallback(success) Modified: trunk/pybridge/pybridge/network/localtable.py =================================================================== --- trunk/pybridge/pybridge/network/localtable.py 2007-07-11 13:47:03 UTC (rev 474) +++ trunk/pybridge/pybridge/network/localtable.py 2007-07-11 13:54:14 UTC (rev 475) @@ -37,7 +37,7 @@ implements(ITable, ISubject, IListener) - info = property(lambda self: {'game': self.gametype.__name__}) + info = property(lambda self: {'gamename': self.game.__class__.__name__}) class TableClient(pb.Viewable): @@ -61,12 +61,11 @@ return self.__table.leaveGame(user, position) - def __init__(self, id, gametype, config={}): + def __init__(self, id, gameclass, config={}): self.listeners = [] self.id = id - self.gametype = gametype - self.game = gametype() # Initialise game. + self.game = gameclass() # Initialise game. self.game.attach(self) # Listen for game events. self.chat = LocalChat() @@ -96,7 +95,7 @@ state = {} state['id'] = self.id state['chat'] = self.chat - state['gametype'] = self.gametype.__name__ + state['gamename'] = self.info['gamename'] state['gamestate'] = self.game.getState() state['observers'] = [p.name for p in self.observers.keys()] state['players'] = dict([(pos, p.name) Modified: trunk/pybridge/pybridge/network/remotetable.py =================================================================== --- trunk/pybridge/pybridge/network/remotetable.py 2007-07-11 13:47:03 UTC (rev 474) +++ trunk/pybridge/pybridge/network/remotetable.py 2007-07-11 13:54:14 UTC (rev 475) @@ -23,12 +23,9 @@ from pybridge.interfaces.table import ITable from pybridge.network.error import DeniedRequest, IllegalRequest +from pybridge.games import SUPPORTED_GAMES -# TODO: move to somewhere more appropriate. -from pybridge.bridge.game import BridgeGame -GAMETYPES = {'BridgeGame' : BridgeGame} - class RemoteTable(pb.RemoteCache): """A client-side implementation of ITable providing a "front-end" to a remote server-side LocalTable. @@ -40,7 +37,7 @@ implements(ITable, ISubject) - info = property(lambda self: {'game': self.gametype.__name__}) + info = property(lambda self: {'gamename': self.game.__class__.__name__}) def __init__(self): @@ -49,12 +46,12 @@ def setCopyableState(self, state): self.id = state['id'] - if state['gametype'] in GAMETYPES: - self.gametype = GAMETYPES[state['gametype']] - self.game = self.gametype() + if state['gamename'] in SUPPORTED_GAMES: + gameclass = SUPPORTED_GAMES[state['gamename']] + self.game = gameclass() self.game.setState(state['gamestate']) else: - raise NameError, "Unknown game type %s" % state['gametype'] + raise NameError, "Unsupported game class %s" % state['gamename'] self.chat = state['chat'] self.observers = state['observers'] Modified: trunk/pybridge/pybridge/network/tablemanager.py =================================================================== --- trunk/pybridge/pybridge/network/tablemanager.py 2007-07-11 13:47:03 UTC (rev 474) +++ trunk/pybridge/pybridge/network/tablemanager.py 2007-07-11 13:54:14 UTC (rev 475) @@ -16,6 +16,7 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from pybridge.games import SUPPORTED_GAMES from roster import LocalRoster, RemoteRoster @@ -25,12 +26,12 @@ def openTable(self, table): # TODO: don't notify clients which don't recognise game type. self[table.id] = table - self.notify('openTable', tableid=table.id, info=table.info) + self.notify('openTable', table.id, table.info) def closeTable(self, table): del self[table.id] - self.notify('closeTable', tableid=table.id) + self.notify('closeTable', table.id) @@ -40,10 +41,13 @@ def observe_openTable(self, tableid, info): self[tableid] = info - self.notify('openTable', tableid=tableid, info=info) + if info['gamename'] in SUPPORTED_GAMES: + self[tableid]['gameclass'] = SUPPORTED_GAMES[info['gamename']] + self.notify('openTable', tableid, info) + def observe_closeTable(self, tableid): del self[tableid] - self.notify('closeTable', tableid=tableid) + self.notify('closeTable', tableid) Modified: trunk/pybridge/pybridge/server/server.py =================================================================== --- trunk/pybridge/pybridge/server/server.py 2007-07-11 13:47:03 UTC (rev 474) +++ trunk/pybridge/pybridge/server/server.py 2007-07-11 13:54:14 UTC (rev 475) @@ -21,15 +21,14 @@ import database as db from pybridge import __version__ as version +from pybridge.games import SUPPORTED_GAMES from pybridge.network.error import DeniedRequest, IllegalRequest from pybridge.network.localtable import LocalTable from pybridge.network.tablemanager import LocalTableManager from pybridge.network.usermanager import LocalUserManager -from pybridge.bridge.game import BridgeGame - availableTables = LocalTableManager() onlineUsers = LocalUserManager() @@ -68,7 +67,7 @@ raise DeniedRequest, "User account does not exist" -def createTable(tableid, gametype): +def createTable(tableid, gamename): """Create a new table for the specified game type. @param tableid: a unique identifier for the table. @@ -80,10 +79,11 @@ raise IllegalRequest, "Invalid table identifier format" if tableid in availableTables: raise DeniedRequest, "Table name exists" -# if tabletype not in supported: -# raise DeniedRequest, "Table type not suppported by this server" + if gamename not in SUPPORTED_GAMES: + raise DeniedRequest, "Unsupported game class %s" % gamename - table = LocalTable(tableid, BridgeGame) # Ignore gametype for now. + gameclass = SUPPORTED_GAMES[gamename] + table = LocalTable(tableid, gameclass) # Provide table instance with a means of closing itself. table.close = lambda: availableTables.closeTable(table) availableTables.openTable(table) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-11 13:56:12
|
Revision: 476 http://svn.sourceforge.net/pybridge/?rev=476&view=rev Author: umgangee Date: 2007-07-11 06:56:10 -0700 (Wed, 11 Jul 2007) Log Message: ----------- Change pybridge.bridge references to pybridge.games.bridge. Modified Paths: -------------- trunk/pybridge/pybridge/games/bridge/ui/window_bidbox.py trunk/pybridge/pybridge/ui/dialog_newtable.py trunk/pybridge/pybridge/ui/dialog_preferences.py trunk/pybridge/pybridge/ui/vocabulary.py trunk/pybridge/pybridge/ui/window_main.py Modified: trunk/pybridge/pybridge/games/bridge/ui/window_bidbox.py =================================================================== --- trunk/pybridge/pybridge/games/bridge/ui/window_bidbox.py 2007-07-11 13:54:14 UTC (rev 475) +++ trunk/pybridge/pybridge/games/bridge/ui/window_bidbox.py 2007-07-11 13:56:10 UTC (rev 476) @@ -22,7 +22,7 @@ from pybridge.ui.eventhandler import SimpleEventHandler from pybridge.ui.vocabulary import * -import pybridge.bridge.call as Call +import pybridge.games.bridge.call as Call class WindowBidbox(object): Modified: trunk/pybridge/pybridge/ui/dialog_newtable.py =================================================================== --- trunk/pybridge/pybridge/ui/dialog_newtable.py 2007-07-11 13:54:14 UTC (rev 475) +++ trunk/pybridge/pybridge/ui/dialog_newtable.py 2007-07-11 13:56:10 UTC (rev 476) @@ -20,6 +20,7 @@ from wrapper import GladeWrapper from pybridge.network.client import client +from pybridge.games import SUPPORTED_GAMES from manager import wm @@ -29,6 +30,7 @@ def setUp(self): + # TODO: display intersection of games supported by client and server. pass @@ -57,7 +59,8 @@ def on_okbutton_clicked(self, widget, *args): tableid = self.entry_tablename.get_text() - d = client.joinTable(tableid, host=True) + d = client.joinTable(tableid, gameclass=SUPPORTED_GAMES['Bridge'], + host=True) d.addCallbacks(self.createSuccess, self.createFailure) @@ -66,3 +69,7 @@ sensitive = self.entry_tablename.get_text() != "" self.okbutton.set_property('sensitive', sensitive) + + def on_window_delete_event(self, widget, *args): + wm.close(self) + Modified: trunk/pybridge/pybridge/ui/dialog_preferences.py =================================================================== --- trunk/pybridge/pybridge/ui/dialog_preferences.py 2007-07-11 13:54:14 UTC (rev 475) +++ trunk/pybridge/pybridge/ui/dialog_preferences.py 2007-07-11 13:56:10 UTC (rev 476) @@ -25,7 +25,7 @@ from manager import wm from vocabulary import * -from pybridge.bridge.symbols import Suit +from pybridge.games.bridge.symbols import Suit SUIT_LABEL_TEMPLATE = "<span color=\'%s\' size=\'x-large\'>%s</span>" Modified: trunk/pybridge/pybridge/ui/vocabulary.py =================================================================== --- trunk/pybridge/pybridge/ui/vocabulary.py 2007-07-11 13:54:14 UTC (rev 475) +++ trunk/pybridge/pybridge/ui/vocabulary.py 2007-07-11 13:56:10 UTC (rev 476) @@ -22,8 +22,8 @@ import gtk -from pybridge.bridge.symbols import * -import pybridge.bridge.call as Call +from pybridge.games.bridge.symbols import * +import pybridge.games.bridge.call as Call from config import config Modified: trunk/pybridge/pybridge/ui/window_main.py =================================================================== --- trunk/pybridge/pybridge/ui/window_main.py 2007-07-11 13:54:14 UTC (rev 475) +++ trunk/pybridge/pybridge/ui/window_main.py 2007-07-11 13:56:10 UTC (rev 476) @@ -23,6 +23,7 @@ import webbrowser from pybridge import __version__ as PYBRIDGE_VERSION +from pybridge.games import SUPPORTED_GAMES import pybridge.environment as env from pybridge.network.client import client @@ -34,7 +35,7 @@ from dialog_preferences import DialogPreferences # TODO: import all Window*Table classes automatically. -from pybridge.bridge.ui.window_bridgetable import WindowBridgeTable +from pybridge.games.bridge.ui.window_bridgetable import WindowBridgeTable TABLE_ICON = env.find_pixmap("table.png") USER_ICON = env.find_pixmap("user.png") @@ -164,7 +165,9 @@ def event_openTable(self, tableid, info): """Adds a table to the table listing.""" - self.tableview_model.append([tableid, self.tableview_icon]) + # Only display table if it supported by client. + if info['gamename'] in SUPPORTED_GAMES: + self.tableview_model.append([tableid, self.tableview_icon]) def event_closeTable(self, tableid): @@ -221,7 +224,7 @@ # Display information about table. self.frame_tableinfo.set_property('sensitive', True) self.label_tableid.set_text(tableid) - self.label_tabletype.set_text(client.tableRoster[tableid]['game']) + self.label_tabletype.set_text(client.tableRoster[tableid]['gamename']) else: self.frame_tableinfo.set_property('sensitive', False) self.label_tableid.set_text('') This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-12 14:01:44
|
Revision: 479 http://svn.sourceforge.net/pybridge/?rev=479&view=rev Author: umgangee Date: 2007-07-12 07:01:43 -0700 (Thu, 12 Jul 2007) Log Message: ----------- Tidy up Canvas and CardArea. Modified Paths: -------------- trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py trunk/pybridge/pybridge/ui/canvas.py trunk/pybridge/pybridge/ui/cardarea.py Modified: trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py =================================================================== --- trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py 2007-07-11 14:07:44 UTC (rev 478) +++ trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py 2007-07-12 14:01:43 UTC (rev 479) @@ -208,7 +208,9 @@ self.takeseat.set_menu(menu) # Set up CardArea widget. - self.cardarea = CardArea() + self.cardarea = CardArea(positions=Direction) + #self.cardarea.set_player_mapping(focus=Direction.South) + self.cardarea.on_card_clicked = self.on_card_clicked self.cardarea.on_hand_clicked = self.on_hand_clicked self.cardarea.set_size_request(640, 480) @@ -229,7 +231,8 @@ exp.add(frame) self.sidebar.pack_start(exp) -# self.lasttrick = CardArea() +# self.lasttrick = CardArea(positions=Direction) +# self.lasttrick.set_player_mapping(focus=Direction.South) # frame = gtk.Frame() # frame.add(self.lasttrick) # exp = gtk.Expander(_('Last Trick')) @@ -251,7 +254,7 @@ def errback(self, failure): print "Error: %s" % failure.getErrorMessage() - #print failure.getBriefTraceback() + print failure.getBriefTraceback() def setTable(self, table): @@ -271,18 +274,13 @@ # If trick play in progress, redraw trick. if self.table.game.play: self.redrawTrick() + # TODO: redraw last trick. self.setTurnIndicator() for call in self.table.game.bidding.calls: position = self.table.game.bidding.whoCalled(call) self.biddingview.add_call(call, position) - # If playing, set trick counts. - if self.table.game.play: - for position, cards in self.table.game.play.played.items(): - for card in cards: - self.addCard(card, position) - # If user is a player and bidding in progress, open bidding box. if self.player and not self.table.game.bidding.isComplete(): bidbox = self.children.open(WindowBidbox, parent=self) @@ -311,24 +309,6 @@ self.dashboard.set_vulnerability(self.table.game) - def addCard(self, card, position): - """""" -# position = self.table.game.play.whoPlayed(card) -# column = position.index -# row = self.table.game.play.played[position].index(card) -# -# if self.trick_store.get_iter_first() == None: -# self.trick_store.append() -# iter = self.trick_store.get_iter_first() -# for i in range(row): -# iter = self.trick_store.iter_next(iter) -# if iter is None: -# iter = self.trick_store.append() -# -# format = render_card(card) -# self.trick_store.set(iter, column, format) - - def gameComplete(self): # Display all previously revealed hands - the server will reveal the others. for position in self.table.game.visibleHands: @@ -535,12 +515,15 @@ def event_playCard(self, card, position): # Determine the position of the hand from which card was played. playfrom = self.table.game.play.whoPlayed(card) - self.addCard(card, playfrom) self.setTurnIndicator() self.dashboard.set_trickcount(self.table.game) self.redrawTrick() self.redrawHand(playfrom) - + +# if len(self.table.game.play.winners) > 0: +# lasttrick = self.table.game.play.getTrick(len(self.table.game.play.winners) - 1) +# self.lasttrick.set_trick(lasttrick) + if not self.table.game.inProgress(): self.gameComplete() @@ -576,6 +559,7 @@ def success(r): self.cardarea.set_player_mapping(self.position) + #self.lasttrick.set_player_mapping(self.position) if self.table.game.inProgress(): d = self.player.callRemote('getHand') d.addCallbacks(self.table.game.revealHand, self.errback, Modified: trunk/pybridge/pybridge/ui/canvas.py =================================================================== --- trunk/pybridge/pybridge/ui/canvas.py 2007-07-11 14:07:44 UTC (rev 478) +++ trunk/pybridge/pybridge/ui/canvas.py 2007-07-12 14:01:43 UTC (rev 479) @@ -24,10 +24,14 @@ class CairoCanvas(gtk.DrawingArea): - """Provides a simple canvas layer for the display of graphics.""" + """A simple canvas layer for the display of graphics. + + This may be useful for other projects which require a cross-platform + PyGTK+Cairo canvas layer. + + Requirements: Cairo (>=1.0), PyGTK (>= 2.8). + """ - # TODO: enhance documentation. - background_path = config['Appearance'].get('Background', env.find_pixmap('baize.png')) background = cairo.ImageSurface.create_from_png(background_path) @@ -37,18 +41,17 @@ def __init__(self): super(CairoCanvas, self).__init__() # Initialise parent. - self.items = {} - - # Set up gtk.Widget signals. - self.connect('configure_event', self.configure) - self.connect('expose_event', self.expose) + # Set up gtk.DrawingArea signals. + self.connect('configure_event', self._configure) + self.connect('expose_event', self._expose) + def clear(self): """Clears all items from canvas.""" self.items = {} # Remove all item references. - + # Redraw background pattern on backing. width, height = self.window.get_size() context = cairo.Context(self.backing) @@ -62,39 +65,42 @@ def add_item(self, id, source, xy, z_index, opacity=1): """Places source item into items list. - @param id: unique identifier for source. - @param source: ImageSurface. - @param xy: tuple providing (x, y) coords for source in backing. - @param z_index: integer. - @param opacity: integer in range 0 to 1. + @param id: a unique identifier for source. + @param source: an ImageSurface. + @param xy: tuple providing (x, y) coords for source. + @param z_index: an integer for the z-index of the item. + @param opacity: a number between 0.0 to 1.0. """ + assert id not in self.items # Calculate and cache the on-screen area of the item. area = self.get_area(source, xy) self.items[id] = {'source': source, 'area': area, 'xy': xy, - 'z-index': z_index, 'opacity' : opacity, } + 'z-index': z_index, 'opacity' : opacity} self.redraw(*area) def remove_item(self, id): """Removes source item with identifier from items list. - @param id: unique identifier for source. + @param id: the identifier for source. """ - if self.items.get(id): - area = self.items[id]['area'] - del self.items[id] - self.redraw(*area) + assert id in self.items + area = self.items[id]['area'] + del self.items[id] + self.redraw(*area) + def update_item(self, id, source=None, xy=None, z_index=0, opacity=0): """ - @param id: unique identifier for source. - @param source: if specified, ImageSurface. - @param xy: if specified, tuple providing (x, y) coords for source - in backing. - @param z_index: if specified, integer. - @param opacity: if specified, integer in range 0 to 1. + @param id: the identifier for source. + @param source: if specified, an ImageSurface. + @param xy: if specified, tuple providing (x, y) coords for source. + @param z_index: if specified, an integer for the z-index of the item. + @param opacity: if specified, a number between 0.0 and 1.0. """ + assert id in self.items + # If optional parameters are not specified, use stored values. z_index = z_index or self.items[id]['z-index'] opacity = opacity or self.items[id]['opacity'] @@ -112,9 +118,9 @@ source = self.items[id]['source'] xy = self.items[id]['xy'] area = self.items[id]['area'] - + self.items[id] = {'source': source, 'area': area, 'xy' : xy, - 'z-index': z_index, 'opacity' : opacity, } + 'z-index': z_index, 'opacity' : opacity} self.redraw(*area) @@ -133,19 +139,18 @@ # Redraw background pattern in area. context.set_source(self.pattern) context.paint() - + # Build list of sources to redraw in area, in order of z-index. # TODO: Find sources which intersect with area. area = gtk.gdk.Rectangle(x, y, width, height) items = self.items.values() items.sort(lambda i, j : cmp(i['z-index'], j['z-index'])) - + for item in items: pos_x, pos_y = item['area'][0:2] context.set_source_surface(item['source'], pos_x, pos_y) -# context.paint() context.paint_with_alpha(item['opacity']) - + context.reset_clip() self.window.invalidate_rect((x, y, width, height), False) # Expose. @@ -153,8 +158,8 @@ def get_area(self, source, xy): """Calculates the on-screen area of the specified source centred at xy. - @param source: - @param xy: + @param source: an ImageSurface. + @param xy: tuple providing (x, y) coords for source. @return: a tuple (x, y, width, height) """ win_w, win_h = self.window.get_size() # Window width and height. @@ -192,19 +197,19 @@ return surface, context - def configure(self, widget, event): + def _configure(self, widget, event): width, height = self.window.get_size() self.backing = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) - + # Recalculate position of all items. for id, item in self.items.iteritems(): self.items[id]['area'] = self.get_area(item['source'], item['xy']) - + self.redraw(0, 0, width, height) # Full redraw required. return True # Expected to return True. - def expose(self, widget, event): + def _expose(self, widget, event): context = widget.window.cairo_create() context.rectangle(*event.area) context.clip() # Only redraw the exposed area. Modified: trunk/pybridge/pybridge/ui/cardarea.py =================================================================== --- trunk/pybridge/pybridge/ui/cardarea.py 2007-07-11 14:07:44 UTC (rev 478) +++ trunk/pybridge/pybridge/ui/cardarea.py 2007-07-12 14:01:43 UTC (rev 479) @@ -26,8 +26,7 @@ from config import config from vocabulary import * -from pybridge.bridge.card import Card -from pybridge.bridge.symbols import Direction, Rank, Suit +from pybridge.games.bridge.symbols import Rank, Suit # The order in which card graphics are expected in card mask. CARD_MASK_RANKS = [Rank.Ace, Rank.Two, Rank.Three, Rank.Four, Rank.Five, @@ -40,9 +39,10 @@ class CardArea(CairoCanvas): - """This widget is a graphical display of tricks and hands of cards. + """A graphical display of a 4-player trick-taking card game. - Requirements: Cairo (>=1.0), PyGTK (>= 2.8). + Future considerations: + - support card games with more/less than 4 players. """ # Load card mask. @@ -59,35 +59,42 @@ spacing_y = int(card_height * 0.2) - def __init__(self): + def __init__(self, positions): + """Initialise card area. + + To receive card click and hand click signals, please override the + on_card_clicked and on_hand_clicked methods. + + @param positions: a 4-tuple containing position identifiers, + starting from top and rotating clockwise. + """ super(CardArea, self).__init__() # Initialise parent. - # To receive these events, override with external method. - self.on_card_clicked = lambda card, position: True - self.on_hand_clicked = lambda position: True - - self.focus = Direction.South + self.TOP, self.RIGHT, self.BOTTOM, self.LEFT = positions + self.focus = self.BOTTOM self.hands = {} self.trick = None - self.players = {} - self.set_player_mapping(Direction.South, redraw=False) + self.playernames = {} + #self.set_player_mapping(Direction.South, redraw=False) - self.connect('button_press_event', self.button_press) + # Set up gtk.DrawingArea signals. + self.connect('button_press_event', self._button_press) self.add_events(gtk.gdk.BUTTON_PRESS_MASK) def draw_card(self, context, pos_x, pos_y, card): """Draws graphic of specified card to context at (pos_x, pos_y). - @param context: a cairo.Context + @param context: a cairo.Context on which to draw the card. @param pos_x: @param pos_y: - @param card: the Card to draw. + @param card: the card to draw. Specify a non-card to draw face-down. """ - if isinstance(card, Card): # Determine coordinates of card graphic. + if hasattr(card, 'rank') and hasattr(card, 'suit'): # A card. + # Determine coordinates of card graphic within card mask. src_x = CARD_MASK_RANKS.index(card.rank) * self.card_width src_y = CARD_MASK_SUITS.index(card.suit) * self.card_height - else: # Draw a face-down card. + else: # Card not specified; draw face-down. src_x, src_y = self.card_width*2, self.card_height*4 context.rectangle(pos_x, pos_y, self.card_width, self.card_height) @@ -101,9 +108,9 @@ """Sets the hand of player at position. Draws cards in hand to context. The hand is buffered into an ImageSurface, since hands change - infrequently and multiple calls to draw_card() are expensive. + infrequently and repeated calls to draw_card() are expensive. - @param hand: a list of Card objects. + @param hand: a list of cards. @param position: a member of Direction. @param facedown: if True, cards are drawn face-down. @param visible: a list of elements of hand to draw. @@ -141,22 +148,21 @@ coords[card] = (pos_x, pos_y) return coords - if facedown is False: + if not facedown: # Order hand by sorted suits. # Split hand into suits. suits = dict([(suit, []) for suit in Suit]) for card in hand: suits[card.suit].append(card) - # Sort suits. for suit in suits: - suits[suit].sort(reverse=True) # High to low. - # Reorder hand by sorted suits. - hand = [] + suits[suit].sort(reverse=True) # Sort suits, high to low. + hand = [] # Rebuild hand, ordered by sorted suits. for suit in RED_BLACK: hand.extend(suits[suit]) + # Retrieve hand information. saved = self.hands.get(position) if saved and saved['hand'] == hand: - # If hand has been set previously, do not recalculate coords. + # Hand has been set previously, so need not recalculate coords. coords = saved['coords'] else: coords = get_coords_for_hand() @@ -172,42 +178,41 @@ pos_x, pos_y = coords[card] self.draw_card(context, pos_x, pos_y, card) - # Save. - self.hands[position] = {'hand' : hand, 'visible' : visible, - 'surface' : surface, 'coords' : coords, - 'facedown' : facedown} + # Save hand information. + self.hands[position] = {'hand': hand, 'visible': visible, + 'surface': surface, 'coords': coords, 'facedown': facedown} - id = 'hand-%s' % position # Identifier for this item. + id = ('hand', position) # Identifier for this item. if id in self.items: self.update_item(id, source=surface) else: - xy = {self.TOP : (0.5, 0.15), self.BOTTOM : (0.5, 0.85), - self.LEFT : (0.15, 0.5), self.RIGHT : (0.85, 0.5), } - opacity = (self.players.get(position) is None) and 0.5 or 1 + xy = {self.TOP: (0.5, 0.15), self.BOTTOM: (0.5, 0.85), + self.LEFT: (0.15, 0.5), self.RIGHT: (0.85, 0.5)} + opacity = (self.playernames.get(position) is None) and 0.5 or 1 self.add_item(id, surface, xy[position], 0, opacity=opacity) def set_player_name(self, position, name=None): - """ + """Sets the name of the player at position. @param position: the position of the player. - @param name: the name of the player, or None. + @param name: the name of the player, or None for no player. """ - self.players[position] = name + self.playernames[position] = name + id = ('playername', position) # Identifier for player name item. # If no name specified, show hand at position as translucent. - if ('hand-%s' % position) in self.items: - opacity = (name is None) and 0.5 or 1 - self.update_item('hand-%s' % position, opacity=opacity) + handid = ('hand', position) + if handid in self.items: + opacity = (name and 1) or 0.5 + self.update_item(handid, opacity=opacity) - id = 'player-%s' % position - layout = pango.Layout(self.create_pango_context()) layout.set_font_description(self.font_description) if name is None: - layout.set_text('%s' % DIRECTION_NAMES[position]) + layout.set_text(DIRECTION_NAMES[position]) else: - layout.set_text('%s: %s' % (DIRECTION_NAMES[position], name)) + layout.set_text(DIRECTION_NAMES[position] + ': ' + name) # Create an ImageSurface respective to dimensions of text. width, height = layout.get_pixel_size() @@ -254,7 +259,7 @@ hands = self.hands.copy() self.hands.clear() for position in Direction: - self.set_player_name(position, self.players.get(position)) + self.set_player_name(position, self.playernames.get(position)) self.set_hand(hands[position]['hand'], position, facedown=hands[position]['facedown'], visible=hands[position]['visible']) @@ -278,7 +283,7 @@ # The order of play is the leader, then clockwise around Direction. order = Direction[leader.index:] + Direction[:leader.index] for i, position in enumerate(order): - id = 'trick-%s' % position + id = ('trick', position) old_card = self.trick and self.trick[1].get(position) or None new_card = cards.get(position) or None @@ -295,13 +300,32 @@ self.update_item(id, surface, z_index=i+1) elif self.trick: # Remove all cards from previous trick. - for player in self.trick[1]: - self.remove_item('trick-%s' % player) + for position in self.trick[1]: + id = ('trick', position) + if id in self.items: + self.remove_item(id) self.trick = trick # Save trick. - def button_press(self, widget, event): + def on_card_clicked(self, card, position): + """Called when a card is clicked by user. + + @param card: the card clicked. + @param position: the position of hand which contains card. + """ + pass # Override this method. + + + def on_hand_clicked(self, position): + """Called when a hand is clicked by user. + + @param position: the position of hand. + """ + pass # Override this method. + + + def _button_press(self, widget, event): """Determines if a card was clicked: if so, calls on_card_selected.""" if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: found_hand = False @@ -310,7 +334,7 @@ for position in self.hands: card_coords = self.hands[position]['coords'] surface = self.hands[position]['surface'] - hand_x, hand_y = self.items['hand-%s' % position]['area'][0:2] + hand_x, hand_y = self.items[('hand', position)]['area'][0:2] if (hand_x <= event.x <= hand_x + surface.get_width()) and \ (hand_y <= event.y <= hand_y + surface.get_height()): found_hand = True This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-12 16:51:42
|
Revision: 480 http://svn.sourceforge.net/pybridge/?rev=480&view=rev Author: umgangee Date: 2007-07-12 08:44:44 -0700 (Thu, 12 Jul 2007) Log Message: ----------- In CardArea, make xy-coordinates dynamic properties, and avoid rebuilding everything in set_position_mapping. Modified Paths: -------------- trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py trunk/pybridge/pybridge/ui/canvas.py trunk/pybridge/pybridge/ui/cardarea.py Modified: trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py =================================================================== --- trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py 2007-07-12 14:01:43 UTC (rev 479) +++ trunk/pybridge/pybridge/games/bridge/ui/window_bridgetable.py 2007-07-12 15:44:44 UTC (rev 480) @@ -209,7 +209,7 @@ # Set up CardArea widget. self.cardarea = CardArea(positions=Direction) - #self.cardarea.set_player_mapping(focus=Direction.South) + #self.cardarea.set_position_mapping(focus=Direction.South) self.cardarea.on_card_clicked = self.on_card_clicked self.cardarea.on_hand_clicked = self.on_hand_clicked @@ -232,7 +232,7 @@ self.sidebar.pack_start(exp) # self.lasttrick = CardArea(positions=Direction) -# self.lasttrick.set_player_mapping(focus=Direction.South) +# self.lasttrick.set_position_mapping(focus=Direction.South) # frame = gtk.Frame() # frame.add(self.lasttrick) # exp = gtk.Expander(_('Last Trick')) @@ -558,8 +558,8 @@ def on_takeseat_clicked(self, widget, position=None): def success(r): - self.cardarea.set_player_mapping(self.position) - #self.lasttrick.set_player_mapping(self.position) + self.cardarea.set_position_mapping(self.position) + #self.lasttrick.set_position_mapping(self.position) if self.table.game.inProgress(): d = self.player.callRemote('getHand') d.addCallbacks(self.table.game.revealHand, self.errback, Modified: trunk/pybridge/pybridge/ui/canvas.py =================================================================== --- trunk/pybridge/pybridge/ui/canvas.py 2007-07-12 14:01:43 UTC (rev 479) +++ trunk/pybridge/pybridge/ui/canvas.py 2007-07-12 15:44:44 UTC (rev 480) @@ -58,7 +58,7 @@ context.rectangle(0, 0, width, height) context.set_source(self.pattern) context.paint() - # Trigger a call to self.expose(). + # Trigger a call to self._expose(). self.window.invalidate_rect((0, 0, width, height), False) @@ -75,7 +75,7 @@ # Calculate and cache the on-screen area of the item. area = self.get_area(source, xy) self.items[id] = {'source': source, 'area': area, 'xy': xy, - 'z-index': z_index, 'opacity' : opacity} + 'z-index': z_index, 'opacity': opacity} self.redraw(*area) @@ -120,7 +120,7 @@ area = self.items[id]['area'] self.items[id] = {'source': source, 'area': area, 'xy' : xy, - 'z-index': z_index, 'opacity' : opacity} + 'z-index': z_index, 'opacity' : opacity} self.redraw(*area) Modified: trunk/pybridge/pybridge/ui/cardarea.py =================================================================== --- trunk/pybridge/pybridge/ui/cardarea.py 2007-07-12 14:01:43 UTC (rev 479) +++ trunk/pybridge/pybridge/ui/cardarea.py 2007-07-12 15:44:44 UTC (rev 480) @@ -58,7 +58,18 @@ spacing_x = int(card_width * 0.4) spacing_y = int(card_height * 0.2) + # Coordinates wrapped in lambdas, since positions may be remapped. + hand_xy = property(lambda s: {s.TOP: (0.5, 0.15), s.BOTTOM: (0.5, 0.85), + s.LEFT: (0.15, 0.5), s.RIGHT: (0.85, 0.5)}) + + player_xy = property(lambda s: {s.TOP: (0.5, 0.2), s.BOTTOM: (0.5, 0.9), + s.LEFT: (0.125, 0.625), s.RIGHT: (0.875, 0.625)}) + + trick_xy = property(lambda s: {s.TOP: (0.5, 0.425), s.BOTTOM: (0.5, 0.575), + s.LEFT: (0.425, 0.5), s.RIGHT: (0.575, 0.5)}) + + def __init__(self, positions): """Initialise card area. @@ -70,12 +81,13 @@ """ super(CardArea, self).__init__() # Initialise parent. + self.positions = positions self.TOP, self.RIGHT, self.BOTTOM, self.LEFT = positions self.focus = self.BOTTOM + self.hands = {} self.trick = None self.playernames = {} - #self.set_player_mapping(Direction.South, redraw=False) # Set up gtk.DrawingArea signals. self.connect('button_press_event', self._button_press) @@ -186,10 +198,8 @@ if id in self.items: self.update_item(id, source=surface) else: - xy = {self.TOP: (0.5, 0.15), self.BOTTOM: (0.5, 0.85), - self.LEFT: (0.15, 0.5), self.RIGHT: (0.85, 0.5)} - opacity = (self.playernames.get(position) is None) and 0.5 or 1 - self.add_item(id, surface, xy[position], 0, opacity=opacity) + opacity = (self.playernames.get(position) and 1) or 0.5 + self.add_item(id, surface, self.hand_xy[position], 0, opacity=opacity) def set_player_name(self, position, name=None): @@ -234,50 +244,15 @@ if id in self.items: self.update_item(id, source=surface) else: - xy = {self.TOP : (0.5, 0.2), self.BOTTOM : (0.5, 0.9), - self.LEFT : (0.125, 0.625), self.RIGHT : (0.875, 0.625), } - self.add_item(id, surface, xy[position], 2) + self.add_item(id, surface, self.player_xy[position], z_index=2) - def set_player_mapping(self, focus=Direction.South, redraw=True): - """Sets the mapping between players at table and positions of hands. - - @param focus: the position to be drawn "closest" to the observer. - @param redraw: if True, redraw the card area display immediately. - """ - # Assumes Direction elements are ordered clockwise from North. - order = Direction[focus.index:] + Direction[:focus.index] - for player, attr in zip(order, ('BOTTOM', 'LEFT', 'TOP', 'RIGHT')): - setattr(self, attr, player) - - # Only redraw if focus has changed. - if redraw and focus != self.focus: - self.focus = focus - self.clear() # Wipe all saved ImageSurface objects - not subtle! - - # Use a copy of self.hands, since it will be changed by set_hand(). - hands = self.hands.copy() - self.hands.clear() - for position in Direction: - self.set_player_name(position, self.playernames.get(position)) - self.set_hand(hands[position]['hand'], position, - facedown=hands[position]['facedown'], - visible=hands[position]['visible']) - - trick = self.trick - self.trick = None - self.set_trick(trick) - - def set_trick(self, trick): """Sets the current trick. Draws representation of current trick to context. @param trick: a (leader, cards_played) pair, or None. """ - xy = {self.TOP : (0.5, 0.425), self.BOTTOM : (0.5, 0.575), - self.LEFT : (0.425, 0.5), self.RIGHT : (0.575, 0.5), } - if trick: leader, cards = trick # The order of play is the leader, then clockwise around Direction. @@ -291,7 +266,7 @@ if old_card is None and new_card is not None: surface, context = self.new_surface(self.card_width, self.card_height) self.draw_card(context, 0, 0, new_card) - self.add_item(id, surface, xy[position], z_index=i+1) + self.add_item(id, surface, self.trick_xy[position], z_index=i+1) elif new_card is None and old_card is not None: self.remove_item(id) elif old_card != new_card: @@ -308,6 +283,44 @@ self.trick = trick # Save trick. + def set_position_mapping(self, focus): + """Move displayed items to new positions, with respect to focus. + + @param focus: the position to be drawn 'closest' to user. + """ + if self.focus == focus: + return # No need to do anything! + self.focus = focus + + # Remap position symbols, with self.BOTTOM assigned focus. + order = self.positions + neworder = order[focus.index:] + order[:focus.index] + self.BOTTOM, self.LEFT, self.TOP, self.RIGHT = neworder + + # Disclaimer: + # CardArea was not designed with this operation in mind, and the + # following code works as 'brain surgery' on the unsuspecting module. + + # Use a copy of self.hands, since it will be changed by set_hand(). + hands = self.hands.copy(); self.hands.clear() + # Make a copy of self.items, since self.clear() resets it. + items = self.items.copy() + self.clear() # Resets self.items. + + for id, item in items.iteritems(): + cls, position = id + self.items[id] = item # Add the item back. + + if cls == 'hand': # Rebuild hands. + del self.items[id] + h = hands[position] + self.set_hand(h['hand'], position, h['facedown'], h['visible']) + elif cls == 'playername': + self.update_item(id, xy=self.player_xy[position]) + elif cls == 'trick': + self.update_item(id, xy=self.trick_xy[position]) + + def on_card_clicked(self, card, position): """Called when a card is clicked by user. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <umg...@us...> - 2007-07-22 15:49:47
|
Revision: 487 http://svn.sourceforge.net/pybridge/?rev=487&view=rev Author: umgangee Date: 2007-07-22 08:49:44 -0700 (Sun, 22 Jul 2007) Log Message: ----------- Simplify some remote-invokable methods. Modified Paths: -------------- trunk/pybridge/pybridge/network/client.py trunk/pybridge/pybridge/server/server.py trunk/pybridge/pybridge/server/user.py Modified: trunk/pybridge/pybridge/network/client.py =================================================================== --- trunk/pybridge/pybridge/network/client.py 2007-07-20 17:35:23 UTC (rev 486) +++ trunk/pybridge/pybridge/network/client.py 2007-07-22 15:49:44 UTC (rev 487) @@ -188,12 +188,11 @@ self.notify('joinTable', tableid=tableid, table=table) return table + params = {} if host: - # TODO: why not just joinTable, host=True? - gamename = gameclass.__name__ - d = self.avatar.callRemote('hostTable', tableid, gamename) - else: - d = self.avatar.callRemote('joinTable', tableid) + params['gamename'] = gameclass.__name__ + + d = self.avatar.callRemote('joinTable', tableid, host, **params) d.addCallback(success) return d Modified: trunk/pybridge/pybridge/server/server.py =================================================================== --- trunk/pybridge/pybridge/server/server.py 2007-07-20 17:35:23 UTC (rev 486) +++ trunk/pybridge/pybridge/server/server.py 2007-07-22 15:49:44 UTC (rev 487) @@ -33,10 +33,9 @@ onlineUsers = LocalUserManager() -def getServerInfo(): - return {'compatibleVersions': (version, version), # minimum, maximum - 'supportedGames': 'bridge', # TODO - 'version': version} +serverData = {'compatibleClients': (version, version), # minimum, maximum + 'supportedGames': SUPPORTED_GAMES.keys(), + 'version': version} def registerUser(username, password): @@ -54,8 +53,8 @@ log.msg("New user %s registered" % username) -def changeUserPassword(username, password): - """Sets the password of user's account. +def setUserPassword(username, password): + """Changes the password of user's account. @param username: the user identifier. @param password: the new password for user. @@ -67,11 +66,12 @@ raise DeniedRequest, "User account does not exist" -def createTable(tableid, gamename): +def createTable(tableid, gamename, **tableOptions): """Create a new table for the specified game type. @param tableid: a unique identifier for the table. - @param gametype: a game identifier. + @param gamename: a game class identifier. + @param tableOptions: optional parameters for table initialisation. """ # TODO: convert gametype string to corresponding class. @@ -87,5 +87,6 @@ # Provide table instance with a means of closing itself. table.close = lambda: availableTables.closeTable(table) availableTables.openTable(table) + return table Modified: trunk/pybridge/pybridge/server/user.py =================================================================== --- trunk/pybridge/pybridge/server/user.py 2007-07-20 17:35:23 UTC (rev 486) +++ trunk/pybridge/pybridge/server/user.py 2007-07-22 15:49:44 UTC (rev 487) @@ -28,7 +28,8 @@ class RegisteredUser(pb.Avatar): - info = property(lambda self: {}) # TODO: Send profile data? + # Static for duration of connection. + info = property(lambda self: {'registered': True}) def __init__(self, name): @@ -56,11 +57,6 @@ # Perspective methods, accessible by client. - def perspective_getServerInfo(self): - """Provides a dict of information about the server.""" - return server.getServerInfo() - - def perspective_getRoster(self, name): """Provides roster requested by client.""" if name == 'tables': @@ -71,44 +67,59 @@ raise DeniedRequest, "Unknown roster name \'%s\'" % name - def perspective_getUserProfile(self, username): - """Provides profile information for user with specified username.""" - pass + def perspective_getServerData(self): + """Provides a dict of information about the server.""" + return server.serverData + def perspective_getUserInformation(self, username=None): + """Returns public information for user with specified username. + + If username is unspecified, returns user's own profile. + """ + if username is None: + username = self.name + + try: + user = db.UserAccount.selectBy(username=username)[0] + except IndexError: + raise DeniedRequest, "Specified user does not exist" + + return {'realname': user.realname, 'email': user.email, + 'profile': user.profile} + + +# def perspective_setProfile(self, **kwargs): +# """Sets avatar's user account profile information to that specified.""" +# pass + + def perspective_changePassword(self, password): """Sets avatar's user account password to that specified.""" if not isinstance(password, str): raise IllegalRequest, "Invalid parameter for password" - try: - server.changeUserPassword(self.name, password) + try: # Validate password before it is changed. + server.setUserPassword(self.name, password) except ValueError, err: # Password validation failed. raise DeniedRequest, err - def perspective_hostTable(self, tableid, gametype): - """Creates a new table.""" + def perspective_joinTable(self, tableid, host=False, **hostParams): + """Joins an existing table, or creates and joins a new table.""" if not isinstance(tableid, str): raise IllegalRequest, "Invalid parameter for table identifier" - if not isinstance(gametype, str): - raise IllegalRequest, "Invalid parameter for game type" - - table = server.createTable(tableid, gametype) - # Force client to join table. - return self.perspective_joinTable(tableid) + elif tableid in self.joinedTables: + raise DeniedRequest, "Already joined table" + if host: + table = server.createTable(tableid, **hostParams) + else: + try: + table = server.availableTables[tableid] + except KeyError: + raise DeniedRequest, "No such table" - def perspective_joinTable(self, tableid): - """Joins an existing table.""" - if not isinstance(tableid, str): - raise IllegalRequest, "Invalid parameter for table identifier" - elif tableid not in server.availableTables: - raise DeniedRequest, "No such table" - elif tableid in self.joinedTables: - raise DeniedRequest, "Already joined table" - - table = server.availableTables[tableid] self.joinedTables[tableid] = table return table @@ -135,3 +146,4 @@ server.registerUser(username, password) except ValueError, err: # Username/password validation failed. raise DeniedRequest, err + This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |