Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlejandro Segovia <asegovi@gmail.com>2012-05-07 23:15:50 (GMT)
committer Alejandro Segovia <asegovi@gmail.com>2012-05-07 23:15:50 (GMT)
commit24932f700e9fdacf349e5a32517ccfe8984ea63c (patch)
treebf9b4bfc851beac5f9437d41eff31940e1ed0bc9
Initial commit of the Ceibal-Chess source code and binaries
-rw-r--r--GPL_V2341
-rw-r--r--MANIFEST59
-rw-r--r--Makefile20
-rw-r--r--activity-icon.svg155
-rw-r--r--activity/activity-icon.svg155
-rw-r--r--activity/activity.info10
-rw-r--r--board.py288
-rw-r--r--boardcontroller.py174
-rw-r--r--cell.py80
-rw-r--r--chessactivity.py55
-rw-r--r--chessengine.py229
-rwxr-xr-xcreatebundle.sh48
-rw-r--r--data/bg.pngbin0 -> 162088 bytes
-rw-r--r--data/bg.png.oldbin0 -> 870514 bytes
-rw-r--r--data/bishop.pngbin0 -> 2753 bytes
-rw-r--r--data/bishopblack.pngbin0 -> 19025 bytes
-rw-r--r--data/bishopwhite.pngbin0 -> 20581 bytes
-rw-r--r--data/btn_back.pngbin0 -> 30085 bytes
-rw-r--r--data/king.pngbin0 -> 3044 bytes
-rw-r--r--data/kingblack.pngbin0 -> 5086 bytes
-rw-r--r--data/kingwhite.pngbin0 -> 5512 bytes
-rw-r--r--data/knight.pngbin0 -> 3069 bytes
-rw-r--r--data/knightblack.pngbin0 -> 5174 bytes
-rw-r--r--data/knightwhite.pngbin0 -> 20098 bytes
-rw-r--r--data/menu_back.pngbin0 -> 10772 bytes
-rw-r--r--data/menu_back2.pngbin0 -> 5875 bytes
-rw-r--r--data/pawn.pngbin0 -> 2247 bytes
-rw-r--r--data/pawnblack.pngbin0 -> 4982 bytes
-rw-r--r--data/pawnwhite.pngbin0 -> 19626 bytes
-rw-r--r--data/queen.pngbin0 -> 3225 bytes
-rw-r--r--data/queenblack.pngbin0 -> 19732 bytes
-rw-r--r--data/queenwhite.pngbin0 -> 19936 bytes
-rw-r--r--data/rook.pngbin0 -> 2151 bytes
-rw-r--r--data/rookblack.pngbin0 -> 4901 bytes
-rw-r--r--data/rookwhite.pngbin0 -> 5264 bytes
-rw-r--r--data/wood.pngbin0 -> 100020 bytes
-rwxr-xr-xdata_bw/bg.pngbin0 -> 162088 bytes
-rwxr-xr-xdata_bw/bishop.pngbin0 -> 2760 bytes
-rwxr-xr-xdata_bw/bishopblack.pngbin0 -> 3678 bytes
-rwxr-xr-xdata_bw/bishopwhite.pngbin0 -> 3797 bytes
-rwxr-xr-xdata_bw/btn_back.pngbin0 -> 30085 bytes
-rwxr-xr-xdata_bw/king.pngbin0 -> 3044 bytes
-rwxr-xr-xdata_bw/kingblack.pngbin0 -> 3321 bytes
-rwxr-xr-xdata_bw/kingwhite.pngbin0 -> 3490 bytes
-rwxr-xr-xdata_bw/knight.pngbin0 -> 3069 bytes
-rwxr-xr-xdata_bw/knightblack.pngbin0 -> 3589 bytes
-rwxr-xr-xdata_bw/knightwhite.pngbin0 -> 3740 bytes
-rwxr-xr-xdata_bw/menu_back.pngbin0 -> 10772 bytes
-rwxr-xr-xdata_bw/menu_back2.pngbin0 -> 5875 bytes
-rwxr-xr-xdata_bw/pawn.pngbin0 -> 2247 bytes
-rwxr-xr-xdata_bw/pawnblack.pngbin0 -> 3679 bytes
-rwxr-xr-xdata_bw/pawnwhite.pngbin0 -> 3789 bytes
-rwxr-xr-xdata_bw/queen.pngbin0 -> 3225 bytes
-rwxr-xr-xdata_bw/queenblack.pngbin0 -> 3748 bytes
-rwxr-xr-xdata_bw/queenwhite.pngbin0 -> 3849 bytes
-rwxr-xr-xdata_bw/rook.pngbin0 -> 2151 bytes
-rwxr-xr-xdata_bw/rookblack.pngbin0 -> 3344 bytes
-rwxr-xr-xdata_bw/rookwhite.pngbin0 -> 3477 bytes
-rwxr-xr-xdata_bw/wood.pngbin0 -> 100020 bytes
-rwxr-xr-xengines/cygwin1.dllbin0 -> 1872884 bytes
-rwxr-xr-xengines/gnuchess-linuxbin0 -> 820514 bytes
-rwxr-xr-xengines/gnuchess-osxbin0 -> 235592 bytes
-rwxr-xr-xengines/gnuchess-win32.exebin0 -> 214664 bytes
-rw-r--r--errors.py33
-rwxr-xr-xmain.py389
-rw-r--r--menu.py125
-rw-r--r--messenger.py68
-rw-r--r--piece.py485
-rw-r--r--po/ceibalchess.pot53
-rw-r--r--po/en/LC_MESSAGES/messages.mobin0 -> 793 bytes
-rw-r--r--po/en_US.po54
-rw-r--r--po/es/LC_MESSAGES/messages.mobin0 -> 798 bytes
-rw-r--r--po/es_UY.po55
-rw-r--r--pychess-UML.jpgbin0 -> 507778 bytes
-rw-r--r--resourcemanager.py62
-rwxr-xr-xsetup.py22
-rw-r--r--sugargame/__init__.py1
-rw-r--r--sugargame/canvas.py62
-rw-r--r--sugargame/event.py243
-rw-r--r--testcases/boardtests.py131
-rw-r--r--testcases/piecetests.py482
-rwxr-xr-xtestcases/runtests.sh13
-rw-r--r--ui.py176
83 files changed, 4068 insertions, 0 deletions
diff --git a/GPL_V2 b/GPL_V2
new file mode 100644
index 0000000..21b9363
--- /dev/null
+++ b/GPL_V2
@@ -0,0 +1,341 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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) 19yy <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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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) 19yy 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.
diff --git a/MANIFEST b/MANIFEST
new file mode 100644
index 0000000..8d32020
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,59 @@
+menu.py
+GPL_V2
+ui.py
+appconfig.py
+movehistory.py
+chessengine.py
+piece.py
+data
+data/knight.png
+data/rookblack.png
+data/pawnwhite.png
+data/menu_back.png
+data/kingwhite.png
+data/king.png
+data/knightblack.png
+data/kingblack.png
+data/queen.png
+data/rookwhite.png
+data/bishopblack.png
+data/queenwhite.png
+data/menu_back2.png
+data/bg.png
+data/btn_back.png
+data/knightwhite.png
+data/bg.png.old
+data/queenblack.png
+data/rook.png
+data/pawnblack.png
+data/pawn.png
+data/bishopwhite.png
+data/bishop.png
+data/wood.png
+cell.py
+main.py
+engines
+engines/gnuchess-linux
+activity
+activity/activity.info
+activity/activity-icon.svg
+chessactivity.py
+testcases
+testcases/boardtests.py
+testcases/runtests.sh
+testcases/piecetests.py
+locale
+locale/es
+locale/es/LC_MESSAGES
+locale/es/LC_MESSAGES/org.x.ajedrezactivity.mo
+locale/es/activity.linfo
+board.py
+po
+po/es.po
+errors.py
+messenger.py
+resourcemanager.py
+Makefile
+boardcontroller.py
+createbundle.sh
+setup.py
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..9fce532
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,20 @@
+CLEANFILES = $(shell find . -type f -name '*.py[oc]')
+CLEANDIRS = dist locale
+
+.PHONY: bundle test
+
+all:
+ python setup.py build
+
+bundle:
+ ./createbundle.sh
+
+test:
+ ./testcases/runtests.sh
+
+clean:
+ @rm -rf $(addprefix ./,$(CLEANDIRS))
+ @rm -rf $(addprefix ./,$(CLEANFILES))
+
+run:
+ python main.py
diff --git a/activity-icon.svg b/activity-icon.svg
new file mode 100644
index 0000000..583c1db
--- /dev/null
+++ b/activity-icon.svg
@@ -0,0 +1,155 @@
+<?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://creativecommons.org/ns#"
+ 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://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64px"
+ height="64px"
+ id="svg2447"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="chess icon.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs2449">
+ <filter
+ inkscape:collect="always"
+ id="filter3576">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.78138117"
+ id="feGaussianBlur3578" />
+ </filter>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 32 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="64 : 32 : 1"
+ inkscape:persp3d-origin="32 : 21.333333 : 1"
+ id="perspective2455" />
+ <filter
+ inkscape:collect="always"
+ id="filter3252">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.85786015"
+ id="feGaussianBlur3254" />
+ </filter>
+ </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="28.694228"
+ inkscape:cy="36.511785"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:document-units="px"
+ inkscape:grid-bbox="true"
+ inkscape:window-width="1269"
+ inkscape:window-height="721"
+ inkscape:window-x="5"
+ inkscape:window-y="48" />
+ <metadata
+ id="metadata2452">
+ <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="g3256"
+ transform="matrix(1.1671607,0,0,1.2862955,-5.6097216,-7.3310909)">
+ <rect
+ transform="matrix(1,0,-2.1880957e-3,0.9999976,0,0)"
+ y="39.598446"
+ x="41.706299"
+ height="15.004455"
+ width="17.256737"
+ id="rect2425"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880957e-3,0.9999976,0,0)"
+ y="23.047192"
+ x="22.973858"
+ height="15.004455"
+ width="17.256737"
+ id="rect2423"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#666666;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880959e-3,0.9999976,0,0)"
+ y="8.2298822"
+ x="41.965664"
+ height="15.004452"
+ width="17.256737"
+ id="rect2419"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880962e-3,0.9999976,0,0)"
+ y="7.8751988"
+ x="5.5564451"
+ height="15.004449"
+ width="17.256737"
+ id="rect2415"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880956e-3,0.9999976,0,0)"
+ y="38.872116"
+ x="5.5869327"
+ height="15.004457"
+ width="17.256737"
+ id="rect2573"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <path
+ transform="matrix(0.1398092,0,0,0.1682205,-11.962716,-53.009917)"
+ inkscape:export-ydpi="32.462494"
+ inkscape:export-xdpi="32.462494"
+ inkscape:export-filename="/home/quintaesencia/Imágenes/caballo.png"
+ id="rect3876"
+ d="M 258.19318,493.53613 C 250.25008,493.53613 243.84948,497.26313 243.84948,501.91113 L 243.84948,510.22363 C 243.84948,514.87153 250.24998,518.59863 258.19318,518.59863 L 268.47448,518.59863 C 265.00668,521.79863 262.66198,528.13873 262.66198,535.44233 L 262.66198,554.28613 C 262.66198,562.12143 265.36788,568.82113 269.25578,571.75483 L 234.41198,571.75483 C 223.77058,571.75483 215.19318,576.80693 215.19318,583.09863 L 215.19318,594.34863 C 215.19318,597.78843 217.78898,600.83183 221.84948,602.91113 L 213.47448,602.91113 C 199.89328,602.91113 188.94318,608.83923 188.94318,616.19237 L 188.94318,629.31737 C 188.94318,636.67047 199.89318,642.59867 213.47448,642.59867 L 448.19318,642.59867 C 461.77448,642.59867 472.69318,636.67047 472.69318,629.31737 L 472.69318,616.19237 C 472.69318,608.83923 461.77438,602.91113 448.19318,602.91113 L 430.88078,602.91113 C 434.94118,600.83183 437.53698,597.78843 437.53698,594.34863 L 437.53698,583.09863 C 437.53698,576.80693 428.95958,571.75483 418.31818,571.75483 L 383.47448,571.75483 C 387.36228,568.82113 390.06818,562.12143 390.06818,554.28613 L 390.06818,535.44233 C 390.06818,528.13873 387.72348,521.79863 384.25578,518.59863 L 395.47448,518.59863 C 403.41758,518.59863 409.78698,514.87153 409.78698,510.22363 L 409.78698,501.91113 C 409.78698,497.26313 403.41768,493.53613 395.47448,493.53613 L 258.19318,493.53613 z"
+ style="fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#d6d7ed;stroke-width:4.15098858;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter3576)" />
+ <g
+ transform="matrix(0.1189242,0,0,0.1102313,-473.97977,-5.1461451)"
+ id="g3936"
+ style="fill:#0000ff;fill-opacity:1;stroke:#d6d7ed;stroke-width:5.27482986;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3252)">
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4212.4147,191.17133 C 4212.4147,191.17133 4238.6588,198.44109 4215.7174,257.42487 L 4212.5041,296.96294 C 4209.4959,307.49857 4189.895,313.71464 4182.8512,307.60339 C 4183.3169,294.564 4193.6193,274.4623 4184.2482,269.99857 C 4193.6193,275.00551 4183.3169,295.14598 4182.8512,307.71968 C 4181.9967,312.76419 4163.2342,318.81759 4159.1008,307.71968 C 4158.0523,292.14212 4156.5856,311.78092 4148.6475,295.54078 C 4144.4553,286.9641 4128.0073,226.89553 4125.2571,219.01273 C 4117.0241,195.41472 4125.1211,191.17126 4124.1739,170.80604 C 4114.86,160.5608 4093.6523,158.40645 4096.2323,140.07033 C 4138.6103,122.37401 4153.9782,147.98711 4150.7184,176.39436 C 4151.3195,166.8086 4151.7794,147.52419 4138.452,139.087 C 4152.8885,117.19945 4176.7972,111.66308 4201.0132,109.33462 C 4307.3727,107.35162 4368.8195,200.47932 4356.1016,322.29746 L 4189.0241,322.29747 L 4192.3832,310.05726 C 4199.2283,308.91128 4208.755,304.53155 4212.9184,297.45645 L 4215.5237,257.50686 C 4219.3492,248.37528 4235.1097,201.62026 4212.4147,191.17133 z"
+ id="path3928"
+ sodipodi:nodetypes="cccccccssccccccccccc" />
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4156.3067,232.27747 C 4157.7038,222.96362 4164.902,210.62306 4160.4979,204.33591 L 4163.2921,203.46296"
+ id="path3930"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4171.9354,129.66914 C 4308.1707,89.75231 4331.6019,244.19452 4328.581,322.66445"
+ id="path3932"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4119.0909,142.18494 C 4105.4162,155.1643 4130.6796,162.58107 4136.474,174.16978"
+ id="path3934"
+ sodipodi:nodetypes="cc" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/activity/activity-icon.svg b/activity/activity-icon.svg
new file mode 100644
index 0000000..583c1db
--- /dev/null
+++ b/activity/activity-icon.svg
@@ -0,0 +1,155 @@
+<?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://creativecommons.org/ns#"
+ 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://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="64px"
+ height="64px"
+ id="svg2447"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="chess icon.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape">
+ <defs
+ id="defs2449">
+ <filter
+ inkscape:collect="always"
+ id="filter3576">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.78138117"
+ id="feGaussianBlur3578" />
+ </filter>
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 32 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="64 : 32 : 1"
+ inkscape:persp3d-origin="32 : 21.333333 : 1"
+ id="perspective2455" />
+ <filter
+ inkscape:collect="always"
+ id="filter3252">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.85786015"
+ id="feGaussianBlur3254" />
+ </filter>
+ </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="28.694228"
+ inkscape:cy="36.511785"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:document-units="px"
+ inkscape:grid-bbox="true"
+ inkscape:window-width="1269"
+ inkscape:window-height="721"
+ inkscape:window-x="5"
+ inkscape:window-y="48" />
+ <metadata
+ id="metadata2452">
+ <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="g3256"
+ transform="matrix(1.1671607,0,0,1.2862955,-5.6097216,-7.3310909)">
+ <rect
+ transform="matrix(1,0,-2.1880957e-3,0.9999976,0,0)"
+ y="39.598446"
+ x="41.706299"
+ height="15.004455"
+ width="17.256737"
+ id="rect2425"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880957e-3,0.9999976,0,0)"
+ y="23.047192"
+ x="22.973858"
+ height="15.004455"
+ width="17.256737"
+ id="rect2423"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#666666;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880959e-3,0.9999976,0,0)"
+ y="8.2298822"
+ x="41.965664"
+ height="15.004452"
+ width="17.256737"
+ id="rect2419"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880962e-3,0.9999976,0,0)"
+ y="7.8751988"
+ x="5.5564451"
+ height="15.004449"
+ width="17.256737"
+ id="rect2415"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <rect
+ transform="matrix(1,0,-2.1880956e-3,0.9999976,0,0)"
+ y="38.872116"
+ x="5.5869327"
+ height="15.004457"
+ width="17.256737"
+ id="rect2573"
+ style="fill:#aa4400;fill-opacity:0.97397766;fill-rule:evenodd;stroke:#aa4400;stroke-width:0.31013331;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.95539036" />
+ <path
+ transform="matrix(0.1398092,0,0,0.1682205,-11.962716,-53.009917)"
+ inkscape:export-ydpi="32.462494"
+ inkscape:export-xdpi="32.462494"
+ inkscape:export-filename="/home/quintaesencia/Imágenes/caballo.png"
+ id="rect3876"
+ d="M 258.19318,493.53613 C 250.25008,493.53613 243.84948,497.26313 243.84948,501.91113 L 243.84948,510.22363 C 243.84948,514.87153 250.24998,518.59863 258.19318,518.59863 L 268.47448,518.59863 C 265.00668,521.79863 262.66198,528.13873 262.66198,535.44233 L 262.66198,554.28613 C 262.66198,562.12143 265.36788,568.82113 269.25578,571.75483 L 234.41198,571.75483 C 223.77058,571.75483 215.19318,576.80693 215.19318,583.09863 L 215.19318,594.34863 C 215.19318,597.78843 217.78898,600.83183 221.84948,602.91113 L 213.47448,602.91113 C 199.89328,602.91113 188.94318,608.83923 188.94318,616.19237 L 188.94318,629.31737 C 188.94318,636.67047 199.89318,642.59867 213.47448,642.59867 L 448.19318,642.59867 C 461.77448,642.59867 472.69318,636.67047 472.69318,629.31737 L 472.69318,616.19237 C 472.69318,608.83923 461.77438,602.91113 448.19318,602.91113 L 430.88078,602.91113 C 434.94118,600.83183 437.53698,597.78843 437.53698,594.34863 L 437.53698,583.09863 C 437.53698,576.80693 428.95958,571.75483 418.31818,571.75483 L 383.47448,571.75483 C 387.36228,568.82113 390.06818,562.12143 390.06818,554.28613 L 390.06818,535.44233 C 390.06818,528.13873 387.72348,521.79863 384.25578,518.59863 L 395.47448,518.59863 C 403.41758,518.59863 409.78698,514.87153 409.78698,510.22363 L 409.78698,501.91113 C 409.78698,497.26313 403.41768,493.53613 395.47448,493.53613 L 258.19318,493.53613 z"
+ style="fill:#0000ff;fill-opacity:1;fill-rule:nonzero;stroke:#d6d7ed;stroke-width:4.15098858;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter3576)" />
+ <g
+ transform="matrix(0.1189242,0,0,0.1102313,-473.97977,-5.1461451)"
+ id="g3936"
+ style="fill:#0000ff;fill-opacity:1;stroke:#d6d7ed;stroke-width:5.27482986;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter3252)">
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4212.4147,191.17133 C 4212.4147,191.17133 4238.6588,198.44109 4215.7174,257.42487 L 4212.5041,296.96294 C 4209.4959,307.49857 4189.895,313.71464 4182.8512,307.60339 C 4183.3169,294.564 4193.6193,274.4623 4184.2482,269.99857 C 4193.6193,275.00551 4183.3169,295.14598 4182.8512,307.71968 C 4181.9967,312.76419 4163.2342,318.81759 4159.1008,307.71968 C 4158.0523,292.14212 4156.5856,311.78092 4148.6475,295.54078 C 4144.4553,286.9641 4128.0073,226.89553 4125.2571,219.01273 C 4117.0241,195.41472 4125.1211,191.17126 4124.1739,170.80604 C 4114.86,160.5608 4093.6523,158.40645 4096.2323,140.07033 C 4138.6103,122.37401 4153.9782,147.98711 4150.7184,176.39436 C 4151.3195,166.8086 4151.7794,147.52419 4138.452,139.087 C 4152.8885,117.19945 4176.7972,111.66308 4201.0132,109.33462 C 4307.3727,107.35162 4368.8195,200.47932 4356.1016,322.29746 L 4189.0241,322.29747 L 4192.3832,310.05726 C 4199.2283,308.91128 4208.755,304.53155 4212.9184,297.45645 L 4215.5237,257.50686 C 4219.3492,248.37528 4235.1097,201.62026 4212.4147,191.17133 z"
+ id="path3928"
+ sodipodi:nodetypes="cccccccssccccccccccc" />
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4156.3067,232.27747 C 4157.7038,222.96362 4164.902,210.62306 4160.4979,204.33591 L 4163.2921,203.46296"
+ id="path3930"
+ sodipodi:nodetypes="ccc" />
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4171.9354,129.66914 C 4308.1707,89.75231 4331.6019,244.19452 4328.581,322.66445"
+ id="path3932"
+ sodipodi:nodetypes="cc" />
+ <path
+ style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:#d6d7ed;stroke-width:5.27482986;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 4119.0909,142.18494 C 4105.4162,155.1643 4130.6796,162.58107 4136.474,174.16978"
+ id="path3934"
+ sodipodi:nodetypes="cc" />
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..a4b1664
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,10 @@
+[Activity]
+name = Ajedrez
+license = GPLv2+
+bundle_id = org.x.ajedrezactivity
+class = chessactivity.ChessActivity
+icon = activity-icon
+activity_version = 1
+host_version = 1
+show_launcher = yes
+service_name = org.x.ajedrezactivity
diff --git a/board.py b/board.py
new file mode 100644
index 0000000..a698e43
--- /dev/null
+++ b/board.py
@@ -0,0 +1,288 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+import os
+import time
+from cell import Cell
+from errors import MoveError
+import logging
+log = logging.getLogger()
+
+WHITE = 'white'
+BLACK = 'black'
+
+LEFT = -1
+RIGHT = 1
+UP = -1
+DOWN = 1
+
+WHITE_RANK = 7
+WHITE_PAWN_DIR = UP
+BLACK_RANK = 0
+BLACK_PAWN_DIR = DOWN
+
+class Player(object):
+ def __init__(self, name, rank, pawn_dir):
+ self.name = name
+ self.en_passant = None
+ self.enemy = None
+ self.rank = rank
+ self.castling_performed = False
+ self.pawn_dir = pawn_dir
+
+ def __str__(self):
+ return self.name
+
+class Board(object):
+ '''Representation of the board. A board holds the piece instances that
+ live in it. It is also responsible for asking moves to perform themselves,
+ storing them in the move stack (for undo) and for asking pieces to render
+ themselves.
+
+ A board's individual cells can be accessed using the array subscript
+ operator twice. Eg. board[0][0] contains the cell (instance of class
+ Cell) at the row 0, column 0. Boards are column-major like a 2D
+ coordinate system, meaning board[i][j] will access column i, row j.
+
+ Individual pieces may be accessed through their cells, like so:
+ board[i][j].piece will reference the piece stored at the cell at
+ column i, row j.
+
+ Boards by default are not hypothetical unless they are created by a call
+ to clone() on an exisiting board. Hypothetical boards are used by the
+ pieces to determine their moves and stop recursion.
+ Conversely, special parameter hypothetical is used to flag whether moves
+ are being calculated for a hypothetical board (such as checking if the
+ king is checked after moving to some cell). This parameter prevents an
+ inifite recursion when checking a king's possible moves for instance.
+
+ Boards contain a move stack used to implement undo and a list of dirty
+ cells that need to have their clean_moves method called after a move is
+ successfully performed.
+
+ '''
+ def __init__(self, width=1, height=1):
+ '''Create a new instance of Board.
+
+ Parameters width and height specify
+ the board's visual width and height for rendering purposes.
+
+ '''
+ self.w, self.h = width, height
+ self.board = []
+ self.move_stack = []
+ self.dirty_cells = []
+
+ self.cells = []
+
+ self.white = Player(WHITE, WHITE_RANK, WHITE_PAWN_DIR)
+ self.black = Player(BLACK, BLACK_RANK, BLACK_PAWN_DIR)
+ self.white.enemy = self.black
+ self.black.enemy = self.white
+ self.players = [self.white, self.black]
+
+ #Current turn
+ self.current_turn = self.white
+ self.turns = 1
+
+ #Populate the board with Cells:
+ for i in range(0, 8):
+ self.board.append([])
+ for j in range(0, 8):
+ if i % 2 != j % 2:
+ color = (0, 0, 0)
+ else:
+ color = (255, 255, 255)
+ cell = Cell((i, j), width/8, color)
+ self.board[i].append(cell)
+ self.cells.append(cell)
+
+ def __getitem__(self, col):
+ '''Overload operator[] so Board cells can be accessed using the
+ boad[column][row] convention.
+
+ '''
+ return self.board[col[0]][col[1]]
+
+ #def on_cell_selected(self, selected_cell):
+ # '''Handle cell selected events sent by the Board Controller.
+ #
+ # This will involve determining the movements for the piece in the
+ # selected cell and updating every destination cell's move list.
+ #
+ # '''
+ #
+ # if not selected_cell.piece:
+ # return
+ #
+ # moves = selected_cell.piece.get_moves(selected_cell.i, selected_cell.j, self)
+ # for move in moves:
+ # col, row = move.to_c, move.to_r
+ # self.board[col][row].add_move(move)
+ # self.dirty_cells.append((col,row))
+
+ def can_move_piece_in_cell_to(self, cell, to):
+ '''Determine whether the piece in the cell can move
+ to the (to[0], to[1]) cell in the board.
+
+ Returns True if there is a piece in selected_cell and it can move to
+ board[to]. This method must be called before calling
+ move_piece_in_cell_to.
+
+ '''
+ if cell.piece:
+ move = cell.piece.get_move(cell.pos, to, self)
+ return move is not None and \
+ not move.causes_check(self, cell.piece.owner)
+ else:
+ return False
+
+
+ def move_piece_in_cell_to(self, player, fro, to, **options):
+ '''
+ Move a piece from position "fro" to position "to".
+
+ This method checks whether a piece is actually at "fro" and that its
+ owner is the given "player" parameter.
+
+ "options" is an optional parameter used to provide move metadata, such
+ as the piece a pawn is crowned to.
+ '''
+
+ if not self[fro].piece:
+ raise MoveError("No piece to move at (%d,%d)" % fro)
+
+ if self[fro].piece.owner != player:
+ raise MoveError("Piece at (%d,%d) is not from player %s" %
+ (fro[0], fro[1], player))
+
+ move = self[fro].piece.get_move(fro, to, self, **options)
+ if not move:
+ raise MoveError(
+ "No moves take from (%d,%d) to (%d,%d) that this piece knows of" %
+ (fro + to))
+
+ self.move_stack.append(move)
+ move.perform(self)
+
+ #for col,row in self.dirty_cells:
+ # self.board[col][row].clear_moves()
+ self.next_turn()
+ return move
+
+ def perform_move(self, move):
+ '''Apply a move on the board.
+
+ Normally, callers will use the board.move_piece_in_cell_to method in order
+ to move the piece stored in the selected cell to a certain cell in the
+ board.
+
+ This method is useful for applying moves on the board which come from
+ external sources, such as a Chess Engine (GNU Chess) or over the network
+ (when implemented).
+
+ '''
+
+ if not self[move.fro].piece:
+ raise MoveError("Cannot move from (%d,%d) to (%d,%d). No piece there." %
+ (move.fro + move.to))
+
+ self.move_stack.append(move)
+ move.perform(self)
+ self.next_turn()
+ return move
+
+ def undo_move(self):
+ if self.move_stack:
+ self.previous_turn()
+ self.move_stack.pop().undo(self)
+
+ def get_all_moves(self, owner, attack_only = False, filter_check=False):
+ '''Get all owner's moves'''
+ #rebuild move cache:
+ all_moves = []
+ for cell in self.cells:
+ if cell.piece and cell.piece.owner == owner:
+ all_moves.extend(cell.piece.get_moves(
+ cell.pos, self, attack_only,
+ filter_check=filter_check))
+
+ return all_moves
+
+ def has_moves(self, owner, filter_check=True):
+ for cell in self.cells:
+ piece = cell.piece
+ if piece and piece.owner == owner and \
+ piece.has_moves(cell.pos, self, filter_check=filter_check):
+ return True
+ return False
+
+ def get_all_attack_moves(self, owner, piece=None):
+ '''Get all owner's enemy's moves.'''
+ attack_moves = self.get_all_moves(owner, attack_only=True, filter_check=False)
+ if piece is not None:
+ return [x for x in attack_moves if self[x.to] == piece]
+ else:
+ return attack_moves
+
+ def king_is_checked(self, owner):
+ '''Check whether the king of the given owner is under attack'''
+ #Find the king and all attacks
+ king_pos = self.get_king_position(owner)
+ for move in self.get_all_attack_moves(owner.enemy):
+ if move.to == king_pos:
+ return True
+ return False
+
+ def king_is_checkmated(self, owner):
+ return not self.has_moves(owner, filter_check=True) and \
+ self.king_is_checked(owner)
+
+ def get_king_position(self,owner):
+ '''Find the owner's (white or black) king's position'''
+ for cell in self.cells:
+ if cell.piece and cell.piece.type == "king" and \
+ cell.piece.owner == owner:
+ return cell.pos
+ raise Exception("Error: %s king not found" % owner)
+
+ def next_turn(self):
+ '''Make the change of turn.'''
+ self.turns += 1
+ self.current_turn = self.current_turn.enemy
+ return self.current_turn
+
+ def previous_turn(self):
+ self.turns -= 1
+ self.current_turn = self.current_turn.enemy
+ return self.current_turn
+
+ def put_piece_at(self, piece, pos):
+ if pos[0] < 0 or pos[0] > 7 or pos[1] < 0 or pos[1] > 7:
+ raise Exception("Indices out of board: (%d, %d)" % pos)
+
+ self[pos].piece = piece
+ self.moves_cache_dirty = True
+
+ def pick(self, x, y):
+ '''Try to pick piece in the cell below the x,y screen position.
+ If the cell does not contain a piece, return None.'''
+ for cell in self.cells:
+ if cell.contains(x, y):
+ return cell
+ return None
diff --git a/boardcontroller.py b/boardcontroller.py
new file mode 100644
index 0000000..5d611dc
--- /dev/null
+++ b/boardcontroller.py
@@ -0,0 +1,174 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+from piece import *
+from messenger import *
+from chessengine import *
+
+MODE_P_VS_CPU = 0
+MODE_P_VS_P = 1
+
+import logging
+log = logging.getLogger()
+
+class BoardController:
+ PLAYING = 'playing'
+ CHECKMATE = 'checkmate'
+ def __init__(self, board, mode = MODE_P_VS_P, debug=False):
+ '''Create a new board controller.'''
+ self.board = board
+ self.selected_cell = None
+ self.board.current_turn = self.board.black #will be flipped
+ self.board.next_turn() #set turn to white and show a message
+ self.game_state = BoardController.PLAYING
+
+ self.mode = mode
+ self.debug = debug
+ self.checkmate = False
+
+ self.ai = None
+ if mode == MODE_P_VS_CPU:
+ try:
+ self.ai = GnuChessEngine()
+ except Exception,ex:
+ log.error("Cannot start gnuchess. Defaulting to PvP.")
+ log.exception(ex)
+ self.mode = MODE_P_VS_P
+ #no last known player move
+ self.last_p_move = None
+
+ def close(self, message = None):
+ if self.ai:
+ self.ai.close()
+ self.ai = None
+ if message:
+ log.info(message + "\n")
+
+ def undo_move(self):
+ #FIXME: check if its possible to remove game_state variable.
+ #self.game_state = BoardController.PLAYING
+ if self.checkmate:
+ return
+
+ self.selected_cell = None #unselect piece (if any)
+
+ self.board.undo_move()
+ if self.ai:
+ self.ai.undo()
+ # first undo was ai move, now undo players
+ self.board.undo_move()
+
+ #TODO: Add castling and en-passant pawns indications.
+ def init_board_text(self, text):
+ '''Initialize board to a serialized position'''
+ column, row = 0, 0
+ kind_by_char = {'P': Pawn, 'N': Knight, 'B': Bishop,
+ 'R': Rook, 'Q': Queen, 'K': King}
+ for char in text:
+ player = char.isupper() and self.board.white or self.board.black
+ kind = kind_by_char.get(char.upper())
+
+ if kind:
+ self.board.put_piece_at(kind(player), (column, row))
+ if column == 7:
+ column, row = -1, row + 1
+ column = column + 1
+
+ self.board.current_turn = self.board.white
+ self.checkmate = self.board.king_is_checkmated(self.board.current_turn)
+
+ def init_board(self):
+ '''Initialize board to starting chess configuration'''
+ self.init_board_text("rnbqkbnr" + "p" * 8 + "." * 32 + "P" * 8 + "RNBQKBNR")
+
+ def update(self):
+ '''
+ Perform updates on the board such as animations (not implemented yet)
+ and calling the IA.
+ '''
+ #TODO: Animate piece movements
+
+ #Call IA:
+ if self.ai and self.board.current_turn == self.board.black and \
+ self.game_state != BoardController.CHECKMATE:
+ if self.last_p_move:
+ self.ai.move(self.last_p_move, self)
+ if self.debug:
+ self.ai.assert_sync(self.board)
+
+ def on_checkmate(self):
+ '''Handle checkmate events.'''
+ self.close("Checkmated")
+ self.checkmate = True
+
+ def on_cell_clicked(self, clicked_cell):
+ '''Handle mouse events from the user. This method gets called
+ by the event control code when the user clicks on a cell.'''
+
+ # Select a piece?
+ if clicked_cell.piece and not self.selected_cell:
+ self.selected_cell = clicked_cell
+ else:
+ # Move piece or deselect
+ if self.selected_cell:
+ if self.selected_cell.piece.is_turn(self.board.current_turn) and \
+ (self.selected_cell != clicked_cell):
+ self.move_piece(clicked_cell)
+ else:
+ self.selected_cell = None
+ else:
+ pass
+
+ def move_piece(self, cell):
+ '''Move the currently selected piece to a new cell.
+ The currently selected piece is at self.selected_cell.'''
+
+ if self.checkmate:
+ return
+
+ # Try to move the piece on the board:
+ if self.board.can_move_piece_in_cell_to(self.selected_cell, cell.pos):
+ move = self.board.move_piece_in_cell_to(
+ self.board.current_turn,
+ self.selected_cell.pos,
+ cell.pos)
+ if self.ai:
+ self.last_p_move = move
+ self.selected_cell = None
+ else:
+ if cell.piece:
+ self.selected_cell = cell
+
+ def move(self, player, fro, to, **options):
+ '''
+ Move a piece from position "fro" to position "to".
+
+ This method checks whether a piece is actually at "fro" and that its
+ owner is the given "player" parameter.
+
+ "options" is an optional parameter used to provide move metadata, such
+ as the piece a pawn is crowned to.
+ '''
+
+ if self.checkmate:
+ return
+
+ log.debug("%s: %d, %d to %d, %d", player, fro[0], fro[1], to[0], to[1])
+
+ self.board.move_piece_in_cell_to(player, fro, to, **options)
diff --git a/cell.py b/cell.py
new file mode 100644
index 0000000..b367d63
--- /dev/null
+++ b/cell.py
@@ -0,0 +1,80 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+class Cell(object):
+ '''A Cell instance represents a cell in a Board which may or may not store
+ a piece.
+
+ Cells are the objects that know where pieces are in the board.
+ Positions in the board are representd by the i and j
+ attributes, where i is the column the cell is at within the board and j is
+ the row the cell is at in the board.
+
+ In order to simplify the implementation of the board, cells are aware of the
+ valid moves that take to them. This is stored in the special list "moves" and
+ works as follows:
+
+ 1) When a cell is selected on the board and its moves are calculated, cells
+ where that piece may move to are notified by having their add_move method
+ called.
+
+ 2) When the user requests a move to be performed, the board will match the
+ valid moves at the given destination's cell with the cell selected at the
+ moment (the cell that contains the piece that's about to be moved).
+
+ 3) The move is performed and all the cells move lists are cleared.
+ '''
+ def __init__(self, pos, size, color, piece = None):
+ '''Create a new instance of Cell.
+ Expected parameters are:
+ color: the color of the cell.
+ pos: the cell position.
+ size: attribute determining the size of the cell when rendered.
+ piece: the piece this cell contains (None by default).
+ '''
+ self.color = color
+ self.piece = piece
+ self.size = size
+ self.pos = pos
+ self.moves = []
+
+ def __getitem__(self, idx):
+ return self.pos[idx]
+
+ def __eq__(self, other):
+ return isinstance(other, Cell) and self.pos == other.pos
+
+ def contains(self, x, y):
+ '''Return True if this cell contains the given (x,y) point,
+ False otherwise. This method is used to implement pick()ing into the
+ board.'''
+ tx = self[0] * self.size
+ ty = self[1] * self.size
+
+ if x > tx and x < tx + self.size and \
+ y > ty and y < ty + self.size:
+ return True
+
+ def add_move(self, move):
+ '''Add a move to the list of possible moves that take pieces to this cell.'''
+ self.moves.append(move)
+
+ def clear_moves(self):
+ '''Erase the list of possible moves that take pieces into this cell. '''
+ self.moves = []
diff --git a/chessactivity.py b/chessactivity.py
new file mode 100644
index 0000000..f1a33d6
--- /dev/null
+++ b/chessactivity.py
@@ -0,0 +1,55 @@
+import sys
+import os
+from sugargame.canvas import PygameCanvas
+import gobject
+
+import gettext
+from gettext import gettext as _
+
+from sugar.activity import activity
+from main import CeibalChess, log
+
+class ChessActivity(activity.Activity):
+ '''
+ ChessActivity provides the basic configuration for
+ setting up and running Ceibal-Chess as an Activity and
+ for embedding pygame in a gtk window.
+
+ For all the methods of activity.Activity please visit:
+ http://api.sugarlabs.org/epydocs/sugar.activity.activity-pysrc.html
+
+ For the logic behind the GTK-pygame wrapping, visit:
+ http://wiki.sugarlabs.org/go/Development_Team/Sugargame
+ '''
+ def __init__(self, handle):
+ # i18n:
+ bundle_path = os.environ["SUGAR_BUNDLE_PATH"]
+ i18n_path = os.path.join(bundle_path, "po")
+ gettext.bindtextdomain("messages", i18n_path)
+ gettext.textdomain("messages")
+
+ activity.Activity.__init__(self, handle)
+ self.canvas = PygameCanvas(self)
+ self.set_canvas(self.canvas)
+ self.chess = CeibalChess()
+ self.chess.set_close_callback(self.close)
+ self.show()
+ gobject.idle_add(self.start_cb, None)
+ #rc = CeibalChess().start(1200, 900)
+ #sys.exit(rc)
+
+ def start_cb(self, param):
+ log.info("Starting pygame widget...")
+ self.canvas.run_pygame(self.chess.start)
+
+ def read_file(self, filepath):
+ pass
+
+ def write_file(self, filepath):
+ pass
+
+ def can_close(self):
+ log.info("Activity requested to stop...")
+ self.chess.stop()
+ return True
+
diff --git a/chessengine.py b/chessengine.py
new file mode 100644
index 0000000..78db952
--- /dev/null
+++ b/chessengine.py
@@ -0,0 +1,229 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+import sys
+from subprocess import Popen, PIPE
+from piece import Move
+from errors import IAError
+
+import logging
+log = logging.getLogger()
+
+class GnuChessEngine:
+ '''GNU Chess wrapper class.'''
+ def __init__(self):
+ '''Create a new instance of the GNU Chess wrapper, locate the
+ gnuchess executable, open a pipe to it and setup the comm.'''
+ try:
+ path = os.path.join(os.environ["SUGAR_BUNDLE_PATH"],"engines")
+ if not "Ajedrez.activity" in path:
+ print "Runningn ceibal-chess from Terminal or some other place"
+ path = os.path.join("", "engines")
+ except:
+ path = os.path.join(".", "engines")
+
+ self.plat = sys.platform
+ if self.plat == "linux2":
+ engine_exec = "gnuchess-linux"
+ elif self.plat == "darwin":
+ engine_exec = "gnuchess-osx"
+ elif self.plat == "win32":
+ engine_exec = "gnuchess-win32.exe"
+ else:
+ log.warn("No gnuchess for %s, using system default", plat)
+ engine_exec = ""
+
+ if engine_exec != "":
+ engine_path = os.path.join(path, engine_exec)
+ else:
+ log.info("Trying to find gnuchess in PATH")
+ engine_path = "gnuchess"
+
+ #Check whether the engine is executable:
+ if not os.access(engine_path.split()[0], os.X_OK):
+ log.error("Engine is not executable, try: chmod +x %s",
+ engine_path.split()[0])
+ raise IOError("Chess engine is not executable.")
+
+ args = [engine_exec, '-e', '-x']
+
+ try:
+ if self.plat == "win32":
+ self.proc = Popen(args, executable=os.path.abspath(engine_path), stdin=PIPE, stdout=PIPE)
+ else:
+ self.proc = Popen(args, executable=os.path.abspath(engine_path), stdin=PIPE, stdout=PIPE, close_fds=True)
+ self.fin = self.proc.stdout
+ self.fout = self.proc.stdin
+
+ #Check pipe:
+ self.fout.write("\n")
+ self.fout.flush()
+ self.fin.readline()
+ self.fin.readline()
+ self.fin.readline()
+ self.fout.write("depth 1\n")
+ except Exception, ex:
+ print ex
+ self.close()
+ raise
+
+ def undo(self):
+ # undo ai move
+ self.fout.write('undo\n')
+ # undo player move
+ self.fout.write('undo\n')
+ self.fout.flush()
+
+ def move(self, move, controller):
+ '''Write a player's move to GNU Chess. Return the engine's move.'''
+ move_str = self.move_to_gnuchess(move)
+
+ log.debug("Calling GNU Chess with move: %s", move_str)
+
+ self.fout.write(move_str + "\n")
+ self.fout.flush()
+ l = self.fin.readline()
+ while l.find("My move is") == -1:
+ if l.find("Illegal move") != -1:
+ raise IAError( \
+ "Player performed an illegal move: (%s), move was: %s" %
+ (l, move_str))
+ l = self.fin.readline()
+ ans = l.split()[3]
+ log.debug("got answer from gnuchess '%s'", l)
+
+ chess_ans = None
+ type = None
+
+ if len(ans) == 4:
+ chess_ans = (self.gnuchess_to_coords(ans[:2]), \
+ self.gnuchess_to_coords(ans[2:]))
+ elif len(ans) == 5:
+ chess_ans = (self.gnuchess_to_coords(ans[:2]), \
+ self.gnuchess_to_coords(ans[2:4]))
+ type = ans[4].upper()
+ else:
+ raise IAError("Unknown answer from gnuchess: %s" % (ans))
+
+ controller.move(controller.board.black, chess_ans[0], chess_ans[1], type=type)
+
+ def close(self):
+ try:
+ if self.fout:
+ self.fout.write("quit\n")
+ self.fout.flush()
+ self.fout.close()
+ self.fout = None
+ if self.fin:
+ self.fin.close()
+ self.fin = None
+ finally:
+ if self.proc:
+ try:
+ if self.plat == "win32":
+ pass
+ #os.system("taskkill /PID %s" %self.proc.pid)
+ #FIXME: Add something to kill the process in Windows OS,
+ #the os.system solution does not work.
+ else:
+ os.kill(self.proc.pid, 15)
+ except OSError:
+ pass
+ self.proc.wait()
+ self.proc = None
+
+ def move_to_gnuchess(self, move):
+ return str(move)
+
+ def gnuchess_to_coords(self, move):
+ letters = ["a", "b", "c", "d", "e", "f", "g", "h"]
+ c = letters.index(move[0])
+ r = int(8 - int(move[1]))
+ return (c,r)
+
+ def assert_sync(self, board):
+ '''
+ Validate whether the AI's internal representation of the board
+ matches our board.
+
+ Raises IAError (games out of sync) on error, otherwise, just returns.
+
+ This is rather expensive and should only be perfomed
+ in debug mode.
+ '''
+ log.debug("Called IA.assert_sync...")
+ try:
+ self.fout.write("show board\n")
+ self.fout.write("\n")
+ self.fout.flush()
+
+ log.debug("read 1...")
+ self.fin.readline()
+
+ log.debug("read 2...")
+ ai_turn = self.fin.readline().split(" ")[0]
+
+ #if ai_turn != board.current_turn.name:
+ # raise IAError("Turns out of sync!")
+
+ line = ""
+ ai_row = []
+ while len(ai_row) < 8:
+ line = self.fin.readline().replace("\n", "").strip()
+ ai_row = line.split(" ")
+
+ for row in range(8):
+ log.debug("row %d: %s" % (row, line))
+
+ for col in range(8):
+ ai_cell = ai_row[col]
+ if ai_cell == ".":
+ if not board[col, row].piece:
+ continue
+ raise AIError( \
+ "Out of sync: AI thinks (%d, %d) should be empty" %
+ (col, row))
+
+ if ai_cell.upper() != board[col, row].piece.CODE:
+ raise AIError("Out of sync: piece at (%d, %d) differs" %
+ (col, row))
+
+ if ai_cell.islower() and board[col, row].piece.owner.name != "black":
+ raise AIError("Out of sync: owner at (%d, %d) differs" %
+ (col, row))
+
+ if ai_cell.isupper() and board[col, row].piece.owner.name != "white":
+ raise AIError("Out of sync: owner at (%d, %d) differs" %
+ (col, row))
+
+ if row < 7:
+ line = self.fin.readline().replace("\n", "").strip()
+ ai_row = line.split(" ")
+
+ log.debug("read 10...")
+ self.fin.readline()
+
+
+ log.debug("read 11...")
+ self.fin.readline()
+
+ except IOError, err:
+ raise IAError("Could not talk to engine: %s" % err.message )
+
diff --git a/createbundle.sh b/createbundle.sh
new file mode 100755
index 0000000..e52edcf
--- /dev/null
+++ b/createbundle.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+echo "Creating bundle/ajedrez.xo"
+rm -rdf bundle
+mkdir bundle
+mkdir bundle/Ajedrez.activity
+cp *.py bundle/Ajedrez.activity/
+cp -R sugargame bundle/Ajedrez.activity/
+cp -r po bundle/Ajedrez.activity/
+
+mkdir bundle/Ajedrez.activity/engines
+cp engines/gnuchess-linux bundle/Ajedrez.activity/engines/
+
+#mkdir bundle/Ajedrez.activity/data
+#cp -R data/* bundle/Ajedrez.activity/data/
+
+mkdir bundle/Ajedrez.activity/data_bw
+cp -R data_bw/* bundle/Ajedrez.activity/data_bw/
+
+mkdir bundle/Ajedrez.activity/activity
+cp activity-icon.svg bundle/Ajedrez.activity/activity/
+cd bundle
+
+#Create activity.info
+cat > Ajedrez.activity/activity/activity.info <<EOF
+
+[Activity]
+name = Ajedrez
+service_name = org.x.ajedrezactivity
+activity_version = 1
+host_version = 1
+bundle_id = org.x.ajedrezactivity
+icon = activity-icon
+class = chessactivity.ChessActivity
+show_launcher = yes
+EOF
+
+#Create setup.py
+cat > Ajedrez.activity/setup.py <<EOF
+from sugar.activity import bundlebuilder
+if __name__ == "__main__":
+ bundlebuilder.start("ajedrezactivity")
+EOF
+
+find Ajedrez.activity -type f | grep -v MANIFEST | grep -v ".svn" | sed -e 's,^Ajedrez.activity/,,' > Ajedrez.activity/MANIFEST
+rm -f ajedrez.xo
+zip -rq ajedrez.xo Ajedrez.activity
+#zip -dq ajedrez.xo "*.svn*"
+cd ..
diff --git a/data/bg.png b/data/bg.png
new file mode 100644
index 0000000..1b888ad
--- /dev/null
+++ b/data/bg.png
Binary files differ
diff --git a/data/bg.png.old b/data/bg.png.old
new file mode 100644
index 0000000..6cf85fa
--- /dev/null
+++ b/data/bg.png.old
Binary files differ
diff --git a/data/bishop.png b/data/bishop.png
new file mode 100644
index 0000000..9539f1e
--- /dev/null
+++ b/data/bishop.png
Binary files differ
diff --git a/data/bishopblack.png b/data/bishopblack.png
new file mode 100644
index 0000000..294db1f
--- /dev/null
+++ b/data/bishopblack.png
Binary files differ
diff --git a/data/bishopwhite.png b/data/bishopwhite.png
new file mode 100644
index 0000000..b8efa58
--- /dev/null
+++ b/data/bishopwhite.png
Binary files differ
diff --git a/data/btn_back.png b/data/btn_back.png
new file mode 100644
index 0000000..8229b73
--- /dev/null
+++ b/data/btn_back.png
Binary files differ
diff --git a/data/king.png b/data/king.png
new file mode 100644
index 0000000..afacdd1
--- /dev/null
+++ b/data/king.png
Binary files differ
diff --git a/data/kingblack.png b/data/kingblack.png
new file mode 100644
index 0000000..bd57424
--- /dev/null
+++ b/data/kingblack.png
Binary files differ
diff --git a/data/kingwhite.png b/data/kingwhite.png
new file mode 100644
index 0000000..97098cb
--- /dev/null
+++ b/data/kingwhite.png
Binary files differ
diff --git a/data/knight.png b/data/knight.png
new file mode 100644
index 0000000..ee46ec1
--- /dev/null
+++ b/data/knight.png
Binary files differ
diff --git a/data/knightblack.png b/data/knightblack.png
new file mode 100644
index 0000000..92446d1
--- /dev/null
+++ b/data/knightblack.png
Binary files differ
diff --git a/data/knightwhite.png b/data/knightwhite.png
new file mode 100644
index 0000000..bfdb14d
--- /dev/null
+++ b/data/knightwhite.png
Binary files differ
diff --git a/data/menu_back.png b/data/menu_back.png
new file mode 100644
index 0000000..44b275b
--- /dev/null
+++ b/data/menu_back.png
Binary files differ
diff --git a/data/menu_back2.png b/data/menu_back2.png
new file mode 100644
index 0000000..9430048
--- /dev/null
+++ b/data/menu_back2.png
Binary files differ
diff --git a/data/pawn.png b/data/pawn.png
new file mode 100644
index 0000000..c911794
--- /dev/null
+++ b/data/pawn.png
Binary files differ
diff --git a/data/pawnblack.png b/data/pawnblack.png
new file mode 100644
index 0000000..3e54624
--- /dev/null
+++ b/data/pawnblack.png
Binary files differ
diff --git a/data/pawnwhite.png b/data/pawnwhite.png
new file mode 100644
index 0000000..36e86e2
--- /dev/null
+++ b/data/pawnwhite.png
Binary files differ
diff --git a/data/queen.png b/data/queen.png
new file mode 100644
index 0000000..b55b1a2
--- /dev/null
+++ b/data/queen.png
Binary files differ
diff --git a/data/queenblack.png b/data/queenblack.png
new file mode 100644
index 0000000..3b08b9d
--- /dev/null
+++ b/data/queenblack.png
Binary files differ
diff --git a/data/queenwhite.png b/data/queenwhite.png
new file mode 100644
index 0000000..e9c81ae
--- /dev/null
+++ b/data/queenwhite.png
Binary files differ
diff --git a/data/rook.png b/data/rook.png
new file mode 100644
index 0000000..a2391e1
--- /dev/null
+++ b/data/rook.png
Binary files differ
diff --git a/data/rookblack.png b/data/rookblack.png
new file mode 100644
index 0000000..cd8cf83
--- /dev/null
+++ b/data/rookblack.png
Binary files differ
diff --git a/data/rookwhite.png b/data/rookwhite.png
new file mode 100644
index 0000000..5ba23c4
--- /dev/null
+++ b/data/rookwhite.png
Binary files differ
diff --git a/data/wood.png b/data/wood.png
new file mode 100644
index 0000000..e9a76ea
--- /dev/null
+++ b/data/wood.png
Binary files differ
diff --git a/data_bw/bg.png b/data_bw/bg.png
new file mode 100755
index 0000000..1b888ad
--- /dev/null
+++ b/data_bw/bg.png
Binary files differ
diff --git a/data_bw/bishop.png b/data_bw/bishop.png
new file mode 100755
index 0000000..90f461b
--- /dev/null
+++ b/data_bw/bishop.png
Binary files differ
diff --git a/data_bw/bishopblack.png b/data_bw/bishopblack.png
new file mode 100755
index 0000000..269a609
--- /dev/null
+++ b/data_bw/bishopblack.png
Binary files differ
diff --git a/data_bw/bishopwhite.png b/data_bw/bishopwhite.png
new file mode 100755
index 0000000..4fac6d7
--- /dev/null
+++ b/data_bw/bishopwhite.png
Binary files differ
diff --git a/data_bw/btn_back.png b/data_bw/btn_back.png
new file mode 100755
index 0000000..8229b73
--- /dev/null
+++ b/data_bw/btn_back.png
Binary files differ
diff --git a/data_bw/king.png b/data_bw/king.png
new file mode 100755
index 0000000..afacdd1
--- /dev/null
+++ b/data_bw/king.png
Binary files differ
diff --git a/data_bw/kingblack.png b/data_bw/kingblack.png
new file mode 100755
index 0000000..e0dfde1
--- /dev/null
+++ b/data_bw/kingblack.png
Binary files differ
diff --git a/data_bw/kingwhite.png b/data_bw/kingwhite.png
new file mode 100755
index 0000000..a4ea0ae
--- /dev/null
+++ b/data_bw/kingwhite.png
Binary files differ
diff --git a/data_bw/knight.png b/data_bw/knight.png
new file mode 100755
index 0000000..ee46ec1
--- /dev/null
+++ b/data_bw/knight.png
Binary files differ
diff --git a/data_bw/knightblack.png b/data_bw/knightblack.png
new file mode 100755
index 0000000..979ce4f
--- /dev/null
+++ b/data_bw/knightblack.png
Binary files differ
diff --git a/data_bw/knightwhite.png b/data_bw/knightwhite.png
new file mode 100755
index 0000000..ecf211e
--- /dev/null
+++ b/data_bw/knightwhite.png
Binary files differ
diff --git a/data_bw/menu_back.png b/data_bw/menu_back.png
new file mode 100755
index 0000000..44b275b
--- /dev/null
+++ b/data_bw/menu_back.png
Binary files differ
diff --git a/data_bw/menu_back2.png b/data_bw/menu_back2.png
new file mode 100755
index 0000000..9430048
--- /dev/null
+++ b/data_bw/menu_back2.png
Binary files differ
diff --git a/data_bw/pawn.png b/data_bw/pawn.png
new file mode 100755
index 0000000..c911794
--- /dev/null
+++ b/data_bw/pawn.png
Binary files differ
diff --git a/data_bw/pawnblack.png b/data_bw/pawnblack.png
new file mode 100755
index 0000000..4c87359
--- /dev/null
+++ b/data_bw/pawnblack.png
Binary files differ
diff --git a/data_bw/pawnwhite.png b/data_bw/pawnwhite.png
new file mode 100755
index 0000000..72ff325
--- /dev/null
+++ b/data_bw/pawnwhite.png
Binary files differ
diff --git a/data_bw/queen.png b/data_bw/queen.png
new file mode 100755
index 0000000..b55b1a2
--- /dev/null
+++ b/data_bw/queen.png
Binary files differ
diff --git a/data_bw/queenblack.png b/data_bw/queenblack.png
new file mode 100755
index 0000000..a6f0ade
--- /dev/null
+++ b/data_bw/queenblack.png
Binary files differ
diff --git a/data_bw/queenwhite.png b/data_bw/queenwhite.png
new file mode 100755
index 0000000..8bec7bf
--- /dev/null
+++ b/data_bw/queenwhite.png
Binary files differ
diff --git a/data_bw/rook.png b/data_bw/rook.png
new file mode 100755
index 0000000..a2391e1
--- /dev/null
+++ b/data_bw/rook.png
Binary files differ
diff --git a/data_bw/rookblack.png b/data_bw/rookblack.png
new file mode 100755
index 0000000..30c566b
--- /dev/null
+++ b/data_bw/rookblack.png
Binary files differ
diff --git a/data_bw/rookwhite.png b/data_bw/rookwhite.png
new file mode 100755
index 0000000..0d12ec7
--- /dev/null
+++ b/data_bw/rookwhite.png
Binary files differ
diff --git a/data_bw/wood.png b/data_bw/wood.png
new file mode 100755
index 0000000..e9a76ea
--- /dev/null
+++ b/data_bw/wood.png
Binary files differ
diff --git a/engines/cygwin1.dll b/engines/cygwin1.dll
new file mode 100755
index 0000000..0296b95
--- /dev/null
+++ b/engines/cygwin1.dll
Binary files differ
diff --git a/engines/gnuchess-linux b/engines/gnuchess-linux
new file mode 100755
index 0000000..39557b7
--- /dev/null
+++ b/engines/gnuchess-linux
Binary files differ
diff --git a/engines/gnuchess-osx b/engines/gnuchess-osx
new file mode 100755
index 0000000..b9468dc
--- /dev/null
+++ b/engines/gnuchess-osx
Binary files differ
diff --git a/engines/gnuchess-win32.exe b/engines/gnuchess-win32.exe
new file mode 100755
index 0000000..9b52397
--- /dev/null
+++ b/engines/gnuchess-win32.exe
Binary files differ
diff --git a/errors.py b/errors.py
new file mode 100644
index 0000000..5b85616
--- /dev/null
+++ b/errors.py
@@ -0,0 +1,33 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+class UndoError(Exception):
+ '''Exception class for undo errors.'''
+ def __init__(self, message):
+ Exception.__init__(self, message)
+
+class MoveError(Exception):
+ '''Exception class for Move errors.'''
+ def __init__(self, message):
+ Exception.__init__(self, message)
+
+class IAError(Exception):
+ '''Exception class for IA errors.'''
+ def __init__(self, message):
+ Exception.__init__(self, message)
diff --git a/main.py b/main.py
new file mode 100755
index 0000000..dd4e3ae
--- /dev/null
+++ b/main.py
@@ -0,0 +1,389 @@
+#!/usr/bin/env python
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import os
+import sys
+import time
+import traceback
+import logging
+import gtk
+import gettext
+from gettext import gettext as _
+
+logging.basicConfig(
+ #level=logging.NOTSET,
+ level=logging.DEBUG,
+ format='%(asctime)s %(levelname)s %(message)s',
+ )
+log = logging.getLogger()
+
+try:
+ import pygame
+ vers = pygame.vernum
+ # Force to False for rendering like on the XO
+ alpha_blending = vers[0] >= 1 and vers[1] >= 8
+ if not alpha_blending:
+ log.warn("Pygame version does not support alpha blending. Disabling...")
+
+except Exception, ex:
+ print >>sys.stderr, \
+ """\n\n############################################################
+ You either do not have pygame installed or it's not within PYTHONPATH,
+ read the trace for aditional information
+ ############################################################\n\n"""
+ print >>sys.stderr, ex.message
+ log.exception(ex)
+ sys.exit(1)
+
+try:
+ from board import *
+ from piece import *
+ from boardcontroller import *
+ #from messenger import *
+ from menu import *
+ from ui import StatePanel, BoardRenderer
+ from resourcemanager import image_manager
+
+except Exception, ex:
+ print >>sys.stderr, \
+ """\n\n##########################################
+ Something went wrong loading game modules,
+ read the trace for additional information
+ ##########################################\n\n"""
+ print >>sys.stderr, ex.message
+ log.exception(ex)
+ sys.exit(1)
+
+class CeibalChess(object):
+ def __init__(self, dump=False):
+ self.controller = None
+ self.screen = None
+ self.game_code = None
+ self.dump_path = None
+ self.close_callback = None
+
+ def start(self, scr_w=1200, scr_h=900, dump=False, gtk_embedded=True):
+ log.warn("LANG is %s" % os.environ["LANG"])
+ self.debug = dump
+ self.gtk_embedded = gtk_embedded
+ try:
+ if dump:
+ self._init_dump()
+ try:
+ self.done = False
+ self._run(scr_w, scr_h)
+ return 0
+ except KeyboardInterrupt:
+ return 0
+ except:
+ log.exception("Caught unhandled exception, dumping and cleaning up...")
+ if dump:
+ self._save_dump()
+ print >>sys.stderr,\
+ "Ceibal-Chess is done crashing... "\
+ "Game code was: %s.\nHave a nice day :) " % self.game_code
+ return -1
+ finally:
+ self._cleanup()
+
+ def set_close_callback(self, close_callback):
+ self.close_callback = close_callback
+
+ def stop(self):
+ self.controller.close()
+ self.done = True
+
+ def _init_dump(self):
+ self.game_code = str(int(time.time()))
+ home = os.environ.get("HOME")
+ if home:
+ self.dump_path = os.path.join(home, ".cchess")
+ else:
+ self.dump_path = ".cchess"
+ if not os.path.isdir(self.dump_path):
+ os.mkdir(self.dump_path)
+
+ def _save_dump(self):
+ try:
+ trace_file = open(os.path.join(self.dump_path, self.game_code + ".trace"), "w")
+ try:
+ traceback.print_exc(file=trace_file)
+ finally:
+ trace_file.close()
+
+ #Dump screen to image file
+ if self.screen:
+ pygame.image.save(self.screen, os.path.join(self.dump_path, self.game_code + ".png"))
+ except:
+ pass
+
+ def _run(self, scr_w, scr_h):
+ pygame.init()
+
+ #Messages
+ game_messages = {}
+ game_messages["checkmate"] = Message(_("Checkmate!"), 10, 40, (255, 0, 0))
+ game_messages["check"] = Message(_("Check"), 10, 40, (128, 0, 0))
+ game_messages["none"] = Message("", 10, 40)
+
+ #XO: 1200x900
+ if len(sys.argv) == 3:
+ scr_w = int(sys.argv[1])
+ scr_h = int(sys.argv[2])
+
+ size = width, height = scr_h - 70, scr_h - 70
+
+ #Screen config
+ if not self.gtk_embedded:
+ self.screen = pygame.display.set_mode((scr_w, scr_h))
+ else:
+ self.screen = pygame.display.get_surface()
+ pygame.display.set_caption("Ceibal-Chess")
+
+ surface = pygame.Surface(size)
+ bg_img = image_manager.get_image("bg.png")
+ bg_img = pygame.transform.scale(bg_img, (scr_w, scr_h))
+ log.info("Starting width=%s height=%s", scr_w, scr_h)
+
+ accum_surface = pygame.Surface((scr_w, scr_h), pygame.SRCALPHA)
+
+ #Center chessboard in screen
+ screen_rect = self.screen.get_rect()
+ sface_rect = surface.get_rect()
+ delta_x = (screen_rect.w - sface_rect.w) / 2
+ delta_y = (screen_rect.h - sface_rect.h) / 2
+
+ clock = pygame.time.Clock()
+
+ board = Board(width, height)
+
+ menu_opts = [_("New CPU Game"), _("Player vs. Player"), \
+ _("Credits"), _("Quit Ceibal-Chess")]
+ menu = Menu(scr_h, scr_h, menu_opts)
+ menu.visible = True
+
+ #LOG related:
+ #Unique identifier for this game (used for logs)
+
+ #Controller
+ self.controller = BoardController(board, MODE_P_VS_P)
+ self.controller.init_board()
+
+ #FPS
+ #messenger.messages["FPS"] = Message("FPS: (calculating)", 10, 10)
+ fps = 0
+ last_time = time.time()
+
+ #Create UI Elements:
+ turn_display = StatePanel(scr_w - scr_w/6.5, scr_h/40, 120, 120)
+ board_renderer = BoardRenderer(width, height)
+
+ clock = pygame.time.Clock()
+ #Post an ACTIVEEVENT to render the first time
+ pygame.event.set_blocked(pygame.MOUSEMOTION)
+ pygame.event.post(pygame.event.Event(pygame.ACTIVEEVENT))
+
+ while not self.done:
+ fps += 1
+ new_time = time.time()
+ if new_time - last_time > 1:
+ #messenger.messages["FPS"] = Message("FPS: " + str(fps/5.0), 10, 10)
+ last_time = new_time
+ fps = 0
+
+ # no more than 30 FPS
+ clock.tick(20)
+
+
+ #Event handling
+ if self.gtk_embedded:
+ while gtk.events_pending():
+ gtk.main_iteration()
+
+ #event = pygame.event.wait()
+ for event in pygame.event.get():
+ #discard mousemotion event (too expensive)
+ #while event.type == pygame.MOUSEMOTION:
+ # event = pygame.event.wait()
+
+ if event.type == pygame.QUIT:
+ self.controller.close()
+ self.done = True
+ break
+
+ if event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_ESCAPE:
+ menu.toggle_visible()
+ if event.key == pygame.K_u and not menu.visible:
+ self.controller.undo_move()
+ #else:
+ # controller.close()
+ # sys.exit(0)
+
+ if event.type == pygame.MOUSEBUTTONDOWN:
+ x, y = pygame.mouse.get_pos()
+
+ if not menu.visible:
+ clicked_cell = board.pick(x-delta_x, y-delta_y)
+ if clicked_cell:
+ self.controller.on_cell_clicked(clicked_cell)
+ else:
+ option = menu.on_click(x-delta_x, y-delta_y)
+ if option:
+ if option == menu_opts[3]:
+ self.controller.close()
+ self.done = True
+ break
+ elif option in menu_opts[0:2]:
+ game_mode = MODE_P_VS_CPU
+ if option == menu_opts[1]:
+ game_mode = MODE_P_VS_P
+ board = Board(width, height)
+ self.controller.close("Started new game")
+ self.controller = BoardController(board, game_mode, self.debug)
+ self.controller.init_board()
+ menu.visible = False
+ turn_display.set_state("move_white")
+
+ # This dirty piece of code makes the refresh and checkmate check only happen when needed
+ # Need to fix this and how the _update function works.
+ #if (board.current_turn.name == "black") or \
+ # ((board.current_turn.name == "white") and \
+ # (event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEBUTTONUP)) or \
+ # (turn_display.loaded == False):
+
+
+ self._update(board_renderer, board, surface, accum_surface, menu, sface_rect, delta_x, delta_y, bg_img, turn_display)
+
+ # Update IA if on "player vs cpu" mode and menu is not visible:
+ if not menu.visible:
+ self.controller.update()
+
+ log.debug("Exiting...")
+ if not self.gtk_embedded:
+ pygame.quit()
+
+ if self.close_callback:
+ self.close_callback()
+
+
+ def _update(self, board_renderer, board, surface, accum_surface, menu, sface_rect, delta_x, delta_y, bg_img, turn_display):
+ if not menu.visible:
+ #print "Checking if king is checkmated:"
+ t_ini = time.time()
+ checkmated = board.king_is_checkmated(board.current_turn)
+ #print "Check if checkmate for %s took %.5f secs" % \
+ #(board.current_turn, time.time() - t_ini)
+
+ if checkmated:
+ #print "Checkmate for", board.current_turn
+ #messenger.messages["check"] = game_messages["checkmate"]
+ #self.controller.game_state = "checkmate"
+ turn_display.set_state("checkmate_" + board.current_turn.name)
+ self.controller.on_checkmate()
+
+ elif board.king_is_checked(board.current_turn):
+ #messenger.messages["check"] = game_messages["check"]
+ turn_display.set_state("check_" + board.current_turn.name)
+ else:
+ #messenger.messages["check"] = game_messages["none"]
+ turn_display.set_state("move_" + board.current_turn.name)
+
+ #time visual update:
+ t_ini = time.time()
+
+ #No need to clear the buffers if we are redrawing them from scratch
+ #self._clear(self.screen)
+ #self._clear(surface)
+
+ self.screen.blit(bg_img, bg_img.get_rect())
+ #accum_surface.blit(bg_img, bg_img.get_rect())
+ #surface.blit(bg_img, bg_img.get_rect())
+
+ board_renderer.render_background(board, surface)
+
+ if self.controller.selected_cell is not None:
+ board_renderer.render_moves_for_piece_in_cell(board, surface, self.controller.selected_cell)
+
+ board_renderer.render_foreground(board, surface)
+
+ menu.render(surface)
+
+ global alpha_blending
+
+ if not menu.visible:
+ if alpha_blending:
+ turn_display.render(accum_surface)
+
+ # Frame alpha blending
+ if alpha_blending:
+ debug_t = pygame.time.get_ticks()
+ fade_time = 350.0 #Time in ms the animation lasts
+ blit_accum_t = 0
+
+ last_blit = pygame.time.get_ticks()
+
+ while blit_accum_t < fade_time:
+ curr_t = pygame.time.get_ticks()
+ delta_t = curr_t - last_blit
+ blit_accum_t += delta_t
+
+ surface.set_alpha(int(blit_accum_t / float(fade_time) * 255))
+ #self.screen.blit(surface, sface_rect.move(delta_x, delta_y))
+ accum_surface.blit(surface, sface_rect.move(delta_x, delta_y))
+
+ #self.screen.blit(accum_surface, accum_surface.get_rect())
+ self.screen.blit(accum_surface, sface_rect)
+ pygame.display.flip()
+ last_blit = curr_t
+
+
+ #print "fade in lasted", pygame.time.get_ticks() - debug_t, "ms"
+ else:
+ self.screen.blit(surface, sface_rect.move(delta_x, delta_y))
+
+ if not menu.visible and not alpha_blending:
+ turn_display.render(self.screen)
+
+ pygame.display.flip()
+
+ messenger.render_messages(self.screen)
+ #log.debug("Visual WHITE refresh took %.5f secs", (time.time() - t_ini))
+ pygame.display.flip()
+
+ def _clear(self, surface):
+ surface.fill((0, 0, 0))
+
+ def _cleanup(self):
+ if self.controller:
+ self.controller.close()
+
+if __name__ == "__main__":
+ # i18n
+ gettext.bindtextdomain("messages", "./po/")
+ gettext.textdomain("messages")
+
+ # Check if we are running on a XO or not.
+ if os.access("/sys/power/olpc-pm", os.F_OK):
+ resolution = (1200, 900)
+ else:
+ resolution = (933, 700)
+ rc = CeibalChess().start(dump=True, gtk_embedded=False, *resolution)
+ sys.exit(rc)
diff --git a/menu.py b/menu.py
new file mode 100644
index 0000000..f0fe5ae
--- /dev/null
+++ b/menu.py
@@ -0,0 +1,125 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+import pygame
+import os
+import logging
+from resourcemanager import image_manager
+
+log = logging.getLogger()
+
+class Menu:
+ def __init__(self, screen_w, screen_h, options):
+ '''The Game menu for selecting the play mode or quitting.
+ options contains a list of options, such as:
+ ["New CPU Game", "New PvP Game", "Quit Ceibal-Chess"].
+ The option clicked by the user is returned by the on_click
+ method'''
+
+ self.scr_w = screen_w
+ self.scr_h = screen_h
+
+ self.bg_w = 2 * screen_w / 3
+ self.bg_h = 2 * screen_h / 3 + 20
+
+ self.font = None
+
+ self.visible = False
+
+ self.btn_back_img = None
+ self.menu_back_img = None
+
+ self.options = options
+
+ self.option_coords = []
+ for option in self.options:
+ self.option_coords.append((0,0))
+
+ def toggle_visible(self):
+ if self.visible:
+ self.visible = False
+ else:
+ self.visible = True
+
+ def render(self, surface):
+ if not self.visible:
+ return
+
+ if self.font is None:
+ self.font = pygame.font.Font(None, 30)
+
+ #Render bg:
+ bg_x = (self.scr_w - self.bg_w) / 2
+ bg_y = (self.scr_h - self.bg_h) / 2
+ if not self.menu_back_img:
+ self.load_menu_bg("menu_back.png")
+
+ surface.blit(self.menu_back_img, pygame.Rect(bg_x, bg_y, self.bg_w, self.bg_h))
+
+ #Render menu options:
+ entry_w = 2 * self.bg_w / 3
+ entry_x = bg_x + (self.bg_w - entry_w) / 2
+ entry_y = bg_y + (self.bg_h - (self.font.get_height()+ 40 + 20)*len(self.options)) / 2
+ #print "bg_h:", self.bg_h, "entries_height:", (self.font.get_height()+20)*len(self.options)
+
+ for i in range(0, len(self.options)):
+ option = self.options[i]
+ #text_sface = self.font.render(option, 1, (255, 255, 255))
+ text_sface = self.font.render(option, 1, (170, 88, 0))
+
+ entry_h = text_sface.get_height() + 20
+
+ #Button background:
+ if not self.btn_back_img:
+ self.btn_back_img = image_manager.get_image("btn_back.png")
+
+ entry_x = bg_x + (self.bg_w - self.btn_back_img.get_width()) / 2
+ surface.blit(self.btn_back_img, pygame.Rect(entry_x, entry_y, entry_w, entry_h))
+
+ #Button text:
+ x = entry_x + (self.btn_back_img.get_width() - text_sface.get_width())/2
+ surface.blit(text_sface, (x, entry_y+(self.btn_back_img.get_height()-text_sface.get_height())/2))
+
+ #self.option_coords[i] = (entry_x, entry_y, entry_x+entry_w, entry_y+entry_h)
+ self.option_coords[i] = (entry_x, entry_y, entry_x+self.btn_back_img.get_width(), \
+ entry_y+self.btn_back_img.get_height())
+
+ entry_y += entry_h + 40
+
+ def on_click(self, x, y):
+ if not self.visible:
+ raise Exception("Called on_click on the menu while it was hidden!")
+
+ for i in range(0, len(self.options)):
+ coords = self.option_coords[i]
+ if x > coords[0] and x < coords[2] and y > coords[1] and y < coords[3]:
+ log.debug("Selected %s", self.options[i])
+ return self.options[i]
+ return None
+
+ def load_menu_bg(self, filename):
+ '''Load an image file from the data directory and store it in dest'''
+ bg1 = image_manager.get_image(filename)
+ bg2 = image_manager.get_image("menu_back2.png")
+
+ bg1 = pygame.transform.scale(bg1, (self.bg_w, self.bg_h))
+ bg2 = pygame.transform.scale(bg2, (int(self.bg_w/1.1), int(self.bg_h/1.1)))
+ self.menu_back_img = bg1
+ tx = (bg1.get_width()-bg2.get_width())/2
+ ty = (bg1.get_height()-bg2.get_height())/2
+ self.menu_back_img.blit(bg2, bg2.get_rect().move(tx, ty))
diff --git a/messenger.py b/messenger.py
new file mode 100644
index 0000000..168cea0
--- /dev/null
+++ b/messenger.py
@@ -0,0 +1,68 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import pygame
+
+#class DebugMessenger:
+# def __init__(self):
+# print "creating new debug messenger"
+# self.messages = []
+# self.y_spacing = 25
+# self.surface_h = 200
+#
+# def render_messages(self, surface):
+# self.surface_h = surface.get_height()
+# y = 10
+# for message in self.messages:
+# font = pygame.font.Font(None, 25)
+# text_color = font.render(message, 1, (90, 255, 90))
+# surface.blit(text_color, (10, y))
+# y += self.y_spacing
+#
+# def add_message(self, message):
+# '''Add a message to this messenger. Always use this method to
+# add messages, since the internal structure of this class may
+# be changed in the future.'''
+# self.messages.append(message)
+# while len(self.messages)*self.y_spacing > self.surface_h/2:
+# print "pop"
+# self.messages.pop(0)
+
+class Message():
+ def __init__(self, text, x = 0, y = 0, color = (0, 255, 0)):
+ self.x = x
+ self.y = y
+ self.text = text
+ self.color = color
+
+class Messenger():
+ def __init__(self):
+ #print "creating new messenger"
+ self.messages = {}
+ self.font = None
+
+ def render_messages(self, surface):
+ if self.font is None:
+ self.font = pygame.font.Font(None, 25)
+ for key,message in self.messages.iteritems():
+ text_sface = self.font.render(message.text, 1, message.color)
+ surface.blit(text_sface, (message.x, message.y))
+
+messenger = Messenger()
+
diff --git a/piece.py b/piece.py
new file mode 100644
index 0000000..2e5ec4a
--- /dev/null
+++ b/piece.py
@@ -0,0 +1,485 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+from board import LEFT, RIGHT
+from errors import UndoError
+
+def _coord_to_code(c):
+ return '%s%d' % (chr(0x61+c[0]), 8-c[1])
+
+# Move classes
+class BaseMove(object):
+ def __init__(self, fro, to):
+ self.fro = fro
+ self.to = to
+ self.peformed = False
+ self.acting_piece = None
+
+ self.type = self.__class__.__name__
+
+ def __eq__(self, other):
+ '''Compare instances. Two moves are equal if they take a piece from the
+ cell to the same cell'''
+ return isinstance(other, BaseMove) and \
+ self.fro == other.fro and \
+ self.to == other.to
+
+ def __ne__(self, other):
+ '''Compare instances. Two moves are not equal if they are not equal.'''
+ return not (self == other)
+
+ def perform(self, board):
+ self.performed = True
+ piece = board[self.fro].piece
+ piece.moved(self, board)
+
+ def undo(self, board):
+ if not self.performed:
+ raise UndoError("Move never performed: from (%d,%d) to (%d,%d)" % \
+ (self.fro + self.to))
+ piece = board[self.to].piece
+ piece.moved_back(self, board)
+ self.performed = False
+
+ def causes_check(self, board, owner):
+ self.perform(board)
+ checked = board.king_is_checked(owner)
+ self.undo(board)
+ return checked
+
+ def __repr__(self):
+ return '%s((%s, %s), (%s, %s))' % (
+ (self.__class__.__name__,) +
+ self.fro + self.to
+ )
+
+class Move(BaseMove):
+ '''Represents a regular piece movement in chess.'''
+ def __init__(self, fro, to):
+ super(Move, self).__init__(fro, to)
+ self.attacking = False
+
+ def __eq__(self, other):
+ '''Compare instances. Two moves are equal if they take a piece from the
+ cell to the same cell'''
+ return isinstance(other, Move) and \
+ self.fro == other.fro and \
+ self.to == other.to
+
+ def perform(self, board):
+ '''Perform a move on a board.'''
+ super(Move, self).perform(board)
+ self.src_piece = board[self.fro].piece
+ self.dst_piece = board[self.to].piece
+
+ board[self.to].piece = self.src_piece
+ board[self.fro].piece = None
+
+ def undo(self, board):
+ '''Undo the effects of a move on a board.'''
+ super(Move, self).undo(board)
+
+ board[self.fro].piece = self.src_piece
+ board[self.to].piece = self.dst_piece
+
+ def __str__(self):
+ if self.acting_piece:
+ piece_code = self.acting_piece.CODE
+ else:
+ piece_code = ''
+ #return '%s%s%s' % (piece_code, self.attacking and 'x' or '', _coord_to_code(self.to))
+ return "%s%s" % (_coord_to_code(self.fro), _coord_to_code(self.to))
+
+class EnPassant(BaseMove):
+ def __init__(self, fro, dir, owner):
+ assert dir in (LEFT, RIGHT)
+ super(EnPassant, self).__init__(
+ fro,
+ (fro[0] + dir, fro[1] + owner.pawn_dir))
+ self.attacking = True
+
+ def perform(self, board):
+ super(EnPassant, self).perform(board)
+ # destination can not have a piece since the captured pawn has just movd
+ self.src_piece = board[self.fro].piece
+ self.captured = board[self.to[0], self.fro[1]].piece
+
+ board[self.fro].piece = None
+ board[self.to].piece = self.src_piece
+ board[self.to[0], self.fro[1]].piece = None
+
+ def undo(self, board):
+ super(EnPassant, self).undo(board)
+
+ board[self.fro].piece = self.src_piece
+ board[self.to].piece = None
+ board[self.to[0], self.fro[1]].piece = self.captured
+
+ def __str__(self):
+ #return '%s(ep)' % _coord_to_code(self.to)
+ return '%s%s' % (_coord_to_code(self.fro), \
+ _coord_to_code(self.to))
+
+class Castling(BaseMove):
+ QUEENSIDE = 'left'
+ KINGSIDE = 'right'
+ '''Represents a Castling move in chess.'''
+ def __init__(self, castling_type, castling_owner):
+ '''Create a new instance of a Castling Move. Valid castling_types
+ are "left" and "right"'''
+ if not castling_type in [Castling.QUEENSIDE, Castling.KINGSIDE]:
+ raise Exception("Invalid Castling type: " + castling_type)
+ super(Castling, self).__init__(
+ (4, castling_owner.rank),
+ (castling_type == Castling.KINGSIDE and 6 or 2, castling_owner.rank))
+ self.castling_type = castling_type
+ self.castling_owner = castling_owner
+
+ def __eq__(self, other):
+ '''Compare instances. Two castling instances are equal if they are the
+ same type'''
+ return isinstance(other, Castling) and \
+ self.castling_type == other.castling_type
+
+ def perform(self, board):
+ '''Perform a Castling move on a board.'''
+ super(Castling, self).perform(board)
+ j = self.castling_owner.rank
+ self.castling_owner.castling_allowed = False
+
+ if self.castling_type == Castling.QUEENSIDE:
+ board[2,j].piece = board[4,j].piece
+ board[4,j].piece = None
+ board[3,j].piece = board[0,j].piece
+ board[0,j].piece = None
+ elif self.castling_type == Castling.KINGSIDE:
+ board[6,j].piece = board[4,j].piece
+ board[4,j].piece = None
+ board[5,j].piece = board[7,j].piece
+ board[7,j].piece = None
+ else:
+ raise MoveError("Invalid Castling type. Valid types are '%s' and '%s'" %
+ (Castling.QUEENSIDE, Castling.KINGSIDE))
+
+ def undo(self, board):
+ '''Undo a Castling move.'''
+ super(Castling, self).undo(board)
+ j = board.current_turn.rank
+ self.castling_owner.castling_allowed = True
+
+ if self.castling_type == Castling.QUEENSIDE:
+ board[4, j].piece = board[2, j].piece
+ board[2, j].piece = None
+ board[0, j].piece = board[3, j].piece
+ board[3, j].piece = None
+ elif self.castling_type == Castling.KINGSIDE:
+ board[4, j].piece = board[6, j].piece
+ board[6, j].piece = None
+ board[7, j].piece = board[5, j].piece
+ board[5, j].piece = None
+ else:
+ raise MoveError("Invalid Castling type. Valid types are '%s' and '%s'" %
+ (Castling.QUEENSIDE, Castling.KINGSIDE))
+
+ def __str__(self):
+ #if self.acting_piece:
+ # piece_code = self.acting_piece.CODE
+ #else:
+ # piece_code = ''
+ if self.castling_type == Castling.QUEENSIDE:
+ return '0-0-0'# % piece_code
+ elif self.castling_type == Castling.KINGSIDE:
+ return '0-0'# % piece_code
+
+class Crowning(BaseMove):
+ '''Represents a Pawn Crowning move in chess.'''
+ def __init__(self, fro, to, piece):
+ super(Crowning, self).__init__(fro, to)
+ self.piece = piece
+
+ def __eq__(self, other):
+ '''Compare instances. Two Crowning moves are equal if they move from
+ the same cell to the same cell and crown into the same piece'''
+ return isinstance(other, Crowning) and \
+ self.fro == other.fro and self.to == other.to and \
+ self.piece == other.piece
+
+ def perform(self, board):
+ '''Perform this move on a board.'''
+ super(Crowning, self).perform(board)
+ self.src_piece = board[self.fro].piece
+ self.dst_piece = board[self.to].piece
+
+ board[self.to].piece = self.piece
+ board[self.fro].piece = None
+
+ def undo(self, board):
+ '''Undo the effects of this move on a board.'''
+ super(Crowning, self).undo(board)
+ board[self.fro].piece = self.src_piece
+ board[self.to].piece = self.dst_piece
+
+ def __str__(self):
+ #if self.acting_piece:
+ # piece_code = self.acting_piece.CODE
+ #else:
+ # piece_code = ''
+ return '%s%s%s' % (_coord_to_code(self.fro),\
+ _coord_to_code(self.to), \
+ self.piece.CODE)
+ #return '%s%s(%s)' % (
+ # piece_code,
+ # _coord_to_code(self.to),
+ # self.piece.CODE)
+
+class BasePiece(object):
+ '''Base class for pieces.'''
+ def __init__(self, owner):
+ '''Create a new instance of Piece.'''
+ self.owner = owner
+ self.move_cache = []
+ self.attack_cache = []
+ self.type = self.__class__.__name__.lower()
+ self.move_count = 0
+
+ def __eq__(self, other):
+ return isinstance(other, BasePiece) and self.type == other.type and self.owner == other.owner
+
+ def is_turn(self, lastowner):
+ if self.owner == lastowner:
+ return True
+ else:
+ return False
+
+ def moved(self, move, board):
+ self.move_count += 1
+ self.r, self.c = move.to
+
+ def moved_back(self, move, board):
+ self.move_count -= 1
+ self.r, self.c = move.fro
+
+ def get_move(self, fro, to, board, **options):
+ # FIXME filter move by selected options
+ for move in self.get_moves(fro, board, **options):
+ if move.to == to:
+ return move
+ return None
+
+ def get_moves(self, fro, board, attack_only = False, filter_check=False,
+ **options):
+ moves = self._get_moves(fro, board, attack_only = attack_only, **options)
+ for move in moves:
+ move.acting_piece = self
+ if filter_check:
+ return [x for x in moves if not x.causes_check(board, self.owner)]
+ else:
+ return moves
+
+ def has_moves(self, fro, board, filter_check=True):
+ return len(self.get_moves(fro, board, attack_only=False, filter_check=filter_check)) > 0
+
+DIR_N = ( 0,-1) # delta column, delta row
+DIR_S = ( 0, 1)
+DIR_W = (-1, 0)
+DIR_E = ( 1, 0)
+
+DIR_NE = ( 1, -1)
+DIR_NW = (-1, -1)
+DIR_SE = ( 1, 1)
+DIR_SW = (-1, 1)
+
+DIRS_DIAGONALS = [DIR_NE, DIR_NW, DIR_SE, DIR_SW]
+DIRS_HORIZONTALS = [DIR_E, DIR_W, DIR_S, DIR_N]
+DIRS_ALL = DIRS_HORIZONTALS + DIRS_DIAGONALS
+
+def _cascades(dirs, fro, board, owner):
+ '''Cascade calculates al moves from a given coordinate in all given directions.
+ '''
+ dests = []
+ for dc,dr in dirs:
+ to = (fro[0] + dc, fro[1] + dr)
+ p = None
+ while 0 <= to[0] < 8 and 0 <= to[1] < 8 and not p:
+ p = board[to].piece
+ if not p or p.owner != owner:
+ dests.append(Move(fro, to))
+ to = (to[0] + dc, to[1] + dr)
+ return dests
+
+class Knight(BasePiece):
+ '''Representation of the Knight piece.'''
+
+ CODE = 'N'
+
+ def __init__(self, owner):
+ '''Create a new instance of Knight. owner may be "white" or "black".'''
+ super(Knight, self).__init__(owner)
+
+ def _get_moves(self, (col, row), board, attack_only = False, **options):
+ dests = []
+ for x in (1,-1):
+ for y in (2, -2):
+ for c, r in ((col+x,row+y),(col+y,row+x)):
+ if 0 <= c < 8 and 0 <= r < 8:
+ p = board[c,r].piece
+ if not p or p.owner != self.owner:
+ dests.append(Move((col, row), (c, r)))
+ return dests
+
+class Rook(BasePiece):
+ '''Representation of the Rook piece.'''
+
+ CODE = 'R'
+
+ def __init__(self, owner):
+ '''Create a new instance of Rook. owner may be "white" or "black".'''
+ super(Rook, self).__init__(owner)
+
+ def _get_moves(self, fro, board, attack_only = False, **options):
+ return _cascades(DIRS_HORIZONTALS, fro, board, self.owner)
+
+class Bishop(BasePiece):
+ '''Representation of the Bishop piece.'''
+
+ CODE = 'B'
+
+ def __init__(self, owner):
+ '''Create a new instance of Bishop. owner may be "white" or "black".'''
+ super(Bishop, self).__init__(owner)
+
+ def _get_moves(self, fro, board, attack_only = False, **options):
+ return _cascades(DIRS_DIAGONALS, fro, board, self.owner)
+
+class Queen(BasePiece):
+ '''Representation of the Queen piece'''
+
+ CODE = 'Q'
+
+ def __init__(self, owner):
+ '''Create a new instance of Queen. owner may be "white" or "black".'''
+ super(Queen, self).__init__(owner)
+
+ def _get_moves(self, fro, board, attack_only = False, **options):
+ return _cascades(DIRS_ALL, fro, board, self.owner)
+
+class King(BasePiece):
+ '''Representation of the King piece'''
+
+ CODE = 'K'
+
+ def __init__(self, owner):
+ '''Create a new instance of King. owner may be "white" or "black".'''
+ super(King, self).__init__(owner)
+
+ def _get_moves(self, (col, row), board, attack_only = False, **options):
+ dests = []
+ for dc in [-1,0,1]:
+ for dr in [-1,0,1]:
+ c = col + dc
+ r = row + dr
+ if 0 <= c < 8 and 0 <= r < 8:
+ p = board[c,r].piece
+ if not p or (p.owner != self.owner):
+ dests.append(Move((col,row),(c,r)))
+
+ if not attack_only:
+ threats = [x.to for x in board.get_all_attack_moves(self.owner.enemy)]
+ #Castling
+ if not self.move_count and \
+ not board.king_is_checked(self.owner):
+ right_rook = board[7,row].piece
+ left_rook = board[0,row].piece
+ if right_rook and not right_rook.move_count and \
+ not board[col+1,row].piece and \
+ not board[col+2,row].piece and \
+ (col+2, row) not in threats and \
+ (col+1, row) not in threats:
+ dests.append(Castling(Castling.KINGSIDE, self.owner))
+
+ if left_rook and not left_rook.move_count and \
+ not board[col-1,row].piece and \
+ not board[col-2,row].piece and \
+ not board[col-3,row].piece and \
+ (col-2, row) not in threats and \
+ (col-1, row) not in threats:
+ dests.append(Castling(Castling.QUEENSIDE, self.owner))
+ dests = filter(lambda x: x.to not in threats, dests)
+ return dests
+
+class Pawn(BasePiece):
+ '''Representation of the Pawn piece.'''
+
+ CODE = 'P'
+
+ def __init__(self, owner):
+ super(Pawn, self).__init__(owner)
+ '''Create a new instance of Pawn. owner may be "white" or "black".'''
+ self.en_passant = 0
+
+ def moved(self, move, board):
+ if move.to[1] == self.owner.rank + self.owner.pawn_dir * 3:
+ self.en_passant = board.turns
+
+ def _get_moves(self, (col, row), board, attack_only = False, **options):
+ dests = []
+ dr = self.owner.pawn_dir
+ rank = self.owner.rank
+
+ #Bound checking:
+ if 0 <= row + dr < 8:
+ #Normal move or Crowning?
+ if not attack_only:
+ if row + dr != 0 and row + dr != 7:
+ if not board[col, row + dr].piece:
+ dests.append(Move((col, row), (col, row + dr)))
+ else:
+ if not board[col, row + dr].piece:
+ # create the piece from the given class name :ugh:
+ type = options.get('type', Queen.CODE)
+ dests.append(Crowning((col, row), (col, row + dr),
+ PIECES_BY_CODE[type](self.owner)))
+
+ #Double step:
+ if row == rank + dr and \
+ not board[col, row + 2*dr].piece and \
+ not board[col, row + 1*dr].piece:
+ dests.append(Move((col, row), (col, row + 2 * dr)))
+
+ #Attack moves:
+ for dir in LEFT, RIGHT:
+ if 0 <= col + dir < 8:
+
+ # normal attack
+ p = board[col+dir, row+dr].piece
+ if p and p.owner != self.owner:
+ if row + dr != 0 and row + dr != 7:
+ dests.append(Move((col, row), (col + dir, row + dr)))
+ else:
+ # Crowning attack
+ dests.append(Crowning((col, row), (col + dir, row + dr), Queen(self.owner)))
+
+ # en passant
+ elif row == (self.owner.enemy.rank + self.owner.enemy.pawn_dir * 3):
+ p = board[col + dir, row].piece
+ if p and p.owner != self.owner and p.type == self.type and p.en_passant == board.turns -1:
+ dests.append(EnPassant((col, row), dir, self.owner))
+ return dests
+
+PIECES = (Rook, Knight, Queen, King, Pawn, Bishop)
+PIECES_BY_CODE = dict([(x.CODE, x) for x in PIECES])
diff --git a/po/ceibalchess.pot b/po/ceibalchess.pot
new file mode 100644
index 0000000..ff9551e
--- /dev/null
+++ b/po/ceibalchess.pot
@@ -0,0 +1,53 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-12-08 16:46-0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: main.py:141
+msgid "Checkmate!"
+msgstr ""
+
+#: main.py:142
+msgid "Check"
+msgstr ""
+
+#: main.py:176
+msgid "New CPU Game"
+msgstr ""
+
+#: main.py:176
+msgid "Player vs. Player"
+msgstr ""
+
+#: main.py:177
+msgid "Credits"
+msgstr ""
+
+#: main.py:177
+msgid "Quit Ceibal-Chess"
+msgstr ""
+
+#: ui.py:63
+msgid "Current Turn:"
+msgstr ""
+
+#: ui.py:64
+msgid "Check:"
+msgstr ""
+
+#: ui.py:65
+msgid "Checkmate:"
+msgstr ""
diff --git a/po/en/LC_MESSAGES/messages.mo b/po/en/LC_MESSAGES/messages.mo
new file mode 100644
index 0000000..81443b1
--- /dev/null
+++ b/po/en/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/po/en_US.po b/po/en_US.po
new file mode 100644
index 0000000..7f556df
--- /dev/null
+++ b/po/en_US.po
@@ -0,0 +1,54 @@
+# English translations for Ceibal-Chess package.
+# Copyright (C) 2010 THE Ceibal-Chess' COPYRIGHT HOLDER
+# This file is distributed under the same license as the Ceibal-Chess
+# package.
+# Alejandro Segovia <asegovi@gmail.com>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-12-08 16:46-0200\n"
+"PO-Revision-Date: 2010-12-08 16:47-0200\n"
+"Last-Translator: Alejandro Segovia <asegovi@gmail.com>\n"
+"Language-Team: English\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: main.py:141
+msgid "Checkmate!"
+msgstr "Checkmate!"
+
+#: main.py:142
+msgid "Check"
+msgstr "Check"
+
+#: main.py:176
+msgid "New CPU Game"
+msgstr "New CPU Game"
+
+#: main.py:176
+msgid "Player vs. Player"
+msgstr "Player vs. Player"
+
+#: main.py:177
+msgid "Credits"
+msgstr "Credits"
+
+#: main.py:177
+msgid "Quit Ceibal-Chess"
+msgstr "Quit Ceibal-Chess"
+
+#: ui.py:63
+msgid "Current Turn:"
+msgstr "Current Turn:"
+
+#: ui.py:64
+msgid "Check:"
+msgstr "Check:"
+
+#: ui.py:65
+msgid "Checkmate:"
+msgstr "Checkmate:"
diff --git a/po/es/LC_MESSAGES/messages.mo b/po/es/LC_MESSAGES/messages.mo
new file mode 100644
index 0000000..ec60312
--- /dev/null
+++ b/po/es/LC_MESSAGES/messages.mo
Binary files differ
diff --git a/po/es_UY.po b/po/es_UY.po
new file mode 100644
index 0000000..b99c9dc
--- /dev/null
+++ b/po/es_UY.po
@@ -0,0 +1,55 @@
+# Spanish translations for Ceibal-Chess package
+# Traducciones al español para el paquete Ceibal-Chess.
+# Copyright (C) 2010 THE CEIBAL-CHESS'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the Ceibal-Chess
+# package.
+# Alejandro Segovia <asegovi@gmail.com>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: 1.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-12-08 16:46-0200\n"
+"PO-Revision-Date: 2010-12-08 16:50-0200\n"
+"Last-Translator: Alejandro Segovia <asegovi@gmail.com>\n"
+"Language-Team: Spanish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: main.py:141
+msgid "Checkmate!"
+msgstr "Mate!"
+
+#: main.py:142
+msgid "Check"
+msgstr "Jaque"
+
+#: main.py:176
+msgid "New CPU Game"
+msgstr "Nuevo Juego UCP"
+
+#: main.py:176
+msgid "Player vs. Player"
+msgstr "Jugador vs. Jugador"
+
+#: main.py:177
+msgid "Credits"
+msgstr "Creditos"
+
+#: main.py:177
+msgid "Quit Ceibal-Chess"
+msgstr "Salir de Ceibal-Chess"
+
+#: ui.py:63
+msgid "Current Turn:"
+msgstr "Turno Actual:"
+
+#: ui.py:64
+msgid "Check:"
+msgstr "Jaque:"
+
+#: ui.py:65
+msgid "Checkmate:"
+msgstr "Mate:"
diff --git a/pychess-UML.jpg b/pychess-UML.jpg
new file mode 100644
index 0000000..f009fd3
--- /dev/null
+++ b/pychess-UML.jpg
Binary files differ
diff --git a/resourcemanager.py b/resourcemanager.py
new file mode 100644
index 0000000..e46ab7c
--- /dev/null
+++ b/resourcemanager.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+import pygame
+import os
+import logging
+
+log = logging.getLogger()
+
+class ImageManager:
+ '''Load and manage images, making them available through the applications.
+ All image access should be performed through the singleton instance of
+ this class, under the name imagem_anager. Usage of pygame.image.load
+ should be avoided in favor of image_manager.get_image(<img_file_name>).'''
+
+ def __init__(self):
+ '''Create a new instance of ImageManager. Only one instance of
+ this class should exist at any time, which is accessible as
+ the variable "image_manager"'''
+ self.images = { }
+
+ def get_image(self, imgname):
+ '''Look for an image in the internal image dictionary. If the
+ image is available, return it, otherwise load it from disk and keep
+ an keep a reference to it.'''
+ try:
+ return self.images[imgname]
+ except KeyError, err:
+ log.debug("Image '%s' not found. Loading...", imgname)
+
+ try:
+ path = os.environ["SUGAR_BUNDLE_PATH"]
+ if not "Ajedrez.activity" in path:
+ print "Running ceibal-chess from Terminal or some other place"
+ path = ""
+ except:
+ path = ""
+
+ self.images[imgname] = pygame.image.load(os.path.join(path, "data_bw", imgname))
+ log.debug("Image '%s': %dx%d" % (imgname, \
+ self.images[imgname].get_width(), \
+ self.images[imgname].get_height()))
+ return self.images[imgname]
+
+#ImageManager singleton
+image_manager = ImageManager()
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..746727c
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2006, Red Hat, Inc.
+#
+# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from sugar.activity import bundlebuilder
+
+if __name__ == "__main__":
+ bundlebuilder.start('ceibalchess')
diff --git a/sugargame/__init__.py b/sugargame/__init__.py
new file mode 100644
index 0000000..439eb0c
--- /dev/null
+++ b/sugargame/__init__.py
@@ -0,0 +1 @@
+__version__ = '1.1'
diff --git a/sugargame/canvas.py b/sugargame/canvas.py
new file mode 100644
index 0000000..980cb73
--- /dev/null
+++ b/sugargame/canvas.py
@@ -0,0 +1,62 @@
+import os
+import gtk
+import gobject
+import pygame
+import event
+
+CANVAS = None
+
+class PygameCanvas(gtk.EventBox):
+
+ """
+ mainwindow is the activity intself.
+ """
+ def __init__(self, mainwindow, pointer_hint = True):
+ gtk.EventBox.__init__(self)
+
+ global CANVAS
+ assert CANVAS == None, "Only one PygameCanvas can be created, ever."
+ CANVAS = self
+
+ # Initialize Events translator before widget gets "realized".
+ self.translator = event.Translator(mainwindow, self)
+
+ self._mainwindow = mainwindow
+
+ self.set_flags(gtk.CAN_FOCUS)
+
+ self._socket = gtk.Socket()
+ self.add(self._socket)
+ self.show_all()
+
+ def run_pygame(self, main_fn):
+ # Run the main loop after a short delay. The reason for the delay is that the
+ # Sugar activity is not properly created until after its constructor returns.
+ # If the Pygame main loop is called from the activity constructor, the
+ # constructor never returns and the activity freezes.
+ gobject.idle_add(self._run_pygame_cb, main_fn)
+
+ def _run_pygame_cb(self, main_fn):
+ assert pygame.display.get_surface() is None, "PygameCanvas.run_pygame can only be called once."
+
+ # Preinitialize Pygame with the X window ID.
+ assert pygame.display.get_init() == False, "Pygame must not be initialized before calling PygameCanvas.run_pygame."
+ os.environ['SDL_WINDOWID'] = str(self._socket.get_id())
+ pygame.init()
+
+ # Restore the default cursor.
+ self._socket.window.set_cursor(None)
+
+ # Initialize the Pygame window.
+ r = self.get_allocation()
+ pygame.display.set_mode((r.width, r.height), pygame.RESIZABLE)
+
+ # Hook certain Pygame functions with GTK equivalents.
+ self.translator.hook_pygame()
+
+ # Run the Pygame main loop.
+ main_fn()
+ return False
+
+ def get_pygame_widget(self):
+ return self._socket
diff --git a/sugargame/event.py b/sugargame/event.py
new file mode 100644
index 0000000..4cc3be8
--- /dev/null
+++ b/sugargame/event.py
@@ -0,0 +1,243 @@
+import gtk
+import gobject
+import pygame
+import pygame.event
+import logging
+
+class _MockEvent(object):
+ def __init__(self, keyval):
+ self.keyval = keyval
+
+class Translator(object):
+ key_trans = {
+ 'Alt_L': pygame.K_LALT,
+ 'Alt_R': pygame.K_RALT,
+ 'Control_L': pygame.K_LCTRL,
+ 'Control_R': pygame.K_RCTRL,
+ 'Shift_L': pygame.K_LSHIFT,
+ 'Shift_R': pygame.K_RSHIFT,
+ 'Super_L': pygame.K_LSUPER,
+ 'Super_R': pygame.K_RSUPER,
+ 'KP_Page_Up' : pygame.K_KP9,
+ 'KP_Page_Down' : pygame.K_KP3,
+ 'KP_End' : pygame.K_KP1,
+ 'KP_Home' : pygame.K_KP7,
+ 'KP_Up' : pygame.K_KP8,
+ 'KP_Down' : pygame.K_KP2,
+ 'KP_Left' : pygame.K_KP4,
+ 'KP_Right' : pygame.K_KP6,
+
+ }
+
+ mod_map = {
+ pygame.K_LALT: pygame.KMOD_LALT,
+ pygame.K_RALT: pygame.KMOD_RALT,
+ pygame.K_LCTRL: pygame.KMOD_LCTRL,
+ pygame.K_RCTRL: pygame.KMOD_RCTRL,
+ pygame.K_LSHIFT: pygame.KMOD_LSHIFT,
+ pygame.K_RSHIFT: pygame.KMOD_RSHIFT,
+ }
+
+ def __init__(self, mainwindow, inner_evb):
+ """Initialise the Translator with the windows to which to listen"""
+ self._mainwindow = mainwindow
+ self._inner_evb = inner_evb
+
+ # Enable events
+ # (add instead of set here because the main window is already realized)
+ self._mainwindow.add_events(
+ gtk.gdk.KEY_PRESS_MASK | \
+ gtk.gdk.KEY_RELEASE_MASK \
+ )
+
+ self._inner_evb.set_events(
+ gtk.gdk.POINTER_MOTION_MASK | \
+ gtk.gdk.POINTER_MOTION_HINT_MASK | \
+ gtk.gdk.BUTTON_MOTION_MASK | \
+ gtk.gdk.BUTTON_PRESS_MASK | \
+ gtk.gdk.BUTTON_RELEASE_MASK
+ )
+
+ self._mainwindow.set_flags(gtk.CAN_FOCUS)
+ self._inner_evb.set_flags(gtk.CAN_FOCUS)
+
+ # Callback functions to link the event systems
+ self._mainwindow.connect('unrealize', self._quit_cb)
+ self._inner_evb.connect('key_press_event', self._keydown_cb)
+ self._inner_evb.connect('key_release_event', self._keyup_cb)
+ self._inner_evb.connect('button_press_event', self._mousedown_cb)
+ self._inner_evb.connect('button_release_event', self._mouseup_cb)
+ self._inner_evb.connect('motion-notify-event', self._mousemove_cb)
+ self._inner_evb.connect('expose-event', self._expose_cb)
+ self._inner_evb.connect('configure-event', self._resize_cb)
+
+ # Internal data
+ self.__stopped = False
+ self.__keystate = [0] * 323
+ self.__button_state = [0,0,0]
+ self.__mouse_pos = (0,0)
+ self.__repeat = (None, None)
+ self.__held = set()
+ self.__held_time_left = {}
+ self.__held_last_time = {}
+ self.__tick_id = None
+
+ def hook_pygame(self):
+ pygame.key.get_pressed = self._get_pressed
+ pygame.key.set_repeat = self._set_repeat
+ pygame.mouse.get_pressed = self._get_mouse_pressed
+ pygame.mouse.get_pos = self._get_mouse_pos
+
+ def _expose_cb(self, event, widget):
+ if pygame.display.get_init():
+ pygame.event.post(pygame.event.Event(pygame.VIDEOEXPOSE))
+ return True
+
+ def _resize_cb(self, widget, event):
+ evt = pygame.event.Event(pygame.VIDEORESIZE,
+ size=(event.width,event.height), width=event.width, height=event.height)
+ pygame.event.post(evt)
+ return False # continue processing
+
+ def _quit_cb(self, data=None):
+ self.__stopped = True
+ pygame.event.post(pygame.event.Event(pygame.QUIT))
+
+ def _keydown_cb(self, widget, event):
+ key = event.keyval
+ if key in self.__held:
+ return True
+ else:
+ if self.__repeat[0] is not None:
+ self.__held_last_time[key] = pygame.time.get_ticks()
+ self.__held_time_left[key] = self.__repeat[0]
+ self.__held.add(key)
+
+ return self._keyevent(widget, event, pygame.KEYDOWN)
+
+ def _keyup_cb(self, widget, event):
+ key = event.keyval
+ if self.__repeat[0] is not None:
+ if key in self.__held:
+ # This is possibly false if set_repeat() is called with a key held
+ del self.__held_time_left[key]
+ del self.__held_last_time[key]
+ self.__held.discard(key)
+
+ return self._keyevent(widget, event, pygame.KEYUP)
+
+ def _keymods(self):
+ mod = 0
+ for key_val, mod_val in self.mod_map.iteritems():
+ mod |= self.__keystate[key_val] and mod_val
+ return mod
+
+ def _keyevent(self, widget, event, type):
+ key = gtk.gdk.keyval_name(event.keyval)
+ if key is None:
+ # No idea what this key is.
+ return False
+
+ keycode = None
+ if key in self.key_trans:
+ keycode = self.key_trans[key]
+ elif hasattr(pygame, 'K_'+key.upper()):
+ keycode = getattr(pygame, 'K_'+key.upper())
+ elif hasattr(pygame, 'K_'+key.lower()):
+ keycode = getattr(pygame, 'K_'+key.lower())
+ elif key == 'XF86Start':
+ # view source request, specially handled...
+ self._mainwindow.view_source()
+ else:
+ print 'Key %s unrecognized' % key
+
+ if keycode is not None:
+ if type == pygame.KEYDOWN:
+ mod = self._keymods()
+ self.__keystate[keycode] = type == pygame.KEYDOWN
+ if type == pygame.KEYUP:
+ mod = self._keymods()
+ ukey = unichr(gtk.gdk.keyval_to_unicode(event.keyval))
+ if ukey == '\000':
+ ukey = ''
+ evt = pygame.event.Event(type, key=keycode, unicode=ukey, mod=mod)
+ self._post(evt)
+
+ return True
+
+ def _get_pressed(self):
+ return self.__keystate
+
+ def _get_mouse_pressed(self):
+ return self.__button_state
+
+ def _mousedown_cb(self, widget, event):
+ self.__button_state[event.button-1] = 1
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONDOWN)
+
+ def _mouseup_cb(self, widget, event):
+ self.__button_state[event.button-1] = 0
+ return self._mouseevent(widget, event, pygame.MOUSEBUTTONUP)
+
+ def _mouseevent(self, widget, event, type):
+ evt = pygame.event.Event(type, button=event.button, pos=(event.x, event.y))
+ self._post(evt)
+ return True
+
+ def _mousemove_cb(self, widget, event):
+ # From http://www.learningpython.com/2006/07/25/writing-a-custom-widget-using-pygtk/
+ # if this is a hint, then let's get all the necessary
+ # information, if not it's all we need.
+ if event.is_hint:
+ x, y, state = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+ state = event.state
+
+ rel = (x - self.__mouse_pos[0], y - self.__mouse_pos[1])
+ self.__mouse_pos = (x, y)
+
+ self.__button_state = [
+ state & gtk.gdk.BUTTON1_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON2_MASK and 1 or 0,
+ state & gtk.gdk.BUTTON3_MASK and 1 or 0,
+ ]
+
+ evt = pygame.event.Event(pygame.MOUSEMOTION,
+ pos=self.__mouse_pos, rel=rel, buttons=self.__button_state)
+ self._post(evt)
+ return True
+
+ def _tick_cb(self):
+ cur_time = pygame.time.get_ticks()
+ for key in self.__held:
+ delta = cur_time - self.__held_last_time[key]
+ self.__held_last_time[key] = cur_time
+
+ self.__held_time_left[key] -= delta
+ if self.__held_time_left[key] <= 0:
+ self.__held_time_left[key] = self.__repeat[1]
+ self._keyevent(None, _MockEvent(key), pygame.KEYDOWN)
+
+ return True
+
+ def _set_repeat(self, delay=None, interval=None):
+ if delay is not None and self.__repeat[0] is None:
+ self.__tick_id = gobject.timeout_add(10, self._tick_cb)
+ elif delay is None and self.__repeat[0] is not None:
+ gobject.source_remove(self.__tick_id)
+ self.__repeat = (delay, interval)
+
+ def _get_mouse_pos(self):
+ return self.__mouse_pos
+
+ def _post(self, evt):
+ try:
+ pygame.event.post(evt)
+ except pygame.error, e:
+ if str(e) == 'Event queue full':
+ print "Event queue full!"
+ pass
+ else:
+ raise e
diff --git a/testcases/boardtests.py b/testcases/boardtests.py
new file mode 100644
index 0000000..c3377ea
--- /dev/null
+++ b/testcases/boardtests.py
@@ -0,0 +1,131 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import unittest
+
+from board import Board
+from piece import Move, Pawn, King, Queen
+from errors import MoveError
+
+class BoardTest(unittest.TestCase):
+ '''Perform unit testing on the new Board class.'''
+
+ def test_king_is_checked(self):
+ board = Board()
+ board[0, 0].piece = King(board.black)
+ board[1, 1].piece = Queen(board.white)
+ board[7, 7].piece = King(board.white)
+ self.assertTrue(board.king_is_checked(board.black))
+ self.assertFalse(board.king_is_checked(board.white))
+
+ def test_king_is_checkmated(self):
+ board = Board()
+ board[0, 0].piece = King(board.black)
+ board[1, 1].piece = Queen(board.white)
+ board[7, 7].piece = King(board.white)
+ self.assertFalse(board.king_is_checkmated(board.black))
+
+ board[2, 2].piece = Queen(board.white)
+ self.assertTrue(board.king_is_checkmated(board.black))
+
+ def test_board_creation(self):
+ '''Test creating a board.
+
+ '''
+
+ board = Board()
+
+ self.assertTrue(len(board.board) == 8)
+ self.assertTrue(len(board.board[c]) == 8 for c in range(0,8))
+
+ def test_move_piece_in_cell_to_dest(self):
+ '''Test moving a piece in a selected cell to a given destination.
+
+ This method evaluates performing moves on the board in the same way the
+ BoardController class does.
+
+ '''
+
+ #Setup board with a white pawn at start position (1,6)
+ board = Board()
+ pawn = Pawn(board.white)
+
+ board[0, 4].piece = King(board.black)
+ board[7, 4].piece = King(board.white)
+
+ col, row = 1, 6
+
+ board[col, row].piece = pawn
+
+ #Simulate BoardController:
+ selected_cell = board[col, row]
+ #board.on_cell_selected(selected_cell)
+
+ #Preconditions:
+ self.assertTrue(board.can_move_piece_in_cell_to(selected_cell,
+ (col, row-1)))
+ self.assertTrue(board.can_move_piece_in_cell_to(selected_cell,
+ (col, row-2)))
+
+ #Check whether cells are aware of the moves that take pieces to them
+ #self.assertTrue(len(board[col, row-1].moves) == 1)
+ #self.assertTrue(len(board[col, row-2].moves) == 1)
+
+ #Perform movement:
+ board.move_piece_in_cell_to(pawn.owner, (col, row), (col, row-1))
+
+ #Check whether cells have been clean
+ self.assertTrue(len(board[col, row-1].moves) == 0)
+ self.assertTrue(len(board[col, row-2].moves) == 0)
+
+ def test_move_wrong_piece_to_dest(self):
+ ''''Test requesting moving a wrong piece to a destination cell.
+
+ '''
+
+ #Setup board with a two white pawns:
+ board = Board()
+ pawn1 = Pawn(board.white)
+ pawn2 = Pawn(board.white)
+
+ board[0, 4].piece = King(board.black)
+ board[7, 4].piece = King(board.white)
+
+ col1, col2, row = 1, 3, 5
+
+ board[col1, row].piece = pawn1
+ board[col2, row].piece = pawn2
+
+ #Simulate BoardController:
+ selected_cell = board[col1, row]
+ #board.on_cell_selected(selected_cell)
+
+ #Preconditions:
+ self.assertTrue(board.can_move_piece_in_cell_to(selected_cell,
+ (col1, row-1)))
+
+ #Try moving pawn2 to the destination for pawn1:
+ self.assertFalse(board.can_move_piece_in_cell_to(board[col2,row],
+ (col1, row-1)))
+ self.assertRaises(MoveError, board.move_piece_in_cell_to,
+ pawn2.owner, (col2,row), (col1, row-1))
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/testcases/piecetests.py b/testcases/piecetests.py
new file mode 100644
index 0000000..ffa619f
--- /dev/null
+++ b/testcases/piecetests.py
@@ -0,0 +1,482 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+import unittest
+from board import Board, RIGHT, LEFT
+from piece import Move, EnPassant, Castling, Crowning, Pawn, Knight, Queen, Rook, King
+
+class PawnTest(unittest.TestCase):
+ '''Class for testing a pawn's moves'''
+
+ def test_basic_white_moves(self):
+ '''Test basic white pawn movement (step and double-step).'''
+ board = Board(10, 10)
+ pawn = Pawn(board.white)
+
+ i, j = 1, 6
+ board[i, j].piece = pawn
+
+ moves = pawn.get_moves((i, j), board)
+
+ self.assertEquals(len(moves), 2)
+ self.assertTrue(Move((i,j), (i,j-1)) in moves)
+ self.assertTrue(Move((i,j), (i,j-2)) in moves)
+
+ def test_en_passant_right(self):
+ '''Test en passant for black pawn.'''
+ board = Board(10, 10)
+ white_pawn = Pawn(board.white)
+ black_pawn = Pawn(board.black)
+
+ board[4, 4].piece = black_pawn
+ board[5, 6].piece = white_pawn
+
+ board.perform_move(Move((5,6), (5,4)))
+ moves = black_pawn.get_moves((4, 4), board)
+
+ self.assertEquals(2, len(moves))
+ self.assertTrue(Move((4,4),(4,5)) in moves)
+ self.assertTrue(EnPassant((4,4), RIGHT, board.black) in moves)
+
+ def test_en_passant_left(self):
+ '''Test en passant for black pawn.'''
+ board = Board(10, 10)
+ white_pawn = Pawn(board.white)
+ black_pawn = Pawn(board.black)
+
+ board[4, 4].piece = black_pawn
+ board[3, 6].piece = white_pawn
+
+ board.perform_move(Move((3,6), (3,4)))
+ moves = black_pawn.get_moves((4, 4), board)
+
+ self.assertEquals(2, len(moves))
+ self.assertTrue(Move((4,4),(4,5)) in moves)
+ self.assertTrue(EnPassant((4,4), LEFT, board.black) in moves)
+
+ def test_white_attack_move(self):
+ '''Test attack move for white pawn.'''
+ board = Board(10, 10)
+ white_pawn = Pawn(board.white)
+ black_pawn = Pawn(board.black)
+
+ i, j = 1, 6
+ board[i, j].piece = white_pawn
+ board[i-1, j-1].piece = black_pawn
+ board[i+1, j-1].piece = black_pawn
+
+ moves = white_pawn.get_moves((i, j), board)
+
+ self.assertEquals(4, len(moves))
+ self.assertTrue(Move((i,j),(i-1,j-1)) in moves)
+ self.assertTrue(Move((i,j),(i+1,j-1)) in moves)
+
+ def test_white_crowning(self):
+ '''Test white crowning.'''
+ board = Board(10, 10)
+ pawn = Pawn(board.white)
+
+ i, j = 1, 1
+ board[i, j].piece = pawn
+
+ moves = pawn.get_moves((i, j), board)
+
+ self.assertEquals(len(moves), 1)
+ self.assertTrue(moves[0].type == "Crowning")
+
+ def test_white_crowning_with_type(self):
+ '''Test white crowning.'''
+ board = Board(10, 10)
+ pawn = Pawn(board.white)
+
+ i, j = 1, 1
+ board[i, j].piece = pawn
+
+ moves = pawn.get_moves((i, j), board, type="R")
+
+ self.assertEquals(len(moves), 1)
+ self.assertTrue(moves[0].type == "Crowning")
+ self.assertTrue(moves[0].type == "Crowning")
+ self.assertTrue(isinstance(moves[0].piece, Rook))
+
+ def test_white_crowning_attack(self):
+ '''Test white crowning attack.'''
+ board = Board(10, 10)
+ pawn = Pawn(board.white)
+ rook = Rook(board.black)
+
+ i, j = 1, 1
+ board[i, j].piece = pawn
+ board[0, 0].piece = rook
+
+ moves = pawn.get_moves((i, j), board)
+
+ self.assertEquals(2, len(moves))
+ self.assertTrue(Crowning((1,1),(1,0),Queen(board.white)) in moves)
+ self.assertTrue(Crowning((1,1),(0,0),Queen(board.white)) in moves)
+
+class KnightTest(unittest.TestCase):
+ '''Class for testing the knight's moves'''
+
+ def test_basic_knight_moves(self):
+ '''Test a knight's basic moves'''
+ board = Board(10, 10)
+ knight = Knight(board.white)
+
+ i, j = 3, 4
+ board[i, j].piece = knight
+
+ moves = knight.get_moves((i, j), board)
+
+ self.assertEquals(len(moves), 8)
+
+ def test_knight_near_edge(self):
+ '''Test a knight's moves when near an edge of the board'''
+ board = Board(10, 10)
+ knight = Knight(board.white)
+
+ i, j = 0, 4
+ board[i, j].piece = knight
+
+ moves = knight.get_moves((i, j), board)
+
+ self.assertEquals(len(moves), 4)
+
+class EqualityTest(unittest.TestCase):
+ '''Test for move equality'''
+
+ def test_move_equality(self):
+ '''Test Move instance equality'''
+ m1 = Move((0, 0), (1, 1))
+ m2 = Move((0, 0), (1, 1))
+ self.assertEquals(m1, m2)
+
+ def test_castling_equality(self):
+ '''Test Castling instance equality'''
+ board = Board()
+ m1 = Castling("left", board.white)
+ m2 = Castling("left", board.white)
+ self.assertEquals(m1, m2)
+
+ def test_crowning(self):
+ '''Test Crowning instance equality'''
+ board = Board()
+ queen = Queen(board.white)
+ m1 = Crowning((0,1),(0,0), queen)
+ m2 = Crowning((0,1),(0,0), queen)
+ self.assertEquals(m1, m2)
+
+class MovementTest(unittest.TestCase):
+ '''Test piece movements'''
+
+ def test_move_instance(self):
+ '''Test Move instance's data storage'''
+ fro = 1,2
+ to = 3,4
+ m = Move(fro, to)
+
+ self.assertEquals(m.fro, fro)
+ self.assertEquals(m.to, to)
+
+ def test_queen_horizontal_move_blocked(self):
+ '''Test the Queen's horizontal move when blocked by a piece from the
+ same owner.'''
+ board = Board(10, 10)
+ queen = Queen(board.white)
+ pawn = Pawn(board.white)
+
+ i, j = 0, 4
+ board[i, j].piece = queen
+ board[i+2, j].piece = pawn
+
+ moves = queen.get_moves((i, j), board)
+
+ self.assertTrue(Move((i,j), (i-1,j)) not in moves)
+ self.assertTrue(Move((i,j), (i+1,j)) in moves)
+ self.assertTrue(Move((i,j), (i+2,j)) not in moves)
+ self.assertTrue(Move((i,j), (i+3,j)) not in moves)
+
+ def test_queen_moves(self):
+ '''Test the rook's moves unblocked'''
+ board = Board(10, 10)
+ queen = Queen(board.white)
+
+ i, j = 4, 4
+ board[i, j].piece = queen
+ moves = queen.get_moves((i, j), board)
+
+ self.assertTrue(Move((i,j),(i,0)) in moves)
+ self.assertTrue(Move((i,j),(i,7)) in moves)
+ self.assertTrue(Move((i,j),(0,j)) in moves)
+ self.assertTrue(Move((i,j),(7,j)) in moves)
+ self.assertTrue(Move((i,j),(i+1,j+1)) in moves)
+ self.assertTrue(Move((i,j),(i+1,j-1)) in moves)
+ self.assertTrue(Move((i,j),(i-1,j+1)) in moves)
+ self.assertTrue(Move((i,j),(i-1,j-1)) in moves)
+
+ def test_rook_moves(self):
+ '''Test the rook's moves unblocked'''
+ board = Board(10, 10)
+ rook = Rook(board.white)
+
+ i, j = 4, 4
+ board[i, j].piece = rook
+ moves = rook.get_moves((i, j), board)
+
+ self.assertTrue(Move((i,j),(i,0)) in moves)
+ self.assertTrue(Move((i,j),(i,7)) in moves)
+ self.assertTrue(Move((i,j),(0,j)) in moves)
+ self.assertTrue(Move((i,j),(7,j)) in moves)
+
+class CastlingTest(unittest.TestCase):
+ '''Test the king's castling move.'''
+
+ def test_castling_no_moves(self):
+ '''Test castling when neither the king nor the rook's have moved.'''
+ board = Board(10, 10)
+ king = King(board.white)
+ rook_l = Rook(board.white)
+ rook_r = Rook(board.white)
+
+ board[0, 7].piece = rook_l
+ board[7, 7].piece = rook_r
+ board[4, 7].piece = king
+
+ moves = king.get_moves((4, 7), board)
+
+ self.assertTrue(Castling("left",board.white) in moves)
+ self.assertTrue(Castling("right",board.white) in moves)
+
+ def test_castling_forbidden_king_moved(self):
+ '''Test that castling is forbidden once the king moved.'''
+ board = Board(10, 10)
+ king = King(board.white)
+ rook_l = Rook(board.white)
+ rook_r = Rook(board.white)
+
+ board[0, 7].piece = rook_l
+ board[7, 7].piece = rook_r
+ board[4, 7].piece = king
+
+ moves = king.get_moves((4, 7), board)
+ move = Move((4,7),(5,7))
+ self.assertTrue(move in moves)
+ move.perform(board)
+
+ moves = king.get_moves((4, 7), board)
+ self.assertFalse(Castling("left",board.white) in moves)
+ self.assertFalse(Castling("right",board.white) in moves)
+
+ def test_castling_forbidden_rook_moved(self):
+ '''Test that castling is forbidden once the rook moved.'''
+ board = Board(10, 10)
+ king = King(board.white)
+ rook_l = Rook(board.white)
+ rook_r = Rook(board.white)
+
+ board[0, 7].piece = rook_l
+ board[7, 7].piece = rook_r
+ board[4, 7].piece = king
+
+ moves = king.get_moves((4, 7), board)
+ self.assertTrue(Castling("left",board.white) in moves)
+ self.assertTrue(Castling("right",board.white) in moves)
+
+ move = Move((0,7),(1,7))
+ self.assertTrue(move in rook_l.get_moves((0,7),board))
+ move.perform(board)
+
+ moves = king.get_moves((4, 7), board)
+ self.assertFalse(Castling("left",board.white) in moves)
+ self.assertTrue(Castling("right",board.white) in moves)
+
+ move = Move((7,7),(6,7))
+ self.assertTrue(move in rook_r.get_moves((7,7),board))
+ move.perform(board)
+
+ moves = king.get_moves((4, 7), board)
+ self.assertFalse(Castling("left",board.white) in moves)
+ self.assertFalse(Castling("right",board.white) in moves)
+
+ def test_castling_permitted_after_undo(self):
+ board = Board(10, 10)
+ king = King(board.white)
+ rook_l = Rook(board.white)
+ rook_r = Rook(board.white)
+
+ board[0, 7].piece = rook_l
+ board[7, 7].piece = rook_r
+ board[4, 7].piece = king
+
+ moves = king.get_moves((4, 7), board)
+ self.assertTrue(Castling("left",board.white) in moves)
+ self.assertTrue(Castling("right",board.white) in moves)
+
+ move = Move((0,7),(1,7))
+ self.assertTrue(move in rook_l.get_moves((0,7),board))
+ move.perform(board)
+ move.undo(board)
+
+ moves = king.get_moves((4, 7), board)
+ self.assertTrue(Castling("left",board.white) in moves)
+ self.assertTrue(Castling("right",board.white) in moves)
+
+class PerformUndoMoveTest(unittest.TestCase):
+ '''Test performing moves and undoing them on a board.'''
+
+ def test_move_perform_undo(self):
+ '''Test performing a move and then undoing it.'''
+ #set up:
+ board = Board(10, 10)
+
+ pawn = Pawn(board.white)
+ i, j = 0, 6
+ board[i, j].piece = pawn
+
+ move = pawn.get_moves((i, j), board)[0]
+
+ #initial conditions:
+ self.assertTrue(not board[i, j-1].piece)
+ self.assertTrue(board[i, j].piece)
+
+ #test performing the move:
+ move.perform(board)
+ self.assertTrue(board[i, j-1].piece)
+ self.assertTrue(not board[i, j].piece)
+
+ self.assertTrue(not move.dst_piece)
+ self.assertTrue(move.performed)
+
+ #test undoing the move:
+ move.undo(board)
+ self.assertTrue(not board[i, j-1].piece)
+ self.assertTrue(board[i, j].piece)
+ self.assertTrue(not move.performed)
+
+ def test_castling_white_left(self):
+ '''Test performing a castling move to the left and then undoing it.'''
+ board = Board(10, 10)
+ board.current_turn = board.white
+ king = King(board.white)
+ rook = Rook(board.white)
+
+ ki, ri, j = 4, 0, 7
+
+ board[ki, j].piece = king
+ board[ri, j].piece = rook
+
+ #Preconditions:
+ self.assertTrue(board[ki, j].piece)
+ self.assertTrue(board[ri, j].piece)
+ self.assertTrue(not board[ki-1, j].piece)
+ self.assertTrue(not board[ki-2, j].piece)
+
+ #Perform castling:
+ move = Castling("left", board.white)
+ move.perform(board)
+
+ #Postconditions after castling
+ self.assertTrue(not board[ki, j].piece)
+ self.assertTrue(not board[ri, j].piece)
+ self.assertTrue(board[ki-1, j].piece)
+ self.assertTrue(board[ki-2, j].piece)
+
+ #Undo castling:
+ move.undo(board)
+
+ #Undo postconditions:
+ self.assertTrue(board[ki, j].piece)
+ self.assertTrue(board[ri, j].piece)
+ self.assertTrue(not board[ki-1, j].piece)
+ self.assertTrue(not board[ki-2, j].piece)
+
+ def test_castling_white_right(self):
+ '''Test performing a castling move to the right and then undoing it.'''
+ board = Board(10, 10)
+ board.current_turn = board.white
+ king = King(board.white)
+ rook = Rook(board.white)
+
+ ki, ri, j = 4, 7, 7
+
+ board[ki, j].piece = king
+ board[ri, j].piece = rook
+
+ #Preconditions:
+ self.assertTrue(board[ki, j].piece)
+ self.assertTrue(board[ri, j].piece)
+ self.assertTrue(not board[ki+1, j].piece)
+ self.assertTrue(not board[ki+2, j].piece)
+
+ #Perform castling:
+ move = Castling("right", board.white)
+ move.perform(board)
+
+ #Postconditions after castling
+ self.assertTrue(not board[ki, j].piece)
+ self.assertTrue(not board[ri, j].piece)
+ self.assertTrue(board[ki+1, j].piece)
+ self.assertTrue(board[ki+2, j].piece)
+
+ #Undo castling:
+ move.undo(board)
+
+ #Undo postconditions:
+ self.assertTrue(board[ki, j].piece)
+ self.assertTrue(board[ri, j].piece)
+ self.assertTrue(not board[ki+1, j].piece)
+ self.assertTrue(not board[ki+2, j].piece)
+
+ def test_crowning_perform_undo(self):
+ '''Test performing a crowning move and then undoing it'''
+ #init:
+ board = Board(10, 10)
+ pawn = Pawn(board.white)
+
+ i, j = 0, 1
+ board[i, j].piece = pawn
+ src_type = str(pawn).split(" ")[0].replace("<", "")
+
+ move = filter(lambda x: x.type == "Crowning", pawn.get_moves((i, j), board))[0]
+
+ #Preconditions:
+ self.assertTrue(not board[i, j-1].piece)
+ self.assertTrue(board[i, j].piece)
+
+ move.perform(board)
+
+ self.assertTrue(board[i, j-1].piece)
+ self.assertTrue(not board[i, j].piece)
+ self.assertTrue(move.performed)
+
+ #compare piece types (should differ if crowning succeeded):
+ dst_type = str(board[i, j-1].piece).split(" ")[0].replace("<","")
+ self.assertTrue(src_type != dst_type)
+
+ #undo move:
+ move.undo(board)
+ self.assertTrue(not board[i, j-1].piece)
+ self.assertTrue(board[i, j].piece)
+ self.assertTrue(not move.performed)
+
+ #compare piece types (should be both pawns):
+ dst_type = str(board[i, j].piece).split(" ")[0].replace("<","")
+ self.assertTrue(src_type == dst_type)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/testcases/runtests.sh b/testcases/runtests.sh
new file mode 100755
index 0000000..840d367
--- /dev/null
+++ b/testcases/runtests.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+PREVPATH=$PYTHONPATH
+
+DIR=${0%/*}
+export PYTHONPATH="${DIR}:${DIR}/../"
+
+TESTS=`ls -1 ${DIR}/*tests.py`
+
+for t in ${TESTS}; do
+ python $t
+done
+
+export PYTHONPATH=$PREVPATH
diff --git a/ui.py b/ui.py
new file mode 100644
index 0000000..549cda6
--- /dev/null
+++ b/ui.py
@@ -0,0 +1,176 @@
+#
+# Ceibal Chess - A chess activity for Sugar.
+# Copyright (C) 2008, 2009 Alejandro Segovia <asegovi@gmail.com>
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#
+import pygame
+import os
+import gettext
+from gettext import gettext as _
+from resourcemanager import image_manager
+
+class StatePanel:
+ '''Shows the current game state in a panel. The displayed game state
+ usually implies displaying the current turn (white or black), and
+ indicating a Checkmate.'''
+
+ def __init__(self, x, y, w, h):
+ '''Create a new instance of State Panel.
+ x,y are the position where the panel will be rendered.
+ w,h are the panel's desired size.'''
+
+ self.state = "move_white"
+ self.x, self.y, self.w, self.h = x, y, w, h
+ self.loaded = False
+
+ def set_state(self, state):
+ '''Set the state to the given parameter.
+ Valid states are: move_white, move_black, check_white, check_black,
+ checkmate_white and checkmate_black .'''
+ if not state in ["move_white", "move_black", "check_white", \
+ "check_black", "checkmate_white", "checkmate_black"]:
+ raise Exception("Invalid State: " + state)
+ self.state = state
+
+ def initialize(self):
+ '''Initialize Fonts, Images, etc.'''
+ self.bg = pygame.transform.scale( \
+ image_manager.get_image("menu_back.png"), (self.w, self.h))
+
+ pawn_white = image_manager.get_image("pawnwhite.png")
+ pawn_black = image_manager.get_image("pawnblack.png")
+ king_white = image_manager.get_image("kingwhite.png")
+ king_black = image_manager.get_image("kingblack.png")
+
+ self.turn_imgs = { "move_white" : pawn_white, \
+ "move_black" : pawn_black, \
+ "check_white" : king_white, \
+ "check_black" : king_black, \
+ "checkmate_white" : king_white, \
+ "checkmate_black" : king_black }
+
+ font = pygame.font.Font(None, 25)
+ self.turn_text = font.render(_("Current Turn:"), 1, (255, 255, 255))
+ self.check_text = font.render(_("Check:"), 1, (255, 255, 0))
+ self.mate_text = font.render(_("Checkmate:"), 1, (255, 20, 20))
+
+ self.loaded = True
+
+ def render(self, surface):
+ '''Render this panel on the given surface.'''
+
+ if not self.loaded:
+ self.initialize()
+
+ x,y,w,h = self.x, self.y, self.w, self.h
+
+ surface.blit(self.bg, pygame.Rect(x,y,w,h))
+
+ if self.state in ["move_white", "move_black"]:
+ surface.blit(self.turn_text, (x+(w-self.turn_text.get_width())/2.0, w/5.5))
+ elif self.state in ["check_white", "check_black"]:
+ surface.blit(self.check_text, (x+(w-self.check_text.get_width())/2.0, w/5.5))
+ else:
+ surface.blit(self.mate_text, (x+(w-self.mate_text.get_width())/2.0, w/5.5))
+
+ img = self.turn_imgs[self.state]
+ iw, ih = img.get_width(), img.get_height()
+ surface.blit(img, pygame.Rect(x + (w-iw)/2, y + (h-ih)/2, iw, ih))
+
+class BoardRenderer(object):
+ def __init__(self, w, h):
+ self.background = None
+ self.w = w
+ self.h = h
+ self.cell_renderer = CellRenderer(PieceRenderer())
+
+ def render_moves_for_piece_in_cell(self, board, surface, cell):
+ '''Highlight possible moves for the piece in the given cell'''
+ if cell.piece is None:
+ raise Exception("cell does not contain a piece!")
+
+ #select hightlight colors:
+ if cell.piece.owner == board.current_turn:
+ color = (0, 255, 0)
+ color2 = (0, 180, 0)
+ else:
+ color = (255, 0, 0)
+ color2 = (180, 0, 0)
+
+ dests = cell.piece.get_moves(cell.pos, board, filter_check=True)
+
+ for dest in dests:
+ self.cell_renderer.render_as_highlight(board[dest.to], surface, color)
+ self.cell_renderer.render_as_highlight(cell, surface, color2)
+
+ def render_background(self, board, surface):
+ '''Render the checkboard background'''
+ if self.background is None:
+ #Create alternating background
+ self.background = pygame.Surface((self.w, self.h))
+ for i in range(0, 8):
+ for j in range(0, 8):
+ self.cell_renderer.render_background(board[i, j], self.background)
+
+ #Load texture and blit it
+ texture = image_manager.get_image("wood.png")
+ texture = pygame.transform.scale(texture, (self.w, self.h))
+ self.background.blit(texture, texture.get_rect())
+
+ surface.blit(self.background, self.background.get_rect())
+
+ def render_foreground(self, board, surface):
+ '''Render cell contents. Call render_foreground on each cell.'''
+ for i in range(0, 8):
+ for j in range(0, 8):
+ self.cell_renderer.render_foreground(board[i, j], surface)
+
+class CellRenderer(object):
+ def __init__(self, piece_renderer):
+ self.piece_renderer = piece_renderer
+
+ def render_background(self, cell, surface):
+ '''Render the cell's background onto the given surface'''
+ tx = cell[0] * cell.size
+ ty = cell[1] * cell.size
+ surface.fill(cell.color, pygame.Rect(tx, ty, cell.size, cell.size))
+
+ def render_foreground(self, cell, surface):
+ '''Render the cell's foreground. Currently, this just forwards the
+ rendering call to the contained piece, if any. Rendering is performed
+ on the given surface.'''
+ if cell.piece:
+ self.piece_renderer.render(cell.pos, cell.size, cell.piece, surface)
+
+ def render_as_highlight(self, cell, surface, color = (0, 255, 0)):
+ '''Render the cell highlighted with a given color onto the given
+ surface. Used to highlight possible piece movements.'''
+ tx = cell[0] * cell.size
+ ty = cell[1] * cell.size
+ surface.fill(color, pygame.Rect(tx+5, ty+5, cell.size-10, cell.size-10))
+
+class PieceRenderer(object):
+ def render(self, pos, size, piece, surface):
+ '''
+ Render the piece. x,y are the top left corner of the
+ container cell; cell_size is its size
+ '''
+ img = image_manager.get_image('%s%s.png' % (piece.type, piece.owner.name))
+ w,h = img.get_width(), img.get_height()
+ tx = pos[0]*size + (size - w) / 2
+ ty = pos[1]*size + (size - h) / 2
+
+ surface.blit(img, pygame.Rect(tx, ty, w, h))