gameclock-5.0/0000755000000000000000000000000012141542305010166 5ustar gameclock-5.0/scripts/0000755000000000000000000000000012141542147011661 5ustar gameclock-5.0/scripts/gameclock0000755000000000000000000000314012141542147013532 0ustar #!/usr/bin/env python # # gameclock - a simple chess/game clock import getopt import sys import os # crude hack to support running from the source directory d, s = os.path.split(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))) if s == 'scripts': sys.path.insert(0, d) import gameclock verbose = 0 # 0 means not verbose, higher numbers show more information, see ui.debug for more info def usage(): """gameclock v%s %s Usage: %s [ -h | -v ... | -f ] -h --help: display this help -v --verbose: display progress information to stdout. repeating the flag will show more information -f --fullscreen: start in fullscreen mode See the manpage for more information.""" print usage.__doc__ % (gameclock.__version__, gameclock.__copyright__, sys.argv[0]) if __name__ == "__main__": try: opts, args = getopt.getopt(sys.argv[1:], "hvf", ["help", "verbose", "fullscreen"]) except getopt.GetoptError, err: # print help information and exit: usage() print "\nError: %s" % err # will print something like "option -a not recognized" sys.exit(2) settings = {} for o, a in opts: if o in ("-v", "--verbose"): if not 'verbose' in settings: settings['verbose'] = 0 settings['verbose'] += 1 elif o in ("-h", "--help"): usage() sys.exit() elif o in ("-f", "--fullscreen"): settings['fullscreen'] = True else: assert False, "unhandled option" from gameclock.gtkui import GameclockUI clock = GameclockUI(**settings) clock.main() gameclock-5.0/run.py0000777000000000000000000000000012141542147014704 2scripts/gameclockustar gameclock-5.0/gameclock.svg0000644000000000000000000003240612141542147012645 0ustar Analogue Clock clock signs_and_symbols Dennis Craven Dennis Craven Dennis Craven image/svg+xml en gameclock-5.0/gameclock.60000644000000000000000000000553212141542147012213 0ustar .\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH GAMECLOCK 6 "August 13, 2008" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME gameclock \- a simple game clock .SH SYNOPSIS .B gameclock .SH DESCRIPTION This manual page documents briefly the .B gameclock command. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBgameclock\fP is a simple application designed to track the time spent thinking by the players during a chess game. Various ways of tracking time are supported, with only `countdown' (aka `blitz') and `fischer' for now. The graphical interface is keyboard driven and is intended to be minimal and simple. The code is made to be extensible to other game types. .SH OPTIONS These programs follow the usual GNU command line syntax, with long options starting with two dashes (\-\-). A summary of options is included below. .TP .B \-h, \-\-help Show summary of options. This includes the program's version and license. .TP .B \-v, \-\-verbose Show progress information to standard output. More `\-v' will display more information: timestamps then game state engine. .TP .B \-f, \-\-fullscreen Start the application in fullscreen mode. .SH KEYBINDINGS Most of the interface is keyboard-driven, apart from the initial clock and game configuration. .TP .B space start the game / end turn .TP .B shift keys end the turn, but only for the appropriate side (left or right side) .TP .B p pause game .TP .B control-r, escape reset game - this resets all counters and clocks to the initial configuration, use this to restart the game. .TP .B f toggle the fullscreen mode .TP .B control-q quit application .SH THEMING The look of .B gameclock can be customized fairly easily using GTK resource files. The clock widgets are conveniently named to allow easy customizing. Take for example the sample "Green" builtin theme: .PP .nf style "clockui" { bg[NORMAL] = "black" fg[NORMAL] = "white" bg[SELECTED] = "red" fg[SELECTED] = "black" bg[ACTIVE] = "green" fg[ACTIVE] = "black" } widget "*.clockui.GtkEventBox" style "clockui" widget "*.clockui.*Label" style "clockui" .fi .SH SEE ALSO .BR xboard (1), .BR http://gnomecoder.wordpress.com/chessclock/ . .br .SH AUTHOR gameclock was written by Antoine Beaupré. gameclock-5.0/HACKING.mdwn0000644000000000000000000001505412141542147012132 0ustar This file documents the developpment process and the internal structure of the code from a more general standpoint. Release process =============== Code is maintained in Git, and bugs are tracked on a Koumbit Redmine project here: https://redmine.koumbit.net/projects/gameclock/ Details on how to checkout the code should also be available there. Checklist --------- 1. look at the issue queues to see if there are any bugs to be fixed: https://redmine.koumbit.net/projects/gameclock/issues http://bugs.debian.org/gameclock 2. update the `debian/changelog` with the latest changes: dch -i "this release blabla" 3. update the version number in `gameclock/__init__.py` 4. lay down a tag: git tag -s -u anarcat@debian.org X.Y 5. build and test the debian package: git-buildpackage 6. push the code and tag: git push git push --tags 7. publish the tarball generated by git-buildpackage in Redmine 8. publish the Debian package in Debian 9. announce the release in Redmine, Freshmeat, etc? Code structure ============== The code is written in Python with the GTK GUI frontend. However, it is designed to support multiple backends and a class could be written for a ncurses frontend for example. General ------- Most classes override the __str__() handler to provide a human-readable debugging version of the object. There are some global module variables, more or less self-documented. Command line parsing is done in the traditionnal 'main block' of python programs. Game class ---------- The game class is a general representation of a chess game that doesn't take account the moves, position or board in any way. It only counts turns and timing. It is now designed to support an arbitrary number of players (above 0 of course). It's mostly a placeholder for the clocks (a linked list of Clock objects, with the head being Game.first_clock). It has a notion of the clock current active (Game.cur_clock) that gets updated when the turns end (Game.end_turn(), which in turns calls Clock.stop(), and Clock.start() on the relevant clocks and increments the turn count) and when the clocks are switched (Game.switch_clock()). There's also a handler to start the game (Game.start()) that starts and updates the right clock and pause the game. The turn counting is a bit peculiar and so needs a bit of explaining. It is counted using a float (Game.turns) that is incremented by a half (0.5) at every Game.end_turn(). So turns, in a way, count the number of 'two turns', which is a bit of a vocabulary problem here. Clock class ----------- The clock class represents a player's clock. It can be paused, stopped and started. The difference between pause() and stop() is that pause() will restart the clock when called twice. stop() will also add extra time to the player's clock if the clock's mode is fischer. The way time is counted is this: there is an integer (Clock.time) that counts the number of miliseconds passed playing by a player. That number is incremented at the end of that player's turn (and also when the game is paused). To evaluate the time spent in a turn, a float (Clock.last_start) that marks the beginning of the turn (or the last pause()) is marked when the turn starts (or when the game is unpaused). When the time ends (or the game is pause()d), that time is compared to the current time as returned by by Python's time()[4] function and is added to the player's clock time (Clock.time). All that processing is isolated in Clock.get_time(). So in summary Clock.time contains the time spent by the player throughout the game not including the turn he's currently playing (if any). Therefore, to get the proper value, Clock.get_time() needs to be used. The Clock class also keeps an string representation (Clock.text), a cache of a human-readable version of the clock's state. It displays the hours, minutes and seconds of time counted by the clock. Depending on the truth value of Clock.miliseconds, it will also (or not) show the miliseconds. If the clock goes beyond 24h, it will also display the number of days. If the clock goes below zero it will display a minus in front of the string. For performance reasons, that cache is updated only when relevant (with Clock.update()). Care has been taken to call that function as little as possible. Python's strftime() function[4] is currently used for rendering hours, minutes and seconds. Similarly, the clock keeps a cache of the negativness of the clock (Clock.dead). That cache is also updated only when necessary (again with Clock.update()). The clock depends on the Game to manage clock changes and its internal engine is therefore considered to be exposed to other classes. Also note that a Clock is usually part of a chained list of clocks through the Clock.next pointer. FischerClock class ''''''''''''''''' This is a subclass of the generic Clock class that implements fischer style time tracking: the stop() function has simply been overriden to add n miliseconds to the clock before switching. Also note that the constructor is different to allow customization of that delay. Clock precision ''''''''''''''' There are some areas where the clock might introduce some imprecision. It can be due to Python's floating point arithmetics, since the number of miliseconds is deduced from the mantissa of the float returned by time(), but that's probably negligible. Most of the imprecision is likely to come from the time spent in the end_turn() function (and of course the general system processing between the players brain's, the computer keyboard, the kernel, X11, GTK and the Python main loop). I would expect this to be lower than 10ms, but I have absolutely no metrics to prove that assertion. [4] http://docs.python.org/lib/module-time.html User interface -------------- When/if a new frontend is written, it would probably appropriate to refactor some code of the GameclockGtkUI class into a parent class. In the meantime, that code was moved to a separate file to ease extensibility. The GTK UI code has became messy. Some work has been done to uncouple it from the game engine, but it still needs to be improved on that side. A significant amount of work was done to move the UI buttons to a separate window that pops up on start up and doesn't clutter the UI. The code is not much more readable but at least there is more isolation between the game handling and configuration sides. There is a ClockUI subclass that regroups each clock's widget. As the Clock class, it is organised as a chained list and can be iterated similarly. A next step would be to cleanup the gtkui.py file to make it more readable and modular. gameclock-5.0/msgfmt.py0000755000000000000000000001451312141542147012050 0ustar #!/usr/bin/env python # -*- coding: iso-8859-1 -*- # Written by Martin v. Lwis # Plural forms support added by alexander smishlajev """ Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into a binary GNU catalog (.mo file). This is essentially the same function as the GNU msgfmt program, however, it is a simpler implementation. Usage: msgfmt.py [OPTIONS] filename.po Options: -o file --output-file=file Specify the output file to write to. If omitted, output will go to a file named filename.mo (based off the input file name). -h --help Print this message and exit. -V --version Display version information and exit. """ import sys import os import getopt import struct import array __version__ = "1.1" MESSAGES = {} def usage (ecode, msg=''): """ Print usage and msg and exit with given code. """ print >> sys.stderr, __doc__ if msg: print >> sys.stderr, msg sys.exit(ecode) def add (msgid, transtr, fuzzy): """ Add a non-fuzzy translation to the dictionary. """ global MESSAGES if not fuzzy and transtr and not transtr.startswith('\0'): MESSAGES[msgid] = transtr def generate (): """ Return the generated output. """ global MESSAGES keys = MESSAGES.keys() # the keys are sorted in the .mo file keys.sort() offsets = [] ids = strs = '' for _id in keys: # For each string, we need size and file offset. Each string is NUL # terminated; the NUL does not count into the size. offsets.append((len(ids), len(_id), len(strs), len(MESSAGES[_id]))) ids += _id + '\0' strs += MESSAGES[_id] + '\0' output = '' # The header is 7 32-bit unsigned integers. We don't use hash tables, so # the keys start right after the index tables. # translated string. keystart = 7*4+16*len(keys) # and the values start after the keys valuestart = keystart + len(ids) koffsets = [] voffsets = [] # The string table first has the list of keys, then the list of values. # Each entry has first the size of the string, then the file offset. for o1, l1, o2, l2 in offsets: koffsets += [l1, o1+keystart] voffsets += [l2, o2+valuestart] offsets = koffsets + voffsets output = struct.pack("Iiiiiii", 0x950412deL, # Magic 0, # Version len(keys), # # of entries 7*4, # start of key index 7*4+len(keys)*8, # start of value index 0, 0) # size and offset of hash table output += array.array("i", offsets).tostring() output += ids output += strs return output def make (filename, outfile): ID = 1 STR = 2 global MESSAGES MESSAGES = {} # Compute .mo name from .po name and arguments if filename.endswith('.po'): infile = filename else: infile = filename + '.po' if outfile is None: outfile = os.path.splitext(infile)[0] + '.mo' try: lines = open(infile).readlines() except IOError, msg: print >> sys.stderr, msg sys.exit(1) section = None fuzzy = 0 # Parse the catalog msgid = msgstr = '' lno = 0 for l in lines: lno += 1 # If we get a comment line after a msgstr, this is a new entry if l[0] == '#' and section == STR: add(msgid, msgstr, fuzzy) section = None fuzzy = 0 # Record a fuzzy mark if l[:2] == '#,' and (l.find('fuzzy') >= 0): fuzzy = 1 # Skip comments if l[0] == '#': continue # Start of msgid_plural section, separate from singular form with \0 if l.startswith('msgid_plural'): msgid += '\0' l = l[12:] # Now we are in a msgid section, output previous section elif l.startswith('msgid'): if section == STR: add(msgid, msgstr, fuzzy) section = ID l = l[5:] msgid = msgstr = '' # Now we are in a msgstr section elif l.startswith('msgstr'): section = STR l = l[6:] # Check for plural forms if l.startswith('['): # Separate plural forms with \0 if not l.startswith('[0]'): msgstr += '\0' # Ignore the index - must come in sequence l = l[l.index(']') + 1:] # Skip empty lines l = l.strip() if not l: continue # XXX: Does this always follow Python escape semantics? l = eval(l) if section == ID: msgid += l elif section == STR: msgstr += l else: print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ 'before:' print >> sys.stderr, l sys.exit(1) # Add last entry if section == STR: add(msgid, msgstr, fuzzy) # Compute output output = generate() try: open(outfile,"wb").write(output) except IOError,msg: print >> sys.stderr, msg def main (): try: opts, args = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version', 'output-file=']) except getopt.error, msg: usage(1, msg) outfile = None # parse options for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-V', '--version'): print >> sys.stderr, "msgfmt.py", __version__ sys.exit(0) elif opt in ('-o', '--output-file'): outfile = arg # do it if not args: print >> sys.stderr, 'No input file given' print >> sys.stderr, "Try `msgfmt --help' for more information." return for filename in args: make(filename, outfile) if __name__ == '__main__': main() gameclock-5.0/HISTORY.mdwn0000644000000000000000000000452412141542147012227 0ustar Playing chess with friends in the Pampa Humida, I couldn't find a proper way to teach my friends on how to play quickly enough so that the game wouldn't become boring as hell. So I found this [web page](1) with a simple chess clock on it. Written in javascript, it wasn't exactly reliable or fast, and required me to have network access (unless I saved the page to my machine, but anyways). Also, the web browser overhead was too much for my taste. [1]: http://smashhatter.com/chess/chesstimer/chessTimer.html So I started looking around for software to fulfill my needs. The only clock I could find was [Ghronos](2) but it was Java-based and (so?) I couldn't get it to run natively on my machine (it would run in a browser, but then I would be back to square one). [2]: http://ghronos.sourceforge.net/ So I opened up Emacs and started hacking at a pygtk program, because I like Python and I found that GTK looked decent enough I wanted to learn it. Within a few hours, I got a basic version working (0.1) that was just a countdown. A few hours more and I got fischer delays implemented (0.2). (That took around five hours according to the changelog.) Then I went to debconf8 in Mar del Plata and tried real hard (okay, I didn't try at all) to keep myself from working on the software and follow the conference, and failed, so I polished the interface and implemented more features: a reset handler, control over the initial time (duh!), colors, etc. Now I think it's a pretty decent clock, still lacking some features, but it's been fun anyways. Now I've got a 1.0 version which seems pretty mature to me anyways. And only now does a friend of mine point me to an already existing "chessclock" program, written in [ruby](3), and much to my demise: it works well and looks pretty good! Still, I had fun writing my version of the software, it's just unfortunate to duplicate work like this. I have therefore started integrating the features from "chessclock" missing in my program, namely: fullscreen mode, shift keys to change turns and turn counting. I consider now both applications to be roughly equivalent. [3]: http://gnomecoder.wordpress.com/chessclock/ I have now therefore renamed my software to pychessclock to avoid confusion. Version 2.0 rewrites the gaming engine to support any number of clocks and the package was therefore renamed again, this time to gameclock. gameclock-5.0/README.mdwn0000644000000000000000000000251312141542147012017 0ustar A python Chess clock ==================== This is a simple game clock (mainly for chess now) that is designed to be fast and simple to use. Usage ----- Starting it should be as simple as just starting the script from the shell: ./scripts/gameclock If that doesn't work, use the python interpreter directly: python scripts/gameclock Most of the controls are done through the keyboard, see the features list for the shortcuts. Requirements ------------ * Python 2.3 or greater * PyGTK 2.0 or greater License ------- The chess clock is free software, see the COPYRIGHT file for more information. Features -------- Arbitrary number of clocks. Can act as a chronometer when only one clock is enabled Game modes: * Class (Times per move) - after move, reset counter * Blitz - simple countdown * Fischer - add N seconds after player move * Hourglass - time used by a player is given to the other(s) Key controls (buttons): * space - start game, end turn * shift keys end turn for the right or left player, as appropriate * control-n - start a new game * control-p - pause * control-q - quit * control-f - toggle fullscreen Other controls: * starting left or right * setting initial time * miliseconds display Bugs, missing features and project updates are reported here: https://redmine.koumbit.net/projects/gameclock gameclock-5.0/po/0000755000000000000000000000000012141542147010610 5ustar gameclock-5.0/po/Makefile0000644000000000000000000000041612141542147012251 0ustar *.po: messages.pot msgmerge -U $@ $< messages.pot: ../gameclock/game.py ../scripts/gameclock ../gameclock/gtkui.py pygettext -k_ -kN_ -D -o $@ ../gameclock/game.py xgettext -k_ -kN_ -L Python -j -o $@ ../scripts/gameclock ../gameclock/gtkui.py ../gameclock/clock.py gameclock-5.0/po/fr_CA.po0000644000000000000000000002137612141542147012133 0ustar # French translations for PACKAGE package # Traductions françaises du paquet PACKAGE. # Copyright (C) 2013 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # anarcat , 2013. # msgid "" msgstr "" "Project-Id-Version: 5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-01-22 23:50-0500\n" "PO-Revision-Date: 2013-04-07 19:26-0400\n" "Last-Translator: Antoine Beaupré \n" "Language-Team: French\n" "Language: fr_CA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Poedit-Language: French\n" "X-Poedit-Country: CANADA\n" #: ../gameclock/game.py:161 msgid "A regular chess game of 60 minutes per player, with no increment." msgstr "" "Une partie d'échecs régulière de 60 minutes par joueur, sans délai spécial." #: ../gameclock/game.py:174 msgid "A quick 15 minutes per player chess game, no increment." msgstr "Une partie rapide, 15 minutes par jour, sans incrémentation." #: ../gameclock/game.py:179 msgid "A very fast chess game, 2 minutes per player no increment." msgstr "Une partie très rapide, 5 minutes par jour, sans incrémentation." #: ../gameclock/game.py:184 msgid "" "A delay timing style used for chess and designed by Bobby Fischer. Everytime " "the player makes a move, a delay is added to the clock. Defaults: 2 minutes " "per player, 10 second increment, results in a reasonably short chess game." msgstr "" "Un style d'horloge à délai pour les échecs conçue par Bobby\n" "Fischer. Chaque fois qu'un joueur termine son tour, un délai est\n" "ajouté à son horloge. 10 secondes de délai par défaut et un compteur\n" "de départ de 2 minutes donne une partie d'une durée raisonnable." #: ../gameclock/game.py:189 msgid "" "A regular board game. A clock goes down until time runs out. The counter is " "reset at every move. Default is to give 2 minutes per move." msgstr "" "Une partie de table normale, 2 minutes par tour, remise à zéro à\n" "chaque tour." #: ../gameclock/game.py:193 msgid "" "Behave like an hourglass: the clock goes down on one side and up on the " "other, 60 seconds by default." msgstr "" "Comme un sablier: quand une horloge augmente, l'autre descend. 60\n" "secondes par défaut." #: ../gameclock/game.py:199 msgid "" "A standard or \"japanese\" Go counter. A clock counts down until its time " "runs out and then enters overtime. Once in overtime the player has a " "specific number of overtime periods (or \"Byo-yomi\") to play. If the player " "moves within the chosen delay, the clock's time reverts to the top of the " "period and no periods are lost. If the player does not move within the " "period, the next period begins and the period count is reduced by 1. A " "player loses when his/her clock runs out of time and no more periods remain." msgstr "" "Une horloge de go standard ou \"japonaise\". L'horloge descend jusqu'à\n" "ce que le temps arrive à zéro. À ce moment le joueur a un nombre de\n" "périodes supplémentaires (le \"byo-yomi\") pour jouer. Si le joueur joue\n" "durant le délai spécifié, l'horloge revient au délai de départ et\n" "aucune période n'est perdue. Si le joueur ne joue pas durant la\n" "période, la période suivante commence et le nombre de période est\n" "réduit de 1. Le joueur perd lorsque son horloge n'a plus de temps et\n" "qu'il n'y a plus de période disponible." #: ../gameclock/gtkui.py:65 msgid "Default" msgstr "Par défaut" #: ../gameclock/gtkui.py:65 msgid "Use the default theme" msgstr "Utiliser le thème par défaut" #: ../gameclock/gtkui.py:66 msgid "Green" msgstr "Vert" #: ../gameclock/gtkui.py:66 #, fuzzy msgid "" "Selected player is green, dead player is red, normal background black (4.0 " "default)" msgstr "" "Le joueur actif est vert, le jouer mort est rouge, et le fond est noir\n" "(équivalent à la version 4.0)" #: ../gameclock/gtkui.py:80 msgid "Blue" msgstr "Bleu" #: ../gameclock/gtkui.py:80 msgid "Selected player is blue, dead player is red, normal background black" msgstr "Le joueur actif bleu, le joueur mort rouge, le fond noir." #: ../gameclock/gtkui.py:107 msgid "_Game" msgstr "_Jeu" #: ../gameclock/gtkui.py:108 msgid "_New" msgstr "_Nouvelle partie" #: ../gameclock/gtkui.py:109 msgid "Start" msgstr "Démarrer" #: ../gameclock/gtkui.py:110 msgid "Start/pause game" msgstr "Démarrer/arrêter la partie" #: ../gameclock/gtkui.py:111 msgid "_Full screen" msgstr "Plein écran" #: ../gameclock/gtkui.py:112 msgid "Full screen mode" msgstr "Mode plein écran" #: ../gameclock/gtkui.py:113 ../gameclock/gtkui.py:177 msgid "_Quit" msgstr "_Quitter" #: ../gameclock/gtkui.py:114 msgid "Quit the Program" msgstr "Quitter le programme" #: ../gameclock/gtkui.py:115 msgid "_Settings" msgstr "_Réglages" #: ../gameclock/gtkui.py:116 msgid "_Set time..." msgstr "_Temps..." #: ../gameclock/gtkui.py:117 msgid "Set the starting time of clocks" msgstr "Régler le temps initial des horloges" #: ../gameclock/gtkui.py:118 msgid "_Players..." msgstr "_Joueurs..." #: ../gameclock/gtkui.py:119 msgid "Set the number of players and starting player" msgstr "Régler le nombre de joueur et le premier joueur" #: ../gameclock/gtkui.py:120 msgid "Theme" msgstr "Thème" #: ../gameclock/gtkui.py:121 msgid "_Help" msgstr "_Aide" #: ../gameclock/gtkui.py:122 msgid "_About" msgstr "À propos..." #: ../gameclock/gtkui.py:123 msgid "More information about this software" msgstr "Obtenir plus d'informations sur ce logiciel" #: ../gameclock/gtkui.py:124 msgid "Keyboard shortcuts" msgstr "Raccourcis clavier" #: ../gameclock/gtkui.py:125 msgid "Display the available keyboard shortcuts" msgstr "Montrer les raccourcis claviers disponibles" #: ../gameclock/gtkui.py:178 msgid "_Sound" msgstr "_Son" #: ../gameclock/gtkui.py:178 msgid "Enable/disable sound" msgstr "Activer/désactiver le son" #: ../gameclock/gtkui.py:428 msgid "Change time settings" msgstr "Changer le temps des horloges" #: ../gameclock/gtkui.py:446 msgid "Time limit: " msgstr "Limite de temps: " #: ../gameclock/gtkui.py:457 msgid "Delay: " msgstr "Délai: " #: ../gameclock/gtkui.py:469 msgid "Number of Byo-yomi: " msgstr "Nombre de Byo-yomi: " #: ../gameclock/gtkui.py:496 msgid "Change players settings" msgstr "Changer les joueurs" #: ../gameclock/gtkui.py:505 msgid "Number of players: " msgstr "Nombre de joueurs: " #: ../gameclock/gtkui.py:517 msgid "Starting player: " msgstr "Premier joueur: " #: ../gameclock/gtkui.py:519 msgid "Left" msgstr "Gauche" #: ../gameclock/gtkui.py:522 msgid "Right" msgstr "Droite" #: ../gameclock/gtkui.py:594 msgid "Resume" msgstr "Reprendre" #: ../gameclock/gtkui.py:595 msgid "game paused" msgstr "partie en pause" #: ../gameclock/gtkui.py:598 ../gameclock/gtkui.py:611 msgid "Pause" msgstr "Pause" #: ../gameclock/gtkui.py:599 msgid "game resumed" msgstr "partie reprise" #: ../gameclock/gtkui.py:613 msgid "game running" msgstr "partie en cours" #: ../gameclock/gtkui.py:636 msgid "" "Keyboard shortcuts\n" "\n" "shift and space keys end turns\n" "control-q quits\n" "control-n starts a new game\n" "control-f enables fullscreen mode\n" "control-p, F5-F8 pause the game" msgstr "" #: ../gameclock/gtkui.py:643 msgid "" "Left side of the keyboard ends left side's turn and vice-versa for right " "turn.\n" "\n" "We do not currently handle the numpad and arrow keys as we can't tell if " "they are present or not (e.g. laptops) and that would favor too much right " "right side if it is present." msgstr "" #: ../gameclock/gtkui.py:654 msgid "A simple game clock to be used for Chess or any board game." msgstr "" "Un chronomètre de jeu simple pour les jeux d'échecs ou tout autre jeu\n" "de table." #: manual entry for generated content msgid "Regular Chess" msgstr "Échecs (normal)" #: manual entry for generated content msgid "Quick Chess" msgstr "Échecs (rapide)" #: manual entry for generated content msgid "Lightning Chess" msgstr "Échecs (éclair)" #: manual entry for generated content msgid "Hourglass" msgstr "Sablier" #: manual entry for generated content msgid "Fischer" msgstr "Échecs (Fischer)" #: manual entry for generated content msgid "Board" msgstr "Partie de table" #: manual entry for generated content msgid "Go Standard Byoyomi" msgstr "Go Byoyomi Standard" #: ../gameclock/clock.py:91 #, python-format msgid "%d move" msgid_plural "%d moves" msgstr[0] "%d coup" msgstr[1] "%d coups" #: ../gameclock/clock.py:235 #, python-format msgid " %d byoyomi" msgid_plural " %d byoyomis" msgstr[0] " %d byoyomi" msgstr[1] " %d byoyomis" #: ../gameclock/clock.py:237 msgid " (lost)" msgstr " (perdu)" #~ msgid "" #~ "\n" #~ "Error: %s" #~ msgstr "" #~ "\n" #~ "Erreur: %s" #~ msgid "unhandled option" #~ msgstr "option inconnue" #~ msgid "Turn %d" #~ msgstr "Tour %d" gameclock-5.0/po/messages.pot0000644000000000000000000001441112141542147013144 0ustar # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2013-01-22 23:50-0500\n" "PO-Revision-Date: 2013-04-07 18:34-0400\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: ../gameclock/game.py:161 msgid "A regular chess game of 60 minutes per player, with no increment." msgstr "" #: ../gameclock/game.py:174 msgid "A quick 15 minutes per player chess game, no increment." msgstr "" #: ../gameclock/game.py:179 msgid "A very fast chess game, 5 minutes per player no increment." msgstr "" #: ../gameclock/game.py:184 msgid "" "A delay timing style used for chess and designed by Bobby Fischer. Everytime " "the player makes a move, a delay is added to the clock. Defaults: 2 minutes " "per player, 10 second increment, results in a reasonably short chess game." msgstr "" #: ../gameclock/game.py:189 msgid "" "A regular board game. A clock goes down until time runs out. The counter is " "reset at every move. Default is to give 2 minutes per move." msgstr "" #: ../gameclock/game.py:193 msgid "" "Behave like an hourglass: the clock goes down on one side and up on the " "other, 60 seconds by default." msgstr "" #: ../gameclock/game.py:199 msgid "" "A standard or \"japanese\" Go counter. A clock counts down until its time " "runs out and then enters overtime. Once in overtime the player has a " "specific number of overtime periods (or \"Byo-yomi\") to play. If the player " "moves within the chosen delay, the clock's time reverts to the top of the " "period and no periods are lost. If the player does not move within the " "period, the next period begins and the period count is reduced by 1. A " "player loses when his/her clock runs out of time and no more periods remain." msgstr "" #: ../gameclock/gtkui.py:65 msgid "Default" msgstr "" #: ../gameclock/gtkui.py:65 msgid "Use the default theme" msgstr "" #: ../gameclock/gtkui.py:66 msgid "Green" msgstr "" #: ../gameclock/gtkui.py:66 msgid "" "Selected player is green, dead player is red, normal background black (4.0 " "default)" msgstr "" #: ../gameclock/gtkui.py:80 msgid "Blue" msgstr "" #: ../gameclock/gtkui.py:80 msgid "Selected player is blue, dead player is red, normal background black" msgstr "" #: ../gameclock/gtkui.py:107 msgid "_Game" msgstr "" #: ../gameclock/gtkui.py:108 msgid "_New" msgstr "" #: ../gameclock/gtkui.py:109 msgid "Start" msgstr "" #: ../gameclock/gtkui.py:110 msgid "Start/pause game" msgstr "" #: ../gameclock/gtkui.py:111 msgid "_Full screen" msgstr "" #: ../gameclock/gtkui.py:112 msgid "Full screen mode" msgstr "" #: ../gameclock/gtkui.py:113 ../gameclock/gtkui.py:177 msgid "_Quit" msgstr "" #: ../gameclock/gtkui.py:114 msgid "Quit the Program" msgstr "" #: ../gameclock/gtkui.py:115 msgid "_Settings" msgstr "" #: ../gameclock/gtkui.py:116 msgid "_Set time..." msgstr "" #: ../gameclock/gtkui.py:117 msgid "Set the starting time of clocks" msgstr "" #: ../gameclock/gtkui.py:118 msgid "_Players..." msgstr "" #: ../gameclock/gtkui.py:119 msgid "Set the number of players and starting player" msgstr "" #: ../gameclock/gtkui.py:120 msgid "Theme" msgstr "" #: ../gameclock/gtkui.py:121 msgid "_Help" msgstr "" #: ../gameclock/gtkui.py:122 msgid "_About" msgstr "" #: ../gameclock/gtkui.py:123 msgid "More information about this software" msgstr "" #: ../gameclock/gtkui.py:124 msgid "Keyboard shortcuts" msgstr "" #: ../gameclock/gtkui.py:125 msgid "Display the available keyboard shortcuts" msgstr "" #: ../gameclock/gtkui.py:178 msgid "_Sound" msgstr "" #: ../gameclock/gtkui.py:178 msgid "Enable/disable sound" msgstr "" #: ../gameclock/gtkui.py:428 msgid "Change time settings" msgstr "" #: ../gameclock/gtkui.py:446 msgid "Time limit: " msgstr "" #: ../gameclock/gtkui.py:457 msgid "Delay: " msgstr "" #: ../gameclock/gtkui.py:469 msgid "Number of Byo-yomi: " msgstr "" #: ../gameclock/gtkui.py:496 msgid "Change players settings" msgstr "" #: ../gameclock/gtkui.py:505 msgid "Number of players: " msgstr "" #: ../gameclock/gtkui.py:517 msgid "Starting player: " msgstr "" #: ../gameclock/gtkui.py:519 msgid "Left" msgstr "" #: ../gameclock/gtkui.py:522 msgid "Right" msgstr "" #: ../gameclock/gtkui.py:594 msgid "Resume" msgstr "" #: ../gameclock/gtkui.py:595 msgid "game paused" msgstr "" #: ../gameclock/gtkui.py:598 ../gameclock/gtkui.py:611 msgid "Pause" msgstr "" #: ../gameclock/gtkui.py:599 msgid "game resumed" msgstr "" #: ../gameclock/gtkui.py:613 msgid "game running" msgstr "" #: ../gameclock/gtkui.py:636 msgid "" "Keyboard shortcuts\n" "\n" "shift and space keys end turns\n" "control-q quits\n" "control-n starts a new game\n" "control-f enables fullscreen mode\n" "control-p, F5-F8 pause the game" msgstr "" #: ../gameclock/gtkui.py:643 msgid "" "Left side of the keyboard ends left side's turn and vice-versa for right " "turn.\n" "\n" "We do not currently handle the numpad and arrow keys as we can't tell if " "they are present or not (e.g. laptops) and that would favor too much right " "right side if it is present." msgstr "" #: ../gameclock/gtkui.py:654 msgid "A simple game clock to be used for Chess or any board game." msgstr "" #: manual entry for generated content msgid "Regular Chess" msgstr "" #: manual entry for generated content msgid "Quick Chess" msgstr "" #: manual entry for generated content msgid "Lightning Chess" msgstr "" #: manual entry for generated content msgid "Hourglass" msgstr "" #: manual entry for generated content msgid "Fischer" msgstr "" #: manual entry for generated content msgid "Board" msgstr "" #: manual entry for generated content msgid "Go Standard Byoyomi" msgstr "" #: ../gameclock/clock.py:91 #, python-format msgid "%d move" msgid_plural "%d moves" msgstr[0] "" msgstr[1] "" #: ../gameclock/clock.py:235 #, python-format msgid " %d byoyomi" msgid_plural " %d byoyomis" msgstr[0] "" msgstr[1] "" #: ../gameclock/clock.py:237 msgid " (lost)" msgstr "" gameclock-5.0/setup.py0000755000000000000000000000533712141542147011717 0ustar #!/usr/bin/env python # -*- coding: utf-8 -*- """ setup.py for Gameclock """ import os import sys from setuptools import setup from gameclock import __version__ from distutils import cmd from distutils.command.build import build as _build import msgfmt # stolen from deluge-1.3.3 (GPL3) class build_trans(cmd.Command): description = 'Compile .po files into .mo files' user_options = [ ('build-lib', None, "lib build folder") ] def initialize_options(self): self.build_lib = None def finalize_options(self): self.set_undefined_options('build', ('build_lib', 'build_lib')) def run(self): po_dir = os.path.join(os.path.dirname(__file__), 'po/') print('Compiling po files from %s...' % po_dir), for path, names, filenames in os.walk(po_dir): for f in filenames: uptoDate = False if f.endswith('.po'): lang = f[:len(f) - 3] src = os.path.join(path, f) dest_path = os.path.join(self.build_lib, 'gameclock', 'po', lang, \ 'LC_MESSAGES') dest = os.path.join(dest_path, 'gameclock.mo') if not os.path.exists(dest_path): os.makedirs(dest_path) if not os.path.exists(dest): sys.stdout.write('%s, ' % lang) sys.stdout.flush() msgfmt.make(src, dest) else: src_mtime = os.stat(src)[8] dest_mtime = os.stat(dest)[8] if src_mtime > dest_mtime: sys.stdout.write('%s, ' % lang) sys.stdout.flush() msgfmt.make(src, dest) else: uptoDate = True if uptoDate: sys.stdout.write(' po files already upto date. ') sys.stdout.write('\b\b \nFinished compiling translation files. \n') class build(_build): sub_commands = [('build_trans', None)] + _build.sub_commands def run(self): # Run all sub-commands (at least those that need to be run) _build.run(self) cmdclass = { 'build': build, 'build_trans': build_trans, } setup( name="gameclock", version=__version__, description="The Gameclock", author="Antoine Beaupré", author_email="anarcat@orangeseeds.org", license='GPLv3', url="https://redmine.koumbit.net/projects/gameclock", cmdclass=cmdclass, packages=["gameclock"], scripts=["scripts/gameclock"], data_files=[('share/pixmaps', ['gameclock.svg', 'gameclock.xpm'])], ) gameclock-5.0/setup.cfg0000644000000000000000000000004512141542147012012 0ustar [install] install-scripts=/usr/games gameclock-5.0/COPYRIGHT0000644000000000000000000010437412141542147011476 0ustar GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . gameclock-5.0/gameclock.xpm0000644000000000000000000000424012141542147012645 0ustar /* XPM */ static char *gameclock[] = { /* columns rows colors chars-per-pixel */ "32 32 45 1 ", " c #0CCF0AED0AED", ". c #11D40ECE0ECE", "X c #144212821282", "o c #1E831D2C1D2C", "O c #200A1D661D66", "+ c #25BF24C724C7", "@ c #299226CC26CC", "# c #2C732ADB2AEC", "$ c #31372E952E95", "% c #34B434323433", "& c #3AA236993699", "* c #3B8C3B413B41", "= c #40223EE83F67", "- c #441E43D143E1", "; c #484A472A4792", ": c #48DA474C4858", "> c #4CC64C594C82", ", c #50A24F544F54", "< c #549953E75435", "1 c #5BDC5BAC5BC2", "2 c #60DA5EFD6012", "3 c #634D62FF632C", "4 c #6C006BBA6BD6", "5 c #740073D673E5", "6 c #783776EC77CB", "7 c #7C537C077C2C", "8 c #80A47F087FD2", "9 c #8392833D8359", "0 c #887787DF87E8", "q c #8C258BBD8BDD", "w c #9383931C9335", "e c #9C4B9C089C1A", "r c #A3D3A396A3AE", "t c #ACE9ACC4ACC4", "y c #B3CFB3CEB3CF", "u c #BD18BCDEBCF8", "i c #C51AC4E8C4EF", "p c #C86FC7E8C7E8", "a c #CD28CCCDCCD0", "s c #D3D0D3C3D3C5", "d c #DCB8DCB4DCB4", "f c #E502E4FEE4FE", "g c #EC11EC0FEC10", "h c #F46BF468F468", "j c #FF84FF84FF84", /* pixels */ "jjjjjjjjjjjjjjjjjjjjjjjhjjjjjjjj", "jjjjjjjjjjjgirq0qeufjja7hjjjjjjj", "jjjjjjjjhi6--<241>-*3r-6hjjjjjjj", "jjjjjjjs4&4ydfgffdpr3. wjjjjjjjj", "jjjjjjr*3shhfuetrddd0o#%9gjjjjjj", "jjjjjw%ejhgff0<41adt%+we%1fjjjjj", "jjjje%yjhggfgw263sp,+:pst%1djjjj", "jjjp%tjhgggffauiud6@+qssar#4fjjj", "jjh<6jhggggffffdfr$#;issaa9+ehjj", "jjr*djhgggfgffdfa>$@qssssai;;pjj", "jhassssaaaa1-th", "j65jjhghgggfffw#&#eautw515ua0#ed", "h1qpruggggffgs:#@@2<=@$=608qq+ws", "h1w7,7gggggfp4X. +##%4eri9-<9@qp", "h1wr6eggggs8*X O,69%14#5w34q+qp", "j469<9ggggw=%X =tady*%-w%-#<6+wp", "j91gdfghggdawX7gjy<>1hjjjf,i", "jjjjd<3dggfffa15>%8ii1r1ejjjjj3r", "jjjjjf1-yfgffp>3;63:-e8:fjjjjj4q", "jjjjjjg9*4ydfdwrqpsy83+yjjjjjj58", "jjjjjjjjp4&>7rtytr8<% >dffffff17", "jjjjjjjjjjsr4-&$$$=30:=1111111*e", "jjjjjjjjjjjjjhgdsdfghfdddsdssddg", "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj" }; gameclock-5.0/gameclock/0000755000000000000000000000000012141542305012113 5ustar gameclock-5.0/gameclock/i18n.py0000644000000000000000000000125212141542147013250 0ustar # -*- coding: utf-8 -*- # Simple wrappers for localisation import locale import gettext import pkg_resources # Initialize gettext, taken from deluge 1.3.3 (GPL3) try: locale.setlocale(locale.LC_ALL, '') if hasattr(locale, "bindtextdomain"): locale.bindtextdomain("gameclock", pkg_resources.resource_filename("gameclock", "po")) if hasattr(locale, "textdomain"): locale.textdomain("gameclock") gettext.install("gameclock", pkg_resources.resource_filename("gameclock", "po"), unicode=True, names='ngettext') except Exception, e: print "Unable to initialize translations: %s" % e import __builtin__ __builtin__.__dict__["_"] = lambda x: x gameclock-5.0/gameclock/clock.py0000644000000000000000000002173512141542147013574 0ustar # -*- coding: utf-8 -*- # # The Gameclock clock engines # # This is where the clock engines reside. # # (C) Anarcat 2011 import time import math import gameclock.i18n import gettext class Clock: """The main clock engine Each clock is an instance of this class. This could be considered like a merge between a controler and a model in an MVC model. However, this software isn't based on the MVC model in any remote way. This should be pretty precise and doesn't lag from empirical observations (watching the clocks compared to the Gnome clock applet) """ def __init__(self, **settings): """Setup the clock engine The clock is stopped by default and the display is refreshed """ # the time, in miliseconds self.time = settings['start_time'] # 0 if stopped self.last_start = 0 # usually, clocks go backwards, but some games might require it to go other ways self.factor = -1 # a clock is part of a chained list self.next = settings['next_clock'] if 'next_clock' in settings else None self.moves = 0 def start(self): """Start the timer This marks the new timestamp and sets the background color """ self.last_start = time.time() def stop(self): """Stop the timer This computes the new cumulatative time based on the start timestamp. It resets the timestamp to zero to mark the timer as stopped. This also resets the event box's color and triggers an update of the label. XXX: Note that this function takes *some* time to process. This time is lost and not given to any participant. Maybe that should be fixed for the clock to be really precised, by compensation for the duration of the function. Another solution would be to create a thread for the Game engine """ if self.last_start: self.time = self.get_time() self.last_start = 0 self.moves += 1 def pause(self): """pause/unpause the timer this will start the timeer if stopped and stop it if started """ if self.last_start: self.stop() else: self.start() def running(self): return self.last_start def get_time(self): """return the current time of the clock in ms""" if self.last_start: diff = time.time() - self.last_start else: diff = 0 return self.time + (self.factor * diff*1000) def moves_fmt(self): return ngettext("%d move", "%d moves", self.moves) % self.moves def is_dead(self): return self.get_time() <= 0 def update(self): """Refresh the display of the clock's widget""" return self.format() suggested_formats = [ '%02i:%02d', '%02i:%04.1f', '%i:%02i:%02d', '%i:%02i:%04.1f' ] def format(self, fmt = '%02i:%02d'): """Format this clock's internal time as a human-readable string. Can contain 2 or three formatters. If there are only two, hours are displayed in the minutes.""" miliseconds = abs(self.get_time()) if self.get_time() < 0: fmt = '-' + fmt if fmt.count('%') == 3: hours, milliseconds = divmod(miliseconds, 3600000) minutes, milliseconds = divmod(miliseconds, 60000) seconds = float(milliseconds) / 1000 if fmt.count('%') == 3: return fmt % (hours, minutes, seconds) else: return fmt % (minutes, seconds) def __str__(self): """make a better string representation of the objects we basically dump all variables and some functions """ return " clock engine %s time: %d last: %d diff: %f dead: %d text: %s\n next %s" % ( object.__str__(self), self.time, self.last_start, time.time() - self.last_start, self.is_dead(), self.format(), self.next) class ChessClock(Clock): """A typical Chess clock This clock will stop at the end of your turn, and should represent fairly faithfully tabletop chess clocks. A typical setting is 5 minutes each, which is considered to be a "blitz". 2 minutes is often called "lightning chess". """ # this is just an alias to the base Game class which implements everything pass class FischerChessClock(Clock): """A chess clock, as modified by Garry Fischer This is a regular chess clock with one little twist: every time a player finishes its turn, he gets extra time. This allows for timed game that are still fairly interactive, as the player is forced to move within a certain timeframe. A typical setting is 2 minutes + 10 seconds delay, which leads to games of around 10 to 20 minutes.""" delay = 10 def __init__(self, **settings): self.delay = settings['delay'] del settings['delay'] Clock.__init__(self, **settings) def stop(self): """end the turn, fischer style this increments the current clock before switching turns as normal """ self.time += self.delay Clock.stop(self) class BoardClock(Clock): """A simple clock for general board games. A player gets a specific amount of time to play his turn, but the leftover time isn't carried over to the next turn.""" # we need to remember the original time default_time = None def __init__(self, **settings): Clock.__init__(self, **settings) self.default_time = settings['start_time'] def stop(self): """override the end_turn function to reset the timers at the end of turns""" Clock.stop(self) self.time = self.default_time class GoStandardByoyomiClock(Clock): """a regular go clock. it has a certain time where it behaves like a regular chess clock, but then behaves like a board clock after the given time. at that point there is a certain number of "byoyomis" of a chosen "delay" that moves need to be played within.""" def __init__(self, **settings): self.delay = settings['delay'] # number of extra time periods, if 0, no time periods are left # and the user dies # we increment this because we burn the first byoyomi when entering it self._byoyomi = settings['byoyomi'] + 1 self._start_byoyomi = settings['byoyomi'] Clock.__init__(self, **settings) def get_byoyomi(self): """look if we are dead and consume a byoyomi if so should be called as often as necessary to display things consistently. returns the number of byoyomis used (-1 being regular game play, 0 being none, 1 being one, etc). """ # can't use our own is_dead, it's subverted by the byoyomi logic if Clock.is_dead(self) and not self.is_dead(): self._byoyomi -= 1 if self.is_dead(): self.time = 0 else: # reset the timer only if we didn't blow byoyomi self.time = self.delay if self.running(): self.start() # reset return self._byoyomi def set_byoyomi(self, byoyomi): self._start_byoyomi = byoyomi self._byoyomi = byoyomi + 1 def is_dead(self): return self._byoyomi <= 0 def is_byoyomi(self): return self.get_byoyomi() <= self._start_byoyomi def format(self, fmt = '%02i:%02d'): self.get_byoyomi() return Clock.format(self, fmt) def stop(self): Clock.stop(self) if self.is_byoyomi() and not self.is_dead(): self.time = self.delay def moves_fmt(self): ret = Clock.moves_fmt(self) if self.is_byoyomi(): ret += ngettext(' %d byoyomi', ' %d byoyomis', self._byoyomi) % self._byoyomi if self.is_dead(): ret += _(' (lost)') return ret class HourglassClock(Clock): """Hourglass emulation. This clock is similar to having an hourglass on a table that is flipped at the end of each turn. We do allow each player to start with a certain amonut of time (or "sand", if you will) on his side, in other words, this is as if the hourglass was half-empty/half-full when the game starts. Note that this doesn't make much sense with more than two players...""" def __init__(self, **settings): """this overrides the base constructor to make only one clock have an initial time basically, this is to represent that when you start it, the hour glass is empty on one side """ Clock.__init__(self, **settings) def start(self): """reimplement the start() function altogether make sure all the clocks are started and they are in the right direction """ Clock.stop(self) self.factor = -1 Clock.start(self) def stop(self): """reimplement the end_turn function altogether we go to the next clock, stop it, reverse its direction and start it again """ Clock.stop(self) self.factor = 1 Clock.start(self) gameclock-5.0/gameclock/sound.py0000644000000000000000000000205612141542147013624 0ustar # -*- coding: utf-8 -*- import os class AudioDriverException(Exception): pass # basic sound support, depends on python-pygame and sound-theme-freedesktop class Player: def __init__(self, sounds, callback = None): """initializes the audio engine with a list of sounds. the callback is called to see if we can play sounds, allows for disabling the engine live.""" self.sounds = {} self.callback = callback import pygame from pygame import mixer try: mixer.init() except pygame.error as e: raise AudioDriverException("%s" % e) else: for sound, filename in sounds.iteritems(): if os.path.exists(filename): self.sounds[sound] = mixer.Sound(filename) else: raise IOError('sound file "%s" not found: %s' % (sound, filename)) def play(self, sound): if self.callback(): self.sounds[sound].play() class DumbPlayer: def play(self, sound): pass gameclock-5.0/gameclock/__init__.py0000644000000000000000000000212412141542147014227 0ustar # -*- coding: utf-8 -*- """The gameclock""" __version_info__ = ('5','0') __version__ = '.'.join(__version_info__) __copyright__ = """Copyright (C) 2008-2013 Antoine Beaupré This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. For details see the COPYRIGHT file distributed along this program.""" __license__ = """ This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or any later version. This package 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 package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ gameclock-5.0/gameclock/gtkui.py0000644000000000000000000007146612141542147013632 0ustar # -*- coding: utf-8 -*- # The GTK graphical user interface import pygtk pygtk.require('2.0') import pango import gtk from glib import GError import gobject import time import os import re import pkg_resources from gameclock.game import * import gameclock.clock import gameclock.sound from gameclock import __version__, __license__, __copyright__ import gameclock.i18n class GameclockUI: """this class handles most of the UI and turned-based logic It is not designed for UI-abstraction yet, but could be, if the turn-based logic is ripped in a seperate class """ first_clock = None cur_clock = None clock_widget_cnt = 0 # the game this UI references game = None sec_loop_timeout = 500 # in ms, clocks are incremented every time this timeout ends ms_loop_timeout = 100 # in ms, the same as above, in milisecond mode menubar = None # used in the fullscreen toggle ui_desc = """ """ themes = [ ('default', _('Default'), _('Use the default theme'), None), ('green', _('Green'), _('Selected player is green, dead player is red, normal background black (4.0 default)'), """ style "clockui" { bg[NORMAL] = "black" fg[NORMAL] = "white" bg[SELECTED] = "red" fg[SELECTED] = "black" bg[ACTIVE] = "green" fg[ACTIVE] = "black" } widget "*.clockui.GtkEventBox" style "clockui" widget "*.clockui.*Label" style "clockui" """ ), ('blue', _('Blue'), _('Selected player is blue, dead player is red, normal background black'), """ style "clockui" { bg[NORMAL] = "black" fg[NORMAL] = "white" bg[SELECTED] = "red" fg[SELECTED] = "black" bg[ACTIVE] = "blue" fg[ACTIVE] = "white" } widget "*.clockui.GtkEventBox" style "clockui" widget "*.clockui.*Label" style "clockui" """), ] def __init__(self, verbose = 0, fullscreen = False, **settings): """the UI constructor we take settings in to allow unit tests to override the default game """ self.verbose = verbose self.fullscreen = fullscreen self.debug('running with verbosity: %d' % self.verbose) # the default game self.game = LightningChessGame(**settings) self.ui_actions = [('game', None, _('_Game')), ('new', gtk.STOCK_NEW, _('_New')), ('pause', gtk.STOCK_MEDIA_PLAY, _('Start'), 'p', _('Start/pause game'), self.handle_pause), ('restart', gtk.STOCK_REFRESH, _('Restart'), 'r', _('Restart game'), self.handle_restart), ('fullscreen', gtk.STOCK_FULLSCREEN, _('_Full screen'), 'f', _('Full screen mode'), self.handle_fullscreen), ('quit', gtk.STOCK_QUIT, _('_Quit'), 'q', _('Quit the Program'), gtk.main_quit), ('settings', None, _('_Settings')), ('time', None, _('_Set time...'), None, _('Set the starting time of clocks'), self.handle_time), ('players', None, _('_Players...'), None, _('Set the number of players and starting player'), self.handle_players), ('theme', None, _('Theme')), ('help', None, _('_Help')), ('about', None, _('_About'), None, _('More information about this software'), self.about_dialog), ('keyboard', None, _('Keyboard shortcuts'), None, _('Display the available keyboard shortcuts'), self.shortcuts_dialog), ] # setup icon try: icon = pkg_resources.resource_filename(pkg_resources.Requirement.parse("gameclock"), 'gameclock.svg') gtk.window_set_default_icon_from_file(icon) except GError as e: # misconfigured, ignore self.debug("could not find icon: %s" % e) except pkg_resources.DistributionNotFound: # not installed system-wide, ignore pass finally: # last resort, try the FHS and source directories for icon in ["/usr/share/pixmaps/gameclock.svg", os.path.dirname(__file__) + "/../gameclock.svg"]: if os.path.exists(icon): gtk.window_set_default_icon_from_file(icon) # create a new window self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) # handle window close events self.window.connect("delete_event", lambda a, b: False) self.window.connect("destroy", gtk.main_quit) self.window.connect('key_press_event', self.handle_key_press) event_box = gtk.EventBox() self.window.add(event_box) event_box.show() # catch clicks as end turn event_box.set_extension_events(gtk.gdk.EXTENSION_EVENTS_ALL) event_box.set_events(gtk.gdk.BUTTON_PRESS_MASK) event_box.connect("button_press_event", self.handle_move) # main window consists of a vbox containing two hbox self.vlayout = vlayout = gtk.VBox(False, 0) event_box.add(vlayout) # Create a UIManager instance uimanager = gtk.UIManager() # Add the accelerator group to the toplevel window accelgroup = uimanager.get_accel_group() self.window.add_accel_group(accelgroup) # Create an ActionGroup actiongroup = gtk.ActionGroup('UIManagerExample') self.actiongroup = actiongroup # Create actions actiongroup.add_actions(self.ui_actions) actiongroup.get_action('quit').set_property('short-label', _('_Quit')) self.soundaction = gtk.ToggleAction('sound', _('_Sound'), _('Enable/disable sound'), None) actiongroup.add_action(self.soundaction) # Add a UI description uimanager.add_ui_from_string(self.ui_desc) # add the games from the game module to the UI i = 0 radiogroup = None for name, cls in enumerate_games(): name = _(cls.nice_name()) self.debug('adding game %s %s' % (name, cls)) # add the given entry as an action to the menu uimanager.add_ui(uimanager.new_merge_id(), '/menubar/game/new', name, cls.__name__, gtk.UI_MANAGER_AUTO, True) action = gtk.RadioAction(cls.__name__, name, _(cls.__doc__), None, i) if cls == self.game.__class__: action.set_current_value(i) i += 1 action.connect('activate', self.handle_type, cls) if radiogroup is None: radiogroup = action else: action.set_group(radiogroup) actiongroup.add_action(action) # setup theme menu i = 0 radiogroup = None for name, label, tooltip, rcstring in self.themes: uimanager.add_ui(uimanager.new_merge_id(), '/menubar/settings/theme', name, name, gtk.UI_MANAGER_AUTO, True) action = gtk.RadioAction(name, label, tooltip, None, i) i += 1 action.connect('activate', self.handle_theme, name) if radiogroup is None: radiogroup = action else: action.set_group(radiogroup) actiongroup.add_action(action) action.set_current_value(0) # Add the actiongroup to the uimanager uimanager.insert_action_group(actiongroup, 0) # this is to make the tooltip appear, rather silly... # see https://www.daa.com.au/pipermail/pygtk/2010-September/018978.html uimanager.connect('connect-proxy', self.uimanager_connect_proxy) # Create a MenuBar self.menubar = uimanager.get_widget('/menubar') uimanager.get_widget('/menubar/help').set_right_justified(True) self.uimanager = uimanager self.vlayout.pack_start(self.menubar, False, True, 0) self.menubar.show() # the clocks self.clock_table = gtk.Table(1, 2, True) self.clock_table.show() vlayout.pack_start(self.clock_table, True, True, 0) vlayout.show() # we need to flip this because handle_fullscreen *toggles* the fullscreen self.fullscreen = not fullscreen self.handle_fullscreen() # basic sound support, depends on python-pygame and sound-theme-freedesktop try: self.sounds = gameclock.sound.Player({ 'dead': '/usr/share/sounds/freedesktop/stereo/complete.oga', 'move': '/usr/share/sounds/freedesktop/stereo/dialog-information.oga' }, self.soundaction.get_active) except (gameclock.sound.AudioDriverException, IOError, ImportError) as e: self.debug('sound disabled: %s' % e) self.sounds = gameclock.sound.DumbPlayer() def uimanager_connect_proxy(self, uimgr, action, widget): tooltip = action.get_property('tooltip') if isinstance(widget, gtk.MenuItem) and tooltip: widget.set_tooltip_text(tooltip) def main(self): """create the main user interface with GTK""" if self.game: self.setup_clocks() self.window.show() gtk.main() def setup_clocks(self): for c in self.clock_table.get_children(): self.clock_table.remove(c) del c q = self.game.first_clock self.first_clock = None x = 1 y = -1 prev = None for i in range(self.game.players): clock = ClockUI(self, q) q = q.next if prev is not None: prev.next = clock prev = clock if self.first_clock is None: self.cur_clock = self.first_clock = clock if x == 1: x = 0 y = y + 1 # new row else: x = 1 self.clock_table.attach(clock, x, x+1, y, y+1) cols = 2 rows = (self.game.players - 1)/ cols + 1 self.clock_table.resize(rows, cols) self.refresh() self.debug("now at %d clocks, table size is %dX%d" % (self.game.players, rows, cols)) def next(self): """change the current clock simply switch the clock in the game engine and rehilight """ self.game.next() self.cur_clock = self.cur_clock.next if not self.cur_clock: self.cur_clock = self.first_clock def status(self, text): self.debug(text) #self.turns.set_label(text) def debug(self, text): if self.verbose: t = "" state = "" if self.verbose > 1: t = "[%f] " % time.time() if self.verbose > 2: state = "\n game engine state: %s" % self.game print t + text + state def refresh(self): p = self.first_clock while p: p.refresh() p = p.next self.hilight() def refresh_current(self): """refresh the active clock this handler is ran periodically through a timeout signal to make sure that the current clock is updated """ keep_handler = self.game.running() # in hourglass, we just update both clocks all the time if isinstance(self.game.first_clock, gameclock.clock.HourglassClock): # check if any clock is below 60s p = self.first_clock while p: p = p.next if keep_handler and p: # if we are in a sprint, it will switch the timeout handler, ditch ours keep_handler = not p.check_sprint() # refresh both clocks self.refresh() return keep_handler if keep_handler: # if we are in a sprint, it will switch the timeout handler, ditch ours keep_handler = not self.cur_clock.check_sprint() self.cur_clock.refresh() return keep_handler def hilight(self): """hilight the proper clocks with proper colors this is 'transparent' for the inactive clock and colored for the active clock. the color depends on wether the clock is 'dead' or not """ p = self.first_clock while p: p.hilight(p == self.cur_clock) p = p.next def handle_key_press(self, widgets, event): keyname = gtk.gdk.keyval_name(event.keyval) # see this FAQ for more information about keycodes: http://faq.pygtk.org/index.py?file=faq05.005.htp&req=edit # notice how we do not handle the arrow keys/home/end/scrlock and num pad if event.state & gtk.gdk.CONTROL_MASK or event.state & gtk.gdk.MOD1_MASK: # gtk.gdk.SHIFT_MASK is okay self.debug("key pressed with mod/control ignored"); elif ( keyname == 'Shift_L' or keyname == 'Caps_Lock' or keyname == 'Alt_L' or keyname == 'Super_L' or event.hardware_keycode == 49 # ~ or event.hardware_keycode in range(52, 56) # z-b or event.hardware_keycode in range(38, 42) # a-g or event.hardware_keycode in range(23, 28) # tab-q-t or event.hardware_keycode in range(10, 15) # 1-6 or event.hardware_keycode in range(67, 70)): # F1-F4 if self.game.cur_clock == self.game.first_clock: self.handle_move() elif ( keyname == 'Shift_R' or keyname == 'Return' or keyname == 'Alt_R' or keyname == 'Super_R' or event.hardware_keycode in range(57, 61) # n-/ or event.hardware_keycode in range(43, 48) # h-' or event.hardware_keycode in range(29, 35) # y-] or event.hardware_keycode in range(16, 22) # 7-backspace or event.hardware_keycode in range(75, 76) # F9-F10 or event.hardware_keycode in range(95, 96) # F10-F11 (wtf?) or keyname == 'Menu' or event.hardware_keycode == 51): # \ if self.game.cur_clock != self.game.first_clock: self.handle_move() elif event.hardware_keycode in range(71, 74): # F5-F8 is pause self.handle_pause() elif keyname == 'space': self.handle_move() elif keyname == 'Escape': if self.fullscreen: self.handle_fullscreen() self.debug("key %s (%d/%d) was pressed" % (keyname, event.keyval, event.hardware_keycode)) def handle_fullscreen(self, action = None): if self.fullscreen: self.window.unfullscreen() self.menubar.show() else: self.window.fullscreen() self.menubar.hide() self.fullscreen = not self.fullscreen def handle_type(self, action, game): self.debug("setting up for game %s" % game) if action.get_active(): self.game = game() # XXX: this looses all time settings self.setup_clocks() self.uimanager.get_action('/menubar/settings').set_sensitive(True) def handle_restart(self, action = None): """restart the game with the backed up settings""" self.game.pause() self.game = self.game_restart.copy() self.setup_clocks() self.uimanager.get_action('/menubar/settings').set_sensitive(True) def handle_time(self, action): """handle the time selection menu this generates a popup window to allow the user to change the times, including fischer-like delays, and that will then call handle_time_response() to process the results. """ window = gtk.Dialog(_("Change time settings"), self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) window.set_default_response(gtk.RESPONSE_ACCEPT) window.connect("delete_event", lambda a, b: False) window.connect("destroy", lambda a: window.destroy) window.connect('response', self.handle_time_response) # the widgets to change the starting time minutes, milliseconds = divmod(self.game.time, 60000) seconds = float(milliseconds) / 1000 self.minutes_val = gtk.Adjustment(minutes, 0, 1440, 1, 10, 0) self.seconds_val = gtk.Adjustment(seconds, 0, 59, 1, 10, 0) minutes_val_btn = gtk.SpinButton(self.minutes_val, 1.0, 0) seconds_val_btn = gtk.SpinButton(self.seconds_val, 1.0, 0) minutes_val_btn.show() seconds_val_btn.show() clock_controls = gtk.HBox(False, 0) label = gtk.Label(_("Time limit: ")) label.show() clock_controls.pack_start(label, False, False) clock_controls.pack_start(minutes_val_btn, False, False) clock_controls.pack_start(seconds_val_btn, False, False) clock_controls.show() clock_controls_box = gtk.VBox(False, 0) window.vbox.pack_start(clock_controls, False, False, 10) if isinstance(self.game, gameclock.game.AbstractDelayGame): controls = gtk.HBox(False, 0) label = gtk.Label(_('Delay: ')) label.show() controls.pack_start(label, True, False, 0) self.delay_val = gtk.Adjustment(self.game.delay/1000, 1, 10000, 1, 10, 0) self.delay_val_btn = gtk.SpinButton(self.delay_val, 0.0, 0) self.delay_val_btn.show() controls.pack_start(self.delay_val_btn, False, False, 0) controls.show() window.vbox.pack_start(controls, False, False, 0) if isinstance(self.game, gameclock.game.AbstractGoGame): controls = gtk.HBox(False, 0) label = gtk.Label(_('Number of Byo-yomi: ')) label.show() controls.pack_start(label, True, False, 0) self.byoyomi_val = gtk.Adjustment(self.game.byoyomi, 1, 100, 1, 10, 0) self.byoyomi_val_btn = gtk.SpinButton(self.byoyomi_val, 0.0, 0) self.byoyomi_val_btn.show() controls.pack_start(self.byoyomi_val_btn, False, False, 0) controls.show() window.vbox.pack_start(controls, False, False, 0) window.show() def handle_time_response(self, dialog, response_id): """handle the time selected in the handle_time() dialog""" dialog.destroy() if response_id == gtk.RESPONSE_ACCEPT: minutes = self.minutes_val.get_value() seconds = self.seconds_val.get_value() time = ( ( minutes * 60 ) + seconds ) * 1000 self.game.set_time(time) if isinstance(self.game, gameclock.game.AbstractDelayGame): self.game.set_delay(int(self.delay_val.get_value() * 1000)) if isinstance(self.game, gameclock.game.AbstractGoGame): self.game.set_byoyomi(int(self.byoyomi_val.get_value())) self.refresh() def handle_players(self, action): window = gtk.Dialog(_("Change players settings"), self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) window.set_default_response(gtk.RESPONSE_ACCEPT) window.connect("delete_event", lambda a, b: False) window.connect("destroy", lambda a: window.destroy) window.connect('response', self.handle_players_response) hbox = gtk.HBox(False, 0) label = gtk.Label(_('Number of players: ')) label.show() self.players_val = gtk.Adjustment(self.game.players, 1, 10000, 1, 10, 0) players_val_btn = gtk.SpinButton(self.players_val, 0.0, 0) players_val_btn.show() hbox.pack_start(label, False, False, 10) hbox.pack_start(players_val_btn, False, False, 0) hbox.show() window.vbox.pack_start(hbox, False, False, 0) # the main toggle button, used by various signal handlers hbox = gtk.HBox(False, 0) label = gtk.Label(_('Starting player: ')) label.show() self.left_starts = gtk.RadioButton(None, _('Left')) self.left_starts.set_active(True) self.left_starts.show() button = gtk.RadioButton(self.left_starts, _('Right')) button.show() hbox.pack_start(label, False, False, 10) hbox.pack_start(self.left_starts, False, False, 10) hbox.pack_start(button, False, False, 10) hbox.show() window.vbox.pack_start(hbox, False, False, 0) window.show() def handle_players_response(self, dialog, response_id): """handle the time selected in the handle_time() dialog""" dialog.destroy() if response_id == gtk.RESPONSE_ACCEPT: self.game.resize(int(self.players_val.get_value())) self.setup_clocks() if not self.left_starts.get_active(): self.next() self.hilight() def handle_theme(self, action, theme): for name, label, tooltip, rcstring in self.themes: if name == theme: if rcstring is None: # special: reset all styles if hasattr(self, 'default_style'): p = self.first_clock while p: p.evbox.set_style(self.default_style) p.label.set_style(self.default_style) p = p.next else: if not hasattr(self, 'default_style'): # store the previous style so we can restore it self.default_style = self.first_clock.label.get_style() gtk.rc_parse_string(rcstring) # necessary for changes to apply gtk.rc_reset_styles(self.window.get_settings()) def handle_move(self, widget = None, event = None): """handle end turn events this passes the message to the gaming engine as quickly as possible then goes around updating the UI """ # it may be that we need to start the display if not self.game.running(): self.start_game() elif not self.first_clock.next: self.game.pause() else: self.move() # some reason it doesn't work to just update the old clock label, we need to update both self.refresh() self.hilight() self.debug("turn finished") self.sounds.play('move') def handle_pause(self, action = None): """pause handler just a stub for the game engine for now """ moveaction = self.uimanager.get_action('/menubar/game/pause') if not self.game.running() and moveaction.get_label() == 'Start': # XXX: hack. we should be able to differentiate a paused # game (started and not running) from a game about to # started (not running but not started) self.start_game() elif self.game.pause(): moveaction.set_stock_id(gtk.STOCK_MEDIA_PLAY) moveaction.set_label(_('Resume')) self.status(_('game paused')) else: moveaction.set_stock_id(gtk.STOCK_MEDIA_PAUSE) moveaction.set_label(_('Pause')) self.status(_('game resumed')) # the timeout handler is removed when we pause, resume self.timeout_source = gobject.timeout_add(self.loop_timeout, self.refresh_current) def start_game(self): # backup the game settings for a possible restart self.game_restart = self.game.copy() self.next() self.hilight() self.game.start() self.loop_timeout = self.sec_loop_timeout self.timeout_source = gobject.timeout_add(self.loop_timeout, self.refresh_current) moveaction = self.uimanager.get_action('/menubar/game/pause') moveaction.set_stock_id(gtk.STOCK_MEDIA_PAUSE) moveaction.set_label(_('Pause')) self.status(_("game running")) self.debug("refresh rate %dms" % self.loop_timeout) self.uimanager.get_action('/menubar/settings').set_sensitive(False) def move(self): self.game.move() # update the current clock pointer self.cur_clock = self.cur_clock.next if not self.cur_clock: self.cur_clock = self.first_clock def players(self): """return the number of players configured""" try: return int(self.players_val.get_value()) except: return ChessGame.players def shortcuts_dialog(self, action = None): window = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK) #window.set_default_response(gtk.RESPONSE_OK) # little help window.set_markup(_("""Keyboard shortcuts shift and space keys end turns control-q quits control-n starts a new game control-f enables fullscreen mode control-p, F5-F8 pause the game""")) window.format_secondary_text(_("""Left side of the keyboard ends left side's turn and vice-versa for right turn. We do not currently handle the numpad and arrow keys as we can't tell if they are present or not (e.g. laptops) and that would favor too much right right side if it is present.""")) window.connect("response", lambda a, b: window.destroy()) window.show() def about_dialog(self, action = None): dialog = gtk.AboutDialog() dialog.set_version(__version__) dialog.set_copyright(__copyright__) dialog.set_license(__license__) dialog.set_comments(_("A simple game clock to be used for Chess or any board game.")) dialog.set_website("https://redmine.koumbit.net/projects/gameclock") dialog.connect("response", lambda a, b: dialog.destroy()) dialog.show() class ClockUI(gtk.VBox): """this class is used to encapsulate the various controls related with a clock""" # like the game Clock, it is a linked list next = None ui = None def __init__(self, ui, clock, next = None): gtk.VBox.__init__(self) # for theming self.set_name('clockui') self.ui = ui self.next = next self.clock = clock self.dead_sound_played = False self.moves = gtk.Label() self.moves.show() # for theming to apply e = gtk.EventBox() e.add(self.moves) e.show() self.pack_start(e, False) self.label = gtk.Label() self.label.modify_font(pango.FontDescription("72")) self.label.show() self.guess_format() # event boxes to be able to color the labels self.evbox = gtk.EventBox() self.evbox.add(self.label) self.evbox.show() self.pack_start(self.evbox, True, True) self.show() def refresh(self): self.ui.debug("clock time: %f" % self.clock.get_time()) self.guess_format() self.moves.set_label(self.clock.moves_fmt()) self.label.set_label(self.clock.format(self.format)) if self.clock.is_dead(): if not self.dead_sound_played: self.ui.sounds.play('dead') self.dead_sound_played = True # need to set it at least only once on refresh # further updates can be taken care of on turn changes (hilight()) self.evbox.set_state(gtk.STATE_SELECTED) def hilight(self, active): if active: if self.clock.is_dead(): self.evbox.set_state(gtk.STATE_SELECTED) else: self.evbox.set_state(gtk.STATE_ACTIVE) else: self.evbox.set_state(gtk.STATE_NORMAL) def guess_format(self): if self.clock.get_time() < 60000: self.format = '%02i:%04.1f' else: self.format = '%02i:%02d' def check_sprint(self): # we should move this back to the GameUI if self.clock.get_time() < 60000: # below 60 seconds, kick the ds self.ui.loop_timeout = GameclockUI.ms_loop_timeout self.timeout_source = gobject.timeout_add(self.ui.loop_timeout, self.ui.refresh_current) return True return False def __del__(self): self.label.destroy() del self.label gameclock-5.0/gameclock/game.py0000644000000000000000000001763612141542147013417 0ustar # -*- coding: utf-8 -*- # # The gameclock game engines # # This is where the game engines reside. # # (C) Anarcat 2011 import math import sys import re import inspect import gameclock.clock def valid_game(cls): return inspect.isclass(cls) and not cls.__name__.startswith('Abstract') def enumerate_games(): classes = inspect.getmembers(gameclock.game, valid_game) return sorted(classes, lambda x,y: cmp(_(x[1].nice_name()), _(y[1].nice_name())), reverse=True) class AbstractGame: """the game engine this regroups clocks and handles turn switches this is an abstract class, it needs to be extended to do anything. """ # default settings for new games, overridable in the constructor # or the properties of the object after creation players = 2 # either name the class after the clock (e.g. ChessClock -> # ChessGame) or define this to associate this game with a specific # clock clock_type = None def __init__(self, **settings): # import settings as object attributes for key, val in settings.iteritems(): setattr(self, key, val) # export back those attributes as settings to the clock for key in vars(self.__class__): settings[key] = getattr(self, key) settings['start_time'] = self.time # this finds the class name of the clock we want, either set # explicitely in the game class attribute or by guessing the # name from the game class clock_type = self.__class__.clock_type or getattr(gameclock.clock, self.__class__.__name__.split('Game')[0] + 'Clock') # the clock engines p = clock_type(**settings) # the last clock (next_clock = None) # this goes backwards for i in range(self.players-1): settings['next_clock'] = p p = clock_type(**settings) # the previous clock # the clocks in the game # # in chess there are two clocks, but there can be more. this is simply a list self.first_clock = self.cur_clock = p def resize(self, players): clock_type = self.__class__.clock_type or getattr(gameclock.clock, self.__class__.__name__.split('Game')[0] + 'Clock') settings = {} for key in vars(self.__class__): settings[key] = getattr(self, key) settings['start_time'] = self.time settings['next'] = None p = self.first_clock p.next = None for i in range(players-1): if p.next is None: p.next = clock_type(**settings) p = p.next self.players = players def copy(self): """return a new game object similar to this one""" settings = {} for key in vars(self.__class__): settings[key] = getattr(self, key) return self.__class__(**settings) def start(self): """start the game this basically starts the clock """ self.cur_clock.start() def move(self): """make a move, that is, end the current turn this is the handler for the main button. it will stop the active clock and start the other and switch the active clock """ self.cur_clock.stop() # XXX: we might lose a few ms here self.next() self.cur_clock.start() def pause(self): """pause the game this just pauses the current clock returns true if the current clock is paused """ self.cur_clock.pause() return not self.cur_clock.running() def next(self): """change the current clock to the next one""" self.cur_clock = self.cur_clock.next or self.first_clock def alive(self): def _check_alive(clock): return not clock.is_dead() return self.foreach(_check_alive) def dead(self): return not self.alive() def running(self): return self.cur_clock.running() def foreach(self, function): """run the given function on all clock objects return true if all calls return true""" ret = True p = self.first_clock while p: ret = function(p) and ret p = p.next return ret def set_time(self, time): """reset the time of all clocks to the given time""" self.time = time # this is actually almost as long as the original non-foreach # function, but allows for testing the API def h(p): p.time = time self.foreach(h) def __str__(self): """make a better string representation of the objects we basically dump all variables and some functions """ return " game engine %s\n \n first %s\n current %s" % ( object.__str__(self), self.first_clock, self.cur_clock) @classmethod def nice_name(cls): """this funky bit splits the class name on capital letters and gets rid of the last entry ('Game')""" return ' '.join(filter(lambda x: x, re.split('([A-Z][a-z]*)', cls.__name__))[:-1]) class AbstractDelayGame(AbstractGame): """games that have a concept of a delay""" delay = None # abstract class def set_delay(self, delay): def s(clock): clock.delay = delay self.foreach(s) class AbstractGoGame(AbstractDelayGame): """go games have a delay, but also a byoyomi""" byoyomi = None # abstract class def set_byoyomi(self, byoyomi): def s(clock): clock.set_byoyomi(byoyomi) self.foreach(s) @classmethod def nice_name(cls): return _('Go: %s') % ' '.join(filter(lambda x: x, re.split('([A-Z][a-z]*)', cls.__name__))[1:-1]) class AbstractChessGame(AbstractGame): clock_type = gameclock.clock.ChessClock @classmethod def nice_name(cls): return _('Chess: %s') % re.split('([A-Z][a-z]*)', cls.__name__)[1] class RegularChessGame(AbstractChessGame): """A regular chess game of 60 minutes per player, with no increment.""" time = 60 * 60 * 1000 class QuickChessGame(AbstractChessGame): """A quick 15 minutes per player chess game, no increment.""" time = 15 * 60 * 1000 class LightningChessGame(AbstractChessGame): """A very fast chess game, 5 minutes per player no increment.""" time = 5 * 60 * 1000 class FischerChessGame(AbstractDelayGame): """A delay timing style used for chess and designed by Bobby Fischer. Everytime the player makes a move, a delay is added to the clock. Defaults: 2 minutes per player, 10 second increment, results in a reasonably short chess game.""" time = 2 * 60 * 1000 delay = 10 * 1000 @classmethod def nice_name(cls): return _('Chess: Fischer') class BoardGame(AbstractGame): """A regular board game. A clock goes down until time runs out. The counter is reset at every move. Default is to give 2 minutes per move.""" time = 2 * 60 * 1000 class HourglassGame(AbstractGame): """Behave like an hourglass: the clock goes down on one side and up on the other, 60 seconds by default.""" time = 60 * 1000 def start(self): """override the start routine to make sure clocks are properly started""" p = self.first_clock while p: if p == self.cur_clock: p.start() else: p.stop() p = p.next class GoStandardByoyomiGame(AbstractGoGame): """A standard or "japanese" Go counter. A clock counts down until its time runs out and then enters overtime. Once in overtime the player has a specific number of overtime periods (or "Byo-yomi") to play. If the player moves within the chosen delay, the clock's time reverts to the top of the period and no periods are lost. If the player does not move within the period, the next period begins and the period count is reduced by 1. A player loses when his/her clock runs out of time and no more periods remain.""" time = 30 * 60 * 1000 # 30 minutes byoyomi = 5 # 5 spare delays delay = 60 * 1000 # 60 seconds gameclock-5.0/tests/0000755000000000000000000000000012141542147011334 5ustar gameclock-5.0/tests/test_pygame.py0000755000000000000000000000123512141542147014233 0ustar #!/usr/bin/python import unittest import pygame import sys class MixerTest(unittest.TestCase): def setUp(self): pygame.init() #import pdb; pdb.set_trace() pygame.mixer.init() pass def test_stop(self): """this should not hang, but it does!""" with self.assertRaises(SystemExit): sys.exit() def test_something_else(self): """dumb entry just so the unit tests continue to demonstrate the above bug""" self.test_list_threads() def test_list_threads(self): """debug: list threads""" import threading for t in threading.enumerate(): print t gameclock-5.0/tests/__init__.py0000644000000000000000000000000012141542147013433 0ustar gameclock-5.0/tests/test_clock.py0000755000000000000000000001672412141542147014055 0ustar #!/usr/bin/python import unittest import sys import os import time sys.path.append((os.path.dirname(__file__) or '.') + '/..') import gameclock.clock import gameclock.game import gameclock.i18n class BaseClockTest(unittest.TestCase): """ This is the base classe for all clock tests and shouldn't be used directly. """ settings = { 'start_time': 1000 } decisecs = True # deciseconds, aka tenth of a second def setUp(self): self.clock = gameclock.clock.ChessClock(**self.settings) class SimpleClockTest(BaseClockTest): """those are tests that don't need to be ran on all clocks""" def test_format(self): """test if the output is readable""" self.assertEqual(self.clock.format(), '00:01') def test_str(self): self.assertRegexpMatches(str(self.clock), 'gameclock.clock.ChessClock') def test_moves_format(self): self.assertEqual(self.clock.moves_fmt(), _('0 moves')) class NegativeClockTest(BaseClockTest): settings = { 'start_time': -1000 } def test_format(self): self.assertEqual(self.clock.format(), '-00:01') class ChessClockTest(BaseClockTest): """this tests the basic chess clock, but also contains tests which should be ran on all clocks""" settings = { 'start_time': 10 } # 10ms, may need to be bumped up on slow computers def test_start(self): """test if the clock starts without error and is running""" self.clock.start() self.assertTrue(self.clock.running()) def test_stopped(self): """test if the clock is stopped when initialised""" self.assertFalse(self.clock.running()) def test_stop(self): """test if the clock stops without error and is stopped""" self.clock.start() self.clock.stop() self.test_stopped() def test_dead(self): """test if clock is alive when started then dies after set time""" self.clock.start() self.assertFalse(self.clock.is_dead()) time.sleep(self.settings['start_time']/1000.0) self.assertLess(self.clock.get_time(), 0) self.assertTrue(self.clock.is_dead()) self.assertRegexpMatches(self.clock.format(), '^-') def test_time(self): """test if the current time makes sense""" self.assertEqual(self.clock.get_time(), self.settings['start_time']) self.clock.start() time.sleep(0.010) # 10ms self.assertLess(self.clock.get_time(), self.settings['start_time']) def test_turn_one(self): """test if the first turn is turn 0""" self.clock.start() self.assertEqual(self.clock.moves, 0) class FischerChessClockTest(ChessClockTest): settings = { 'start_time': 10, 'delay': 1000 } def setUp(self): self.clock = gameclock.clock.FischerChessClock(**self.settings) def test_stop_fischer(self): """test that stop adds time to the clock""" self.clock.stop() self.assertEqual(self.clock.get_time(), self.settings['start_time'] + self.settings['delay']) class BoardClockTest(ChessClockTest): def setUp(self): self.clock = gameclock.clock.BoardClock(**self.settings) def test_stop_board(self): """test that stop resets the clock""" self.test_time() # safety check, but also expected to start the clock self.clock.stop() self.assertEqual(self.clock.get_time(), self.settings['start_time']) class HourglassClockTest(ChessClockTest): def setUp(self): self.clock = gameclock.clock.HourglassClock(**self.settings) def test_stop(self): """test that time goes backwards when the clock is stopped""" self.test_time() # safety check, but also expected to start the clock self.clock.stop() time.sleep(0.100) # 100ms, should be enough to go backwards self.assertGreater(self.clock.get_time(), self.settings['start_time']) class GoStandardByoyomiClockTest(ChessClockTest): settings = { 'start_time': 100, 'byoyomi': 2, 'delay': 50 } # 100ms, 2 byoyomis, 10ms def setUp(self): self.clock = gameclock.clock.GoStandardByoyomiClock(**self.settings) def test_dead(self): self.clock.start() self.assertFalse(self.clock.is_dead(), "game should start alive") self.assertFalse(self.clock.is_byoyomi(), "we shouldn't be in byoyomi yet") time.sleep(0.110) # 110 ms should cross the above 100ms self.assertEqual(self.clock.get_byoyomi(), self.settings['byoyomi'], "we should be in our first byoyomi: %d" % self.clock.get_byoyomi()) self.assertTrue(self.clock.is_byoyomi(), "we should be in a byoyomi however") self.assertFalse(self.clock.is_dead(), "go games have a byoyomi after regular time, so this should not die just yet") time.sleep(0.110) # 110 ms should cross the first byoyomi, but not die yet self.assertEqual(self.clock.get_byoyomi(), self.settings['byoyomi'] - 1, "we should be in our second byoyomi: %d" % self.clock.get_byoyomi()) self.assertTrue(self.clock.is_byoyomi(), "we should be in a byoyomi however") self.assertFalse(self.clock.is_dead(), "go games have a byoyomi after regular time, so this should not die just yet (clock time: %d, byoyomis: %d)" % (self.clock.time, self.clock.get_byoyomi())) time.sleep(0.110) # 110 ms should cross the first byoyomi, but not die yet self.assertEqual(self.clock.get_byoyomi(), 0, "we should be in our last byoyomi") self.assertTrue(self.clock.is_dead(), "we have crossed the byoyomi, we should die") self.assertEqual(self.clock.get_byoyomi(), 0, "we should STILL be in our last byoyomi") def test_stop_go(self): """test that the clock doesn't reset for nothing""" self.clock.start() self.clock.stop() self.assertLess(self.clock.get_time(), self.settings['start_time']) self.assertNotEqual(self.clock.get_time(), self.settings['delay']) self.assertFalse(self.clock.is_byoyomi(), "we shouldn't be in byoyomi yet") def test_moves_format(self): self.assertEqual(self.clock.moves_fmt(), _('0 moves')) self.clock.start() time.sleep(0.110) self.assertEqual(self.clock.moves_fmt(), _('0 moves 2 byoyomis')) time.sleep(0.110) self.assertEqual(self.clock.moves_fmt(), _('0 moves 1 byoyomi')) time.sleep(0.110) self.assertEqual(self.clock.moves_fmt(), _('0 moves 0 byoyomis (lost)')) class WeirdGoStandardByoyomiClockTest(BaseClockTest): settings = { 'start_time': 100, 'byoyomi': 1, 'delay': 50 } # 100ms, 2 byoyomis, 10ms def setUp(self): self.clock = gameclock.clock.GoStandardByoyomiClock(**self.settings) def test_die_clock(self): """test that the clock dies in the last byoyomi""" self.clock.start() self.assertFalse(self.clock.is_dead(), "game should start alive") self.assertFalse(self.clock.is_byoyomi(), "we shouldn't be in byoyomi yet") time.sleep(0.100) self.assertTrue(self.clock.is_byoyomi(), "we should be in a byoyomi now") self.assertEqual(self.clock.get_byoyomi(), 1, "we should be in our first byoyomi: %d" % self.clock.get_byoyomi()) time.sleep(0.05) self.assertLess(self.clock.get_time(), 0) self.assertEqual(self.clock.get_byoyomi(), 0, "we should be in our second byoyomi: %d" % self.clock.get_byoyomi()) self.assertTrue(self.clock.is_dead()) self.assertTrue(gameclock.clock.Clock.is_dead(self.clock), "time clock should also die when byoyomi") if __name__ == '__main__': unittest.main() gameclock-5.0/tests/test_game.py0000755000000000000000000001050012141542147013655 0ustar #!/usr/bin/python import unittest import sys import os import time import random sys.path.append((os.path.dirname(__file__) or '.') + '/..') from gameclock.game import * class AbstractGameTest(unittest.TestCase): game_type = None # to override autodetection settings = {} # to override default game settings """ This is the base classe for all clock tests and shouldn't be used directly. """ def setUp(self): # strip out the "Test" part to guess the class name of the game to test self.game_type = self.game_type or getattr(gameclock.game, self.__class__.__name__.split('Test')[0]) self.game = self.game_type(**self.settings) class RegularChessGameTest(AbstractGameTest): """basic chess games tests""" def test_defaulttime(self): """just a basic test to check that the class works""" self.assertEqual(self.game.time, self.game_type.time) def test_started(self): """test if the game starts and is running""" self.game.start() self.assertTrue(self.game.running()) def test_count_players(self): """test if count_players returns the expected number of players""" self.assertEqual(self.game.players, self.game_type.players) def test_alive(self): """a game just started should be alive""" self.assertFalse(self.game.dead()) class AbsurdGameTest(AbstractGameTest): """tests for a game with an absurdly small clock time""" def setUp(self): self.game = RegularChessGame(time = 100) def test_dies(self): self.game.start() time.sleep(0.150) # 100 ms should cross the above 100ms self.assertTrue(self.game.dead()) class SimpleGameTest(AbstractGameTest): """some simple tests for a game that shouldn't be tested on all games""" game_type = RegularChessGame def test_set_time(self): time = random.randint(0, 10000) self.game.set_time(time) c = self.game.first_clock while c: self.assertEqual(c.time, time) c = c.next def test_resize_back_to_first(self): """test that we go back to the first clock after 3 moves""" self.game.resize(3) self.assertEqual(self.game.first_clock, self.game.cur_clock, "safety check, this should not fail") self.game.move() self.game.move() self.game.move() self.assertEqual(self.game.first_clock, self.game.cur_clock, "we should have gone back to the first clock") def test_nice_name(self): self.assertEqual(RegularChessGame.nice_name(), _('Chess: Regular')) class LightningChessGameTest(RegularChessGameTest): """test the new blitz game type""" pass class FischerChessGameTest(RegularChessGameTest): def test_delay(self): self.assertEqual(self.game.delay, self.game_type.delay) class ThreePlayerGameTest(AbstractGameTest): settings = { 'players': 3, 'time': 60000 } game_type = RegularChessGame def test_back_to_first(self): """test that we go back to the first clock after 3 moves""" self.game.move() self.game.move() self.game.move() self.assertEqual(self.game.first_clock, self.game.cur_clock) class OnePlayerGameTest(SimpleGameTest): settings = { 'players': 1, 'time': 3600000 } game_type = RegularChessGame def test_back_to_first(self): """test that we go back to the first clock after 3 moves""" self.game.move() self.assertEqual(self.game.first_clock, self.game.cur_clock) self.game.move() self.assertEqual(self.game.first_clock, self.game.cur_clock) self.game.move() self.assertEqual(self.game.first_clock, self.game.cur_clock) def test_resize(self): self.game = RegularChessGame(time = 100) self.game.resize(1) i = 0 p = self.game.first_clock while p: p = p.next i += 1 self.assertEqual(i, self.game.players) self.assertEqual(i, 1) class GoStandardByoyomiGameTest(AbstractGameTest): settings = { 'players': 2, 'time': 100, 'byoyomi': 5 } def test_change_byoyomi(self): self.game.set_byoyomi(self.settings['byoyomi'] - 1) self.assertFalse(self.game.cur_clock.is_byoyomi()) self.game.set_byoyomi(self.settings['byoyomi'] + 1) self.assertFalse(self.game.cur_clock.is_byoyomi()) gameclock-5.0/tests/test_gtkui.py0000755000000000000000000002741312141542147014102 0ustar #!/usr/bin/python import unittest import sys import os import time import gtk import gobject sys.path.append((os.path.dirname(__file__) or '.') + '/..') from gameclock.gtkui import GameclockUI, ClockUI from gameclock.clock import ChessClock from gameclock.game import * class BaseGtkUITest(unittest.TestCase): """base class for UI tests, will not be called directly""" def refresh_gui(self, delay=0): """process gtk events The idea here is to do this without entering the main loop and loosing control. Taken from http://unpythonic.blogspot.ca/2007/03/unit-testing-pygtk.html and ultimately http://www.async.com.br/projects/kiwi/. """ while gtk.events_pending(): gtk.main_iteration_do(block=False) time.sleep(delay) self.ui.refresh_current() class TimeoutHandlerUITest(BaseGtkUITest): def notice_cb(self): self.noticed += 1 def setUp(self): self.noticed = 0 self.timeout_source = gobject.timeout_add(10, self.notice_cb) @unittest.expectedFailure def test_cb(self): """test if the callback gets called in unit tests by the timeout handler""" self.refresh_gui(0.100) self.assertGreater(self.noticed, 0) class DummyGameclockUI(GameclockUI): """a dumber GameclockUI that doesn't fire up all those windows""" def __init__(self, players = RegularChessGame.players, time = RegularChessGame.time): GameclockUI.__init__(self, players = players, time = time) #def debug(self, msg): # print msg class ClockUITest(BaseGtkUITest): settings = {'start_time': 100} def setUp(self): self.clockui = ClockUI(DummyGameclockUI(), ChessClock(**self.settings)) def test_refresh(self): """test if label displays the right time after refresh""" self.clockui.refresh() self.assertEqual(self.clockui.label.get_label(), self.clockui.clock.format(self.clockui.format)) class BaseGameclockUITest(BaseGtkUITest): """base class for GameclockUI tests, will not be called directly""" settings = { 'players': RegularChessGame.players, 'time': RegularChessGame.time } def setUp(self): self.ui = DummyGameclockUI(**self.settings) self.ui.setup_clocks() class GameclockUITest(BaseGameclockUITest): def test_setup(self): """test if clock setup sets the right number of clocks""" self.assertEqual(len(self.ui.clock_table.get_children()), self.ui.game.players) self.assertEqual(self.ui.clock_table.get_property('n-rows'), self.ui.game.players/2) self.assertEqual(self.ui.clock_table.get_property('n-columns'), 2) # iterate through the list and make sure we have the right count p = self.ui.cur_clock i = 0 while p: p = p.next i += 1 self.assertEqual(i, self.ui.game.players) def test_setup_twice(self): """test for bug #9658 - make sure we can recreate functional clocks""" game = self.ui.game # keep a backup self.refresh_gui(0.100) # 100ms self.ui.move() self.refresh_gui(0.100) # 100ms clockui = self.ui.cur_clock # create a *second* game, which is what handle_new_dialog is doing in effect self.ui.game = RegularChessGame(**self.settings) self.ui.setup_clocks() self.refresh_gui(0.100) # 100ms # those should be different clocks self.assertNotEqual(game.cur_clock, self.ui.game.cur_clock) self.assertEqual(self.ui.cur_clock.label.get_label(), '60:00') self.assertTrue(self.ui.cur_clock in self.ui.clock_table.get_children()) # this old clock widget should be gone self.assertFalse(clockui in self.ui.clock_table.get_children()) def test_time_passing(self): self.assertFalse(self.ui.game.running()) self.ui.handle_move() # should start the game self.assertTrue(self.ui.game.running()) self.assertTrue(self.ui.cur_clock.clock.running()) self.assertFalse(self.ui.first_clock.clock.running()) self.assertNotEqual(self.ui.cur_clock, self.ui.first_clock) self.refresh_gui(0.100) self.assertEqual(self.ui.cur_clock.label.get_label(), '59:59') self.refresh_gui(1.000) self.assertEqual(self.ui.cur_clock.label.get_label(), '59:58') self.assertLess(self.ui.cur_clock.clock.get_time(), 60 * 60 * 1000) class SinglePlayerGameclockUITest(BaseGameclockUITest): settings = { 'players': 1, 'time': RegularChessGame.time } def test_setup(self): self.assertEqual(len(self.ui.clock_table.get_children()), self.ui.game.players) #self.assertEqual(self.ui.clock_table.get_property('n-rows'), self.ui.game.players/2) self.assertEqual(self.ui.clock_table.get_property('n-columns'), 2) # iterate through the list and make sure we have the right count p = self.ui.first_clock i = 0 while p: p = p.next i += 1 self.assertEqual(i, 1) p = self.ui.game.first_clock i = 0 while p: p = p.next i += 1 self.assertEqual(i, 1) def test_time_passing(self): self.assertFalse(self.ui.game.running()) self.ui.handle_move() # should start the game self.assertTrue(self.ui.game.running()) self.assertTrue(self.ui.cur_clock.clock.running()) self.assertTrue(self.ui.first_clock.clock.running()) self.assertEqual(self.ui.cur_clock, self.ui.first_clock) self.refresh_gui(0.100) self.assertEqual(self.ui.cur_clock, self.ui.first_clock) self.assertEqual(self.ui.cur_clock.label.get_label(), '59:59') self.refresh_gui(1.000) self.assertEqual(self.ui.cur_clock.label.get_label(), '59:58') self.assertLess(self.ui.cur_clock.clock.get_time(), 60 * 60 * 1000) class HourglassUITest(BaseGameclockUITest): def setUp(self): self.ui = DummyGameclockUI(**self.settings) self.ui.game = HourglassGame() self.ui.setup_clocks() self.ui.refresh() def test_first_clock_increments(self): """check that the first clock goes up while the other one goes down""" self.ui.handle_move() self.refresh_gui(0.100) self.assertGreater(self.ui.first_clock.clock.get_time(), 60000) self.ui.handle_move() self.refresh_gui(0.100) self.ui.handle_move() self.refresh_gui(0.200) self.assertGreater(self.ui.first_clock.clock.get_time(), 60000) class ThreePlayerUITest(BaseGameclockUITest): def setUp(self): self.ui = DummyGameclockUI(**self.settings) self.ui.game.resize(3) self.ui.setup_clocks() self.ui.refresh() def test_third_player(self): first_clockui = self.ui.cur_clock first_clock = self.ui.cur_clock.clock self.assertEqual(self.ui.game.cur_clock, self.ui.cur_clock.clock, "the game agrees with the UI about the current clock") self.assertFalse(self.ui.game.running(), 'the game starts stopped') self.ui.handle_move() self.assertEqual(self.ui.game.cur_clock, self.ui.cur_clock.clock, "the game agrees with the UI about the current clock") self.refresh_gui(0.100) self.assertLess(self.ui.cur_clock.clock.get_time(), 60 * 60 * 1000, 'time has passed on the second clock') self.ui.handle_move() self.assertEqual(self.ui.game.cur_clock, self.ui.cur_clock.clock, "the game agrees with the UI about the current clock") self.ui.handle_move() self.refresh_gui(0.100) # 1 second, for the counter of the first clock to change self.assertTrue(self.ui.game.running(), 'the game is still running') self.assertEqual(self.ui.game.first_clock, self.ui.game.cur_clock, 'the game engine is on the first clock') self.assertEqual(self.ui.cur_clock, self.ui.first_clock, 'we are back on the first clock after three moves') self.assertEqual(self.ui.cur_clock, first_clockui, "it's the same clockUI as when we started") self.assertEqual(self.ui.game.cur_clock, self.ui.cur_clock.clock, "the game agrees with the UI about the current clock") self.assertEqual(self.ui.cur_clock.clock, first_clock, "it's the same clock too") self.assertTrue(self.ui.cur_clock.clock.running(), "that clock is running") self.assertLess(self.ui.cur_clock.clock.get_time(), 60 * 60 * 1000, "the clock has passed some time") self.assertNotEqual(self.ui.cur_clock.label.get_label(), '60:00') class ShortGameclockUITest(BaseGameclockUITest): """test code for games shorter than 60 seconds""" settings = { 'players': RegularChessGame.players, 'time': 59000 } def test_dc_start(self): """test if the display is showing tenths of a second if the game is started with less than 60 seconds""" self.assertEqual(self.ui.cur_clock.label.get_label(), '00:59.0') class GameclockMenuUITest(BaseGameclockUITest): """test the menus generated by the constructor""" def debug(self, s): print s.name def assertRadio(self, m): self.debug(m) print repr(m) if m.name is not None: self.assertEqual(m.__class__.__name__, 'RadioMenuItem') @unittest.expectedFailure def test_radio_menu(self): """for some reason, the new radio items are not radios... """ found_games = False for m in self.ui.menubar.get_children(): for n in m.get_submenu().get_children(): if n.name == 'new': for g in n.get_submenu().get_children(): if g.name is not None: found_games = True self.assertEqual(m.__class__.__name__, 'RadioMenuItem') self.assertTrue(found_games) def test_sort_menu(self): """make sure the new menu is sorted""" found_games = False n = self.ui.uimanager.get_widget('/menubar/game/new') previous = '' for g in n.get_submenu().get_children(): if g.name is not None: found_games = True self.assertLess(previous, g.get_label()) previous = g.get_label() self.assertTrue(found_games) class StyleTest(unittest.TestCase): """basic gtk self-tests so i understand how the style shit works""" def setUp(self): # we draw colors in evenboxes around here self.e = gtk.EventBox() self.e.show() # we need to wrap this in a window for some obscure reason self.w = gtk.Window() self.w.add(self.e) self.w.realize() def test_color_change(self): """make sure our own logic for checking background colors is sound""" self.e.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color('green')) self.assertEqual(self.e.style.bg[gtk.STATE_NORMAL], gtk.gdk.Color('green')) def test_style_change(self): """check if we can change the color with modify_style()""" style = gtk.RcStyle() style.bg[gtk.STATE_NORMAL] = gtk.gdk.Color('green') self.e.modify_style(style) self.assertEqual(self.e.style.bg[gtk.STATE_NORMAL], gtk.gdk.Color('green')) class GtkUiStyleTest(BaseGameclockUITest): def test_green(self): """make sure we can change the theme manually""" gtk.rc_parse_string(""" style "clockui" { bg[NORMAL] = "black" fg[NORMAL] = "white" bg[SELECTED] = "green" fg[SELECTED] = "black" bg[ACTIVE] = "red" fg[ACTIVE] = "black" } widget "*EventBox" style "clockui" """) self.ui.refresh() self.ui.window.realize() # hack to make sure the window is drawn, but without showing it. self.assertEqual(self.ui.cur_clock.evbox.style.bg[gtk.STATE_NORMAL], gtk.gdk.Color('black')) self.assertEqual(self.ui.cur_clock.evbox.style.bg[gtk.STATE_SELECTED], gtk.gdk.Color('green')) self.assertEqual(self.ui.cur_clock.evbox.style.bg[gtk.STATE_ACTIVE], gtk.gdk.Color('red')) gameclock-5.0/debian/0000755000000000000000000000000012141542147011414 5ustar gameclock-5.0/debian/menu0000644000000000000000000000017512141542147012306 0ustar ?package(gameclock):needs="X11" section="Games/Board" title="gameclock" command="/usr/games/gameclock" icon="gameclock.xpm" gameclock-5.0/debian/compat0000644000000000000000000000000212141542147012612 0ustar 9 gameclock-5.0/debian/control0000644000000000000000000000161612141542147013023 0ustar Source: gameclock Section: games Priority: optional Maintainer: Antoine Beaupré Build-Depends: debhelper (>= 9.0), python, python-setuptools Standards-Version: 3.9.4 Homepage: https://redmine.koumbit.net/projects/gameclock Vcs-Browser: https://redmine.koumbit.net/projects/gameclock/repository Vcs-Git: git://git.koumbit.net/gameclock/ Package: gameclock Architecture: all Depends: ${python:Depends}, python-gtk2, ${misc:Depends} Recommends: sound-theme-freedesktop, python-pygame Description: simple chess clock to track time in real life games Fairly simple application designed to track the time spent thinking by the players during a chess game. Various clock engines are available (speed chess, fischer chess, board games or hourglass). The graphical interface is keyboard-driven and is intended to be minimal and simple. The code is made to be extensible to other game types. gameclock-5.0/debian/changelog0000644000000000000000000001463112141542147013273 0ustar gameclock (5.0) unstable; urgency=low * fix sort order of games * upload to unstable -- Antoine Beaupré Sun, 05 May 2013 16:42:23 -0400 gameclock (5.0~beta3) experimental; urgency=low * fix theming translations * prettier shortcuts window * fix a few bugs with the File menu * implement restart * change default to 5 minute lightning game -- Antoine Beaupré Sun, 07 Apr 2013 20:11:44 -0400 gameclock (5.0~beta2) experimental; urgency=low * sort game names * fix player switching * fix fullscreen commandline option * add theming support * fix single player support * sort game names -- Antoine Beaupré Wed, 23 Jan 2013 10:26:11 -0500 gameclock (5.0~beta1) experimental; urgency=low * sound support * standard go timers support * per clock move counters * dropped status bar again -- Antoine Beaupré Tue, 22 Jan 2013 22:38:55 -0500 gameclock (5.0~alpha1) experimental; urgency=low * add unit tests * remove the "new" dialog that was confusing for new users and not very pretty * create better menu system for changing settings * major API changes and cleanup in game and clock modules * fix misspelling in "Fischer" * make this application translatable, ship a fr_CA translation * allow for arbitrary format of clocks * switch to tenths of a seconds after a threshold, instead of asking the user * tons of other small changes -- Antoine Beaupré Mon, 21 Jan 2013 23:25:57 -0500 gameclock (4.2) unstable; urgency=low * fix old display bug in the first window * remove hours from display * fix critical bug with fisher that was giving 10s advantage to the left clock * switch from miliseconds to tenths of a second * merge the status bars back into one -- Antoine Beaupré Thu, 17 Jan 2013 23:47:50 -0500 gameclock (4.1) unstable; urgency=low * make this a native package now that I am a DD * fix python dependency (Closes: #662152) * upgrade to debhelper 9.0 * update to policy 3.9.4, no change * fix about dialog * fix pause display * escape now exits full screen * pause doesn't change turns anymore * added a statusbar * display logo properly in menu, usage and icon -- Antoine Beaupré Sun, 13 Jan 2013 17:07:22 -0500 gameclock (4.0-3) unstable; urgency=low * change my email address * no need for DM field anymore, I am a DD! -- Antoine Beaupré Sun, 06 Nov 2011 22:27:30 -0500 gameclock (4.0-2) unstable; urgency=low [Colin Watson] * Build-depend on python-setuptools (Closes: #646801). -- Antoine Beaupré Thu, 27 Oct 2011 10:14:55 -0400 gameclock (4.0-1) unstable; urgency=low * change build system to use setuptools * new upstream release for the API change of moving files around -- Antoine Beaupré Tue, 11 Oct 2011 00:45:28 -0400 gameclock (3.1-3) unstable; urgency=low * fix FTBFS by reverting a lintian fix on the manpage. this will get into the debian package once i make a new upstream release (Closes: #643128). -- Antoine Beaupré Fri, 30 Sep 2011 00:25:50 -0400 gameclock (3.1-2) unstable; urgency=low * Fix lintian warnings (copyright symbol, url in copyright file) * Switch to source format 3.0 (quilt) -- Antoine Beaupré Sun, 21 Aug 2011 16:05:18 +0100 gameclock (3.1-1) unstable; urgency=low * New upstream release: * hide menu in fullscreen * add clocks descriptions * Fix description and Vcs-Browser links -- Antoine Beaupré Sat, 23 Jul 2011 23:34:42 -0400 gameclock (3.0-2) unstable; urgency=low * Fix homepage field to point to project page -- Antoine Beaupré Fri, 08 Jul 2011 02:29:04 -0400 gameclock (3.0-1) unstable; urgency=low * New upstream version * Major overhaul of the user interface * use dh simpler targets in debian/rules * silence some lintian warnings * Update standards version to 3.9.2, no change -- Antoine Beaupré Fri, 08 Jul 2011 02:00:51 -0400 gameclock (2.1-2) unstable; urgency=low * Switch to git for project management * Make DM-Upload ready since I am now a Debian Maintainer * Update standards versions to 3.8.3 -- Antoine Beaupré Sun, 30 Aug 2009 16:34:22 -0400 gameclock (2.1-1) unstable; urgency=low * New upstream release. This release adds support for two new game types and eases adaptation to other game types. I also designed a simple logo. -- Antoine Beaupré Fri, 19 Sep 2008 12:52:39 +0200 gameclock (2.0-1) unstable; urgency=low * New upstream release. The major new feature is the ability to have an arbitrary number of clocks/players. * Package renamed to gameclock since it's more than a chessclock now. * Fix my name (it spells with an accent) and my email (more frequently used). -- Antoine Beaupré Wed, 20 Aug 2008 00:01:37 -0400 pychessclock (1.2-1) unstable; urgency=low * New upstream release * It happens that there already is another "chessclock" out there, at http://gnomecoder.wordpress.com/chessclock/ . That other chessclock is written in Ruby/Gtk/Glade and is very similar to my clock. I have therefore renamed my version to avoid confusion. I also think keeping on developing my version as I am more familiar with Python. * This release adds features from chessclock that were missing in pychessclock: full screen and turn counting, mainly. -- Antoine Beaupre Thu, 14 Aug 2008 16:50:16 -0300 chessclock (1.1-2) unstable; urgency=low * Try to reupload without an accent in my name. -- Antoine Beaupre Thu, 14 Aug 2008 11:48:21 -0300 chessclock (1.1-1) unstable; urgency=low * Turn this into a non-native package * Change my email address to the one I registered to mentors.d.n with -- Antoine Beaupré Thu, 14 Aug 2008 10:47:41 -0300 chessclock (1.1) unstable; urgency=low * First Debian packaging * Improvements over the help and output system -- The Anarcat Thu, 14 Aug 2008 01:55:21 -0300 chessclock (1.0) unstable; urgency=low * First official release of the Chess Clock, no debian package. -- The Anarcat Wed, 13 Aug 2008 20:14:51 -0300 gameclock-5.0/debian/dirs0000644000000000000000000000001212141542147012271 0ustar usr/games gameclock-5.0/debian/gbp.conf0000644000000000000000000000004712141542147013034 0ustar [git-buildpackage] debian-branch = 5.x gameclock-5.0/debian/docs0000644000000000000000000000004612141542147012267 0ustar README.mdwn HISTORY.mdwn HACKING.mdwn gameclock-5.0/debian/rules0000755000000000000000000000014112141542147012470 0ustar #!/usr/bin/make -f %: dh $@ --with=python2 override_dh_installman: dh_installman gameclock.6 gameclock-5.0/debian/copyright0000644000000000000000000000236212141542147013352 0ustar This package was debianized by Antoine Beaupré (AKA The Anarcat) on Wed, 13 Aug 2008 20:14:51 -0300. It was downloaded from Upstream Author: Antoine Beaupré Copyright: Copyright © 2008-2011 Antoine Beaupré License: This package is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or any later version. This package 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 package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA On Debian systems, the complete text of the GNU General Public License can be found in `/usr/share/common-licenses/GPL-3'. The Debian packaging is © 2008-2011, Antoine Beaupré and is licensed under the GPL, see above. gameclock-5.0/debian/source/0000755000000000000000000000000012141542147012714 5ustar gameclock-5.0/debian/source/format0000644000000000000000000000001512141542147014123 0ustar 3.0 (native) gameclock-5.0/run_tests.py0000755000000000000000000000030612141542147012574 0ustar #!/usr/bin/python import unittest import sys import os import time sys.path.append(os.path.dirname(__file__)) suite = unittest.TestLoader().discover('tests') unittest.TextTestRunner().run(suite)