Add glChess to gnome-games.

This commit is contained in:
Andreas Røsdal 2006-10-28 16:37:30 +00:00
commit 3ab0fee043
81 changed files with 17365 additions and 0 deletions

4
BUGS Normal file
View file

@ -0,0 +1,4 @@
If glChess exits without deleting AIs then they remain as processes
using 100% CPU.
If AIs fail to execute or crash then glChess does not know this.

340
COPYING Normal file
View file

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) 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
this service 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 make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. 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.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the 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 a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE 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.
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
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision 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, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This 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 Library General
Public License instead of this License.

868
ChangeLog Normal file
View file

@ -0,0 +1,868 @@
------------------------------------------------------------------------------
CHANGELOG
------------------------------------------------------------------------------
2006-10-23 vers. 1.0 RC1
o AI options can be entered into the AI configuration file.
o Game defaults to human (white) versus the first detected AI.
o Lightened 3D models.
o Updated German translation from Rosetta.
o Changed AI file IO and animation from polled to event driven to save
CPU.
2006-10-20
o Fix bug with long castling.
o AI configuration moved into XML file.
------------------------------------------------------------------------------
2006-10-18 vers. 0.9.12
o Gracefully handle (disable) missing stock icons.
2006-10-13
o Updated German translation from launchpad.net.
o Added Turkish translation (Thanks to Hakan Bekdas
<hakanbekdas@yahoo.com>).
2006-10-11
o Finished converting all internal move formats to LAN.
o Added support for Universal Chess Interface (UCI) chess engines.
o Added support for Glaurung engine (UCI).
2006-10-05
o Updated translations from Rosetta (new fr and es).
2006-10-04
o Fix bug with decoding promotion from CECP engines.
o Refactored SAN and LAN en/decoding, changed much of the integer
internal location format to algebraic (i.e. (0, 0) -> 'a1').
------------------------------------------------------------------------------
2006-10-04 vers. 0.9.11
o OpenGL rendering is now optional
2006-10-03
o Recoloured 3D mode to be clearer and more consistent with 2D mode.
o Textures are now luminence only (save 1/3 memory).
o Python Imaging Library now optional - falls back to native Python
PNG loader.
o New game window has 'start' button active by default.
2006-10-02
o Cairo animation is now all double buffered.
o Simplified new game dialog - player types in single combo boxes with
icons.
o Add MIME file for /usr/share/mime - the freedesktop.org one is
missing the sub_class_of field (freedesktop.org bug #8286).
o Debian package updates desktop and MIME databases so .pgn games can
be executed graphically.
------------------------------------------------------------------------------
2006-10-01 vers. 0.9.10
o Set default configuration values (otherwise upgrading users hit
configuration exception).
------------------------------------------------------------------------------
2006-10-01 vers. 0.9.9
o Updated .desktop file to register application/x-chess-pgn MIME type.
2006-09-30
o Games can be 2D (Cairo) or 3D (OpenGL). The code does not currently
work if OpenGL is not installed (for next release).
o Fix bugs in validating castle moves.
o Made colour scheme softer (still needs someone skilled to pick
colours and textures).
o Stack trace is printed to stdout when OpenGL rendering errors occur.
o Only print out OpenGL info once
2006-09-29
o Fix bug in game saving. (Bug from Jens Hamacher)
o Fix bug reporting OpenGL errors. (Bug from Jens Hamacher)
o Send both 'sd' and 'depth' to CECP engines to set search
depth. CECP specifies only 'sd' but GNUchess does not accept
this. (Bug from Jens Hamacher)
o Append .pgn to the end of save games
o Report the version on stdout on startup - should help in bug reports
2006-09-16
o Refactor.
o Remove notebook border and add spacing.
2006-09-12
o Removed more dbus code that shouldn't have been there...
o Added support to load games from the command line.
------------------------------------------------------------------------------
2006-09-11 vers. 0.9.8
o Whoops release - some networking code was accidently enabled
(avahi/dbus) that caused problems for people.
------------------------------------------------------------------------------
2006-09-09 vers. 0.9.7
o Added window that shows the communication with the AIs.
o Kill AIs that don't quit properly.
o Catch exceptions and autosave and kill any AIs. This stops the 100%
CPU GNUchess processes being left running.
2006-09-07
o Handle exceptions from rendering - Mesa is still producing the odd
error. This will cause a corrupt frame but the program will still
run.
o Fix bug with notifying players of moves. This caused AI players to
fail if playing black.
o Added support for the Phalanx AI.
------------------------------------------------------------------------------
2006-08-28 vers. 0.9.6
o Tweaked perspective to make better use of visual space
o Clear the OpenGL depth buffer after drawing the board. This stops
Mesa from corrupting the bottom of the piece models.
2006-08-24
o Changed display list code to avoid Mesa bug
(http://bugs.freedesktop.org/show_bug.cgi?id=7984).
o Disabled networking code to make 0.9.6 release that avoids
the Mesa bug.
2006-08-21
o Debian package now depends on Python OpenGL bindings.
2006-08-11
o Moved existing splashscreen to about dialog. When no games are
present an empty board is displayed.
2006-08-09
o Added Italian translation from Luca Marturana
<lucamarturana@gmail.com>, thanks again!
2006-07-29
o Added icon from Luca Marturana <lucamarturana@gmail.com>,
thanks Luca!
------------------------------------------------------------------------------
2006-06-26 vers. 0.9.5
o Changed build process to use distutils
2006-06-10
o Added 'faile' chess engine to autodetection list
(http://faile.sourceforge.net)
------------------------------------------------------------------------------
2006-06-10 vers. 0.9.4
o UI saves view options
2006-05-29
o Added internationalisation support (I18N)
o Added translations for en, en_NZ, en_AU, en_US and de
------------------------------------------------------------------------------
2006-05-14 vers. 0.9.3
o Refactored the game/scene/view relationship
o Added a lot more pydoc documentation
o Animate pieces when moving
o Change 'File' menu to 'Game' menu with all menubar options
2006-05-13
o Added move history controls
------------------------------------------------------------------------------
2006-05-07 vers. 0.9.2
o Replaced model set with new high-res version from John-Paul
Gignac <jjgignac@users.sourceforge.net>
o Install a .desktop file (from anonymous commenter on glchess.sf.net)
o Reverted piece textures to "wood" from version 0.4.6
o Added material properties for models (now shiny)
------------------------------------------------------------------------------
2006-05-04 vers. 0.9.1
o Quick tidy up for release.
2006-03-05
o Added animation to rotate board so human player is in foreground.
o Render pieces and the board to display lists where possible - huge
performance improvement.
o Fixed bug in rendering (was linking top of revolved models to the
bottom).
o Added texturing support.
2006-02-19
o More refactoring: Reduce code complexity and add comments.
2006-02-17
o Refactored code into src/.
o Filled out README, INSTALL.
o Added menu (new feature is ability to hide toolbar).
------------------------------------------------------------------------------
2006-02-11 vers. 0.9.0
o Complete rewrite in Python, uses code ported from 0.8 series.
o Board rules appear to be more reliable than previous series
(tested with AI vs AI games).
o Able to load and save files to PGN format - also stores AI
information.
o Automatically saves games on exit.
o Network support removed (to be added before 1.0.0).
o Supports the GNUchess, Crafty, Sjeng and Amy AIs.
(autodetected on startup).
o UI simplified (will add menus before 1.0.0).
o Able to drag pieces when moving (no animation yet).
------------------------------------------------------------------------------
2005-04-23 vers. 0.8.6
o UI can now choose different AIs
o Engine processes are now completed on (normal) exit
o Able to save PGN files from the UI
2005-04-22
o Slimmed down states for core board rules
o Added support for PGN output
o Changed default engine to gnuchess (crafty complains about illegal
moves when playing AI vs. AI - I think this is a crafty bug)
o Support promotion pieces from SAN moves
o Fix bug where glChess thought a castle was in progress but it was
just an illegal two square king move.
2005-04-21
o Board module now records all previous states with a board stack.
o Got rid of all compiler warnings
------------------------------------------------------------------------------
2005-04-14 vers. 0.8.5
o Fix missing datapath for chess view UI (affects installed versions)
o Fix .deb build process so copies directory to correct name
o Update about dialog to reflect copyright for old and new versions
------------------------------------------------------------------------------
2005-03-28 vers. 0.8.4
o Servers now announce themselves when created (protocol change)
o Now can be build as a .deb package (RPM build using alien)
------------------------------------------------------------------------------
2005-03-28 vers. 0.8.3
2005-03-26
o End game button now works
o Removed some obsolete printfs
2005-03-25
o Only the server displays the network game dialog
o Server informs connecting players of current players
o Players must connect with unique names
o Fixed dobule free bug when cancelling servers
2005-03-24
o Games now opened in tabs
o Human vs human local games have chat entry disabled
------------------------------------------------------------------------------
2005-03-23 vers. 0.8.2
o Messages are marked with which player sent them / if they are from the server
o Network messages contain the source client
o Clients can now disconnect from the server
o Clients now correctly connect as spectators
2005-02-27
o SAN module now generates mate and checkmate symbols (+ and #)
o Got message sending working in main view
2005-02-26
o Made board and UI modules into objects.
o Board rotates around for human players (as in glChess 0.4)
2005-02-23
o Players now have names for local game
o AI players no longer use hard-coded name
o Network game servers can swap players (e.g. if the black player is
selected to be the same as the white player they are swapped).
o Made scene module into an object (so future support for multiple games/scenes possible)
------------------------------------------------------------------------------
2005-02-20 vers. 0.8.1
o Network support works 95%
- Can have n clients connected (2 players + spectators)
- Players can be human or AI
- Multiple servers on one machine
2005-02-05
o Added copyright headers on the files
------------------------------------------------------------------------------
2005-02-05 vers. 0.8.0
o Complete rewrite from the ground up
o Uses GTK+ 2
0 Uses gtkglext over gtkglarea
o Planned support for (by release 0.9)
- Network play
- Textures
- Configuration using gconf
- Distribution packages
- etc...
o Note also the new website (in drupal)
------------------------------------------------------------------------------
2002-09-09 vers. 0.4.7
2002-09-09
o Applied patches from Martin Jacobs, fixes some problems getting glChess
to work with gnuchess, and misc. bugs
2002-03-27
o Some GUI/glchessrc patches from Michael Moerz
o Fixed ambiguity in shared files install dir, it is now /usr/local/share/games/
------------------------------------------------------------------------------
2002-03-25 vers. 0.4.6
2002-03-25
o Applied some Makefile simplification patches from Michael Moerz
o glChess now sleeps when not doing anything -- doesn't waste CPU
------------------------------------------------------------------------------
2002-03-23 vers. 0.4.5
2002-03-23
o Applied patch from Michael Hanson to fix calls with execvp() when
argv == NULL
------------------------------------------------------------------------------
2002-01-23 vers. 0.4.4
2002-01-23
o main.c: (Re)fixed bug which had strcmp() on NULL pointers
2002-01-22
o san.c: Fixed bug in SAN parsing code
2002-01-18
o san.c: Resolved a bug with one given coordinate
2002-01-17
o dialog.c: Implemented a file dialog for binary paths
o README: Updated some info, added configuration section
2002-01-15
o move.c: Fixed a bug which could appear when castling,
'en passant'ing or promoting and king would be in check
------------------------------------------------------------------------------
2002-01-10 vers. 0.4.3
2002-01-07
o move.c: Improved some move rule code
2001-12-29
o dialog.c: Applied patch by Robert Mibus to prevent a SEGFAULT
2001-12-28
o *.[ch]: Replaced "Copyright 2001" by "Copyright 2002"
o dialog.c: Interpret user defined NULL binary path as "do not use"
2001-12-27
o move.c: Fixed a promotion bug when unselecting
o dialog.c: Added gtk_window_set_position to all dialogs
o dialog.c: Make entries uneditable if "do not use" is set
o move.c: Replaced "---" by "do not use"
o dialog.c: dto.
------------------------------------------------------------------------------
2001-12-25 vers. 0.4.2
2001-12-25
o move.c: Finally fixed promotion dialog
o dialog.c: dto.
o menu.c: dto.
2001-12-24
o dialog.c: Added save button to binary dialog
o dialog.h: Clean up
2001-12-23
o dialog.c: Added check whether user defined paths exist
o *.[ch]: Ran indent
2001-12-20
o menu.c: Added "Edit binary paths" to menu
o dialog.c: Added "Don't have engine" check boxes
o menu.c: Adapted to "Don't have engine"
o engine.c: dto.
2001-12-19
o san.c: Hopefully improved debug ouput to track down a quite
strange bug
2001-12-18
o anim.[ch]: Renamed animation.[ch]
o prefs.[ch]: Renamed preferences.[ch]
(I like short file names :-)
2001-12-16
o dialog.c: Fixed GdkWarning by adding gtk_widget_realize
o *.[ch]: Changed debug_output format
2001-12-14
o global.h: Removed GLCHESS_VERSION_STRING
o dialog.c, interface.c: Use VERSION instead
o preferences.c: Rewrote some code for reflection patch
o interface.c: Disabled board rotation when engine is AI
2001-12-09
o Makefile, configure.in: Finished autoconf/automake
o README: documentation update
2001-12-08
o dialog.c: Added quit dialog
o dialog.c: set engine dialog modal
------------------------------------------------------------------------------
2001-12-07 vers. 0.4.1
2001-12-07
o src/Makefile: Removed the -s flag
o Makefile: Included strip in install target as Robert proposed
o *.[ch]: Ensured that every debug_output checks debug first
o Makefile: Added $(PREFIX) = /usr/local as suggested by PLD
Did a include-cleanup:
o image.c: Removed string.h include
o menu.c: Removed string.h include
o models.c: Removed stdio.h include
o move.c: Removed gdk/gdk.h include
Moved all code concerning dialogs into dialog.c
o dialog.[ch]: Added
o engine.c, menu.c: Moved dialog code to dialog.c
2001-12-06
o main.c: Reordered functions, so they do what they should do
o main.c, engine.c, menu.c: Removed some DogFood(tm)
2001-12-05
o main.c: Fixed segfault due to looking at NULL strings
o menu.c: Fixed binary path dialog, made modal
o menu.c: Stopped indent from messing up ItemFactory struct,
cleaned up struct, so is readable
------------------------------------------------------------------------------
2001-12-01 vers 0.4.0 (inofficial release)
2001-11-30
o menu.c: Added dialog for entry of binary path
2001-11-26
o engine.c: Added find_binary and check_binary
o glchessrc: Added entries for binary paths
o global.h: Bumped version to 0.4.0
o glchess.spec: dto.
2001-11-20
o interface.c: Removed handle menu box
o interface.c: Same for status bar
2001-11-16
o draw.c, menu.c:
Applied a patch from Bartosz Taudul <wolf@ae.pl> to
speed up reflections
o AUTHORS: Added patches section
2001-11-09
o menu.c: Corrected a menu entry
o interface.c: Tweaked whole app by removing unneeded animate call
2001-10-31
o menu.c: Made "piece size" menu a radio button one
o menu.c: Moved "piece size" menu to "View"
2001-10-23
o Some cosmetic changes
------------------------------------------------------------------------------
2001-09-04 vers 0.3.5
2001-09-04
o menu.c: Tweaked about dialog
o logo.xpm: Added
o move.c: Fixed a critical bug
o san.c: dto.
2001-09-03
o interface.c: Changed timer interval to 1 sec
o move.c: Move list now scrolls down when it reachs bottom
2001-09-01
o Makefile: Fixed compile line to have LIBS last.
Thanks to Sean Fleming for the bug report
o Makefile.IRIX: dto.
------------------------------------------------------------------------------
2001-08-18 vers 0.3.4
2001-08-17
o menu.c: Started to implement promotion dialog (not finished yet)
2001-08-15
Thanks to Guiseppe Borzi' <gborzi@ieee.org> we now have a RPM package
for the very first time.
o glchess.spec: Added
o glchess.menu: dto.
o glchess.patch: dto.
2001-08-14
o menu.[ch]: Renamed help_about about_dialog
2001-08-13
o move.c: Implemented material statistic display
o interface.c: dto.
2001-08-12
o glchessrc.installed: Removed
o main.c: Let us exit normally when no config file was found
2001-08-11
o src/interface.c: Finished move list
2001-08-08
o src/interface.c: Added frame for move list
o dto.: Renamed create_window to create_main_window
2001-08-07
o src/Makefile*: Added optimization and stripping at compile time
o src/move.c: Limited the trackball zoom range
o dto.: Fixed PAWN move bug
o src/models.c: Removed some useless lines
2001-08-06
o src/global.h: Did a little clean-up in the player structure
2001-08-02
o src/splash.c: Resolved FIXME by changing rotation code
2001-08-01
o src/move.c: Removed a useless line
o src/game.c: Removed two crufty functions
o src/interface.c: Removed unused 'killcount' code
o src/global.h: dto.
2001-07-31
o src/models.c: Added ability to change size of pieces
o dto.: Removed some unneeded lines
o dto.: Removed constant variable norm_theta
o src/menu.c: Added menu entries for this
2001-07-19
o src/Makefile: Let's `make` do the work, not us
o src/Makefile.IRIX: dto.
2001-07-18
o Makefile: Noticed that 'tar zcvf' doesn't compress on the highest
level, added a separate call for zipping with -9v
------------------------------------------------------------------------------
2001-07-17 vers 0.3.3
2001-07-17
o interface.c: Fixed crash, when playing against an engine,
changing color and then playing against yourself
o menu.c: Fixed crash, when changing color without a running game
o config.c: Simplified some stuff
o config.h: Added prototype of get_colour
2001-07-16
o config.c: Wiped out the length limits for options, esp. paths
2001-07-11
o san.c: Complete rewrite. Used move rules to do the thing
o menu.c: Added menu entries for color choice
2001-07-09
o san.c: Replaced stupid linear code by more sophisticated code:-)
KING: 49 lines -> 7 lines
KNIGTH: 47 lines -> 8 lines
o BUGS: Added
2001-07-08
o interface.c, preferences.c, splash.c: Rename all
gtk_gl_area_swapbuffers() calls to gtk_gl_area_swap_buffers()
calls, following the GtkGLArea HOWTO
o game.c: Little optimization in reset_board
o texture.c: Made several file name length dynamically allocatable
------------------------------------------------------------------------------
2001-07-07 vers 0.3.2
2001-07-07
o move.c: Fixed castling rule by using internal move list
o Makefile: Added version and indent target
o move.c: Added a 'in check' check algorithm, this means
move rules finally and completely work now
o move.c: Fixed some strange movement bugs with PAWN and KING
2001-07-05
o engine.c, move.c, global.c: Implemented internal move list
o move.c: Fixed a bug with PAWN which could be moved sidewards
2001-07-04
o engine.c., menu.c: You could have started an engine while playing
local Human vs. local Human, fixed
o move.c: Moved some code to check_move function, mainly enhanced
KING and PAWN checking code
o move.c: Rule checking is now completely done in the enhanced old,
but very sophisticated code. Though some things are missing
2001-07-03
o picksquare.[ch]: Renamed to move.[ch]
o config.c, glchessrc: Added debugging option to config file
(Until now debugging was on by default)
o engine.c, san.c, move.c: Adapted
------------------------------------------------------------------------------
2001-07-04 vers 0.3.1
2001-07-02
o interface.c: Fixed a long standing NVIDIA bug
(It _was_ our fault... :-)
o menu.c: Fixed bug where splashscreen wasn't displayed with NVIDIA
drivers
------------------------------------------------------------------------------
2001-06-27 vers 0.3.0
2001-06-26
o engine.c: Enabled engine pawn promotion (not only QUEEN)
2001-06-25
o picksquare.c: Added dialog to local H vs H code
o engine.c: Fixed bug, when starting several engines in a row
o engine.c: Fixed local H vs H again(!)
o menu.c: Renamed debug option
o TODO: Enlarged
2001-06-24
o san.c: Fixed a coordinate validity bug
o engine.c: Further improved read stability
o engine.c, picksquare.c:
Implemented GNU Chess support
o engine.c: fixed a newly introduced bug in read_from_engine
o menu.c: Simplified help_about
o engine.c: added engine_dialog
2001-06-23
o menu_preferences.[ch]: renamed to preferences.[ch]
o splash.c: moved init_game call to menu.c
o menu.c: added entries for engine choice
o *.[ch]: removed all USE_ENGINE and DEBUG_ENGINE defines
o menu.c: added entries whether or not to show debug information
o INSTALL: Rewrote some parts
2001-06-22
o san.c: Further optimization, esp. ROOK, KNIGHT, QUEEN
o san.c: Fixed a bug where the wrong piece could be moved
2001-06-21
o README, AUTHORS: Rewrote some parts
o TODO: Reordered, sorted entries by category and importance,
added estimated version number of implementation
2001-06-20
o san.c: Reduced memory usage
o Makefile: Added irix target, simply type make irix for IRIX now
o Makefile: Added make target, simply type make help for install notes
o *.[ch]: Changed code style, did a indent with: '-kr -i2 -bli0 -bl'
------------------------------------------------------------------------------
2001-06-19 vers 0.2.9
2001-06-19
o Added SAN support => Crafty > 18.x is usable now
2001-06-18
o Updated Makefile.IRIX
o Added NEWS file to inform you about recent plans
2001-06-15
o Replaced all malloc.h includes by stdlib.h ones
2001-06-12
o Added read_char which won't wait for ever if there is no char
o Added check_notation_format which will tell you if glChess
supports the format the engine uses
------------------------------------------------------------------------------
2001-05-30 vers 0.2.8
2001-05-30
o Resolved a segfault problem in engine.c/clear_engine_msg on startup
o Fixed a misbehaviour in make release
2001-05-28
o Merged types.h and global.h to global.h, added global.c
o Added info on how to pull glChess via CVS
2001-05-25
o Added full "en passant" support
Be warned! It's a rare case (at least for me), I couldn't
really test it.
o Fixed a bug in engine.c/parse_move_from_engine with learn message
o Did a TODO file clean-up
o Automated versioning in documentation
o Replaced "!!note" by "FIXME" entries (it's more logical to me)
------------------------------------------------------------------------------
2001-05-24 vers 0.2.7
2001-05-24
o glChess now quits normally if OpenGL is not supported
o Fixed a SEGFAULT in engine.c/clear_engine_msg
o Re-added COPYING to sources due to problems with CVS and symlinks
2001-05-24
o Finally resolved my problems when starting glChess as root
by writing clear_startup_msg
o Improved reading from engine mechanism
2001-05-12
o Fixed two bugs in Makefile (install, release)
o Brought man page up to date
o Added target 'uninstall' to Makefile, self-explanatory
o Enhanced About dialog
2001-05-11
o Changed ChangeLog format, so you can see when something was done
o Fully enabled beep on illegal move, added config and menu entry
for this
------------------------------------------------------------------------------
2001-05-09 vers 0.2.6
o Changed normal printf(...) to fprintf(stderr, ...) in config.c
o First approach for pawn promotion, still lacking a dialog
promotion to queen is default
o Integrated beep on move
o Changed GlChessWidget to glChessWidget for conformity
o Changed coords color to white
o Added an IRIX Makefile in
o Did a shoddy fix for NVIDIAS worthless drivers
------------------------------------------------------------------------------
2001-05-06 vers 0.2.5
o Config files with no newline at the end now supported.
o Improved readability in engine.c
o Changed description text
o Reduced startup problems with engine. Anybody with pipe
experience out there ?
o Crafty/CECP options now in config file
o glChess now stops crafty when game is resigned
o crafty is only started when game is started, not when glChess starts
o Added in 'make release' -- packages up the source
------------------------------------------------------------------------------
2001-05-01 vers 0.2.4
o Code readibiliy fixes
o Co-ordinates reflected on other side of board now
o Moved man/man6/glchess.6.gz to man/glchess.6.gz
o Started implementing the Chess Engine Communication Protocol.
So far you can only use crafty and CECP is not fully
useable.
Basic game playing works. We need feedback !!
o Expanded TODO for engine
o Changed the date strings in Changelog
o Fixed a little bug in picksquare with new castling rules
------------------------------------------------------------------------------
2001-04-16 vers 0.2.3
o Changed COPYING to be a symlink to /usr/share/automake/COPYING
o Fixed bug when try to resign from splash screen, and similar when start from game
screen
o Added in grid co-ordinates
o Added new config options -- texture_dir number_colour letter_colour
o Added in Michael Duelli as an author
------------------------------------------------------------------------------
2001-04-15 vers 0.2.2
o Fixed signal disconnection error
o Improved rc file format (added in quotes)
o Got rid of lots of bad global variables/bad programming
(isn't it fun to come back to something you wrote ages ago... :) )
o Using shortcut to start game now refreshes view
o Changed pitch of rotating view to look down more (easier to play)
o Lots of good bug/convention fixes from Michael Duelli -- thanks!
including:
o Changed default install dir to /usr/local/bin since most distributions
don't have /usr/local/games in their path.
o Limited the trackball mode so can't see under the board
o Changed glchess.[ch] to main.[ch] -- more conventional
o Fixed error with default flat/smooth shading
o Misc code readibility fixes
o Fixed stretching in Ortho mode
o Added in preferences dialog -- warning it doesn't work 100%!!!
o Added in AUTHORS file -- becoming a multi-developer project
o Changed base directory to glchess-VERSION
o Added in man page
------------------------------------------------------------------------------
2001-04-08 vers 0.2.1
o Reorded menus
o Reenabled trackball mode
o Reenabled free mode
o Added in save options menu item
o Got rid of all C++ style comments ('//')
------------------------------------------------------------------------------
2001-04-08 vers 0.2.0
o Changed email address
o Changed interface to GTK+
o Changed piece textures
------------------------------------------------------------------------------
2001-02-18 vers 0.1.11
o Added in texturing
o Added in revolve_line() -- greatly simplified models.c
o Improved rc file loading
o Now checks the current dir for a rc file (if not installed)
------------------------------------------------------------------------------
2001-01-13 vers 0.1.10
o Added some more libs into the Makefile
o Changed the selected piece colour to red with a higher alpha -- easier to see
o FPS only updates every 1 sec now -- easier to read
o Reflections are initially disabled -- performance (press j to activate)
o Added a _very_ simple rc file, not very robust, just copy /etc/glchessrc to ~/.glchessrc
------------------------------------------------------------------------------
2001-01-11 vers 0.1.9
o Fixed some problems in the Makefile (sorry)
o Linked to GLU, one person's glut or similar not linking it seems
------------------------------------------------------------------------------
2001-01-08 vers 0.1.8
o Now compiled with a makefile
o Fixed error in lighting from selected piece
o Realised castling rule is not really correct, not fixed, will not change the rules
again because a) I don't think anyone is actually using it
and b) It will use crafty or something similar *soon* so no need to bother
o FPS meter fades when max FPS is reached
o Name changes to red when editing
------------------------------------------------------------------------------
2001-01-05 vers 0.1.7
o Fixed castling rule (I wasn't fully aware of what the rule was)
o Changed the colour of the pieces/board
o Fixed an error in the shading toggle
o Added a pop-up menu for quit/toggles/viewmode
o Changed default view-mode to 2 (rotating)
o Got stencil buffer working by enabling it in glut (oops)
o Added a frame rate limiter in
o Added ability to change player names
o Game no longer automatically starts (r-click for menu)
o Fixed error in knight model
o Changed lighting on selected piece
o Added a fourth view mode (trackball)
------------------------------------------------------------------------------
2001-01-01 vers. 0.1.6
o Added floor reflections (stencil buffer doesn't seem to work though)
o Added castling rule
o Added in a make script (it _will_ be a makefile one day)
o Fixed the location of the kings/queens (you can tell I don't play chess)
o Removed some debugging output to stdout
------------------------------------------------------------------------------
2000-10-13 vers. 0.1.5
o Actually finished knight model :)
o Found out how to cull back facing polygons -- fps greatly improved
o Fixed alignment of black players time on HUD (for 480x480 res)
o Disabled killcount -- slowing down fps too much
o Added webpage url into README
------------------------------------------------------------------------------
2000-09-28 vers. 0.1.4
o Co-ordinate system changed - y is up instead of z now (native OpenGL)
o Finished knight model (finally)
o Selected model lighting fixed
o Error (segfault) in selection algorithm fixed
o Beginnings of HUD added - fps meter, player time, killcount
------------------------------------------------------------------------------
2000-05-27 vers. 0.1.3
o Players now take turns
o 3 Modes of view - Dynamic/Rotating/Ortho
------------------------------------------------------------------------------
2000-05-26 vers. 0.1.2
o Pieces now have movement rules
------------------------------------------------------------------------------
2000-05-22 vers. 0.1.1
o fixed draw.c (accidentially overwritten in previous release)
o pieces can now be moved
------------------------------------------------------------------------------
2000-05-15 vers. 0.1.0
o first release
------------------------------------------------------------------------------

17
MANIFEST.in Normal file
View file

@ -0,0 +1,17 @@
include lib/glchess/gtkui/*.glade
include po/*.po po/*.pot
include BUGS ChangeLog COPYING INSTALL README TODO
include ai.xml glchess.svg glchess.desktop
include Makefile
include MANIFEST.in
include textures/*.png
include mime/glchess.xml
include debian/changelog
include debian/compat
include debian/control
include debian/copyright
include debian/docs
include debian/manpage.sgml
include debian/menu
include debian/rules
include debian/glchess.mime

25
Makefile.am Normal file
View file

@ -0,0 +1,25 @@
SUBDIRS = data glade help src textures
# Source files
EXTRA_DIST =
CLEAN_DIST =
CLEANFILES =
EXTRA_DIST += intltool-extract.in intltool-merge.in intltool-update.in
DISTCLEANFILES = intltool-extract intltool-merge intltool-update
#################################################################
# Launcher
@INTLTOOL_DESKTOP_RULE@
desktopdir = $(datadir)/applications
desktop_in_files = glchess.desktop.in
desktop_DATA = $(desktop_in_files:.desktop.in=.desktop)
EXTRA_DIST += $(desktop_in_files)
CLEANFILES += $(desktop_DATA)
#################################################################
## Executable
bin_SCRIPTS = src/glchess
EXTRA_DIST += src/glchess

38
README Normal file
View file

@ -0,0 +1,38 @@
1. Description
glChess is a 2D/3D chess game interfacing via the Chess Engine
Communication Protocol (CECP) by Tim Mann. This means it can currently use
engines such as GNUChess, Sjeng, Faile, Amy, Crafty and Phalanx.
2. Homepage
Visit the homepage for information on how to report bugs and to keep
up on development progress.
SourceForge Homepage -> http://glchess.sourceforge.net
SourceForge Project Page -> http://sourgeforge.net/projects/glchess
3. Requirements
glChess requires the following software to be installed to work:
- Python
- Gtk+ including the python bindings
For 3D support the following are optionally required:
- GtkGLExt including the python bindings
- OpenGL python bindings
For faster texture loading:
- Python Imaging Library
Optionally you can install CECP compatible AIs - glChess will detect the ones
it can use.
4. License
glChess is released under the GNU General Public License. In short, you may
copy this program (including source) freely, but see the COPYING file for
full details.

28
TODO Normal file
View file

@ -0,0 +1,28 @@
1.0 Features
-------------
Supply application/x-chess-pgn icons
1.1 Features
--------------
Network support
Crash dialog so users can easily make bug reports
Make configuration dialog (with AI config too)
Don't autosave games that have finished (move into a history file)
Reflect chess pieces in board
Chess piece shadows
Anti-aliasing
Drag and drop 2D pieces
Drag and drop application/x-chess-pgn files
Add icons for human/network/ai players. Use Red Robot for the AI!
Share textures and display lists between games
1.2+ Features
--------------
ICS support
Dialog if don't have correct version of GTK+, GTKGLarea installed
Help file
Get setup.py to install translations. This may be a limitation of distutils? They need compiling and renaming
Make glChess able to generate thumbnails for Nautilus

3
data/Makefile.am Normal file
View file

@ -0,0 +1,3 @@
EXTRA_DIST = ai.xml
aidir = $(datadir)/glchess/
ai_DATA = ai.xml

54
data/ai.xml Normal file
View file

@ -0,0 +1,54 @@
<aiconfig>
<ai type="cecp">
<name>GNUchess</name>
<binary>gnuchess</binary>
<option>hard</option>
</ai>
<ai type="cecp">
<name>Sjeng</name>
<binary>sjeng</binary>
<option>easy</option>
<option>depth 2</option>
</ai>
<ai type="cecp">
<name>Amy</name>
<binary>Amy</binary>
<option>easy</option>
<option>depth 2</option>
</ai>
<ai type="cecp">
<name>Crafty</name>
<binary>crafty</binary>
<option>easy</option>
<option>depth 2</option>
</ai>
<ai type="cecp">
<name>Faile</name>
<binary>faile</binary>
<option>easy</option>
<option>depth 2</option>
</ai>
<ai type="cecp">
<name>Phalanx</name>
<binary>phalanx</binary>
<option>easy</option>
<option>level 5</option>
<option>depth 2</option>
</ai>
<ai type="uci">
<name>Glaurung</name>
<binary>glaurung</binary>
<option name="Hash" type="spin" min="4" max="1024">32</option>
<option name="Aggressiveness" type="spin" min="0" max="300">130</option>
<option name="Cowardice" type="spin" min="0" max="300">100</option>
<option name="Ponder" type="check">true</option>
</ai>
</aiconfig>

120
debian/changelog vendored Normal file
View file

@ -0,0 +1,120 @@
glchess (1.0RC1-1) unstable; urgency=low
+ New upstream release
+ Changed PIL and OpenGL packages from Suggests to Recommends.
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Mon, 23 October 2006 10:17:00 +0000
glchess (0.9.12-1) unstable; urgency=low
+ New upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Mon, 2 October 2006 13:31:00 +0000
glchess (0.9.11-1) unstable; urgency=low
+ New upstream release
+ Update desktop and mime databases after installation
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Mon, 2 October 2006 13:31:00 +0000
glchess (0.9.10-1) unstable; urgency=low
+ New upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 1 October 2006 17:04:00 +0000
glchess (0.9.9-1) unstable; urgency=low
+ New upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 1 October 2006 11:32:00 +0000
glchess (0.9.8-1) unstable; urgency=low
+ New upstream release
+ Added dependencies to python2.4, python2.4-gtk2
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 11 September 2006 21:01:00 +0000
glchess (0.9.7-1) unstable; urgency=low
+ New upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 9 September 2006 12:40:00 +0000
glchess (0.9.6-1) unstable; urgency=low
+ New upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Mon, 28 August 2006 20:59:00 +0000
glchess (0.9.5-2) unstable; urgency=low
+ New upstream release
+ Package now depends on python2.4-opengl
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Mon, 21 August 2006 15:23:00 +0000
glchess (0.9.5-1) unstable; urgency=low
+ new upstream release
+ Python files are compiled on installation
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Tue, 13 June 2006 22:44:00 +0000
glchess (0.9.4-2) unstable; urgency=low
+ Fix menu entry to point at correct binary
+ Add man page to package
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 10 June 2006 16:04:00 +0100
glchess (0.9.4-1) unstable; urgency=low
+ new upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 10 June 2006 12:46:00 +0000
glchess (0.9.3-1) unstable; urgency=low
+ new upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sun, 14 May 2006 15:14:00 +0000
glchess (0.9.2-1) unstable; urgency=low
+ new upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sun, 7 May 2006 13:03:00 +0000
glchess (0.9.1-1) unstable; urgency=low
+ new upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Thu, 4 May 2006 21:29:00 +0000
glchess (0.9.0-1) unstable; urgency=low
+ new upstream release - rewrite in Python
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Sat, 11 Feb 2006 12:09:00 +0000
glchess (0.8.6-1) unstable; urgency=low
+ new upstream release
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Thu, 23 Apr 2005 18:15:00 +1200
glchess (0.8.5-1) unstable; urgency=low
+ new upstream release - fixes UI path bug
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Thu, 14 Apr 2005 23:51:00 +1200
glchess (0.8.4-1) unstable; urgency=low
* Initial Release.
-- Robert Cleaver Ancell <bob27@users.sourceforge.net> Mon, 28 Mar 2005 15:37:31 +1200

1
debian/compat vendored Normal file
View file

@ -0,0 +1 @@
4

16
debian/control vendored Normal file
View file

@ -0,0 +1,16 @@
Source: glchess
Section: gnome
Priority: optional
Maintainer: Robert Ancell <bob27@users.sourceforge.net>
Build-Depends: debhelper (>= 4.0.0), docbook-to-man, python-glade2 (>=2.8.1)
Standards-Version: 3.6.1
Package: glchess
Architecture: all
Depends: python2.4, python2.4-gtk2, python2.4-glade2 (>= 2.8.1)
Recommends: python2.4-gtkglext1, python2.4-opengl, python2.4-imaging
Description: A 2D/3D chess interface.
glChess is a 2D/3D chess program. It supports:
* Multiple simultaneous games using tabs
* Artificial intelligence using external engines
* 2D/3D rendering

27
debian/copyright vendored Normal file
View file

@ -0,0 +1,27 @@
This package was debianized by Robert Cleaver Ancell <bob27@users.sourceforge.net> on
Mon, 28 Mar 2005 15:37:31 +1200.
It was downloaded from http://glchess.sourceforge.net
Copyright Holder: Robert Ancell
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 2 of the License, or
(at your option) 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., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.
On Debian systems, the complete text of the GNU General
Public License can be found in `/usr/share/common-licenses/GPL'.

3
debian/docs vendored Normal file
View file

@ -0,0 +1,3 @@
BUGS
README
TODO

8
debian/glchess.mime vendored Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-chess-pgn">
<sub-class-of type="text/plain"/>
<comment>PGN chess game</comment>
<glob pattern="*.pgn"/>
</mime-type>
</mime-info>

111
debian/manpage.sgml vendored Normal file
View file

@ -0,0 +1,111 @@
<!doctype refentry PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
<!-- Process this file with docbook-to-man to generate an nroff manual
page: `docbook-to-man manpage.sgml > manpage.1'. You may view
the manual page with: `docbook-to-man manpage.sgml | nroff -man |
less'. A typical entry in a Makefile or Makefile.am is:
manpage.1: manpage.sgml
docbook-to-man $< > $@
The docbook-to-man binary is found in the docbook-to-man package.
Please remember that if you create the nroff version in one of the
debian/rules file targets (such as build), you will need to include
docbook-to-man in your Build-Depends control field.
-->
<!ENTITY dhfirstname "<firstname>Robert</firstname>">
<!ENTITY dhsurname "<surname>Ancell</surname>">
<!-- Please adjust the date whenever revising the manpage. -->
<!ENTITY dhdate "<date>March 28, 2005</date>">
<!ENTITY dhsection "<manvolnum>6</manvolnum>">
<!ENTITY dhemail "<email>bob27@users.sourceforge.net</email>">
<!ENTITY dhusername "Robert Ancell">
<!ENTITY dhucpackage "<refentrytitle>GLCHESS</refentrytitle>">
<!ENTITY dhpackage "glchess">
<!ENTITY debian "<productname>Debian</productname>">
<!ENTITY gnu "<acronym>GNU</acronym>">
<!ENTITY gpl "&gnu; <acronym>GPL</acronym>">
]>
<refentry>
<refentryinfo>
<address>
&dhemail;
</address>
<author>
&dhfirstname;
&dhsurname;
</author>
<copyright>
<year>2005-2006</year>
<holder>&dhusername;</holder>
</copyright>
&dhdate;
</refentryinfo>
<refmeta>
&dhucpackage;
&dhsection;
</refmeta>
<refnamediv>
<refname>&dhpackage;</refname>
<refpurpose>A 3D chess application</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>&dhpackage;</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>DESCRIPTION</title>
<para><command>&dhpackage;</command> is a 3D chess application for
GTK+ which supports CECP compatible artificial intelligences.</para>
<para>More information can be found at http://glchess.sourceforge.net.</para>
</refsect1>
<refsect1>
<title>SEE ALSO</title>
<para>gnuchess(6), crafty(6), amy(6), faile(6), xboard(6).</para>
</refsect1>
<refsect1>
<title>AUTHOR</title>
<para>glChess and this manual page were written by &dhusername; &dhemail;.
Permission is granted to copy, distribute and/or modify this document under
the terms of the &gnu; General Public License, Version 2 any
later version published by the Free Software Foundation.
</para>
<para>
On Debian systems, the complete text of the GNU General Public
License can be found in /usr/share/common-licenses/GPL.
</para>
</refsect1>
</refentry>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-omittag:t
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:2
sgml-indent-data:t
sgml-parent-document:nil
sgml-default-dtd-file:nil
sgml-exposed-tags:nil
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
End:
-->

1
debian/menu vendored Normal file
View file

@ -0,0 +1 @@
?package(glchess):needs="X11" section="Games/Board" title="glChess" command="/usr/games/glchess"

76
debian/rules vendored Executable file
View file

@ -0,0 +1,76 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
configure: configure-stamp
configure-stamp:
dh_testdir
# Add here commands to configure the package.
touch configure-stamp
build: build-stamp
build-stamp: configure-stamp
dh_testdir
# Add here commands to compile the package.
$(MAKE)
python setup.py build
docbook-to-man debian/manpage.sgml > glchess.6
touch build-stamp
clean:
dh_testdir
dh_testroot
rm -f build-stamp configure-stamp
# Add here commands to clean up after the build process.
python setup.py clean --all
dh_clean
install: build
dh_testdir
dh_testroot
dh_clean -k
dh_installdirs
# Add here commands to install the package into debian/glchess.
$(MAKE) install DESTDIR=$(CURDIR)/debian/glchess
python setup.py install --root $(CURDIR)/debian/glchess
# Build architecture-independent files here.
binary-indep: build install
dh_testdir
dh_testroot
dh_installmenu
dh_installman glchess.6
dh_installchangelogs ChangeLog
dh_link -i
dh_compress -i
dh_fixperms -i
dh_python -i
#dh_pysupport -i # Used by Debian
dh_desktop -i
dh_installmime -i
dh_installdeb -i
dh_gencontrol -i
dh_md5sums -i
dh_builddeb -i
# Build architecture-dependent files here.
binary-arch: build install
# No architecture dependant files
binary: binary-indep binary-arch
.PHONY: build clean binary-indep binary-arch binary install

14
glade/Makefile.am Normal file
View file

@ -0,0 +1,14 @@
uidir = $(datadir)/glchess
ui_DATA = \
about.glade \
ai.glade \
chess_view.glade \
error_dialog.glade \
glchess.glade \
load_game.glade \
network_game.glade \
new_game.glade \
save_game.glade
EXTRA_DIST = $(ui_DATA)

31
glade/about.glade Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkAboutDialog" id="glchess_about_dialog">
<property name="visible">True</property>
<property name="destroy_with_parent">True</property>
<property name="name">glChess 2.17.1</property>
<property name="copyright">Copyright 2005-2006 Robert Ancell (and contributors)</property>
<property name="comments" translatable="yes">A 2D/3D chess interface for Gnome</property>
<property name="license">This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA</property>
<property name="wrap_license">True</property>
<property name="website">http://glchess.sourceforge.net</property>
<property name="website_label" translatable="yes">glChess homepage</property>
<property name="authors">Robert Ancell &lt;bob27@users.sourceforge.net&gt;</property>
<property name="artists">John-Paul Gignac (3D Models)
Thomas Dybdahl Ahle (2D Models)</property>
<property name="translator_credits">Luca Marturana &lt;lucamarturana@gmail.com&gt; (It)
Hakan Bekdas &lt;hakanbekdas@yahoo.com&gt; (tr)
Many translators on launchpad.net.
</property>
<property name="logo">glchess.svg</property>
<signal name="response" handler="_on_glchess_about_dialog_close" last_modification_time="Fri, 11 Aug 2006 12:44:07 GMT"/>
</widget>
</glade-interface>

212
glade/ai.glade Normal file
View file

@ -0,0 +1,212 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="ai_entry">
<property name="title" translatable="no">(dummy window)</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<child>
<widget class="GtkTable" id="ai_table">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<widget class="GtkLabel" id="label52">
<property name="visible">True</property>
<property name="label" translatable="yes">Executable:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="executable_label">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="no">(executable name)</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">True</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label55">
<property name="visible">True</property>
<property name="label" translatable="yes">Playing as:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="game_label">
<property name="visible">True</property>
<property name="label" translatable="no">(player in game)</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTextView" id="comms_textview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_NONE</property>
<property name="cursor_visible">True</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">0</property>
<property name="right_margin">0</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
</widget>
</child>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label58">
<property name="visible">True</property>
<property name="label" translatable="yes">Communication:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

116
glade/chess_view.glade Normal file
View file

@ -0,0 +1,116 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<requires lib="gnome"/>
<widget class="GtkWindow" id="window1">
<property name="width_request">300</property>
<property name="height_request">300</property>
<property name="visible">True</property>
<property name="title">window1</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<child>
<widget class="GtkVPaned" id="chess_view">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="position">300</property>
<child>
<widget class="Custom" id="drawing_area">
<property name="visible">True</property>
<property name="creation_function">make_chess_view</property>
<property name="int1">0</property>
<property name="int2">0</property>
<property name="last_modification_time">Thu, 24 Mar 2005 01:41:43 GMT</property>
</widget>
<packing>
<property name="shrink">True</property>
<property name="resize">True</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox21">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow4">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTextView" id="message_text">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_NONE</property>
<property name="cursor_visible">False</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">0</property>
<property name="right_margin">0</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="message_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">*</property>
<property name="activates_default">False</property>
<signal name="activate" handler="_on_message_entry_activate" last_modification_time="Sun, 27 Feb 2005 10:01:23 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="shrink">True</property>
<property name="resize">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

160
glade/error_dialog.glade Normal file
View file

@ -0,0 +1,160 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkDialog" id="dialog">
<property name="border_width">12</property>
<property name="visible">True</property>
<property name="title" translatable="yes"></property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">True</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">True</property>
<property name="has_separator">False</property>
<signal name="close" handler="_on_close" last_modification_time="Thu, 09 Feb 2006 21:59:18 GMT"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="closebutton1">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-close</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-7</property>
<signal name="clicked" handler="_on_close" last_modification_time="Thu, 09 Feb 2006 21:59:06 GMT"/>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child>
<widget class="GtkLabel" id="title_label">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">&lt;b&gt;&lt;big&gt;Error Title&lt;/big&gt;&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">12</property>
<child>
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-dialog-warning</property>
<property name="icon_size">6</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="content_label">
<property name="width_request">350</property>
<property name="height_request">50</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">This area contains a description of the error.
In this case the error is made up and I'll just fill this with a pile of junk. Oh yes look at all the junk here.</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

1463
glade/glchess.glade Normal file

File diff suppressed because it is too large Load diff

111
glade/load_game.glade Normal file
View file

@ -0,0 +1,111 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkDialog" id="game_load_dialog">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="title" translatable="yes">Load a chess game</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="icon">glchess.svg</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="has_separator">True</property>
<signal name="close" handler="_on_close" last_modification_time="Thu, 09 Feb 2006 19:38:50 GMT"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="button6">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
<signal name="clicked" handler="_on_close" last_modification_time="Thu, 09 Feb 2006 19:38:43 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="properties_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-properties</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-10</property>
<signal name="clicked" handler="_on_configure_game" last_modification_time="Thu, 09 Feb 2006 19:37:29 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="open_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-open</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="_on_load_game" last_modification_time="Thu, 09 Feb 2006 19:41:01 GMT"/>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkFileChooserWidget" id="filechooserwidget">
<property name="width_request">600</property>
<property name="height_request">400</property>
<property name="visible">True</property>
<property name="action">GTK_FILE_CHOOSER_ACTION_OPEN</property>
<property name="local_only">True</property>
<property name="select_multiple">False</property>
<property name="show_hidden">False</property>
<signal name="file_activated" handler="_on_load_game" last_modification_time="Thu, 09 Feb 2006 19:41:14 GMT"/>
<signal name="selection_changed" handler="_on_file_changed" last_modification_time="Thu, 09 Feb 2006 19:41:29 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

413
glade/network_game.glade Normal file
View file

@ -0,0 +1,413 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkDialog" id="network_game_dialog">
<property name="width_request">300</property>
<property name="height_request">300</property>
<property name="title" translatable="yes">Waiting for players</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="has_separator">True</property>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox4">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area4">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="ng_network_game_cancel_button">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
</widget>
</child>
<child>
<widget class="GtkButton" id="ng_network_game_ready_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">0</property>
<child>
<widget class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">0</property>
<property name="yscale">0</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">0</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkHBox" id="hbox12">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">2</property>
<child>
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-go-forward</property>
<property name="icon_size">4</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label32">
<property name="visible">True</property>
<property name="label" translatable="yes">Ready</property>
<property name="use_underline">True</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox17">
<property name="border_width">12</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">12</property>
<child>
<widget class="GtkFrame" id="frame1">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="label_yalign">0.5</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<child>
<widget class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">12</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">6</property>
<property name="column_spacing">6</property>
<child>
<widget class="GtkLabel" id="label33">
<property name="visible">True</property>
<property name="label" translatable="yes">White Player:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label34">
<property name="visible">True</property>
<property name="label" translatable="yes">Black Player:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="ng_network_game_white_combo">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="add_tearoffs">False</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options">fill</property>
</packing>
</child>
<child>
<widget class="GtkComboBox" id="ng_network_game_black_combo">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="add_tearoffs">False</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options">fill</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label33">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Players&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="label_yalign">0.5</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<child>
<widget class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xscale">1</property>
<property name="yscale">1</property>
<property name="top_padding">0</property>
<property name="bottom_padding">0</property>
<property name="left_padding">12</property>
<property name="right_padding">0</property>
<child>
<widget class="GtkVBox" id="vbox18">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">6</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow3">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTextView" id="ng_network_chat_text">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_NONE</property>
<property name="cursor_visible">False</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">0</property>
<property name="right_margin">0</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="ng_network_game_chat_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">*</property>
<property name="activates_default">False</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label35">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Status / Chat&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

1230
glade/new_game.glade Normal file

File diff suppressed because it is too large Load diff

80
glade/save_game.glade Normal file
View file

@ -0,0 +1,80 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkFileChooserDialog" id="dialog">
<property name="visible">True</property>
<property name="action">GTK_FILE_CHOOSER_ACTION_SAVE</property>
<property name="local_only">True</property>
<property name="select_multiple">False</property>
<property name="show_hidden">False</property>
<property name="do_overwrite_confirmation">False</property>
<property name="title" translatable="yes">Save chess game</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="icon">glchess.svg</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<signal name="close" handler="_on_close" last_modification_time="Thu, 09 Feb 2006 20:10:44 GMT"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">24</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="button6">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-6</property>
<signal name="clicked" handler="_on_close" last_modification_time="Thu, 09 Feb 2006 20:10:20 GMT"/>
</widget>
</child>
<child>
<widget class="GtkButton" id="button7">
<property name="visible">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-save</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<property name="response_id">-5</property>
<signal name="clicked" handler="_on_save" last_modification_time="Thu, 09 Feb 2006 20:10:28 GMT"/>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

15
glchess.desktop.in Normal file
View file

@ -0,0 +1,15 @@
[Desktop Entry]
_Name=Chess
_Comment=Play the classic two-player boardgame of glChess
Version=1.0
Encoding=UTF-8
Exec=glchess
Terminal=false
Type=Application
Categories=GNOME;Application;Game;BoardGame;
StartupNotify=true
Icon=glchess
MimeType=application/x-chess-pgn;
GenericName=3D Chess Game
GenericName[pt_BR]=Jogo de Xadrez 3D
GenericName[de]=3D Schach

7
help/C/Makefile.am Normal file
View file

@ -0,0 +1,7 @@
docname = glchess
lang = C
helpdir = $(datadir)/gnome/help/$(docname)/$(lang)
help_DATA = glchess.xml legal.xml
EXTRA_DIST = $(help_DATA)
EXTRA_DIST += $(helpfig_DATA)

24
help/C/glchess-C.omf Normal file
View file

@ -0,0 +1,24 @@
<?xml version="1.0" standalone="no"?>
<omf>
<resource>
<title>
glChess Manual
</title>
<date>
2006-10-14
</date>
<version identifier="2.0" date="2002-05-05" description="Updated to full OMF format"/>
<subject category="GNOME|Games"/>
<description>
glChess
</description>
<type>
user's guide
</type>
<format mime="text/xml" dtd="-//OASIS//DTD DocBook XML V4.1.2//EN"/>
<identifier url="mahjongg.xml"/>
<language code="C"/>
<relation seriesid="67884844-600a-11d6-93a3-cf9f9ec0c847"/>
<rights type="GNU FDL" license.version="1.1" holder="Eric Baudais"/>
</resource>
</omf>

15
help/C/glchess.xml Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[
]
>
<article>
<articleinfo>
<title>glChess documentation</title>
<pubdate>April 17th, 2004</pubdate>
</articleinfo>
<para>
TODO!
</para>
</article>

76
help/C/legal.xml Normal file
View file

@ -0,0 +1,76 @@
<legalnotice id="legalnotice">
<para>
Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation
License (GFDL), Version 1.1 or any later version published
by the Free Software Foundation with no Invariant Sections,
no Front-Cover Texts, and no Back-Cover Texts. You can find
a copy of the GFDL at this <ulink type="help"
url="ghelp:fdl">link</ulink> or in the file COPYING-DOCS
distributed with this manual.
</para>
<para> This manual is part of a collection of GNOME manuals
distributed under the GFDL. If you want to distribute this
manual separately from the collection, you can do so by
adding a copy of the license to the manual, as described in
section 6 of the license.
</para>
<para>
Many of the names used by companies to distinguish their
products and services are claimed as trademarks. Where those
names appear in any GNOME documentation, and the members of
the GNOME Documentation Project are made aware of those
trademarks, then the names are in capital letters or initial
capital letters.
</para>
<para>
DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT ARE PROVIDED
UNDER THE TERMS OF THE GNU FREE DOCUMENTATION LICENSE
WITH THE FURTHER UNDERSTANDING THAT:
<orderedlist>
<listitem>
<para>DOCUMENT IS PROVIDED ON AN "AS IS" BASIS,
WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR
IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
THAT THE DOCUMENT OR MODIFIED VERSION OF THE
DOCUMENT IS FREE OF DEFECTS MERCHANTABLE, FIT FOR
A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE
RISK AS TO THE QUALITY, ACCURACY, AND PERFORMANCE
OF THE DOCUMENT OR MODIFIED VERSION OF THE
DOCUMENT IS WITH YOU. SHOULD ANY DOCUMENT OR
MODIFIED VERSION PROVE DEFECTIVE IN ANY RESPECT,
YOU (NOT THE INITIAL WRITER, AUTHOR OR ANY
CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS
LICENSE. NO USE OF ANY DOCUMENT OR MODIFIED
VERSION OF THE DOCUMENT IS AUTHORIZED HEREUNDER
EXCEPT UNDER THIS DISCLAIMER; AND
</para>
</listitem>
<listitem>
<para>UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL
THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE),
CONTRACT, OR OTHERWISE, SHALL THE AUTHOR,
INITIAL WRITER, ANY CONTRIBUTOR, OR ANY
DISTRIBUTOR OF THE DOCUMENT OR MODIFIED VERSION
OF THE DOCUMENT, OR ANY SUPPLIER OF ANY OF SUCH
PARTIES, BE LIABLE TO ANY PERSON FOR ANY
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES OF ANY CHARACTER
INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS
OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR
MALFUNCTION, OR ANY AND ALL OTHER DAMAGES OR
LOSSES ARISING OUT OF OR RELATING TO USE OF THE
DOCUMENT AND MODIFIED VERSIONS OF THE DOCUMENT,
EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF
THE POSSIBILITY OF SUCH DAMAGES.
</para>
</listitem>
</orderedlist>
</para>
</legalnotice>

1
help/Makefile.am Normal file
View file

@ -0,0 +1 @@
SUBDIRS = C

8
mime/glchess.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-chess-pgn">
<sub-class-of type="text/plain"/>
<comment>PGN chess game</comment>
<glob pattern="*.pgn"/>
</mime-type>
</mime-info>

63
setup.py Normal file
View file

@ -0,0 +1,63 @@
import os
import glob
from distutils.core import setup
DESCRIPTION = """glChess is an open source 3D chess interface for the Gnome desktop.
It is designed to be used by both beginner and experienced players.
Games can be played between a combination of local players, players connected via a LAN and artificial intelligences.
"""
CLASSIFIERS = ['License :: OSI-Approved Open Source :: GNU General Public License (GPL)',
'Intended Audience :: by End-User Class :: End Users/Desktop',
'Development Status :: 4 - Beta',
'Topic :: Desktop Environment :: Gnome',
'Topic :: Games/Entertainment :: Board Games',
'Programming Language :: Python',
'Operating System :: Grouping and Descriptive Categories :: All POSIX (Linux/BSD/UNIX-like OSes)',
'Operating System :: Modern (Vendor-Supported) Desktop Operating Systems :: Linux',
'User Interface :: Graphical :: Gnome',
'User Interface :: Graphical :: OpenGL',
'User Interface :: Toolkits/Libraries :: GTK+',
'Translations :: English', 'Translations :: German', 'Translations :: Italian']
DATA_FILES = []
# MIME files
DATA_FILES.append(('share/mime/packages', ['mime/glchess.xml']))
# UI files
DATA_FILES.append(('share/games/glchess/gui', ['glchess.svg'] + glob.glob('lib/glchess/gtkui/*.glade')))
# Config files
DATA_FILES.append(('share/games/glchess/', ['ai.xml']))
# Texture files
TEXTURES = []
for file in ['board.png', 'piece.png']:
TEXTURES.append('textures/' + file)
DATA_FILES.append(('share/games/glchess/textures', TEXTURES))
DATA_FILES.append(('share/applications', ['glchess.desktop']))
DATA_FILES.append(('share/pixmaps', ['glchess.svg']))
# Language files
#for poFile in glob.glob('po/*.po'):
# language = poFile[3:-3]
# DATA_FILES.append(('share/locale/' + language + '/LC_MESSAGES', poFile))
#print DATA_FILES
setup(name = 'glchess',
version = '1.0RC1',
classifiers = CLASSIFIERS,
description = '3D Chess Interface',
long_description = DESCRIPTION,
author = 'Robert Ancell',
author_email = 'bob27@users.sourceforge.net',
license = 'GPL',
url = 'http://glchess.sourceforge.net',
download_url = 'http://sourceforge.net/project/showfiles.php?group_id=6348',
package_dir = {'': 'lib'},
packages = ['glchess', 'glchess.chess', 'glchess.scene', 'glchess.scene.cairo', 'glchess.scene.opengl', 'glchess.ui', 'glchess.gtkui', 'glchess.network'],
data_files = DATA_FILES,
scripts = ['glchess'])

1
src/Makefile.am Normal file
View file

@ -0,0 +1 @@
SUBDIRS = lib

10
src/glchess Executable file
View file

@ -0,0 +1,10 @@
#! /usr/bin/env python
try:
from glchess.glchess import start_game
except ImportError:
import sys
sys.path.append('/usr/share/glchess/')
from glchess.glchess import start_game
start_game()

12
src/lib/Makefile.am Normal file
View file

@ -0,0 +1,12 @@
SUBDIRS = chess gtkui network scene ui
glchessdir = $(pythondir)/glchess
glchess_PYTHON = \
ai.py \
cecp.py \
defaults.py \
game.py \
glchess.py \
__init__.py \
main.py \
uci.py

1
src/lib/__init__.py Normal file
View file

@ -0,0 +1 @@

335
src/lib/ai.py Normal file
View file

@ -0,0 +1,335 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import os
import sys
import select
import xml.dom.minidom
import game
import cecp
import uci
from defaults import *
CECP = 'CECP'
UCI = 'UCI'
class Option:
value = ''
pass
class Profile:
"""
"""
name = ''
protocol = ''
executable = ''
path = ''
arguments = None
options = None
def __init__(self):
self.arguments = []
self.options = []
def detect(self):
"""
"""
try:
path = os.environ['PATH'].split(os.pathsep)
except KeyError:
path = []
for directory in path:
b = directory + os.sep + self.executable
if os.path.isfile(b):
self.path = b
return
self.path = None
def _getXMLText(node):
"""
"""
if len(node.childNodes) == 0:
return ''
if len(node.childNodes) > 1 or node.childNodes[0].nodeType != node.TEXT_NODE:
raise ValueError
return node.childNodes[0].nodeValue
def loadProfiles():
"""
"""
profiles = []
fileNames = [os.path.expanduser('~/.glchess/ai.xml'), os.path.join(BASE_DIR,'ai.xml'), 'ai.xml']
document = None
for f in fileNames:
try:
document = xml.dom.minidom.parse(f)
except IOError:
pass
except xml.parsers.expat.ExpatError:
print 'AI configuration at ' + f + ' is invalid, ignoring'
else:
print 'Loading AI configuration from ' + f
break
if document is None:
print 'WARNING: No AI configuration'
return profiles
elements = document.getElementsByTagName('aiconfig')
if len(elements) == 0:
return profiles
for e in elements:
for p in e.getElementsByTagName('ai'):
try:
protocolName = p.attributes['type'].nodeValue
except KeyError:
assert(False)
if protocolName == 'cecp':
protocol = CECP
elif protocolName == 'uci':
protocol = UCI
else:
assert(False), 'Uknown AI type: ' + repr(protocolName)
n = p.getElementsByTagName('name')
assert(len(n) > 0)
name = _getXMLText(n[0])
n = p.getElementsByTagName('binary')
assert(len(n) > 0)
executable = _getXMLText(n[0])
arguments = [executable]
n = p.getElementsByTagName('argument')
for x in n:
args.append(_getXMLText(x))
options = []
n = p.getElementsByTagName('option')
for x in n:
option = Option()
option.value = _getXMLText(x)
try:
attribute = x.attributes['name']
except KeyError:
pass
else:
option.name = _getXMLText(attribute)
options.append(option)
profile = Profile()
profile.name = name
profile.protocol = protocol
profile.executable = executable
profile.arguments = arguments
profile.options = options
profiles.append(profile)
return profiles
class CECPConnection(cecp.Connection):
"""
"""
player = None
def __init__(self, player):
"""
"""
self.player = player
cecp.Connection.__init__(self)
def onOutgoingData(self, data):
"""Called by cecp.Connection"""
self.player.logText(data, 'output')
self.player.sendToEngine(data)
def onMove(self, move):
"""Called by cecp.Connection"""
self.player.moving = True
self.player.move(move)
def logText(self, text, style):
"""Called by cecp.Connection"""
self.player.logText(text, style)
class UCIConnection(uci.StateMachine):
"""
"""
player = None
def __init__(self, player):
"""
"""
self.player = player
uci.StateMachine.__init__(self)
def onOutgoingData(self, data):
"""Called by uci.StateMachine"""
self.player.logText(data, 'output')
self.player.sendToEngine(data)
def logText(self, text, style):
"""Called by uci.StateMachine"""
self.player.logText(text, style)
def onMove(self, move):
"""Called by uci.StateMachine"""
self.player.move(move)
class Player(game.ChessPlayer):
"""
"""
# The profile we are using
__profile = None
__pipeFromEngine = None
__pipeToEngine = None
# PID of the subprocess containing the engine
__pid = None
__connection = None
moving = False
def __init__(self, name, profile):
"""Constructor for an AI player.
'name' is the name of the player (string).
'profile' is the profile to use for the AI (Profile).
"""
self.__profile = profile
game.ChessPlayer.__init__(self, name)
# Create pipes for communication with AI process
self.__pipeToEngine = os.pipe()
self.__pipeFromEngine = os.pipe()
# Fork sub-process for engine
self.__pid = os.fork()
# This is the forked process, replace it with the engine
if self.__pid == 0:
self.__startEngine(profile.path, profile.arguments)
# Close the ends of the pipe we are not using
os.close(self.__pipeFromEngine[1]);
os.close(self.__pipeToEngine[0]);
if profile.protocol == CECP:
self.connection = CECPConnection(self)
elif profile.protocol == UCI:
self.connection = UCIConnection(self)
else:
assert(False)
self.connection.start()
self.connection.configure(profile.options)
# Methods to extend
def logText(self, text, style):
"""
"""
pass
# Public methods
def getProfile(self):
"""
"""
return self.__profile
def fileno(self):
"""Returns the file descriptor for communicating with the engine (integer)"""
return self.__pipeFromEngine[0]
def read(self):
"""Read an process data from the engine.
This is non-blocking.
"""
while True:
# Check if data is available
(rlist, _, _) = select.select([self.__pipeFromEngine[0]], [], [], 0)
if len(rlist) == 0:
return
# Read a chunk and process
data = os.read(self.__pipeFromEngine[0], 256)
if data == '':
return
self.connection.registerIncomingData(data)
def sendToEngine(self, data):
"""
"""
try:
os.write(self.__pipeToEngine[1], data)
except OSError, e:
print 'Failed to write to engine: ' + str(e)
def quit(self):
"""Disconnect the AI"""
# Wait for the pipe to close
# There must be a better way of doing this!
count = 0
while True:
select.select([], [], [], 0.1)
try:
os.write(self.__pipeToEngine[1], '\nquit\n') # FIXME: CECP specific
except OSError:
return
count += 1
if count > 5:
break
print 'Killing AI ' + str(self.__pid)
os.kill(self.__pid, 9)
# Extended methods
def onPlayerMoved(self, player, move):
"""Called by game.ChessPlayer"""
isSelf = player is self and self.moving
self.moving = False
self.connection.reportMove(move.canMove, isSelf)
def readyToMove(self):
"""Called by game.ChessPlayer"""
self.connection.requestMove()
def onGameEnded(self, winningPlayer = None):
"""Called by game.ChessPlayer"""
self.quit()
# Private methods
def __startEngine(self, executable, arguments):
"""
"""
# Connect stdin and stdout to the pipes to the main process
os.dup2(self.__pipeFromEngine[1], sys.stdout.fileno())
os.dup2(self.__pipeToEngine[0], sys.stdin.fileno())
# Close the ends of the pipe we are not using
os.close(self.__pipeFromEngine[0]);
os.close(self.__pipeToEngine[1]);
# Make the process nice so it doesn't hog the CPU
os.nice(15)
# Start the AI
try:
os.execv(executable, arguments)
except OSError:
select.select([], [], [], None)
os._exit(os.EX_UNAVAILABLE)

218
src/lib/cecp.py Normal file
View file

@ -0,0 +1,218 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
class CECPProtocol:
"""CECP protocol en/decoder.
"""
# Data being accumulated to be parsed
__buffer = ''
NEWLINE = '\n'
MOVE_PREFIXS = ['My move is: ', 'my move is ', 'move ']
INVALID_MOVE_PREFIX = 'Illegal move: '
RESIGN_PREFIX = 'tellics resign'
DRAW_PREFIX = '1/2-1/2'
def __init__(self):
"""
"""
# Go to simple interface mode
self.onOutgoingData('xboard\n')
# Methods to extend
def onOutgoingData(self, data):
"""Called when there is data to send to the CECP engine.
'data' is the data to give to the AI (string).
"""
print 'OUT: ' + repr(data)
def onUnknownLine(self, line):
"""Called when an unknown line is received from the CECP AI.
'line' is the line that has not been decoded (string). There is
no newline on the end of the string.
"""
print 'Unknown CECP line: ' + line
def onMove(self, move):
"""Called when the AI makes a move.
'move' is the move the AI has decided to make (string).
"""
print 'CECP move: ' + move
def onIllegalMove(self, move):
"""Called when the AI rejects a move.
'move' is the move the AI rejected (string).
"""
print 'CECP illegal move: ' + move
def onResign(self):
"""Called when the AI resigns"""
print 'CECP AI resigns'
# Public methods
def sendSetSearchDepth(self, searchDepth):
"""Set the search depth for the AI.
'searchDepth' is the number of moves to look ahead (integer).
"""
# This is the CECP specified method
self.onOutgoingData('sd %i\n' % int(searchDepth))
# GNUchess uses this instead
self.onOutgoingData('depth %i\n' % int(searchDepth))
def sendSetPondering(self, aiPonders):
"""Enable/disable AI pondering.
'aiPonders' is a flag to show if the AI thinks during opponent moves (True) or not (False).
"""
if aiPonders:
self.onOutgoingData('hard\n')
else:
self.onOutgoingData('easy\n')
def sendMove(self, move):
"""Move for the current player.
'move' is the move the current player has made (string).
"""
self.onOutgoingData(move + '\n')
def sendWait(self):
"""Stop the AI from automatically moving"""
self.onOutgoingData('force\n')
def sendMovePrompt(self):
"""Get the AI to move for the current player"""
self.onOutgoingData('go\n')
def sendQuit(self):
"""Quit the engine"""
# Send 'quit' starting with a newline in case there are some characters already sent
self.onOutgoingData('\nquit\n')
def registerIncomingData(self, data):
"""
"""
self.__buffer += data
self.__parseData()
# Private methods
def __parseData(self):
while True:
index = self.__buffer.find(self.NEWLINE)
if index < 0:
return
line = self.__buffer[:index]
self.__buffer = self.__buffer[index+1:]
self.__parseLine(line)
def __parseLine(self, line):
for prefix in self.MOVE_PREFIXS:
if line.startswith(prefix):
move = line[len(prefix):]
self.logText(line + '\n', 'move')
self.onMove(move.strip())
return
if line.startswith(self.INVALID_MOVE_PREFIX):
self.onIllegalMove(line[len(self.INVALID_MOVE_PREFIX):])
elif line.startswith(self.RESIGN_PREFIX):
self.onResign()
elif line.startswith(self.DRAW_PREFIX):
print 'AI calls a draw'
else:
self.onUnknownLine(line)
self.logText(line + '\n', 'input')
import select
class Connection(CECPProtocol):
"""
"""
def __init__(self):
"""
"""
# Start protocol
CECPProtocol.__init__(self)
# Methods to extend
def logText(self, text, style):
"""FIXME: define style
"""
pass
def onMove(self, move):
"""Called when the AI makes a move.
'move' is the move the AI made (string).
"""
print 'AI moves: ' + move
# Public methods
def start(self):
"""
"""
pass
def configure(self, options):
"""
"""
for option in options:
self.onOutgoingData(option.value + '\n')
def requestMove(self):
"""Request the AI moves for the current player"""
# Prompt the AI to move
self.sendMovePrompt()
def reportMove(self, move, isSelf):
"""Report the move the current player has made.
'move' is the move to report (string).
'isSelf' is a flag to say if the move is the move this AI made (True).
"""
# Don't report the move we made
if isSelf:
return
# Stop the AI from automatically moving
self.sendWait()
# Report the move
self.sendMove(move)
# Private methods
def onUnknownLine(self, line):
"""Called by CECPProtocol"""
pass#print 'Unknown CECP line: ' + line
def onIllegalMove(self, move):
"""Called by CECPProtocol"""
print 'CECP illegal move: ' + move
if __name__ == '__main__':
c = CECPConnection('gnuchess')
while True:
c.read()

View file

@ -0,0 +1,7 @@
glchessdir = $(pythondir)/glchess/chess
glchess_PYTHON = \
board.py \
__init__.py \
lan.py \
pgn.py \
san.py

View file

@ -0,0 +1,4 @@
import board
import pgn
import lan
import san

1025
src/lib/chess/board.py Normal file

File diff suppressed because it is too large Load diff

178
src/lib/chess/lan.py Normal file
View file

@ -0,0 +1,178 @@
"""
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import board
CHECK = '+'
CHECKMATE = '#'
# Notation for takes
MOVE = '-'
TAKE = 'x'
# Castling moves
CASTLE_SHORT = 'O-O'
CASTLE_LONG = 'O-O-O'
# Characters used to describe pieces
_typeToLAN = {board.PAWN: 'P',
board.KNIGHT: 'N',
board.BISHOP: 'B',
board.ROOK: 'R',
board.QUEEN: 'Q',
board.KING: 'K'}
_lanToType = {}
for (pieceType, character) in _typeToLAN.iteritems():
_lanToType[character] = pieceType
class DecodeError(Exception):
"""
"""
pass
def _checkLocation(location):
"""
"""
if len(location) != 2:
raise DecodeError('Invalid length location')
if location[0] < 'a' or location[0] > 'h':
raise DecodeError('Invalid rank')
if location[1] < '0' or location[1] > '8':
raise DecodeError('Invalid file')
return location
def decode(colour, move):
"""Decode a long algebraic format move.
'colour' is the colour of the player making the move (board.WHITE or board.BLACK).
'move' is the move description (string).
Returns a tuple containing (start, end, piece, moveType, promotionType, result)
'start' is the location being moved from (string, e.g. 'a1', 'h8').
'end' is the location being moved to (string, e.g. 'a1', 'h8').
'piece' is the piece being moved (board.PAWN, board.ROOK, ... or None if not specified).
'moveType' is a flag to show if this move takes an oppoenent piece (MOVE, TAKE or None if not specified).
'promotionType' is the piece type to promote to (board.ROOK, board.KNIGHT, ... or None if not specified).
'check' is the result after the move (CHECK, CHECKMATE or None if not specified).
Raises DecodeError if the move is unable to be decoded.
"""
pieceType = None
promotionType = None
moveType = None
result = None
# FIXME: Get 'result' from the end of the move description
if colour is board.WHITE:
baseFile = '1'
else:
baseFile = '8'
if move == CASTLE_SHORT:
return ('e' + baseFile, 'g' + baseFile, None, None, None, None)
elif move == CASTLE_LONG:
return ('e' + baseFile, 'c' + baseFile, None, None, None, None)
# First character can be the piece types
if len(move) < 1:
raise DecodeError('Too short')
try:
pieceType = _lanToType[move[0]]
except KeyError:
pieceType = None
else:
move = move[1:]
if len(move) < 2:
raise DecodeError('Too short')
start = _checkLocation(move[:2])
move = move[2:]
if len(move) < 1:
raise DecodeError('Too short')
if move[0] == MOVE or move[0] == TAKE:
moveType = move[0]
move = move[1:]
if len(move) < 2:
raise DecodeError('Too short')
end = _checkLocation(move[:2])
move = move[2:]
# Look for promotion type, note this can be in upper or lower case
if len(move) > 0:
if move[0] == '=':
if len(move) < 2:
raise DecodeError('Too short')
try:
promotionType = _lanToType[move[1].upper()]
except KeyError:
raise DecodeError('Unknown promotion type')
move = move[2:]
else:
try:
promotionType = _lanToType[move[0].upper()]
except KeyError:
pass
else:
move = move[1:]
if len(move) > 0:
if move[0] == CHECK or move[0] == CHECKMATE:
result = move[0]
move = move[1:]
if len(move) != 0:
raise DecodeError('Extra characters')
return (start, end, pieceType, moveType, promotionType, result)
def encode(colour, start, end, piece = None, moveType = None, promotionType = None, result = None):
"""Encode a long algebraic format move.
'start' is the location being moved from (string, e.g. 'a1', 'h8').
'end' is the location being moved to (string, e.g. 'a1', 'h8').
'piece' is the piece being moved (board.PAWN, board.ROOK, ... or None if not specified).
'moveType' is a flag to show if this move takes an oppoenent piece (MOVE, TAKE or None if not specified).
'promotionType' is the piece type to promote to (board.ROOK, board.KNIGHT, ... or None if not specified).
'check' is the result after the move (CHECK, CHECKMATE or None if not specified).
Returns a string describing this move.
"""
try:
_checkLocation(start)
_checkLocation(end)
except DecodeError:
raise TypeError("Invalid values for 'start' and 'end'")
string = ''
# Report the piece being moved
if piece is not None:
string += _typeToLAN[piece]
# Report the source location
string += start
# Report if this is a move or a take
if moveType != None:
string += moveType
# Report the target location
string += end
# Report the promotion type
# FIXME: Only report if a pawn promotion
if promotionType != None:
if False: # FIXME: What to name this flag?
string += '='
string += _typeToLAN[promotionType].lower()
# Report the check result
if result is not None:
string += result
return string

686
src/lib/chess/pgn.py Normal file
View file

@ -0,0 +1,686 @@
"""
Implement a PGN reader/writer.
See http://www.chessclub.com/help/PGN-spec
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
"""
; Example PGN file
[Event "F/S Return Match"]
[Site "Belgrade, Serbia JUG"]
[Date "1992.11.04"]
[Round "29"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1/2-1/2"]
1. e4 e5 2. Nf3 Nc6 3. Bb5 a6 4. Ba4 Nf6 5. O-O Be7 6. Re1 b5 7. Bb3 d6 8. c3
O-O 9. h3 Nb8 10. d4 Nbd7 11. c4 c6 12. cxb5 axb5 13. Nc3 Bb7 14. Bg5 b4 15.
Nb1 h6 16. Bh4 c5 17. dxe5 Nxe4 18. Bxe7 Qxe7 19. exd6 Qf6 20. Nbd2 Nxd6 21.
Nc4 Nxc4 22. Bxc4 Nb6 23. Ne5 Rae8 24. Bxf7+ Rxf7 25. Nxf7 Rxe1+ 26. Qxe1 Kxf7
27. Qe3 Qg5 28. Qxg5 hxg5 29. b3 Ke6 30. a3 Kd6 31. axb4 cxb4 32. Ra5 Nd5 33.
f3 Bc8 34. Kf2 Bf5 35. Ra7 g6 36. Ra6+ Kc5 37. Ke1 Nf4 38. g3 Nxh3 39. Kd2 Kb5
40. Rd6 Kc5 41. Ra6 Nf2 42. g4 Bd3 43. Re6 1/2-1/2
"""
# Comments are bounded by ';' to '\n' or '{' to '}'
# Lines starting with '%' are ignored and are used as an extension mechanism
# Strings are bounded by '"' and '"' and quotes inside the strings are escaped with '\"'
class Error(Exception):
"""PGN exception class"""
__errorType = 'Unknown'
def __init__(self, error = None):
self.__errorType = error
Exception.__init__(self)
def __str__(self):
return repr(self.__errorType)
class PGNToken:
"""
"""
# Token types
LINE_COMMENT = 'Line comment'
COMMENT = 'Comment'
PERIOD = 'Period'
TAG_START = 'Tag start'
TAG_END = 'Tag end'
STRING = 'String'
SYMBOL = 'Symbol'
RAV_START = 'RAV start'
RAV_END = 'RAV end'
XML_START = 'XML start'
XML_END = 'XML end'
NAG = 'NAG'
type = None
SYMBOL_START_CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + '*'
SYMBOL_CONTINUATION_CHARACTERS = SYMBOL_START_CHARACTERS + '_+#=:-' + '/' # Not in spec but required from game draw and imcomplete
NAG_CONTINUATION_CHARACTERS = '0123456789'
GAME_TERMINATE_INCOMPLETE = '*'
GAME_TERMINATE_WHITE_WIN = '1-0'
GAME_TERMINATE_BLACK_WIN = '0-1'
GAME_TERMINATE_DRAW = '1/2-1/2'
data = None
lineNumber = -1
characterNumber = -1
def __init__(self, lineNumber, characterNumber, tokenType, data = None):
"""
"""
self.type = tokenType
self.data = data
self.lineNumber = lineNumber
self.characterNumber = characterNumber
def __str__(self):
string = self.type
if self.data is not None:
string += ': ' + self.data
return string
class PGNParser:
"""
"""
__inComment = False
__comment = ''
__startOffset = -1
def __extractPGNString(self, data):
#"""Extract a PGN string.
#'data' is the data to extract the string from (string). It must start with a quote character '"'.
#Return a tuple containing the first PGN string and the number of characters of data it required.
#e.g. '"Mike \"Dog\" Smith"' -> ('Mike "Dog" Smith', 20).
#If no string is found a Error is raised.
#"""
if data[0] != '"':
raise Error('PGN string does not start with "')
offset = 1
escaped = False
while True:
try:
c = data[offset]
escaped = (c == '\\')
if c == '"' and escaped is False:
pgnString = data[1:offset]
pgnString.replace('\\"', '"')
return (pgnString, offset + 1)
except IndexError:
raise Error('Unterminated PGN string')
offset += 1
def parseLine(self, line, lineNumber):
"""TODO
Return an array of tokens extracted from the line.
"""
tokens = []
inSymbol = False
inNAG = False
symbol = ''
nag = ''
offset = 0
while offset < len(line):
c = line[offset]
if self.__inComment is True:
if c == '}':
tokens.append(PGNToken(lineNumber, self.__startOffset, PGNToken.LINE_COMMENT, self.__comment))
self.__inComment = False
else:
self.__comment += c
offset += 1
continue
if inSymbol:
if PGNToken.SYMBOL_CONTINUATION_CHARACTERS.find(c) >= 0:
symbol += c
offset += 1
continue
else:
tokens.append(PGNToken(lineNumber, self.__startOffset, PGNToken.SYMBOL, symbol))
inSymbol = False
elif inNAG:
if PGNToken.NAG_CONTINUATION_CHARACTERS.find(c) >= 0:
symbol += c
offset += 1
continue
else:
# FIXME: Should be at least one character
tokens.append(PGNToken(lineNumber, self.__startOffset, PGNToken.NAG, nag))
inNAG = False
if c.isspace():
pass
elif c == ';':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.LINE_COMMENT, line[offset:]))
return tokens
elif c == '{':
self.__comment = ''
self.__inComment = True
self.__startOffset = offset
elif c == '.':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.PERIOD))
elif c == '[':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.TAG_START))
elif c == ']':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.TAG_END))
elif c == '"':
(string, newOffset) = self.__extractPGNString(line[offset:])
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.STRING, string))
offset += newOffset
continue
elif c == '(':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.RAV_START))
elif c == ')':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.RAV_END))
elif c == '<':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.XML_START))
elif c == '>':
tokens.append(PGNToken(lineNumber, offset+1, PGNToken.XML_END))
elif c == '$':
inNAG = True
self.__startOffset = offset
elif PGNToken.SYMBOL_START_CHARACTERS.find(c) >= 0:
symbol = c
inSymbol = True
self.__startOffset = offset
else:
raise Error('Unknown character ' + repr(c))
offset += 1
# Complete any symbols or NAGs
if inSymbol:
tokens.append(PGNToken(lineNumber, self.__startOffset+1, PGNToken.SYMBOL, symbol))
if inNAG:
# FIXME: Must be 1 or more char..
tokens.append(PGNToken(lineNumber, self.__startOffset+1, PGNToken.NAG, nag))
return tokens
def endParse(self):
pass
class PGNGameParser:
"""
"""
STATE_IDLE = 'IDLE'
STATE_TAG_NAME = 'TAG_NAME'
STATE_TAG_VALUE = 'TAG_VALUE'
STATE_TAG_END = 'TAG_END'
STATE_MOVETEXT = 'MOVETEXT'
STATE_RAV = 'RAV'
STATE_XML = 'XML'
__state = STATE_IDLE
# The game being assembled
__game = None
# The tag being assembled
__tagName = None
__tagValue = None
# The move number being decoded
__expectedMoveNumber = 0
__lastTokenIsMoveNumber = False
# The last white move
__whiteMove = None
# The Recursive Annotation Variation (RAV) stack
__ravDepth = 0
def __parseTokenMovetext(self, token):
"""
"""
if token.type is PGNToken.RAV_START:
self.__ravDepth += 1
# FIXME: Check for RAV errors
return
elif token.type is PGNToken.RAV_END:
self.__ravDepth -= 1
# FIXME: Check for RAV errors
return
# Ignore tokens inside RAV
if self.__ravDepth != 0:
return
if token.type is PGNToken.PERIOD:
if self.__lastTokenIsMoveNumber is False:
raise Error('Unexpected period on line ' + str(token.lineNumber) + ':' + str(token.characterNumber))
elif token.type is PGNToken.SYMBOL:
# See if this is a game terminate
if token.data == PGNToken.GAME_TERMINATE_INCOMPLETE or \
token.data == PGNToken.GAME_TERMINATE_WHITE_WIN or \
token.data == PGNToken.GAME_TERMINATE_BLACK_WIN or \
token.data == PGNToken.GAME_TERMINATE_DRAW:
# Complete any half moves
if self.__whiteMove is not None:
self.__game.addMove(self.__whiteMove, None)
game = self.__game
self.__game = None
return game
# Otherwise it is a move number or a move
else:
# See if this is a move number or a SAN move
try:
moveNumber = int(token.data)
self.__lastTokenIsMoveNumber = True
if moveNumber != self.__expectedMoveNumber:
raise Error('Expected move number ' + str(self.__expectedMoveNumber) + ', got ' + str(moveNumber) + ' on line ' + str(token.lineNumber) + ':' + str(token.characterNumber))
except ValueError:
self.__lastTokenIsMoveNumber = False
if self.__whiteMove is None:
self.__whiteMove = token.data
else:
self.__game.addMove(self.__whiteMove, token.data)
self.__whiteMove = None
self.__expectedMoveNumber += 1
elif token.type is PGNToken.NAG:
pass
else:
raise Error('Unknown token ' + token.type + ' in movetext on line ' + str(token.lineNumber) + ':' + str(token.characterNumber))
def parseToken(self, token):
"""TODO
Return a game object if a game is complete otherwise None.
"""
# Ignore all comments at any time
if token.type is PGNToken.LINE_COMMENT or token.type is PGNToken.COMMENT:
return None
if self.__state is self.STATE_IDLE:
if self.__game is None:
self.__game = PGNGame()
if token.type is PGNToken.TAG_START:
self.__state = self.STATE_TAG_NAME
return
elif token.type is PGNToken.SYMBOL:
self.__expectedMoveNumber = 1
self.__whiteMove = None
self.__lastTokenIsMoveNumber = False
self.__ravDepth = 0
self.__state = self.STATE_MOVETEXT
else:
raise Error('Unexpected token ' + token.type + ' on line ' + str(token.lineNumber) + ':' + str(token.characterNumber))
if self.__state is self.STATE_TAG_NAME:
if token.type is PGNToken.SYMBOL:
self.__tagName = token.data
self.__state = self.STATE_TAG_VALUE
else:
raise Error()
elif self.__state is self.STATE_TAG_VALUE:
if token.type is PGNToken.STRING:
self.__tagValue = token.data
self.__state = self.STATE_TAG_END
else:
raise Error()
elif self.__state is self.STATE_TAG_END:
if token.type is PGNToken.TAG_END:
self.__game.setTag(self.__tagName, self.__tagValue)
self.__state = self.STATE_IDLE
else:
raise Error()
elif self.__state is self.STATE_MOVETEXT:
game = self.__parseTokenMovetext(token)
if game is not None:
self.__state = self.STATE_IDLE
return game
def complete(self):
"""
"""
pass
# Raise an error if there was a partial game
#raise Error()
class PGNGame:
"""
"""
"""The required tags in a PGN file (the seven tag roster, STR)"""
PGN_TAG_EVENT = 'Event'
PGN_TAG_SITE = 'Site'
PGN_TAG_DATE = 'Date'
PGN_TAG_ROUND = 'Round'
PGN_TAG_WHITE = 'White'
PGN_TAG_BLACK = 'Black'
PGN_TAG_RESULT = 'Result'
# The seven tag roster in the required order (REFERENCE)
__strTags = [PGN_TAG_EVENT, PGN_TAG_SITE, PGN_TAG_DATE, PGN_TAG_ROUND, PGN_TAG_WHITE, PGN_TAG_BLACK, PGN_TAG_RESULT]
# The tags in this game
__tagsByName = None
__moves = None
def __init__(self):
# Set the default STR tags
self.__tagsByName = {}
self.setTag(self.PGN_TAG_EVENT, '?')
self.setTag(self.PGN_TAG_SITE, '?')
self.setTag(self.PGN_TAG_DATE, '????.??.??')
self.setTag(self.PGN_TAG_ROUND, '?')
self.setTag(self.PGN_TAG_WHITE, '?')
self.setTag(self.PGN_TAG_BLACK, '?')
self.setTag(self.PGN_TAG_RESULT, '*')
self.__moves = []
def getLines(self):
lines = []
# Get the names of the non STR tags
otherTags = list(set(self.__tagsByName).difference(self.__strTags))
# Write seven tag roster and the additional tags
for name in self.__strTags + otherTags:
value = self.__tagsByName[name]
lines.append('['+ name + ' ' + self.__makePGNString(value) + ']')
lines.append('')
# Insert numbers in-between moves
tokens = []
moveNumber = 1
for m in self.__moves:
tokens.append('%i.' % moveNumber)
moveNumber += 1
tokens.append(m[0])
if m[1] is not None:
tokens.append(m[1])
# Add result token to the end
tokens.append(self.__tagsByName[self.PGN_TAG_RESULT])
# Print moves keeping the line length to less than 256 characters (PGN requirement)
line = ''
for t in tokens:
if line == '':
x = t
else:
x = ' ' + t
if len(line) + len(x) >= 80: #>= 256:
lines.append(line)
line = t
else:
line += x
lines.append(line)
return lines
def setTag(self, name, value):
"""Set a PGN tag.
'name' is the name of the tag to set (string).
'value' is the value to set the tag to (string) or None to delete the tag.
Tag names cannot contain whitespace.
Deleting a tag that does not exist has no effect.
Deleting a STR tag or setting one to an invalid value will raise an Error exception.
"""
if self.__isValidTagName(name) is False:
raise Error(str(name) + ' is an invalid tag name')
# If no value delete
if value is None:
# If is a STR tag throw an exception
if self.__strTags.has_key(name):
raise Error(name + ' is a PGN STR tag and cannot be deleted')
# Delete the tag
try:
self.__strTags.pop(name)
except KeyError:
pass
# Otherwise set the tag to the new value
else:
# FIXME: Validate if it is a STR tag
self.__tagsByName[name] = value
def getTag(self, name):
"""Get a PGN tag.
'name' is the name of the tag to get (string).
Return the value of the tag (string) or None if the tag does not exist.
"""
try:
return self.__tagsByName[name]
except KeyError:
return None
def addMove(self, whiteMove, blackMove):
self.__moves.append((whiteMove, blackMove))
def getWhiteMove(self, moveNumber):
return self.__moves[moveNumber - 1][0]
def getBlackMove(self, moveNumber):
return self.__moves[moveNumber - 1][1]
def getMoves(self):
moves = []
for m in self.__moves:
moves.append(m[0])
if m[1] is not None:
moves.append(m[1])
return moves
def __str__(self):
string = ''
for tag, value in self.__tagsByName.iteritems():
string += tag + ' = ' + value + '\n'
string += '\n'
number = 1
for move in self.__moves:
string += '%3i. ' % number + str(move[0]) + ' ' + str(move[1]) + '\n'
number += 1
return string
# Private methods
def __makePGNString(self, string):
"""Make a PGN string.
'string' is the string to convert to a PGN string (string).
All characters are valid and quotes are escaped with '\"'.
Return the string surrounded with quotes. e.g. 'Mike "Dog" Smith' -> '"Mike \"Dog\" Smith"'
"""
pgnString = string
pgnString.replace('"', '\\"')
return '"' + pgnString + '"'
def __isValidTagName(self, name):
"""Valid a PGN tag name.
'name' is the tag name to validate (string).
Tags can only contain the characters, a-Z A-Z and _.
Return True if this is a valid tag name otherwise return False.
"""
if name is None or len(name) == 0:
return False
validCharacters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
for c in name:
if validCharacters.find(c) < 0:
return False
return True
class PGN:
"""
"""
__games = None
def __init__(self, fileName = None, maxGames = None):
"""Create a PGN reader/writer.
'fileName' is the file to load the PGN from or None to generate an empty PGN file.
'maxGames' is the maximum number of games to load from the file or None
to load the whole file. (int, Only applicable if a filename is supplied).
"""
self.__games = []
if fileName is not None:
self.__load(fileName, maxGames)
def addGame(self):
"""Add a new game to the PGN file.
Returns the PGNGame instance to modify"""
game = PGNGame()
self.__games.append(game)
return game
def getGame(self, index):
"""Get a game from the PGN file.
'index' is the game index to get (integer, 0-N).
Return this PGN game or raise an IndexError if no game with this index.
"""
return self.__games[index]
def save(self, fileName):
"""Save the PGN file.
'fileName' is the name of the file to save to.
"""
try:
f = file(fileName, 'w')
except IOError, e:
raise Error('Unable to write to PGN file: ' + str(e))
# FIXME: Set the newline characters to the correct type?
# Sign it from glChess
f.write('; PGN saved game generated by glChess\n')
f.write('; http://glchess.sourceforge.net\n')
for game in self.__games:
f.write('\n')
for line in game.getLines():
f.write(line + '\n')
f.close()
def __getitem__(self, index):
return self.__games[index]
def __getslice__(self, start, end):
return self.__games[start:end]
# Private methods
def __load(self, fileName, maxGames = None):
"""
"""
# Convert the file into PGN tokens
try:
f = file(fileName, 'r')
except IOError, e:
raise Error('Unable to open PGN file: ' + str(e))
p = PGNParser()
gp = PGNGameParser()
lineNumber = 1
gameCount = 0
while True:
# Read a line from the file
line = f.readline()
if line == '':
break
# Parse the line into tokens
tokens = p.parseLine(line, lineNumber)
# Decode the tokens into PGN games
for token in tokens:
game = gp.parseToken(token)
# Store this game and stop if only required to parse a certain number
if game is not None:
self.__games.append(game)
gameCount += 1
if maxGames is not None and gameCount >= maxGames:
break
# YUCK... FIXME
if maxGames is not None and gameCount >= maxGames:
break
lineNumber += 1
# Must be at least one game in the PGN file
if gameCount == 0:
raise Error('Empty PGN file')
# Tidy up
gp.complete()
p.endParse()
f.close()
if __name__ == '__main__':
def test(fileName, maxGames = None):
p = PGN(fileName, maxGames)
number = 1
games = p[:]
for game in games:
print 'Game ' + str(number)
print game
print
number += 1
test('example.pgn')
test('rav.pgn')
test('wolga-benko.pgn', 3)
p = PGN('example.pgn')
p.save('out.pgn')

456
src/lib/chess/san.py Normal file
View file

@ -0,0 +1,456 @@
"""
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
__all__ = ['SANConverter']
# Examples of SAN moves:
#
# f4 (pawn move to f4)
# fxg3 (pawn on file f takes opponent on g3)
# Qh5 (queen moves to h5)
# Qh5+ (queen moves to h5 and puts opponent into check)
# Ned4 (knight on file e moves to d4)
# gxh8=Q# (pawn on g7 takes opponent in h8 promotes to queen and puts oponent into checkmate (smooth!))
# Notation for takes
SAN_TAKE = 'x'
# Castling moves
SAN_CASTLE_SHORT = 'O-O'
SAN_CASTLE_LONG = 'O-O-O'
RANKS = 'abcdefgh'
FILES = '12345678'
# Suffixes
SAN_PROMOTE = '='
class Error(Exception):
"""
"""
# Properties of the error
__move = ''
__description = ''
def __init__(self, move, description):
"""Constructor for a SAN exception.
'move' is the SAN move that generated the exception (string).
'description' is the description of the exception that occured (string).
"""
self.__move = str(move)
self.__description = str(description)
Exception.__init__(self)
def __str__(self):
"""Convert the SAN exception to a string"""
return 'Error parsing SAN move ' + repr(self.__move) + ': ' + self.__description
class SANConverter:
"""
Define file and rank
"""
# Piece colours
WHITE = 'White'
BLACK = 'Black'
# SAN piece types
PAWN = 'P'
KNIGHT = 'N'
BISHOP = 'B'
ROOK = 'R'
QUEEN = 'Q'
KING = 'K'
__pieceTypes = PAWN + KNIGHT + BISHOP + ROOK + QUEEN + KING
# Valid promotion types
__promotionTypes = [PAWN, KNIGHT, BISHOP, ROOK, QUEEN]
# Move results
CHECK = '+'
CHECKMATE = '#'
def __init__(self):
"""Constructor"""
pass
# Methods to extend
def getPiece(self, location):
"""Get a piece from the chess board.
'location' is the location to get the piece from (string, e.g. 'a1', h8').
Return a tuple containing (colour, type) or None if no piece at this location.
"""
return None
def testMove(self, colour, start, end, promotionType, allowSuicide = False):
"""Test if a move is valid.
'colour' is the colour of the player making the move (self.WHITE or self.BLACK).
'start' is the board location to move from (string, e.g. 'a1', 'h8').
'end' is the board location to move to (string, e.g. 'a1', 'h8').
'promotionType' is the piece type to promote to (self.[PAWN|KNIGHT|BISHOP|ROOK|QUEEN]).
'allowSuicide' is a flag to show if the move should be disallowed (False) or
allowed (True) if it would put the moving player into check.
Return False if the move is dissallowed or
self.CHECK if the move puts the opponent into check or
self.CHECKMATE if the move puts the opponent into checkmate or
True if the move is allowed and does not put the opponent into check.
"""
pass
def decodeSAN(self, colour, san):
"""Decode a SAN move.
'colour' is the colour of the player making the move (self.WHITE or self.BLACK).
'san' is the SAN description of the move (string).
Returns the move this SAN describes in the form (start, end, promotionType).
'start' is the square to move from (string, e.g. 'a1', 'h8').
'end' is the square to move to (string, e.g. 'a1', 'h8').
'promotionType' is the piece to promote to (self.[KNIGHT|BISHOP|ROOK|QUEEN]).
If the move is invalid then an Error expection is raised.
"""
copy = san[:]
# Look for check hints
expectedResult = True
if copy[-1] == self.CHECK or copy[-1] == self.CHECKMATE:
expectedResult = copy[-1]
copy = copy[:-1]
# Extract promotions
promotionType = self.QUEEN
if copy[-2] == SAN_PROMOTE:
promotionType = copy[-1]
copy = copy[:-2]
try:
self.__promotionTypes.index(promotionType)
except ValueError:
raise Error(san, 'Invalid promotion type ' + promotionType)
# Check for castling moves
if colour is self.WHITE:
baseFile = '1'
else:
baseFile = '8'
# FIXME: Update moveResult and compare against expectedResult
if copy == SAN_CASTLE_SHORT:
return ('e' + baseFile, 'g' + baseFile, expectedResult, promotionType)
elif copy == SAN_CASTLE_LONG:
return ('e' + baseFile, 'c' + baseFile, expectedResult, promotionType)
# Get the destination (the last two characters before the suffix)
end = copy[-2:]
copy = copy[:-2]
if RANKS.find(end[0]) < 0 or FILES.find(end[1]) < 0:
raise Error(san, 'Invalid destination: ' + end)
# Check if is a take move (use try in case there are no more characters)
isTake = False
try:
if copy[-1] == SAN_TAKE:
isTake = True
copy = copy[:-1]
except:
pass
# The first character is the piece type (or pawn if not specified)
pieceType = self.PAWN
if len(copy) > 0:
if self.__pieceTypes.find(copy[0]) >= 0:
pieceType = copy[0]
copy = copy[1:]
# Get the rank of the source piece (if supplied)
rank = None
if len(copy) > 0:
if RANKS.find(copy[0]) >= 0:
rank = copy[0]
copy = copy[1:]
# Get the file of the source piece (if supplied)
file = None
if len(copy) > 0:
if FILES.find(copy[0]) >= 0:
file = copy[0]
copy = copy[1:]
# There should be no more characters
if len(copy) != 0:
raise Error(san, 'Unexpected extra characters: ' + copy)
# If have both rank and file for source then we have the move completely defined
moveResult = None
move = None
if rank is not None and file is not None:
start = rank + file
moveResult = self.testMove(colour, start, end, promotionType = promotionType)
move = (start, end)
else:
# Try and find a piece that matches the source one
if file is None:
fileRange = FILES
else:
fileRange = file
if rank is None:
rankRange = RANKS
else:
rankRange = rank
for file in fileRange:
for rank in rankRange:
start = rank + file
# Only test our pieces
piece = self.getPiece(start)
if piece is None:
continue
if piece[0] != colour or piece[1] != pieceType:
continue
# If move is valid this is a possible move
# FIXME: Check the crafty case in reverse (i.e. suicidal moves)
result = self.testMove(colour, start, end, promotionType = promotionType)
if result is False:
continue
# Multiple matches
if moveResult is not None:
raise Error(san, 'Move is ambiguous, at least ' + str(move) + ' and ' + str([start, end]) + ' are possible')
moveResult = result
move = [start, end]
# Failed to find a match
if moveResult is None:
raise Error(san, 'Not a valid move')
return (move[0], move[1], expectedResult, promotionType)
def encode(self, start, end, promotionType = QUEEN):
"""Convert glChess co-ordinate move to SAN notation.
'start' is the square to move from (string, e.g. 'a1', 'h8').
'end' is the square to move to (string, e.g. 'a1', 'h8').
'promotionType' is the piece used for pawn promotion (if necesasary).
Return the move in SAN notation or None if unable to convert.
"""
piece = self.getPiece(start)
if piece is None:
return None
(pieceColour, pieceType) = piece
victim = self.getPiece(end)
# Test the move is valid
if self.testMove(pieceColour, start, end, promotionType) is False:
return None
# Check for castling
if pieceType is self.KING:
# Castling
if pieceColour is self.WHITE:
baseFile = '1'
else:
baseFile = '8'
shortCastle = ('e' + baseFile, 'g' + baseFile)
longCastle = ('e' + baseFile, 'c' + baseFile)
# FIXME: Add check result
if (start, end) == shortCastle:
return SAN_CASTLE_SHORT
elif (start, end) == longCastle:
return SAN_CASTLE_LONG
# Try and describe this piece with the minimum of information
file = '?'
rank = '?'
# Pawns always explicitly provide rank when taking
if pieceType is self.PAWN and victim is not None:
rank = start[0]
# First try no rank or file, then just file, then just rank, then both
result = self.__isUnique(pieceColour, pieceType, rank + file, end, promotionType)
if result is None:
# Try with rank
rank = start[0]
file = '?'
result = self.__isUnique(pieceColour, pieceType, rank + '?', end, promotionType)
if result is None:
# Try with file
rank = '?'
file = start[1]
result = self.__isUnique(pieceColour, pieceType, '?' + file, end, promotionType)
if result is None:
# Try with rank and file
result = self.__isUnique(pieceColour, pieceType, rank + file, end, promotionType)
# This move is illegal
if result is None:
return None
# Store the piece that is being moved, note pawns are not marked
san = ''
if pieceType is not self.PAWN:
san += pieceType
# Disambiguations
if rank != '?':
san += rank
if file != '?':
san += file
# Mark if taking a piece
if victim is not None:
san += SAN_TAKE
# Write target co-ordinate
san += end
# If a pawn promotion record the type
if pieceColour is self.WHITE:
promotionFile = '8'
else:
promotionFile = '1'
if pieceType == self.PAWN and end[1] == promotionFile:
san += SAN_PROMOTE + promotionType
# Record if this is a check/checkmate move
if result is self.CHECK:
san += self.CHECK
elif result is self.CHECKMATE:
san += self.CHECKMATE
return san
def __isUnique(self, colour, pieceType, start, end, promotionType = QUEEN):
"""Test if a move is unique.
'colour' is the piece colour being moved. (self.WHITE or self.BLACK).
'pieceType' is the type of the piece being moved (self.[PAWN|KNIGHT|BISHOP|ROOK|QUEEN|KING]).
'start' is the start location of the move (tuple (file, rank). rank and file can be None).
'end' is the end point of the move (tuple (file,rank)).
'promotionType' is the piece type to promote pawns to (self.[PAWN|KNIGHT|BISHOP|ROOK|QUEEN]).
Return the result of self.testMove() if a unique move is found otherwise None.
"""
lastResult = None
# Work out what ranges to iterate over
if start[0] == '?':
rankRange = RANKS
else:
rankRange = start[0]
if start[1] == '?':
fileRange = FILES
else:
fileRange = start[1]
for file in fileRange:
for rank in rankRange:
# Check if there is a piece of this type and colour at this location
p = self.getPiece(rank + file)
if p is None:
continue
if p[1] != pieceType or p[0] != colour:
continue
# If move is valid this is a possible move
# NOTE: We check moves that would be suicide for us otherwise crafty claims they
# are ambiguous, the PGN specification says we don't need to disambiguate if only
# one non-suicidal move is available.
# (8.2.3.4: Disambiguation) */
result = self.testMove(colour, rank + file, end, promotionType = promotionType, allowSuicide = True)
if result is not False:
# If multiple matches then not unique (duh!)
if lastResult != None:
return None
lastResult = result
# Return the result of the move
return lastResult
if __name__ == '__main__':
import chess_board
class TestConverter(SANConverter):
"""
"""
__colourToSAN = {chess_board.WHITE: SANConverter.WHITE, chess_board.BLACK: SANConverter.BLACK}
__sanToColour = {}
for (a, b) in __colourToSAN.iteritems():
__sanToColour[b] = a
__typeToSAN = {chess_board.PAWN: SANConverter.PAWN,
chess_board.KNIGHT: SANConverter.KNIGHT,
chess_board.BISHOP: SANConverter.BISHOP,
chess_board.ROOK: SANConverter.ROOK,
chess_board.QUEEN: SANConverter.QUEEN,
chess_board.KING: SANConverter.KING}
__sanToType = {}
for (a, b) in __typeToSAN.iteritems():
__sanToType[b] = a
__board = None
def __init__(self, board):
self.__board = board
def testEncode(self, start, end):
print str((start, end)) + ' => ' + str(self.encode(start, end))
def testDecode(self, colour, san):
try:
result = self.decodeSAN(colour, san)
print san.ljust(7) + ' => ' + str(result)
except Error, e:
print san.ljust(7) + ' !! ' + str(e)
def getPiece(self, file, rank):
"""Called by SANConverter"""
piece = self.__board.getPiece((file, rank))
if piece is None:
return None
return (self.__colourToSAN[piece.getColour()], self.__typeToSAN[piece.getType()])
def testMove(self, colour, start, end, promotionType, allowSuicide = False):
"""Called by SANConverter"""
moveResult = self.__board.testMove(self.__sanToColour[colour], ((start, end)), self.__sanToType[promotionType], allowSuicide)
return {chess_board.MOVE_RESULT_ILLEGAL: False,
chess_board.MOVE_RESULT_ALLOWED: True,
chess_board.MOVE_RESULT_OPPONENT_CHECK: self.CHECK,
chess_board.MOVE_RESULT_OPPONENT_CHECKMATE: self.CHECKMATE}[moveResult]
b = chess_board.ChessBoard()
c = TestConverter(b)
print b
c.testEncode((1,1), (1,2))
c.testEncode((1,0), (2,2))
c.testDecode(c.WHITE, 'c3') # Simple pawn move
c.testDecode(c.WHITE, 'Pc3') # Explicit pawn move
c.testDecode(c.WHITE, 'c4') # Pawn march
c.testDecode(c.WHITE, 'Nc3') # Non-pawn move
c.testDecode(c.WHITE, 'Qd3') # Invalid move
c.testDecode(c.WHITE, 'Qd3=X') # Invalid promotion type
c.testDecode(c.WHITE, 'x3') # Invalid destination
c.testDecode(c.WHITE, 'ic3') # Extra characters
# TODO: Ambiguous move
print b

47
src/lib/defaults.py Normal file
View file

@ -0,0 +1,47 @@
import os, os.path
import gettext
#DOMAIN = 'glchess'
DOMAIN = 'gnome-games'
gettext.bindtextdomain(DOMAIN)
gettext.textdomain(DOMAIN)
from gettext import gettext as _
import gtk.glade
gtk.glade.bindtextdomain (DOMAIN)
gtk.glade.textdomain (DOMAIN)
VERSION = "2.17.1"
APPNAME = _("glChess")
# grab the proper subdirectory, assuming we're in
# lib/python/site-packages/glchess/
# special case our standard debian install, which puts
# all the python libraries into /usr/share/glchess
if __file__.find('/usr/share/glchess')==0:
usr='/usr'
elif __file__.find('/usr/local/share/glchess')==0:
usr='/usr/local'
else:
usr=os.path.split(os.path.split(os.path.split(os.path.split(os.path.split(__file__)[0])[0])[0])[0])[0]
# add share/glchess
# this assumes the user only specified a general build
# prefix. If they specified data and lib prefixes, we're
# screwed. See the following email for details:
# http://mail.python.org/pipermail/python-list/2004-May/220700.html
if usr:
APP_DATA_DIR = os.path.join(usr,'share')
ICON_DIR = os.path.join(APP_DATA_DIR,'pixmaps')
IMAGE_DIR = os.path.join(ICON_DIR,'glchess')
GLADE_DIR = os.path.join(APP_DATA_DIR,'glchess')
BASE_DIR = os.path.join(APP_DATA_DIR,'glchess')
else:
ICON_DIR = '../../textures'
IMAGE_DIR = '../../textures'
GLADE_DIR = '../../glade'
BASE_DIR = '../../data'
DATA_DIR = os.path.expanduser('~/.gnome2/glchess/')
def initialize_games_dir ():
if not os.path.exists(DATA_DIR): os.makedirs(DATA_DIR)

526
src/lib/game.py Normal file
View file

@ -0,0 +1,526 @@
"""
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import chess.board
import chess.san
class ChessMove:
"""
"""
# The move number (game starts at 0)
number = 0
# The player and piece that moved
player = None
piece = None
# The start and end position of the move
start = None
end = None
# The move in CAN and SAN format
canMove = ''
sanMove = ''
class ChessPlayer:
"""
"""
# The name of the player
__name = None
__type = None
# The game this player is in
__game = None
# Flag to show if this player is able to move
__readyToMove = False
def __init__(self, name):
"""Constructor for a chess player.
'name' is the name of the player.
"""
self.__name = str(name)
# Methods to extend
def onPieceMoved(self, piece, start, end):
"""Called when a chess piece is moved.
'piece' is the piece that has been moved (chess.board.ChessPiece).
'start' is the location the piece in LAN format (string) or None if the piece has been created.
'end' is the location the piece has moved to in LAN format (string) or None if the piece has been deleted.
"""
pass
def onPlayerMoved(self, player, move):
"""Called when a player has moved.
'player' is the player that has moved (ChessPlayer).
'move' is the record for this move (ChessMove).
"""
pass
def onGameEnded(self, winningPlayer = None):
"""Called when a chess game has ended.
'winningPlayer' is the player that won or None if the game was a draw.
"""
pass
# Public methods
def getName(self):
"""Get the name of this player.
Returns the player name (string).
"""
return self.__name
def readyToMove(self):
"""
"""
return self.__readyToMove
def canMove(self, start, end, promotionType = chess.board.QUEEN):
"""
"""
return self.__game.canMove(self, start, end, promotionType)
def move(self, move):
"""
"""
self.__game.move(self, move)
# Private methods
def _setGame(self, game):
"""
"""
self.__game = game
def _setReadyToMove(self, readyToMove):
self.__readyToMove = readyToMove
if readyToMove is True:
self.readyToMove()
class ChessGameBoard(chess.board.ChessBoard):
"""
"""
# Reference to the game
__game = None
def __init__(self, game):
"""
"""
self.__game = game
chess.board.ChessBoard.__init__(self)
def onPieceMoved(self, piece, start, end):
"""Called by chess.board.ChessBoard"""
self.__game._onPieceMoved(piece, start, end)
class ChessGameSANConverter(chess.san.SANConverter):
"""
"""
__colourToSAN = {chess.board.WHITE: chess.san.SANConverter.WHITE,
chess.board.BLACK: chess.san.SANConverter.BLACK}
__sanToColour = {}
for (a, b) in __colourToSAN.iteritems():
__sanToColour[b] = a
__typeToSAN = {chess.board.PAWN: chess.san.SANConverter.PAWN,
chess.board.KNIGHT: chess.san.SANConverter.KNIGHT,
chess.board.BISHOP: chess.san.SANConverter.BISHOP,
chess.board.ROOK: chess.san.SANConverter.ROOK,
chess.board.QUEEN: chess.san.SANConverter.QUEEN,
chess.board.KING: chess.san.SANConverter.KING}
__sanToType = {}
for (a, b) in __typeToSAN.iteritems():
__sanToType[b] = a
__resultToSAN = {chess.board.MOVE_RESULT_ILLEGAL: False,
chess.board.MOVE_RESULT_ALLOWED: True,
chess.board.MOVE_RESULT_OPPONENT_CHECK: chess.san.SANConverter.CHECK,
chess.board.MOVE_RESULT_OPPONENT_CHECKMATE: chess.san.SANConverter.CHECKMATE}
__board = None
def __init__(self, board):
self.__board = board
chess.san.SANConverter.__init__(self)
def decodeSAN(self, colour, move):
(start, end, result, promotionType) = chess.san.SANConverter.decodeSAN(self, self.__colourToSAN[colour], move)
return (start, end, self.__sanToType[promotionType])
def encodeSAN(self, start, end, promotionType):
if promotionType is None:
promotion = self.QUEEN
else:
promotion = self.__typeToSAN[promotionType]
return chess.san.SANConverter.encode(self, start, end, promotion)
def getPiece(self, location):
"""Called by chess.san.SANConverter"""
piece = self.__board.getPiece(location)
if piece is None:
return None
return (self.__colourToSAN[piece.getColour()], self.__typeToSAN[piece.getType()])
def testMove(self, colour, start, end, promotionType, allowSuicide = False):
"""Called by chess.san.SANConverter"""
moveResult = self.__board.testMove(self.__sanToColour[colour], start, end, self.__sanToType[promotionType], allowSuicide)
return self.__resultToSAN[moveResult]
class ChessGame:
"""
"""
# The players and spectators in the game
__players = None
__whitePlayer = None
__blackPlayer = None
__spectators = None
# The board to move on
__board = None
# SAN en/decoders
__sanConverter = None
# The game state (started and player to move)
__started = False
__currentPlayer = None
# Flag to show if calling a player and the queued up moves
__notifyingPlayer = False
__queuedMoves = None
__moves = None
def __init__(self):
"""Game constructor"""
self.__players = []
self.__spectators = []
self.__board = ChessGameBoard(self)
self.__sanConverter = ChessGameSANConverter(self.__board)
self.__moves = []
def getAlivePieces(self, moveNumber = -1):
"""Get the alive pieces on the board.
'moveNumber' is the move to get the pieces from (integer).
Returns a dictionary of the alive pieces (board.ChessPiece) keyed by location.
Raises an IndexError exception if moveNumber is invalid.
"""
return self.__board.getAlivePieces(moveNumber)
def getDeadPieces(self, moveNumber = -1):
"""Get the dead pieces from the game.
'moveNumber' is the move to get the pieces from (integer).
Returns a list of the pieces (board.ChessPiece) in the order they were killed.
Raises an IndexError exception if moveNumber is invalid.
"""
return self.__board.getDeadPieces(moveNumber)
def setWhite(self, player):
"""Set the white player in the game.
'player' is the player to use as white.
If the game has started or there is a white player an exception is thrown.
"""
assert(self.__started is False)
assert(self.__whitePlayer is None)
self.__whitePlayer = player
self.__connectPlayer(player)
def getWhite(self):
"""Returns the current white player (player.Player)"""
return self.__whitePlayer
def setBlack(self, player):
"""Set the black player in the game.
'player' is the player to use as black.
If the game has started or there is a black player an exception is thrown.
"""
assert(self.__started is False)
assert(self.__blackPlayer is None)
self.__blackPlayer = player
self.__connectPlayer(player)
def getBlack(self):
"""Returns the current white player (player.Player)"""
return self.__blackPlayer
def addSpectator(self, player):
"""Add a spectator to the game.
'player' is the player spectating.
This can be called after the game has started.
"""
self.__spectators.append(player)
self.__connectPlayer(player)
def start(self, moves = []):
"""Start the game.
'moves' is a list of moves to start with.
If there is no white or black player then an exception is raised.
"""
assert(self.__whitePlayer is not None and self.__blackPlayer is not None)
# Disabled for now
#import network
#self.x = network.GameReporter('Test Game', 12345)
#print 'Reporting'
# Set initial state
self.__queuedMoves = []
self.__currentPlayer = self.__whitePlayer
# Load starting moves
try:
for move in moves:
self.move(self.__currentPlayer, move)
except chess.san.Error, e:
print e
self.__started = True
# Get the next player to move
self.__currentPlayer._setReadyToMove(True)
def getSquareOwner(self, coord):
"""TODO
"""
piece = self.__board.getPiece(coord)
if piece is None:
return None
colour = piece.getColour()
if colour is chess.board.WHITE:
return self.__whitePlayer
elif colour is chess.board.BLACK:
return self.__blackPlayer
else:
return None
def canMove(self, player, start, end, promotionType):
"""Test if a player can move.
'player' is the player making the move.
'start' is the location to move from in LAN format (string).
'end' is the location to move from in LAN format (string).
'promotionType' is the piece type to promote pawns to. FIXME: Make this a property of the player
Return True if can move, otherwise False.
"""
if player is not self.__currentPlayer:
return False
if player is self.__whitePlayer:
colour = chess.board.WHITE
elif player is self.__blackPlayer:
colour = chess.board.BLACK
else:
assert(False)
moveResult = self.__board.testMove(colour, start, end, promotionType = promotionType)
return moveResult is not chess.board.MOVE_RESULT_ILLEGAL
def move(self, player, move):
"""Get a player to make a move.
'player' is the player making the move.
'move' is the move to make in SAN or LAN format (string).
"""
self.__queuedMoves.append((player, move))
# Don't process if still finishing the last move
if self.__notifyingPlayer:
return
while True:
try:
(movingPlayer, move) = self.__queuedMoves.pop(0)
except IndexError:
return
if movingPlayer is not self.__currentPlayer:
print 'Player attempted to move out of turn'
else:
self.__notifyingPlayer = True
self._move(movingPlayer, move)
self.__notifyingPlayer = False
def _move(self, player, move):
"""
"""
if self.__currentPlayer is self.__whitePlayer:
nextPlayer = self.__blackPlayer
colour = chess.board.WHITE
else:
nextPlayer = self.__whitePlayer
colour = chess.board.BLACK
# If move is SAN process it as such
try:
(start, end, _, _, promotionType, _) = chess.lan.decode(colour, move)
except chess.lan.DecodeError, e:
try:
(start, end, promotionType) = self.__sanConverter.decodeSAN(colour, move)
except chess.san.Error, e:
print 'Invalid move: ' + move
return
# Only use promotion type if a pawn move to far file
piece = self.__board.getPiece(start)
promotion = None
if piece is not None and piece.getType() is chess.board.PAWN:
if colour is chess.board.WHITE:
if end[1] == '8':
promotion = promotionType
else:
if end[1] == '1':
promotion = promotionType
# Re-encode for storing and reporting
sanMove = self.__sanConverter.encodeSAN(start, end, promotionType)
canMove = chess.lan.encode(colour, start, end, promotionType = promotion)
moveResult = self.__board.movePiece(colour, start, end, promotionType)
if moveResult is chess.board.MOVE_RESULT_ILLEGAL:
print 'Illegal move: ' + str(move)
return
move = ChessMove()
if len(self.__moves) == 0:
move.number = 1
else:
move.number = self.__moves[-1].number + 1
move.player = self.__currentPlayer
move.start = start
move.end = end
move.canMove = canMove
move.sanMove = sanMove
self.__moves.append(move)
# This player has now moved
self.__currentPlayer._setReadyToMove(False)
# Inform other players of the result
for player in self.__players:
player.onPlayerMoved(self.__currentPlayer, move)
# Notify the next player they can move
self.__currentPlayer = nextPlayer
if self.__started is True:
nextPlayer._setReadyToMove(True)
def getMoves(self):
"""
"""
return self.__moves[:]
def end(self):
"""End the game"""
# Inform players
for player in self.__players:
player.onGameEnded()
# Private methods:
def __connectPlayer(self, player):
"""Add a player into the game.
'player' is the player to add.
The player will be notified of the current state of the board.
"""
self.__players.append(player)
player._setGame(self)
# Notify the player of the current state
# FIXME: Make the board iteratable...
for file in '12345678':
for rank in 'abcdefgh':
coord = rank + file
piece = self.__board.getPiece(coord)
if piece is None:
continue
# These are moves from nowhere to their current location
player.onPieceMoved(piece, None, coord)
def _onPieceMoved(self, piece, start, end):
"""Called by the chess board"""
# Notify all players of creations and deletions
# NOTE: Normal moves are done above since the SAN moves are calculated before the move...
# FIXME: Change this so the SAN moves are done afterwards...
for player in self.__players:
player.onPieceMoved(piece, start, end)
class NetworkChessGame(ChessGame):
"""
"""
def move(self, player, move):
"""Get a player to make a move.
'player' is the player making the move.
'move' is the move to make. It can be of the form:
A coordinate move in the form ((file0, rank0), (file1, rank1), promotionType) ((int, int), (int, int), chess.board.PIECE_TYPE) or
A SAN move (string).
"""
# Send to the server
if __name__ == '__main__':
game = ChessGame()
import pgn
p = pgn.PGN('black.pgn')
g = p.getGame(0)
class PGNPlayer(ChessPlayer):
__moveNumber = 1
__isWhite = True
def __init__(self, isWhite):
self.__isWhite = isWhite
def readyToMove(self):
if self.__isWhite:
move = g.getWhiteMove(self.__moveNumber)
else:
move = g.getBlackMove(self.__moveNumber)
self.__moveNumber += 1
self.move(move)
white = PGNPlayer(True)
black = PGNPlayer(False)
game.setWhite(white)
game.setBlack(black)
game.start()

4
src/lib/glchess.py Normal file
View file

@ -0,0 +1,4 @@
def start_game ():
import main
app = main.Application()
app.start()

View file

@ -0,0 +1,5 @@
glchessdir = $(pythondir)/glchess/gtkui
glchess_PYTHON = \
dialogs.py \
gtkui.py \
__init__.py

View file

@ -0,0 +1,3 @@
#!/usr/bin/env python
from gtkui import GtkView, GtkUI

540
src/lib/gtkui/dialogs.py Normal file
View file

@ -0,0 +1,540 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import os
import gobject
import gtk
import gtk.glade
import gtk.gdk
import gtkui
class GtkServerList:
__gui = None
def __init__(self, gui):
self.__gui = gui
self.__servers = []
view = gui.get_widget('server_list')
if view is not None:
store = gtk.ListStore(str, gobject.TYPE_PYOBJECT)
view.set_model(store)
cell = gtk.CellRendererText()
column = gtk.TreeViewColumn('name', cell)
column.add_attribute(cell, 'text', 0)
view.append_column(column)
def add(self, name, game):
"""
"""
view = self.__gui.get_widget('server_list')
if view is None:
return
model = view.get_model()
iter = model.append()
model.set(iter, 0, name)
model.set(iter, 1, game)
def getSelected(self):
"""
"""
view = self.__gui.get_widget('server_list')
if view is None:
return None
selection = view.get_selection()
(model, iter) = selection.get_selected()
if iter is None:
return None
else:
return model.get_value(iter, 1)
def remove(self, game):
"""
"""
view = self.__gui.get_widget('server_list')
if view is None:
return
model = view.get_model()
iter = model.get_iter_first()
while iter is not None:
if model.get_value(iter, 1) is game:
break
iter = model.iter_next(iter)
if iter is not None:
model.remove(iter)
class GtkNewGameDialog:
"""
"""
# The main UI and the ???
__mainUI = None
__gui = None
__moves = None
def __init__(self, mainUI, aiModel, gameName = None,
whiteName = None, blackName = None,
whiteAI = None, blackAI = None, moves = None):
"""Constructor for a new game dialog.
'mainUI' is the main UI.
'aiModel' is the AI models to use.
'gameName' is the name of the game (string) or None if unknown.
'whiteName' is the name of the white player (string) or None if unknown.
'blackName' is the name of the white player (string) or None if unknown.
'whiteAI' is the type of AI the white player is (string) or None if no AI.
'blackAI' is the type of AI the black player is (string) or None if no AI.
'moves' is a list of moves (strings) that the have already been made.
"""
self.__mainUI = mainUI
self.__moves = moves
# Load the UI
self.__gui = gtkui.loadGladeFile('new_game.glade', 'new_game_dialog', domain = 'glchess')
self.__gui.signal_autoconnect(self)
# Make all the AI combo boxes use one list of AI types
for name in ['black_type_combo', 'white_type_combo']:
widget = self.__gui.get_widget(name)
if widget is None:
continue
widget.set_model(aiModel)
cell = gtk.CellRendererPixbuf()
widget.pack_start(cell, False)
widget.add_attribute(cell, 'pixbuf', 1)
cell = gtk.CellRendererText()
widget.pack_start(cell, False)
widget.add_attribute(cell, 'text', 2)
widget.set_active(0)
# Use the supplied properties
if moves:
self.__getWidget('new_game_dialog').set_title('Restore game (%i moves)' % len(moves))
if gameName:
self.__getWidget('game_name_entry').set_text(gameName)
if whiteName:
self.__getWidget('white_name_entry').set_text(whiteName)
if blackName:
self.__getWidget('black_name_entry').set_text(blackName)
# Configure AIs
if whiteAI:
self.__getWidget('white_type_combo').set_active_iter(self.__getAIIter(aiModel, whiteAI))
if blackAI:
self.__getWidget('black_type_combo').set_active_iter(self.__getAIIter(aiModel, blackAI))
# Show the dialog
self.__getWidget('new_game_dialog').show()
self.__testReady()
# Private methods
def __getAIIter(self, model, name):
"""Get an AI engine.
'name' is the name of the AI engine to find.
Return the iter for this AI or None if no AI of this name.
"""
# FIXME: I'm sure there is a more efficient way of doing this...
iter = model.get_iter_first()
while True:
if name == model.get_value(iter, 0):
return iter
iter = model.iter_next(iter)
if iter is None:
return None
def __getWidget(self, name):
"""
"""
return self.__gui.get_widget(name)
def __getAIType(self, comboBox):
"""
"""
model = comboBox.get_model()
iter = comboBox.get_active_iter()
if iter is None:
return None
data = model.get(iter, 0)
return data[0]
def __getWhitePlayer(self):
"""
"""
name = self.__getWidget('white_name_entry').get_text()
if len(name) == 0:
return (None, None)
aiType = self.__getAIType(self.__getWidget('white_type_combo'))
return (name, aiType)
def __getBlackPlayer(self):
"""
"""
name = self.__getWidget('black_name_entry').get_text()
if len(name) == 0:
return (None, None)
aiType = self.__getAIType(self.__getWidget('black_type_combo'))
return (name, aiType)
def __testReady(self):
ready = True
# Must have a name for the game
name = self.__getWidget('game_name_entry').get_text()
if len(name) == 0:
ready = False
# Must have two valid players
white = self.__getWhitePlayer()
black = self.__getBlackPlayer()
if white is None or black is None:
ready = False
# Can only click OK if have enough information
self.__getWidget('start_button').set_sensitive(ready)
def __startGame(self):
# FIXME: Game properties
gameName = self.__getWidget('game_name_entry').get_text()
allowSpectators = True
# Get the players
white = self.__getWhitePlayer()
black = self.__getBlackPlayer()
assert(white is not None)
assert(black is not None)
# Inform the child class
self.__mainUI.onGameStart(gameName, allowSpectators, white[0], white[1], black[0], black[1], self.__moves)
# Gtk+ signal handlers
def _on_properties_changed(self, widget, *data):
"""Gtk+ callback"""
self.__testReady()
def _on_response(self, widget, response_id, data = None):
"""Gtk+ callback"""
if response_id == gtk.RESPONSE_OK:
self.__startGame()
self.__getWidget('new_game_dialog').destroy()
class GtkLoadGameDialog:
"""
"""
__mainUI = None
__gui = None
def __init__(self, mainUI):
"""
"""
self.__mainUI = mainUI
# Load the UI
self.__gui = gtkui.loadGladeFile('load_game.glade', domain = 'glchess')
self.__gui.signal_autoconnect(self)
fileChooser = self.__gui.get_widget('filechooserwidget')
# Filter out non PGN files by default
pgnFilter = gtk.FileFilter()
pgnFilter.set_name('PGN files')
pgnFilter.add_pattern('*.pgn')
fileChooser.add_filter(pgnFilter)
allFilter = gtk.FileFilter()
allFilter.set_name('All files')
allFilter.add_pattern('*')
fileChooser.add_filter(allFilter)
dialog = self.__gui.get_widget('game_load_dialog')
dialog.show()
def __getFilename(self):
"""Get the currently selected filename.
Returns the filename (string) or None if none selected.
"""
return self.__gui.get_widget('filechooserwidget').get_filename()
def _on_file_changed(self, widget, data = None):
"""Gtk+ callback"""
name = self.__getFilename()
if name is None:
isFile = False
else:
isFile = os.path.isfile(name)
self.__gui.get_widget('open_button').set_sensitive(isFile)
self.__gui.get_widget('properties_button').set_sensitive(isFile)
def _on_load_game(self, widget, data = None):
"""Gtk+ callback"""
self.__mainUI.loadGame(self.__getFilename(), False)
self._on_close(widget, data)
def _on_configure_game(self, widget, data = None):
"""Gtk+ callback"""
self.__mainUI.loadGame(self.__getFilename(), True)
self._on_close(widget, data)
def _on_close(self, widget, data = None):
"""Gtk+ callback"""
self.__gui.get_widget('game_load_dialog').destroy()
class GtkJoinGameDialog:
"""
"""
# The main UI and the ???
__mainUI = None
__gui = None
__serverList = None
__moves = None
def __init__(self, mainUI, aiModel, gameName = None,
localName = None, localAI = None, moves = None):
"""Constructor for a join game dialog.
'mainUI' is the main UI.
'aiModel' is the AI models to use.
'gameName' is the name of the game (string) or None if unknown.
'localName' is the name of the local player (string) or None if unknown.
'localAI' is the type of AI the local player is (string) or None if no AI.
'moves' is a list of moves (strings) that the have already been made.
"""
self.__mainUI = mainUI
self.__moves = moves
# Load the UI
self.__gui = gtkui.loadGladeFile('new_game.glade', 'join_game_dialog', domain = 'glchess')
# Make all the AI combo boxes use one list of AI types
combo = self.__gui.get_widget('local_type_combo')
combo.set_model(aiModel)
cell = gtk.CellRendererPixbuf()
combo.pack_start(cell, False)
combo.add_attribute(cell, 'pixbuf', 1)
cell = gtk.CellRendererText()
combo.pack_start(cell, False)
combo.add_attribute(cell, 'text', 2)
combo.set_active(0)
# Use the supplied properties
if moves:
self.__getWidget('join_game_dialog').set_title('Restore game (%i moves)' % len(moves))
if gameName:
self.__getWidget('game_name_entry').set_text(gameName)
# Configure local player
if localName:
self.__getWidget('local_name_entry').set_text(whiteName)
if localAI:
# FIXME
self.__getWidget('local_ai_type_combo').set_active_iter(self.__getAIIter(aiModel, localAI))
# ...
self.__serverList = GtkServerList(self.__gui)
view = self.__getWidget('server_list')
if view is not None:
selection = view.get_selection()
selection.connect('changed', self._on_properties_changed)
# Show the dialog
self.__gui.signal_autoconnect(self)
self.__getWidget('join_game_dialog').show()
self.__testReady()
def addNetworkGame(self, name, game):
"""
"""
self.__serverList.add(name, game)
# FIXME: Update?
def removeNetworkGame(self, game):
"""
"""
self.__serverList.remove(game)
# FIXME: Update?
# Private methods
def __getAIIter(self, model, name):
"""Get an AI engine.
'name' is the name of the AI engine to find.
Return the iter for this AI or None if no AI of this name.
"""
# FIXME: I'm sure there is a more efficient way of doing this...
iter = model.get_iter_first()
while True:
if name == model.get_value(iter, 0):
return iter
iter = model.iter_next(iter)
if iter is None:
return None
def __getWidget(self, name):
"""
"""
return self.__gui.get_widget(name)
def __getAIType(self, comboBox):
"""
"""
model = comboBox.get_model()
iter = comboBox.get_active_iter()
if iter is None:
return None
data = model.get(iter, 0)
return data[0]
def __getLocalPlayer(self):
"""
"""
name = self.__getWidget('local_name_entry').get_text()
if len(name) == 0:
return (None, None)
aiType = self.__getAIType(self.__getWidget('local_type_combo'))
return (name, aiType)
def __testReady(self):
ready = True
# Must have a selected server
if self.__serverList.getSelected() is None:
ready = False
# Must have a valid local player
player = self.__getLocalPlayer()
if player is None:
ready = False
# FIXME: Some games do not allow spectators
# Can only click OK if have enough information
self.__getWidget('join_button').set_sensitive(ready)
def __startGame(self):
player = self.__getLocalPlayer()
assert(player is not None)
# Joining a server
server = self.__serverList.getSelected()
assert(server is not None)
self.__mainUI.onGameJoin(player[0], player[1], server)
# Gtk+ signal handlers
def _on_find_servers_button_clicked(self, widget, data=None):
"""Gtk+ callback"""
host = self.__getWidget('server_entry').get_text()
if host == '':
self.__mainUI.onNetworkServerSearch()
else:
self.__mainUI.onNetworkServerSearch(host)
def _on_search_server_entry_changed(self, widget, data=None):
"""Gtk+ callback"""
# FIXME: Change colour back to default
pass
def _on_properties_changed(self, widget, *data):
"""Gtk+ callback"""
self.__testReady()
def _on_response(self, widget, response_id, data = None):
"""Gtk+ callback"""
if response_id == gtk.RESPONSE_OK:
self.__startGame()
self.__getWidget('join_game_dialog').destroy()
class GtkSaveGameDialog:
"""
"""
# The main UI
__mainUI = None
# The view that is being saved
__view = None
# The GUI
__gui = None
def __init__(self, mainUI, view):
"""
"""
self.__mainUI = mainUI
self.__view = view
# Load the UI
self.__gui = gtkui.loadGladeFile('save_game.glade', domain = 'glchess')
self.__gui.signal_autoconnect(self)
# Filter out non PGN files by default
dialog = self.__gui.get_widget('dialog')
pgnFilter = gtk.FileFilter()
pgnFilter.set_name('PGN files')
pgnFilter.add_pattern('*.pgn')
dialog.add_filter(pgnFilter)
allFilter = gtk.FileFilter()
allFilter.set_name('All files')
allFilter.add_pattern('*')
dialog.add_filter(allFilter)
def _on_save(self, widget, data = None):
"""Gtk+ callback"""
dialog = self.__gui.get_widget('dialog')
# Append .pgn to the end if not provided
fname = dialog.get_filename()
if fname[-4:].lower() != '.pgn':
fname += '.pgn'
self.__mainUI._saveView(self.__view, fname)
dialog.destroy()
def _on_close(self, widget, data = None):
"""Gtk+ callback"""
dialog = self.__gui.get_widget('dialog')
dialog.destroy()
self.__mainUI._saveView(self.__view, None)
class GtkErrorDialog:
"""
"""
__gui = None
def __init__(self, title, contents):
"""
"""
self.__gui = gtkui.loadGladeFile('error_dialog.glade', domain = 'glchess')
self.__gui.signal_autoconnect(self)
self.__gui.get_widget('title_label').set_markup('<b><big>' + title + '</big></b>')
self.__gui.get_widget('content_label').set_text(contents)
dialog = self.__gui.get_widget('dialog').show_all()
def _on_close(self, widget, data = None):
"""Gtk+ callback"""
dialog = self.__gui.get_widget('dialog').destroy()

931
src/lib/gtkui/gtkui.py Normal file
View file

@ -0,0 +1,931 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
__all__ = ['GtkView', 'GtkUI']
# TODO: Extend base UI classes?
import os
import sys
import traceback
import time
import gettext
import ConfigParser
import gobject
import gtk
import gtk.glade
import gtk.gdk
import pango
import gnome, gnome.ui
from glchess.defaults import *
# Optionally use OpenGL support
try:
import gtk.gtkgl
except ImportError:
pass
# Stop PyGTK from catching exceptions
os.environ['PYGTK_FATAL_EXCEPTIONS'] = '1'
import glchess.ui
import dialogs
def loadGladeFile(name, root = None, domain = None):
return gtk.glade.XML(os.path.join(GLADE_DIR, name), root, domain = domain)
class GtkViewArea(gtk.DrawingArea):
"""Custom widget to render an OpenGL scene"""
# The view this widget is rendering
view = None
renderGL = False
# Pixmaps to use for double buffering
pixmap = None
dynamicPixmap = None
# TODO...
__glDrawable = None
def __init__(self, view):
"""
"""
gtk.DrawingArea.__init__(self)
self.view = view
gnome.program_init('glchess',VERSION,
properties={gnome.PARAM_APP_DATADIR:APP_DATA_DIR}
)
# Allow notification of button presses
self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_MOTION_MASK)
# Make openGL drawable
if hasattr(gtk, 'gtkgl'):
gtk.gtkgl.widget_set_gl_capability(self, self.view.ui.notebook.glConfig)# FIXME:, share_list=glContext)
# Connect signals
self.connect('realize', self.__init)
self.connect('configure_event', self.__configure)
self.connect('expose_event', self.__expose)
self.connect('button_press_event', self.__button_press)
self.connect('button_release_event', self.__button_release)
# Public methods
def redraw(self):
"""Request this widget is redrawn"""
#FIXME: Check this is valid
self.window.invalidate_rect(self.allocation, False)
def setRenderGL(self, renderGL):
"""Enable OpenGL rendering"""
if not hasattr(gtk, 'gtkgl'):
renderGL = False
if self.renderGL == renderGL:
return
self.renderGL = renderGL
self.redraw()
# Private methods
def __startGL(self):
"""Get the OpenGL context"""
if not self.renderGL:
return
assert(self.__glDrawable is None)
# Obtain a reference to the OpenGL drawable
# and rendering context.
glDrawable = gtk.gtkgl.widget_get_gl_drawable(self)
glContext = gtk.gtkgl.widget_get_gl_context(self)
# OpenGL begin.
if not glDrawable.gl_begin(glContext):
return
self.__glDrawable = glDrawable
import OpenGL
if not self.view.ui.openGLInfoPrinted:
print 'Using OpenGL:'
print 'VENDOR=' + OpenGL.GL.glGetString(OpenGL.GL.GL_VENDOR)
print 'RENDERER=' + OpenGL.GL.glGetString(OpenGL.GL.GL_RENDERER)
print 'VERSION=' + OpenGL.GL.glGetString(OpenGL.GL.GL_VERSION)
print 'EXTENSIONS=' + OpenGL.GL.glGetString(OpenGL.GL.GL_EXTENSIONS)
self.view.ui.openGLInfoPrinted = True
def __endGL(self):
"""Free the OpenGL context"""
if not self.renderGL:
return
assert(self.__glDrawable is not None)
self.__glDrawable.gl_end()
self.__glDrawable = None
def __init(self, widget):
"""Gtk+ signal"""
if self.view.feedback is not None:
self.view.feedback.reshape(widget.allocation.width, widget.allocation.height)
def __configure(self, widget, event):
"""Gtk+ signal"""
self.pixmap = gtk.gdk.Pixmap(widget.window, event.width, event.height)
self.dynamicPixmap = gtk.gdk.Pixmap(widget.window, event.width, event.height)
self.__startGL()
if self.view.feedback is not None:
self.view.feedback.reshape(event.width, event.height)
self.__endGL()
def __expose(self, widget, event):
"""Gtk+ signal"""
if self.renderGL:
self.__startGL()
# Get the scene rendered
try:
if self.view.feedback is not None:
self.view.feedback.renderGL()
except GLerror, e:
print 'Rendering Error: ' + str(e)
traceback.print_exc(file = sys.stdout)
# Paint this
if self.__glDrawable.is_double_buffered():
self.__glDrawable.swap_buffers()
else:
glFlush()
self.__endGL()
else:
context = self.pixmap.cairo_create()
if self.view.feedback is not None:
self.view.feedback.renderCairoStatic(context)
# Copy the background to render the dynamic elements on top
self.dynamicPixmap.draw_drawable(widget.get_style().white_gc, self.pixmap, 0, 0, 0, 0, -1, -1)
context = self.dynamicPixmap.cairo_create()
# Set a clip region for the expose event
context.rectangle(event.area.x, event.area.y, event.area.width, event.area.height)
context.clip()
# Render the dynamic elements
if self.view.feedback is not None:
self.view.feedback.renderCairoDynamic(context)
# Draw the window
widget.window.draw_drawable(widget.get_style().white_gc, self.dynamicPixmap,
event.area.x, event.area.y,
event.area.x, event.area.y, event.area.width, event.area.height)
def __button_press(self, widget, event):
"""Gtk+ signal"""
self.__startGL()
if self.view.feedback is not None:
self.view.feedback.select(event.x, event.y)
self.__endGL()
def __button_release(self, widget, event):
"""Gtk+ signal"""
self.__startGL()
if self.view.feedback is not None:
self.view.feedback.deselect(event.x, event.y)
self.__endGL()
class GtkView(glchess.ui.ViewController):
"""
"""
# The UI this view belongs to
ui = None
# The widget to render the scene to
widget = None
# A Gtk+ tree model to store the move history
moveModel = None
selectedMove = -1
def __init__(self, ui, feedback, isActive = True):
"""Constructor for a view.
'feedback' is the feedback object for this view (extends ui.ViewFeedback).
'isActive' is a flag showing if this view can be controlled by the user (True) or not (False).
"""
self.ui = ui
self.feedback = feedback
self.isActive = isActive
self.widget = GtkViewArea(self)
# Make a model for navigation
model = gtk.ListStore(int, str)
iter = model.append()
model.set(iter, 0, 0, 1, gettext.gettext('Game Start'))
self.moveModel = model
self.widget.show_all()
# Extended methods
def render(self):
"""Extends glchess.ui.ViewController"""
self.widget.redraw()
def addMove(self, move):
"""Extends glchess.ui.ViewController"""
# FIXME: Make a '@ui' player who watches for these itself?
iter = self.moveModel.append()
string = '%2i. ' % ((move.number - 1) / 2 + 1)
if move.number % 2 == 0:
string += '... '
string += move.sanMove
self.moveModel.set(iter, 0, move.number, 1, string)
# If is the current view and tracking the game select this
if self.selectedMove == -1:
self.ui._updateViewButtons()
def close(self):
"""Extends glchess.ui.ViewController"""
self.ui._removeView(self)
# Public methods
def _getModel(self):
"""
"""
return (self.moveModel, self.selectedMove)
def _setMoveNumber(self, moveNumber):
"""Set the move number this view requests.
'moveNumber' is the move number to use (integer).
"""
self.selectedMove = moveNumber
if self.feedback is not None:
self.feedback.setMoveNumber(moveNumber)
class GtkGameNotebook(gtk.Notebook):
"""
"""
glConfig = None
defaultView = None
viewsByWidget = None
def __init__(self, ui):
"""
"""
self.ui = ui
self.viewsByWidget = {}
gtk.Notebook.__init__(self)
self.set_show_border(False)
# Make the tabs scrollable so the area is not resized
self.set_scrollable(True)
# Configure openGL
try:
gtk.gdkgl
except AttributeError:
self.glConfig = None
else:
display_mode = (gtk.gdkgl.MODE_RGB | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE)
try:
self.glConfig = gtk.gdkgl.Config(mode = display_mode)
except gtk.gdkgl.NoMatches:
display_mode &= ~gtk.gdkgl.MODE_DOUBLE
self.glConfig = gtk.gdkgl.Config(mode = display_mode)
self.set_show_tabs(False)
def setDefault(self, feedback):
"""
"""
assert(self.defaultView is None)
self.defaultView = GtkView(self.ui, feedback)
page = self.append_page(self.defaultView.widget)
self.set_current_page(page)
self.__updateTabVisibleState()
return self.defaultView
def addView(self, title, feedback):
"""
"""
view = GtkView(self.ui, feedback)
self.viewsByWidget[view.widget] = view
page = self.append_page(view.widget)
self.set_tab_label_text(view.widget, title)
self.set_current_page(page)
self.__updateTabVisibleState()
return view
def getView(self, pageNumber = None):
"""Get the view at a given page number.
'pageNumber' is the page to check or None to get the selected page.
Return the view (GtkView) on this page or None if no view here.
"""
if pageNumber is None:
# If splashscreen present then there is no view
if len(self.viewsByWidget) > 0:
num = self.get_current_page()
if num < 0:
return None
widget = self.get_nth_page(num)
else:
return None
else:
widget = self.get_nth_page(pageNumber)
return self.viewsByWidget[widget]
def removeView(self, view):
"""Remove a view from the notebook.
'view' is the view to remove.
"""
self.remove_page(self.page_num(view.widget))
self.viewsByWidget.pop(view.widget)
self.__updateTabVisibleState()
def __updateTabVisibleState(self):
"""
"""
# Only show tabs if there is more than one game
self.set_show_tabs(len(self.viewsByWidget) > 1)
# Show/hide the default view
if len(self.viewsByWidget) == 0:
self.defaultView.widget.show()
else:
self.defaultView.widget.hide()
class AIWindow:
"""
"""
notebook = None
defaultPage = None
# We keep track of the number of pages as there is a bug
# in GtkNotebook (Gnome bug #331785).
pageCount = 0
def __init__(self, notebook):
"""
"""
self.notebook = notebook
self.defaultPage = notebook.get_nth_page(0)
def addView(self, title, executable, description):
"""
"""
# Hide the default page
self.defaultPage.hide()
self.notebook.set_show_tabs(True)
self.pageCount += 1
return AIView(self, title, executable, description)
class AIView:
"""
"""
__gui = None
def __init__(self, window, title, executable, description):
"""
"""
self.window = window
self.__gui = loadGladeFile('ai.glade', 'ai_table', domain = 'glchess')
self.__gui.get_widget('executable_label').set_text(executable)
self.__gui.get_widget('game_label').set_text(description)
# Add into the notebook
self.root = self.__gui.get_widget('ai_table')
notebook = window.notebook
notebook.append_page(self.root, gtk.Label(title))
# Create styles for the buffer
buffer = self.__gui.get_widget('comms_textview').get_buffer()
buffer.create_tag('input', family='Monospace')
buffer.create_tag('output', family='Monospace', weight = pango.WEIGHT_BOLD)
buffer.create_tag('move', family='Monospace', foreground = 'blue')
buffer.create_tag('info', family='Monospace', foreground = 'green')
buffer.create_tag('error', family='Monospace', foreground = 'red')
def addText(self, text, style):
"""FIXME: Define style
"""
buffer = self.__gui.get_widget('comms_textview').get_buffer()
buffer.insert_with_tags_by_name(buffer.get_end_iter(), text, style)
def close(self):
"""
"""
self.window.pageCount -= 1
self.window.notebook.remove_page(self.window.notebook.page_num(self.root))
# Show the default page
if self.window.pageCount == 0:
self.window.defaultPage.show()
self.window.notebook.set_show_tabs(False)
class GtkUI(glchess.ui.UI):
"""
"""
# The Gtk+ GUI
_gui = None
# The time stored for animation
__lastTime = None
__animationTimer = None
# The notebook containing games
notebook = None
# The Gtk+ list model of the available player types
__playerModel = None
# The about dialog open
__aboutDialog = None
# Dictionary of save game dialogs keyed by view
__saveGameDialogs = None
# Dictionary of configuration options
__config = None
__applyingConfig = False
__renderGL = False
openGLInfoPrinted = False
# TODO
__joinGameDialogs = None
__networkGames = None
__defaultWhiteAI = None
__defaultBlackAI = None
def __init__(self):
"""Constructor for a GTK+ glChess GUI"""
self.__networkGames = {}
self.__saveGameDialogs = {}
self.__joinGameDialogs = []
self._gui = loadGladeFile('glchess.glade', domain = 'glchess')
self._gui.signal_autoconnect(self)
# Make a notebook for the games
self.notebook = GtkGameNotebook(self)
self.notebook.connect_after('switch-page', self._on_view_changed)
self.__getWidget('game_viewport').add(self.notebook)
self.notebook.show_all()
# Create the model for the player types
self.__playerModel = gtk.ListStore(gobject.TYPE_PYOBJECT, gtk.gdk.Pixbuf, str)
iconTheme = gtk.icon_theme_get_default()
try:
icon = iconTheme.load_icon('stock_people', 24, gtk.ICON_LOOKUP_USE_BUILTIN)
except gobject.GError:
icon = None
iter = self.__playerModel.append()
self.__playerModel.set(iter, 0, None, 1, icon, 2, gettext.gettext('Human'))
# FIXME: Add spectators for network games
self.__aiWindow = AIWindow(self._gui.get_widget('ai_notebook'))
combo = self.__getWidget('history_combo')
cell = gtk.CellRendererText()
combo.pack_start(cell, False)
combo.add_attribute(cell, 'text', 1)
self.defaultViewController = self.notebook.setDefault(None)
# Disable OpenGL support
if not hasattr(gtk, 'gtkgl'):
self._gui.get_widget('menu_view_3d').set_sensitive(False)
# Load UI preferences
self.__config = {}
self.__loadConfig()
self.defaultViewController.widget.setRenderGL(self.__renderGL)
# Public methods
def watchFileDescriptor(self, fd):
"""Extends ui.UI"""
gobject.io_add_watch(fd, gobject.IO_IN, self.__readData)
def __readData(self, fd, condition):
return self.onReadFileDescriptor(fd)
def addAIEngine(self, name):
"""Register an AI engine.
'name' is the name of the engine.
TODO: difficulty etc etc
"""
iconTheme = gtk.icon_theme_get_default()
try:
icon = iconTheme.load_icon("stock_notebook", 24, gtk.ICON_LOOKUP_USE_BUILTIN)
except gobject.GError:
icon = None
iter = self.__playerModel.append()
self.__playerModel.set(iter, 0, name, 1, icon, 2, name)
# Get the human to play against this AI
if self.__defaultBlackAI is None:
self.__defaultBlackAI = name
def setDefaultView(self, feedback):
"""Extends ui.UI"""
self.defaultViewController.feedback = feedback
return self.defaultViewController
def addView(self, title, feedback):
"""Extends ui.UI"""
view = self.notebook.addView(title, feedback)
view.widget.setRenderGL(self.__renderGL)
return view
def addAIWindow(self, title, executable, description):
"""
"""
return self.__aiWindow.addView(title, executable, description)
def run(self):
"""Run the UI.
This method will not return.
"""
gtk.main()
# Extended methods
def reportError(self, title, error):
"""Extends glchess.ui.UI"""
dialogs.GtkErrorDialog(title, error)
def reportGameLoaded(self, gameName = None,
whiteName = None, blackName = None,
whiteAI = None, blackAI = None, moves = None):
"""Extends glchess.ui.UI"""
dialogs.GtkNewGameDialog(self, self.__playerModel, gameName = gameName,
whiteName = whiteName, whiteAI = whiteAI,
blackName = blackName, blackAI = blackAI, moves = moves)
def addNetworkGame(self, name, game):
"""Extends glchess.ui.UI"""
self.__networkGames[game] = name
# Update the open dialogs
for dialog in self.__joinGameDialogs:
dialog.addNetworkGame(name, game)
def removeNetworkGame(self, game):
"""Extends glchess.ui.UI"""
self.__networkGames.pop(game)
# Update the open dialogs
for dialog in self.__joinGameDialogs:
dialog.removeNetworkGame(game)
# Protected methods
def _saveView(self, view, path):
"""
"""
self.__saveGameDialogs.pop(view)
if path is None:
return
if view.feedback is not None:
view.feedback.save(path)
def _removeView(self, view):
"""Remove a view from the UI.
'view' is the view to remove.
"""
self.notebook.removeView(view)
self._updateViewButtons()
# Private methods
def __loadConfig(self):
"""
"""
# Set defaults
self.__config = {'show_toolbar': 'True',
'show_history': 'True',
'show_3d': 'False'}
name = os.path.expanduser('~/.glchess/gtkui.ini')
cp = ConfigParser.ConfigParser()
cp.read(name)
try:
items = cp.items('ui')
except ConfigParser.NoSectionError:
self.__saveConfig()
else:
for (name, value) in items:
self.__config[name] = value
self.__applyConfig()
def __setConfig(self, name, value):
"""
"""
if self.__applyingConfig:
return
self.__config[name] = value
self.__applyConfig()
self.__saveConfig()
def __saveConfig(self):
"""
"""
name = os.path.expanduser('~/.glchess/gtkui.ini')
try:
f = file(name, 'w')
except IOError, e:
print 'Unable to save config: ' + str(e)
return
cp = ConfigParser.ConfigParser()
cp.add_section('ui')
for (name, value) in self.__config.iteritems():
cp.set('ui', name, value)
cp.write(f)
def __applyConfig(self):
"""
"""
# Stop recursion
self.__applyingConfig = True
# Show/hide the toolbar
toolbar = self.__getWidget('toolbar')
menu = self.__getWidget('menu_view_toolbar')
if self.__config['show_toolbar'] == 'True':
menu.set_active(True)
toolbar.show()
else:
menu.set_active(False)
toolbar.hide()
# Show/hide the history
box = self.__getWidget('navigation_box')
menu = self.__getWidget('menu_view_history')
if self.__config['show_history'] == 'True':
menu.set_active(True)
box.show()
else:
menu.set_active(False)
box.hide()
# Enable/disable OpenGL rendering
self.__renderGL = self.__config['show_3d'] == 'True'
menuItem = self.__getWidget('menu_view_3d')
menuItem.set_active(self.__renderGL)
self.notebook.defaultView.widget.setRenderGL(self.__renderGL)
for view in self.notebook.viewsByWidget.itervalues():
view.widget.setRenderGL(self.__renderGL)
self.__applyingConfig = False
def startAnimation(self):
"""Start the animation callback"""
if self.__animationTimer is None:
self.__lastTime = time.time()
self.__animationTimer = gobject.timeout_add(10, self.__animate)
def __animate(self):
# Get the timestep, if it is less than zero or more than a second
# then the system clock was probably changed.
now = time.time()
step = now - self.__lastTime
if step < 0.0:
step = 0.0
elif step > 1.0:
step = 1.0
self.__lastTime = now
# Animate!
animating = self.onAnimate(step)
if not animating:
self.__animationTimer = None
# Keep/delete timer
return animating
def __getWidget(self, name):
return self._gui.get_widget(name)
def _on_show_toolbar_clicked(self, widget, data = None):
"""Gtk+ callback"""
if widget.get_active():
value = 'True'
else:
value = 'False'
self.__setConfig('show_toolbar', value)
def _on_show_history_clicked(self, widget, data = None):
"""Gtk+ callback"""
if widget.get_active():
value = 'True'
else:
value = 'False'
self.__setConfig('show_history', value)
def _on_toggle_3d_clicked(self, widget, data = None):
"""Gtk+ callback"""
if widget.get_active():
value = 'True'
else:
value = 'False'
self.__setConfig('show_3d', value)
def _on_show_ai_stats_clicked(self, widget, data = None):
"""Gtk+ callback"""
window = self._gui.get_widget('ai_window')
if widget.get_active():
window.show()
else:
window.hide()
def _on_history_combo_changed(self, widget, data = None):
"""Gtk+ callback"""
model = widget.get_model()
iter = widget.get_active_iter()
if iter is None:
return
# Get the move number
moveNumber = model.get_value(iter, 0)
string = 'Show move number: ' + str(moveNumber)
if moveNumber == len(model) - 1:
string += ' (latest)'
moveNumber = -1
view = self.notebook.getView()
if view is not None:
view._setMoveNumber(moveNumber)
def __selectMoveNumber(self, moveNumber):
"""FIXME
"""
combo = self.__getWidget('history_combo')
# Limit moves to the maximum value
maxNumber = len(combo.get_model())
# Allow negative indexing
if moveNumber < 0:
moveNumber = maxNumber + moveNumber
if moveNumber < 0:
moveNumber = 0
if moveNumber >= maxNumber:
moveNumber = maxNumber - 1
combo.set_active(moveNumber)
def __selectMoveNumberRelative(self, offset):
"""FIXME
"""
combo = self.__getWidget('history_combo')
selected = combo.get_active()
maxNumber = len(combo.get_model())
new = selected + offset
if new < 0:
new = 0
elif new >= maxNumber:
new = maxNumber - 1
self.__selectMoveNumber(new)
def _on_history_start_clicked(self, widget, data = None):
"""Gtk+ callback"""
self.__selectMoveNumber(0)
def _on_history_previous_clicked(self, widget, data = None):
"""Gtk+ callback"""
self.__selectMoveNumberRelative(-1)
def _on_history_next_clicked(self, widget, data = None):
"""Gtk+ callback"""
self.__selectMoveNumberRelative(1)
def _on_history_latest_clicked(self, widget, data = None):
"""Gtk+ callback"""
self.__selectMoveNumber(-1)
def _on_view_changed(self, widget, page, pageNum, data = None):
"""Gtk+ callback"""
self._updateViewButtons()
def _updateViewButtons(self):
"""
"""
view = self.notebook.getView()
enableWidgets = (view is not None) and view.isActive
self.__getWidget('end_game_button').set_sensitive(enableWidgets)
self.__getWidget('save_game_button').set_sensitive(enableWidgets)
self.__getWidget('menu_save_item').set_sensitive(enableWidgets)
self.__getWidget('menu_end_game_item').set_sensitive(enableWidgets)
combo = self.__getWidget('history_combo')
if view is None:
combo.set_model(None)
else:
(model, selected) = view._getModel()
combo.set_model(model)
if selected < 0:
selected = len(model) + selected
combo.set_active(selected)
self.__getWidget('navigation_box').set_sensitive(enableWidgets)
def _on_new_game_button_clicked(self, widget, data = None):
"""Gtk+ callback"""
dialogs.GtkNewGameDialog(self, self.__playerModel, whiteAI = self.__defaultWhiteAI, blackAI = self.__defaultBlackAI)
def _on_join_game_button_clicked(self, widget, data = None):
"""Gtk+ callback"""
# Create the dialog
dialog = dialogs.GtkJoinGameDialog(self, self.__playerModel)
self.__joinGameDialogs.append(dialog)
# FIXME: Remove from this list when they dissapear
# Add the detected games into the dialog
for (game, name) in self.__networkGames.iteritems():
dialog.addNetworkGame(name, game)
def _on_open_game_button_clicked(self, widget, data = None):
"""Gtk+ callback"""
dialogs.GtkLoadGameDialog(self)
def _on_save_game_button_clicked(self, widget, data = None):
"""Gtk+ callback"""
view = self.notebook.getView()
if not self.__saveGameDialogs.has_key(view):
self.__saveGameDialogs[view] = dialogs.GtkSaveGameDialog(self, view)
def _on_end_game_button_clicked(self, widget, data = None):
"""Gtk+ callback"""
view = self.notebook.getView()
assert(view is not None)
if view.feedback is not None:
view.feedback.close()
def _on_help_clicked(self, widget, data = None):
"""Gtk+ callback"""
gnome.help_display('glchess')
def _on_about_clicked(self, widget, data = None):
"""Gtk+ callback"""
if self.__aboutDialog is None:
self.__aboutDialog = loadGladeFile('about.glade', domain = 'glchess')
self.__aboutDialog.signal_autoconnect(self)
def _on_glchess_about_dialog_close(self, widget, data = None):
"""Gtk+ callback"""
self.__aboutDialog = None
def _on_ai_window_delete_event(self, widget, data = None):
"""Gtk+ callback"""
self._gui.get_widget('menu_view_ai').set_active(False)
# Stop the event - the window will be closed by the menu event
return True
def _on_quit(self, widget, data = None):
"""Gtk+ callback"""
self.onQuit()
if __name__ == '__main__':
ui = GtkUI()
ui.run()

992
src/lib/main.py Normal file
View file

@ -0,0 +1,992 @@
"""
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
__all__ = ['Application']
import sys
import os
import gettext
import traceback
import ui
import gtkui
import scene.cairo
import scene.opengl
import scene.human
import game
import chess.board
import chess.lan
import ai
#import dbus.glib
#import network
import chess.pgn
class Config:
"""
"""
__directory = None
def __init__(self):
"""Constructor for a confgiuration object"""
self.__directory = os.path.expanduser('~/.glchess')
# Create the directory if it does not exist
if not os.path.exists(self.__directory):
os.mkdir(self.__directory)
else:
assert(os.path.isdir(self.__directory))
def getAutosavePath(self):
"""Get the path to the autosave file"""
return self.__directory + '/autosave.pgn'
class MovePlayer(game.ChessPlayer):
"""This class provides a pseudo-player to watch for piece movements"""
# The game to control
__game = None
# A dictionary of pieces added into the scene
__pieces = None
def __init__(self, chessGame):
"""Constructor for a move player.
'chessGame' is the game to make changes to (ChessGame).
"""
self.__game = chessGame
game.ChessPlayer.__init__(self, '@move')
# Extended methods
def onPieceMoved(self, piece, start, end):
"""Called by chess.board.ChessPlayer"""
self.__game.scene._movePiece(piece, start, end)
self.__game.cairoScene._movePiece(piece, start, end)
def onPlayerMoved(self, player, move):
"""Called by chess.board.ChessPlayer"""
self.__game._onPlayerMoved(player, move)
class HumanPlayer(game.ChessPlayer):
"""
"""
__game = None
def __init__(self, chessGame, name):
"""Constructor.
'chessGame' is the game this player is in (game.ChessGame).
'name' is the name of this player (string).
"""
game.ChessPlayer.__init__(self, name)
self.__game = chessGame
def readyToMove(self):
# FIXME: ???
self.__game.scene.setHumanPlayer(self)
self.__game.cairoScene.setHumanPlayer(self)
class AIPlayer(ai.Player):
"""
"""
def __init__(self, application, name, profile, description):
"""
"""
self.window = application.ui.addAIWindow(profile.name, profile.path, description)
ai.Player.__init__(self, name, profile)
def logText(self, text, style):
"""Called by ai.Player"""
self.window.addText(text, style)
class SceneCairo(scene.cairo.Scene, scene.human.SceneHumanInput):
"""
"""
# The game this scene is rendering
__game = None
# TODO
__moveNumber = -1
__pieceModels = None
# The current human player or None if not a player in play
__humanPlayer = None
def __init__(self, chessGame):
"""
"""
self.__game = chessGame
self.__pieceModels = {}
# Call parent constructors
scene.human.SceneHumanInput.__init__(self)
scene.cairo.Scene.__init__(self)
def setHumanPlayer(self, player):
"""TODO
"""
self.__humanPlayer = player
# Animate the board
if player is self.__game.getWhite():
self.setBoardRotation(0.0)
elif player is self.__game.getBlack():
self.setBoardRotation(180.0)
else:
assert(False), 'Human player is not white or black'
def setMoveNumber(self, moveNumber):
"""Set the move number to watch.
'moveNumber' is the move to watch (integer).
"""
if self.__moveNumber == moveNumber:
return
self.__moveNumber = moveNumber
# Lock the scene if not tracking the game
self.enableHumanInput(moveNumber == -1)
# Get the state of this scene
piecesByLocation = self.__game.getAlivePieces(moveNumber)
# Remove any models not present
requiredPieces = piecesByLocation.values()
for (piece, model) in self.__pieceModels.items():
try:
requiredPieces.index(piece)
except ValueError:
self.__pieceModels.pop(piece)
self.removeChessPiece(model)
# Move the models in the scene
for (location, piece) in piecesByLocation.iteritems():
self.__movePiece(piece, location)
def _movePiece(self, piece, start, end):
"""TODO
"""
# Only allow then watching the active game
if self.__moveNumber == -1:
self.__movePiece(piece, end)
def __movePiece(self, piece, location):
"""
"""
# Get the model for this piece creating one if it doesn't exist
try:
model = self.__pieceModels[piece]
except KeyError:
# No need to create if didn't exist anyway
if location is None:
return
# Make the new model
pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
model = self.addChessPiece(chessSet, pieceName, location)
self.__pieceModels[piece] = model
# Delete or move the model
if location is None:
self.__pieceModels.pop(piece)
self.removeChessPiece(model)
else:
model.move(location)
# Extended methods
def onRedraw(self):
"""Called by scene.cairo.Scene"""
if self.__game.view.activeScene is self and self.__game.view is not None:
self.__game.view.controller.render()
def startAnimation(self):
"""Called by scene.cairo.Scene"""
self.__game.application.ui.startAnimation()
def playerIsHuman(self):
"""Called by scene.human.SceneHumanInput"""
return self.__humanPlayer is not None
def squareIsFriendly(self, coord):
"""Called by scene.human.SceneHumanInput"""
owner = self.__game.getSquareOwner(coord)
if owner is None:
return False
return owner is self.__humanPlayer
def canMove(self, start, end):
"""Called by scene.human.SceneHumanInput"""
if self.__humanPlayer is None:
return False
return self.__humanPlayer.canMove(start, end) # FIXME: Promotion type
def moveHuman(self, start, end):
"""Called by scene.human.SceneHumanInput"""
player = self.__humanPlayer
self.__humanPlayer = None
if player is self.__game.getWhite():
colour = chess.board.WHITE
else:
colour = chess.board.BLACK
move = chess.lan.encode(colour, start, end, promotionType = chess.board.QUEEN) # FIXME: Promotion type
player.move(move)
class SceneOpenGL(scene.opengl.Scene, scene.human.SceneHumanInput):
"""
"""
# The game this scene is rendering
__game = None
# TODO
__moveNumber = -1
__pieceModels = None
# The current human player or None if not a player in play
__humanPlayer = None
def __init__(self, chessGame):
"""Constructor for a glChess scene.
'chessGame' is the game the scene is rendering (game.ChessGame).
"""
self.__game = chessGame
self.__pieceModels = {}
# Call parent constructors
scene.human.SceneHumanInput.__init__(self)
scene.opengl.Scene.__init__(self)
def setHumanPlayer(self, player):
"""TODO
"""
self.__humanPlayer = player
# Animate the board
if player is self.__game.getWhite():
self.setBoardRotation(0.0)
elif player is self.__game.getBlack():
self.setBoardRotation(180.0)
else:
assert(False), 'Human player is not white or black'
def setMoveNumber(self, moveNumber):
"""Set the move number to watch.
'moveNumber' is the move to watch (integer).
"""
if self.__moveNumber == moveNumber:
return
self.__moveNumber = moveNumber
# Lock the scene if not tracking the game
self.enableHumanInput(moveNumber == -1)
# Get the state of this scene
piecesByLocation = self.__game.getAlivePieces(moveNumber)
# Remove any models not present
requiredPieces = piecesByLocation.values()
for (piece, model) in self.__pieceModels.items():
try:
requiredPieces.index(piece)
except ValueError:
self.__pieceModels.pop(piece)
self.removeChessPiece(model)
# Move the models in the scene
for (location, piece) in piecesByLocation.iteritems():
self.__movePiece(piece, location)
def _movePiece(self, piece, start, end):
"""TODO
"""
# Ignore if not watching the active game
if self.__moveNumber != -1:
return
self.__movePiece(piece, end)
def __movePiece(self, piece, location):
"""
"""
# Get the model for this piece creating one if it doesn't exist
try:
model = self.__pieceModels[piece]
except KeyError:
# No need to create if didn't exist anyway
if location is None:
return
# Make the new model
pieceName = {chess.board.PAWN: 'pawn', chess.board.ROOK: 'rook', chess.board.KNIGHT: 'knight',
chess.board.BISHOP: 'bishop', chess.board.QUEEN: 'queen', chess.board.KING: 'king'}[piece.getType()]
chessSet = {chess.board.WHITE: 'white', chess.board.BLACK: 'black'}[piece.getColour()]
model = self.addChessPiece(chessSet, pieceName, location)
self.__pieceModels[piece] = model
# Delete or move the model
if location is None:
self.__pieceModels.pop(piece)
self.removeChessPiece(model)
else:
model.move(location)
# Extended methods
def onRedraw(self):
"""Called by scene.opengl.Scene"""
if self.__game.view.activeScene is self and self.__game.view is not None:
self.__game.view.controller.render()
def startAnimation(self):
"""Called by scene.opengl.Scene"""
self.__game.application.ui.startAnimation()
def playerIsHuman(self):
"""Called by scene.human.SceneHumanInput"""
return self.__humanPlayer is not None
def squareIsFriendly(self, coord):
"""Called by scene.human.SceneHumanInput"""
owner = self.__game.getSquareOwner(coord)
if owner is None:
return False
return owner is self.__humanPlayer
def canMove(self, start, end):
"""Called by scene.human.SceneHumanInput"""
if self.__humanPlayer is None:
return False
return self.__humanPlayer.canMove(start, end) # FIXME: Promotion type
def moveHuman(self, start, end):
"""Called by scene.human.SceneHumanInput"""
player = self.__humanPlayer
self.__humanPlayer = None
if player is self.__game.getWhite():
colour = chess.board.WHITE
else:
colour = chess.board.BLACK
move = chess.lan.encode(colour, start, end, promotionType = chess.board.QUEEN) # FIXME: Promotion type
player.move(move)
class Splashscreen(ui.ViewFeedback):
"""
"""
application = None
scene = None
def __init__(self, application):
"""Constructor.
'application' is ???
"""
self.application = application
self.cairoScene = scene.cairo.Scene()
self.scene = scene.opengl.Scene()
def renderGL(self):
"""Called by ui.ViewFeedback"""
self.scene.render()
def renderCairoStatic(self, context):
"""Called by ui.ViewFeedback"""
return self.cairoScene.renderStatic(context)
def renderCairoDynamic(self, context):
"""Called by ui.ViewFeedback"""
self.cairoScene.renderDynamic(context)
def reshape(self, width, height):
"""Called by ui.View"""
self.scene.reshape(width, height)
self.cairoScene.reshape(width, height)
class View(ui.ViewFeedback):
"""
"""
# The game this view is rendering
game = None
# TEMP: The scene to render (switches between OpenGL and Cairo).
activeScene = None
# The controller object for this view
controller = None
def __init__(self, game):
"""Constructor.
'game' is ???
"""
self.game = game
def renderGL(self):
"""Called by ui.ViewFeedback"""
self.activeScene = self.game.scene
self.activeScene.render()
def renderCairoStatic(self, context):
"""Called by ui.ViewFeedback"""
self.activeScene = self.game.cairoScene
return self.activeScene.renderStatic(context)
def renderCairoDynamic(self, context):
"""Called by ui.ViewFeedback"""
self.activeScene.renderDynamic(context)
def reshape(self, width, height):
"""Called by ui.ViewFeedback"""
self.game.scene.reshape(width, height)
self.game.cairoScene.reshape(width, height)
def select(self, x, y):
"""Called by ui.ViewFeedback"""
self.activeScene.select(x, y)
def deselect(self, x, y):
"""Called by ui.ViewFeedback"""
self.activeScene.deselect(x, y)
def setMoveNumber(self, moveNumber):
"""Called by ui.ViewFeedback"""
self.game.scene.setMoveNumber(moveNumber)
self.game.cairoScene.setMoveNumber(moveNumber)
def save(self, filename):
"""Called by ui.ViewFeedback"""
try:
f = file(filename, 'w')
except IOError, e:
self.reportError('Unable to save PGN file ' + filename, str(e))
return
pgnGame = chess.pgn.PGNGame()
self.game.toPGN(pgnGame)
lines = pgnGame.getLines()
for line in lines:
f.write(line + '\n')
f.write('\n')
f.close()
def close(self):
"""Called by ui.ViewFeedback"""
# The user requests the game to end, for now we just do it
self.game.remove()
class ChessGame(game.ChessGame):
"""
"""
# Link back to the main application
application = None
# The name of the game
__name = None
# The scene for this game
scene = None
# The view watching this scene
view = None
# The players in the game
__movePlayer = None
__aiPlayers = None
__humanPlayers = None
def __init__(self, application, name):
"""Constructor for a chess game.
'application' is a reference to the glChess application.
'name' is the name of the game (string).
"""
self.application = application
self.__name = name
self.__aiPlayers = []
self.__humanPlayers = []
# Call parent constructor
game.ChessGame.__init__(self)
# Create a scene to render to
self.scene = SceneOpenGL(self)
self.cairoScene = SceneCairo(self)
self.view = View(self)
self.view.controller = application.ui.addView(name, self.view)
# Watch for piece moves with a player
self.__movePlayer = MovePlayer(self)
self.addSpectator(self.__movePlayer)
def getName(self):
"""Returns the name of the game (string)"""
return self.__name
def addAIPlayer(self, name, profile):
"""Create an AI player.
'name' is the name of the player to create (string).
'profile' is the the AI profile to use (ai.Profile).
Returns an AI player to use (game.ChessPlayer).
"""
description = "'" + name + "' in '" + self.__name + "'"
player = AIPlayer(self.application, name, profile, description)
self.__aiPlayers.append(player)
self.application.watchAIPlayer(player)
return player
def addHumanPlayer(self, name):
"""Create a human player.
'name' is the name of the player to create.
Returns a human player to use (game.ChessPlayer).
"""
player = HumanPlayer(self, name)
self.__humanPlayers.append(player)
return player
def playerIsHuman(self, player):
"""Test if a player is human.
'player' is the player to check (game.ChessPlayer).
Returns True if this is a human player in this game otherwise False.
"""
try:
if self.__humanPlayers.index(player) < 0:
return False
else:
return True
except ValueError:
return False
def toPGN(self, pgnGame):
"""Write the properties of this game into a PGN game.
'pgnGame' is the game to write into (pgn.PGNGame). All the tags should be unset.
"""
white = self.getWhite()
black = self.getBlack()
pgnGame.setTag(pgnGame.PGN_TAG_EVENT, self.getName())
pgnGame.setTag(pgnGame.PGN_TAG_WHITE, white.getName())
pgnGame.setTag(pgnGame.PGN_TAG_BLACK, black.getName())
if isinstance(white, ai.Player):
pgnGame.setTag('WhiteAI', white.getProfile().name)
if isinstance(black, ai.Player):
pgnGame.setTag('BlackAI', black.getProfile().name)
moves = self.getMoves()
while len(moves) > 0:
if len(moves) == 1:
pgnGame.addMove(moves[0].sanMove, None)
break
else:
pgnGame.addMove(moves[0].sanMove, moves[1].sanMove)
moves = moves[2:]
def animate(self, timeStep):
"""
"""
animating1 = self.scene.animate(timeStep)
animating2 = self.cairoScene.animate(timeStep)
return animating1 or animating2
def remove(self):
"""Remove this game"""
# Remove AI player windows
for player in self.__aiPlayers:
player.window.close()
self.application.unwatchAIPlayer(player)
# End the game
self.end()
# Remove the game from the UI
self.application._removeGame(self)
self.view.controller.close()
# Private methods
def _onPlayerMoved(self, player, move):
"""FIXME: Rename this
"""
self.view.controller.addMove(move)
class UI(gtkui.GtkUI):
"""
"""
__application = None
splashscreen = None
def __init__(self, application):
"""
"""
self.__application = application
gtkui.GtkUI.__init__(self)
self.splashscreen = Splashscreen(self)
self.setDefaultView(self.splashscreen)
def onAnimate(self, timeStep):
"""Called by UI"""
return self.__application.animate(timeStep)
def onReadFileDescriptor(self, fd):
"""Called by UI"""
try:
player = self.__application.aiPlayers[fd]
except KeyError:
return False
else:
player.read()
return True
def onGameStart(self, gameName, allowSpectators, whiteName, whiteType, blackName, blackType, moves = None):
"""Called by UI"""
g = self.__application.addGame(gameName, whiteName, whiteType, blackName, blackType)
print 'Starting game ' + gameName + ' between ' + whiteName + '(' + str(whiteType) + ') and ' + blackName + '(' + str(blackType) + ')'
if moves:
g.start(moves)
else:
g.start()
def loadGame(self, path, returnResult):
"""Called by ui.UI"""
try:
p = chess.pgn.PGN(path, 1)
except chess.pgn.Error, e:
self.reportError('Unable to open PGN file ' + path, str(e))
return
# Use the first game
pgnGame = p[0]
if returnResult is True:
whiteAI = pgnGame.getTag('WhiteAI')
blackAI = pgnGame.getTag('BlackAI')
msg = ''
if whiteAI and self.__application.getAIProfile(whiteAI) is None:
msg += "AI '" + whiteAI + "' is not installed, white player is now human"
whiteAI = None
if blackAI and self.__application.getAIProfile(blackAI) is None:
if msg != '':
msg += '\n'
msg += "AI '" + blackAI + "' is not installed, black player is now human"
blackAI = None
self.reportGameLoaded(gameName = pgnGame.getTag(pgnGame.PGN_TAG_EVENT),
whiteName = pgnGame.getTag(pgnGame.PGN_TAG_WHITE),
blackName = pgnGame.getTag(pgnGame.PGN_TAG_BLACK),
whiteAI = whiteAI, blackAI = blackAI,
moves = pgnGame.getMoves())
if len(msg) > 0:
self.reportError('Game modified', msg)
else:
g = self.__application.addPGNGame(pgnGame)
moves = pgnGame.getMoves()
if moves:
g.start(moves)
else:
g.start()
def onGameJoin(self, localName, localType, game):
"""Called by UI"""
print 'Joining game ' + str(game) + ' as ' + localName + '(' + str(localType) + ')'
def onQuit(self):
"""Called by UI"""
self.__application.quit()
#class GameDetector(network.GameDetector):
# """
# """
# def __init__(self, app):
# """
# """
# self.__app = app
# network.GameDetector.__init__(self)
#
# def onGameDetected(self, game):
# """Called by network.GameDetector"""
# self.__app.ui.addNetworkGame(game.name, game)
#
# def onGameRemoved(self, game):
# """Called by network.GameDetector"""
# self.__app.ui.removeNetworkGame(game)
class Application:
"""
"""
# The configuration
__config = None
# The glChess UI
ui = None
# The AI types
__aiProfiles = None
# AI players keyed by file descriptor
aiPlayers = None
# The network game detector
__detector = None
# The games present
__games = None
def __init__(self):
"""Constructor for glChess application"""
self.__aiProfiles = {}
self.__games = []
self.aiPlayers = {}
self.__config = Config()
self.__detector = None#GameDetector(self)
self.ui = UI(self)
def addAIProfile(self, profile):
"""Add a new AI profile into glChess.
'profile' is the profile to add (ai.Profile).
"""
name = profile.name
assert(self.__aiProfiles.has_key(name) is False)
self.__aiProfiles[name] = profile
self.ui.addAIEngine(name)
def getAIProfile(self, name):
"""Get an installed AI profile.
'name' is the name of the profile to get (string).
Return the profile (ai.Profile) or None if it does not exist.
"""
try:
return self.__aiProfiles[name]
except KeyError:
return None
def watchAIPlayer(self, player):
"""
"""
self.aiPlayers[player.fileno()] = player
self.ui.watchFileDescriptor(player.fileno())
def unwatchAIPlayer(self, player):
"""
"""
self.aiPlayers.pop(player.fileno())
def addGame(self, name, whiteName, whiteType, blackName, blackType):
"""Add a chess game into glChess.
'name' is the name of the game (string).
'whiteName' is the name of the white player (string).
'whiteType' is the AI profile to use for white (string) or None if white is human.
'blackName' is the name of the black player (string).
'blackType' is the AI profile to use for black (string) or None if black is human.
Returns the game object. Use game.start() to start the game.
"""
# Create the game
g = ChessGame(self, name)
self.__games.append(g)
msg = ''
if whiteType is None:
player = g.addHumanPlayer(whiteName)
else:
try:
profile = self.__aiProfiles[whiteType]
player = g.addAIPlayer(whiteName, profile)
except KeyError:
msg += "AI '" + whiteType + "' is not installed, white player is now human"
player = g.addHumanPlayer(whiteName)
g.setWhite(player)
if blackType is None:
player = g.addHumanPlayer(blackName)
else:
try:
profile = self.__aiProfiles[blackType]
player = g.addAIPlayer(blackName, profile)
except KeyError:
msg += "AI '" + blackType + "' is not installed, black player is now human"
player = g.addHumanPlayer(blackName)
g.setBlack(player)
if len(msg) > 0:
self.ui.reportError('Game modified', msg)
return g
def addPGNGame(self, pgnGame):
"""Add a PGN game.
'pgnGame' is the game to add (chess.pgn.PGNGame).
Returns the game object. Use game.start() to start the game.
"""
return self.addGame(pgnGame.getTag(pgnGame.PGN_TAG_EVENT),
pgnGame.getTag(pgnGame.PGN_TAG_WHITE),
pgnGame.getTag('WhiteAI'),
pgnGame.getTag(pgnGame.PGN_TAG_BLACK),
pgnGame.getTag('BlackAI'))
def addMove(self, view, move):
# TEMP
self.ui.addMove(view, move)
def start(self):
"""Run glChess.
This method does not return.
"""
print 'This is glChess 1.0RC1'
# Load AI profiles
profiles = ai.loadProfiles()
for p in profiles:
p.detect()
if p.path is not None:
print 'Detected AI: ' + p.name + ' at ' + p.path
self.addAIProfile(p)
nArgs = len(sys.argv)
# Load existing games
if nArgs == 1:
self.__autoload()
# Load requested game
# TODO: Merge this with the UI requested load games
elif nArgs == 2:
path = sys.argv[1]
try:
p = chess.pgn.PGN(path, 1)
except chess.pgn.Error, e:
# TODO: Pop-up dialog
print e
else:
# Use the first game
pgnGame = p[0]
g = self.addPGNGame(pgnGame)
moves = pgnGame.getMoves()
if moves:
g.start(moves)
else:
g.start()
else:
print 'Usage: ' + sys.argv[0] + ' [PGN file]'
sys.exit()
# Start UI (does not return)
try:
self.ui.run()
except:
print 'glChess has crashed. Please report this bug to http://glchess.sourceforge.net'
print 'Debug output:'
print traceback.format_exc()
self.quit()
sys.exit(1)
def animate(self, timeStep):
"""
"""
animating = False
for g in self.__games:
if g.animate(timeStep):
animating = True
return animating
def quit(self):
"""Quit glChess"""
# Save any open games
self.__autosave()
# End current games (will delete AIs etc)
for game in self.__games[:]:
game.end()
# Exit the application
sys.exit()
# Private methods
def _removeGame(self, g):
"""
"""
self.__games.remove(g)
def __autoload(self):
"""Restore games from the autosave file"""
path = self.__config.getAutosavePath()
print 'Auto-loading from ' + path + '...'
try:
p = chess.pgn.PGN(path)
games = p[:]
except chess.pgn.Error, e:
print 'Invalid autoload file ' + path + ': ' + str(e)
games = []
except IOError, e:
print 'Unable to autoload from ' + path + ': ' + str(e)
games = []
# Delete the file once loaded
try:
os.unlink(path)
except OSError:
pass
# Restore each game
for pgnGame in games:
g = self.addPGNGame(pgnGame)
g.start(pgnGame.getMoves())
def __autosave(self):
"""Save any open games to the autosave file"""
if len(self.__games) == 0:
return
fname = self.__config.getAutosavePath()
print 'Auto-saving to ' + fname + '...'
f = file(fname, 'a')
for g in self.__games:
pgnGame = chess.pgn.PGNGame()
g.toPGN(pgnGame)
lines = pgnGame.getLines()
for line in lines:
f.write(line + '\n')
f.write('\n')
f.close()
if __name__ == '__main__':
gettext.textdomain('gnome-games')
app = Application()
app.start()

View file

@ -0,0 +1,5 @@
glchessdir = $(pythondir)/glchess/network
glchess_PYTHON = \
announce.py \
__init__.py \
protocol.py

View file

@ -0,0 +1,2 @@
from protocol import *
from announce import *

127
src/lib/network/announce.py Normal file
View file

@ -0,0 +1,127 @@
"""
"""
#avahi = __import__('avahi')
#dbus = __import__('dbus')
import avahi
import dbus
class RemoteGame:
"""
"""
def __init__(self, name, address):
"""
"""
self.name = name
self.address = address
class GameReporter:
"""
"""
def __init__(self, name, port):
"""Constructor.
'name' is the name of the game started (string).
'port' is the UDP/IP port the game is running on (integer).
"""
self.name = name
self.port = port
# Connect to the Avahi server
bus = dbus.SystemBus()
self.server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
# Register this service
path = self.server.EntryGroupNew()
group = dbus.Interface(bus.get_object(avahi.DBUS_NAME, path), avahi.DBUS_INTERFACE_ENTRY_GROUP)
n = name
index = 1
while True:
try:
group.AddService(avahi.IF_UNSPEC, avahi.PROTO_INET, 0, n, '_glchess._udp', '', '', port, avahi.string_array_to_txt_array(['hi=gi']))
except dbus.dbus_bindings.DBusException:
index += 1
n = name + ' (' + str(index) + ')'
else:
break
group.Commit()
class GameDetector:
"""Class to detect glChess network games.
"""
# The known about games
__games = None
__gamesByName = None
def __init__(self):
"""Constructor"""
self.__games = []
self.__gamesByName = {}
# Connect to the Avahi server
bus = dbus.SystemBus()
self.server = dbus.Interface(bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER)
# Listen for glChess servers
# FIXME: Can raise a dbus_bindings.DBusException (local name collision)
browser = self.server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_INET, '_glchess._udp', '', 0)
listener = dbus.Interface(bus.get_object(avahi.DBUS_NAME, browser), avahi.DBUS_INTERFACE_SERVICE_BROWSER)
listener.connect_to_signal('ItemNew', self.__serviceDetected)
listener.connect_to_signal('ItemRemove', self.__serviceRemoved)
# Methods to extend
def onGameDetected(self, game):
"""Called when a game is detected.
'game' is the game that has been detected (RemoteGame).
"""
pass
def onGameRemoved(self, game):
"""Called when a game is removed.
'game' is the game that has been removed (RemoteGame).
"""
pass
# Public methods
def getGames(self):
"""Returns a list of games that are known of"""
return self.__games[:]
# Private methods
def __serviceDetected(self, interface, protocol, name, stype, domain, flags):
"""D-Dbus callback"""
# Get information on this service
self.server.ResolveService(interface, protocol, name, stype, domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
reply_handler = self.__serverResolved, error_handler = self.__resolveError)
def __serverResolved(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags):
"""D-Dbus callback"""
#print avahi.txt_array_to_string_array(txt)
print 'Game detected: ' + str(address) + ':' + str(port) + ' (' + name + ')'
assert(not self.__gamesByName.has_key(name))
game = RemoteGame(name, (address, port))
self.__gamesByName[name] = game
self.__games.append(game)
self.onGameDetected(game)
def __resolveError(self, error):
"""D-Dbus callback"""
print 'Avahi/D-Bus error: ' + repr(error)
def __serviceRemoved(self, interface, protocol, name, stype, domain, flags):
"""D-Dbus callback"""
print 'Game removed: ' + str(name)
game = self.__gamesByName.pop(name)
self.__games.remove(game)
self.onGameRemoved(game)

352
src/lib/network/protocol.py Normal file
View file

@ -0,0 +1,352 @@
"""
"""
def decodeMessage(data):
"""
"""
fields = []
lines = data.split('\n')
(sequenceNumber, messageType) = lines[0].split(' ')
try:
sequenceNumber = int(sequenceNumber)
except ValueError:
sequenceNumber = None
fieldName = None
fieldValue = None
for line in lines[1:]:
(name, value) = line.split('=', 1)
if name == '~':
assert(fieldName is not None)
fieldValue += '\n' + value
else:
if fieldName is not None:
fields.append((fieldName, fieldValue))
fieldName = name
fieldValue = value
if fieldName is not None:
fields.append((fieldName, fieldValue))
return (sequenceNumber, messageType, fields)
def encodeMessage(sequenceNumber, messageType, fields):
"""
"""
data = str(sequenceNumber) + ' ' + messageType + '\n'
for (name, value) in fields:
data += name + '='
lines = value.split('\n')
data += lines[0]
for line in lines[1:]:
data += '\n~=' + line
data += '\n'
return data[:-1]
class OutgoingMessage:
"""
"""
sequenceNumber = None
data = None
def __init__(self, sequenceNumber, data):
"""
"""
self.sequenceNumber = sequenceNumber
self.data = data
class Sender:
"""
"""
# Sequence number to mark message with
__sequenceNumber = 0
# Messages waiting to be sent
__queuedMessages = None
# Timout in seconds sending a message
TIMEOUT = 1.0
# Number of times to send a message until assuming the receiver has died
RETRIES = 3
def __init__(self):
"""Constructor"""
self.__queuedMessages = []
# Methods to extend
def onOutgoingMessage(self, data):
"""Called when a message is generated to be sent.
'data' is the raw message to send (string).
"""
pass
def startTimer(self):
"""Start the message retry timer.
If the timer expires call retryMessage().
"""
pass
def stopTimer():
"""Stop the retry timer"""
pass
# Public methods
def sendAcknowledge(self, sequenceNumber):
"""Send an acknowledge message.
'sequenceNumber' is the sequence number of the message being acknowledged (int).
"""
d = encodeMessage('*', 'ACK', [('seq', str(sequenceNumber))])
self.onOutgoingMessage(d)
def queueMessage(self, messageType, fields = []):
"""Queue a message for sending.
'messageType' is the type of message (string).
'fields' is a list of containing (name, value) pairs for message fields.
"""
# Encode with the message header
self.__sequenceNumber = (self.__sequenceNumber + 1) % 1000
data = encodeMessage(self.__sequenceNumber, messageType, fields)
self.__queuedMessages.append(OutgoingMessage(self.__sequenceNumber, data))
# Send if no other queued messages
if len(self.__queuedMessages) == 1:
self.startTimer()
self.onOutgoingMessage(data)
def acknowledgeMessage(self, sequenceNumber):
"""Confirm a message has been received at the far end.
'sequenceNumber' is the sequence number that has been acknowledged (int).
"""
# If this matches the last message sent then remove it from the message queue
if len(self.__queuedMessages) == 0:
return
if sequenceNumber != self.__queuedMessages[0].sequenceNumber:
return
self.__queuedMessages = self.__queuedMessages[1:]
# Send the next message
if len(self.__queuedMessages) > 0:
self.startTimer()
self.onOutgoingMessage(self.__queuedMessages[0].data)
else:
self.stopTimer()
def retryMessage(self):
"""Resend the last message"""
try:
data = self.__queuedMessages[0].data
except IndexError:
return
self.startTimer()
self.onOutgoingMessage(data)
class Receiver:
"""
"""
# Expected sequence number to receive
__expectedSequenceNumber = None
__processing = False
__queue = None
def __init__(self):
"""Constructor"""
self.__queue = []
# Methods to extend
def processMessage(self, messageType, fields):
"""Called when a message is available for processing.
'messageType' is the message type (string).
'fields' is a dictionary of field values keyed by field name.
"""
pass
# Public methods
def filterMessage(self, sequenceNumber, messageType, fields):
"""Check the header fields on a message.
'sequenceNumber' is the sequence number of the incoming message (int).
'messageType' is the type of message (string).
'fields' is a dictionary of field values keyed by field name.
processMessage() is called if this message has the correct sequence number.
"""
# Stop recursion
if self.__processing:
self.__queue.append((sequenceNumber, messageType, fields))
return
# Check sequence number matches
if sequenceNumber is not None:
expected = self.__expectedSequenceNumber
if expected is not None and sequenceNumber != expected:
return
self.__expectedSequenceNumber = (sequenceNumber + 1) % 1000
# Pass to higher level
self.__processing = True
self.processMessage(messageType, fields)
self.__processing = False
# Process any messages received while in the callback
while len(self.__queue) > 0:
(sequenceNumber, messageType, fields) = self.__queue[0]
self.__queue = self.__queue[1:]
self.filterMessage(sequenceNumber, messageType, fields)
class StateMachine(Receiver, Sender):
"""
"""
def __init__(self):
"""Constructor"""
Sender.__init__(self)
Receiver.__init__(self)
# Methods to extend
def onOutgoingMessage(self, message):
"""Called when a message is generated to send.
'message' is the message to send (string).
"""
pass
def processMessage(self, messageType, fields):
"""Called when a message is available for processing.
'messageType' is the message type (string).
'fields' is a dictionary of field values keyed by field name.
"""
pass
# Public methods
def sendMessage(self, messageType, fields):
"""Send a message.
'messageType' is the message type to send (string made up of A-Z).
'fields' is a list of 2-tuples containing field names and values (strings).
"""
self.queueMessage(messageType, fields)
def registerIncomingMessage(self, message):
"""Register a received message.
'message' is the raw received message (string).
"""
(sequenceNumber, messageType, fields) = decodeMessage(message)
fields = dict(fields)
# FIXME: 'seq' could be an invalid integer
if messageType == 'ACK':
try:
seq = int(fields['seq'])
except (KeyError, ValueError):
return
self.acknowledgeMessage(seq)
return
# Acknowledge this message
# FIXME: We should check its sequence number first
self.sendAcknowledge(sequenceNumber)
# Process it
self.filterMessage(sequenceNumber, messageType, fields)
class Encoder:
"""
"""
__sequenceNumber = 0
def onOutgoingMessage(self, message):
"""
"""
pass
# Public methods
def __sendMessage(self, messageType, fields = []):
"""
"""
self.__sequenceNumber += 1
if self.__sequenceNumber > 999:
self.__sequenceNumber = 0
d = encodeMessage(messageType, [('seq', str(self.__sequenceNumber)), ('src', self.__sourceAddress), ('dst', self.__destinationAddress)].join(fields))
self.onOutgoingMessage(d)
def sendAcknowledge(self):
"""
"""
self.__sendMessage('ACK')
def sendNotAcknowledge(self, error):
"""
"""
self.__sendMessage('NACK', [('error', error)])
def sendJoin(self, name, playerType):
"""
"""
self.__sendMessage('JOIN', [('name', name), ('type', playerType)])
def sendLeave(self, name, reason):
"""
"""
self.__sendMessage('LEAVE', [('name', name), ('reason', reason)])
def sendMove(self, player, move):
"""
"""
self.__sendMessage('MOVE', [('player', player), ('move', move)])
def sendGameAnnounce(self, name, result, white = None, black = None, spectators = [], player = None, moves = []):
"""
"""
fields = [('name', name), ('result', result)]
if white is not None:
fields.append(('white', white))
if black is not None:
fields.append(('black', black))
if player is not None:
fields.append(('player', player))
for player in spectators:
fields.append(('spectator', player))
for move in moves:
fields.append(('move', move))
self.__sendMessage('GAME', fields)
if __name__ == '__main__':
x = encodeMessage('TEST', [('field1', 'value1'), ('long_field', 'This is a long message.\nBlah Blah\n\nAll done\n')])
print x
(t, f) = decodeMessage(x)
print t
print f
x = encodeMessage('X', [])
print x
(t, f) = decodeMessage(x)
print t
print f

View file

@ -0,0 +1,6 @@
SUBDIRS = cairo opengl
glchessdir = $(pythondir)/glchess/scene
glchess_PYTHON = \
human.py \
__init__.py

127
src/lib/scene/__init__.py Normal file
View file

@ -0,0 +1,127 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
# Highlight types
HIGHLIGHT_SELECTED = 'selected'
HIGHLIGHT_CAN_MOVE = 'canMove'
class ChessSet:
"""
"""
def drawPiece(self, pieceName, state, context = None):
"""Draw a piece.
'pieceName' is the piece name (string).
'state' is the piece state (string).
'context' is a reference to the rendering context being used (user-defined).
"""
pass
class ChessPiece:
"""Abstract class for a glChess chess piece model"""
def move(self, coord):
"""Move this piece to a board location.
'coord' is a 2-tuple containing the file and rank of the board location to move to.
The values are:
Black
(0,7) +-----+ (7,7)
| |
| |
(0,0) +-----+ (7,0)
White
"""
pass
class Scene:
"""Abstract class for glChess scenes
Extend this class to make a scene
"""
# Metheds to extend by a higher class
def onRedraw(self):
"""This method is called when the scene needs redrawing"""
pass
def startAnimation(self):
"""Called when the animate() method should be called"""
pass
# Methods to implement
def reshape(self, width, height):
"""Resize the viewport into the scene.
'width' is the width of the viewport in pixels.
'height' is the width of the viewport in pixels.
"""
pass
def addChessPiece(self, chessSet, name, coord):
"""Add a chess piece model into the scene.
'chessSet' is the name of the chess set (string).
'name' is the name of the piece (string).
'coord' is the the chess board location of the piece (tuple, (file,rank)).
Returns a reference to this chess piece or raises an exception.
"""
raise Exception('Not implemented')
def removeChessPiece(self, piece):
"""Remove chess piece.
'piece' is a chess piece instance as returned by addChessPiece().
"""
pass
def setBoardHighlight(self, coords):
"""Highlight a square on the board.
'coords' is a dictionary of highlight types keyed by square co-ordinates.
The co-ordinates are a tuple in the form (file,rank).
If None the highlight will be cleared.
"""
pass
def setBoardRotation(self, angle):
"""Set the rotation on the board.
'angle' is the angle the board should be drawn at in degress (float, [0.0, 360.0]).
"""
pass
def animate(self, timeStep):
"""Animate the scene.
'timeStep' is the time since this method has last been called in seconds (float).
Returns False once all animation is complete otherwise returns True. Once animation
is complete do not call this method again until startAnimation() is called.
"""
pass
def render(self, context):
"""Manually render the scene.
'context' TODO
"""
pass
def getSquare(self, x, y):
"""Find the chess square at a given 2D location.
'x' is the number of pixels from the left of the scene to select.
'y' is the number of pixels from the bottom of the scene to select.
This requires an OpenGL context.
Return the co-ordinate in LAN format (string) or None if no square at this point.
"""
return None

View file

@ -0,0 +1,4 @@
glchessdir = $(pythondir)/glchess/scene/cairo
glchess_PYTHON = \
__init__.py \
pieces.py

View file

@ -0,0 +1,297 @@
import math
import glchess.scene
import pieces
BACKGROUND_COLOUR = (0.53, 0.63, 0.75)
BORDER_COLOUR = (0.808, 0.361, 0.0)#(0.757, 0.490, 0.067)#(0.36, 0.21, 0.05)
BLACK_SQUARE_COLOURS = {None: (0.8, 0.8, 0.8), glchess.scene.HIGHLIGHT_SELECTED: (0.3, 1.0, 0.3), glchess.scene.HIGHLIGHT_CAN_MOVE: (0.3, 0.3, 1.0)}
WHITE_SQUARE_COLOURS = {None: (1.0, 1.0, 1.0), glchess.scene.HIGHLIGHT_SELECTED: (0.2, 1.0, 0.0), glchess.scene.HIGHLIGHT_CAN_MOVE: (0.2, 0.2, 0.8)}
PIECE_COLOUR = (0.0, 0.0, 0.0)
class ChessPiece(glchess.scene.ChessPiece):
"""
"""
__scene = None
name = None
__targetPos = None
pos = None
moving = False
def __init__(self, scene, name, startPos = (0.0, 0.0)):
"""
"""
self.__scene = scene
self.name = name
self.pos = self.__coordToLocation(startPos)
def __coordToLocation(self, coord):
"""
"""
rank = ord(coord[0]) - ord('a')
file = ord(coord[1]) - ord('1')
return (float(rank), float(file))
def move(self, coord):
"""Extends glchess.scene.ChessPiece"""
self.__targetPos = self.__coordToLocation(coord)
self.moving = True
self.__scene._startAnimation()
def draw(self, state = 'default'):
"""
"""
pass
def animate(self, timeStep):
"""
Return True if the piece has moved otherwise False.
"""
if self.__targetPos is None:
return False
if self.pos == self.__targetPos:
self.__targetPos = None
return False
# Get distance to target
dx = self.__targetPos[0] - self.pos[0]
dy = self.__targetPos[1] - self.pos[1]
# Get movement step in each direction
SPEED = 4.0 # FIXME
xStep = timeStep * SPEED
if xStep > abs(dx):
xStep = dx
else:
xStep *= cmp(dx, 0.0)
yStep = timeStep * SPEED
if yStep > abs(dy):
yStep = dy
else:
yStep *= cmp(dy, 0.0)
# Move the piece
self.pos = (self.pos[0] + xStep, self.pos[1] + yStep)
return True
def render(self, offset, context):
"""
"""
x = offset[0] + self.pos[0] * self.__scene.squareSize + self.__scene.PIECE_BORDER
y = offset[1] + (7 - self.pos[1]) * self.__scene.squareSize + self.__scene.PIECE_BORDER
pieces.piece(self.name, context, self.__scene.pieceSize, x, y)
context.fill()
class Scene(glchess.scene.Scene):
"""
"""
__pieces = None
__highlights = None
__animating = False
__changed = True
BORDER = 6.0
PIECE_BORDER = 2.0
def __init__(self):
"""Constructor for a Cairo scene"""
self.__highlight = {}
self.__pieces = []
def onRedraw(self):
"""This method is called when the scene needs redrawing"""
pass
def _startAnimation(self):
"""
"""
self.__changed = True
if self.__animating is False:
self.__animating = True
self.startAnimation()
def addChessPiece(self, chessSet, name, coord):
"""Add a chess piece model into the scene.
'chessSet' is the name of the chess set (string).
'name' is the name of the piece (string).
'coord' is the the chess board location of the piece in LAN format (string).
Returns a reference to this chess piece or raises an exception.
"""
name = chessSet + name[0].upper() + name[1:]
piece = ChessPiece(self, name, coord)
self.__pieces.append(piece)
# Redraw the scene
self.__changed = True
self.onRedraw()
return piece
def removeChessPiece(self, piece):
"""Remove chess piece.
'piece' is a chess piece instance as returned by addChessPiece().
"""
self.__pieces.remove(piece)
self.__changed = True
self.onRedraw()
def setBoardHighlight(self, coords):
"""Highlight a square on the board.
'coords' is a dictionary of highlight types keyed by square co-ordinates.
The co-ordinates are a tuple in the form (file,rank).
If None the highlight will be cleared.
"""
self.__changed = True
if coords is None:
self.__highlight = {}
else:
self.__highlight = coords.copy()
self.onRedraw()
def reshape(self, width, height):
"""Resize the viewport into the scene.
'width' is the width of the viewport in pixels.
'height' is the width of the viewport in pixels.
"""
self.__changed = True
self.width = width
self.height = height
shortEdge = min(self.width, self.height)
self.squareSize = (shortEdge - 2.0*self.BORDER) / 9.0
self.pieceSize = self.squareSize - 2.0*self.PIECE_BORDER
boardWidth = self.squareSize * 9.0
self.offset = ((self.width - boardWidth) / 2.0, (self.height - boardWidth) / 2.0)
self.__changed = True
self.onRedraw()
def setBoardRotation(self, angle):
"""Set the rotation on the board.
'angle' is the angle the board should be drawn at in degress (float, [0.0, 360.0]).
"""
pass
def animate(self, timeStep):
"""Extends glchess.scene.Scene"""
redraw = False
for piece in self.__pieces:
if piece.animate(timeStep):
piece.moving = True
redraw = True
else:
# Redraw static scene once pieces stop
if piece.moving:
redraw = True
self.__changed = True
piece.moving = False
# Redraw scene or stop animation
if redraw:
self.__animating = True
self.onRedraw()
else:
self.__animating = False
return self.__animating
def renderStatic(self, context):
"""Render the static elements in a scene.
"""
if self.__changed is False:
return False
self.__changed = False
# Clear background
context.set_source_rgb(*BACKGROUND_COLOUR)
context.paint()
# Draw border
context.set_source_rgb(*BORDER_COLOUR)
context.rectangle(self.offset[0], self.offset[1], self.squareSize * 9.0, self.squareSize * 9.0)
context.fill()
offset = (self.offset[0] + self.squareSize * 0.5, self.offset[1] + self.squareSize * 0.5)
for i in xrange(8):
for j in xrange(8):
x = offset[0] + i * self.squareSize
y = offset[1] + (7 - j) * self.squareSize
coord = chr(ord('a') + i) + chr(ord('1') + j)
try:
highlight = self.__highlight[coord]
except KeyError:
highlight = None
context.rectangle(x, y, self.squareSize, self.squareSize)
if (i + j) % 2 == 0:
colour = BLACK_SQUARE_COLOURS[highlight]
else:
colour = WHITE_SQUARE_COLOURS[highlight]
context.set_source_rgb(*colour)
context.fill()
context.set_source_rgb(*PIECE_COLOUR)
for piece in self.__pieces:
if piece.moving:
continue
piece.render(offset, context)
return True
def renderDynamic(self, context):
"""Render the dynamic elements in a scene.
This requires a Cairo context.
"""
offset = (self.offset[0] + self.squareSize * 0.5, self.offset[1] + self.squareSize * 0.5)
context.set_source_rgb(*PIECE_COLOUR)
for piece in self.__pieces:
if not piece.moving:
continue
piece.render(offset, context)
def getSquare(self, x, y):
"""Find the chess square at a given 2D location.
'x' is the number of pixels from the left of the scene to select.
'y' is the number of pixels from the bottom of the scene to select.
Return the co-ordinate in LAN format (string) or None if no square at this point.
"""
offset = (self.offset[0] + self.squareSize * 0.5, self.offset[1] + self.squareSize * 0.5)
rank = (x - offset[0]) / self.squareSize
if rank < 0 or rank >= 8.0:
return None
rank = int(rank)
file = (y - offset[1]) / self.squareSize
if file < 0 or file >= 8.0:
return None
file = 7 - int(file)
# Convert from co-ordinates to LAN format
rank = chr(ord('a') + rank)
file = chr(ord('1') + file)
return rank + file
# Private methods

File diff suppressed because one or more lines are too long

165
src/lib/scene/human.py Normal file
View file

@ -0,0 +1,165 @@
"""
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import glchess.scene
class SceneHumanInput:
"""
"""
# Flag to control if human input is enabled
__inputEnabled = True
# The selected square to move from
__startSquare = None
def __init__(self):
"""Constructor for a scene with human selectable components"""
pass
# Methods to extend
def onRedraw(self):
"""This method is called when the scene needs redrawing"""
pass
def playerIsHuman(self):
"""Check if the current player is a human.
Return True the current player is human else False.
"""
return False
def getSquare(self, x, y):
"""Find the chess square at a given 2D location.
'x' is the number of pixels from the left of the scene to select.
'y' is the number of pixels from the bottom of the scene to select.
Return the co-ordinate as a tuple in the form (file,rank) or None if
no square at this point.
"""
pass
def squareIsFriendly(self, coord):
"""Check if a given square contains a friendly piece.
Return True if this square contains a friendly piece.
"""
return False
def canMove(self, start, end):
"""Check if a move is valid.
'start' is the location to move from in LAN format (string).
'end' is the location to move from in LAN format (string).
"""
return False
def moveHuman(self, start, end):
"""Called when a human player moves.
'start' is the location to move from in LAN format (string).
'end' is the location to move from in LAN format (string).
"""
pass
def setBoardHighlight(self, coords):
"""Called when a human player changes the highlighted squares.
'coords' is a list or tuple of co-ordinates to highlight.
The co-ordinates are in LAN format (string).
If None the highlight should be cleared.
"""
pass
# Public methods
def enableHumanInput(self, inputEnabled):
"""Enable/disable human input.
'inputEnabled' is a flag to show if human input is enabled (True) or disabled (False).
"""
if inputEnabled is False:
self.__startSquare = None
self.setBoardHighlight(None)
self.__inputEnabled = inputEnabled
def select(self, x, y):
"""
"""
if self.__inputEnabled is False:
return
# Only bother if the current player is human
if self.playerIsHuman() is False:
return
# Get the selected square
coord = self.getSquare(x, y)
if coord is None:
return
# If this is a friendly piece then select it
if self.squareIsFriendly(coord):
self.__startSquare = coord
# Highlight the squares that can be moved to
highlights = {}
for file in '12345678':
for rank in 'abcdefgh':
if self.canMove(coord, rank + file):
highlights[rank + file] = glchess.scene.HIGHLIGHT_CAN_MOVE
highlights[coord] = glchess.scene.HIGHLIGHT_SELECTED
self.setBoardHighlight(highlights)
else:
# If we have already selected a start move try
# and move to this square
if self.__startSquare is not None:
self.__move(self.__startSquare, coord)
# Redraw the scene
self.onRedraw()
return coord
def deselect(self, x, y):
"""
"""
if self.__inputEnabled is False:
return
# Only bother if the current player is human
if self.playerIsHuman() is False:
return
# Get the selected square
coord = self.getSquare(x, y)
if coord is None:
return
# Attempt to move here
if self.__startSquare is not None and self.__startSquare != coord:
self.__move(self.__startSquare, coord)
# Redraw the scene
self.onRedraw()
return coord
# Private methods
def __move(self, start, end):
"""Attempt to make a move.
...
"""
if self.canMove(start, end) is False:
return
self.__selectedSquare = None
self.setBoardHighlight(None)
self.moveHuman(start, end)

View file

@ -0,0 +1,8 @@
glchessdir = $(pythondir)/glchess/scene/opengl
glchess_PYTHON = \
builtin_models.py \
__init__.py \
new_models.py \
opengl.py \
texture.py \
png.py

View file

@ -0,0 +1,17 @@
try:
import OpenGL.GL
except ImportError:
import glchess.scene
class Piece(glchess.scene.ChessPiece):
pass
class Scene(glchess.scene.Scene):
def __init__(self):
pass
def addChessPiece(self, chessSet, name, coord):
return Piece()
else:
from opengl import *

View file

@ -0,0 +1,527 @@
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
import math
from OpenGL.GL import *
import glchess.scene
import texture
from glchess.defaults import *
# HACK
import os.path
WHITE_BASE = (0.95, 0.81, 0.64)
WHITE_AMBIENT = (0.4*WHITE_BASE[0], 0.4*WHITE_BASE[1], 0.4*WHITE_BASE[2], 1.0)
WHITE_DIFFUSE = (0.7*WHITE_BASE[0], 0.7*WHITE_BASE[1], 0.7*WHITE_BASE[2], 1.0)
WHITE_SPECULAR = (1.0*WHITE_BASE[0], 1.0*WHITE_BASE[1], 1.0*WHITE_BASE[2], 1.0)
WHITE_SHININESS = 64.0
BLACK_BASE = (0.62, 0.45, 0.28)
BLACK_AMBIENT = (0.4*BLACK_BASE[0], 0.4*BLACK_BASE[1], 0.4*BLACK_BASE[2], 1.0)
BLACK_DIFFUSE = (0.7*BLACK_BASE[0], 0.7*BLACK_BASE[1], 0.7*BLACK_BASE[2], 1.0)
BLACK_SPECULAR = (1.0*BLACK_BASE[0], 1.0*BLACK_BASE[1], 1.0*BLACK_BASE[2], 1.0)
BLACK_SHININESS = 64.0
class BuiltinSet(glchess.scene.ChessSet):
"""
"""
# The models
__pawn = None
__rook = None
__knight = None
__bishop = None
__queen = None
__king = None
# A dictionary of models indexed by name
__modelsByName = None
# The rotation in degrees of pieces in the set (i.e. 0.0 for white and 180.0 for black)
__rotation = 0.0
__stateColours = None
__defaultState = None
# The display lists for each model
__displayLists = None
# The model texture
__texture = None
def __init__(self, textureFileName, ambient, diffuse, specular, shininess):
self.__pawn = Pawn()
self.__rook = Rook()
self.__knight = Knight()
self.__bishop = Bishop()
self.__queen = Queen()
self.__king = King()
self.__modelsByName = {'pawn': self.__pawn,
'rook': self.__rook,
'knight': self.__knight,
'bishop': self.__bishop,
'queen': self.__queen,
'king': self.__king}
self.__displayLists = {}
self.__stateColours = {}
self.__texture = texture.Texture(textureFileName, ambient = ambient, diffuse = diffuse,
specular = specular, shininess = shininess)
def setRotation(self, theta):
"""
"""
self.__rotation = theta
def addState(self, name, colour, default = False):
"""
"""
self.__stateColours[name] = colour
if default is True:
self.__defaultState = colour
def drawPiece(self, pieceName, state, context):
"""Draw a piece.
'pieceName' is the piece name (string).
'state' is the piece state (string).
'context' is a reference to the openGL context being used (user-defined).
If a context is provided then the models are rendered using display lists.
"""
glRotatef(self.__rotation, 0.0, 1.0, 0.0)
# Draw as white if textured
if glGetBoolean(GL_TEXTURE_2D):
glColor3f(1.0, 1.0, 1.0)
else:
try:
colour = self.__stateColours[state]
except KeyError:
colour = self.__defaultState
glColor3fv(colour)
self.__texture.bind()
# Render to a display list for optimisation
# TODO: This lists should be able to be shared between colours and games
try:
list = self.__displayLists[pieceName]
except KeyError:
# Get model to render
piece = self.__modelsByName[pieceName]
# Attempt to make an optimised list, if none available just render normally
list = self.__displayLists[pieceName] = glGenLists(1)
if list == 0:
piece.draw()
return
# Draw the model
glNewList(list, GL_COMPILE)
piece.draw()
glEndList()
# Draw pre-rendered model
glCallList(list)
class WhiteBuiltinSet(BuiltinSet):
"""
"""
def __init__(self):
BuiltinSet.__init__(self, os.path.join(IMAGE_DIR, 'piece.png'), WHITE_AMBIENT, WHITE_DIFFUSE, WHITE_SPECULAR, WHITE_SHININESS)
self.setRotation(180.0)
self.addState('unselected', (0.9, 0.9, 0.9), default = True)
class BlackBuiltinSet(BuiltinSet):
"""
"""
def __init__(self):
BuiltinSet.__init__(self, os.path.join(IMAGE_DIR, 'piece.png'), BLACK_AMBIENT, BLACK_DIFFUSE, BLACK_SPECULAR, BLACK_SHININESS)
self.addState('unselected', (0.2, 0.2, 0.2), default = True)
class SimpleModel:
"""
"""
pos = None
def start(self):
pass
def revolve(self, coords, divisions = 16, scale = 1.0, maxHeight = None):
"""
"""
# Get the number of points
# If less than two points, can not revolve
if len(coords) < 2:
raise TypeError() # FIXME
# If the max_height hasn't been defined, find it
if maxHeight is None:
maxHeight = 0.0
for coord in coords:
if maxHeight < coord[1]:
maxHeight = coord[1]
# Draw the revolution
sin_ptheta = 0.0
cos_ptheta = 1.0
norm_ptheta = 0.0
for division in range(1, divisions+1):
theta = 2.0 * (float(division) * math.pi) / float(divisions)
sin_theta = math.sin(theta)
cos_theta = math.cos(theta)
norm_theta = theta / (2.0 * math.pi)
coord = coords[0]
pradius = coord[0] * scale
pheight = coord[1] * scale
for coord in coords[1:]:
radius = coord[0] * scale
height = coord[1] * scale
# Get the normalized lengths of the normal vector
dradius = radius - pradius
dheight = height - pheight
length = math.sqrt(dradius * dradius + dheight * dheight)
dradius /= length
dheight /= length
normal = (dheight, -dradius)
# Rotate the normal
n0 = (normal[0] * sin_ptheta, normal[1], normal[0] * cos_ptheta)
n1 = (normal[0] * sin_theta, normal[1], normal[0] * cos_theta)
#
# | |
# | | _ height1
# | \
# | \
# | \ _ height0
# | |
#
# | |
# 0 r1 r0
#
# d +----------+ c - upper point
# | |
# | |
# | |
# | |
# a +----------+ b - lower point
#
# | |
# smaller larger
# angle angle
a = (pradius * sin_ptheta, pheight, pradius * cos_ptheta)
b = (pradius * sin_theta, pheight, pradius * cos_theta)
c = (radius * sin_theta, height, radius * cos_theta)
d = (radius * sin_ptheta, height, radius * cos_ptheta)
# Texture co-ordinates (conical transformation)
ta = self.__getTextureCoord(a, maxHeight)
tb = self.__getTextureCoord(b, maxHeight)
tc = self.__getTextureCoord(c, maxHeight)
td = self.__getTextureCoord(d, maxHeight)
# If only triangles required
if c == d:
glBegin(GL_TRIANGLES)
glNormal3fv(n0)
glTexCoord2fv(ta)
glVertex3fv(a)
glNormal3fv(n1)
glTexCoord2fv(tb)
glVertex3fv(b)
# FIXME: should have an average normal
glTexCoord2fv(tc)
glVertex3fv(c)
glEnd()
if a == b:
glBegin(GL_TRIANGLES)
glNormal3fv(n0)
glTexCoord2fv(ta)
glVertex3fv(a)
glNormal3fv(n1)
glTexCoord2fv(tc)
glVertex3fv(c)
# FIXME: should have an average normal
glVertex3fv(td)
glVertex3fv(d)
glEnd()
else:
# quads required
glBegin(GL_QUADS)
glNormal3fv(n0)
glTexCoord2fv(ta)
glVertex3fv(a)
glNormal3fv(n1)
glTexCoord2fv(tb)
glVertex3fv(b)
glNormal3fv(n1)
glTexCoord2fv(tc)
glVertex3fv(c)
glNormal3fv(n0)
glTexCoord2fv(td)
glVertex3fv(d)
glEnd()
pradius = radius
pheight = height
sin_ptheta = sin_theta
cos_ptheta = cos_theta
norm_ptheta = norm_theta
def __getTextureCoord(self, vertex, maxHeight):
"""
"""
# FIXME: Change to a hemispherical projection so the top is not so flat
# Conical transformation, get u and v based on vertex angle
u = vertex[0]
v = vertex[2]
# Normalise
length = math.sqrt(u**2 + v**2)
if length != 0.0:
u /= length
v /= length
# Maximum height is in the middle of the texture, minimum on the boundary
h = 1.0 - (vertex[1] / maxHeight)
return (0.5 + 0.5 * h * u, 0.5 + 0.5 * h * v)
def __drawVertex(self, vertex, maxHeight):
glTexCoord2fv(self.__getTextureCoord(vertex, maxHeight))
glVertex3fv(vertex)
def drawTriangles(self, triangles, verticies, normals, maxHeight):
"""
"""
glBegin(GL_TRIANGLES)
for t in triangles:
glNormal3fv(normals[t[0]])
v = t[1]
self.__drawVertex(verticies[v[0]], maxHeight)
self.__drawVertex(verticies[v[1]], maxHeight)
self.__drawVertex(verticies[v[2]], maxHeight)
glEnd()
def drawQuads(self, quads, verticies, normals, maxHeight):
"""
"""
glBegin(GL_QUADS)
for t in quads:
glNormal3fv(normals[t[0]])
v = t[1]
self.__drawVertex(verticies[v[0]], maxHeight)
self.__drawVertex(verticies[v[1]], maxHeight)
self.__drawVertex(verticies[v[2]], maxHeight)
self.__drawVertex(verticies[v[3]], maxHeight)
glEnd()
def end(self):
pass
def draw(self):
"""
"""
pass
class Pawn(SimpleModel):
"""
"""
# The co-ordinates of the revolution that makes the model
__revolveCoords = [(3.5, 0.0), (3.5, 2.0), (2.5, 3.0), (2.5, 4.0), (1.5, 6.0), (1.0, 8.8),
(1.8, 8.8), (1.0, 9.2), (2.0, 11.6), (1.0, 13.4), (0.0, 13.4)]
def __init__(self):
self.pos = (0.0, 0.0, 0.0)
pass
def draw(self):
self.revolve(self.__revolveCoords)
class Rook(SimpleModel):
"""
"""
# The co-ordinates of the revolution that makes the model
__revolveCoords = [(3.8, 0.0), (3.8, 2.0), (2.6, 5.0), (2.0, 10.2), (2.8, 10.2), (2.8, 13.6), (2.2, 13.6), (2.2, 13.0), (0.0, 13.0)]
def __init__(self):
self.pos = (0.0, 0.0, 0.0)
pass
def draw(self):
self.revolve(self.__revolveCoords)
class Knight(SimpleModel):
"""
"""
__maxHeight = 0.0
# The co-ordinates of the revolution that makes the model
__revolveCoords = [(4.1, 0.0), (4.1, 2.0), (2.0, 3.6), (2.0, 4.8), (2.6, 5.8)]
# The other polygons
__verticies = [(2.6, 5.8, 2.6), (-2.6, 5.8, 2.6), (-2.6, 5.8, -0.8), (2.6, 5.8, -0.8),
(0.8, 16.2, 4.0), (1.0, 16.8, 3.4), (-1.0, 16.8, 3.4), (-0.80, 16.2, 4.0),
(1.0, 16.8, 3.0), (-1.0, 16.8, 3.0), (0.5, 16.8, 1.6), (-0.5, 16.8, 1.6),
(1.0, 16.8, 0.20), (-1.0, 16.8, 0.20), (1.0, 16.8, -0.20), (-1.0, 16.8, -0.20),
(0.4, 16.8, -1.1), (-0.4, 16.8, -1.1), (1.0, 16.8, -2.0), (-1.0, 16.8, -2.0),
(1.0, 16.8, -4.4), (-1.0, 16.8, -4.4), (1.0, 15.0, -4.4), (-1.0, 15.0, -4.4),
(0.55, 14.8, -2.8), (-0.55, 14.8, -2.8), (-1.0, 14.0, 1.3), (-1.2, 13.8, 2.4),
(-0.8, 16.8, 0.20), (-1.2, 13.8, 0.20), (-0.82666667, 16.6, 0.20), (-1.0, 16.6, -0.38),
(-0.88, 16.2, 0.20), (-1.0, 16.2, -0.74), (-1.2, 13.6, -0.20), (-1.0, 15.8, -1.1),
(-0.6, 14.0, -1.4), (1.2, 13.8, 2.4), (1.0, 14.0, 1.3), (1.2, 13.8, 0.20),
(0.8, 16.8, 0.20), (0.82666667, 16.6, 0.20), (1.0, 16.6, -0.38), (1.2, 13.6, -0.20),
(1.0, 16.2, -0.74), (0.88, 16.2, 0.20), (0.6, 14.0, -1.4), (1.0, 15.8, -1.1),
(0.8, 16.4, -0.56), (0.61333334, 16.4, 0.20), (-0.61333334, 16.4, 0.20), (-0.8, 16.4, -0.56),
(0.35, 17.8, -0.8), (0.35, 17.8, -4.4), (-0.35, 17.8, -4.4), (-0.35, 17.8, -0.8),
(0.35, 16.8, -0.8), (0.35, 16.8, -4.4), (-0.35, 16.8, -4.4), (-0.35, 16.8, -0.8),
(0.0, 15.0, -3.6), (0.0, 7.8, -4.0), (-0.5, 13.8, 0.4), (-2.0, 8.8, 4.0),
(2.0, 8.8, 4.0), (0.5, 13.8, 0.4), (-1.4, 12.2, -0.4), (-1.1422222222, 12.2, -2.2222222222),
(1.4, 12.2, -0.4), (1.1422222222, 12.2, -2.2222222222), (1.44, 5.8, -2.6), (-1.44, 5.8, -2.6),
(0.0, 14.0, 4.0), (-0.45, 13.8, -0.20), (0.45, 13.8, -0.20)]
__normals = [(0.0, -1.0, 0.0), (0.0, 0.707107, 0.707107), (0.0, 1.0, 0.0), (0.0, 0.0, -1.0),
(-0.933878, 0.128964, -0.333528), (-0.966676, 0.150427, 0.207145), (-0.934057, 0.124541, -0.334704), (-0.970801, -0.191698, -0.144213),
(-0.97561, 0.219512, 0.0), (0.933878, 0.128964, -0.333528), (0.966676, 0.150427, 0.207145), (0.934057, 0.124541, -0.334704),
(0.970801, -0.191698, -0.144213), (0.97561, -0.219512, 0.0), (0.598246, 0.797665, 0.076372), (0.670088, -0.714758, 0.200256),
(-0.598246, 0.797665, 0.076372), (-0.670088, -0.714758, 0.200256), (1.0, 0.0, 0.0), (-1.0, 0.0, 0.0),
(0.0, 0.0, 1.0), (0.0, -0.853282, 0.52145), (0.0, -0.98387, -0.178885), (-0.788443, 0.043237, -0.613587),
(0.788443, 0.043237, -0.613587), (0.0, 0.584305, 0.811534), (0.0, -0.422886, 0.906183), (-0.969286, 0.231975, -0.081681),
(-0.982872, 0.184289, 0.0), (0.969286, 0.231975, -0.081681), (0.982872, 0.184289, 0.0), (0.81989, -0.220458, -0.528373),
(0.0, -0.573462, -0.819232), (-0.819890, -0.220459, -0.528373), (-0.752714, -0.273714, 0.59875), (-0.957338, 0.031911, 0.287202),
(-0.997785, 0.066519, 0.0), (0.752714, -0.273714, 0.59875), (0.957338, 0.031911, 0.287202), (0.997785, 0.066519, 0.0),
(0.0, -0.992278, 0.124035), (-0.854714, 0.484047, 0.187514), (-0.853747, 0.515805, -0.0711460), (0.854714, 0.484047, 0.187514),
(0.853747, 0.515805, -0.0711460), (0.252982, -0.948683, -0.189737), (0.257603, -0.966012, 0.021467), (0.126745, -0.887214, 0.443607),
(-0.252982, -0.948683, -0.189737), (-0.257603, -0.966012, 0.021467), (-0.126745, -0.887214, 0.443607), (0.000003, -0.668965, 0.743294),
(-0.000003, -0.668965, 0.743294), (-0.997484, 0.070735, 0.004796), (-0.744437, 0.446663, -0.496292), (0.997484, 0.070735, 0.004796),
(0.744437, 0.446663, -0.496292)]
__triangles = ((31, (3, 70, 61)), (32, (70, 71, 61)), (33, (2, 61, 71)), (20, (72, 4, 7)),
(34, (72, 7, 27)), (35, (27, 7, 6)), (36, (27, 6, 9)), (37, (72, 37, 4)),
(38, (37, 5, 4)), (39, (37, 8, 5)), (40, (72, 27, 37)), (41, (36, 66, 73)),
(42, (73, 66, 62)), (43, (46, 74, 68)), (44, (74, 65, 68)), (45, (46, 43, 74)),
(46, (65, 74, 43)), (47, (65, 43, 39)), (48, (36, 73, 34)), (49, (62, 34, 73)),
(50, (62, 29, 34)), (3, (45, 49, 41)), (51, (44, 42, 48)), (3, (32, 30, 50)),
(52, (33, 51, 31)), (53, (17, 19, 35)), (54, (15, 17, 35)), (55, (16, 47, 18)),
(56, (14, 47, 16)))
__quads = ((0, (0, 1, 2, 3)), (1, (4, 5, 6, 7)), (2, (5, 8, 9, 6)), (2, (8, 10, 11, 9)),
(2, (10, 12, 13, 11)), (2, (12, 14, 15, 13)), (2, (14, 16, 17, 15)), (2, (16, 18, 19, 17)),
(2, (18, 20, 21, 19)), (3, (21, 20, 22, 23)), (3, (23, 22, 24, 25)), (4, (9, 11, 26, 27)),
(5, (11, 28, 29, 26)), (6, (30, 28, 15, 31)), (6, (29, 32, 33, 34)), (6, (34, 33, 35, 36)),
(7, (19, 25, 36, 35)), (8, (19, 21, 23, 25)), (9, (8, 37, 38, 10)), (10, (10, 38, 39, 40)),
(11, (41, 42, 14, 40)), (11, (39, 43, 44, 45)), (11, (43, 46, 47, 44)), (12, (18, 47, 46, 24)),
(13, (18, 24, 22, 20)), (14, (45, 44, 48, 49)), (15, (49, 48, 42, 41)), (16, (32, 50, 51, 33)),
(17, (50, 30, 31, 51)), (2, (52, 53, 54, 55)), (18, (52, 56, 57, 53)), (19, (55, 54, 58, 59)),
(20, (52, 55, 59, 56)), (3, (53, 57, 58, 54)), (21, (26, 29, 39, 38)), (22, (26, 38, 37, 27)),
(23, (2, 25, 60, 61)), (24, (3, 61, 60, 24)), (25, (62, 63, 64, 65)), (26, (63, 1, 0, 64)),
(27, (62, 66, 1, 63)), (28, (66, 67, 2, 1)), (28, (25, 67, 66, 36)), (29, (65, 64, 0, 68)),
(30, (68, 0, 3, 69)), (30, (24, 46, 68, 69)))
def __init__(self):
self.pos = (0.0, 0.0, 0.0)
self.__maxHeight = 0.0
for v in self.__verticies:
if v[1] > self.__maxHeight:
self.__maxHeight = v[1]
def draw(self):
self.revolve(self.__revolveCoords, maxHeight = self.__maxHeight)
self.drawTriangles(self.__triangles, self.__verticies, self.__normals, self.__maxHeight)
self.drawQuads(self.__quads, self.__verticies, self.__normals, self.__maxHeight)
class Bishop(SimpleModel):
"""
"""
# The co-ordinates of the revolution that makes the model
__revolveCoords = [(4.0, 0.0), (4.0, 2.0), (2.5, 3.0), (2.5, 4.0), (1.5, 7.0), (1.2, 9.4), (2.5, 9.4), (1.7, 11.0),
(1.7, 12.2), (2.2, 13.2), (2.2, 14.8), (1.0, 16.0), (0.8, 17.0), (1.2, 17.7), (0.8, 18.4), (0.0, 18.4)]
def __init__(self):
self.pos = (0.0, 0.0, 0.0)
pass
def draw(self):
self.revolve(self.__revolveCoords)
class Queen(SimpleModel):
"""
"""
# The co-ordinates of the revolution that makes the model
__revolveCoords = [(4.8, 0.0), (4.8, 2.2), (3.4, 4.0), (3.4, 5.0), (1.8, 8.0), (1.4, 11.8), (2.9, 11.8),
(1.8, 13.6), (1.8, 15.2), (2.0, 17.8), (2.7, 19.2), (2.4, 20.0), (1.7, 20.0),
(0.95, 20.8), (0.7, 20.8), (0.9, 21.4), (0.7, 22.0), (0.0, 22.0)]
def __init__(self):
self.pos = (0.0, 0.0, 0.0)
pass
def draw(self):
self.revolve(self.__revolveCoords)
class King(SimpleModel):
"""
"""
__maxHeight = 0.0
# The co-ordinates of the revolution that makes the model
__revolveCoords = [(5.0, 0.0), (5.0, 2.0), (3.5, 3.0), (3.5, 4.6), (2.0, 7.6), (1.4, 12.6), (3.0, 12.6),
(2.0, 14.6), (2.0, 15.6), (2.8, 19.1), (1.6, 19.7), (1.6, 20.1), (0.0, 20.1)]
__verticies = [(-0.3, 20.1, 0.351), (0.3, 20.1, 0.35), (0.3, 23.1, 0.35), (-0.3, 23.1, 0.35),
(-0.9, 21.1, 0.35), (-0.3, 21.1, 0.35), (-0.3, 22.1, 0.35), (-0.9, 22.1, 0.35),
(0.9, 21.1, 0.35), (0.9, 22.1, 0.35), (0.3, 22.1, 0.35), (0.3, 21.1, 0.35),
(0.3, 20.1, -0.35), (-0.3, 20.1, -0.35), (-0.3, 23.1, -0.35), (0.3, 23.1, -0.35),
(-0.3, 21.1, -0.35), (-0.9, 21.1, -0.35), (-0.9, 22.1, -0.35), (-0.3, 22.1, -0.35),
(0.3, 21.1, -0.35), (0.3, 22.1, -0.35), (0.9, 22.1, -0.35), (0.9, 21.1, -0.35),
(-0.3, 20.1, 0.35), (-0.3, 22.1, 0.3), (-0.3, 23.1, 0.3), (-0.3, 23.1, -0.3),
(-0.3, 22.1, -0.3)]
__normals = [(0.0, 0.0, 1.0), (0.0, 0.0, -1.0), (-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)]
__quads = [(0, (0, 1, 2, 3)), (0, (4, 5, 6, 7)), (0, (8, 9, 10, 11)), (1, (12, 13, 14, 15)),
(1, (16, 17, 18, 19)), (1, (20, 21, 22, 23)), (2, (4, 7, 18, 17)), (2, (24, 5, 16, 13)),
(2, (25, 26, 27, 28)), (3, (23, 22, 9, 8)), (3, (12, 20, 11, 1)), (3, (21, 15, 2, 10)),
(4, (18, 7, 6, 19)), (4, (21, 10, 9, 22)), (4, (14, 3, 2, 15))]
def __init__(self):
self.pos = (0.0, 0.0, 0.0)
self.__maxHeight = 0.0
for v in self.__verticies:
if v[1] > self.__maxHeight:
self.__maxHeight = v[1]
def draw(self):
self.revolve(self.__revolveCoords, maxHeight = self.__maxHeight)
self.drawQuads(self.__quads, self.__verticies, self.__normals, self.__maxHeight)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,680 @@
import math
from OpenGL.GL import *
from OpenGL.GLU import *
from glchess.defaults import *
import glchess.scene
import texture
import new_models
builtin_models = new_models
SQUARE_WIDTH = 10.0
BOARD_DEPTH = 3.0
BOARD_BORDER = 5.0
BOARD_CHAMFER = 2.0
BOARD_INNER_WIDTH = (SQUARE_WIDTH * 8.0)
BOARD_OUTER_WIDTH = (BOARD_INNER_WIDTH + BOARD_BORDER * 2.0)
OFFSET = (BOARD_OUTER_WIDTH * 0.5)
LIGHT_AMBIENT_COLOUR = (0.4, 0.4, 0.4, 1.0)
LIGHT_DIFFUSE_COLOUR = (0.7, 0.7, 0.7, 1.0)
LIGHT_SPECULAR_COLOUR = (1.0, 1.0, 1.0, 1.0)
BOARD_AMBIENT = (0.2, 0.2, 0.2, 1.0)
BOARD_DIFFUSE = (0.8, 0.8, 0.8, 1.0)
BOARD_SPECULAR = (1.0, 1.0, 1.0, 1.0)
BOARD_SHININESS = 128.0
BACKGROUND_COLOUR = (0.53, 0.63, 0.75, 0.0)
BORDER_COLOUR = (0.72, 0.33, 0.0)
BLACK_SQUARE_COLOURS = {None: (0.8, 0.8, 0.8), glchess.scene.HIGHLIGHT_SELECTED: (0.3, 1.0, 0.3), glchess.scene.HIGHLIGHT_CAN_MOVE: (0.3, 0.3, 1.0)}
WHITE_SQUARE_COLOURS = {None: (1.0, 1.0, 1.0), glchess.scene.HIGHLIGHT_SELECTED: (0.2, 1.0, 0.0), glchess.scene.HIGHLIGHT_CAN_MOVE: (0.2, 0.2, 0.8)}
# HACK
import os.path
class ChessPiece(glchess.scene.ChessPiece):
"""
"""
__scene = None
__chessSet = None
__name = None
pos = None
__targetPos = None
def __init__(self, scene, chessSet, name, startPos = (0.0, 0.0, 0.0)):
"""
"""
self.__scene = scene
self.__chessSet = chessSet
self.__name = name
self.pos = startPos
def move(self, coord):
"""Extends glchess.scene.ChessPiece"""
self.__targetPos = self.__scene._coordToLocation(coord)
self.__scene._startAnimation()
def draw(self, state = 'default'):
"""
"""
self.__chessSet.drawPiece(self.__name, state, self.__scene)
def animate(self, timeStep):
"""
Return True if the piece has moved otherwise False.
"""
if self.__targetPos is None:
return False
if self.pos == self.__targetPos:
self.__targetPos = None
return False
# Get distance to target
dx = self.__targetPos[0] - self.pos[0]
dy = self.__targetPos[1] - self.pos[1]
dz = self.__targetPos[2] - self.pos[2]
# Get movement step in each direction
SPEED = 50.0 # FIXME
xStep = timeStep * SPEED
if xStep > abs(dx):
xStep = dx
else:
xStep *= cmp(dx, 0.0)
yStep = timeStep * SPEED
if yStep > abs(dy):
yStep = dy
else:
yStep *= cmp(dy, 0.0)
zStep = timeStep * SPEED
if zStep > abs(dz):
zStep = dz
else:
zStep *= cmp(dz, 0.0)
# Move the piece
self.pos = (self.pos[0] + xStep, self.pos[1] + yStep, self.pos[2] + zStep)
return True
class Scene(glchess.scene.Scene):
"""
"""
# The viewport dimensions
__viewportWidth = 0
__viewportHeight = 0
__viewportAspect = 1.0
__animating = False
# Loading screen properties
__throbberEnabled = False
__throbberAngle = 0.0
# The scene light position
__lightPos = None
# The board angle in degrees
__boardAngle = 0.0
__oldBoardAngle = 0.0
__targetBoardAngle = 0.0
# OpenGL display list for the board and a flag to regenerate it
__boardList = None
__regenerateBoard = False
# Texture objects for the board
__whiteTexture = None
__blackTexture = None
# ...
__pieces = None
__chessSets = None
__piecesMoving = False
# Dictionary of co-ordinates to highlight
__highlights = None
def __init__(self):
"""Constructor for an OpenGL scene"""
self.__lightPos = [100.0, 100.0, 100.0, 1.0]
self.__pieces = []
self.__highlights = {}
self.__chessSets = {'white': builtin_models.WhiteBuiltinSet(), 'black': builtin_models.BlackBuiltinSet()}
self.__whiteTexture = texture.Texture(os.path.join(IMAGE_DIR, 'board.png'),
ambient = BOARD_AMBIENT, diffuse = BOARD_DIFFUSE,
specular = BOARD_SPECULAR, shininess = BOARD_SHININESS)
self.__blackTexture = texture.Texture(os.path.join(IMAGE_DIR, 'board.png'),
ambient = BOARD_AMBIENT, diffuse = BOARD_DIFFUSE,
specular = BOARD_SPECULAR, shininess = BOARD_SHININESS)
def onRedraw(self):
"""This method is called when the scene needs redrawing"""
pass
def _startAnimation(self):
"""
"""
self.__changed = True
if self.__animating is False:
self.__animating = True
self.startAnimation()
def addChessPiece(self, chessSet, name, coord):
"""Add a chess piece model into the scene.
'chessSet' is the name of the chess set (string).
'name' is the name of the piece (string).
'coord' is the the chess board location of the piece in LAN format (string).
Returns a reference to this chess piece or raises an exception.
"""
chessSet = self.__chessSets[chessSet]
piece = ChessPiece(self, chessSet, name, self._coordToLocation(coord))
self.__pieces.append(piece)
# Redraw the scene
self.onRedraw()
return piece
def removeChessPiece(self, piece):
"""Remove chess piece.
'piece' is a chess piece instance as returned by addChessPiece().
"""
self.__pieces.remove(piece)
self.onRedraw()
def setBoardHighlight(self, coords):
"""Highlight a square on the board.
'coords' is a dictionary of highlight types keyed by square co-ordinates.
The co-ordinates are a tuple in the form (file,rank).
If None the highlight will be cleared.
"""
if coords is None:
self.__highlights = {}
else:
self.__highlights = coords.copy()
# Regenerate the optimised board model
self.__regenerateBoard = True
self.onRedraw()
def reshape(self, width, height):
"""Resize the viewport into the scene.
'width' is the width of the viewport in pixels.
'height' is the width of the viewport in pixels.
"""
self.__viewportWidth = int(width)
self.__viewportHeight = int(height)
self.__viewportAspect = float(self.__viewportWidth) / float(self.__viewportHeight)
self.onRedraw()
def setBoardRotation(self, angle):
"""Set the rotation on the board.
'angle' is the angle the board should be drawn at in degress (float, [0.0, 360.0]).
"""
self.__targetBoardAngle = angle
self._startAnimation()
def animate(self, timeStep):
"""Extends glchess.scene.Scene"""
redraw1 = self.__animateThrobber(timeStep)
self.__piecesMoving = self.__animatePieces(timeStep)
redraw2 = self.__animateRotation(timeStep)
if redraw1 or redraw2 or self.__piecesMoving:
self.__animating = True
self.onRedraw()
else:
self.__animating = False
return self.__animating
def render(self):
"""Render the scene.
This requires an OpenGL context.
"""
glClearColor(*BACKGROUND_COLOUR)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Set the projection for this scene
self.__setViewport()
# Do camera and board rotation/translation
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
self.__transformCamera()
glLightfv(GL_LIGHT0, GL_AMBIENT, LIGHT_AMBIENT_COLOUR)
glLightfv(GL_LIGHT0, GL_DIFFUSE, LIGHT_DIFFUSE_COLOUR)
glLightfv(GL_LIGHT0, GL_SPECULAR, LIGHT_SPECULAR_COLOUR)
glLightfv(GL_LIGHT0, GL_POSITION, self.__lightPos)
glEnable(GL_LIGHTING)
glEnable(GL_LIGHT0)
self.__transformBoard()
glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
glEnable(GL_TEXTURE_2D)
glEnable(GL_COLOR_MATERIAL)
self.__drawBoard()
glDisable(GL_COLOR_MATERIAL)
glDisable(GL_TEXTURE_2D)
# WORKAROUND: Mesa is corrupting polygons on the bottom of the models
# It could be because the depth buffer has a low bit depth?
glClear(GL_DEPTH_BUFFER_BIT)
if self.__throbberEnabled:
self.__drawThrobber()
else:
self.__drawPieces()
def getSquare(self, x, y):
"""Find the chess square at a given 2D location.
'x' is the number of pixels from the left of the scene to select.
'y' is the number of pixels from the bottom of the scene to select.
This requires an OpenGL context.
Return the co-ordinate in LAN format (string) or None if no square at this point.
"""
# FIXME: Don't rely on this? It seems to get corrupt when multiple games are started
viewport = glGetIntegerv(GL_VIEWPORT)
# Don't render to screen, just select
# Selection buffer is large in case we select multiple squares at once (it generates an exception)
glSelectBuffer(20)
glRenderMode(GL_SELECT)
glInitNames()
glPushName(0)
# Create pixel picking region near cursor location
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPickMatrix(x, (float(viewport[3]) - y), 1.0, 1.0, viewport)
gluPerspective(60.0, float(viewport[2]) / float(viewport[3]), 0, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
self.__transformCamera()
# Draw board
self.__transformBoard()
self.__drawSquares()
# Render and check for hits
# Catch the exception in case we select more than we can fit in the selection buffer
glFlush()
try:
records = glRenderMode(GL_RENDER)
except GLerror:
records = None
# Get the first record and use this as the selected square
coord = None
if records is not None:
for record in records:
coord = record[2]
break
# Reset projection matrix
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60.0, float(viewport[2]) / float(viewport[3]), 0.1, 1000)
# Convert from co-ordinates to LAN format
rank = chr(ord('a') + coord[0])
file = chr(ord('1') + coord[1])
return rank + file
# Private methods
def _coordToLocation(self, coord):
"""
"""
rank = ord(coord[0]) - ord('a')
file = ord(coord[1]) - ord('1')
x = BOARD_BORDER + float(rank) * SQUARE_WIDTH + 0.5 * SQUARE_WIDTH
z = -(BOARD_BORDER + float(file) * SQUARE_WIDTH + 0.5 * SQUARE_WIDTH)
return (x, 0.0, z)
def __animateThrobber(self, timeStep):
"""
"""
if self.__throbberEnabled is False:
return False
self.__throbberAngle += timeStep * (math.pi * 2.0) / 2.0
while self.__throbberAngle > (math.pi * 2.0):
self.__throbberAngle -= 2.0 * math.pi
return True
def __animateRotation(self, timeStep):
"""
"""
if self.__boardAngle == self.__targetBoardAngle:
return False
# Wait unti pieces have stopped moving
if self.__piecesMoving:
return False
# Rotate board to the chosen angle
length = abs(self.__targetBoardAngle - self.__oldBoardAngle)
self.__boardAngle += timeStep * length / 0.8
while self.__boardAngle > 360.0:
self.__boardAngle -= 360.0
travelled = self.__targetBoardAngle - self.__boardAngle
while travelled < 0.0:
travelled += 360.0
# If have moved through the remaining angle then clip to the target
if travelled >= length:
self.__oldBoardAngle = self.__boardAngle = self.__targetBoardAngle
return True
def __animatePieces(self, timeStep):
"""
"""
active = False
for piece in self.__pieces:
if piece.animate(timeStep):
active = True
# If the throbber is enabled the pieces are hidden so don't redraw
if active and not self.__throbberEnabled:
return True
return False
def __drawThrobber(self):
"""
"""
glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
# Orthographic projection with even scaling
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
if self.__viewportWidth > self.__viewportHeight:
h = 1.0
w = 1.0 * self.__viewportWidth / self.__viewportHeight
else:
h = 1.0 * self.__viewportHeight / self.__viewportWidth
w = 1.0
gluOrtho2D(0, w, 0, h)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glColor4f(0.0, 0.0, 0.0, 0.75)
glBegin(GL_QUADS)
glVertex2f(-1.0, -1.0)
glVertex2f(w + 1.0, -1.0)
glVertex2f(w + 1.0, h + 1.0)
glVertex2f(-1.0, h + 1.0)
glEnd()
NSECTIONS = 9
RADIUS_OUT0 = 0.4
RADIUS_OUT1 = 0.43
RADIUS_OUT2 = 0.4
RADIUS_IN0 = 0.25#0.1
RADIUS_IN1 = 0.24#0.09
RADIUS_IN2 = 0.25#0.1
STEP_ANGLE = 2.0 * math.pi / float(NSECTIONS)
HALF_WIDTH = 0.8 * (0.5 * STEP_ANGLE)
glTranslatef(0.5 * w, 0.5 * h, 0.0)
glBegin(GL_QUADS)
for i in xrange(NSECTIONS):
theta = 2.0 * math.pi * float(i) / float(NSECTIONS)
leadTheta = theta + HALF_WIDTH
lagTheta = theta - HALF_WIDTH
x0 = math.sin(leadTheta)
y0 = math.cos(leadTheta)
x1 = math.sin(theta)
y1 = math.cos(theta)
x2 = math.sin(lagTheta)
y2 = math.cos(lagTheta)
angleDifference = self.__throbberAngle - theta
if angleDifference > math.pi:
angleDifference -= 2.0 * math.pi
elif angleDifference < -math.pi:
angleDifference += 2.0 * math.pi
stepDifference = angleDifference / STEP_ANGLE
if stepDifference > -0.5 and stepDifference < 0.5:
x = 2.0 * abs(stepDifference)
glColor4f(1.0, x, x, 0.6)
else:
glColor4f(1.0, 1.0, 1.0, 0.6)
glVertex2f(RADIUS_IN0 * x0, RADIUS_IN0 * y0)
glVertex2f(RADIUS_OUT0 * x0, RADIUS_OUT0 * y0)
glVertex2f(RADIUS_OUT1 * x1, RADIUS_OUT1 * y1)
glVertex2f(RADIUS_IN1 * x1, RADIUS_IN1 * y1)
glVertex2f(RADIUS_IN1 * x1, RADIUS_IN1 * y1)
glVertex2f(RADIUS_OUT1 * x1, RADIUS_OUT1 * y1)
glVertex2f(RADIUS_OUT2 * x2, RADIUS_OUT2 * y2)
glVertex2f(RADIUS_IN2 * x2, RADIUS_IN2 * y2)
glEnd()
glDisable(GL_BLEND)
def __setViewport(self):
"""Perform the projection matrix transformation for the current viewport"""
glViewport(0, 0, self.__viewportWidth, self.__viewportHeight)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(60.0, self.__viewportAspect, 0.1, 1000)
def __transformCamera(self):
"""Perform the camera matrix transformation"""
gluLookAt(0.0, 90.0, 45.0,
0.0, 0.0, 5.0,
0.0, 1.0, 0.0)
def __drawBoard(self):
"""Draw a chessboard"""
# Use pre-rendered version if available
if self.__regenerateBoard is False and self.__boardList is not None:
glCallList(self.__boardList)
return
# Attempt to store the board as a display list
if self.__boardList is None:
list = glGenLists(1)
if list != 0:
self.__boardList = list
# If have a list store there
if self.__boardList is not None:
glNewList(self.__boardList, GL_COMPILE_AND_EXECUTE)
# Board verticies
# (lower 12-15 are under 8-11)
#
# a b c d e f
#
# 8-----------------9 g
# |\ /|
# | 4-------------5 | h
# | | | |
# | | 0---------1 | | i
# | | | | | |
# | | | | | |
# | | 3---------2 | | j
# | | | |
# | 7-------------6 | k
# |/ \|
# 11---------------10 l
#
# |- board -|
# width
# Draw the border
glColor3f(*BORDER_COLOUR)
# Top
a = 0.0
b = BOARD_CHAMFER
c = BOARD_BORDER
d = c + (SQUARE_WIDTH * 8.0)
e = d + BOARD_BORDER - BOARD_CHAMFER
f = d + BOARD_BORDER
l = 0.0
k = -BOARD_CHAMFER
j = -BOARD_BORDER
i = j - (SQUARE_WIDTH * 8.0)
h = i - BOARD_BORDER + BOARD_CHAMFER
g = i - BOARD_BORDER
verticies = [(c, 0.0, i), (d, 0.0, i),
(d, 0.0, j), (c, 0.0, j),
(b, 0.0, h), (e, 0.0, h),
(e, 0.0, k), (b, 0.0, k),
(a, -BOARD_CHAMFER, g), (f, -BOARD_CHAMFER, g),
(f, -BOARD_CHAMFER, l), (a, -BOARD_CHAMFER, l),
(a, -BOARD_DEPTH, g), (f, -BOARD_DEPTH, g), (f, -BOARD_DEPTH, l), (a, -BOARD_DEPTH, l)]
normals = [(0.0, 1.0, 0.0), (0.0, 0.0, -1.0), (1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (-1.0, 0.0, 0.0),
(0.0, 0.707, -0.707), (0.707, 0.707, 0.0), (0.0, 0.707, 0.707), (-0.707, 0.707, 0.0)]
#textureCoords = [(0.0, 0.0), (
quads = [(0, 1, 5, 4, 0), (1, 2, 6, 5, 0), (2, 3, 7, 6, 0), (3, 0, 4, 7, 0),
(4, 5, 9, 8, 5), (5, 6, 10, 9, 6), (6, 7, 11, 10, 7), (7, 4, 8, 11, 8),
(8, 9, 13, 12, 1), (9, 10, 14, 13, 2), (10, 11, 15, 14, 3), (11, 8, 12, 15, 4)]
glDisable(GL_TEXTURE_2D)
glBegin(GL_QUADS)
for q in quads:
glNormal3fv(normals[q[4]])
#glTexCoord2fv(textureCoords[q[0]])
glVertex3fv(verticies[q[0]])
#glTexCoord2fv(textureCoords[q[1]])
glVertex3fv(verticies[q[1]])
#glTexCoord2fv(textureCoords[q[2]])
glVertex3fv(verticies[q[2]])
#glTexCoord2fv(textureCoords[q[3]])
glVertex3fv(verticies[q[3]])
glEnd()
# Draw the squares
glEnable(GL_TEXTURE_2D)
for x in [0, 1, 2, 3, 4, 5, 6, 7]:
for y in [0, 1, 2, 3, 4, 5, 6, 7]:
isBlack = (x + (y % 2) + 1) % 2
# Get the highlight type
coord = chr(ord('a') + x) + chr(ord('1') + y)
try:
highlight = self.__highlights[coord]
except KeyError:
highlight = None
if isBlack:
colour = BLACK_SQUARE_COLOURS[highlight]
self.__whiteTexture.bind() #blackTexture
else:
colour = WHITE_SQUARE_COLOURS[highlight]
self.__whiteTexture.bind()
x0 = BOARD_BORDER + (x * SQUARE_WIDTH)
x1 = x0 + SQUARE_WIDTH
z0 = BOARD_BORDER + (y * SQUARE_WIDTH)
z1 = z0 + SQUARE_WIDTH
glBegin(GL_QUADS)
glNormal3f(0.0, 1.0, 0.0)
glColor3fv(colour)
glTexCoord2f(0.0, 0.0)
glVertex3f(x0, 0.0, -z0)
glTexCoord2f(1.0, 0.0)
glVertex3f(x1, 0.0, -z0)
glTexCoord2f(1.0, 1.0)
glVertex3f(x1, 0.0, -z1)
glTexCoord2f(0.0, 1.0)
glVertex3f(x0, 0.0, -z1)
glEnd()
if self.__boardList is not None:
glEndList()
glCallList(self.__boardList)
def __drawSquares(self):
"""Draw the board squares for picking"""
# draw the floor squares
for u in [0, 1, 2, 3, 4, 5, 6, 7]:
glLoadName(u)
for v in [0, 1, 2, 3, 4, 5, 6, 7]:
glPushName(v)
# Draw square
glBegin(GL_QUADS)
x0 = BOARD_BORDER + (u * SQUARE_WIDTH)
x1 = x0 + SQUARE_WIDTH
z0 = BOARD_BORDER + (v * SQUARE_WIDTH)
z1 = z0 + SQUARE_WIDTH
glVertex3f(x0, 0.0, -z0)
glVertex3f(x1, 0.0, -z0)
glVertex3f(x1, 0.0, -z1)
glVertex3f(x0, 0.0, -z1)
glEnd()
glPopName()
def __drawPieces(self):
"""Draw the pieces in the scene"""
glEnable(GL_TEXTURE_2D)
for piece in self.__pieces:
glPushMatrix()
glTranslatef(piece.pos[0], piece.pos[1], piece.pos[2])
# Draw the model
piece.draw()
glPopMatrix()
glDisable(GL_TEXTURE_2D)
def __transformBoard(self):
"""Perform the board transform"""
# Rotate the board
glRotatef(self.__boardAngle, 0.0, 1.0, 0.0)
# Offset board so centre is (0.0,0.0)
glTranslatef(-OFFSET, 0.0, OFFSET)

1022
src/lib/scene/opengl/png.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,133 @@
from OpenGL.GL import *
from OpenGL.GLU import *
import png
import array
class Texture:
"""
"""
# Texture data
__data = None
__format = GL_RGB
__width = None
__height = None
# Material properties
__ambient = None
__diffuse = None
__specular = None
__emission = None
__shininess = None
# OpenGL texture ID
__texture = None
def __init__(self, fileName,
ambient = (0.2, 0.2, 0.2, 1.0),
diffuse = (0.8, 0.8, 0.8, 1.0),
specular = (0.0, 0.0, 0.0, 1.0),
emission = (0.0, 0.0, 0.0, 1.0),
shininess = 0.0):
"""Constructor for an openGL texture.
'fileName' is the name of the image file to use for the texture (string).
An IOError is raised if the file does not exist.
This does not need an openGL context.
"""
self.__ambient = ambient
self.__diffuse = diffuse
self.__specular = specular
self.__emission = emission
self.__shininess = shininess
try:
self.__loadPIL(fileName)
except ImportError:
self.__loadPNG(fileName)
def __loadPNG(self, fileName):
"""
"""
reader = png.Reader(fileName)
(width, height, data, metaData) = reader.read()
self.__width = width
self.__height = height
self.__data = array.array('B', data).tostring()
if metaData['has_alpha']:
self.__format = GL_RGBA
else:
self.__format = GL_RGB
def __loadPIL(self, fileName):
"""
"""
import Image
# Load the image file
image = Image.open(fileName)
# Crop the image so it has height/width a multiple of 2
width = image.size[0]
height = image.size[1]
w = 1
while 2*w <= width:
w *= 2
h = 1
while 2*h <= height:
h *= 2
(self.__width, self.__height) = (w, h)
image = image.crop((0, 0, w, h))
# Convert to a format that OpenGL can access
self.__data = image.tostring('raw', 'RGB', 0, -1)
self.__format = GL_RGB
def __generate(self):
"""
"""
# FIXME: Can fail
texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D, texture)
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
#glTexImage2D(GL_TEXTURE_2D,
# 0, # Level
# 3, # Depth
# width, # Width
# height, # Height
# 0, # Border
# GL_RGB, # Format
# GL_UNSIGNED_BYTE, # Type
# data)
# Generate mipmaps
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_LUMINANCE, self.__width, self.__height, self.__format, GL_UNSIGNED_BYTE, self.__data)
return texture
def bind(self):
"""Bind this texture to the current surface.
This requires an openGL context.
"""
if self.__texture is None:
self.__texture = self.__generate()
self.__data = None
# Use texture
glBindTexture(GL_TEXTURE_2D, self.__texture)
# Use material properties
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, self.__ambient)
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, self.__diffuse)
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, self.__specular)
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, self.__emission)
glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, self.__shininess)

161
src/lib/uci.py Normal file
View file

@ -0,0 +1,161 @@
class StateMachine:
"""
"""
STATE_IDLE = 'IDLE'
STATE_CONNECTING = 'CONNECTING'
options = None
buffer = ''
__readyToConfigure = False
__options = None
__ready = False
__inCallback = False
__queuedCommands = None
def __init__(self):
"""
"""
self.options = {}
self.__queuedCommands = []
def logText(self, text, style):
"""
"""
pass
def onOutgoingData(self, data):
"""
"""
pass
def onMove(self, move):
"""Called when the AI makes a move.
'move' is the move the AI has decided to make (string).
"""
print 'UCI move: ' + move
def registerIncomingData(self, data):
"""
"""
self.__inCallback = True
self.buffer += data
while True:
index = self.buffer.find('\n')
if index < 0:
break
line = self.buffer[:index]
self.buffer = self.buffer[index + 1:]
self.parseLine(line)
self.__inCallback = False
if self.__options is not None and self.__readyToConfigure:
options = self.__options
self.__options = None
self.configure(options)
# Send queued commands once have OK
if len(self.__queuedCommands) > 0 and self.__ready:
commands = self.__queuedCommands
self.__queuedCommands = []
for c in commands:
self.__sendCommand(c)
def __sendCommand(self, command):
"""
"""
if self.__ready and not self.__inCallback:
self.onOutgoingData(command + '\n')
else:
self.__queuedCommands.append(command)
def start(self):
"""
"""
self.onOutgoingData('uci\n')
def configure(self, options):
"""
"""
if not self.__readyToConfigure:
self.__options = options
return
for option in options:
if not hasattr(option, 'name'):
print 'Ignoring unnamed UCI option'
continue
if option.value == '':
continue
self.onOutgoingData('setoption ' + option.name + ' value ' + option.value + '\n')
self.onOutgoingData('isready\n')
def requestMove(self):
"""
"""
self.__sendCommand('go')
def reportMove(self, move, isSelf):
"""
"""
self.__sendCommand('position moves ' + move)
def parseLine(self, line):
"""
"""
words = line.split()
while True:
if len(words) == 0:
self.logText(line + '\n', 'input')
return
style = self.parseCommand(words[0], words[1:])
if style is not None:
self.logText(line + '\n', style)
return
print 'WARNING: Unknown command: ' + repr(words[0])
words = words[1:]
def parseCommand(self, command, args):
"""
"""
if command == 'id':
return 'info'
elif command == 'uciok':
if len(args) != 0:
print 'WARNING: Arguments on uciok: ' + str(args)
self.__readyToConfigure = True
return 'info'
elif command == 'readyok':
if len(args) != 0:
print 'WARNING: Arguments on readyok: ' + str(args)
self.__ready = True
return 'info'
elif command == 'bestmove':
if len(args) == 0:
print 'WARNING: No move with bestmove'
return 'error'
else:
move = args[0]
self.onMove(move)
# TODO: Check for additional ponder information
return 'move'
elif command == 'info':
return 'info'
elif command == 'option':
return 'info'
return None

4
src/lib/ui/Makefile.am Normal file
View file

@ -0,0 +1,4 @@
glchessdir = $(pythondir)/glchess/ui
glchess_PYTHON = \
__init__.py \
ui.py

1
src/lib/ui/__init__.py Normal file
View file

@ -0,0 +1 @@
from ui import *

259
src/lib/ui/ui.py Normal file
View file

@ -0,0 +1,259 @@
"""
"""
__author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
__license__ = 'GNU General Public License Version 2'
__copyright__ = 'Copyright 2005-2006 Robert Ancell'
class ViewFeedback:
"""Template class for feedback from a view object"""
def saveGame(self, path):
"""Called when the user requests the game in this view to be saved.
'path' is the path to the file to save to (string).
"""
print 'Save game to ' + path
def renderGL(self):
"""Render the scene using OpenGL"""
pass
def renderCairoStatic(self, context):
"""Render the static elements of the scene.
'context' is the cairo context to modify.
Return False if the static elements have not changed otherwise True.
"""
return False
def renderCairoDynamic(self, context):
"""Render the dynamic elements of the scene.
'context' is the cairo context to modify.
"""
pass
def reshape(self, width, height):
"""This method is called when the UI resizes the scene.
'width' is the new width of the scene in pixels (integer).
'height' is the new height of the scene in pixels (integer).
"""
pass
def select(self, x, y):
"""This method is called when the UI selects a position on the scene.
'x' is the horizontal pixel location when the user has selected (integer, 0 = left pixel).
'y' is the vertical pixel location when the user has selected (integer, 0 = top pixel).
"""
pass
def deselect(self, x, y):
"""This method is called when the UI deselects a position on the scene.
'x' is the horizontal pixel location when the user has selected (integer, 0 = left pixel).
'y' is the vertical pixel location when the user has selected (integer, 0 = top pixel).
"""
pass
def setMoveNumber(self, moveNumber):
"""This method is called when the UI changes the move to render.
'moveNumber' is the moveNumber to watch (integer, negative numbers index from latest move).
"""
pass
def save(self, filename):
"""Save the game using this view.
'filename' is the file to save to (string).
"""
pass
def close(self):
"""This method is called when the user requests this view be closed"""
pass
class ViewController:
"""Template class for methods to control a view"""
def addMove(self, move):
"""Register a move with this view.
'move' TODO
"""
pass
def render(self):
"""Request this view is redrawn"""
pass
def close(self):
"""Close this view"""
pass
class UI:
"""Template class for a glChess UI.
"""
# Methods for glChess to implement
def onAnimate(self, timeStep):
"""Called when an animation tick occurs.
'timeStep' is the time between the last call to this method in seconds (float).
Return True if animation should continue otherwise False
"""
return False
def onReadFileDescriptor(self, fd):
"""Called when a file descriptor is able to be read.
'fds' is the file descriptor with available data to read (integer).
Return False when finished otherwise True.
"""
pass
def onGameStart(self, gameName, allowSpectators, whiteName, whiteType, blackName, blackType, moves = None):
"""Called when a local game is started.
'gameName' is the name of the game to create (string).
'allowSpectators' is a flag to show if remote clients can watch this game (True or False).
'whiteName' is the name of the white player.
'whiteType' is the local player type. PLAYER_* or the AI type (string) or None for open.
'blackName' is the name of the black player.
'blackType' is the black player type. PLAYER_* or the AI type (string) or None for open.
'moves' is a list of moves (strings) to start the game with.
"""
pass
def loadGame(self, path, returnResult):
"""Called when a game is loaded.
'path' is the path to the game to load (string).
'returnResult' is a flag to show if the UI requires the result of the load.
If True call reportGameLoaded() if the game can be loaded.
"""
msg = 'Loading game ' + path
if configureGame:
msg += ' after configuring'
print msg
def onGameJoin(self, localName, localType, game):
"""Called when a network game is started (remote server).
'localName' is the name of the local player (string).
'localType' is the local player type. PLAYER_* or the AI type (string).
'game' is the game to join (as passed in addNetworkGame).
"""
pass
def onNetworkServerSearch(self, hostName=None):
"""Called when the user searches for servers.
'hostName' is the name of the host to look for servers on (string) or
None if search whole network.
"""
pass
def onNetworkGameStart(self, localName, localType, serverHost, serverId):
"""Called when a network game is started (remote server).
'localName' is the name of the local player (string).
'localType' is the local player type. PLAYER_* or the AI type (string).
'serverHost' is the hostname of the server to connect to.
'serverId' is the ID of the server to connect to.
"""
pass
def onNetworkGameServerStart(self, localName, localType, serverName):
"""Called when a network game is started (local server).
'localName' is the name of the local player (string).
'localType' is the local player type. PLAYER_* or the AI type (string).
'serverName' is the name of the server to start.
"""
pass
def onQuit(self):
"""Called when the user quits the program"""
pass
# Methods for the UI to implement
def startAnimation(self):
"""Start the animation callback"""
pass
def watchFileDescriptor(self, fd):
"""Notify when a file descriptor is able to be read.
'fd' is the file descriptor to watch (integer).
When data is available onReadFileDescriptor() is called.
"""
pass
def setDefaultView(self, feedback):
"""Set the default view to render.
'feedback' is a object to report view events with (extends ViewFeedback).
This will override the previous default view.
Returns a view controller object (extends ViewController).
"""
return None
def addView(self, title, feedback):
"""Add a view to the UI.
'title' is the title for the view (string).
'feedback' is a object to report view events with (extends ViewFeedback).
Returns a view controller object (extends ViewController).
"""
return None
def reportError(self, title, error):
"""Report an error.
'title' is the title of the error (string).
'error' is the description of the error (string).
"""
pass
def reportGameLoaded(self, gameName = None,
whiteName = None, blackName = None,
whiteAI = None, blackAI = None, moves = None):
"""Report a loaded game as required by onGameLoad().
'gameName' is the name of the game (string) or None if unknown.
'whiteName' is the name of the white player (string) or None if unknown.
'blackName' is the name of the white player (string) or None if unknown.
'whiteAI' is the type of AI the white player is (string) or None if no AI.
'blackAI' is the type of AI the black player is (string) or None if no AI.
'moves' is a list of moves (strings) that the have already been made.
"""
pass
def addNetworkGame(self, name, game):
"""Report a detected network game.
'name' is the name of the network game (string).
'game' is the game detected (user-defined).
"""
pass
def removeNetworkGame(self, game):
"""Report a network game as terminated.
'game' is the game that has removed (as registered with addNetworkGame()).
"""
pass

6
textures/Makefile.am Normal file
View file

@ -0,0 +1,6 @@
EXTRA_DIST = glchess.svg board.png piece.png
icondir = $(datadir)/pixmaps
icon_DATA = glchess.svg
pixmapdir = $(datadir)/pixmaps/glchess/
pixmap_DATA = board.png piece.png

BIN
textures/board.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

103
textures/glchess.svg Normal file
View file

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://web.resource.org/cc/"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
id="svg1315"
sodipodi:version="0.32"
inkscape:version="0.43"
sodipodi:docbase="/home/luca/workspace/glchess"
sodipodi:docname="glchess.svg"
version="1.0">
<defs
id="defs1317">
<linearGradient
id="linearGradient14482">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop14484" />
<stop
id="stop15363"
offset="0"
style="stop-color:#454644;stop-opacity:1;" />
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="1"
id="stop14486" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.5"
inkscape:cx="32"
inkscape:cy="66.251422"
inkscape:current-layer="g4845"
showgrid="true"
inkscape:document-units="px"
inkscape:grid-bbox="true"
inkscape:window-width="1280"
inkscape:window-height="950"
inkscape:window-x="0"
inkscape:window-y="25" />
<metadata
id="metadata1320">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
id="g4845">
<path
sodipodi:nodetypes="cscsc"
id="path3087"
d="M 50.6853,10.24236 C 50.6853,10.24236 73.968077,20.604684 97.250854,20.604684 C 120.53363,20.604684 107.83394,47.546727 107.83394,47.546727 C 107.83394,47.546727 77.624051,44.343825 58.959345,43.967014 C 39.449256,43.573136 133.04091,59.416296 99.175051,95.778634 C 99.175051,95.778634 117.2625,103.31487 112.06717,114.80763 C 106.87184,126.30039 25.285907,123.4743 23.16929,117.0685 C 21.052674,110.6627 22.976871,103.50328 32.597852,95.213417 C 42.218835,86.923558 9.5074944,37.184403 14.702825,18.532219 C 19.898155,-0.1199642 43.373354,6.474242 50.6853,10.24236 z "
style="fill:#0d0d0d;fill-opacity:1;fill-rule:evenodd;stroke:#acacac;stroke-width:2.09442401px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path3089"
d="M 44.484739,62.417484 C 44.484739,62.417484 39.145486,87.338194 55.163242,88.999575 C 71.180999,90.660956 49.015012,51.845062 44.484739,62.417484 z "
style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:0.90689655;fill-rule:evenodd;stroke:#acacac;stroke-width:1.71954751px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible" />
<path
sodipodi:nodetypes="csc"
style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:0.90689655;fill-rule:evenodd;stroke:#acacac;stroke-width:1.86760128px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
d="M 67.933801,62.743788 C 67.933801,62.743788 68.465588,85.99802 86.014582,87.786806 C 103.56357,89.575594 72.897154,51.360599 67.933801,62.743788 z "
id="path3091" />
<path
style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#acacac;stroke-width:2.09442401px;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="M 34.247517,94.047212 C 34.247517,94.047212 62.340785,107.80084 99.862617,96.119677"
id="path9215"
sodipodi:nodetypes="cc" />
<path
sodipodi:type="arc"
style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:0.90689655;fill-rule:evenodd;stroke:#acacac;stroke-width:1px;stroke-linecap:round;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible"
id="path14462"
sodipodi:cx="25"
sodipodi:cy="10.090909"
sodipodi:rx="2.2727273"
sodipodi:ry="2.2727273"
d="M 27.272727 10.090909 A 2.2727273 2.2727273 0 1 1 22.727273,10.090909 A 2.2727273 2.2727273 0 1 1 27.272727 10.090909 z"
transform="matrix(2.116616,0,0,2.072465,-6.449243,0.126875)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5 KiB

BIN
textures/piece.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB