gameclock-5.0/ 0000755 0000000 0000000 00000000000 12141542305 010166 5 ustar gameclock-5.0/scripts/ 0000755 0000000 0000000 00000000000 12141542147 011661 5 ustar gameclock-5.0/scripts/gameclock 0000755 0000000 0000000 00000003140 12141542147 013532 0 ustar #!/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.py 0000777 0000000 0000000 00000000000 12141542147 014704 2scripts/gameclock ustar gameclock-5.0/gameclock.svg 0000644 0000000 0000000 00000032406 12141542147 012645 0 ustar
gameclock-5.0/gameclock.6 0000644 0000000 0000000 00000005532 12141542147 012213 0 ustar .\" 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.mdwn 0000644 0000000 0000000 00000015054 12141542147 012132 0 ustar 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.py 0000755 0000000 0000000 00000014513 12141542147 012050 0 ustar #!/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.mdwn 0000644 0000000 0000000 00000004524 12141542147 012227 0 ustar 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.mdwn 0000644 0000000 0000000 00000002513 12141542147 012017 0 ustar 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/ 0000755 0000000 0000000 00000000000 12141542147 010610 5 ustar gameclock-5.0/po/Makefile 0000644 0000000 0000000 00000000416 12141542147 012251 0 ustar *.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.po 0000644 0000000 0000000 00000021376 12141542147 012133 0 ustar # 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.pot 0000644 0000000 0000000 00000014411 12141542147 013144 0 ustar # 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.py 0000755 0000000 0000000 00000005337 12141542147 011717 0 ustar #!/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.cfg 0000644 0000000 0000000 00000000045 12141542147 012012 0 ustar [install]
install-scripts=/usr/games
gameclock-5.0/COPYRIGHT 0000644 0000000 0000000 00000104374 12141542147 011476 0 ustar
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.xpm 0000644 0000000 0000000 00000004240 12141542147 012645 0 ustar /* 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/ 0000755 0000000 0000000 00000000000 12141542305 012113 5 ustar gameclock-5.0/gameclock/i18n.py 0000644 0000000 0000000 00000001252 12141542147 013250 0 ustar # -*- 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.py 0000644 0000000 0000000 00000021735 12141542147 013574 0 ustar # -*- 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.py 0000644 0000000 0000000 00000002056 12141542147 013624 0 ustar # -*- 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__.py 0000644 0000000 0000000 00000002124 12141542147 014227 0 ustar # -*- 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.py 0000644 0000000 0000000 00000071466 12141542147 013632 0 ustar # -*- 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.py 0000644 0000000 0000000 00000017636 12141542147 013417 0 ustar # -*- 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/ 0000755 0000000 0000000 00000000000 12141542147 011334 5 ustar gameclock-5.0/tests/test_pygame.py 0000755 0000000 0000000 00000001235 12141542147 014233 0 ustar #!/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__.py 0000644 0000000 0000000 00000000000 12141542147 013433 0 ustar gameclock-5.0/tests/test_clock.py 0000755 0000000 0000000 00000016724 12141542147 014055 0 ustar #!/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.py 0000755 0000000 0000000 00000010500 12141542147 013655 0 ustar #!/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.py 0000755 0000000 0000000 00000027413 12141542147 014102 0 ustar #!/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/ 0000755 0000000 0000000 00000000000 12141542147 011414 5 ustar gameclock-5.0/debian/menu 0000644 0000000 0000000 00000000175 12141542147 012306 0 ustar ?package(gameclock):needs="X11" section="Games/Board" title="gameclock" command="/usr/games/gameclock" icon="gameclock.xpm"
gameclock-5.0/debian/compat 0000644 0000000 0000000 00000000002 12141542147 012612 0 ustar 9
gameclock-5.0/debian/control 0000644 0000000 0000000 00000001616 12141542147 013023 0 ustar 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/changelog 0000644 0000000 0000000 00000014631 12141542147 013273 0 ustar 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/dirs 0000644 0000000 0000000 00000000012 12141542147 012271 0 ustar usr/games
gameclock-5.0/debian/gbp.conf 0000644 0000000 0000000 00000000047 12141542147 013034 0 ustar [git-buildpackage]
debian-branch = 5.x
gameclock-5.0/debian/docs 0000644 0000000 0000000 00000000046 12141542147 012267 0 ustar README.mdwn
HISTORY.mdwn
HACKING.mdwn
gameclock-5.0/debian/rules 0000755 0000000 0000000 00000000141 12141542147 012470 0 ustar #!/usr/bin/make -f
%:
dh $@ --with=python2
override_dh_installman:
dh_installman gameclock.6
gameclock-5.0/debian/copyright 0000644 0000000 0000000 00000002362 12141542147 013352 0 ustar 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/ 0000755 0000000 0000000 00000000000 12141542147 012714 5 ustar gameclock-5.0/debian/source/format 0000644 0000000 0000000 00000000015 12141542147 014123 0 ustar 3.0 (native)
gameclock-5.0/run_tests.py 0000755 0000000 0000000 00000000306 12141542147 012574 0 ustar #!/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)