1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
|
This file documents the developpment process and the internal
structure of the code from a more general standpoint.
Development process
===================
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. I also thought of writing a
Gozerbot[1] plugin.
Code is maintained on Koumbit's Git repository[2]. There is a seperate
branch for the Debian package named 'debian' that tracks the
modifications to the debian/ directory, which is not packaged with the
regular distribution, as advised by various people in the debian
project[3]. So to checkout the 'upstream' code, you should use:
git clone git://git.koumbit.net/gameclock/
To checkout the Debian package version, use:
git clone -b debian http://hg.koumbit.net/gameclock/
Releases are tagged as appropriate. For version X.Y, there's a tag
named vX.Y. Debian versions have an additionnal dash (as for all
non-native packages) but otherwise follow the same naming convention.
Porters and packagers should start from tagged version and create a
named branch for their changes, if they modify the code tree
itself. For example, the above 'debian' branch was created like this:
git clone http://hg.koumbit.net/gameclock/
cd gameclock ; hg branch debian
dh_make # make the debian package
git commit -a
Building the debian package should be a breeze. Just use
git-buildpackage. I try to keep the release tags in order so that this
"just works".
Patches and bundles are of course welcome.
[1] http://gozerbot.org/
[2] https://redmine.koumbit.net/projects/gameclock/
[3] http://people.debian.org/~codehelp/#sponsor
http://people.debian.org/~mpalmer/debian-mentors_FAQ.html#packaging
http://lists.debian.org/debian-devel/2007/12/msg00630.html
Code structure
==============
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 fisher.
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.
FisherClock class
'''''''''''''''''
This is a subclass of the generic Clock class that implements fisher
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.
|