Add glChess to gnome-games.
This commit is contained in:
commit
3ab0fee043
81 changed files with 17365 additions and 0 deletions
4
BUGS
Normal file
4
BUGS
Normal 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
340
COPYING
Normal 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
868
ChangeLog
Normal 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
17
MANIFEST.in
Normal 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
25
Makefile.am
Normal 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
38
README
Normal 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
28
TODO
Normal 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
3
data/Makefile.am
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
EXTRA_DIST = ai.xml
|
||||||
|
aidir = $(datadir)/glchess/
|
||||||
|
ai_DATA = ai.xml
|
54
data/ai.xml
Normal file
54
data/ai.xml
Normal 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
120
debian/changelog
vendored
Normal 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
1
debian/compat
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
4
|
16
debian/control
vendored
Normal file
16
debian/control
vendored
Normal 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
27
debian/copyright
vendored
Normal 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
3
debian/docs
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
BUGS
|
||||||
|
README
|
||||||
|
TODO
|
8
debian/glchess.mime
vendored
Normal file
8
debian/glchess.mime
vendored
Normal 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
111
debian/manpage.sgml
vendored
Normal 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
1
debian/menu
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
?package(glchess):needs="X11" section="Games/Board" title="glChess" command="/usr/games/glchess"
|
76
debian/rules
vendored
Executable file
76
debian/rules
vendored
Executable 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
14
glade/Makefile.am
Normal 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
31
glade/about.glade
Normal 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 <bob27@users.sourceforge.net></property>
|
||||||
|
<property name="artists">John-Paul Gignac (3D Models)
|
||||||
|
Thomas Dybdahl Ahle (2D Models)</property>
|
||||||
|
<property name="translator_credits">Luca Marturana <lucamarturana@gmail.com> (It)
|
||||||
|
Hakan Bekdas <hakanbekdas@yahoo.com> (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
212
glade/ai.glade
Normal 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
116
glade/chess_view.glade
Normal 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
160
glade/error_dialog.glade
Normal 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"><b><big>Error Title</big></b></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
1463
glade/glchess.glade
Normal file
File diff suppressed because it is too large
Load diff
111
glade/load_game.glade
Normal file
111
glade/load_game.glade
Normal 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
413
glade/network_game.glade
Normal 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"><b>Players</b></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"><b>Status / Chat</b></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
1230
glade/new_game.glade
Normal file
File diff suppressed because it is too large
Load diff
80
glade/save_game.glade
Normal file
80
glade/save_game.glade
Normal 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
15
glchess.desktop.in
Normal 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
7
help/C/Makefile.am
Normal 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
24
help/C/glchess-C.omf
Normal 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
15
help/C/glchess.xml
Normal 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
76
help/C/legal.xml
Normal 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
1
help/Makefile.am
Normal file
|
@ -0,0 +1 @@
|
||||||
|
SUBDIRS = C
|
8
mime/glchess.xml
Normal file
8
mime/glchess.xml
Normal 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
63
setup.py
Normal 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
1
src/Makefile.am
Normal file
|
@ -0,0 +1 @@
|
||||||
|
SUBDIRS = lib
|
10
src/glchess
Executable file
10
src/glchess
Executable 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
12
src/lib/Makefile.am
Normal 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
1
src/lib/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
335
src/lib/ai.py
Normal file
335
src/lib/ai.py
Normal 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
218
src/lib/cecp.py
Normal 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()
|
7
src/lib/chess/Makefile.am
Normal file
7
src/lib/chess/Makefile.am
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
glchessdir = $(pythondir)/glchess/chess
|
||||||
|
glchess_PYTHON = \
|
||||||
|
board.py \
|
||||||
|
__init__.py \
|
||||||
|
lan.py \
|
||||||
|
pgn.py \
|
||||||
|
san.py
|
4
src/lib/chess/__init__.py
Normal file
4
src/lib/chess/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import board
|
||||||
|
import pgn
|
||||||
|
import lan
|
||||||
|
import san
|
1025
src/lib/chess/board.py
Normal file
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
178
src/lib/chess/lan.py
Normal 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
686
src/lib/chess/pgn.py
Normal 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
456
src/lib/chess/san.py
Normal 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
47
src/lib/defaults.py
Normal 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
526
src/lib/game.py
Normal 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
4
src/lib/glchess.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
def start_game ():
|
||||||
|
import main
|
||||||
|
app = main.Application()
|
||||||
|
app.start()
|
5
src/lib/gtkui/Makefile.am
Normal file
5
src/lib/gtkui/Makefile.am
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
glchessdir = $(pythondir)/glchess/gtkui
|
||||||
|
glchess_PYTHON = \
|
||||||
|
dialogs.py \
|
||||||
|
gtkui.py \
|
||||||
|
__init__.py
|
3
src/lib/gtkui/__init__.py
Normal file
3
src/lib/gtkui/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from gtkui import GtkView, GtkUI
|
540
src/lib/gtkui/dialogs.py
Normal file
540
src/lib/gtkui/dialogs.py
Normal 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
931
src/lib/gtkui/gtkui.py
Normal 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
992
src/lib/main.py
Normal 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()
|
5
src/lib/network/Makefile.am
Normal file
5
src/lib/network/Makefile.am
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
glchessdir = $(pythondir)/glchess/network
|
||||||
|
glchess_PYTHON = \
|
||||||
|
announce.py \
|
||||||
|
__init__.py \
|
||||||
|
protocol.py
|
2
src/lib/network/__init__.py
Normal file
2
src/lib/network/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
from protocol import *
|
||||||
|
from announce import *
|
127
src/lib/network/announce.py
Normal file
127
src/lib/network/announce.py
Normal 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
352
src/lib/network/protocol.py
Normal 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
|
6
src/lib/scene/Makefile.am
Normal file
6
src/lib/scene/Makefile.am
Normal 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
127
src/lib/scene/__init__.py
Normal 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
|
4
src/lib/scene/cairo/Makefile.am
Normal file
4
src/lib/scene/cairo/Makefile.am
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
glchessdir = $(pythondir)/glchess/scene/cairo
|
||||||
|
glchess_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
pieces.py
|
297
src/lib/scene/cairo/__init__.py
Normal file
297
src/lib/scene/cairo/__init__.py
Normal 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
|
||||||
|
|
47
src/lib/scene/cairo/pieces.py
Normal file
47
src/lib/scene/cairo/pieces.py
Normal file
File diff suppressed because one or more lines are too long
165
src/lib/scene/human.py
Normal file
165
src/lib/scene/human.py
Normal 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)
|
8
src/lib/scene/opengl/Makefile.am
Normal file
8
src/lib/scene/opengl/Makefile.am
Normal 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
|
17
src/lib/scene/opengl/__init__.py
Normal file
17
src/lib/scene/opengl/__init__.py
Normal 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 *
|
527
src/lib/scene/opengl/builtin_models.py
Normal file
527
src/lib/scene/opengl/builtin_models.py
Normal 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)
|
1552
src/lib/scene/opengl/new_models.py
Normal file
1552
src/lib/scene/opengl/new_models.py
Normal file
File diff suppressed because it is too large
Load diff
680
src/lib/scene/opengl/opengl.py
Normal file
680
src/lib/scene/opengl/opengl.py
Normal 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
1022
src/lib/scene/opengl/png.py
Normal file
File diff suppressed because it is too large
Load diff
133
src/lib/scene/opengl/texture.py
Normal file
133
src/lib/scene/opengl/texture.py
Normal 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
161
src/lib/uci.py
Normal 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
4
src/lib/ui/Makefile.am
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
glchessdir = $(pythondir)/glchess/ui
|
||||||
|
glchess_PYTHON = \
|
||||||
|
__init__.py \
|
||||||
|
ui.py
|
1
src/lib/ui/__init__.py
Normal file
1
src/lib/ui/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
from ui import *
|
259
src/lib/ui/ui.py
Normal file
259
src/lib/ui/ui.py
Normal 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
6
textures/Makefile.am
Normal 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
BIN
textures/board.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
103
textures/glchess.svg
Normal file
103
textures/glchess.svg
Normal 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
BIN
textures/piece.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
Loading…
Reference in a new issue