diff options
author | Alejandro Segovia <asegovi@gmail.com> | 2012-05-07 23:15:50 (GMT) |
---|---|---|
committer | Alejandro Segovia <asegovi@gmail.com> | 2012-05-07 23:15:50 (GMT) |
commit | 24932f700e9fdacf349e5a32517ccfe8984ea63c (patch) | |
tree | bf9b4bfc851beac5f9437d41eff31940e1ed0bc9 |
Initial commit of the Ceibal-Chess source code and binaries
-rw-r--r-- | GPL_V2 | 341 | ||||
-rw-r--r-- | MANIFEST | 59 | ||||
-rw-r--r-- | Makefile | 20 | ||||
-rw-r--r-- | activity-icon.svg | 155 | ||||
-rw-r--r-- | activity/activity-icon.svg | 155 | ||||
-rw-r--r-- | activity/activity.info | 10 | ||||
-rw-r--r-- | board.py | 288 | ||||
-rw-r--r-- | boardcontroller.py | 174 | ||||
-rw-r--r-- | cell.py | 80 | ||||
-rw-r--r-- | chessactivity.py | 55 | ||||
-rw-r--r-- | chessengine.py | 229 | ||||
-rwxr-xr-x | createbundle.sh | 48 | ||||
-rw-r--r-- | data/bg.png | bin | 0 -> 162088 bytes | |||
-rw-r--r-- | data/bg.png.old | bin | 0 -> 870514 bytes | |||
-rw-r--r-- | data/bishop.png | bin | 0 -> 2753 bytes | |||
-rw-r--r-- | data/bishopblack.png | bin | 0 -> 19025 bytes | |||
-rw-r--r-- | data/bishopwhite.png | bin | 0 -> 20581 bytes | |||
-rw-r--r-- | data/btn_back.png | bin | 0 -> 30085 bytes | |||
-rw-r--r-- | data/king.png | bin | 0 -> 3044 bytes | |||
-rw-r--r-- | data/kingblack.png | bin | 0 -> 5086 bytes | |||
-rw-r--r-- | data/kingwhite.png | bin | 0 -> 5512 bytes | |||
-rw-r--r-- | data/knight.png | bin | 0 -> 3069 bytes | |||
-rw-r--r-- | data/knightblack.png | bin | 0 -> 5174 bytes | |||
-rw-r--r-- | data/knightwhite.png | bin | 0 -> 20098 bytes | |||
-rw-r--r-- | data/menu_back.png | bin | 0 -> 10772 bytes | |||
-rw-r--r-- | data/menu_back2.png | bin | 0 -> 5875 bytes | |||
-rw-r--r-- | data/pawn.png | bin | 0 -> 2247 bytes | |||
-rw-r--r-- | data/pawnblack.png | bin | 0 -> 4982 bytes | |||
-rw-r--r-- | data/pawnwhite.png | bin | 0 -> 19626 bytes | |||
-rw-r--r-- | data/queen.png | bin | 0 -> 3225 bytes | |||
-rw-r--r-- | data/queenblack.png | bin | 0 -> 19732 bytes | |||
-rw-r--r-- | data/queenwhite.png | bin | 0 -> 19936 bytes | |||
-rw-r--r-- | data/rook.png | bin | 0 -> 2151 bytes | |||
-rw-r--r-- | data/rookblack.png | bin | 0 -> 4901 bytes | |||
-rw-r--r-- | data/rookwhite.png | bin | 0 -> 5264 bytes | |||
-rw-r--r-- | data/wood.png | bin | 0 -> 100020 bytes | |||
-rwxr-xr-x | data_bw/bg.png | bin | 0 -> 162088 bytes | |||
-rwxr-xr-x | data_bw/bishop.png | bin | 0 -> 2760 bytes | |||
-rwxr-xr-x | data_bw/bishopblack.png | bin | 0 -> 3678 bytes | |||
-rwxr-xr-x | data_bw/bishopwhite.png | bin | 0 -> 3797 bytes | |||
-rwxr-xr-x | data_bw/btn_back.png | bin | 0 -> 30085 bytes | |||
-rwxr-xr-x | data_bw/king.png | bin | 0 -> 3044 bytes | |||
-rwxr-xr-x | data_bw/kingblack.png | bin | 0 -> 3321 bytes | |||
-rwxr-xr-x | data_bw/kingwhite.png | bin | 0 -> 3490 bytes | |||
-rwxr-xr-x | data_bw/knight.png | bin | 0 -> 3069 bytes | |||
-rwxr-xr-x | data_bw/knightblack.png | bin | 0 -> 3589 bytes | |||
-rwxr-xr-x | data_bw/knightwhite.png | bin | 0 -> 3740 bytes | |||
-rwxr-xr-x | data_bw/menu_back.png | bin | 0 -> 10772 bytes | |||
-rwxr-xr-x | data_bw/menu_back2.png | bin | 0 -> 5875 bytes | |||
-rwxr-xr-x | data_bw/pawn.png | bin | 0 -> 2247 bytes | |||
-rwxr-xr-x | data_bw/pawnblack.png | bin | 0 -> 3679 bytes | |||
-rwxr-xr-x | data_bw/pawnwhite.png | bin | 0 -> 3789 bytes | |||
-rwxr-xr-x | data_bw/queen.png | bin | 0 -> 3225 bytes | |||
-rwxr-xr-x | data_bw/queenblack.png | bin | 0 -> 3748 bytes | |||
-rwxr-xr-x | data_bw/queenwhite.png | bin | 0 -> 3849 bytes | |||
-rwxr-xr-x | data_bw/rook.png | bin | 0 -> 2151 bytes | |||
-rwxr-xr-x | data_bw/rookblack.png | bin | 0 -> 3344 bytes | |||
-rwxr-xr-x | data_bw/rookwhite.png | bin | 0 -> 3477 bytes | |||
-rwxr-xr-x | data_bw/wood.png | bin | 0 -> 100020 bytes | |||
-rwxr-xr-x | engines/cygwin1.dll | bin | 0 -> 1872884 bytes | |||
-rwxr-xr-x | engines/gnuchess-linux | bin | 0 -> 820514 bytes | |||
-rwxr-xr-x | engines/gnuchess-osx | bin | 0 -> 235592 bytes | |||
-rwxr-xr-x | engines/gnuchess-win32.exe | bin | 0 -> 214664 bytes | |||
-rw-r--r-- | errors.py | 33 | ||||
-rwxr-xr-x | main.py | 389 | ||||
-rw-r--r-- | menu.py | 125 | ||||
-rw-r--r-- | messenger.py | 68 | ||||
-rw-r--r-- | piece.py | 485 | ||||
-rw-r--r-- | po/ceibalchess.pot | 53 | ||||
-rw-r--r-- | po/en/LC_MESSAGES/messages.mo | bin | 0 -> 793 bytes | |||
-rw-r--r-- | po/en_US.po | 54 | ||||
-rw-r--r-- | po/es/LC_MESSAGES/messages.mo | bin | 0 -> 798 bytes | |||
-rw-r--r-- | po/es_UY.po | 55 | ||||
-rw-r--r-- | pychess-UML.jpg | bin | 0 -> 507778 bytes | |||
-rw-r--r-- | resourcemanager.py | 62 | ||||
-rwxr-xr-x | setup.py | 22 | ||||
-rw-r--r-- | sugargame/__init__.py | 1 | ||||
-rw-r--r-- | sugargame/canvas.py | 62 | ||||
-rw-r--r-- | sugargame/event.py | 243 | ||||
-rw-r--r-- | testcases/boardtests.py | 131 | ||||
-rw-r--r-- | testcases/piecetests.py | 482 | ||||
-rwxr-xr-x | testcases/runtests.sh | 13 | ||||
-rw-r--r-- | ui.py | 176 |
83 files changed, 4068 insertions, 0 deletions
@@ -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) @@ -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 Binary files differnew file mode 100644 index 0000000..1b888ad --- /dev/null +++ b/data/bg.png diff --git a/data/bg.png.old b/data/bg.png.old Binary files differnew file mode 100644 index 0000000..6cf85fa --- /dev/null +++ b/data/bg.png.old diff --git a/data/bishop.png b/data/bishop.png Binary files differnew file mode 100644 index 0000000..9539f1e --- /dev/null +++ b/data/bishop.png diff --git a/data/bishopblack.png b/data/bishopblack.png Binary files differnew file mode 100644 index 0000000..294db1f --- /dev/null +++ b/data/bishopblack.png diff --git a/data/bishopwhite.png b/data/bishopwhite.png Binary files differnew file mode 100644 index 0000000..b8efa58 --- /dev/null +++ b/data/bishopwhite.png diff --git a/data/btn_back.png b/data/btn_back.png Binary files differnew file mode 100644 index 0000000..8229b73 --- /dev/null +++ b/data/btn_back.png diff --git a/data/king.png b/data/king.png Binary files differnew file mode 100644 index 0000000..afacdd1 --- /dev/null +++ b/data/king.png diff --git a/data/kingblack.png b/data/kingblack.png Binary files differnew file mode 100644 index 0000000..bd57424 --- /dev/null +++ b/data/kingblack.png diff --git a/data/kingwhite.png b/data/kingwhite.png Binary files differnew file mode 100644 index 0000000..97098cb --- /dev/null +++ b/data/kingwhite.png diff --git a/data/knight.png b/data/knight.png Binary files differnew file mode 100644 index 0000000..ee46ec1 --- /dev/null +++ b/data/knight.png diff --git a/data/knightblack.png b/data/knightblack.png Binary files differnew file mode 100644 index 0000000..92446d1 --- /dev/null +++ b/data/knightblack.png diff --git a/data/knightwhite.png b/data/knightwhite.png Binary files differnew file mode 100644 index 0000000..bfdb14d --- /dev/null +++ b/data/knightwhite.png diff --git a/data/menu_back.png b/data/menu_back.png Binary files differnew file mode 100644 index 0000000..44b275b --- /dev/null +++ b/data/menu_back.png diff --git a/data/menu_back2.png b/data/menu_back2.png Binary files differnew file mode 100644 index 0000000..9430048 --- /dev/null +++ b/data/menu_back2.png diff --git a/data/pawn.png b/data/pawn.png Binary files differnew file mode 100644 index 0000000..c911794 --- /dev/null +++ b/data/pawn.png diff --git a/data/pawnblack.png b/data/pawnblack.png Binary files differnew file mode 100644 index 0000000..3e54624 --- /dev/null +++ b/data/pawnblack.png diff --git a/data/pawnwhite.png b/data/pawnwhite.png Binary files differnew file mode 100644 index 0000000..36e86e2 --- /dev/null +++ b/data/pawnwhite.png diff --git a/data/queen.png b/data/queen.png Binary files differnew file mode 100644 index 0000000..b55b1a2 --- /dev/null +++ b/data/queen.png diff --git a/data/queenblack.png b/data/queenblack.png Binary files differnew file mode 100644 index 0000000..3b08b9d --- /dev/null +++ b/data/queenblack.png diff --git a/data/queenwhite.png b/data/queenwhite.png Binary files differnew file mode 100644 index 0000000..e9c81ae --- /dev/null +++ b/data/queenwhite.png diff --git a/data/rook.png b/data/rook.png Binary files differnew file mode 100644 index 0000000..a2391e1 --- /dev/null +++ b/data/rook.png diff --git a/data/rookblack.png b/data/rookblack.png Binary files differnew file mode 100644 index 0000000..cd8cf83 --- /dev/null +++ b/data/rookblack.png diff --git a/data/rookwhite.png b/data/rookwhite.png Binary files differnew file mode 100644 index 0000000..5ba23c4 --- /dev/null +++ b/data/rookwhite.png diff --git a/data/wood.png b/data/wood.png Binary files differnew file mode 100644 index 0000000..e9a76ea --- /dev/null +++ b/data/wood.png diff --git a/data_bw/bg.png b/data_bw/bg.png Binary files differnew file mode 100755 index 0000000..1b888ad --- /dev/null +++ b/data_bw/bg.png diff --git a/data_bw/bishop.png b/data_bw/bishop.png Binary files differnew file mode 100755 index 0000000..90f461b --- /dev/null +++ b/data_bw/bishop.png diff --git a/data_bw/bishopblack.png b/data_bw/bishopblack.png Binary files differnew file mode 100755 index 0000000..269a609 --- /dev/null +++ b/data_bw/bishopblack.png diff --git a/data_bw/bishopwhite.png b/data_bw/bishopwhite.png Binary files differnew file mode 100755 index 0000000..4fac6d7 --- /dev/null +++ b/data_bw/bishopwhite.png diff --git a/data_bw/btn_back.png b/data_bw/btn_back.png Binary files differnew file mode 100755 index 0000000..8229b73 --- /dev/null +++ b/data_bw/btn_back.png diff --git a/data_bw/king.png b/data_bw/king.png Binary files differnew file mode 100755 index 0000000..afacdd1 --- /dev/null +++ b/data_bw/king.png diff --git a/data_bw/kingblack.png b/data_bw/kingblack.png Binary files differnew file mode 100755 index 0000000..e0dfde1 --- /dev/null +++ b/data_bw/kingblack.png diff --git a/data_bw/kingwhite.png b/data_bw/kingwhite.png Binary files differnew file mode 100755 index 0000000..a4ea0ae --- /dev/null +++ b/data_bw/kingwhite.png diff --git a/data_bw/knight.png b/data_bw/knight.png Binary files differnew file mode 100755 index 0000000..ee46ec1 --- /dev/null +++ b/data_bw/knight.png diff --git a/data_bw/knightblack.png b/data_bw/knightblack.png Binary files differnew file mode 100755 index 0000000..979ce4f --- /dev/null +++ b/data_bw/knightblack.png diff --git a/data_bw/knightwhite.png b/data_bw/knightwhite.png Binary files differnew file mode 100755 index 0000000..ecf211e --- /dev/null +++ b/data_bw/knightwhite.png diff --git a/data_bw/menu_back.png b/data_bw/menu_back.png Binary files differnew file mode 100755 index 0000000..44b275b --- /dev/null +++ b/data_bw/menu_back.png diff --git a/data_bw/menu_back2.png b/data_bw/menu_back2.png Binary files differnew file mode 100755 index 0000000..9430048 --- /dev/null +++ b/data_bw/menu_back2.png diff --git a/data_bw/pawn.png b/data_bw/pawn.png Binary files differnew file mode 100755 index 0000000..c911794 --- /dev/null +++ b/data_bw/pawn.png diff --git a/data_bw/pawnblack.png b/data_bw/pawnblack.png Binary files differnew file mode 100755 index 0000000..4c87359 --- /dev/null +++ b/data_bw/pawnblack.png diff --git a/data_bw/pawnwhite.png b/data_bw/pawnwhite.png Binary files differnew file mode 100755 index 0000000..72ff325 --- /dev/null +++ b/data_bw/pawnwhite.png diff --git a/data_bw/queen.png b/data_bw/queen.png Binary files differnew file mode 100755 index 0000000..b55b1a2 --- /dev/null +++ b/data_bw/queen.png diff --git a/data_bw/queenblack.png b/data_bw/queenblack.png Binary files differnew file mode 100755 index 0000000..a6f0ade --- /dev/null +++ b/data_bw/queenblack.png diff --git a/data_bw/queenwhite.png b/data_bw/queenwhite.png Binary files differnew file mode 100755 index 0000000..8bec7bf --- /dev/null +++ b/data_bw/queenwhite.png diff --git a/data_bw/rook.png b/data_bw/rook.png Binary files differnew file mode 100755 index 0000000..a2391e1 --- /dev/null +++ b/data_bw/rook.png diff --git a/data_bw/rookblack.png b/data_bw/rookblack.png Binary files differnew file mode 100755 index 0000000..30c566b --- /dev/null +++ b/data_bw/rookblack.png diff --git a/data_bw/rookwhite.png b/data_bw/rookwhite.png Binary files differnew file mode 100755 index 0000000..0d12ec7 --- /dev/null +++ b/data_bw/rookwhite.png diff --git a/data_bw/wood.png b/data_bw/wood.png Binary files differnew file mode 100755 index 0000000..e9a76ea --- /dev/null +++ b/data_bw/wood.png diff --git a/engines/cygwin1.dll b/engines/cygwin1.dll Binary files differnew file mode 100755 index 0000000..0296b95 --- /dev/null +++ b/engines/cygwin1.dll diff --git a/engines/gnuchess-linux b/engines/gnuchess-linux Binary files differnew file mode 100755 index 0000000..39557b7 --- /dev/null +++ b/engines/gnuchess-linux diff --git a/engines/gnuchess-osx b/engines/gnuchess-osx Binary files differnew file mode 100755 index 0000000..b9468dc --- /dev/null +++ b/engines/gnuchess-osx diff --git a/engines/gnuchess-win32.exe b/engines/gnuchess-win32.exe Binary files differnew file mode 100755 index 0000000..9b52397 --- /dev/null +++ b/engines/gnuchess-win32.exe 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) @@ -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) @@ -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 Binary files differnew file mode 100644 index 0000000..81443b1 --- /dev/null +++ b/po/en/LC_MESSAGES/messages.mo 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 Binary files differnew file mode 100644 index 0000000..ec60312 --- /dev/null +++ b/po/es/LC_MESSAGES/messages.mo 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 Binary files differnew file mode 100644 index 0000000..f009fd3 --- /dev/null +++ b/pychess-UML.jpg 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 @@ -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)) |