diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..b4951ab753 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 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. + + , 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.in b/MANIFEST.in new file mode 100644 index 0000000000..2afd0be536 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,38 @@ +## MANIFEST.in for PySolFC +## +## code +## +include pysol setup.py setup.cfg MANIFEST.in Makefile COPYING README +#recursive-include pysollib *.py +include pysollib/*.py pysollib/tk/*.py +include pysollib/games/*.py pysollib/games/special/*.py +include pysollib/games/ultra/*.py pysollib/games/contrib/*.py +include pysollib/games/mahjongg/*.py +include docs/* +include po/* +include scripts/build.bat scripts/create_iss.py scripts/mahjongg_utils.py +include scripts/all_games.py scripts/cardset_viewer.py +## +## data +## +graft data/cardset-2000 +graft data/cardset-crystal-mahjongg +graft data/cardset-dashavatara-ganjifa +graft data/cardset-hexadeck +graft data/cardset-kintengu +graft data/cardset-gnome-mahjongg-1 +graft data/cardset-matrix +graft data/cardset-mughal-ganjifa +graft data/cardset-oxymoron +graft data/cardset-standard +graft data/cardset-tuxedo +graft data/cardset-vienna-2k +graft data/html +graft data/html-src +graft data/images +#graft data/music +#graft data/plugins +graft data/sound +graft data/tiles +include data/pysol.xbm data/pysol.xpm data/pysol.ico +graft locale diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..aab7698011 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +# Makefile for PySolFC + +PYSOLLIB_FILES=pysollib/tk/*.py pysollib/*.py \ + pysollib/games/*.py pysollib/games/special/*.py \ + pysollib/games/contrib/*.py pysollib/games/ultra/*.py + +.PHONY : install dist all_games_html rules pot mo + +install: + python setup.py install + +dist: + ./scripts/all_games.py > docs/all_games.html + python setup.py sdist + +rpm: + python setup.py bdist_rpm --use-bzip2 + +all_games_html: + ./scripts/all_games.py > docs/all_games.html + +rules: + (cd data/html-src && ./gen-html.py) + cp -r data/html-src/images data/html-src/html + rm -rf data/html + mv data/html-src/html data + +pot: + pygettext.py -k n_ -o po/pysol.pot $(PYSOLLIB_FILES) + ./scripts/all_games.py gettext > po/games.pot + for lng in ru; do \ + mv -f po/$${lng}_pysol.po po/$${lng}_pysol.old.po; \ + msgmerge po/$${lng}_pysol.old.po po/pysol.pot > po/$${lng}_pysol.po; \ + rm -f po/$${lng}_pysol.old.po; \ + mv -f po/$${lng}_games.po po/$${lng}_games.old.po; \ + msgmerge po/$${lng}_games.old.po po/games.pot > po/$${lng}_games.po; \ + rm -f po/$${lng}_games.old.po; \ + done + +mo: + test -d locale/ru/LC_MESSAGES || mkdir -p locale/ru/LC_MESSAGES + test -d locale/ru_RU/LC_MESSAGES || mkdir -p locale/ru_RU/LC_MESSAGES + msgcat po/ru_games.po po/ru_pysol.po > po/ru.po 2>/dev/null + msgfmt -o locale/ru/LC_MESSAGES/pysol.mo po/ru.po + cp -f locale/ru/LC_MESSAGES/pysol.mo locale/ru_RU/LC_MESSAGES/pysol.mo diff --git a/README b/README new file mode 100644 index 0000000000..5096417b26 --- /dev/null +++ b/README @@ -0,0 +1,23 @@ +PySol Fan Club edition +====================== + + +Requirements. +------------- + +- Python (2.3 or later) +- Tkinter +- PySol-Sound-Server: http://www.pysol.org/ (not necessarily) +- PIL (Python Image Library): http://www.pythonware.com/products/pil (not necessarily) +- Freecell Solver: http://vipe.technion.ac.il/~shlomif/freecell-solver/ (not necessarily) + + +Installation. +------------- + +See: http://www.python.org/doc/current/inst/ + +or just run from the source directory: + +$ python pysol + diff --git a/docs/README b/docs/README new file mode 100644 index 0000000000..16359bdac0 --- /dev/null +++ b/docs/README @@ -0,0 +1,46 @@ +================================================================== +PySol - a solitaire game collection +================================================================== + +PySol is an exciting collection of more than 200 solitaire games. + + +Introduction +------------ + Please see the HTML documentation `data/html/intro.html' + for an overview of the many features. + + For other questions like "how do I install PySol ?" and "how to play ?" + also consult the HTML documentation in the `data/html' directory. + + +Feedback +-------- + As I do not have the time to test all variants thoroughly this + release will have bugs and misfeatures. The only way to get + these fixed is to report them. + + I also welcome your comments and suggestions (or just a mail + that you like PySol :-) + + +Copyright +--------- + PySol is Copyright (C) 1998, 1999, 2000, 2001, 2002 + Markus Franz Xaver Johannes Oberhumer + All Rights Reserved + + PySol is distributed under the terms of the GNU General Public License (GPL). + See the file COPYING. + + +Have fun, +Markus + + +http://www.oberhumer.com/pysol + + +P.S. To simplify installation PySol is distributed in a "bundled" version. + The full developers source code is available from the PySol homepage. + diff --git a/docs/README.SOURCE b/docs/README.SOURCE new file mode 100644 index 0000000000..8c1d3af9c9 --- /dev/null +++ b/docs/README.SOURCE @@ -0,0 +1,233 @@ +================================================================== +PySol - a Python Solitaire Game Collection +================================================================== + +This is the developers source code. + + +Background information +---------------------- +In order to simplify installation the main PySol package is distributed +in a "bundled" version, which is basically a concatenation of all +source files. + + +Note for package maintainers +---------------------------- +You are strongly advised to package up the bundled byte-compiled +version as it loads faster, uses less memory and guarantees +compatibility with saved games of older PySol versions (<= 3.00). + + +Prerequisites +------------- +First of all you will need the Python development environment, which +is freely available from http://www.python.org + + +Source code introduction +------------------------ +The source basically consists of these three parts: + + - The main layer + Main application code and game logic. + + - The toolkit layer + Interface to the underlying GUI toolkit and windowing system. + + - The games layer + The actual games and plugins, implemented as subclasses of the + abstract classes Game and Stack. + + +The main layer +-------------- + pysol.py, main.py: + Main entry and initialization. Create an Application and start it. + + app.py: + Main control loop. Contains the class Application which is the glue + between the toplevel window and a Game. Also responsible for global + resources like options, statistics and plugins. + + Main objects making a full PySol application are: + - an Application [app] + - a concrete subclass of a Game [app.game] + - a Toplevel window [app.top] + - a Menubar which is connected to the game and toplevel [app.menubar] + - a Canvas for the playing table [app.canvas] + - a Toolbar which is connected to the game and menubar [app.toolbar] + + pysoltk.py: + Interface to the toolkit layer - see below. + + game.py: + Abstract class Game: undo/redo, hint/demo, load/save. + Responsible for combining the stacks to form a complete game. + + stack.py: + The stacks contain most of the intelligence. Very important methods + are acceptsCards(), canMoveCards(), canDropCards() and canFlipCard(). + Also, the stacks are responsible for card movement (mouse events) + and the card layout (the getPositionFor() method). + *** stack.py is the central file of PySol *** + + layout.py: + Utility class used by subclasses of Game to handle common layout + and graphics tasks. + + move.py: + Implements the actual atomic move types. Any move (and any + visualization of a move) passes through this. + + actions.py: + Implementation of default actions for the menubar and toolbar. + Subclassed by the toolkit layer. + + acard.py: + Implementation of default methods for a card - a card does not + contain any intelligence and is merely a display object. + Subclassed by the toolkit layer. + + hint.py: + Hint/demo logic. Rather generic, individual games may subclass. + Currently optimized for Klondike/Gypsy type games. + + gamedb.py: + Game database and game/plugin loader. + + resource.py: + Resource managers (cardsets, tiles, samples, music) + + stats.py: + Abstract statistics handler. May get rewritten in the future. + + mfxutil.py, util.py, random.py: + More or less standalone utility modules. + + help.py: + Interface to the HTML viewer and some dialogs. + + pysolaudio.py: + Interface to the sound server. + + +The toolkit layer +----------------- + PySol has been designed so that it can run under multiple UI toolkits - + due to the dynamic nature of Python this can even be under control + of a runtime option. + + The preferred toolkit is Tcl/Tk using the Tkinter bindings which + ship with every Python installation, but a very experimental version + for Gnome (using the pygnome and pygtk bindings) exists as well. + + A more exicting idea is to use JPython to make PySol run under a + Java VM using Swing as the toolkit. + + Because Tkinter is the "main" interface other toolkit layers have + to emulate a limited subset of Tkinter's API. This should hopefully + not prove too difficult in practice. + + Relevant modules: + + pysoltk.py: + Any access to the toolkit layer goes via this module. This means + that the implementation of the toolkit layer is completely hidden + and can use any number of internal modules in its subdirectory. + + Important modules of the Tkinter implementation: + + tk/tkconst.py, tk/tkutil.py, tk/tkwidget.py, tk/edittextdialog: + Toolkit constants, utils and generic widgets. + + tk/tkcanvas.py: + Wrapper for canvas widgets. + + tk/tkwrap.py: + Wrapper for other widgets. + + tk/card.py: + How to display a card on the screen. No intelligence here. + Subclasses the main layer. + + tk/menubar.py: + Create menubar and handle menu actions. Delegates to class Game. + Subclasses the actions.py main layer. + + tk/toolbar.py: + Toolbar. Subclasses the actions.py main layer. + + tk/progressbar.py, tk/statusbar.py: + Progress- and statusbar widgets. + + tk/tkstats.py: + Statistics dialogs. Uses stats.py from the main layer. + + tk/tktree.py, tk/selecttree.py, tk/select*.py: + Tree and tree-selection widgets. + + tk/tkhtml.py: + A very limited HTML widget. Could be useful for other projects, though. + + +The games layer +--------------- + games/*.py: + These modules implement subclasses of Game/Stack for the actual game + layout. Start with eiffeltower.py and klondike.py to get an idea + how it works. + + Implementing your own favourite solitaire game is straightforward: + + Create a new source file in the games directory (copy a game that + is somewhat similar for use as a starting point). If your game requires + special intelligence derive subclasses of a stack - see braid.py or + picturegallery.py for really complex examples. You can also derive + from stacks of other games. + + Layout of the stacks and texts is controlled by the game (createGame, + utility class Layout), while intelligence is mostly contained + in the stacks. + + Implement your own hint class if necessary (see golf.py for a + simple example). + + Do not change global files like stack.py, game.py or hint.py - derive + subclasses if necessary. + + Do not pollute the global namespace. + + Follow the PySol style coding style. + + Adapt the parameters in the call of registerGame(): + a unique game ID (should be in range 100000..999999 for user + written games), the game class, the game name, and various other + parameters - see the file gamedb.py for a full description. + + +Converting your game to a plugin +-------------------------------- +Plugins in the `data/plugins' directory will be loaded automatically at +program startup, so you can distribute your plugins separately. + +Converting a game to a plugin is completely trivial - just +move the Python source file to the `data/plugins' directory. + +If you want to contribute your game to the official PySol distribution +you must write a useable HTML documentation. Also, your variant should +have an interesting gameplay. + + +Contributing +------------ +Apart from contributing new games you can also help by improving the +interface - e.g. some fancy statistics dialogs would be very nice. +See the main README for more ideas. + + +Have fun, +Markus + +http://www.oberhumer.com/pysol + diff --git a/docs/pysol.6 b/docs/pysol.6 new file mode 100644 index 0000000000..ab150e1122 --- /dev/null +++ b/docs/pysol.6 @@ -0,0 +1,219 @@ +.\" Automatically generated by Pod::Man v1.34, Pod::Parser v1.13 +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sh \" Subsection heading +.br +.if t .Sp +.ne 5 +.PP +\fB\\$1\fR +.PP +.. +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. | will give a +.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to +.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C' +.\" expand to `' in nroff, nothing in troff, for use with C<>. +.tr \(*W-|\(bv\*(Tr +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +'br\} +.\" +.\" If the F register is turned on, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. nr % 0 +. rr F +.\} +.\" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.hy 0 +.if n .na +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "PYSOL 6" +.TH PYSOL 6 "20 Aug 2003" "Version 4.82" " " +.SH "NAME" +PySol \- a collection of more than 200 solitaire games +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +\&\fBpysol\fR [\fIfilename\fR] +.PP +The only option available is the file name of a saved game. +All other options are ignored \- PySol is fully +configurable within the game. +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +\&\fBPySol\fR is an exciting solitaire card game collection. Its features include +support for more than 200 distinct games, very nice look and feel, unlimited +undo & redo, load & save games, player statistics, hint system, +demo games, support for user written plug-ins, samples and background +music, integrated help browser and lots of documentation. +.PP +\&\fBPySol\fR comes with an integrated help browser. Just press after +the program has started. +.PP +\&\fBPySol\fR is written in 100% pure Python and runs out-of-the-box +on Unix (X11), Windows 95/98/ME/2000/NT/XP and Macintosh platforms. +.SH "FILES" +.IX Header "FILES" +\&\fBPySol\fR searches its data files in a number of directories including: +.PP +.Vb 7 +\& the current directory +\& /usr/share/pysol +\& /usr/lib/pysol +\& /usr/share/games/pysol +\& /usr/lib/games/pysol +\& /usr/games/share/pysol +\& /usr/games/lib/pysol +.Ve +.PP +Options are saved in \fB~/.pysol/options.dat\fR +.PP +Statistics and logs are saved in \fB~/.pysol/statistics.dat\fR +.PP +Additional plugins are searched in \fB~/.pysol/plugins/\fR +.PP +Additional cardsets are searched in \fB~/.pysol/cardset\-*/\fR +.PP +Additional table tiles are searched in \fB~/.pysol/tiles/\fR +.PP +Additional music songs are searched in \fB~/.pysol/music/\fR +.SH "ENVIRONMENT" +.IX Header "ENVIRONMENT" +\&\fB\s-1HOME\s0\fR is used for finding the users home directory. +.PP +\&\fB\s-1USER\s0\fR and \fB\s-1LOGNAME\s0\fR are used for getting the initial name of the player. +.PP +Additional cardsets are searched according to the path variable +\&\fB\s-1PYSOL_CARDSETS\s0\fR. You can specify multiple directories. +.PP +Additional table tiles are searched according to the path variable +\&\fB\s-1PYSOL_TILES\s0\fR. You can specify multiple directories. +.PP +Additional music songs are searched according to the path variable +\&\fB\s-1PYSOL_MUSIC\s0\fR. You can specify multiple directories. +.SH "DIAGNOSTICS" +.IX Header "DIAGNOSTICS" +Exit status is normally 0. +.SH "SEE ALSO" +.IX Header "SEE ALSO" +\&\fBpython\fR(1) +.PP +\&\fBace_solitaire\fR, \fBfreecell\fR, \fBgnome-freecell\fR, +\&\fBkabale\fR, \fBklondike\fR, \fBkpat\fR, \fBseahaven\fR, \fBsol\fR, +\&\fBspider\fR, \fBtksol\fR, \fBxfreecell\fR, \fBxpat2\fR, \fBxsol\fR +.SH "BUGS" +.IX Header "BUGS" +Please report all bugs immediately to the author. +.SH "AUTHOR" +.IX Header "AUTHOR" +Markus F.X.J. Oberhumer +.PP +http://www.oberhumer.com/pysol +.SH "COPYRIGHT" +.IX Header "COPYRIGHT" +PySol is Copyright (C) 1998, 1999, 2000, 2001, 2002 by Markus F.X.J. Oberhumer +.PP +All Rights Reserved. +.PP +PySol is distributed under the terms of the +\&\s-1GNU\s0 General Public License (\s-1GPL\s0) diff --git a/po/games.pot b/po/games.pot new file mode 100644 index 0000000000..f71d5e8f67 --- /dev/null +++ b/po/games.pot @@ -0,0 +1,3050 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PySol 0.0.1\n" +"POT-Creation-Date: Fri May 26 20:25:43 2006\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: ./scripts/all_games.py 0.1\n" + + +msgid " 3x3 Matrix" +msgstr "" + +msgid " 4x4 Matrix" +msgstr "" + +msgid " 5x5 Matrix" +msgstr "" + +msgid " 6x6 Matrix" +msgstr "" + +msgid " 7x7 Matrix" +msgstr "" + +msgid " 8x8 Matrix" +msgstr "" + +msgid " 9x9 Matrix" +msgstr "" + +msgid "10 x 8" +msgstr "" + +msgid "10x10 Matrix" +msgstr "" + +msgid "8 x 8" +msgstr "" + +msgid "Abacus" +msgstr "" + +msgid "Aces High" +msgstr "" + +msgid "Aces Up" +msgstr "" + +msgid "Aces Up 5" +msgstr "" + +msgid "Achtmal Acht" +msgstr "" + +msgid "Acme" +msgstr "" + +msgid "Adelaide" +msgstr "" + +msgid "Agnes Bernauer" +msgstr "" + +msgid "Agnes Sorel" +msgstr "" + +msgid "Akbar's Conquest" +msgstr "" + +msgid "Akbar's Triumph" +msgstr "" + +msgid "Alaska" +msgstr "" + +msgid "Algerian Patience" +msgstr "" + +msgid "Algerian Patience (3 decks)" +msgstr "" + +msgid "Alhambra" +msgstr "" + +msgid "All in a Row" +msgstr "" + +msgid "Altar" +msgstr "" + +msgid "Alternation" +msgstr "" + +msgid "Amazons" +msgstr "" + +msgid "American Toad" +msgstr "" + +msgid "Another Round" +msgstr "" + +msgid "Appachan's Waterfall" +msgstr "" + +msgid "Applegate" +msgstr "" + +msgid "Aqab's" +msgstr "" + +msgid "Arachnida" +msgstr "" + +msgid "Arena" +msgstr "" + +msgid "Arena 2" +msgstr "" + +msgid "Arizona" +msgstr "" + +msgid "Arrow" +msgstr "" + +msgid "Art Moderne" +msgstr "" + +msgid "Ashrafi" +msgstr "" + +msgid "Ashta Dikapala" +msgstr "" + +msgid "Ashwapati" +msgstr "" + +msgid "Auld Lang Syne" +msgstr "" + +msgid "Aunt Mary" +msgstr "" + +msgid "Australian Patience" +msgstr "" + +msgid "Baby Spiderette" +msgstr "" + +msgid "Backbone" +msgstr "" + +msgid "Backbone +" +msgstr "" + +msgid "Bad Seven" +msgstr "" + +msgid "Baker's Dozen" +msgstr "" + +msgid "Baker's Game" +msgstr "" + +msgid "Balance" +msgstr "" + +msgid "Balarama" +msgstr "" + +msgid "Bastion" +msgstr "" + +msgid "Bat" +msgstr "" + +msgid "Bath" +msgstr "" + +msgid "Batsford" +msgstr "" + +msgid "Bavarian Patience" +msgstr "" + +msgid "Beak and Flipper" +msgstr "" + +msgid "Beatle" +msgstr "" + +msgid "Beleaguered Castle" +msgstr "" + +msgid "Belvedere" +msgstr "" + +msgid "Betsy Ross" +msgstr "" + +msgid "Big Easy" +msgstr "" + +msgid "Big Flying Dragon" +msgstr "" + +msgid "Big Forty" +msgstr "" + +msgid "Big Harp" +msgstr "" + +msgid "Big Hole" +msgstr "" + +msgid "Big Mountain" +msgstr "" + +msgid "Big Spider" +msgstr "" + +msgid "Big Spider (1 suit)" +msgstr "" + +msgid "Big Spider (2 suits)" +msgstr "" + +msgid "Big Sumo" +msgstr "" + +msgid "Bim Bom" +msgstr "" + +msgid "Bisley" +msgstr "" + +msgid "Bits n Bytes" +msgstr "" + +msgid "Bizarre" +msgstr "" + +msgid "Black Hole" +msgstr "" + +msgid "Black Widow" +msgstr "" + +msgid "Blind Alleys" +msgstr "" + +msgid "Blockade" +msgstr "" + +msgid "Blondes and Brunettes" +msgstr "" + +msgid "Blue Moon" +msgstr "" + +msgid "Boar" +msgstr "" + +msgid "Boat" +msgstr "" + +msgid "Boudoir" +msgstr "" + +msgid "Box Fan" +msgstr "" + +msgid "Box Kite" +msgstr "" + +msgid "Braid" +msgstr "" + +msgid "Bridesmaids" +msgstr "" + +msgid "Bridge" +msgstr "" + +msgid "Bridge 2" +msgstr "" + +msgid "Bridget's Game" +msgstr "" + +msgid "Bridget's Game Doubled" +msgstr "" + +msgid "Brigade" +msgstr "" + +msgid "Bristol" +msgstr "" + +msgid "British Constitution" +msgstr "" + +msgid "British Square" +msgstr "" + +msgid "Brunswick" +msgstr "" + +msgid "Buffalo Bill" +msgstr "" + +msgid "Bug" +msgstr "" + +msgid "Busy Aces" +msgstr "" + +msgid "Butterfly" +msgstr "" + +msgid "Butterfly 2" +msgstr "" + +msgid "Calculation" +msgstr "" + +msgid "Camelot" +msgstr "" + +msgid "Canfield" +msgstr "" + +msgid "Canister" +msgstr "" + +msgid "Capricieuse" +msgstr "" + +msgid "Captive Queens" +msgstr "" + +msgid "Carlton" +msgstr "" + +msgid "Carpet" +msgstr "" + +msgid "Carre Napoleon" +msgstr "" + +msgid "Carthage" +msgstr "" + +msgid "Casino Klondike" +msgstr "" + +msgid "Castle" +msgstr "" + +msgid "Castle of Indolence" +msgstr "" + +msgid "Castles in Spain" +msgstr "" + +msgid "Cat and Mouse" +msgstr "" + +msgid "Cat's Tail" +msgstr "" + +msgid "Cavalier" +msgstr "" + +msgid "Cell 11" +msgstr "" + +msgid "Ceremonial" +msgstr "" + +msgid "Challenge FreeCell" +msgstr "" + +msgid "Chamberlain" +msgstr "" + +msgid "Chameleon" +msgstr "" + +msgid "Checkered" +msgstr "" + +msgid "Chelicera" +msgstr "" + +msgid "Chequers" +msgstr "" + +msgid "Cherry Bomb" +msgstr "" + +msgid "ChessMania" +msgstr "" + +msgid "Chessboard" +msgstr "" + +msgid "Chinese Discipline" +msgstr "" + +msgid "Chinese Solitaire" +msgstr "" + +msgid "Chip" +msgstr "" + +msgid "Cicely" +msgstr "" + +msgid "Citadel" +msgstr "" + +msgid "Clink" +msgstr "" + +msgid "Clover Leaf" +msgstr "" + +msgid "Cluitjar's Lair" +msgstr "" + +msgid "Cockroach" +msgstr "" + +msgid "Colorado" +msgstr "" + +msgid "Columns" +msgstr "" + +msgid "Concentration" +msgstr "" + +msgid "Cone" +msgstr "" + +msgid "Congress" +msgstr "" + +msgid "Contradance" +msgstr "" + +msgid "Convolution" +msgstr "" + +msgid "Corkscrew" +msgstr "" + +msgid "Corners" +msgstr "" + +msgid "Corona" +msgstr "" + +msgid "Courtyard" +msgstr "" + +msgid "Cross" +msgstr "" + +msgid "Crown" +msgstr "" + +msgid "Cruel" +msgstr "" + +msgid "Cupido's Heart" +msgstr "" + +msgid "Cupola" +msgstr "" + +msgid "Curds and Whey" +msgstr "" + +msgid "Danda" +msgstr "" + +msgid "Dashavatara" +msgstr "" + +msgid "Dashavatara Circles" +msgstr "" + +msgid "Dead King Golf" +msgstr "" + +msgid "Deep" +msgstr "" + +msgid "Deep Well" +msgstr "" + +msgid "Der Katzenschwanz" +msgstr "" + +msgid "Der Zopf" +msgstr "" + +msgid "Der freie Napoleon" +msgstr "" + +msgid "Der kleine Napoleon" +msgstr "" + +msgid "Der lange Zopf" +msgstr "" + +msgid "Der letzte Monarch" +msgstr "" + +msgid "Deuces" +msgstr "" + +msgid "Dhanpati" +msgstr "" + +msgid "Diamond" +msgstr "" + +msgid "Die Bildgallerie" +msgstr "" + +msgid "Die Königsbergerin" +msgstr "" + +msgid "Die Russische" +msgstr "" + +msgid "Die Schlange" +msgstr "" + +msgid "Die böse Sieben" +msgstr "" + +msgid "Die große Harfe" +msgstr "" + +msgid "Die kleine Harfe" +msgstr "" + +msgid "Diplomat" +msgstr "" + +msgid "Dog" +msgstr "" + +msgid "Dojouji's Game" +msgstr "" + +msgid "Dojouji's Game Doubled" +msgstr "" + +msgid "Double Bisley" +msgstr "" + +msgid "Double Canfield" +msgstr "" + +msgid "Double Cockroach" +msgstr "" + +msgid "Double Dot" +msgstr "" + +msgid "Double Drawbridge" +msgstr "" + +msgid "Double Easthaven" +msgstr "" + +msgid "Double FreeCell" +msgstr "" + +msgid "Double Grasshopper" +msgstr "" + +msgid "Double Klondike" +msgstr "" + +msgid "Double Klondike by Threes" +msgstr "" + +msgid "Double Mahjongg Big Castle" +msgstr "" + +msgid "Double Mahjongg Big Flying Dragon" +msgstr "" + +msgid "Double Mahjongg Eight Squares" +msgstr "" + +msgid "Double Mahjongg Faro" +msgstr "" + +msgid "Double Mahjongg Roost" +msgstr "" + +msgid "Double Mahjongg Sphere" +msgstr "" + +msgid "Double Mahjongg Twin Picks" +msgstr "" + +msgid "Double Mahjongg Two Squares" +msgstr "" + +msgid "Double Rail" +msgstr "" + +msgid "Double Samuri" +msgstr "" + +msgid "Double Your Fun" +msgstr "" + +msgid "Double Yukon" +msgstr "" + +msgid "Doublets" +msgstr "" + +msgid "Dover" +msgstr "" + +msgid "Dragon" +msgstr "" + +msgid "Dragon 2" +msgstr "" + +msgid "Drawbridge" +msgstr "" + +msgid "Dress Parade" +msgstr "" + +msgid "Drivel" +msgstr "" + +msgid "Dude" +msgstr "" + +msgid "Duke" +msgstr "" + +msgid "Dumfries" +msgstr "" + +msgid "Eagle Wing" +msgstr "" + +msgid "Eastcliff" +msgstr "" + +msgid "Easthaven" +msgstr "" + +msgid "Easy Supreme" +msgstr "" + +msgid "Easy x One" +msgstr "" + +msgid "Egyptian Solitaire" +msgstr "" + +msgid "Eiffel Tower" +msgstr "" + +msgid "Eight Legions" +msgstr "" + +msgid "Eight Off" +msgstr "" + +msgid "Eight Squares" +msgstr "" + +msgid "Eight Times Eight" +msgstr "" + +msgid "Elevator" +msgstr "" + +msgid "Emperor" +msgstr "" + +msgid "Empty Pyramids" +msgstr "" + +msgid "Enterprise" +msgstr "" + +msgid "Escalator" +msgstr "" + +msgid "Eularia" +msgstr "" + +msgid "Excuse" +msgstr "" + +msgid "Eye" +msgstr "" + +msgid "F-15 Eagle" +msgstr "" + +msgid "Fair Lucy" +msgstr "" + +msgid "Fairest" +msgstr "" + +msgid "Falling Star" +msgstr "" + +msgid "Fan" +msgstr "" + +msgid "Farandole" +msgstr "" + +msgid "Faro" +msgstr "" + +msgid "Fastness" +msgstr "" + +msgid "Fatimeh's Game" +msgstr "" + +msgid "Fatimeh's Game Relaxed" +msgstr "" + +msgid "Fifteen Puzzle" +msgstr "" + +msgid "Fifteen plus" +msgstr "" + +msgid "Firecracker" +msgstr "" + +msgid "First Law" +msgstr "" + +msgid "Fish" +msgstr "" + +msgid "Fish face" +msgstr "" + +msgid "Five Aces" +msgstr "" + +msgid "Five Pyramids" +msgstr "" + +msgid "Floating City" +msgstr "" + +msgid "Flower Arrangement" +msgstr "" + +msgid "Flower Clock" +msgstr "" + +msgid "Flower Garden" +msgstr "" + +msgid "Flowers" +msgstr "" + +msgid "Fly" +msgstr "" + +msgid "Flying Dragon" +msgstr "" + +msgid "ForeCell" +msgstr "" + +msgid "Fort" +msgstr "" + +msgid "Fortress" +msgstr "" + +msgid "Fortress Towers" +msgstr "" + +msgid "Fortune's Favor" +msgstr "" + +msgid "Fortunes" +msgstr "" + +msgid "Forty Thieves" +msgstr "" + +msgid "Forty and Eight" +msgstr "" + +msgid "Four Colours" +msgstr "" + +msgid "Four Kings" +msgstr "" + +msgid "Four Leaf Clovers" +msgstr "" + +msgid "Four Seasons" +msgstr "" + +msgid "Four Stacks" +msgstr "" + +msgid "Four Winds" +msgstr "" + +msgid "Fourteen" +msgstr "" + +msgid "Fred's Spider" +msgstr "" + +msgid "Fred's Spider (3 decks)" +msgstr "" + +msgid "Free Fan" +msgstr "" + +msgid "Free Napoleon" +msgstr "" + +msgid "FreeCell" +msgstr "" + +msgid "Frog" +msgstr "" + +msgid "Full Vision" +msgstr "" + +msgid "Full Vision 2" +msgstr "" + +msgid "Future" +msgstr "" + +msgid "Gajapati" +msgstr "" + +msgid "Gaji" +msgstr "" + +msgid "Galary" +msgstr "" + +msgid "Galloway" +msgstr "" + +msgid "Gaps" +msgstr "" + +msgid "Garden" +msgstr "" + +msgid "Gargantua" +msgstr "" + +msgid "Garhpati" +msgstr "" + +msgid "Gate" +msgstr "" + +msgid "Gayle's" +msgstr "" + +msgid "General's Patience" +msgstr "" + +msgid "Genesis" +msgstr "" + +msgid "Genesis +" +msgstr "" + +msgid "German Patience" +msgstr "" + +msgid "Ghulam" +msgstr "" + +msgid "Giant" +msgstr "" + +msgid "Glade" +msgstr "" + +msgid "Glenwood" +msgstr "" + +msgid "Gloaming" +msgstr "" + +msgid "Gloria" +msgstr "" + +msgid "Gnat" +msgstr "" + +msgid "Golf" +msgstr "" + +msgid "Good Measure" +msgstr "" + +msgid "Grampus" +msgstr "" + +msgid "Grandfather" +msgstr "" + +msgid "Grandfather's Clock" +msgstr "" + +msgid "Grandmother's Game" +msgstr "" + +msgid "Grasshopper" +msgstr "" + +msgid "Great Wall" +msgstr "" + +msgid "Great Wheel" +msgstr "" + +msgid "Greater Queue" +msgstr "" + +msgid "Griffon" +msgstr "" + +msgid "Ground for a Divorce" +msgstr "" + +msgid "Ground for a Divorce (3 decks)" +msgstr "" + +msgid "Ground for a Divorce (4 decks)" +msgstr "" + +msgid "Gypsy" +msgstr "" + +msgid "H for Haga" +msgstr "" + +msgid "Half Mahjongg Happy New Year" +msgstr "" + +msgid "Half Mahjongg Smile" +msgstr "" + +msgid "Half Mahjongg Wall" +msgstr "" + +msgid "Hanoi Puzzle 4" +msgstr "" + +msgid "Hanoi Puzzle 5" +msgstr "" + +msgid "Hanoi Puzzle 6" +msgstr "" + +msgid "Happy New Year" +msgstr "" + +msgid "Hare" +msgstr "" + +msgid "Hayagriva" +msgstr "" + +msgid "Haystack" +msgstr "" + +msgid "Heads and Tails" +msgstr "" + +msgid "Helios" +msgstr "" + +msgid "Hex A Klon" +msgstr "" + +msgid "Hex A Klon by Threes" +msgstr "" + +msgid "Hex Labyrinth" +msgstr "" + +msgid "Hidden Passages" +msgstr "" + +msgid "Hidden Words" +msgstr "" + +msgid "High and Low" +msgstr "" + +msgid "Hiranyaksha" +msgstr "" + +msgid "Hopscotch" +msgstr "" + +msgid "Horse" +msgstr "" + +msgid "House in the Wood" +msgstr "" + +msgid "House on the Hill" +msgstr "" + +msgid "Hovercraft" +msgstr "" + +msgid "Hurdles" +msgstr "" + +msgid "Hurricane" +msgstr "" + +msgid "Idiot's Delight" +msgstr "" + +msgid "Idle Aces" +msgstr "" + +msgid "IloveU" +msgstr "" + +msgid "Imperial Trumps" +msgstr "" + +msgid "Inazuma" +msgstr "" + +msgid "Inca" +msgstr "" + +msgid "Indian" +msgstr "" + +msgid "Indian Patience" +msgstr "" + +msgid "Inner Circle" +msgstr "" + +msgid "Intelligence" +msgstr "" + +msgid "Intelligence +" +msgstr "" + +msgid "Interregnum" +msgstr "" + +msgid "Iris" +msgstr "" + +msgid "Irmgard" +msgstr "" + +msgid "JPs" +msgstr "" + +msgid "Jamestown" +msgstr "" + +msgid "Jane" +msgstr "" + +msgid "Japan" +msgstr "" + +msgid "Japanese Garden" +msgstr "" + +msgid "Japanese Garden II" +msgstr "" + +msgid "Japanese Garden III" +msgstr "" + +msgid "Joker" +msgstr "" + +msgid "Josephine" +msgstr "" + +msgid "Journey to Cuddapah" +msgstr "" + +msgid "Jumbo" +msgstr "" + +msgid "Jungle" +msgstr "" + +msgid "Just For Fun" +msgstr "" + +msgid "K for Kyodai" +msgstr "" + +msgid "Kali's Game" +msgstr "" + +msgid "Kali's Game Doubled" +msgstr "" + +msgid "Kali's Game Relaxed" +msgstr "" + +msgid "Kansas" +msgstr "" + +msgid "Katrina's Game" +msgstr "" + +msgid "Katrina's Game Doubled" +msgstr "" + +msgid "Katrina's Game Relaxed" +msgstr "" + +msgid "Khadga" +msgstr "" + +msgid "King Albert" +msgstr "" + +msgid "King Only Baker's Game" +msgstr "" + +msgid "King Only Hex A Klon" +msgstr "" + +msgid "Kingdom" +msgstr "" + +msgid "Kings" +msgstr "" + +msgid "Kingsdown Eights" +msgstr "" + +msgid "Klondike" +msgstr "" + +msgid "Klondike Plus 16" +msgstr "" + +msgid "Klondike by Threes" +msgstr "" + +msgid "Km" +msgstr "" + +msgid "Krebs" +msgstr "" + +msgid "Kujaku" +msgstr "" + +msgid "Kumo" +msgstr "" + +msgid "Kurma" +msgstr "" + +msgid "Kyodai 14" +msgstr "" + +msgid "Kyodai 17" +msgstr "" + +msgid "Kyodai 18" +msgstr "" + +msgid "Kyodai 20" +msgstr "" + +msgid "Kyodai 23" +msgstr "" + +msgid "Kyodai 24" +msgstr "" + +msgid "Kyodai 25" +msgstr "" + +msgid "Kyodai 26" +msgstr "" + +msgid "Kyodai 27" +msgstr "" + +msgid "Kyodai 28" +msgstr "" + +msgid "Kyodai 41" +msgstr "" + +msgid "Kyodai 42" +msgstr "" + +msgid "La Belle Lucie" +msgstr "" + +msgid "La Nivernaise" +msgstr "" + +msgid "Labyrinth" +msgstr "" + +msgid "Lady Betty" +msgstr "" + +msgid "Lady Palk" +msgstr "" + +msgid "Lady of the Manor" +msgstr "" + +msgid "Lanes" +msgstr "" + +msgid "Lara's Game" +msgstr "" + +msgid "Lara's Game Doubled" +msgstr "" + +msgid "Lara's Game Relaxed" +msgstr "" + +msgid "Lattice" +msgstr "" + +msgid "Le Cadran" +msgstr "" + +msgid "Le Grande Teton" +msgstr "" + +msgid "Leo" +msgstr "" + +msgid "Lesser Queue" +msgstr "" + +msgid "Lexington Harp" +msgstr "" + +msgid "Lily" +msgstr "" + +msgid "Limited" +msgstr "" + +msgid "Lion" +msgstr "" + +msgid "Little Billie" +msgstr "" + +msgid "Little Easy" +msgstr "" + +msgid "Little Forty" +msgstr "" + +msgid "Little Gate" +msgstr "" + +msgid "Long Braid" +msgstr "" + +msgid "Long Journey to Cuddapah" +msgstr "" + +msgid "Loose Ends" +msgstr "" + +msgid "Lost " +msgstr "" + +msgid "Lucas" +msgstr "" + +msgid "Mage's Game" +msgstr "" + +msgid "Mahjongg Altar" +msgstr "" + +msgid "Mahjongg Another Round" +msgstr "" + +msgid "Mahjongg Aqab's" +msgstr "" + +msgid "Mahjongg Arena" +msgstr "" + +msgid "Mahjongg Arena 2" +msgstr "" + +msgid "Mahjongg Arrow" +msgstr "" + +msgid "Mahjongg Art Moderne" +msgstr "" + +msgid "Mahjongg Balance" +msgstr "" + +msgid "Mahjongg Bat" +msgstr "" + +msgid "Mahjongg Beatle" +msgstr "" + +msgid "Mahjongg Big Hole" +msgstr "" + +msgid "Mahjongg Big Mountain" +msgstr "" + +msgid "Mahjongg Bizarre" +msgstr "" + +msgid "Mahjongg Boar" +msgstr "" + +msgid "Mahjongg Boat" +msgstr "" + +msgid "Mahjongg Bridge" +msgstr "" + +msgid "Mahjongg Bridge 2" +msgstr "" + +msgid "Mahjongg Bug" +msgstr "" + +msgid "Mahjongg Butterfly" +msgstr "" + +msgid "Mahjongg Butterfly 2" +msgstr "" + +msgid "Mahjongg Castle" +msgstr "" + +msgid "Mahjongg Cat and Mouse" +msgstr "" + +msgid "Mahjongg Ceremonial" +msgstr "" + +msgid "Mahjongg Checkered" +msgstr "" + +msgid "Mahjongg ChessMania" +msgstr "" + +msgid "Mahjongg Chip" +msgstr "" + +msgid "Mahjongg Columns" +msgstr "" + +msgid "Mahjongg Cross" +msgstr "" + +msgid "Mahjongg Crown" +msgstr "" + +msgid "Mahjongg Cupido's Heart" +msgstr "" + +msgid "Mahjongg Cupola" +msgstr "" + +msgid "Mahjongg Deep Well" +msgstr "" + +msgid "Mahjongg Diamond" +msgstr "" + +msgid "Mahjongg Dog" +msgstr "" + +msgid "Mahjongg Dragon" +msgstr "" + +msgid "Mahjongg Dragon 2" +msgstr "" + +msgid "Mahjongg Dude" +msgstr "" + +msgid "Mahjongg Empty Pyramids" +msgstr "" + +msgid "Mahjongg Enterprise" +msgstr "" + +msgid "Mahjongg Eye" +msgstr "" + +msgid "Mahjongg F-15 Eagle" +msgstr "" + +msgid "Mahjongg Farandole" +msgstr "" + +msgid "Mahjongg Fish" +msgstr "" + +msgid "Mahjongg Fish face" +msgstr "" + +msgid "Mahjongg Five Pyramids" +msgstr "" + +msgid "Mahjongg Floating City" +msgstr "" + +msgid "Mahjongg Flowers" +msgstr "" + +msgid "Mahjongg Flying Dragon" +msgstr "" + +msgid "Mahjongg Fortress Towers" +msgstr "" + +msgid "Mahjongg Full Vision" +msgstr "" + +msgid "Mahjongg Full Vision 2" +msgstr "" + +msgid "Mahjongg Future" +msgstr "" + +msgid "Mahjongg Garden" +msgstr "" + +msgid "Mahjongg Gayle's" +msgstr "" + +msgid "Mahjongg Glade" +msgstr "" + +msgid "Mahjongg H for Haga" +msgstr "" + +msgid "Mahjongg Hare" +msgstr "" + +msgid "Mahjongg Helios" +msgstr "" + +msgid "Mahjongg Hidden Words" +msgstr "" + +msgid "Mahjongg High and Low" +msgstr "" + +msgid "Mahjongg Horse" +msgstr "" + +msgid "Mahjongg Hovercraft" +msgstr "" + +msgid "Mahjongg Hurdles" +msgstr "" + +msgid "Mahjongg Hurricane" +msgstr "" + +msgid "Mahjongg IloveU" +msgstr "" + +msgid "Mahjongg Inazuma" +msgstr "" + +msgid "Mahjongg Inca" +msgstr "" + +msgid "Mahjongg Inner Circle" +msgstr "" + +msgid "Mahjongg JPs" +msgstr "" + +msgid "Mahjongg Japan" +msgstr "" + +msgid "Mahjongg Joker" +msgstr "" + +msgid "Mahjongg K for Kyodai" +msgstr "" + +msgid "Mahjongg Km" +msgstr "" + +msgid "Mahjongg Krebs" +msgstr "" + +msgid "Mahjongg Kujaku" +msgstr "" + +msgid "Mahjongg Kumo" +msgstr "" + +msgid "Mahjongg Kyodai 14" +msgstr "" + +msgid "Mahjongg Kyodai 17" +msgstr "" + +msgid "Mahjongg Kyodai 18" +msgstr "" + +msgid "Mahjongg Kyodai 20" +msgstr "" + +msgid "Mahjongg Kyodai 23" +msgstr "" + +msgid "Mahjongg Kyodai 24" +msgstr "" + +msgid "Mahjongg Kyodai 25" +msgstr "" + +msgid "Mahjongg Kyodai 26" +msgstr "" + +msgid "Mahjongg Kyodai 27" +msgstr "" + +msgid "Mahjongg Kyodai 28" +msgstr "" + +msgid "Mahjongg Kyodai 41" +msgstr "" + +msgid "Mahjongg Kyodai 42" +msgstr "" + +msgid "Mahjongg Labyrinth" +msgstr "" + +msgid "Mahjongg Lattice" +msgstr "" + +msgid "Mahjongg Leo" +msgstr "" + +msgid "Mahjongg Lion" +msgstr "" + +msgid "Mahjongg Loose Ends" +msgstr "" + +msgid "Mahjongg Lost " +msgstr "" + +msgid "Mahjongg Maya" +msgstr "" + +msgid "Mahjongg Mesh" +msgstr "" + +msgid "Mahjongg Mini Traditional" +msgstr "" + +msgid "Mahjongg Mini-Layout" +msgstr "" + +msgid "Mahjongg Mission Impossible" +msgstr "" + +msgid "Mahjongg Monkey" +msgstr "" + +msgid "Mahjongg Moth" +msgstr "" + +msgid "Mahjongg Multi X" +msgstr "" + +msgid "Mahjongg N for Namida" +msgstr "" + +msgid "Mahjongg New Layout" +msgstr "" + +msgid "Mahjongg Okie's Nitemare" +msgstr "" + +msgid "Mahjongg Orbital" +msgstr "" + +msgid "Mahjongg Order" +msgstr "" + +msgid "Mahjongg Owl" +msgstr "" + +msgid "Mahjongg Ox" +msgstr "" + +msgid "Mahjongg Pantheon" +msgstr "" + +msgid "Mahjongg Papillon" +msgstr "" + +msgid "Mahjongg Pattern" +msgstr "" + +msgid "Mahjongg Portal" +msgstr "" + +msgid "Mahjongg Pyramid 1" +msgstr "" + +msgid "Mahjongg Pyramid 2" +msgstr "" + +msgid "Mahjongg Quad" +msgstr "" + +msgid "Mahjongg Ram" +msgstr "" + +msgid "Mahjongg Rat" +msgstr "" + +msgid "Mahjongg Rectangle" +msgstr "" + +msgid "Mahjongg Reindeer" +msgstr "" + +msgid "Mahjongg Rings" +msgstr "" + +msgid "Mahjongg River Bridge" +msgstr "" + +msgid "Mahjongg Rocket" +msgstr "" + +msgid "Mahjongg Roman Arena" +msgstr "" + +msgid "Mahjongg Rooster" +msgstr "" + +msgid "Mahjongg Rugby" +msgstr "" + +msgid "Mahjongg Scorpion" +msgstr "" + +msgid "Mahjongg Screw Up" +msgstr "" + +msgid "Mahjongg Seven" +msgstr "" + +msgid "Mahjongg Seven Pyramids" +msgstr "" + +msgid "Mahjongg Shapeshifter" +msgstr "" + +msgid "Mahjongg Shield" +msgstr "" + +msgid "Mahjongg Siam" +msgstr "" + +msgid "Mahjongg Snake" +msgstr "" + +msgid "Mahjongg Space Bridge" +msgstr "" + +msgid "Mahjongg Space Shuttle" +msgstr "" + +msgid "Mahjongg Square" +msgstr "" + +msgid "Mahjongg Squares" +msgstr "" + +msgid "Mahjongg Squaring" +msgstr "" + +msgid "Mahjongg Stage 1" +msgstr "" + +msgid "Mahjongg Stage 2" +msgstr "" + +msgid "Mahjongg Stairs" +msgstr "" + +msgid "Mahjongg Stairs 2" +msgstr "" + +msgid "Mahjongg Stairs 3" +msgstr "" + +msgid "Mahjongg Star Ship" +msgstr "" + +msgid "Mahjongg Stargate" +msgstr "" + +msgid "Mahjongg Step Pyramid" +msgstr "" + +msgid "Mahjongg Stonehenge" +msgstr "" + +msgid "Mahjongg Sukis" +msgstr "" + +msgid "Mahjongg SunMoon" +msgstr "" + +msgid "Mahjongg Taipei" +msgstr "" + +msgid "Mahjongg Temple" +msgstr "" + +msgid "Mahjongg Temple 1" +msgstr "" + +msgid "Mahjongg Temple 2" +msgstr "" + +msgid "Mahjongg The Door" +msgstr "" + +msgid "Mahjongg The Great Wall" +msgstr "" + +msgid "Mahjongg Theater" +msgstr "" + +msgid "Mahjongg Tiger" +msgstr "" + +msgid "Mahjongg Tile Fighter" +msgstr "" + +msgid "Mahjongg Tilepiles" +msgstr "" + +msgid "Mahjongg Time Tunnel" +msgstr "" + +msgid "Mahjongg Tomb" +msgstr "" + +msgid "Mahjongg Totally Random-Made" +msgstr "" + +msgid "Mahjongg Traditional Reviewed" +msgstr "" + +msgid "Mahjongg Tree of Life" +msgstr "" + +msgid "Mahjongg Trika" +msgstr "" + +msgid "Mahjongg Twin" +msgstr "" + +msgid "Mahjongg Twin Temples" +msgstr "" + +msgid "Mahjongg Two Domes" +msgstr "" + +msgid "Mahjongg Vagues" +msgstr "" + +msgid "Mahjongg Vi" +msgstr "" + +msgid "Mahjongg Victory Arrow" +msgstr "" + +msgid "Mahjongg Wavelets" +msgstr "" + +msgid "Mahjongg Wedges" +msgstr "" + +msgid "Mahjongg Well" +msgstr "" + +msgid "Mahjongg Well2" +msgstr "" + +msgid "Mahjongg Whatever" +msgstr "" + +msgid "Mahjongg Win" +msgstr "" + +msgid "Mahjongg X-Files" +msgstr "" + +msgid "Mahjongg X-Shape" +msgstr "" + +msgid "Mahjongg Yummy" +msgstr "" + +msgid "Makara" +msgstr "" + +msgid "Mancunian" +msgstr "" + +msgid "Maria" +msgstr "" + +msgid "Maria Luisa" +msgstr "" + +msgid "Martha" +msgstr "" + +msgid "Matriarchy" +msgstr "" + +msgid "Matrimony" +msgstr "" + +msgid "MatsuKiri" +msgstr "" + +msgid "MatsuKiri Strict" +msgstr "" + +msgid "Matsya" +msgstr "" + +msgid "Maya" +msgstr "" + +msgid "Maze" +msgstr "" + +msgid "Memory 24" +msgstr "" + +msgid "Memory 30" +msgstr "" + +msgid "Memory 40" +msgstr "" + +msgid "Merlin's Meander" +msgstr "" + +msgid "Mesh" +msgstr "" + +msgid "Midnight Oil" +msgstr "" + +msgid "Midshipman" +msgstr "" + +msgid "Milligan Cell" +msgstr "" + +msgid "Milligan Harp" +msgstr "" + +msgid "Minerva" +msgstr "" + +msgid "Mini Traditional" +msgstr "" + +msgid "Mini-Layout" +msgstr "" + +msgid "Miss Milligan" +msgstr "" + +msgid "Miss Muffet" +msgstr "" + +msgid "Mission Impossible" +msgstr "" + +msgid "Mississippi" +msgstr "" + +msgid "Mod-3" +msgstr "" + +msgid "Monaco" +msgstr "" + +msgid "Monkey" +msgstr "" + +msgid "Montana" +msgstr "" + +msgid "Monte Carlo" +msgstr "" + +msgid "Moonlight" +msgstr "" + +msgid "Moosehide" +msgstr "" + +msgid "Morehead" +msgstr "" + +msgid "Moth" +msgstr "" + +msgid "Mount Olympus" +msgstr "" + +msgid "Mrs. Mop" +msgstr "" + +msgid "Mughal Circles" +msgstr "" + +msgid "Multi X" +msgstr "" + +msgid "Mumbai" +msgstr "" + +msgid "Munger" +msgstr "" + +msgid "Musical Patience" +msgstr "" + +msgid "N for Namida" +msgstr "" + +msgid "Napoleon" +msgstr "" + +msgid "Napoleon at St.Helena" +msgstr "" + +msgid "Napoleon's Exile" +msgstr "" + +msgid "Napoleon's Favorite" +msgstr "" + +msgid "Napoleon's Flank" +msgstr "" + +msgid "Napoleon's Square" +msgstr "" + +msgid "Napoleon's Tomb" +msgstr "" + +msgid "Narasimha" +msgstr "" + +msgid "Narpati" +msgstr "" + +msgid "Nasty" +msgstr "" + +msgid "Nationale" +msgstr "" + +msgid "Needle" +msgstr "" + +msgid "Neighbour" +msgstr "" + +msgid "Nestor" +msgstr "" + +msgid "New British Constitution" +msgstr "" + +msgid "New Layout" +msgstr "" + +msgid "New York" +msgstr "" + +msgid "Nomad" +msgstr "" + +msgid "Nordic" +msgstr "" + +msgid "Northwest Territory" +msgstr "" + +msgid "Number Ten" +msgstr "" + +msgid "Numerica" +msgstr "" + +msgid "Octagon" +msgstr "" + +msgid "Octave" +msgstr "" + +msgid "Odd and Even" +msgstr "" + +msgid "Odessa" +msgstr "" + +msgid "Okie's Nitemare" +msgstr "" + +msgid "Old Mole" +msgstr "" + +msgid "Oonsoo" +msgstr "" + +msgid "Oonsoo Open" +msgstr "" + +msgid "Oonsoo Strict" +msgstr "" + +msgid "Oonsoo Times Two" +msgstr "" + +msgid "Oonsoo Too" +msgstr "" + +msgid "Open Jumbo" +msgstr "" + +msgid "Open Peek" +msgstr "" + +msgid "Open Spider" +msgstr "" + +msgid "Opus" +msgstr "" + +msgid "Orbital" +msgstr "" + +msgid "Order" +msgstr "" + +msgid "Osmosis" +msgstr "" + +msgid "Owl" +msgstr "" + +msgid "Ox" +msgstr "" + +msgid "Pagat" +msgstr "" + +msgid "Pagoda" +msgstr "" + +msgid "Panopticon" +msgstr "" + +msgid "Pantheon" +msgstr "" + +msgid "Papillon" +msgstr "" + +msgid "Parallels" +msgstr "" + +msgid "Parashurama" +msgstr "" + +msgid "Pas Seul" +msgstr "" + +msgid "Pas de Deux" +msgstr "" + +msgid "Patriarchs" +msgstr "" + +msgid "Pattern" +msgstr "" + +msgid "Paulownia" +msgstr "" + +msgid "Peek" +msgstr "" + +msgid "Pegged" +msgstr "" + +msgid "Pegged 6x6" +msgstr "" + +msgid "Pegged 7x7" +msgstr "" + +msgid "Pegged Cross 1" +msgstr "" + +msgid "Pegged Cross 2" +msgstr "" + +msgid "Pegged Triangle 1" +msgstr "" + +msgid "Pegged Triangle 2" +msgstr "" + +msgid "Penguin" +msgstr "" + +msgid "Peony" +msgstr "" + +msgid "Perpetual Motion" +msgstr "" + +msgid "Perseverance" +msgstr "" + +msgid "Phoenix" +msgstr "" + +msgid "Picture Gallery" +msgstr "" + +msgid "Pigtail" +msgstr "" + +msgid "PileOn" +msgstr "" + +msgid "Pine" +msgstr "" + +msgid "Pitchfork" +msgstr "" + +msgid "Plait" +msgstr "" + +msgid "Plus Belle" +msgstr "" + +msgid "Poker Shuffle" +msgstr "" + +msgid "Poker Square" +msgstr "" + +msgid "Ponytail" +msgstr "" + +msgid "Portal" +msgstr "" + +msgid "Portuguese Solitaire" +msgstr "" + +msgid "Progression" +msgstr "" + +msgid "Provisions" +msgstr "" + +msgid "Push Pin" +msgstr "" + +msgid "Puss in the Corner" +msgstr "" + +msgid "Pyramid" +msgstr "" + +msgid "Pyramid 1" +msgstr "" + +msgid "Pyramid 2" +msgstr "" + +msgid "Pyramid Golf" +msgstr "" + +msgid "Q.C." +msgstr "" + +msgid "Quad" +msgstr "" + +msgid "Quadrangle" +msgstr "" + +msgid "Quadruple Alliance" +msgstr "" + +msgid "Queen of Italy" +msgstr "" + +msgid "Queenie" +msgstr "" + +msgid "Quilt" +msgstr "" + +msgid "Rachel" +msgstr "" + +msgid "Raglan" +msgstr "" + +msgid "Rainbow" +msgstr "" + +msgid "Rainfall" +msgstr "" + +msgid "Ram" +msgstr "" + +msgid "Rambling" +msgstr "" + +msgid "Rangoon" +msgstr "" + +msgid "Rank and File" +msgstr "" + +msgid "Rat" +msgstr "" + +msgid "Raw Prawn" +msgstr "" + +msgid "Rectangle" +msgstr "" + +msgid "Red Moon" +msgstr "" + +msgid "Red and Black" +msgstr "" + +msgid "Reindeer" +msgstr "" + +msgid "Relax" +msgstr "" + +msgid "Relaxed FreeCell" +msgstr "" + +msgid "Relaxed Golf" +msgstr "" + +msgid "Relaxed Pyramid" +msgstr "" + +msgid "Relaxed Seahaven Towers" +msgstr "" + +msgid "Relaxed Spider" +msgstr "" + +msgid "Repair" +msgstr "" + +msgid "Retinue" +msgstr "" + +msgid "Rings" +msgstr "" + +msgid "Ripple Fan" +msgstr "" + +msgid "Rittenhouse" +msgstr "" + +msgid "River Bridge" +msgstr "" + +msgid "Robert" +msgstr "" + +msgid "Robin" +msgstr "" + +msgid "Rock Hopper" +msgstr "" + +msgid "Rock Hopper 6x6" +msgstr "" + +msgid "Rock Hopper 7x7" +msgstr "" + +msgid "Rock Hopper Cross 1" +msgstr "" + +msgid "Rock Hopper Cross 2" +msgstr "" + +msgid "Rocket" +msgstr "" + +msgid "Roman Arena" +msgstr "" + +msgid "Roost" +msgstr "" + +msgid "Rooster" +msgstr "" + +msgid "Roslin" +msgstr "" + +msgid "Rouge et Noir" +msgstr "" + +msgid "Rows of Four" +msgstr "" + +msgid "Royal Cotillion" +msgstr "" + +msgid "Royal East" +msgstr "" + +msgid "Royal Family" +msgstr "" + +msgid "Royal Marriage" +msgstr "" + +msgid "Rugby" +msgstr "" + +msgid "Rushdike" +msgstr "" + +msgid "Russian Aces" +msgstr "" + +msgid "Russian Patience" +msgstr "" + +msgid "Russian Point" +msgstr "" + +msgid "Russian Solitaire" +msgstr "" + +msgid "Salic Law" +msgstr "" + +msgid "Samuri" +msgstr "" + +msgid "Sanibel" +msgstr "" + +msgid "Scarab" +msgstr "" + +msgid "Scheidungsgrund" +msgstr "" + +msgid "Scorpion" +msgstr "" + +msgid "Scorpion Head" +msgstr "" + +msgid "Scorpion Tail" +msgstr "" + +msgid "Scotch Patience" +msgstr "" + +msgid "Screw Up" +msgstr "" + +msgid "Sea Towers" +msgstr "" + +msgid "Seahaven Towers" +msgstr "" + +msgid "Senate" +msgstr "" + +msgid "Senate +" +msgstr "" + +msgid "Serpent" +msgstr "" + +msgid "Seven" +msgstr "" + +msgid "Seven Devils" +msgstr "" + +msgid "Seven Pyramids" +msgstr "" + +msgid "Seven by Five" +msgstr "" + +msgid "Seven by Four" +msgstr "" + +msgid "Shamrocks" +msgstr "" + +msgid "Shamsher" +msgstr "" + +msgid "Shanka" +msgstr "" + +msgid "Shapeshifter" +msgstr "" + +msgid "Shield" +msgstr "" + +msgid "Shifting" +msgstr "" + +msgid "Shisen-Sho (No Gra) 14x6" +msgstr "" + +msgid "Shisen-Sho (No Gra) 18x8" +msgstr "" + +msgid "Shisen-Sho (No Gra) 24x12" +msgstr "" + +msgid "Shisen-Sho 14x6" +msgstr "" + +msgid "Shisen-Sho 18x8" +msgstr "" + +msgid "Shisen-Sho 24x12" +msgstr "" + +msgid "Siam" +msgstr "" + +msgid "Sieben bis As" +msgstr "" + +msgid "Simon Jester" +msgstr "" + +msgid "Simple Carlo" +msgstr "" + +msgid "Simple Pairs" +msgstr "" + +msgid "Simple Simon" +msgstr "" + +msgid "Simplex" +msgstr "" + +msgid "Simplicity" +msgstr "" + +msgid "Single Rail" +msgstr "" + +msgid "Sir Tommy" +msgstr "" + +msgid "Six Sages" +msgstr "" + +msgid "Six Tengus" +msgstr "" + +msgid "Sixes and Sevens" +msgstr "" + +msgid "Skiz" +msgstr "" + +msgid "Small Harp" +msgstr "" + +msgid "Small PileOn" +msgstr "" + +msgid "Smile" +msgstr "" + +msgid "Snake" +msgstr "" + +msgid "Snakestone" +msgstr "" + +msgid "Solid Square" +msgstr "" + +msgid "Somerset" +msgstr "" + +msgid "Space Bridge" +msgstr "" + +msgid "Space Shuttle" +msgstr "" + +msgid "Spaces" +msgstr "" + +msgid "Spaces and Aces" +msgstr "" + +msgid "Spanish Patience" +msgstr "" + +msgid "Sphere" +msgstr "" + +msgid "Spider" +msgstr "" + +msgid "Spider (1 suit)" +msgstr "" + +msgid "Spider (2 suits)" +msgstr "" + +msgid "Spider (4 decks)" +msgstr "" + +msgid "Spider 3x3" +msgstr "" + +msgid "Spider Web" +msgstr "" + +msgid "Spidercells" +msgstr "" + +msgid "Spiderette" +msgstr "" + +msgid "Spidike" +msgstr "" + +msgid "Squadron" +msgstr "" + +msgid "Square" +msgstr "" + +msgid "Squares" +msgstr "" + +msgid "Squaring" +msgstr "" + +msgid "St. Helena" +msgstr "" + +msgid "Stage 1" +msgstr "" + +msgid "Stage 2" +msgstr "" + +msgid "Stairs" +msgstr "" + +msgid "Stairs 2" +msgstr "" + +msgid "Stairs 3" +msgstr "" + +msgid "Stalactites" +msgstr "" + +msgid "Star Ship" +msgstr "" + +msgid "Stargate" +msgstr "" + +msgid "Step Pyramid" +msgstr "" + +msgid "Steps" +msgstr "" + +msgid "Stonehenge" +msgstr "" + +msgid "Stonewall" +msgstr "" + +msgid "Storehouse" +msgstr "" + +msgid "Straight Up" +msgstr "" + +msgid "Strategy" +msgstr "" + +msgid "Streets" +msgstr "" + +msgid "Streets and Alleys" +msgstr "" + +msgid "Stronghold" +msgstr "" + +msgid "Sukis" +msgstr "" + +msgid "Sultan" +msgstr "" + +msgid "Sultan +" +msgstr "" + +msgid "Sultan of Turkey" +msgstr "" + +msgid "Sumo" +msgstr "" + +msgid "SunMoon" +msgstr "" + +msgid "Super Challenge FreeCell" +msgstr "" + +msgid "Super Flower Garden" +msgstr "" + +msgid "Super Samuri" +msgstr "" + +msgid "Superior Canfield" +msgstr "" + +msgid "Surprise" +msgstr "" + +msgid "Surukh" +msgstr "" + +msgid "Taipei" +msgstr "" + +msgid "Take Away" +msgstr "" + +msgid "Tam O'Shanter" +msgstr "" + +msgid "Tarantula" +msgstr "" + +msgid "Temple" +msgstr "" + +msgid "Temple 1" +msgstr "" + +msgid "Temple 2" +msgstr "" + +msgid "Ten Across" +msgstr "" + +msgid "Ten Avatars" +msgstr "" + +msgid "Ten by One" +msgstr "" + +msgid "Terrace" +msgstr "" + +msgid "The Bouquet" +msgstr "" + +msgid "The Door" +msgstr "" + +msgid "The Familiar" +msgstr "" + +msgid "The Garden" +msgstr "" + +msgid "The Great Wall" +msgstr "" + +msgid "The Wish" +msgstr "" + +msgid "The Wish (open)" +msgstr "" + +msgid "The last Monarch" +msgstr "" + +msgid "Theater" +msgstr "" + +msgid "Thirteen Up" +msgstr "" + +msgid "Thirty Six" +msgstr "" + +msgid "Three Blind Mice" +msgstr "" + +msgid "Three Peaks" +msgstr "" + +msgid "Three Peaks Non-scoring" +msgstr "" + +msgid "Three Shuffles and a Draw" +msgstr "" + +msgid "Thumb and Pouch" +msgstr "" + +msgid "Tiger" +msgstr "" + +msgid "Tile Fighter" +msgstr "" + +msgid "Tilepiles" +msgstr "" + +msgid "Time Tunnel" +msgstr "" + +msgid "Tipati" +msgstr "" + +msgid "Toad" +msgstr "" + +msgid "Tomb" +msgstr "" + +msgid "Totally Random-Made" +msgstr "" + +msgid "Tournament" +msgstr "" + +msgid "Tower of Hanoy" +msgstr "" + +msgid "Towers" +msgstr "" + +msgid "Traditional Reviewed" +msgstr "" + +msgid "Treasure Trove" +msgstr "" + +msgid "Tree of Life" +msgstr "" + +msgid "Trefoil" +msgstr "" + +msgid "Tri Peaks" +msgstr "" + +msgid "Trika" +msgstr "" + +msgid "Trillium" +msgstr "" + +msgid "Triple Canfield" +msgstr "" + +msgid "Triple Easthaven" +msgstr "" + +msgid "Triple FreeCell" +msgstr "" + +msgid "Triple Klondike" +msgstr "" + +msgid "Triple Klondike by Threes" +msgstr "" + +msgid "Triple Line" +msgstr "" + +msgid "Triple York" +msgstr "" + +msgid "Triple Yukon" +msgstr "" + +msgid "Twenty" +msgstr "" + +msgid "Twin" +msgstr "" + +msgid "Twin Picks" +msgstr "" + +msgid "Twin Temples" +msgstr "" + +msgid "Two Domes" +msgstr "" + +msgid "Two Familiars" +msgstr "" + +msgid "Two Squares" +msgstr "" + +msgid "Union Square" +msgstr "" + +msgid "Vagues" +msgstr "" + +msgid "Vajra" +msgstr "" + +msgid "Vamana" +msgstr "" + +msgid "Varaha" +msgstr "" + +msgid "Variegated Canfield" +msgstr "" + +msgid "Vegas Klondike" +msgstr "" + +msgid "Vertical" +msgstr "" + +msgid "Vi" +msgstr "" + +msgid "Victory Arrow" +msgstr "" + +msgid "Wall" +msgstr "" + +msgid "Waning Moon" +msgstr "" + +msgid "Washington's Favorite" +msgstr "" + +msgid "Wasp" +msgstr "" + +msgid "Wave Motion" +msgstr "" + +msgid "Wavelets" +msgstr "" + +msgid "Weddings" +msgstr "" + +msgid "Wedges" +msgstr "" + +msgid "Well" +msgstr "" + +msgid "Well2" +msgstr "" + +msgid "Westcliff" +msgstr "" + +msgid "Westhaven" +msgstr "" + +msgid "Whatever" +msgstr "" + +msgid "Wheel of Fortune" +msgstr "" + +msgid "Whitehead" +msgstr "" + +msgid "Wicked" +msgstr "" + +msgid "Will o' the Wisp" +msgstr "" + +msgid "Win" +msgstr "" + +msgid "Windmill" +msgstr "" + +msgid "Wisteria" +msgstr "" + +msgid "X-Files" +msgstr "" + +msgid "X-Shape" +msgstr "" + +msgid "York" +msgstr "" + +msgid "Yukon" +msgstr "" + +msgid "Yummy" +msgstr "" + +msgid "Zebra" +msgstr "" + +msgid "Zerline" +msgstr "" + +msgid "Zerline (3 decks)" +msgstr "" + +msgid "Zeus" +msgstr "" + diff --git a/po/pysol.pot b/po/pysol.pot new file mode 100644 index 0000000000..d4b883af26 --- /dev/null +++ b/po/pysol.pot @@ -0,0 +1,2781 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: Fri May 26 20:25:31 2006\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: pysollib/actions.py:345 pysollib/game.py:1205 pysollib/game.py:1220 +#: pysollib/game.py:1226 pysollib/game.py:1231 pysollib/tk/toolbar.py:183 +msgid "New game" +msgstr "" + +#: pysollib/actions.py:358 pysollib/tk/menubar.py:668 +#: pysollib/tk/menubar.py:682 +msgid "Select game" +msgstr "" + +#: pysollib/actions.py:381 +msgid "Invalid game number" +msgstr "" + +#: pysollib/actions.py:382 +msgid "" +"Invalid game number\n" +msgstr "" + +#: pysollib/actions.py:399 +msgid "Select next game number" +msgstr "" + +#: pysollib/actions.py:408 pysollib/actions.py:418 +msgid "Select new game number" +msgstr "" + +#: pysollib/actions.py:409 +msgid "" +"\n" +"\n" +"Enter new game number" +msgstr "" + +#: pysollib/actions.py:410 +msgid "Next number" +msgstr "" + +#: pysollib/actions.py:410 pysollib/app.py:1085 pysollib/app.py:1097 +#: pysollib/game.py:828 pysollib/game.py:1641 pysollib/main.py:399 +#: pysollib/main.py:404 pysollib/tk/colorsdialog.py:131 +#: pysollib/tk/demooptionsdialog.py:87 pysollib/tk/edittextdialog.py:82 +#: pysollib/tk/edittextdialog.py:94 pysollib/tk/fontsdialog.py:140 +#: pysollib/tk/fontsdialog.py:204 pysollib/tk/gameinfodialog.py:133 +#: pysollib/tk/playeroptionsdialog.py:86 +#: pysollib/tk/playeroptionsdialog.py:161 pysollib/tk/selectcardset.py:240 +#: pysollib/tk/selectcardset.py:396 pysollib/tk/selecttile.py:158 +#: pysollib/tk/soundoptionsdialog.py:106 pysollib/tk/soundoptionsdialog.py:158 +#: pysollib/tk/timeoutsdialog.py:92 pysollib/tk/tkhtml.py:506 +#: pysollib/tk/tkstats.py:288 pysollib/tk/tkstats.py:573 +#: pysollib/tk/tkstats.py:647 pysollib/tk/tkstats.py:663 +#: pysollib/tk/tkstats.py:705 pysollib/tk/tkstats.py:776 +#: pysollib/tk/tkstats.py:860 pysollib/tk/tkwidget.py:158 +#: pysollib/tk/tkwidget.py:297 +msgid "OK" +msgstr "" + +#: pysollib/actions.py:410 pysollib/app.py:1097 pysollib/game.py:828 +#: pysollib/game.py:1205 pysollib/game.py:1220 pysollib/game.py:1226 +#: pysollib/game.py:1231 pysollib/tk/colorsdialog.py:131 +#: pysollib/tk/demooptionsdialog.py:87 pysollib/tk/edittextdialog.py:94 +#: pysollib/tk/fontsdialog.py:140 pysollib/tk/fontsdialog.py:204 +#: pysollib/tk/menubar.py:855 pysollib/tk/menubar.py:857 +#: pysollib/tk/playeroptionsdialog.py:86 +#: pysollib/tk/playeroptionsdialog.py:161 pysollib/tk/selectcardset.py:240 +#: pysollib/tk/selectgame.py:268 pysollib/tk/selectgame.py:409 +#: pysollib/tk/selecttile.py:158 pysollib/tk/soundoptionsdialog.py:106 +#: pysollib/tk/timeoutsdialog.py:92 pysollib/tk/tkwidget.py:297 +msgid "Cancel" +msgstr "" + +#: pysollib/actions.py:426 +msgid "Select random game" +msgstr "" + +#: pysollib/actions.py:462 +msgid "Select next game" +msgstr "" + +#: pysollib/actions.py:495 pysollib/tk/toolbar.py:197 +msgid "Quit " +msgstr "" + +#: pysollib/actions.py:545 +msgid "Clear bookmarks" +msgstr "" + +#: pysollib/actions.py:546 +msgid "Clear all bookmarks ?" +msgstr "" + +#: pysollib/actions.py:556 +msgid "Restart game" +msgstr "" + +#: pysollib/actions.py:557 +msgid "Restart this game ?" +msgstr "" + +#: pysollib/actions.py:594 +msgid "" +"Comments for %s:\n" +"\n" +msgstr "" + +#: pysollib/actions.py:596 +msgid "Comments for " +msgstr "" + +#: pysollib/actions.py:614 pysollib/actions.py:650 +msgid "Error while writing to file" +msgstr "" + +#: pysollib/actions.py:617 pysollib/actions.py:653 pysollib/actions.py:956 +msgid " Info" +msgstr "" + +#: pysollib/actions.py:618 +msgid "" +"Comments were appended to\n" +"\n" +msgstr "" + +#: pysollib/actions.py:635 +msgid "Demo statistics" +msgstr "" + +#: pysollib/actions.py:638 +msgid "Your statistics" +msgstr "" + +#: pysollib/actions.py:654 +msgid "" +" were appended to\n" +"\n" +msgstr "" + +#: pysollib/actions.py:668 +msgid " Demo" +msgstr "" + +#: pysollib/actions.py:668 +msgid " Demo " +msgstr "" + +#: pysollib/actions.py:671 pysollib/actions.py:689 +msgid " for " +msgstr "" + +#: pysollib/actions.py:677 pysollib/actions.py:696 +msgid "Statistics for " +msgstr "" + +#: pysollib/actions.py:680 pysollib/tk/selectgame.py:352 +#: pysollib/tk/toolbar.py:194 +msgid "Statistics" +msgstr "" + +#: pysollib/actions.py:683 +msgid "Full log" +msgstr "" + +#: pysollib/actions.py:686 +msgid "Session log" +msgstr "" + +#: pysollib/actions.py:692 +msgid "Game Info" +msgstr "" + +#: pysollib/actions.py:701 +msgid "Full log for " +msgstr "" + +#: pysollib/actions.py:706 +msgid "Session log for " +msgstr "" + +#: pysollib/actions.py:711 +msgid "Reset all statistics" +msgstr "" + +#: pysollib/actions.py:712 +msgid "" +"Reset ALL statistics and logs for player\n" +"%s ?" +msgstr "" + +#: pysollib/actions.py:718 +msgid "Reset game statistics" +msgstr "" + +#: pysollib/actions.py:719 +msgid "" +"Reset statistics and logs for player\n" +"%s\n" +"and game\n" +"%s ?" +msgstr "" + +#: pysollib/actions.py:775 +msgid "Play demo" +msgstr "" + +#: pysollib/actions.py:786 +msgid "Set player options" +msgstr "" + +#: pysollib/actions.py:875 +msgid "Sound settings" +msgstr "" + +#: pysollib/actions.py:896 +msgid "Set demo options" +msgstr "" + +#: pysollib/actions.py:910 +msgid "Set colors" +msgstr "" + +#: pysollib/actions.py:929 +msgid "Set fonts" +msgstr "" + +#: pysollib/actions.py:938 +msgid "Set timeouts" +msgstr "" + +#: pysollib/actions.py:953 +msgid "Error while saving options" +msgstr "" + +#: pysollib/actions.py:957 +msgid "" +"Options were saved to\n" +"\n" +msgstr "" + +#: pysollib/app.py:85 +msgid "Unknown" +msgstr "" + +#: pysollib/app.py:947 +msgid "Loading %s %s..." +msgstr "" + +#: pysollib/app.py:982 +msgid " load error" +msgstr "" + +#: pysollib/app.py:983 +msgid "Error while loading " +msgstr "" + +#: pysollib/app.py:1077 +msgid "Incompatible " +msgstr "" + +#: pysollib/app.py:1079 +msgid "" +"The currently selected %s %s\n" +"is not compatible with the game\n" +"%s\n" +"\n" +"Please select a %s type %s.\n" +msgstr "" + +#: pysollib/app.py:1095 +msgid "Please select a %s type %s" +msgstr "" + +#: pysollib/game.py:750 pysollib/game.py:756 +msgid "" +"Player\n" +msgstr "" + +#: pysollib/game.py:824 +msgid "Discard current game ?" +msgstr "" + +#: pysollib/game.py:1159 +msgid "" +"\n" +"You have reached\n" +"#%d in the %s of playing time" +msgstr "" + +#: pysollib/game.py:1162 +msgid "" +"\n" +"and #%d in the %s of moves" +msgstr "" + +#: pysollib/game.py:1164 +msgid "" +"\n" +"You have reached\n" +"#%d in the %s of moves" +msgstr "" + +#: pysollib/game.py:1167 +msgid "" +"\n" +"and #%d in the %s of total moves" +msgstr "" + +#: pysollib/game.py:1169 +msgid "" +"\n" +"You have reached\n" +"#%d in the %s of total moves" +msgstr "" + +#: pysollib/game.py:1196 pysollib/game.py:1212 +msgid "Game won" +msgstr "" + +#: pysollib/game.py:1197 +msgid "" +"\n" +"Congratulations, this\n" +"was a truly perfect game !\n" +"\n" +"Your playing time is %s\n" +"for %d moves.\n" +"%s\n" +msgstr "" + +#: pysollib/game.py:1213 +msgid "" +"\n" +"Congratulations, you did it !\n" +"\n" +"Your playing time is %s\n" +"for %d moves.\n" +"%s\n" +msgstr "" + +#: pysollib/game.py:1224 pysollib/game.py:1229 +msgid "Game finished" +msgstr "" + +#: pysollib/game.py:1225 pysollib/game.py:1642 +msgid "" +"\n" +"Game finished\n" +msgstr "" + +#: pysollib/game.py:1230 +msgid "" +"\n" +"Game finished, but not without my help...\n" +msgstr "" + +#: pysollib/game.py:1231 pysollib/tk/toolbar.py:184 +msgid "Restart" +msgstr "" + +#: pysollib/game.py:1535 +msgid "Score %6d" +msgstr "" + +#: pysollib/game.py:1634 +msgid "Cool" +msgstr "" + +#: pysollib/game.py:1634 +msgid "Great" +msgstr "" + +#: pysollib/game.py:1634 +msgid "Wow" +msgstr "" + +#: pysollib/game.py:1634 +msgid "Yeah" +msgstr "" + +#: pysollib/game.py:1635 pysollib/game.py:1645 pysollib/game.py:1657 +msgid " Autopilot" +msgstr "" + +#: pysollib/game.py:1636 +msgid "" +"\n" +"Game solved in %d moves.\n" +msgstr "" + +#: pysollib/game.py:1656 +msgid "Hmm" +msgstr "" + +#: pysollib/game.py:1656 +msgid "Oh well" +msgstr "" + +#: pysollib/game.py:1656 +msgid "That's life" +msgstr "" + +#: pysollib/game.py:1658 +msgid "" +"\n" +"This won't come out...\n" +msgstr "" + +#: pysollib/game.py:2062 +msgid "Set bookmark" +msgstr "" + +#: pysollib/game.py:2063 +msgid "Replace existing bookmark %d ?" +msgstr "" + +#: pysollib/game.py:2085 +msgid "Goto bookmark" +msgstr "" + +#: pysollib/game.py:2086 +msgid "Goto bookmark %d ?" +msgstr "" + +#: pysollib/game.py:2117 +msgid "Open game" +msgstr "" + +#: pysollib/game.py:2128 pysollib/game.py:2137 pysollib/game.py:2142 +msgid "Load game error" +msgstr "" + +#: pysollib/game.py:2129 +msgid "" +"Error while loading game.\n" +"\n" +"Probably the game file is damaged,\n" +"but this could also be a bug you might want to report." +msgstr "" + +#: pysollib/game.py:2138 +msgid "Error while loading game" +msgstr "" + +#: pysollib/game.py:2143 +msgid "" +"Internal error while loading game.\n" +"\n" +"Please report this bug." +msgstr "" + +#: pysollib/game.py:2168 +msgid "Save game error" +msgstr "" + +#: pysollib/game.py:2169 +msgid "Error while saving game" +msgstr "" + +#: pysollib/gamedb.py:113 +msgid "Baker's Dozen" +msgstr "" + +#: pysollib/gamedb.py:114 +msgid "Beleaguered Castle" +msgstr "" + +#: pysollib/gamedb.py:115 +msgid "Canfield" +msgstr "" + +#: pysollib/gamedb.py:116 +msgid "Fan" +msgstr "" + +#: pysollib/gamedb.py:117 +msgid "Forty Thieves" +msgstr "" + +#: pysollib/gamedb.py:118 +msgid "FreeCell" +msgstr "" + +#: pysollib/gamedb.py:119 +msgid "Golf" +msgstr "" + +#: pysollib/gamedb.py:120 +msgid "Gypsy" +msgstr "" + +#: pysollib/gamedb.py:121 +msgid "Klondike" +msgstr "" + +#: pysollib/gamedb.py:122 +msgid "Montana" +msgstr "" + +#: pysollib/gamedb.py:123 +msgid "Napoleon" +msgstr "" + +#: pysollib/gamedb.py:124 +msgid "Numerica" +msgstr "" + +#: pysollib/gamedb.py:125 +msgid "Pairing" +msgstr "" + +#: pysollib/gamedb.py:126 +msgid "Raglan" +msgstr "" + +#: pysollib/gamedb.py:127 pysollib/gamedb.py:160 +msgid "Simple games" +msgstr "" + +#: pysollib/gamedb.py:128 +msgid "Spider" +msgstr "" + +#: pysollib/gamedb.py:129 +msgid "Terrace" +msgstr "" + +#: pysollib/gamedb.py:130 +msgid "Yukon" +msgstr "" + +#: pysollib/gamedb.py:131 pysollib/gamedb.py:164 +msgid "One-Deck games" +msgstr "" + +#: pysollib/gamedb.py:132 pysollib/gamedb.py:165 +msgid "Two-Deck games" +msgstr "" + +#: pysollib/gamedb.py:133 pysollib/gamedb.py:166 +msgid "Three-Deck games" +msgstr "" + +#: pysollib/gamedb.py:134 pysollib/gamedb.py:167 +msgid "Four-Deck games" +msgstr "" + +#: pysollib/gamedb.py:146 +msgid "Baker's Dozen type" +msgstr "" + +#: pysollib/gamedb.py:147 +msgid "Beleaguered Castle type" +msgstr "" + +#: pysollib/gamedb.py:148 +msgid "Canfield type" +msgstr "" + +#: pysollib/gamedb.py:149 +msgid "Fan type" +msgstr "" + +#: pysollib/gamedb.py:150 +msgid "Forty Thieves type" +msgstr "" + +#: pysollib/gamedb.py:151 +msgid "FreeCell type" +msgstr "" + +#: pysollib/gamedb.py:152 +msgid "Golf type" +msgstr "" + +#: pysollib/gamedb.py:153 +msgid "Gypsy type" +msgstr "" + +#: pysollib/gamedb.py:154 +msgid "Klondike type" +msgstr "" + +#: pysollib/gamedb.py:155 +msgid "Montana type" +msgstr "" + +#: pysollib/gamedb.py:156 +msgid "Napoleon type" +msgstr "" + +#: pysollib/gamedb.py:157 +msgid "Numerica type" +msgstr "" + +#: pysollib/gamedb.py:158 +msgid "Pairing type" +msgstr "" + +#: pysollib/gamedb.py:159 +msgid "Raglan type" +msgstr "" + +#: pysollib/gamedb.py:161 +msgid "Spider type" +msgstr "" + +#: pysollib/gamedb.py:162 +msgid "Terrace type" +msgstr "" + +#: pysollib/gamedb.py:163 +msgid "Yukon type" +msgstr "" + +#: pysollib/gamedb.py:188 pysollib/gamedb.py:196 +msgid "French type" +msgstr "" + +#: pysollib/gamedb.py:189 pysollib/gamedb.py:197 pysollib/gamedb.py:206 +msgid "Ganjifa type" +msgstr "" + +#: pysollib/gamedb.py:190 pysollib/gamedb.py:198 pysollib/gamedb.py:207 +msgid "Hanafuda type" +msgstr "" + +#: pysollib/gamedb.py:191 pysollib/gamedb.py:199 pysollib/gamedb.py:214 +msgid "Hex A Deck type" +msgstr "" + +#: pysollib/gamedb.py:192 pysollib/gamedb.py:200 pysollib/gamedb.py:219 +msgid "Tarock type" +msgstr "" + +#: pysollib/gamedb.py:205 +msgid "Dashavatara Ganjifa type" +msgstr "" + +#: pysollib/gamedb.py:208 +msgid "Mughal Ganjifa type" +msgstr "" + +#: pysollib/gamedb.py:209 +msgid "Navagraha Ganjifa type" +msgstr "" + +#: pysollib/gamedb.py:213 +msgid "Shisen-Sho" +msgstr "" + +#: pysollib/gamedb.py:215 +msgid "Matrix type" +msgstr "" + +#: pysollib/gamedb.py:216 +msgid "Memory type" +msgstr "" + +#: pysollib/gamedb.py:217 +msgid "Poker type" +msgstr "" + +#: pysollib/gamedb.py:218 +msgid "Puzzle type" +msgstr "" + +#: pysollib/games/auldlangsyne.py:142 pysollib/games/calculation.py:101 +#: pysollib/games/numerica.py:90 pysollib/games/numerica.py:197 +msgid "Row. Build regardless of rank and suit." +msgstr "" + +#: pysollib/games/braid.py:250 pysollib/games/napoleon.py:190 +#: pysollib/games/ultra/dashavatara.py:959 +#: pysollib/games/ultra/hanafuda1.py:256 pysollib/games/ultra/hexadeck.py:1190 +#: pysollib/games/ultra/mughal.py:802 +msgid " Ascending" +msgstr "" + +#: pysollib/games/braid.py:252 pysollib/games/napoleon.py:192 +#: pysollib/games/ultra/dashavatara.py:961 +#: pysollib/games/ultra/hanafuda1.py:258 pysollib/games/ultra/hexadeck.py:1192 +#: pysollib/games/ultra/mughal.py:804 +msgid " Descending" +msgstr "" + +#: pysollib/games/calculation.py:135 pysollib/games/calculation.py:230 +msgid "" +"1: 2 3 4 5 6 7 8 9 T J Q K\n" +"2: 4 6 8 T Q A 3 5 7 9 J K\n" +"3: 6 9 Q 2 5 8 J A 4 7 T K\n" +"4: 8 Q 3 7 J 2 6 T A 5 9 K" +msgstr "" + +#: pysollib/games/curdsandwhey.py:58 +msgid "Row. Build down by suit or of the same rank." +msgstr "" + +#: pysollib/games/fan.py:279 +msgid "Draw" +msgstr "" + +#: pysollib/games/fan.py:279 +msgid "X" +msgstr "" + +#: pysollib/games/fortythieves.py:393 pysollib/games/klondike.py:148 +msgid "Row. Build down in any suit but the same." +msgstr "" + +#: pysollib/games/golf.py:114 pysollib/games/golf.py:413 +#: pysollib/stack.py:1740 +msgid "Row. No building." +msgstr "" + +#: pysollib/games/golf.py:381 +msgid "Balance $%4d" +msgstr "" + +#: pysollib/games/golf.py:497 pysollib/stack.py:1673 +msgid "Foundation. Build up regardless of suit." +msgstr "" + +#: pysollib/games/klondike.py:115 +msgid "Balance $%d" +msgstr "" + +#: pysollib/games/klondike.py:387 +msgid "Reserve. Only Kings are acceptable." +msgstr "" + +#: pysollib/games/matriarchy.py:125 +msgid "Round %d/%d" +msgstr "" + +#: pysollib/games/matriarchy.py:127 +msgid "Deal %d" +msgstr "" + +#: pysollib/games/numerica.py:184 +msgid "Foundation. Build up by color." +msgstr "" + +#: pysollib/games/poker.py:82 +msgid "" +"Royal Flush\n" +"Straight Flush\n" +"Four of a Kind\n" +"Full House\n" +"Flush\n" +"Straight\n" +"Three of a Kind\n" +"Two Pair\n" +"One Pair" +msgstr "" + +#: pysollib/games/poker.py:189 pysollib/games/special/memory.py:181 +msgid "" +"WON\n" +"\n" +msgstr "" + +#: pysollib/games/poker.py:191 pysollib/games/special/memory.py:178 +msgid "Points: %d" +msgstr "" + +#: pysollib/games/poker.py:193 pysollib/games/special/memory.py:182 +msgid "Total: %d" +msgstr "" + +#: pysollib/games/special/tarock.py:222 +msgid "Coin" +msgstr "" + +#: pysollib/games/special/tarock.py:222 +msgid "Cup" +msgstr "" + +#: pysollib/games/special/tarock.py:222 +msgid "Sword" +msgstr "" + +#: pysollib/games/special/tarock.py:222 +msgid "Trump" +msgstr "" + +#: pysollib/games/special/tarock.py:222 +msgid "Wand" +msgstr "" + +#: pysollib/games/special/tarock.py:223 +#: pysollib/games/ultra/dashavatara.py:351 +#: pysollib/games/ultra/hexadeck.py:273 pysollib/games/ultra/mughal.py:254 +#: pysollib/stack.py:1190 pysollib/util.py:80 +msgid "Ace" +msgstr "" + +#: pysollib/games/special/tarock.py:224 +msgid "Page" +msgstr "" + +#: pysollib/games/special/tarock.py:224 +msgid "Valet" +msgstr "" + +#: pysollib/games/special/tarock.py:224 pysollib/stack.py:1188 +#: pysollib/util.py:81 +msgid "Queen" +msgstr "" + +#: pysollib/games/special/tarock.py:224 pysollib/stack.py:1189 +#: pysollib/util.py:81 +msgid "King" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Boar" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Dwarf" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Fish" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Lion" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Tortoise" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Arrow" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Axe" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Horse" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Lotus" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Plow" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:352 pysollib/games/ultra/mughal.py:255 +msgid "Pradhan" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:352 pysollib/games/ultra/mughal.py:255 +msgid "Raja" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Black" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Brown" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Red" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Yellow" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:257 +#: pysollib/tk/selecttile.py:86 +msgid "Green" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:354 +msgid "Crimson" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:354 +msgid "White" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:354 pysollib/games/ultra/mughal.py:257 +msgid "Grey" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:354 pysollib/games/ultra/mughal.py:257 +#: pysollib/tk/selecttile.py:89 +msgid "Orange" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:354 pysollib/tk/selecttile.py:88 +msgid "Olive" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:355 pysollib/games/ultra/mughal.py:258 +msgid "Strong" +msgstr "" + +#: pysollib/games/ultra/dashavatara.py:355 pysollib/games/ultra/mughal.py:258 +msgid "Weak" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:373 +msgid "Rising" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:375 +msgid "Setting" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:511 +msgid "Filled" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid " Deck" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "nd" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "rd" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "st" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "th" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "East" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "North" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "South" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "West" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "NE" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "NW" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "SE" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "SW" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Cherry" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Pine" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Plum" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Wisteria" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Bush Clover" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Eularia" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Iris" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Peony" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Chrysanthemum" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Maple" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Paulownia" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Willow" +msgstr "" + +#: pysollib/games/ultra/larasgame.py:157 pysollib/stack.py:1368 +msgid "Round %d" +msgstr "" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Crown" +msgstr "" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Saber" +msgstr "" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Servant" +msgstr "" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Silver" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +msgid "Document" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +msgid "Gold" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +msgid "Harp" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +msgid "Stores" +msgstr "" + +#: pysollib/games/ultra/mughal.py:257 +msgid "Tan" +msgstr "" + +#: pysollib/games/ultra/threepeaks.py:217 +msgid "Score:\tThis hand: " +msgstr "" + +#: pysollib/games/ultra/threepeaks.py:218 +msgid "\tThis game: " +msgstr "" + +#: pysollib/games/yukon.py:145 +msgid "Row. Build down in any suit but the same, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/games/yukon.py:201 +msgid "Row. Build up or down by suit, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/games/yukon.py:218 +msgid "Row. Build up or down by alternate color, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/games/yukon.py:320 +msgid "" +"Club: A 2 3 4 5 6 7 8 9 T J Q K\n" +"Spade: 2 4 6 8 T Q A 3 5 7 9 J K\n" +"Heart: 3 6 9 Q 2 5 8 J A 4 7 T K\n" +"Diamond: 4 8 Q 3 7 J 2 6 T A 5 9 K" +msgstr "" + +#: pysollib/help.py:64 +msgid "" +"A Python Solitaire Game Collection\n" +msgstr "" + +#: pysollib/help.py:66 +msgid "" +"A World Domination Project\n" +msgstr "" + +#: pysollib/help.py:67 +msgid "Credits..." +msgstr "" + +#: pysollib/help.py:67 +msgid "Nice" +msgstr "" + +#: pysollib/help.py:69 +msgid "Enjoy" +msgstr "" + +#: pysollib/help.py:71 +msgid "" +"Version %s\n" +"\n" +msgstr "" + +#: pysollib/help.py:72 +msgid "About " +msgstr "" + +#: pysollib/help.py:73 +msgid "" +"PySol Fan Club edition\n" +"%s%s\n" +"Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003\n" +"Markus F.X.J. Oberhumer\n" +"Copyright (C) 2003 Mt. Hood Playing Card Co.\n" +"Copyright (C) 2005 Skomoroh (Fan Club edition)\n" +"All Rights Reserved.\n" +"\n" +"PySol is free software distributed under the terms\n" +"of the GNU General Public License.\n" +"\n" +"For more information about this application visit\n" +"%s" +msgstr "" + +#: pysollib/help.py:102 +msgid "Credits" +msgstr "" + +#: pysollib/help.py:103 +msgid "" +" credits go to:\n" +"\n" +"Volker Weidner for getting me into Solitaire\n" +"Guido van Rossum for the initial example program\n" +"T. Kirk for lots of contributed games and cardsets\n" +"Carl Larsson for the background music\n" +"The Gnome AisleRiot team for parts of the documentation\n" +"Natascha\n" +"\n" +"The Python, %s, SDL & Linux crews\n" +"for making this program possible" +msgstr "" + +#: pysollib/help.py:138 +msgid " HTML Problem" +msgstr "" + +#: pysollib/help.py:139 +msgid "" +"Cannot find help document\n" +msgstr "" + +#: pysollib/help.py:152 +msgid " Help" +msgstr "" + +#: pysollib/main.py:68 pysollib/main.py:310 +msgid " installation error" +msgstr "" + +#: pysollib/main.py:69 +msgid "" +"No %ss were found !!!\n" +"\n" +"Main data directory is:\n" +"%s\n" +"\n" +"Please check your %s installation.\n" +msgstr "" + +#: pysollib/main.py:76 pysollib/main.py:319 pysollib/tk/toolbar.py:197 +msgid "Quit" +msgstr "" + +#: pysollib/main.py:95 +msgid "" +"%s: %s\n" +"try %s --help for more information" +msgstr "" + +#: pysollib/main.py:120 +msgid "" +"Usage: %s [OPTIONS] [FILE]\n" +" --fg --foreground=COLOR foreground color\n" +" --bg --background=COLOR background color\n" +" --fn --font=FONT default font\n" +" --nosound disable sound support\n" +" --noplugins disable load plugins\n" +" -h --help display this help and exit\n" +"\n" +" FILE - file name of a saved game\n" +msgstr "" + +#: pysollib/main.py:133 +msgid "" +"%s: too many files\n" +"try %s --help for more information" +msgstr "" + +#: pysollib/main.py:137 +msgid "" +"%s: invalide file name\n" +"try %s --help for more information" +msgstr "" + +#: pysollib/main.py:311 +msgid "" +"\n" +"No games were found !!!\n" +"\n" +"Main data directory is:\n" +"%s\n" +"\n" +"Please check your %s installation.\n" +msgstr "" + +#: pysollib/main.py:397 pysollib/main.py:402 +msgid " installation problem" +msgstr "" + +#: pysollib/main.py:398 +msgid "" +"Your Python installation is compiled without thread support.\n" +"\n" +"Sounds and background music will be disabled." +msgstr "" + +#: pysollib/main.py:403 +msgid "" +"The pysolsoundserver module was not found.\n" +"\n" +"Sounds and background music will be disabled." +msgstr "" + +#: pysollib/main.py:407 +msgid "Welcome to " +msgstr "" + +#: pysollib/settings.py:58 +msgid "Top 10" +msgstr "" + +#: pysollib/stack.py:1184 +msgid "Base card - %s." +msgstr "" + +#: pysollib/stack.py:1185 +msgid "Empty row cannot be filled." +msgstr "" + +#: pysollib/stack.py:1186 +msgid "any card" +msgstr "" + +#: pysollib/stack.py:1187 pysollib/util.py:81 +msgid "Jack" +msgstr "" + +#: pysollib/stack.py:1196 +msgid "No cards" +msgstr "" + +#: pysollib/stack.py:1197 +msgid "1 card" +msgstr "" + +#: pysollib/stack.py:1198 +msgid " cards" +msgstr "" + +#: pysollib/stack.py:1377 pysollib/stack.py:1379 pysollib/stack.py:1410 +msgid "Redeal" +msgstr "" + +#: pysollib/stack.py:1379 +msgid "Stop" +msgstr "" + +#: pysollib/stack.py:1430 +msgid "Variable redeals." +msgstr "" + +#: pysollib/stack.py:1431 +msgid "Unlimited redeals." +msgstr "" + +#: pysollib/stack.py:1432 +msgid "No redeals." +msgstr "" + +#: pysollib/stack.py:1433 +msgid "One redeal." +msgstr "" + +#: pysollib/stack.py:1434 +msgid " redeals." +msgstr "" + +#: pysollib/stack.py:1436 +msgid "Talon." +msgstr "" + +#: pysollib/stack.py:1611 pysollib/stack.py:2035 +msgid "Reserve. No building." +msgstr "" + +#: pysollib/stack.py:1657 +msgid "Foundation. Build up by suit." +msgstr "" + +#: pysollib/stack.py:1658 +msgid "Foundation. Build down by suit." +msgstr "" + +#: pysollib/stack.py:1659 pysollib/stack.py:1675 pysollib/stack.py:1697 +msgid "Foundation. Build by same rank." +msgstr "" + +#: pysollib/stack.py:1674 +msgid "Foundation. Build down regardless of suit." +msgstr "" + +#: pysollib/stack.py:1695 +msgid "Foundation. Build up by alternate color." +msgstr "" + +#: pysollib/stack.py:1696 +msgid "Foundation. Build down by alternate color." +msgstr "" + +#: pysollib/stack.py:1770 +msgid "Row. Build up by alternate color." +msgstr "" + +#: pysollib/stack.py:1771 +msgid "Row. Build down by alternate color." +msgstr "" + +#: pysollib/stack.py:1772 pysollib/stack.py:1782 pysollib/stack.py:1791 +#: pysollib/stack.py:1800 pysollib/stack.py:1828 +msgid "Row. Build by same rank." +msgstr "" + +#: pysollib/stack.py:1780 +msgid "Row. Build up by color." +msgstr "" + +#: pysollib/stack.py:1781 +msgid "Row. Build down by color." +msgstr "" + +#: pysollib/stack.py:1789 +msgid "Row. Build up by suit." +msgstr "" + +#: pysollib/stack.py:1790 +msgid "Row. Build down by suit." +msgstr "" + +#: pysollib/stack.py:1798 pysollib/stack.py:1826 +msgid "Row. Build up regardless of suit." +msgstr "" + +#: pysollib/stack.py:1799 pysollib/stack.py:1827 +msgid "Row. Build down regardless of suit." +msgstr "" + +#: pysollib/stack.py:1849 +msgid "Row. Build up by alternate color, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1850 +msgid "Row. Build down by alternate color, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1851 pysollib/stack.py:1862 +msgid "Row. Build by same rank, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1860 +msgid "Row. Build up by suit, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1861 +msgid "Row. Build down by suit, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1894 +msgid "Row. Build up or down by color." +msgstr "" + +#: pysollib/stack.py:1905 +msgid "Row. Build up or down by alternate color." +msgstr "" + +#: pysollib/stack.py:1916 +msgid "Row. Build up or down by suit." +msgstr "" + +#: pysollib/stack.py:1927 +msgid "Row. Build up or down regardless of suit." +msgstr "" + +#: pysollib/stack.py:1938 +msgid "Waste." +msgstr "" + +#: pysollib/stack.py:2036 +msgid "Free cell." +msgstr "" + +#: pysollib/stats.py:120 pysollib/tk/tkstats.py:78 +msgid "Demo games" +msgstr "" + +#: pysollib/stats.py:121 +msgid "Played" +msgstr "" + +#: pysollib/stats.py:122 pysollib/stats.py:202 +msgid "Won" +msgstr "" + +#: pysollib/stats.py:123 pysollib/stats.py:202 +msgid "Lost" +msgstr "" + +#: pysollib/stats.py:124 pysollib/tk/statusbar.py:134 +msgid "Playing time" +msgstr "" + +#: pysollib/stats.py:125 +msgid "Moves" +msgstr "" + +#: pysollib/stats.py:126 +msgid "% won" +msgstr "" + +#: pysollib/stats.py:155 +msgid "Total (%d out of %d games)" +msgstr "" + +#: pysollib/stats.py:164 +msgid "Game" +msgstr "" + +#: pysollib/stats.py:164 +msgid "Started at " +msgstr "" + +#: pysollib/stats.py:164 +msgid "Status" +msgstr "" + +#: pysollib/stats.py:164 pysollib/tk/statusbar.py:136 +#: pysollib/tk/tkstats.py:734 +msgid "Game number" +msgstr "" + +#: pysollib/stats.py:187 +msgid "** UNKNOWN %d **" +msgstr "" + +#: pysollib/stats.py:195 +msgid "** ERROR **" +msgstr "" + +#: pysollib/stats.py:202 +msgid "Loaded" +msgstr "" + +#: pysollib/stats.py:202 +msgid "Not won" +msgstr "" + +#: pysollib/stats.py:202 +msgid "Perfect" +msgstr "" + +#: pysollib/tk/colorsdialog.py:73 +msgid "Text foreground:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:79 pysollib/tk/colorsdialog.py:97 +#: pysollib/tk/fontsdialog.py:185 +msgid "Change..." +msgstr "" + +#: pysollib/tk/colorsdialog.py:85 pysollib/tk/timeoutsdialog.py:68 +msgid "Highlight piles:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:86 +msgid "Highlight cards 1:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:87 +msgid "Highlight cards 2:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:88 +msgid "Highlight same rank 1:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:89 +msgid "Highlight same rank 2:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:90 +msgid "Hint arrow:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:91 +msgid "Highlight not matching:" +msgstr "" + +#: pysollib/tk/colorsdialog.py:123 +msgid "Select color" +msgstr "" + +#: pysollib/tk/demooptionsdialog.py:66 +msgid "Display floating Demo logo" +msgstr "" + +#: pysollib/tk/demooptionsdialog.py:69 +msgid "Show score in statusbar" +msgstr "" + +#: pysollib/tk/demooptionsdialog.py:73 +msgid "Set demo delay in seconds" +msgstr "" + +#: pysollib/tk/fontsdialog.py:85 +msgid "abcdefghABCDEFGH" +msgstr "" + +#: pysollib/tk/fontsdialog.py:94 +msgid "Bold" +msgstr "" + +#: pysollib/tk/fontsdialog.py:97 +msgid "Italic" +msgstr "" + +#: pysollib/tk/fontsdialog.py:195 +msgid "Select font" +msgstr "" + +#: pysollib/tk/menubar.py:75 +msgid "Style" +msgstr "" + +#: pysollib/tk/menubar.py:84 +msgid "Relief" +msgstr "" + +#: pysollib/tk/menubar.py:85 +msgid "Flat" +msgstr "" + +#: pysollib/tk/menubar.py:89 +msgid "Raised" +msgstr "" + +#: pysollib/tk/menubar.py:94 +msgid "Compound" +msgstr "" + +#: pysollib/tk/menubar.py:100 +msgid "Hide" +msgstr "" + +#: pysollib/tk/menubar.py:103 +msgid "Top" +msgstr "" + +#: pysollib/tk/menubar.py:106 +msgid "Bottom" +msgstr "" + +#: pysollib/tk/menubar.py:109 +msgid "Left" +msgstr "" + +#: pysollib/tk/menubar.py:112 +msgid "Right" +msgstr "" + +#: pysollib/tk/menubar.py:116 +msgid "Small icons" +msgstr "" + +#: pysollib/tk/menubar.py:119 +msgid "Large icons" +msgstr "" + +#: pysollib/tk/menubar.py:125 +msgid "Customize toolbar" +msgstr "" + +#: pysollib/tk/menubar.py:248 +msgid "&File" +msgstr "" + +#: pysollib/tk/menubar.py:249 +msgid "&New game" +msgstr "" + +#: pysollib/tk/menubar.py:250 +msgid "R&ecent games" +msgstr "" + +#: pysollib/tk/menubar.py:252 +msgid "Select &random game" +msgstr "" + +#: pysollib/tk/menubar.py:253 +msgid "&All games" +msgstr "" + +#: pysollib/tk/menubar.py:254 +msgid "Games played and &won" +msgstr "" + +#: pysollib/tk/menubar.py:255 +msgid "Games played and ¬ won" +msgstr "" + +#: pysollib/tk/menubar.py:256 +msgid "Games not &played" +msgstr "" + +#: pysollib/tk/menubar.py:257 +msgid "Select game by nu&mber..." +msgstr "" + +#: pysollib/tk/menubar.py:259 +msgid "Fa&vorite games" +msgstr "" + +#: pysollib/tk/menubar.py:260 +msgid "A&dd to favorites" +msgstr "" + +#: pysollib/tk/menubar.py:261 +msgid "R&emove from favorites" +msgstr "" + +#: pysollib/tk/menubar.py:263 +msgid "&Open..." +msgstr "" + +#: pysollib/tk/menubar.py:264 +msgid "&Save" +msgstr "" + +#: pysollib/tk/menubar.py:265 +msgid "Save &as..." +msgstr "" + +#: pysollib/tk/menubar.py:267 +msgid "&Hold and quit" +msgstr "" + +#: pysollib/tk/menubar.py:268 +msgid "&Quit" +msgstr "" + +#: pysollib/tk/menubar.py:270 +msgid "&Select" +msgstr "" + +#: pysollib/tk/menubar.py:273 +msgid "&Edit" +msgstr "" + +#: pysollib/tk/menubar.py:274 +msgid "&Undo" +msgstr "" + +#: pysollib/tk/menubar.py:275 +msgid "&Redo" +msgstr "" + +#: pysollib/tk/menubar.py:276 +msgid "Redo &all" +msgstr "" + +#: pysollib/tk/menubar.py:279 +msgid "&Set bookmark" +msgstr "" + +#: pysollib/tk/menubar.py:281 pysollib/tk/menubar.py:285 +msgid "Bookmark %d" +msgstr "" + +#: pysollib/tk/menubar.py:283 +msgid "Go&to bookmark" +msgstr "" + +#: pysollib/tk/menubar.py:288 +msgid "&Clear bookmarks" +msgstr "" + +#: pysollib/tk/menubar.py:291 +msgid "Restart &game" +msgstr "" + +#: pysollib/tk/menubar.py:293 +msgid "&Game" +msgstr "" + +#: pysollib/tk/menubar.py:294 +msgid "&Deal cards" +msgstr "" + +#: pysollib/tk/menubar.py:295 pysollib/tk/menubar.py:324 +msgid "&Auto drop" +msgstr "" + +#: pysollib/tk/menubar.py:296 +msgid "&Pause" +msgstr "" + +#: pysollib/tk/menubar.py:299 +msgid "S&tatus..." +msgstr "" + +#: pysollib/tk/menubar.py:300 +msgid "&Comments..." +msgstr "" + +#: pysollib/tk/menubar.py:302 +msgid "&Statistics" +msgstr "" + +#: pysollib/tk/menubar.py:303 pysollib/tk/menubar.py:311 +msgid "Current game..." +msgstr "" + +#: pysollib/tk/menubar.py:304 pysollib/tk/menubar.py:312 +#: pysollib/tk/tkstats.py:289 +msgid "All games..." +msgstr "" + +#: pysollib/tk/menubar.py:306 pysollib/tk/tkstats.py:647 +msgid "Session log..." +msgstr "" + +#: pysollib/tk/menubar.py:307 pysollib/tk/tkstats.py:663 +msgid "Full log..." +msgstr "" + +#: pysollib/tk/menubar.py:310 +msgid "D&emo statistics" +msgstr "" + +#: pysollib/tk/menubar.py:314 +msgid "&Assist" +msgstr "" + +#: pysollib/tk/menubar.py:315 +msgid "&Hint" +msgstr "" + +#: pysollib/tk/menubar.py:316 +msgid "Highlight p&iles" +msgstr "" + +#: pysollib/tk/menubar.py:318 +msgid "&Demo" +msgstr "" + +#: pysollib/tk/menubar.py:319 +msgid "Demo (&all games)" +msgstr "" + +#: pysollib/tk/menubar.py:320 +msgid "&Options" +msgstr "" + +#: pysollib/tk/menubar.py:321 +msgid "&Player options..." +msgstr "" + +#: pysollib/tk/menubar.py:322 +msgid "&Automatic play" +msgstr "" + +#: pysollib/tk/menubar.py:323 +msgid "Auto &face up" +msgstr "" + +#: pysollib/tk/menubar.py:325 +msgid "Auto &deal" +msgstr "" + +#: pysollib/tk/menubar.py:327 +msgid "&Quick play" +msgstr "" + +#: pysollib/tk/menubar.py:328 +msgid "Assist &level" +msgstr "" + +#: pysollib/tk/menubar.py:329 +msgid "Enable &undo" +msgstr "" + +#: pysollib/tk/menubar.py:330 +msgid "Enable &bookmarks" +msgstr "" + +#: pysollib/tk/menubar.py:331 +msgid "Enable &hint" +msgstr "" + +#: pysollib/tk/menubar.py:332 +msgid "Enable highlight p&iles" +msgstr "" + +#: pysollib/tk/menubar.py:333 +msgid "Enable highlight &cards" +msgstr "" + +#: pysollib/tk/menubar.py:334 +msgid "Enable highlight same &rank" +msgstr "" + +#: pysollib/tk/menubar.py:335 +msgid "Highlight &no matching" +msgstr "" + +#: pysollib/tk/menubar.py:337 +msgid "Show removed tiles (in Mahjongg games)" +msgstr "" + +#: pysollib/tk/menubar.py:338 +msgid "Show hint arrow (in Shisen-Sho games)" +msgstr "" + +#: pysollib/tk/menubar.py:340 +msgid "&Sound" +msgstr "" + +#: pysollib/tk/menubar.py:350 +msgid "Cards&et..." +msgstr "" + +#: pysollib/tk/menubar.py:351 +msgid "Table t&ile..." +msgstr "" + +#: pysollib/tk/menubar.py:353 +msgid "Card &background" +msgstr "" + +#: pysollib/tk/menubar.py:354 +msgid "Card &view" +msgstr "" + +#: pysollib/tk/menubar.py:355 +msgid "Card shado&w" +msgstr "" + +#: pysollib/tk/menubar.py:356 +msgid "Shade &legal moves" +msgstr "" + +#: pysollib/tk/menubar.py:357 +msgid "&Negative card bottom" +msgstr "" + +#: pysollib/tk/menubar.py:358 +msgid "A&nimations" +msgstr "" + +#: pysollib/tk/menubar.py:359 +msgid "&None" +msgstr "" + +#: pysollib/tk/menubar.py:360 +msgid "&Timer based" +msgstr "" + +#: pysollib/tk/menubar.py:361 +msgid "&Fast" +msgstr "" + +#: pysollib/tk/menubar.py:362 +msgid "&Slow" +msgstr "" + +#: pysollib/tk/menubar.py:363 +msgid "&Very slow" +msgstr "" + +#: pysollib/tk/menubar.py:364 +msgid "Stick&y mouse" +msgstr "" + +#: pysollib/tk/menubar.py:368 +msgid "&Fonts..." +msgstr "" + +#: pysollib/tk/menubar.py:369 +msgid "&Colors..." +msgstr "" + +#: pysollib/tk/menubar.py:370 +msgid "Time&outs..." +msgstr "" + +#: pysollib/tk/menubar.py:372 +msgid "&Toolbar" +msgstr "" + +#: pysollib/tk/menubar.py:374 +msgid "Stat&usbar" +msgstr "" + +#: pysollib/tk/menubar.py:375 +msgid "Show &statusbar" +msgstr "" + +#: pysollib/tk/menubar.py:376 +msgid "Show &number of cards" +msgstr "" + +#: pysollib/tk/menubar.py:377 +msgid "Show &help bar" +msgstr "" + +#: pysollib/tk/menubar.py:378 +msgid "&Demo logo" +msgstr "" + +#: pysollib/tk/menubar.py:379 +msgid "Startup splash sc&reen" +msgstr "" + +#: pysollib/tk/menubar.py:383 +msgid "&Help" +msgstr "" + +#: pysollib/tk/menubar.py:384 +msgid "&Contents" +msgstr "" + +#: pysollib/tk/menubar.py:385 +msgid "&How to play" +msgstr "" + +#: pysollib/tk/menubar.py:386 +msgid "&Rules for this game" +msgstr "" + +#: pysollib/tk/menubar.py:387 +msgid "&License terms" +msgstr "" + +#: pysollib/tk/menubar.py:390 +msgid "&About " +msgstr "" + +#: pysollib/tk/menubar.py:498 +msgid "All &games..." +msgstr "" + +#: pysollib/tk/menubar.py:499 +msgid "Playable pre&view..." +msgstr "" + +#: pysollib/tk/menubar.py:501 +msgid "&Popular games" +msgstr "" + +#: pysollib/tk/menubar.py:504 +msgid "&French games" +msgstr "" + +#: pysollib/tk/menubar.py:507 +msgid "&Mahjongg games" +msgstr "" + +#: pysollib/tk/menubar.py:510 +msgid "&Oriental games" +msgstr "" + +#: pysollib/tk/menubar.py:514 +msgid "&Special games" +msgstr "" + +#: pysollib/tk/menubar.py:518 +msgid "All games by name" +msgstr "" + +#: pysollib/tk/menubar.py:855 pysollib/tk/menubar.py:857 +#: pysollib/tk/selectcardset.py:240 +msgid "Load" +msgstr "" + +#: pysollib/tk/menubar.py:857 +msgid "Info..." +msgstr "" + +#: pysollib/tk/menubar.py:860 +msgid "Select " +msgstr "" + +#: pysollib/tk/menubar.py:920 +msgid "Select table background" +msgstr "" + +#: pysollib/tk/menubar.py:932 pysollib/tk/selecttile.py:176 +msgid "Select table color" +msgstr "" + +#: pysollib/tk/playeroptionsdialog.py:113 +msgid "" +"\n" +"Please enter your name" +msgstr "" + +#: pysollib/tk/playeroptionsdialog.py:121 +msgid "Select..." +msgstr "" + +#: pysollib/tk/playeroptionsdialog.py:125 +msgid "Confirm quit" +msgstr "" + +#: pysollib/tk/playeroptionsdialog.py:129 +msgid "Update statistics and logs" +msgstr "" + +#: pysollib/tk/playeroptionsdialog.py:146 +msgid "Select name" +msgstr "" + +#: pysollib/tk/selectcardset.py:81 pysollib/tk/selectcardset.py:146 +msgid "(no cardsets)" +msgstr "" + +#: pysollib/tk/selectcardset.py:91 pysollib/tk/selectcardset.py:154 +msgid "by Type" +msgstr "" + +#: pysollib/tk/selectcardset.py:101 pysollib/tk/selectcardset.py:112 +#: pysollib/tk/selectcardset.py:123 +msgid "Uncategorized" +msgstr "" + +#: pysollib/tk/selectcardset.py:102 +msgid "by Style" +msgstr "" + +#: pysollib/tk/selectcardset.py:113 +msgid "by Nationality" +msgstr "" + +#: pysollib/tk/selectcardset.py:124 +msgid "by Date" +msgstr "" + +#: pysollib/tk/selectcardset.py:127 +msgid "All Cardsets" +msgstr "" + +#: pysollib/tk/selectcardset.py:128 +msgid "by Size" +msgstr "" + +#: pysollib/tk/selectcardset.py:129 +msgid "Tiny cardsets" +msgstr "" + +#: pysollib/tk/selectcardset.py:130 +msgid "Small cardsets" +msgstr "" + +#: pysollib/tk/selectcardset.py:131 +msgid "Medium cardsets" +msgstr "" + +#: pysollib/tk/selectcardset.py:132 +msgid "Large cardsets" +msgstr "" + +#: pysollib/tk/selectcardset.py:133 +msgid "XLarge cardsets" +msgstr "" + +#: pysollib/tk/selectcardset.py:319 +msgid "About cardset" +msgstr "" + +#: pysollib/tk/selectcardset.py:335 pysollib/tk/selectgame.py:367 +msgid "Type:" +msgstr "" + +#: pysollib/tk/selectcardset.py:336 +msgid "Styles:" +msgstr "" + +#: pysollib/tk/selectcardset.py:337 +msgid "Nationality:" +msgstr "" + +#: pysollib/tk/selectcardset.py:338 +msgid "Year:" +msgstr "" + +#: pysollib/tk/selectcardset.py:340 +msgid "Size:" +msgstr "" + +#: pysollib/tk/selectgame.py:100 +msgid "(no games)" +msgstr "" + +#: pysollib/tk/selectgame.py:118 +msgid "French games" +msgstr "" + +#: pysollib/tk/selectgame.py:121 +msgid "Oriental Games" +msgstr "" + +#: pysollib/tk/selectgame.py:124 +msgid "Special Games" +msgstr "" + +#: pysollib/tk/selectgame.py:127 +msgid "Original Games" +msgstr "" + +#: pysollib/tk/selectgame.py:141 +msgid "by Compatibility" +msgstr "" + +#: pysollib/tk/selectgame.py:159 +msgid "New games in v" +msgstr "" + +#: pysollib/tk/selectgame.py:162 +msgid "by PySol version" +msgstr "" + +#: pysollib/tk/selectgame.py:169 +msgid "All Games" +msgstr "" + +#: pysollib/tk/selectgame.py:170 +msgid "Alternate Names" +msgstr "" + +#: pysollib/tk/selectgame.py:171 +msgid "Popular Games" +msgstr "" + +#: pysollib/tk/selectgame.py:172 +msgid "Mahjongg Games" +msgstr "" + +#: pysollib/tk/selectgame.py:178 +msgid "by Game Feature" +msgstr "" + +#: pysollib/tk/selectgame.py:179 +msgid "by Number of Cards" +msgstr "" + +#: pysollib/tk/selectgame.py:180 +msgid "32 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:181 +msgid "48 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:182 +msgid "52 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:183 +msgid "64 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:184 +msgid "78 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:185 +msgid "104 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:186 +msgid "144 cards" +msgstr "" + +#: pysollib/tk/selectgame.py:187 +msgid "Other number" +msgstr "" + +#: pysollib/tk/selectgame.py:189 +msgid "by Number of Decks" +msgstr "" + +#: pysollib/tk/selectgame.py:190 +msgid "1 deck games" +msgstr "" + +#: pysollib/tk/selectgame.py:191 +msgid "2 deck games" +msgstr "" + +#: pysollib/tk/selectgame.py:192 +msgid "3 deck games" +msgstr "" + +#: pysollib/tk/selectgame.py:193 +msgid "4 deck games" +msgstr "" + +#: pysollib/tk/selectgame.py:195 +msgid "by Number of Redeals" +msgstr "" + +#: pysollib/tk/selectgame.py:196 +msgid "No redeal" +msgstr "" + +#: pysollib/tk/selectgame.py:197 +msgid "1 redeal" +msgstr "" + +#: pysollib/tk/selectgame.py:198 +msgid "2 redeals" +msgstr "" + +#: pysollib/tk/selectgame.py:199 +msgid "3 redeals" +msgstr "" + +#: pysollib/tk/selectgame.py:200 +msgid "Unlimited redeals" +msgstr "" + +#: pysollib/tk/selectgame.py:202 +msgid "Other number of redeals" +msgstr "" + +#: pysollib/tk/selectgame.py:207 +msgid "Other Categories" +msgstr "" + +#: pysollib/tk/selectgame.py:208 +msgid "Games for Children (very easy)" +msgstr "" + +#: pysollib/tk/selectgame.py:209 +msgid "Games with Scoring" +msgstr "" + +#: pysollib/tk/selectgame.py:210 +msgid "Games with Separate Decks" +msgstr "" + +#: pysollib/tk/selectgame.py:211 +msgid "Open Games (all cards visible)" +msgstr "" + +#: pysollib/tk/selectgame.py:212 +msgid "Relaxed Variants" +msgstr "" + +#: pysollib/tk/selectgame.py:351 +msgid "About game" +msgstr "" + +#: pysollib/tk/selectgame.py:364 +msgid "Name:" +msgstr "" + +#: pysollib/tk/selectgame.py:365 +msgid "Alternate names:" +msgstr "" + +#: pysollib/tk/selectgame.py:366 +msgid "Category:" +msgstr "" + +#: pysollib/tk/selectgame.py:368 +msgid "Decks:" +msgstr "" + +#: pysollib/tk/selectgame.py:369 +msgid "Redeals:" +msgstr "" + +#: pysollib/tk/selectgame.py:371 +msgid "Played:" +msgstr "" + +#: pysollib/tk/selectgame.py:372 pysollib/tk/tkstats.py:111 +#: pysollib/tk/tkstats.py:163 +msgid "Won:" +msgstr "" + +#: pysollib/tk/selectgame.py:373 pysollib/tk/tkstats.py:112 +#: pysollib/tk/tkstats.py:164 +msgid "Lost:" +msgstr "" + +#: pysollib/tk/selectgame.py:374 pysollib/tk/tkstats.py:804 +msgid "Playing time:" +msgstr "" + +#: pysollib/tk/selectgame.py:375 pysollib/tk/tkstats.py:811 +msgid "Moves:" +msgstr "" + +#: pysollib/tk/selectgame.py:376 +msgid "% won:" +msgstr "" + +#: pysollib/tk/selectgame.py:409 +msgid "Select" +msgstr "" + +#: pysollib/tk/selectgame.py:409 pysollib/tk/toolbar.py:195 +msgid "Rules" +msgstr "" + +#: pysollib/tk/selectgame.py:490 +msgid "Playable Preview - " +msgstr "" + +#: pysollib/tk/selectgame.py:538 +msgid "variable" +msgstr "" + +#: pysollib/tk/selectgame.py:539 +msgid "unlimited" +msgstr "" + +#: pysollib/tk/selecttile.py:80 +msgid "(no tiles)" +msgstr "" + +#: pysollib/tk/selecttile.py:84 +msgid "Solid Colors" +msgstr "" + +#: pysollib/tk/selecttile.py:85 +msgid "Blue" +msgstr "" + +#: pysollib/tk/selecttile.py:87 +msgid "Navy" +msgstr "" + +#: pysollib/tk/selecttile.py:90 +msgid "Teal" +msgstr "" + +#: pysollib/tk/selecttile.py:92 +msgid "All Backgrounds" +msgstr "" + +#: pysollib/tk/selecttile.py:158 +msgid "Solid color..." +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:76 +msgid "Sound enabled" +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:82 +msgid "Use DirectX for sound playing" +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:88 +msgid "Sample volume" +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:94 +msgid "Music volume" +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:106 +msgid "Apply" +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:106 pysollib/tk/soundoptionsdialog.py:108 +msgid "Mixer..." +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:155 +msgid "Sound preferences info" +msgstr "" + +#: pysollib/tk/soundoptionsdialog.py:156 +msgid "" +"Changing DirectX settings will take effect\n" +"the next time you restart " +msgstr "" + +#: pysollib/tk/statusbar.py:135 +msgid "Moves/Total moves" +msgstr "" + +#: pysollib/tk/statusbar.py:137 +msgid "Games played: won/lost" +msgstr "" + +#: pysollib/tk/timeoutsdialog.py:65 +msgid "Demo:" +msgstr "" + +#: pysollib/tk/timeoutsdialog.py:66 +msgid "Hint:" +msgstr "" + +#: pysollib/tk/timeoutsdialog.py:67 +msgid "Raise card:" +msgstr "" + +#: pysollib/tk/timeoutsdialog.py:69 +msgid "Highlight cards:" +msgstr "" + +#: pysollib/tk/timeoutsdialog.py:70 +msgid "Highlight same rank:" +msgstr "" + +#: pysollib/tk/tkconst.py:104 +msgid "Icons only" +msgstr "" + +#: pysollib/tk/tkconst.py:105 +msgid "Text below icons" +msgstr "" + +#: pysollib/tk/tkconst.py:106 +msgid "Text beside icons" +msgstr "" + +#: pysollib/tk/tkconst.py:107 +msgid "Text only" +msgstr "" + +#: pysollib/tk/tkhtml.py:280 +msgid "Index" +msgstr "" + +#: pysollib/tk/tkhtml.py:284 +msgid "Back" +msgstr "" + +#: pysollib/tk/tkhtml.py:288 +msgid "Forward" +msgstr "" + +#: pysollib/tk/tkhtml.py:292 +msgid "Close" +msgstr "" + +#: pysollib/tk/tkhtml.py:394 +msgid "" +" HTML limitation:\n" +"The %s protocol is not supported yet.\n" +"\n" +"Please use your standard web browser\n" +"to open the following URL:\n" +"%s\n" +msgstr "" + +#: pysollib/tk/tkhtml.py:419 pysollib/tk/tkhtml.py:423 +msgid "" +"Unable to service request:\n" +msgstr "" + +#: pysollib/tk/tkstats.py:95 +msgid "Total" +msgstr "" + +#: pysollib/tk/tkstats.py:97 +msgid "Current session" +msgstr "" + +#: pysollib/tk/tkstats.py:113 pysollib/tk/tkstats.py:165 +msgid "Total:" +msgstr "" + +#: pysollib/tk/tkstats.py:278 +msgid "No games" +msgstr "" + +#: pysollib/tk/tkstats.py:291 +msgid "Reset..." +msgstr "" + +#: pysollib/tk/tkstats.py:574 pysollib/tk/tkstats.py:647 +#: pysollib/tk/tkstats.py:663 +msgid "Save to file" +msgstr "" + +#: pysollib/tk/tkstats.py:575 +msgid "Reset all..." +msgstr "" + +#: pysollib/tk/tkstats.py:625 +msgid "No entries for player " +msgstr "" + +#: pysollib/tk/tkstats.py:642 +msgid "" +"No log entries for %s\n" +msgstr "" + +#: pysollib/tk/tkstats.py:658 +msgid "" +"No current session log entries for %s\n" +msgstr "" + +#: pysollib/tk/tkstats.py:678 +msgid "Highlight piles: " +msgstr "" + +#: pysollib/tk/tkstats.py:679 +msgid "Highlight cards: " +msgstr "" + +#: pysollib/tk/tkstats.py:680 +msgid "Highlight same rank: " +msgstr "" + +#: pysollib/tk/tkstats.py:683 +msgid "" +"\n" +"Redeals: " +msgstr "" + +#: pysollib/tk/tkstats.py:684 +msgid "" +"\n" +"Cards in Talon: " +msgstr "" + +#: pysollib/tk/tkstats.py:686 +msgid "" +"\n" +"Cards in Waste: " +msgstr "" + +#: pysollib/tk/tkstats.py:688 +msgid "" +"\n" +"Cards in Foundations: " +msgstr "" + +#: pysollib/tk/tkstats.py:691 +msgid "Game status" +msgstr "" + +#: pysollib/tk/tkstats.py:694 +msgid "Playing time: " +msgstr "" + +#: pysollib/tk/tkstats.py:695 +msgid "Started at: " +msgstr "" + +#: pysollib/tk/tkstats.py:696 +msgid "Moves: " +msgstr "" + +#: pysollib/tk/tkstats.py:697 +msgid "Undo moves: " +msgstr "" + +#: pysollib/tk/tkstats.py:698 +msgid "Bookmark moves: " +msgstr "" + +#: pysollib/tk/tkstats.py:699 +msgid "Demo moves: " +msgstr "" + +#: pysollib/tk/tkstats.py:700 +msgid "Total player moves: " +msgstr "" + +#: pysollib/tk/tkstats.py:701 +msgid "Total moves in this game: " +msgstr "" + +#: pysollib/tk/tkstats.py:702 +msgid "Hints: " +msgstr "" + +#: pysollib/tk/tkstats.py:706 +msgid "Statistics..." +msgstr "" + +#: pysollib/tk/tkstats.py:731 +msgid "N" +msgstr "" + +#: pysollib/tk/tkstats.py:737 +msgid "Started at" +msgstr "" + +#: pysollib/tk/tkstats.py:740 +msgid "Result" +msgstr "" + +#: pysollib/tk/tkstats.py:796 +msgid "Minimum" +msgstr "" + +#: pysollib/tk/tkstats.py:797 +msgid "Maximum" +msgstr "" + +#: pysollib/tk/tkstats.py:798 +msgid "Average" +msgstr "" + +#: pysollib/tk/tkstats.py:818 +msgid "Total moves:" +msgstr "" + +#: pysollib/tk/tkstats.py:849 +msgid "No TOP for this game" +msgstr "" + +#: pysollib/tk/toolbar.py:183 +msgid "New" +msgstr "" + +#: pysollib/tk/toolbar.py:184 +msgid "" +"Restart the\n" +"current game" +msgstr "" + +#: pysollib/tk/toolbar.py:186 +msgid "Open" +msgstr "" + +#: pysollib/tk/toolbar.py:186 +msgid "" +"Open a\n" +"saved game" +msgstr "" + +#: pysollib/tk/toolbar.py:187 +msgid "Save" +msgstr "" + +#: pysollib/tk/toolbar.py:187 +msgid "Save game" +msgstr "" + +#: pysollib/tk/toolbar.py:189 +msgid "Undo" +msgstr "" + +#: pysollib/tk/toolbar.py:189 +msgid "Undo last move" +msgstr "" + +#: pysollib/tk/toolbar.py:190 +msgid "Redo" +msgstr "" + +#: pysollib/tk/toolbar.py:190 +msgid "Redo last move" +msgstr "" + +#: pysollib/tk/toolbar.py:191 +msgid "Auto drop cards" +msgstr "" + +#: pysollib/tk/toolbar.py:191 +msgid "Autodrop" +msgstr "" + +#: pysollib/tk/toolbar.py:192 +msgid "Pause" +msgstr "" + +#: pysollib/tk/toolbar.py:192 +msgid "Pause game" +msgstr "" + +#: pysollib/tk/toolbar.py:194 +msgid "View statistics" +msgstr "" + +#: pysollib/tk/toolbar.py:195 +msgid "Rules for this game" +msgstr "" + +#: pysollib/tk/toolbar.py:209 +msgid "Player" +msgstr "" + +#: pysollib/tk/toolbar.py:210 +msgid "Player options" +msgstr "" + +#: pysollib/tk/toolbar.py:428 +msgid "Toolbar" +msgstr "" + +#: pysollib/util.py:76 +msgid "Club" +msgstr "" + +#: pysollib/util.py:76 +msgid "Diamond" +msgstr "" + +#: pysollib/util.py:76 +msgid "Heart" +msgstr "" + +#: pysollib/util.py:76 +msgid "Spade" +msgstr "" + +#: pysollib/util.py:77 +msgid "black" +msgstr "" + +#: pysollib/util.py:77 +msgid "red" +msgstr "" + +#: pysollib/util.py:102 +msgid "cardset" +msgstr "" + diff --git a/po/ru_games.po b/po/ru_games.po new file mode 100644 index 0000000000..e6f2588239 --- /dev/null +++ b/po/ru_games.po @@ -0,0 +1,3188 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PySol 0.0.1\n" +"POT-Creation-Date: Fri May 26 20:25:43 2006\n" +"PO-Revision-Date: 2006-05-13 17:41+0400\n" +"Last-Translator: Скоморох \n" +"Language-Team: Russian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Generated-By: ./scripts/all_games.py 0.1\n" + +msgid " 3x3 Matrix" +msgstr "Матрица 3x3" + +msgid " 4x4 Matrix" +msgstr "Матрица 4x4" + +msgid " 5x5 Matrix" +msgstr "Матрица 5x5" + +msgid " 6x6 Matrix" +msgstr "Матрица 6x6" + +msgid " 7x7 Matrix" +msgstr "Матрица 7x7" + +msgid " 8x8 Matrix" +msgstr "Матрица 8x8" + +msgid " 9x9 Matrix" +msgstr "Матрица 9x9" + +msgid "10 x 8" +msgstr "10 x 8" + +msgid "10x10 Matrix" +msgstr "Матрица 10x10" + +msgid "8 x 8" +msgstr "8 x 8" + +msgid "Abacus" +msgstr "Абак" + +#, fuzzy +msgid "Aces High" +msgstr "Тузы вверх" + +msgid "Aces Up" +msgstr "Тузы вверх" + +msgid "Aces Up 5" +msgstr "Тузы вверх 5" + +msgid "Achtmal Acht" +msgstr "" + +msgid "Acme" +msgstr "" + +#, fuzzy +msgid "Adelaide" +msgstr "Поляна" + +msgid "Agnes Bernauer" +msgstr "Агнесса Берно" + +msgid "Agnes Sorel" +msgstr "Агнесса Сорел" + +msgid "Akbar's Conquest" +msgstr "" + +msgid "Akbar's Triumph" +msgstr "" + +msgid "Alaska" +msgstr "Аляска" + +msgid "Algerian Patience" +msgstr "Алжирский пасьянс" + +#, fuzzy +msgid "Algerian Patience (3 decks)" +msgstr "Алжирский пасьянс" + +msgid "Alhambra" +msgstr "Алхамбра" + +msgid "All in a Row" +msgstr "" + +msgid "Altar" +msgstr "Алтарь" + +msgid "Alternation" +msgstr "Чередование" + +msgid "Amazons" +msgstr "Амазонки" + +msgid "American Toad" +msgstr "Американская жаба" + +msgid "Another Round" +msgstr "Другой Раунд" + +msgid "Appachan's Waterfall" +msgstr "Апачианский водопад" + +msgid "Applegate" +msgstr "" + +msgid "Aqab's" +msgstr "" + +msgid "Arachnida" +msgstr "" + +msgid "Arena" +msgstr "Арена" + +msgid "Arena 2" +msgstr "Арена 2" + +msgid "Arizona" +msgstr "Аризона" + +msgid "Arrow" +msgstr "Стрела" + +msgid "Art Moderne" +msgstr "Современное искусство" + +msgid "Ashrafi" +msgstr "" + +msgid "Ashta Dikapala" +msgstr "" + +msgid "Ashwapati" +msgstr "" + +msgid "Auld Lang Syne" +msgstr "Старые добрые времена" + +msgid "Aunt Mary" +msgstr "" + +#, fuzzy +msgid "Australian Patience" +msgstr "Русский пасьянс" + +#, fuzzy +msgid "Baby Spiderette" +msgstr "Паучок" + +msgid "Backbone" +msgstr "Основа" + +msgid "Backbone +" +msgstr "Основа +" + +msgid "Bad Seven" +msgstr "Плохая семёрка" + +msgid "Baker's Dozen" +msgstr "Чёртова дюжина" + +#, fuzzy +msgid "Baker's Game" +msgstr "Чёртова дюжина" + +msgid "Balance" +msgstr "Баланс" + +msgid "Balarama" +msgstr "" + +msgid "Bastion" +msgstr "Бастион" + +msgid "Bat" +msgstr "Летучая мышь" + +#, fuzzy +msgid "Bath" +msgstr "Летучая мышь" + +msgid "Batsford" +msgstr "Бетсфорд" + +#, fuzzy +msgid "Bavarian Patience" +msgstr "Алжирский пасьянс" + +msgid "Beak and Flipper" +msgstr "Клюв и ласты" + +msgid "Beatle" +msgstr "Жук" + +msgid "Beleaguered Castle" +msgstr "Осаждённый замок" + +msgid "Belvedere" +msgstr "Бельведер" + +msgid "Betsy Ross" +msgstr "Бетси Росс" + +#, fuzzy +msgid "Big Easy" +msgstr "Большая арфа" + +msgid "Big Flying Dragon" +msgstr "Большой Летящий Дракон" + +#, fuzzy +msgid "Big Forty" +msgstr "Форт" + +msgid "Big Harp" +msgstr "Большая арфа" + +msgid "Big Hole" +msgstr "Большая дыра" + +msgid "Big Mountain" +msgstr "Большая гора" + +msgid "Big Spider" +msgstr "Большой паук" + +#, fuzzy +msgid "Big Spider (1 suit)" +msgstr "Паук (1 масть)" + +#, fuzzy +msgid "Big Spider (2 suits)" +msgstr "Паук (2 масти)" + +#, fuzzy +msgid "Big Sumo" +msgstr "Большая дыра" + +msgid "Bim Bom" +msgstr "" + +msgid "Bisley" +msgstr "Бисли" + +msgid "Bits n Bytes" +msgstr "Биты и Байты" + +msgid "Bizarre" +msgstr "Причудливый" + +msgid "Black Hole" +msgstr "Чёрная дыра" + +msgid "Black Widow" +msgstr "Чёрная вдова" + +msgid "Blind Alleys" +msgstr "Тёмные аллеи" + +msgid "Blockade" +msgstr "Блокада" + +msgid "Blondes and Brunettes" +msgstr "Блондинки и Брюнетки" + +msgid "Blue Moon" +msgstr "Голубая луна" + +msgid "Boar" +msgstr "Боров" + +msgid "Boat" +msgstr "Лодка" + +msgid "Boudoir" +msgstr "Будуар" + +msgid "Box Fan" +msgstr "Коробка для веера" + +msgid "Box Kite" +msgstr "" + +msgid "Braid" +msgstr "Коса" + +#, fuzzy +msgid "Bridesmaids" +msgstr "Коса" + +msgid "Bridge" +msgstr "Мост" + +msgid "Bridge 2" +msgstr "Мост 2" + +#, fuzzy +msgid "Bridget's Game" +msgstr "Мост 2" + +msgid "Bridget's Game Doubled" +msgstr "" + +msgid "Brigade" +msgstr "Бригада" + +msgid "Bristol" +msgstr "Бристоль" + +msgid "British Constitution" +msgstr "Британская конституция" + +#, fuzzy +msgid "British Square" +msgstr "Восемь квадратов" + +msgid "Brunswick" +msgstr "Брюнсвик" + +msgid "Buffalo Bill" +msgstr "Буффало Билл" + +msgid "Bug" +msgstr "Клоп" + +#, fuzzy +msgid "Busy Aces" +msgstr "Русские тузы" + +msgid "Butterfly" +msgstr "Бабочка" + +msgid "Butterfly 2" +msgstr "Бабочка 2" + +msgid "Calculation" +msgstr "Вычисление" + +msgid "Camelot" +msgstr "Камелот" + +msgid "Canfield" +msgstr "Кенфилд" + +msgid "Canister" +msgstr "Коробочка" + +msgid "Capricieuse" +msgstr "Каприз" + +msgid "Captive Queens" +msgstr "Пленённые королевы" + +msgid "Carlton" +msgstr "Карлтон" + +msgid "Carpet" +msgstr "Ковёр" + +msgid "Carre Napoleon" +msgstr "Каре Наполеона" + +msgid "Carthage" +msgstr "Карфаген" + +msgid "Casino Klondike" +msgstr "Казино Клондайк" + +msgid "Castle" +msgstr "Замок" + +msgid "Castle of Indolence" +msgstr "Замок праздности" + +msgid "Castles in Spain" +msgstr "Замок в Испании" + +msgid "Cat and Mouse" +msgstr "Кот и Мышь" + +msgid "Cat's Tail" +msgstr "Кошачий хвост" + +msgid "Cavalier" +msgstr "Рыцарь" + +msgid "Cell 11" +msgstr "" + +msgid "Ceremonial" +msgstr "Церемониал" + +msgid "Challenge FreeCell" +msgstr "Сложная Свободная ячейка" + +msgid "Chamberlain" +msgstr "Камергер" + +msgid "Chameleon" +msgstr "Хамелеон" + +msgid "Checkered" +msgstr "Клетчатый" + +msgid "Chelicera" +msgstr "Хелицера" + +msgid "Chequers" +msgstr "Шахматный порядок" + +msgid "Cherry Bomb" +msgstr "Хлопушка" + +#, fuzzy +msgid "ChessMania" +msgstr "Маджонг ChessMania" + +msgid "Chessboard" +msgstr "Шахматная доска" + +msgid "Chinese Discipline" +msgstr "Китайский порядок" + +msgid "Chinese Solitaire" +msgstr "Китайский пасьянс" + +msgid "Chip" +msgstr "Щепка" + +msgid "Cicely" +msgstr "Кервель" + +msgid "Citadel" +msgstr "Цитадель" + +msgid "Clink" +msgstr "Застенок" + +msgid "Clover Leaf" +msgstr "Лепесток клевера" + +msgid "Cluitjar's Lair" +msgstr "" + +msgid "Cockroach" +msgstr "Таракан" + +msgid "Colorado" +msgstr "Колорадо" + +msgid "Columns" +msgstr "Столбцы" + +msgid "Concentration" +msgstr "Концентрация" + +msgid "Cone" +msgstr "Конус" + +msgid "Congress" +msgstr "Конгресс" + +msgid "Contradance" +msgstr "Контрданс" + +msgid "Convolution" +msgstr "Виток" + +msgid "Corkscrew" +msgstr "Штопор" + +msgid "Corners" +msgstr "Углы" + +msgid "Corona" +msgstr "Корона" + +msgid "Courtyard" +msgstr "Внутренний двор" + +msgid "Cross" +msgstr "Крест" + +msgid "Crown" +msgstr "Венец" + +msgid "Cruel" +msgstr "Изнурительный" + +msgid "Cupido's Heart" +msgstr "Сердце Купидона" + +msgid "Cupola" +msgstr "Купол" + +msgid "Curds and Whey" +msgstr "Творог и сыворотка" + +#, fuzzy +msgid "Danda" +msgstr "Алмаз" + +msgid "Dashavatara" +msgstr "Дашаватара" + +#, fuzzy +msgid "Dashavatara Circles" +msgstr "Дашаватара" + +msgid "Dead King Golf" +msgstr "Гольф Смертельный Король" + +#, fuzzy +msgid "Deep" +msgstr "Глубокий колодец" + +msgid "Deep Well" +msgstr "Глубокий колодец" + +msgid "Der Katzenschwanz" +msgstr "" + +msgid "Der Zopf" +msgstr "" + +#, fuzzy +msgid "Der freie Napoleon" +msgstr "Свободный Наполеон" + +#, fuzzy +msgid "Der kleine Napoleon" +msgstr "Свободный Наполеон" + +msgid "Der lange Zopf" +msgstr "" + +#, fuzzy +msgid "Der letzte Monarch" +msgstr "Последний ,Монарх" + +msgid "Deuces" +msgstr "Двойки" + +msgid "Dhanpati" +msgstr "" + +msgid "Diamond" +msgstr "Алмаз" + +msgid "Die Bildgallerie" +msgstr "" + +msgid "Die Königsbergerin" +msgstr "" + +msgid "Die Russische" +msgstr "" + +msgid "Die Schlange" +msgstr "" + +msgid "Die böse Sieben" +msgstr "" + +msgid "Die große Harfe" +msgstr "" + +msgid "Die kleine Harfe" +msgstr "" + +msgid "Diplomat" +msgstr "Дипломат" + +msgid "Dog" +msgstr "Пёс" + +msgid "Dojouji's Game" +msgstr "" + +msgid "Dojouji's Game Doubled" +msgstr "" + +msgid "Double Bisley" +msgstr "Двойной Бисли" + +msgid "Double Canfield" +msgstr "Двойной Кенфилд" + +msgid "Double Cockroach" +msgstr "Двойной таракан" + +#, fuzzy +msgid "Double Dot" +msgstr "Дубликаты" + +msgid "Double Drawbridge" +msgstr "Двойной разводной мост" + +#, fuzzy +msgid "Double Easthaven" +msgstr "Двойной кузнечик" + +msgid "Double FreeCell" +msgstr "Двойная свободная ячейка" + +msgid "Double Grasshopper" +msgstr "Двойной кузнечик" + +msgid "Double Klondike" +msgstr "Двойной Клондайк" + +msgid "Double Klondike by Threes" +msgstr "Двойной Клондайк по три" + +msgid "Double Mahjongg Big Castle" +msgstr "Двойной Маджонг Большой замок" + +msgid "Double Mahjongg Big Flying Dragon" +msgstr "Двойной Маджонг Большой Летящий Дракон" + +msgid "Double Mahjongg Eight Squares" +msgstr "Двойной Маджонг Восемь квадратов" + +msgid "Double Mahjongg Faro" +msgstr "Двойной Маджонг Фараон" + +msgid "Double Mahjongg Roost" +msgstr "Двойной Маджонг Насест" + +msgid "Double Mahjongg Sphere" +msgstr "Двойной Маджонг Сфера" + +msgid "Double Mahjongg Twin Picks" +msgstr "Двойной Маджонг Двойная вершина" + +msgid "Double Mahjongg Two Squares" +msgstr "Двойной Маджонг Два квадрата" + +msgid "Double Rail" +msgstr "Двойные рельсы" + +#, fuzzy +msgid "Double Samuri" +msgstr "Двойные рельсы" + +#, fuzzy +msgid "Double Your Fun" +msgstr "Двойной Юкон" + +msgid "Double Yukon" +msgstr "Двойной Юкон" + +msgid "Doublets" +msgstr "Дубликаты" + +msgid "Dover" +msgstr "Довер" + +msgid "Dragon" +msgstr "Дракон" + +msgid "Dragon 2" +msgstr "Дракон 2" + +msgid "Drawbridge" +msgstr "Разводной мост" + +msgid "Dress Parade" +msgstr "Показ мод" + +msgid "Drivel" +msgstr "Бессмыслица" + +msgid "Dude" +msgstr "Пижон" + +msgid "Duke" +msgstr "Герцог" + +msgid "Dumfries" +msgstr "" + +msgid "Eagle Wing" +msgstr "Крыло орла" + +msgid "Eastcliff" +msgstr "" + +msgid "Easthaven" +msgstr "" + +msgid "Easy Supreme" +msgstr "" + +msgid "Easy x One" +msgstr "" + +msgid "Egyptian Solitaire" +msgstr "Египетский пасьянс" + +msgid "Eiffel Tower" +msgstr "Эйфелева башня" + +msgid "Eight Legions" +msgstr "Восемь легионов" + +msgid "Eight Off" +msgstr "" + +msgid "Eight Squares" +msgstr "Восемь квадратов" + +msgid "Eight Times Eight" +msgstr "Восемь раз по восемь" + +msgid "Elevator" +msgstr "Лифт" + +msgid "Emperor" +msgstr "Император" + +msgid "Empty Pyramids" +msgstr "Пустые пирамиды" + +msgid "Enterprise" +msgstr "Предприятие" + +msgid "Escalator" +msgstr "Эскалатор" + +#, fuzzy +msgid "Eularia" +msgstr "Мария" + +msgid "Excuse" +msgstr "" + +msgid "Eye" +msgstr "Глаз" + +#, fuzzy +msgid "F-15 Eagle" +msgstr "Маджонг F-15 Eagle" + +msgid "Fair Lucy" +msgstr "" + +#, fuzzy +msgid "Fairest" +msgstr "Ковёр" + +msgid "Falling Star" +msgstr "Падающая звезда" + +msgid "Fan" +msgstr "Веер" + +msgid "Farandole" +msgstr "Фарандола" + +msgid "Faro" +msgstr "Фараон" + +msgid "Fastness" +msgstr "Цитадель" + +#, fuzzy +msgid "Fatimeh's Game" +msgstr "Бабушкина игра" + +msgid "Fatimeh's Game Relaxed" +msgstr "" + +#, fuzzy +msgid "Fifteen Puzzle" +msgstr "Пятнашки" + +msgid "Fifteen plus" +msgstr "Пятнадцать плюс" + +msgid "Firecracker" +msgstr "Хлопушка" + +msgid "First Law" +msgstr "Фундаментальный закон" + +msgid "Fish" +msgstr "Рыба" + +#, fuzzy +msgid "Fish face" +msgstr "Маджонг Fish face" + +msgid "Five Aces" +msgstr "Пять тузов" + +msgid "Five Pyramids" +msgstr "Пять пирамид" + +msgid "Floating City" +msgstr "Плавающий город" + +msgid "Flower Arrangement" +msgstr "Аранжировка цветов" + +msgid "Flower Clock" +msgstr "Цветочные часы" + +msgid "Flower Garden" +msgstr "Цветочный сад" + +msgid "Flowers" +msgstr "Цветы" + +msgid "Fly" +msgstr "Полёт" + +msgid "Flying Dragon" +msgstr "Летящий дракон" + +#, fuzzy +msgid "ForeCell" +msgstr "Свободная ячейка" + +msgid "Fort" +msgstr "Форт" + +msgid "Fortress" +msgstr "Крепость" + +msgid "Fortress Towers" +msgstr "Крепостные башни" + +#, fuzzy +msgid "Fortune's Favor" +msgstr "Судьба" + +msgid "Fortunes" +msgstr "Судьба" + +msgid "Forty Thieves" +msgstr "Сорок разбойников" + +msgid "Forty and Eight" +msgstr "Сорок и восемь" + +#, fuzzy +msgid "Four Colours" +msgstr "Четырёхлистный клевер" + +msgid "Four Kings" +msgstr "Четыре короля" + +msgid "Four Leaf Clovers" +msgstr "Четырёхлистный клевер" + +msgid "Four Seasons" +msgstr "Четыре сезона" + +msgid "Four Stacks" +msgstr "Четыре кучи" + +msgid "Four Winds" +msgstr "Четыре ветра" + +msgid "Fourteen" +msgstr "Четырнадцать" + +#, fuzzy +msgid "Fred's Spider" +msgstr "Смягчённый Паук" + +#, fuzzy +msgid "Fred's Spider (3 decks)" +msgstr "Церлин (3 колоды)" + +msgid "Free Fan" +msgstr "Свободный веер" + +msgid "Free Napoleon" +msgstr "Свободный Наполеон" + +msgid "FreeCell" +msgstr "Свободная ячейка" + +msgid "Frog" +msgstr "Лягушка" + +#, fuzzy +msgid "Full Vision" +msgstr "Маджонг Full Vision" + +#, fuzzy +msgid "Full Vision 2" +msgstr "Маджонг Full Vision 2" + +msgid "Future" +msgstr "Будущее" + +msgid "Gajapati" +msgstr "" + +msgid "Gaji" +msgstr "" + +msgid "Galary" +msgstr "Галерея" + +msgid "Galloway" +msgstr "Шотландская лошадка" + +msgid "Gaps" +msgstr "Пробелы" + +msgid "Garden" +msgstr "Сад" + +msgid "Gargantua" +msgstr "Гаргантюа" + +msgid "Garhpati" +msgstr "" + +msgid "Gate" +msgstr "Ворота" + +#, fuzzy +msgid "Gayle's" +msgstr "Маджонг Gayle's" + +msgid "General's Patience" +msgstr "Генеральский пасьянс" + +msgid "Genesis" +msgstr "Происхождение" + +msgid "Genesis +" +msgstr "Происхождение +" + +#, fuzzy +msgid "German Patience" +msgstr "Алжирский пасьянс" + +msgid "Ghulam" +msgstr "" + +msgid "Giant" +msgstr "Великан" + +msgid "Glade" +msgstr "Поляна" + +msgid "Glenwood" +msgstr "Гленвуд" + +msgid "Gloaming" +msgstr "Вечерние сумерки" + +msgid "Gloria" +msgstr "Глория" + +msgid "Gnat" +msgstr "Комар" + +msgid "Golf" +msgstr "Гольф" + +msgid "Good Measure" +msgstr "Полная мера" + +msgid "Grampus" +msgstr "Касатка" + +msgid "Grandfather" +msgstr "Дедушка" + +msgid "Grandfather's Clock" +msgstr "Дедушкины часы" + +msgid "Grandmother's Game" +msgstr "Бабушкина игра" + +msgid "Grasshopper" +msgstr "Кузнечик" + +msgid "Great Wall" +msgstr "Великая стена" + +msgid "Great Wheel" +msgstr "Великое Колесо" + +msgid "Greater Queue" +msgstr "Длинная коса" + +msgid "Griffon" +msgstr "Грифон" + +msgid "Ground for a Divorce" +msgstr "Повод для разрыва" + +msgid "Ground for a Divorce (3 decks)" +msgstr "Повод для разрыва (3 колоды)" + +#, fuzzy +msgid "Ground for a Divorce (4 decks)" +msgstr "Повод для разрыва (3 колоды)" + +msgid "Gypsy" +msgstr "Цыганский" + +#, fuzzy +msgid "H for Haga" +msgstr "Маджонг H for Haga" + +msgid "Half Mahjongg Happy New Year" +msgstr "Половинный Маджонг С Новым Годом" + +msgid "Half Mahjongg Smile" +msgstr "Половинный Маджонг Улыбка" + +msgid "Half Mahjongg Wall" +msgstr "Половинный Маджонг Стена" + +msgid "Hanoi Puzzle 4" +msgstr "Ханойская головоломка 4" + +msgid "Hanoi Puzzle 5" +msgstr "Ханойская головоломка 5" + +msgid "Hanoi Puzzle 6" +msgstr "Ханойская головоломка 6" + +msgid "Happy New Year" +msgstr "С Новым Годом" + +msgid "Hare" +msgstr "Заяц" + +msgid "Hayagriva" +msgstr "" + +msgid "Haystack" +msgstr "Стог сена" + +msgid "Heads and Tails" +msgstr "Головы и хвосты" + +msgid "Helios" +msgstr "Гелиос" + +msgid "Hex A Klon" +msgstr "" + +#, fuzzy +msgid "Hex A Klon by Threes" +msgstr "Клондайк по три" + +msgid "Hex Labyrinth" +msgstr "Шестнадцатеричный лабиринт" + +msgid "Hidden Passages" +msgstr "Тайные ходы" + +msgid "Hidden Words" +msgstr "Спрятанные слова" + +#, fuzzy +msgid "High and Low" +msgstr "Маджонг High and Low" + +msgid "Hiranyaksha" +msgstr "" + +msgid "Hopscotch" +msgstr "Классы" + +msgid "Horse" +msgstr "Лошадь" + +msgid "House in the Wood" +msgstr "Дом в лесу" + +msgid "House on the Hill" +msgstr "Дом на холме" + +msgid "Hovercraft" +msgstr "Ховеркрафт" + +msgid "Hurdles" +msgstr "Барьеры" + +msgid "Hurricane" +msgstr "Ураган" + +msgid "Idiot's Delight" +msgstr "Дурацкое удовольствие" + +#, fuzzy +msgid "Idle Aces" +msgstr "Пять тузов" + +msgid "IloveU" +msgstr "" + +msgid "Imperial Trumps" +msgstr "Имперские козыри" + +#, fuzzy +msgid "Inazuma" +msgstr "Маджонг Inazuma" + +msgid "Inca" +msgstr "" + +msgid "Indian" +msgstr "Индийский" + +#, fuzzy +msgid "Indian Patience" +msgstr "Русский пасьянс" + +msgid "Inner Circle" +msgstr "Внутренний круг" + +msgid "Intelligence" +msgstr "Смекалка" + +msgid "Intelligence +" +msgstr "Смекалка +" + +msgid "Interregnum" +msgstr "Междуцарствие" + +msgid "Iris" +msgstr "Ирис" + +msgid "Irmgard" +msgstr "" + +msgid "JPs" +msgstr "" + +msgid "Jamestown" +msgstr "Джеймстаун" + +msgid "Jane" +msgstr "Джейн" + +msgid "Japan" +msgstr "Япония" + +msgid "Japanese Garden" +msgstr "Японский сад" + +msgid "Japanese Garden II" +msgstr "Японский сад II" + +msgid "Japanese Garden III" +msgstr "Японский сад III" + +msgid "Joker" +msgstr "Джокер" + +msgid "Josephine" +msgstr "Жозефина" + +msgid "Journey to Cuddapah" +msgstr "Путешествие в Куддапах" + +msgid "Jumbo" +msgstr "Гигант" + +msgid "Jungle" +msgstr "Джунгли" + +msgid "Just For Fun" +msgstr "Просто для удовольствия" + +#, fuzzy +msgid "K for Kyodai" +msgstr "Маджонг K for Kyodai" + +msgid "Kali's Game" +msgstr "" + +msgid "Kali's Game Doubled" +msgstr "" + +msgid "Kali's Game Relaxed" +msgstr "" + +msgid "Kansas" +msgstr "Канзас" + +#, fuzzy +msgid "Katrina's Game" +msgstr "Бабушкина игра" + +msgid "Katrina's Game Doubled" +msgstr "" + +msgid "Katrina's Game Relaxed" +msgstr "" + +msgid "Khadga" +msgstr "" + +msgid "King Albert" +msgstr "Король Альберт" + +msgid "King Only Baker's Game" +msgstr "" + +msgid "King Only Hex A Klon" +msgstr "" + +msgid "Kingdom" +msgstr "Королевство" + +msgid "Kings" +msgstr "Короли" + +msgid "Kingsdown Eights" +msgstr "" + +msgid "Klondike" +msgstr "Клондайк" + +#, fuzzy +msgid "Klondike Plus 16" +msgstr "Клондайк" + +msgid "Klondike by Threes" +msgstr "Клондайк по три" + +msgid "Km" +msgstr "" + +msgid "Krebs" +msgstr "" + +msgid "Kujaku" +msgstr "" + +#, fuzzy +msgid "Kumo" +msgstr "Гигант" + +msgid "Kurma" +msgstr "" + +#, fuzzy +msgid "Kyodai 14" +msgstr "Маджонг Kyodai 14" + +#, fuzzy +msgid "Kyodai 17" +msgstr "Маджонг Kyodai 17" + +#, fuzzy +msgid "Kyodai 18" +msgstr "Маджонг Kyodai 18" + +#, fuzzy +msgid "Kyodai 20" +msgstr "Маджонг Kyodai 20" + +#, fuzzy +msgid "Kyodai 23" +msgstr "Маджонг Kyodai 23" + +#, fuzzy +msgid "Kyodai 24" +msgstr "Маджонг Kyodai 24" + +#, fuzzy +msgid "Kyodai 25" +msgstr "Маджонг Kyodai 25" + +#, fuzzy +msgid "Kyodai 26" +msgstr "Маджонг Kyodai 26" + +#, fuzzy +msgid "Kyodai 27" +msgstr "Маджонг Kyodai 27" + +#, fuzzy +msgid "Kyodai 28" +msgstr "Маджонг Kyodai 28" + +#, fuzzy +msgid "Kyodai 41" +msgstr "Маджонг Kyodai 4" + +#, fuzzy +msgid "Kyodai 42" +msgstr "Маджонг Kyodai 42" + +msgid "La Belle Lucie" +msgstr "Прекрасная Люси" + +msgid "La Nivernaise" +msgstr "" + +msgid "Labyrinth" +msgstr "Лабиринт" + +msgid "Lady Betty" +msgstr "Леди Бетти" + +msgid "Lady Palk" +msgstr "Леди Полк" + +msgid "Lady of the Manor" +msgstr "" + +msgid "Lanes" +msgstr "Тропинки" + +#, fuzzy +msgid "Lara's Game" +msgstr "Бабушкина игра" + +msgid "Lara's Game Doubled" +msgstr "" + +msgid "Lara's Game Relaxed" +msgstr "" + +msgid "Lattice" +msgstr "Решётка" + +msgid "Le Cadran" +msgstr "" + +msgid "Le Grande Teton" +msgstr "" + +msgid "Leo" +msgstr "Лев" + +msgid "Lesser Queue" +msgstr "Короткая коса" + +msgid "Lexington Harp" +msgstr "Лексингтонская арфа" + +msgid "Lily" +msgstr "Лили" + +msgid "Limited" +msgstr "Ограниченный" + +msgid "Lion" +msgstr "Лион" + +#, fuzzy +msgid "Little Billie" +msgstr "Малые ворота" + +#, fuzzy +msgid "Little Easy" +msgstr "Малые ворота" + +#, fuzzy +msgid "Little Forty" +msgstr "Малые ворота" + +msgid "Little Gate" +msgstr "Малые ворота" + +msgid "Long Braid" +msgstr "Долгая коса" + +msgid "Long Journey to Cuddapah" +msgstr "Долгое путешествие в Куддапах" + +msgid "Loose Ends" +msgstr "Свободные концы" + +msgid "Lost " +msgstr "Потеря" + +msgid "Lucas" +msgstr "Лукас" + +#, fuzzy +msgid "Mage's Game" +msgstr "Бабушкина игра" + +msgid "Mahjongg Altar" +msgstr "Маджонг Алтарь" + +msgid "Mahjongg Another Round" +msgstr "Маджонг Другой раунд" + +msgid "Mahjongg Aqab's" +msgstr "Маджонг Aqab's" + +msgid "Mahjongg Arena" +msgstr "Маджонг Арена" + +msgid "Mahjongg Arena 2" +msgstr "Маджонг Арена 2" + +msgid "Mahjongg Arrow" +msgstr "Маджонг Стрела" + +msgid "Mahjongg Art Moderne" +msgstr "Маджонг Современное искусство" + +msgid "Mahjongg Balance" +msgstr "Маджонг Баланс" + +msgid "Mahjongg Bat" +msgstr "Маджонг Летучая мышь" + +msgid "Mahjongg Beatle" +msgstr "Маджонг Жук" + +msgid "Mahjongg Big Hole" +msgstr "Маджонг Большая дыра" + +msgid "Mahjongg Big Mountain" +msgstr "Маджонг Большая гора" + +msgid "Mahjongg Bizarre" +msgstr "Маджонг Причудливый" + +msgid "Mahjongg Boar" +msgstr "Маджонг Боров" + +msgid "Mahjongg Boat" +msgstr "Маджонг Лодка" + +msgid "Mahjongg Bridge" +msgstr "Маджонг Мост" + +msgid "Mahjongg Bridge 2" +msgstr "Маджонг Мост 2" + +msgid "Mahjongg Bug" +msgstr "Маджонг Клоп" + +msgid "Mahjongg Butterfly" +msgstr "Маджонг Бабочка" + +msgid "Mahjongg Butterfly 2" +msgstr "Маджонг Бабочка 2" + +msgid "Mahjongg Castle" +msgstr "Маджонг Замок" + +msgid "Mahjongg Cat and Mouse" +msgstr "Маджонг Кот и Мышь" + +msgid "Mahjongg Ceremonial" +msgstr "Маджонг Церемониал" + +msgid "Mahjongg Checkered" +msgstr "Маджонг Клетчатый" + +msgid "Mahjongg ChessMania" +msgstr "Маджонг ChessMania" + +msgid "Mahjongg Chip" +msgstr "Маджонг Щепка" + +msgid "Mahjongg Columns" +msgstr "Маджонг Столбцы" + +msgid "Mahjongg Cross" +msgstr "Маджонг Крест" + +msgid "Mahjongg Crown" +msgstr "Маджонг Венец" + +msgid "Mahjongg Cupido's Heart" +msgstr "Маджонг Сердце Купидона" + +msgid "Mahjongg Cupola" +msgstr "Маджонг Купол" + +msgid "Mahjongg Deep Well" +msgstr "Маджонг Глубокий колодец" + +msgid "Mahjongg Diamond" +msgstr "Маджонг Алмаз" + +msgid "Mahjongg Dog" +msgstr "Маджонг Пёс" + +msgid "Mahjongg Dragon" +msgstr "Маджонг Дракон" + +msgid "Mahjongg Dragon 2" +msgstr "Маджонг Дракон 2" + +msgid "Mahjongg Dude" +msgstr "Маджонг Пижон" + +msgid "Mahjongg Empty Pyramids" +msgstr "Маджонг Пустые пирамиды" + +msgid "Mahjongg Enterprise" +msgstr "Маджонг Предприятие" + +msgid "Mahjongg Eye" +msgstr "Маджонг Глаз" + +msgid "Mahjongg F-15 Eagle" +msgstr "Маджонг F-15 Eagle" + +msgid "Mahjongg Farandole" +msgstr "Маджонг Фарандола" + +msgid "Mahjongg Fish" +msgstr "Маджонг Рыба" + +msgid "Mahjongg Fish face" +msgstr "Маджонг Fish face" + +msgid "Mahjongg Five Pyramids" +msgstr "Маджонг Пять пирамид" + +msgid "Mahjongg Floating City" +msgstr "Маджонг Плавающий город" + +msgid "Mahjongg Flowers" +msgstr "Маджонг Цветы" + +msgid "Mahjongg Flying Dragon" +msgstr "Маджонг Летящий Дракон" + +msgid "Mahjongg Fortress Towers" +msgstr "Маджонг Крепостные башни" + +msgid "Mahjongg Full Vision" +msgstr "Маджонг Full Vision" + +msgid "Mahjongg Full Vision 2" +msgstr "Маджонг Full Vision 2" + +msgid "Mahjongg Future" +msgstr "Маджонг Будущее" + +msgid "Mahjongg Garden" +msgstr "Маджонг Сад" + +msgid "Mahjongg Gayle's" +msgstr "Маджонг Gayle's" + +msgid "Mahjongg Glade" +msgstr "Маджонг Поляна" + +msgid "Mahjongg H for Haga" +msgstr "Маджонг H for Haga" + +msgid "Mahjongg Hare" +msgstr "Маджонг Заяц" + +msgid "Mahjongg Helios" +msgstr "Маджонг Гелиос" + +msgid "Mahjongg Hidden Words" +msgstr "Маджонг Спрятанные слова" + +msgid "Mahjongg High and Low" +msgstr "Маджонг High and Low" + +msgid "Mahjongg Horse" +msgstr "Маджонг Лошадь" + +msgid "Mahjongg Hovercraft" +msgstr "Маджонг Ховеркрафт" + +msgid "Mahjongg Hurdles" +msgstr "Маджонг Барьеры" + +msgid "Mahjongg Hurricane" +msgstr "Маджонг Ураган" + +msgid "Mahjongg IloveU" +msgstr "Маджонг IloveU" + +msgid "Mahjongg Inazuma" +msgstr "Маджонг Inazuma" + +msgid "Mahjongg Inca" +msgstr "Маджонг Inca" + +msgid "Mahjongg Inner Circle" +msgstr "Маджонг Внутренний круг" + +msgid "Mahjongg JPs" +msgstr "Маджонг JPs" + +msgid "Mahjongg Japan" +msgstr "Маджонг Япония" + +msgid "Mahjongg Joker" +msgstr "Маджонг Джокер" + +msgid "Mahjongg K for Kyodai" +msgstr "Маджонг K for Kyodai" + +msgid "Mahjongg Km" +msgstr "Маджонг Km" + +msgid "Mahjongg Krebs" +msgstr "Маджонг Krebs" + +msgid "Mahjongg Kujaku" +msgstr "Маджонг Kujaku" + +msgid "Mahjongg Kumo" +msgstr "Маджонг Kumo" + +msgid "Mahjongg Kyodai 14" +msgstr "Маджонг Kyodai 14" + +msgid "Mahjongg Kyodai 17" +msgstr "Маджонг Kyodai 17" + +msgid "Mahjongg Kyodai 18" +msgstr "Маджонг Kyodai 18" + +msgid "Mahjongg Kyodai 20" +msgstr "Маджонг Kyodai 20" + +msgid "Mahjongg Kyodai 23" +msgstr "Маджонг Kyodai 23" + +msgid "Mahjongg Kyodai 24" +msgstr "Маджонг Kyodai 24" + +msgid "Mahjongg Kyodai 25" +msgstr "Маджонг Kyodai 25" + +msgid "Mahjongg Kyodai 26" +msgstr "Маджонг Kyodai 26" + +msgid "Mahjongg Kyodai 27" +msgstr "Маджонг Kyodai 27" + +msgid "Mahjongg Kyodai 28" +msgstr "Маджонг Kyodai 28" + +msgid "Mahjongg Kyodai 41" +msgstr "Маджонг Kyodai 4" + +msgid "Mahjongg Kyodai 42" +msgstr "Маджонг Kyodai 42" + +msgid "Mahjongg Labyrinth" +msgstr "Маджонг Лабиринт" + +msgid "Mahjongg Lattice" +msgstr "Маджонг Решётка" + +msgid "Mahjongg Leo" +msgstr "Маджонг Лев" + +msgid "Mahjongg Lion" +msgstr "Маджонг Лион" + +msgid "Mahjongg Loose Ends" +msgstr "Маджонг Свободные концы" + +msgid "Mahjongg Lost " +msgstr "Маджонг Потеря" + +msgid "Mahjongg Maya" +msgstr "Маджонг Майя" + +msgid "Mahjongg Mesh" +msgstr "Маджонг Западня" + +msgid "Mahjongg Mini Traditional" +msgstr "Маджонг Mini Traditional" + +msgid "Mahjongg Mini-Layout" +msgstr "Маджонг Mini-Layout" + +msgid "Mahjongg Mission Impossible" +msgstr "Маджонг Миссия невыполнима" + +msgid "Mahjongg Monkey" +msgstr "Маджонг Обезьяна" + +msgid "Mahjongg Moth" +msgstr "Маджонг Мотылёк" + +msgid "Mahjongg Multi X" +msgstr "Маджонг Multi X" + +msgid "Mahjongg N for Namida" +msgstr "Маджонг N for Namida" + +msgid "Mahjongg New Layout" +msgstr "Маджонг New Layout" + +msgid "Mahjongg Okie's Nitemare" +msgstr "Маджонг Okie's Nitemare" + +msgid "Mahjongg Orbital" +msgstr "Маджонг Орбитальный" + +msgid "Mahjongg Order" +msgstr "Маджонг Порядок" + +msgid "Mahjongg Owl" +msgstr "Маджонг Сова" + +msgid "Mahjongg Ox" +msgstr "Маджонг Бык" + +msgid "Mahjongg Pantheon" +msgstr "Маджонг Пантеон" + +msgid "Mahjongg Papillon" +msgstr "Маджонг Папильотка" + +msgid "Mahjongg Pattern" +msgstr "Маджонг Образец" + +msgid "Mahjongg Portal" +msgstr "Маджонг Портал" + +msgid "Mahjongg Pyramid 1" +msgstr "Маджонг Пирамида 1" + +msgid "Mahjongg Pyramid 2" +msgstr "Маджонг Пирамида 2" + +msgid "Mahjongg Quad" +msgstr "Маджонг Quad" + +msgid "Mahjongg Ram" +msgstr "Маджонг Овен" + +msgid "Mahjongg Rat" +msgstr "Маджонг Крыса" + +msgid "Mahjongg Rectangle" +msgstr "Маджонг Прямоугольник" + +msgid "Mahjongg Reindeer" +msgstr "Маджонг Северный олень" + +msgid "Mahjongg Rings" +msgstr "Маджонг Круги" + +msgid "Mahjongg River Bridge" +msgstr "Маджонг Мост через реку" + +msgid "Mahjongg Rocket" +msgstr "Маджонг Ракета" + +msgid "Mahjongg Roman Arena" +msgstr "Маджонг Римская арена" + +msgid "Mahjongg Rooster" +msgstr "Маджонг Петух" + +msgid "Mahjongg Rugby" +msgstr "Маджонг Регби" + +msgid "Mahjongg Scorpion" +msgstr "Маджонг Скорпион" + +msgid "Mahjongg Screw Up" +msgstr "Маджонг Screw Up" + +msgid "Mahjongg Seven" +msgstr "Маджонг Семёрка" + +msgid "Mahjongg Seven Pyramids" +msgstr "Маджонг Семь пирамид" + +msgid "Mahjongg Shapeshifter" +msgstr "Маджонг Shapeshifter" + +msgid "Mahjongg Shield" +msgstr "Маджонг Щит" + +msgid "Mahjongg Siam" +msgstr "Маджонг Сиам" + +msgid "Mahjongg Snake" +msgstr "Маджонг Хвост" + +msgid "Mahjongg Space Bridge" +msgstr "Маджонг Космический мост" + +msgid "Mahjongg Space Shuttle" +msgstr "Маджонг Космический челнок" + +msgid "Mahjongg Square" +msgstr "Маджонг Квадрат" + +msgid "Mahjongg Squares" +msgstr "Маджонг Квадраты" + +msgid "Mahjongg Squaring" +msgstr "Маджонг Squaring" + +msgid "Mahjongg Stage 1" +msgstr "Маджонг Stage 1" + +msgid "Mahjongg Stage 2" +msgstr "Маджонг Stage 2" + +msgid "Mahjongg Stairs" +msgstr "Маджонг Ступени" + +msgid "Mahjongg Stairs 2" +msgstr "Маджонг Ступени 2" + +msgid "Mahjongg Stairs 3" +msgstr "Маджонг Ступени 3" + +msgid "Mahjongg Star Ship" +msgstr "Маджонг Космический корабль" + +msgid "Mahjongg Stargate" +msgstr "Маджонг Звёздные врата" + +msgid "Mahjongg Step Pyramid" +msgstr "Маджонг Семь пирамид" + +msgid "Mahjongg Stonehenge" +msgstr "Маджонг Стоунхендж" + +msgid "Mahjongg Sukis" +msgstr "Маджонг Sukis" + +msgid "Mahjongg SunMoon" +msgstr "Маджонг SunMoon" + +msgid "Mahjongg Taipei" +msgstr "Маджонг Тайпей" + +msgid "Mahjongg Temple" +msgstr "Маджонг Храм" + +msgid "Mahjongg Temple 1" +msgstr "Маджонг Храм 1" + +msgid "Mahjongg Temple 2" +msgstr "Маджонг Храм 2" + +msgid "Mahjongg The Door" +msgstr "Маджонг Дверь" + +msgid "Mahjongg The Great Wall" +msgstr "Маджонг Великая стена" + +msgid "Mahjongg Theater" +msgstr "Маджонг Театр" + +msgid "Mahjongg Tiger" +msgstr "Маджонг Тигр" + +msgid "Mahjongg Tile Fighter" +msgstr "Маджонг Tile Fighter" + +msgid "Mahjongg Tilepiles" +msgstr "Маджонг Tilepiles" + +msgid "Mahjongg Time Tunnel" +msgstr "Маджонг Time Tunnel" + +msgid "Mahjongg Tomb" +msgstr "Маджонг Гробница" + +msgid "Mahjongg Totally Random-Made" +msgstr "Маджонг Totally Random-Made" + +msgid "Mahjongg Traditional Reviewed" +msgstr "Маджонг Traditional Reviewed" + +msgid "Mahjongg Tree of Life" +msgstr "Маджонг Древо жизни" + +msgid "Mahjongg Trika" +msgstr "Маджонг Trika" + +msgid "Mahjongg Twin" +msgstr "Маджонг Twin" + +msgid "Mahjongg Twin Temples" +msgstr "Маджонг Twin Temples" + +msgid "Mahjongg Two Domes" +msgstr "Маджонг Two Domes" + +msgid "Mahjongg Vagues" +msgstr "Маджонг Vagues" + +msgid "Mahjongg Vi" +msgstr "Маджонг Vi" + +msgid "Mahjongg Victory Arrow" +msgstr "Маджонг Victory Arrow" + +msgid "Mahjongg Wavelets" +msgstr "Маджонг Волны" + +msgid "Mahjongg Wedges" +msgstr "Маджонг Клинья" + +msgid "Mahjongg Well" +msgstr "Маджонг Well" + +msgid "Mahjongg Well2" +msgstr "Маджонг Well2" + +msgid "Mahjongg Whatever" +msgstr "Маджонг Нечто" + +msgid "Mahjongg Win" +msgstr "Маджонг Win" + +msgid "Mahjongg X-Files" +msgstr "Маджонг X-Files" + +msgid "Mahjongg X-Shape" +msgstr "Маджонг X-Shape" + +msgid "Mahjongg Yummy" +msgstr "Маджонг Приятный" + +#, fuzzy +msgid "Makara" +msgstr "Мария" + +msgid "Mancunian" +msgstr "Манчестерский" + +msgid "Maria" +msgstr "Мария" + +msgid "Maria Luisa" +msgstr "Мария Луиза" + +msgid "Martha" +msgstr "Марта" + +msgid "Matriarchy" +msgstr "Матриархат" + +#, fuzzy +msgid "Matrimony" +msgstr "Матриархат" + +msgid "MatsuKiri" +msgstr "" + +msgid "MatsuKiri Strict" +msgstr "" + +#, fuzzy +msgid "Matsya" +msgstr "Майя" + +msgid "Maya" +msgstr "Майя" + +msgid "Maze" +msgstr "Путаница" + +msgid "Memory 24" +msgstr "" + +msgid "Memory 30" +msgstr "" + +msgid "Memory 40" +msgstr "" + +msgid "Merlin's Meander" +msgstr "Орнамент Мерлина" + +msgid "Mesh" +msgstr "Западня" + +msgid "Midnight Oil" +msgstr "" + +msgid "Midshipman" +msgstr "Гардемарин" + +#, fuzzy +msgid "Milligan Cell" +msgstr "Мисс Миллиган" + +#, fuzzy +msgid "Milligan Harp" +msgstr "Большая арфа" + +#, fuzzy +msgid "Minerva" +msgstr "Джунгли" + +#, fuzzy +msgid "Mini Traditional" +msgstr "Маджонг Mini Traditional" + +#, fuzzy +msgid "Mini-Layout" +msgstr "Маджонг Mini-Layout" + +msgid "Miss Milligan" +msgstr "Мисс Миллиган" + +msgid "Miss Muffet" +msgstr "Мисс Муффет" + +msgid "Mission Impossible" +msgstr "Миссия невыполнима" + +msgid "Mississippi" +msgstr "Миссисипи" + +msgid "Mod-3" +msgstr "" + +msgid "Monaco" +msgstr "Монако" + +msgid "Monkey" +msgstr "Обезьяна" + +msgid "Montana" +msgstr "Монтана" + +msgid "Monte Carlo" +msgstr "Монте-Карло" + +msgid "Moonlight" +msgstr "Лунный свет" + +#, fuzzy +msgid "Moosehide" +msgstr "Джозефина" + +msgid "Morehead" +msgstr "" + +msgid "Moth" +msgstr "Мотылёк" + +msgid "Mount Olympus" +msgstr "Гора Олимп" + +msgid "Mrs. Mop" +msgstr "Миссис Моп" + +msgid "Mughal Circles" +msgstr "" + +#, fuzzy +msgid "Multi X" +msgstr "Маджонг Multi X" + +msgid "Mumbai" +msgstr "Мумбаи" + +#, fuzzy +msgid "Munger" +msgstr "Джунгли" + +msgid "Musical Patience" +msgstr "Музыкальный пасьянс" + +#, fuzzy +msgid "N for Namida" +msgstr "Маджонг N for Namida" + +msgid "Napoleon" +msgstr "Наполеон" + +msgid "Napoleon at St.Helena" +msgstr "Наполеон на острове св.Елена" + +msgid "Napoleon's Exile" +msgstr "Изгнание Наполеона" + +msgid "Napoleon's Favorite" +msgstr "Фаворит Наполеона" + +#, fuzzy +msgid "Napoleon's Flank" +msgstr "Фланг Наполеона" + +msgid "Napoleon's Square" +msgstr "Квадрат Наполеона" + +msgid "Napoleon's Tomb" +msgstr "Гробница Наполеона" + +msgid "Narasimha" +msgstr "" + +#, fuzzy +msgid "Narpati" +msgstr "Ковёр" + +msgid "Nasty" +msgstr "Противный" + +msgid "Nationale" +msgstr "Национальность" + +msgid "Needle" +msgstr "Иголка" + +msgid "Neighbour" +msgstr "Соседи" + +msgid "Nestor" +msgstr "Нестор" + +msgid "New British Constitution" +msgstr "Новая Британская конституция" + +#, fuzzy +msgid "New Layout" +msgstr "Маджонг New Layout" + +msgid "New York" +msgstr "Нью-Йорк" + +msgid "Nomad" +msgstr "Бродяга" + +msgid "Nordic" +msgstr "Скандинавский" + +msgid "Northwest Territory" +msgstr "Северо-Западные Территории" + +msgid "Number Ten" +msgstr "Номер десять" + +msgid "Numerica" +msgstr "Числовой" + +#, fuzzy +msgid "Octagon" +msgstr "Дракон" + +msgid "Octave" +msgstr "Восемь" + +msgid "Odd and Even" +msgstr "Чёт и нечет" + +msgid "Odessa" +msgstr "Одесса" + +#, fuzzy +msgid "Okie's Nitemare" +msgstr "Маджонг Okie's Nitemare" + +msgid "Old Mole" +msgstr "Старая дамба" + +msgid "Oonsoo" +msgstr "" + +msgid "Oonsoo Open" +msgstr "" + +msgid "Oonsoo Strict" +msgstr "" + +msgid "Oonsoo Times Two" +msgstr "" + +msgid "Oonsoo Too" +msgstr "" + +msgid "Open Jumbo" +msgstr "Открытый гигант" + +msgid "Open Peek" +msgstr "" + +#, fuzzy +msgid "Open Spider" +msgstr "Паук" + +msgid "Opus" +msgstr "" + +msgid "Orbital" +msgstr "Орбитальный" + +msgid "Order" +msgstr "Порядок" + +msgid "Osmosis" +msgstr "Осмос" + +msgid "Owl" +msgstr "Сова" + +msgid "Ox" +msgstr "Бык" + +#, fuzzy +msgid "Pagat" +msgstr "Пагода" + +msgid "Pagoda" +msgstr "Пагода" + +msgid "Panopticon" +msgstr "Паноптикум" + +msgid "Pantheon" +msgstr "Пантеон" + +msgid "Papillon" +msgstr "Папильотка" + +msgid "Parallels" +msgstr "Параллели" + +msgid "Parashurama" +msgstr "" + +msgid "Pas Seul" +msgstr "Сольный танец" + +msgid "Pas de Deux" +msgstr "Па-де-де" + +#, fuzzy +msgid "Patriarchs" +msgstr "Матриархат" + +msgid "Pattern" +msgstr "Образец" + +msgid "Paulownia" +msgstr "" + +msgid "Peek" +msgstr "" + +msgid "Pegged" +msgstr "" + +msgid "Pegged 6x6" +msgstr "" + +msgid "Pegged 7x7" +msgstr "" + +msgid "Pegged Cross 1" +msgstr "" + +msgid "Pegged Cross 2" +msgstr "" + +msgid "Pegged Triangle 1" +msgstr "" + +msgid "Pegged Triangle 2" +msgstr "" + +msgid "Penguin" +msgstr "Пингвин" + +msgid "Peony" +msgstr "Пион" + +msgid "Perpetual Motion" +msgstr "Перпетуум-мобиле" + +msgid "Perseverance" +msgstr "Настойчивость" + +msgid "Phoenix" +msgstr "Феникс" + +msgid "Picture Gallery" +msgstr "Картинная галерея" + +#, fuzzy +msgid "Pigtail" +msgstr "Портал" + +msgid "PileOn" +msgstr "" + +msgid "Pine" +msgstr "Сосна" + +msgid "Pitchfork" +msgstr "Камертон" + +msgid "Plait" +msgstr "" + +msgid "Plus Belle" +msgstr "" + +msgid "Poker Shuffle" +msgstr "" + +#, fuzzy +msgid "Poker Square" +msgstr "Два квадрата" + +msgid "Ponytail" +msgstr "Конский хвост" + +msgid "Portal" +msgstr "Портал" + +msgid "Portuguese Solitaire" +msgstr "Португальский пасьянс" + +msgid "Progression" +msgstr "Движение" + +msgid "Provisions" +msgstr "Припасы" + +msgid "Push Pin" +msgstr "Пуш-пин" + +#, fuzzy +msgid "Puss in the Corner" +msgstr "Дом в лесу" + +msgid "Pyramid" +msgstr "Пирамида" + +msgid "Pyramid 1" +msgstr "Пирамида 1" + +msgid "Pyramid 2" +msgstr "Пирамида 2" + +#, fuzzy +msgid "Pyramid Golf" +msgstr "Пирамида 1" + +msgid "Q.C." +msgstr "" + +msgid "Quad" +msgstr "" + +msgid "Quadrangle" +msgstr "Четырёхугольник" + +msgid "Quadruple Alliance" +msgstr "" + +msgid "Queen of Italy" +msgstr "Королева Италии" + +msgid "Queenie" +msgstr "" + +msgid "Quilt" +msgstr "Одеяло" + +msgid "Rachel" +msgstr "Рашель" + +msgid "Raglan" +msgstr "Реглан" + +msgid "Rainbow" +msgstr "Радуга" + +msgid "Rainfall" +msgstr "Ливень" + +msgid "Ram" +msgstr "Овен" + +msgid "Rambling" +msgstr "Бродячий" + +#, fuzzy +msgid "Rangoon" +msgstr "Дракон" + +msgid "Rank and File" +msgstr "Ряд и шеренга" + +msgid "Rat" +msgstr "Крыса" + +msgid "Raw Prawn" +msgstr "" + +msgid "Rectangle" +msgstr "Прямоугольник" + +msgid "Red Moon" +msgstr "Красная Луна" + +msgid "Red and Black" +msgstr "Красное и Чёрное" + +msgid "Reindeer" +msgstr "Северный олень" + +msgid "Relax" +msgstr "" + +msgid "Relaxed FreeCell" +msgstr "Смягчённая Свободная ячейка" + +msgid "Relaxed Golf" +msgstr "Смягчённый Гольф" + +msgid "Relaxed Pyramid" +msgstr "Смягчённая Пирамида" + +msgid "Relaxed Seahaven Towers" +msgstr "" + +msgid "Relaxed Spider" +msgstr "Смягчённый Паук" + +msgid "Repair" +msgstr "" + +msgid "Retinue" +msgstr "Свита" + +msgid "Rings" +msgstr "Круги" + +msgid "Ripple Fan" +msgstr "Волнистый веер" + +msgid "Rittenhouse" +msgstr "Риттенхаус" + +msgid "River Bridge" +msgstr "Мост через реку" + +#, fuzzy +msgid "Robert" +msgstr "Ракета" + +msgid "Robin" +msgstr "Робин" + +msgid "Rock Hopper" +msgstr "" + +msgid "Rock Hopper 6x6" +msgstr "" + +msgid "Rock Hopper 7x7" +msgstr "" + +msgid "Rock Hopper Cross 1" +msgstr "" + +msgid "Rock Hopper Cross 2" +msgstr "" + +msgid "Rocket" +msgstr "Ракета" + +msgid "Roman Arena" +msgstr "Римская арена" + +msgid "Roost" +msgstr "Насест" + +msgid "Rooster" +msgstr "Петух" + +#, fuzzy +msgid "Roslin" +msgstr "Робин" + +msgid "Rouge et Noir" +msgstr "" + +msgid "Rows of Four" +msgstr "" + +msgid "Royal Cotillion" +msgstr "Королевский котильон" + +#, fuzzy +msgid "Royal East" +msgstr "Королевская семья" + +msgid "Royal Family" +msgstr "Королевская семья" + +msgid "Royal Marriage" +msgstr "Королевская свадьба" + +msgid "Rugby" +msgstr "Регби" + +msgid "Rushdike" +msgstr "" + +msgid "Russian Aces" +msgstr "Русские тузы" + +msgid "Russian Patience" +msgstr "Русский пасьянс" + +msgid "Russian Point" +msgstr "Русский пункт" + +msgid "Russian Solitaire" +msgstr "Русский солитер" + +msgid "Salic Law" +msgstr "Салический закон" + +msgid "Samuri" +msgstr "" + +msgid "Sanibel" +msgstr "Санибел" + +msgid "Scarab" +msgstr "Скарабей" + +msgid "Scheidungsgrund" +msgstr "" + +msgid "Scorpion" +msgstr "Скорпион" + +msgid "Scorpion Head" +msgstr "Голова скорпиона" + +msgid "Scorpion Tail" +msgstr "Хвост скорпиона" + +msgid "Scotch Patience" +msgstr "Шотландский пасьянс" + +#, fuzzy +msgid "Screw Up" +msgstr "Тузы вверх" + +msgid "Sea Towers" +msgstr "Морские башни" + +msgid "Seahaven Towers" +msgstr "" + +msgid "Senate" +msgstr "Сенат" + +msgid "Senate +" +msgstr "Сенат +" + +msgid "Serpent" +msgstr "Змея" + +msgid "Seven" +msgstr "Семёрка" + +msgid "Seven Devils" +msgstr "Семь чертей" + +msgid "Seven Pyramids" +msgstr "Семь пирамид" + +msgid "Seven by Five" +msgstr "Семь по пять" + +msgid "Seven by Four" +msgstr "Семь по четыре" + +msgid "Shamrocks" +msgstr "Трилистник" + +msgid "Shamsher" +msgstr "" + +msgid "Shanka" +msgstr "" + +#, fuzzy +msgid "Shapeshifter" +msgstr "Маджонг Shapeshifter" + +msgid "Shield" +msgstr "Щит" + +msgid "Shifting" +msgstr "" + +msgid "Shisen-Sho (No Gra) 14x6" +msgstr "" + +msgid "Shisen-Sho (No Gra) 18x8" +msgstr "" + +msgid "Shisen-Sho (No Gra) 24x12" +msgstr "" + +msgid "Shisen-Sho 14x6" +msgstr "" + +msgid "Shisen-Sho 18x8" +msgstr "" + +msgid "Shisen-Sho 24x12" +msgstr "" + +msgid "Siam" +msgstr "Сиам" + +msgid "Sieben bis As" +msgstr "" + +msgid "Simon Jester" +msgstr "Саймон Джестер" + +msgid "Simple Carlo" +msgstr "Просто-Карло" + +msgid "Simple Pairs" +msgstr "Простые пары" + +msgid "Simple Simon" +msgstr "Симон-простофиля" + +#, fuzzy +msgid "Simplex" +msgstr "Улыбка" + +msgid "Simplicity" +msgstr "Простота" + +msgid "Single Rail" +msgstr "Одинарные рельсы" + +msgid "Sir Tommy" +msgstr "Сэр Томми" + +msgid "Six Sages" +msgstr "Шесть мудрецов" + +#, fuzzy +msgid "Six Tengus" +msgstr "Шесть мудрецов" + +msgid "Sixes and Sevens" +msgstr "Шестёрки и семёрки" + +msgid "Skiz" +msgstr "" + +msgid "Small Harp" +msgstr "Малая арфа" + +msgid "Small PileOn" +msgstr "" + +msgid "Smile" +msgstr "Улыбка" + +msgid "Snake" +msgstr "Хвост" + +#, fuzzy +msgid "Snakestone" +msgstr "Хвост" + +#, fuzzy +msgid "Solid Square" +msgstr "Два квадрата" + +msgid "Somerset" +msgstr "Сомерсет" + +msgid "Space Bridge" +msgstr "Космический мост" + +msgid "Space Shuttle" +msgstr "Космический челнок" + +msgid "Spaces" +msgstr "Промежутки" + +msgid "Spaces and Aces" +msgstr "Промежутки и тузы" + +msgid "Spanish Patience" +msgstr "Испанский пасьянс" + +msgid "Sphere" +msgstr "Сфера" + +msgid "Spider" +msgstr "Паук" + +msgid "Spider (1 suit)" +msgstr "Паук (1 масть)" + +msgid "Spider (2 suits)" +msgstr "Паук (2 масти)" + +#, fuzzy +msgid "Spider (4 decks)" +msgstr "Паук (1 масть)" + +#, fuzzy +msgid "Spider 3x3" +msgstr "Паук" + +msgid "Spider Web" +msgstr "Паутина" + +#, fuzzy +msgid "Spidercells" +msgstr "Паук" + +msgid "Spiderette" +msgstr "Паучок" + +#, fuzzy +msgid "Spidike" +msgstr "Паук" + +#, fuzzy +msgid "Squadron" +msgstr "Квадрат" + +msgid "Square" +msgstr "Квадрат" + +msgid "Squares" +msgstr "Квадраты" + +#, fuzzy +msgid "Squaring" +msgstr "Квадрат" + +msgid "St. Helena" +msgstr "Св. Елена" + +#, fuzzy +msgid "Stage 1" +msgstr "Звёздные врата" + +#, fuzzy +msgid "Stage 2" +msgstr "Ступени 2" + +msgid "Stairs" +msgstr "Ступени" + +msgid "Stairs 2" +msgstr "Ступени 2" + +msgid "Stairs 3" +msgstr "Ступени 3" + +msgid "Stalactites" +msgstr "Сталактиты" + +msgid "Star Ship" +msgstr "Космический корабль" + +msgid "Stargate" +msgstr "Звёздные врата" + +#, fuzzy +msgid "Step Pyramid" +msgstr "Семь пирамид" + +#, fuzzy +msgid "Steps" +msgstr "Улицы" + +msgid "Stonehenge" +msgstr "Стоунхендж" + +msgid "Stonewall" +msgstr "Оппозиция" + +msgid "Storehouse" +msgstr "Сокровищница" + +msgid "Straight Up" +msgstr "" + +msgid "Strategy" +msgstr "Стратегия" + +msgid "Streets" +msgstr "Улицы" + +msgid "Streets and Alleys" +msgstr "Улицы и аллеи" + +msgid "Stronghold" +msgstr "Цитадель" + +msgid "Sukis" +msgstr "" + +msgid "Sultan" +msgstr "Султан" + +msgid "Sultan +" +msgstr "Султан +" + +msgid "Sultan of Turkey" +msgstr "Турецкий султан" + +#, fuzzy +msgid "Sumo" +msgstr "Гигант" + +#, fuzzy +msgid "SunMoon" +msgstr "Голубая луна" + +msgid "Super Challenge FreeCell" +msgstr "Очень Сложная Свободная ячейка" + +#, fuzzy +msgid "Super Flower Garden" +msgstr "Цветочный сад" + +msgid "Super Samuri" +msgstr "" + +#, fuzzy +msgid "Superior Canfield" +msgstr "Двойной Кенфилд" + +#, fuzzy +msgid "Surprise" +msgstr "Предприятие" + +msgid "Surukh" +msgstr "" + +msgid "Taipei" +msgstr "Тайпей" + +msgid "Take Away" +msgstr "Удаление" + +msgid "Tam O'Shanter" +msgstr "" + +msgid "Tarantula" +msgstr "Тарантул" + +msgid "Temple" +msgstr "Храм" + +msgid "Temple 1" +msgstr "Храм 1" + +msgid "Temple 2" +msgstr "Храм 2" + +msgid "Ten Across" +msgstr "Десять в ширину" + +msgid "Ten Avatars" +msgstr "Десять аватар" + +msgid "Ten by One" +msgstr "Десять по одному" + +msgid "Terrace" +msgstr "Терраса" + +msgid "The Bouquet" +msgstr "Букет" + +msgid "The Door" +msgstr "Дверь" + +msgid "The Familiar" +msgstr "Близкий" + +msgid "The Garden" +msgstr "Сад" + +msgid "The Great Wall" +msgstr "Великая Стена" + +msgid "The Wish" +msgstr "Желание" + +msgid "The Wish (open)" +msgstr "Желание (открытое)" + +msgid "The last Monarch" +msgstr "Последний Монарх" + +msgid "Theater" +msgstr "Театр" + +msgid "Thirteen Up" +msgstr "Тринадцать вверх" + +msgid "Thirty Six" +msgstr "Тридцать шесть" + +msgid "Three Blind Mice" +msgstr "Три слепые мышки" + +msgid "Three Peaks" +msgstr "Три вершины" + +msgid "Three Peaks Non-scoring" +msgstr "Три вершины без подсчёта очков" + +msgid "Three Shuffles and a Draw" +msgstr "" + +msgid "Thumb and Pouch" +msgstr "" + +msgid "Tiger" +msgstr "Тигр" + +#, fuzzy +msgid "Tile Fighter" +msgstr "Маджонг Tile Fighter" + +#, fuzzy +msgid "Tilepiles" +msgstr "Маджонг Tilepiles" + +#, fuzzy +msgid "Time Tunnel" +msgstr "Маджонг Time Tunnel" + +#, fuzzy +msgid "Tipati" +msgstr "Тайпей" + +msgid "Toad" +msgstr "Жаба" + +msgid "Tomb" +msgstr "Гробница" + +#, fuzzy +msgid "Totally Random-Made" +msgstr "Маджонг Totally Random-Made" + +msgid "Tournament" +msgstr "Турнир" + +msgid "Tower of Hanoy" +msgstr "Ханойская башня" + +msgid "Towers" +msgstr "Башни" + +#, fuzzy +msgid "Traditional Reviewed" +msgstr "Маджонг Traditional Reviewed" + +msgid "Treasure Trove" +msgstr "Клад" + +msgid "Tree of Life" +msgstr "Древо жизни" + +msgid "Trefoil" +msgstr "Клевер" + +#, fuzzy +msgid "Tri Peaks" +msgstr "Три вершины" + +msgid "Trika" +msgstr "" + +msgid "Trillium" +msgstr "" + +msgid "Triple Canfield" +msgstr "Тройной Кенфилд" + +msgid "Triple Easthaven" +msgstr "" + +msgid "Triple FreeCell" +msgstr "Тройная Свободная ячейка" + +msgid "Triple Klondike" +msgstr "Тройной Клондайк" + +msgid "Triple Klondike by Threes" +msgstr "Тройной Клондайк по три" + +#, fuzzy +msgid "Triple Line" +msgstr "Тройной Юкон" + +#, fuzzy +msgid "Triple York" +msgstr "Тройной Юкон" + +msgid "Triple Yukon" +msgstr "Тройной Юкон" + +msgid "Twenty" +msgstr "" + +msgid "Twin" +msgstr "" + +msgid "Twin Picks" +msgstr "" + +#, fuzzy +msgid "Twin Temples" +msgstr "Маджонг Twin Temples" + +#, fuzzy +msgid "Two Domes" +msgstr "Маджонг Two Domes" + +msgid "Two Familiars" +msgstr "" + +msgid "Two Squares" +msgstr "Два квадрата" + +#, fuzzy +msgid "Union Square" +msgstr "Два квадрата" + +msgid "Vagues" +msgstr "" + +msgid "Vajra" +msgstr "" + +msgid "Vamana" +msgstr "" + +#, fuzzy +msgid "Varaha" +msgstr "Марта" + +msgid "Variegated Canfield" +msgstr "Пёстрый Кенфилд" + +#, fuzzy +msgid "Vegas Klondike" +msgstr "Казино Клондайк" + +msgid "Vertical" +msgstr "Вертикаль" + +msgid "Vi" +msgstr "" + +#, fuzzy +msgid "Victory Arrow" +msgstr "Маджонг Victory Arrow" + +msgid "Wall" +msgstr "Стена" + +msgid "Waning Moon" +msgstr "Луна на ущербе" + +msgid "Washington's Favorite" +msgstr "Фаворит Вашингтона" + +msgid "Wasp" +msgstr "Оса" + +msgid "Wave Motion" +msgstr "Волновое движение" + +msgid "Wavelets" +msgstr "Волны" + +msgid "Weddings" +msgstr "Свадьбы" + +msgid "Wedges" +msgstr "Клинья" + +#, fuzzy +msgid "Well" +msgstr "Стена" + +#, fuzzy +msgid "Well2" +msgstr "Стена" + +msgid "Westcliff" +msgstr "" + +msgid "Westhaven" +msgstr "" + +msgid "Whatever" +msgstr "Нечто" + +msgid "Wheel of Fortune" +msgstr "Колесо фортуны" + +msgid "Whitehead" +msgstr "" + +msgid "Wicked" +msgstr "Злой" + +msgid "Will o' the Wisp" +msgstr "" + +msgid "Win" +msgstr "" + +msgid "Windmill" +msgstr "Ветряная мельница" + +msgid "Wisteria" +msgstr "Глициния" + +#, fuzzy +msgid "X-Files" +msgstr "Маджонг X-Files" + +#, fuzzy +msgid "X-Shape" +msgstr "Маджонг X-Shape" + +#, fuzzy +msgid "York" +msgstr "Нью-Йорк" + +msgid "Yukon" +msgstr "Юкон" + +msgid "Yummy" +msgstr "Приятный" + +msgid "Zebra" +msgstr "Зебра" + +msgid "Zerline" +msgstr "Церлин" + +msgid "Zerline (3 decks)" +msgstr "Церлин (3 колоды)" + +msgid "Zeus" +msgstr "Зевс" diff --git a/po/ru_pysol.po b/po/ru_pysol.po new file mode 100644 index 0000000000..6cbde4ce41 --- /dev/null +++ b/po/ru_pysol.po @@ -0,0 +1,3005 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PySol 0.0.1\n" +"POT-Creation-Date: Fri May 26 20:25:31 2006\n" +"PO-Revision-Date: 2006-05-13 00:53+0400\n" +"Last-Translator: Скоморох \n" +"Language-Team: Russian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Generated-By: pygettext.py 1.5\n" + +#: pysollib/actions.py:345 pysollib/game.py:1205 pysollib/game.py:1220 +#: pysollib/game.py:1226 pysollib/game.py:1231 pysollib/tk/toolbar.py:183 +msgid "New game" +msgstr "Новая игра" + +#: pysollib/actions.py:358 pysollib/tk/menubar.py:668 +#: pysollib/tk/menubar.py:682 +msgid "Select game" +msgstr "Выбрать игру" + +#: pysollib/actions.py:381 +msgid "Invalid game number" +msgstr "Неправильный номер игры" + +#: pysollib/actions.py:382 +msgid "Invalid game number\n" +msgstr "Неправильный номер игры\n" + +#: pysollib/actions.py:399 +msgid "Select next game number" +msgstr "Выберите номер следующей игры" + +#: pysollib/actions.py:408 pysollib/actions.py:418 +msgid "Select new game number" +msgstr "Выберите номер новой игры" + +#: pysollib/actions.py:409 +msgid "" +"\n" +"\n" +"Enter new game number" +msgstr "" +"\n" +"\n" +"Введите номер новой игры" + +#: pysollib/actions.py:410 +msgid "Next number" +msgstr "Следующий номер" + +#: pysollib/actions.py:410 pysollib/app.py:1085 pysollib/app.py:1097 +#: pysollib/game.py:828 pysollib/game.py:1641 pysollib/main.py:399 +#: pysollib/main.py:404 pysollib/tk/colorsdialog.py:131 +#: pysollib/tk/demooptionsdialog.py:87 pysollib/tk/edittextdialog.py:82 +#: pysollib/tk/edittextdialog.py:94 pysollib/tk/fontsdialog.py:140 +#: pysollib/tk/fontsdialog.py:204 pysollib/tk/gameinfodialog.py:133 +#: pysollib/tk/playeroptionsdialog.py:86 +#: pysollib/tk/playeroptionsdialog.py:161 pysollib/tk/selectcardset.py:240 +#: pysollib/tk/selectcardset.py:396 pysollib/tk/selecttile.py:158 +#: pysollib/tk/soundoptionsdialog.py:106 pysollib/tk/soundoptionsdialog.py:158 +#: pysollib/tk/timeoutsdialog.py:92 pysollib/tk/tkhtml.py:506 +#: pysollib/tk/tkstats.py:288 pysollib/tk/tkstats.py:573 +#: pysollib/tk/tkstats.py:647 pysollib/tk/tkstats.py:663 +#: pysollib/tk/tkstats.py:705 pysollib/tk/tkstats.py:776 +#: pysollib/tk/tkstats.py:860 pysollib/tk/tkwidget.py:158 +#: pysollib/tk/tkwidget.py:297 +msgid "OK" +msgstr "ОК" + +#: pysollib/actions.py:410 pysollib/app.py:1097 pysollib/game.py:828 +#: pysollib/game.py:1205 pysollib/game.py:1220 pysollib/game.py:1226 +#: pysollib/game.py:1231 pysollib/tk/colorsdialog.py:131 +#: pysollib/tk/demooptionsdialog.py:87 pysollib/tk/edittextdialog.py:94 +#: pysollib/tk/fontsdialog.py:140 pysollib/tk/fontsdialog.py:204 +#: pysollib/tk/menubar.py:855 pysollib/tk/menubar.py:857 +#: pysollib/tk/playeroptionsdialog.py:86 +#: pysollib/tk/playeroptionsdialog.py:161 pysollib/tk/selectcardset.py:240 +#: pysollib/tk/selectgame.py:268 pysollib/tk/selectgame.py:409 +#: pysollib/tk/selecttile.py:158 pysollib/tk/soundoptionsdialog.py:106 +#: pysollib/tk/timeoutsdialog.py:92 pysollib/tk/tkwidget.py:297 +msgid "Cancel" +msgstr "Отмена" + +#: pysollib/actions.py:426 +msgid "Select random game" +msgstr "Выбор случайной игры" + +#: pysollib/actions.py:462 +msgid "Select next game" +msgstr "Выбрать следующую игру" + +#: pysollib/actions.py:495 pysollib/tk/toolbar.py:197 +msgid "Quit " +msgstr "Выйти из " + +#: pysollib/actions.py:545 +msgid "Clear bookmarks" +msgstr "Удалить закладки" + +#: pysollib/actions.py:546 +msgid "Clear all bookmarks ?" +msgstr "Удалить все закладки?" + +#: pysollib/actions.py:556 +msgid "Restart game" +msgstr "Начать игру с начала" + +#: pysollib/actions.py:557 +msgid "Restart this game ?" +msgstr "Начать игру с начала?" + +#: pysollib/actions.py:594 +msgid "" +"Comments for %s:\n" +"\n" +msgstr "" +"Комментарий для %s:\n" +"\n" + +#: pysollib/actions.py:596 +msgid "Comments for " +msgstr "Комментарий для " + +#: pysollib/actions.py:614 pysollib/actions.py:650 +msgid "Error while writing to file" +msgstr "Ошибка при записи в файл" + +#: pysollib/actions.py:617 pysollib/actions.py:653 pysollib/actions.py:956 +msgid " Info" +msgstr " Информация" + +#: pysollib/actions.py:618 +msgid "" +"Comments were appended to\n" +"\n" +msgstr "" +"Комментарий добавлен в файл\n" +"\n" + +#: pysollib/actions.py:635 +msgid "Demo statistics" +msgstr "Статистика демо" + +#: pysollib/actions.py:638 +msgid "Your statistics" +msgstr "Ваша статистика" + +#: pysollib/actions.py:654 +msgid "" +" were appended to\n" +"\n" +msgstr "" +" добавлена в файл\n" +"\n" + +#: pysollib/actions.py:668 +msgid " Demo" +msgstr " Демо" + +#: pysollib/actions.py:668 +msgid " Demo " +msgstr " Демо " + +#: pysollib/actions.py:671 pysollib/actions.py:689 +msgid " for " +msgstr " для " + +#: pysollib/actions.py:677 pysollib/actions.py:696 +msgid "Statistics for " +msgstr "Статистика игры " + +#: pysollib/actions.py:680 pysollib/tk/selectgame.py:352 +#: pysollib/tk/toolbar.py:194 +msgid "Statistics" +msgstr "Статистика" + +#: pysollib/actions.py:683 +msgid "Full log" +msgstr "Полный лог" + +#: pysollib/actions.py:686 +msgid "Session log" +msgstr "Лог сессии" + +#: pysollib/actions.py:692 +msgid "Game Info" +msgstr "Информация об игре" + +#: pysollib/actions.py:701 +msgid "Full log for " +msgstr "Полный лог для " + +#: pysollib/actions.py:706 +msgid "Session log for " +msgstr "Лог сессии для " + +#: pysollib/actions.py:711 +msgid "Reset all statistics" +msgstr "Очистить всю статистику" + +#: pysollib/actions.py:712 +msgid "" +"Reset ALL statistics and logs for player\n" +"%s ?" +msgstr "" +"Очистить всю статистику и лог для игрока\n" +"%s?" + +#: pysollib/actions.py:718 +msgid "Reset game statistics" +msgstr "Очистить статистику игры" + +#: pysollib/actions.py:719 +msgid "" +"Reset statistics and logs for player\n" +"%s\n" +"and game\n" +"%s ?" +msgstr "" +"Очистить всю статистику и лог для игрока\n" +"%s\n" +"и игры\n" +"%s?" + +#: pysollib/actions.py:775 +msgid "Play demo" +msgstr "Показать демо" + +#: pysollib/actions.py:786 +msgid "Set player options" +msgstr "Установить настройки игрока" + +#: pysollib/actions.py:875 +msgid "Sound settings" +msgstr "Настройка звука" + +#: pysollib/actions.py:896 +msgid "Set demo options" +msgstr "Настройка демо" + +#: pysollib/actions.py:910 +msgid "Set colors" +msgstr "Настроить цвета" + +#: pysollib/actions.py:929 +msgid "Set fonts" +msgstr "Настроить шрифт" + +#: pysollib/actions.py:938 +msgid "Set timeouts" +msgstr "Настроить таймауты" + +#: pysollib/actions.py:953 +msgid "Error while saving options" +msgstr "Ошибка при сохранении настроек" + +#: pysollib/actions.py:957 +msgid "" +"Options were saved to\n" +"\n" +msgstr "" +"Опции сохранены в\n" +"\n" + +#: pysollib/app.py:85 +msgid "Unknown" +msgstr "Неизвестный" + +#: pysollib/app.py:947 +msgid "Loading %s %s..." +msgstr "Загружается %s %s..." + +#: pysollib/app.py:982 +msgid " load error" +msgstr " ошибка при загрузке" + +#: pysollib/app.py:983 +msgid "Error while loading " +msgstr "Ошибка при загрузке" + +#: pysollib/app.py:1077 +msgid "Incompatible " +msgstr "Несовместимый " + +#: pysollib/app.py:1079 +msgid "" +"The currently selected %s %s\n" +"is not compatible with the game\n" +"%s\n" +"\n" +"Please select a %s type %s.\n" +msgstr "" +"Текущий %s %s\n" +"несовместим с игрой\n" +"%s\n" +"\n" +"Необходимо выбрать %s типа %s.\n" + +#: pysollib/app.py:1095 +msgid "Please select a %s type %s" +msgstr "Выберите %s типа %s" + +#: pysollib/game.py:750 pysollib/game.py:756 +msgid "Player\n" +msgstr "Игрок\n" + +#: pysollib/game.py:824 +msgid "Discard current game ?" +msgstr "Завершить текущую игру?" + +#: pysollib/game.py:1159 +msgid "" +"\n" +"You have reached\n" +"#%d in the %s of playing time" +msgstr "" +"\n" +"Вы достигли\n" +"#%d в %s игрового времени" + +#: pysollib/game.py:1162 +msgid "" +"\n" +"and #%d in the %s of moves" +msgstr "" +"\n" +"и #%d в %s количества ходов" + +#: pysollib/game.py:1164 +msgid "" +"\n" +"You have reached\n" +"#%d in the %s of moves" +msgstr "" +"\n" +"Вы достигли\n" +"#%d в %s количества ходов" + +#: pysollib/game.py:1167 +msgid "" +"\n" +"and #%d in the %s of total moves" +msgstr "" +"\n" +"и #%d в %s общего количества ходов" + +#: pysollib/game.py:1169 +msgid "" +"\n" +"You have reached\n" +"#%d in the %s of total moves" +msgstr "" +"\n" +"Вы достигли\n" +"#%d в %s общего количества ходов" + +#: pysollib/game.py:1196 pysollib/game.py:1212 +msgid "Game won" +msgstr "Игра выиграна" + +#: pysollib/game.py:1197 +msgid "" +"\n" +"Congratulations, this\n" +"was a truly perfect game !\n" +"\n" +"Your playing time is %s\n" +"for %d moves.\n" +"%s\n" +msgstr "" +"\n" +"Поздравляем!\n" +"Это была действительно\n" +"великолепная игра!\n" +"\n" +"Ваше игровое время: %s\n" +"Количество ходов: %s\n" +"%s\n" + +#: pysollib/game.py:1213 +msgid "" +"\n" +"Congratulations, you did it !\n" +"\n" +"Your playing time is %s\n" +"for %d moves.\n" +"%s\n" +msgstr "" +"\n" +"Поздравляем!\n" +"Вы сделали это!\n" +"\n" +"Ваше игровое время: %s\n" +"Количество ходов: %s\n" +"%s\n" + +#: pysollib/game.py:1224 pysollib/game.py:1229 +msgid "Game finished" +msgstr "Игра закончена" + +#: pysollib/game.py:1225 pysollib/game.py:1642 +msgid "" +"\n" +"Game finished\n" +msgstr "" +"\n" +"Игра закончена\n" + +#: pysollib/game.py:1230 +msgid "" +"\n" +"Game finished, but not without my help...\n" +msgstr "" +"\n" +"Игра закончена, но не без моей помощи...\n" + +#: pysollib/game.py:1231 pysollib/tk/toolbar.py:184 +msgid "Restart" +msgstr "Начало" + +#: pysollib/game.py:1535 +msgid "Score %6d" +msgstr "Счет %6d" + +#: pysollib/game.py:1634 +msgid "Cool" +msgstr "Отлично" + +#: pysollib/game.py:1634 +msgid "Great" +msgstr "Эдорово" + +#: pysollib/game.py:1634 +msgid "Wow" +msgstr "Ура" + +#: pysollib/game.py:1634 +msgid "Yeah" +msgstr "Ага" + +#: pysollib/game.py:1635 pysollib/game.py:1645 pysollib/game.py:1657 +msgid " Autopilot" +msgstr " Автопилот" + +#: pysollib/game.py:1636 +msgid "" +"\n" +"Game solved in %d moves.\n" +msgstr "" +"\n" +"Игра решена за %d ходов\n" + +#: pysollib/game.py:1656 +msgid "Hmm" +msgstr "Хмм" + +#: pysollib/game.py:1656 +msgid "Oh well" +msgstr "Ох" + +#: pysollib/game.py:1656 +msgid "That's life" +msgstr "Такова жизнь" + +#: pysollib/game.py:1658 +msgid "" +"\n" +"This won't come out...\n" +msgstr "" +"\n" +"Не удалось...\n" + +#: pysollib/game.py:2062 +msgid "Set bookmark" +msgstr "Установить закладку" + +#: pysollib/game.py:2063 +msgid "Replace existing bookmark %d ?" +msgstr "Заменить существующую закладку %d ?" + +#: pysollib/game.py:2085 +msgid "Goto bookmark" +msgstr "Перейти к закладке" + +#: pysollib/game.py:2086 +msgid "Goto bookmark %d ?" +msgstr "Перейти к закладке %d ?" + +#: pysollib/game.py:2117 +msgid "Open game" +msgstr "Открыть игру" + +#: pysollib/game.py:2128 pysollib/game.py:2137 pysollib/game.py:2142 +msgid "Load game error" +msgstr "Ошибка при загрузке игры" + +#: pysollib/game.py:2129 +msgid "" +"Error while loading game.\n" +"\n" +"Probably the game file is damaged,\n" +"but this could also be a bug you might want to report." +msgstr "" + +#: pysollib/game.py:2138 +msgid "Error while loading game" +msgstr "Ошибка при загрузке игры" + +#: pysollib/game.py:2143 +msgid "" +"Internal error while loading game.\n" +"\n" +"Please report this bug." +msgstr "" +"Внутренняя ошибка при загрузке игры.\n" +"\n" +"Пожалуйста сообщите об этой ошибке." + +#: pysollib/game.py:2168 +msgid "Save game error" +msgstr "Ошибка при сохранении игры" + +#: pysollib/game.py:2169 +msgid "Error while saving game" +msgstr "Ошибка при сохранении игры" + +#: pysollib/gamedb.py:113 +msgid "Baker's Dozen" +msgstr "" + +#: pysollib/gamedb.py:114 +msgid "Beleaguered Castle" +msgstr "" + +#: pysollib/gamedb.py:115 +msgid "Canfield" +msgstr "" + +#: pysollib/gamedb.py:116 +msgid "Fan" +msgstr "" + +#: pysollib/gamedb.py:117 +msgid "Forty Thieves" +msgstr "" + +#: pysollib/gamedb.py:118 +msgid "FreeCell" +msgstr "" + +#: pysollib/gamedb.py:119 +msgid "Golf" +msgstr "" + +#: pysollib/gamedb.py:120 +msgid "Gypsy" +msgstr "" + +#: pysollib/gamedb.py:121 +msgid "Klondike" +msgstr "" + +#: pysollib/gamedb.py:122 +msgid "Montana" +msgstr "" + +#: pysollib/gamedb.py:123 +msgid "Napoleon" +msgstr "" + +#: pysollib/gamedb.py:124 +msgid "Numerica" +msgstr "" + +#: pysollib/gamedb.py:125 +msgid "Pairing" +msgstr "" + +#: pysollib/gamedb.py:126 +msgid "Raglan" +msgstr "" + +#: pysollib/gamedb.py:127 pysollib/gamedb.py:160 +msgid "Simple games" +msgstr "Простые игры" + +#: pysollib/gamedb.py:128 +msgid "Spider" +msgstr "" + +#: pysollib/gamedb.py:129 +msgid "Terrace" +msgstr "" + +#: pysollib/gamedb.py:130 +msgid "Yukon" +msgstr "" + +#: pysollib/gamedb.py:131 pysollib/gamedb.py:164 +msgid "One-Deck games" +msgstr "Игры с одной колодой" + +#: pysollib/gamedb.py:132 pysollib/gamedb.py:165 +msgid "Two-Deck games" +msgstr "Игры с двумя колодами" + +#: pysollib/gamedb.py:133 pysollib/gamedb.py:166 +msgid "Three-Deck games" +msgstr "Игры с тремя колодами" + +#: pysollib/gamedb.py:134 pysollib/gamedb.py:167 +msgid "Four-Deck games" +msgstr "Игры с четырьмя колодами" + +#: pysollib/gamedb.py:146 +msgid "Baker's Dozen type" +msgstr "Игры типа Чёртова Дюжина (Baker's Dozen)" + +#: pysollib/gamedb.py:147 +msgid "Beleaguered Castle type" +msgstr "Игры типа Осаждённый Замок (Beleaguered Castle)" + +#: pysollib/gamedb.py:148 +msgid "Canfield type" +msgstr "Игры типа Кенфилд (Canfield)" + +#: pysollib/gamedb.py:149 +msgid "Fan type" +msgstr "Игры типа Веер (Fan)" + +#: pysollib/gamedb.py:150 +msgid "Forty Thieves type" +msgstr "Игры типа Сорок Воров (Forty Thieves)" + +#: pysollib/gamedb.py:151 +msgid "FreeCell type" +msgstr "Игры типа Свободная Ячейка (FreeCell)" + +#: pysollib/gamedb.py:152 +msgid "Golf type" +msgstr "Игры типа Гольф (Golf)" + +#: pysollib/gamedb.py:153 +msgid "Gypsy type" +msgstr "Игры типа Цыганский Пасьянс (Gypsy)" + +#: pysollib/gamedb.py:154 +msgid "Klondike type" +msgstr "Игры типа Клондайк (Klondike)" + +#: pysollib/gamedb.py:155 +msgid "Montana type" +msgstr "Игры типа Монтана (Montana)" + +#: pysollib/gamedb.py:156 +msgid "Napoleon type" +msgstr "Игры типа Наполеон (Napoleon)" + +#: pysollib/gamedb.py:157 +msgid "Numerica type" +msgstr "Игры числового типа (Numerica)" + +#: pysollib/gamedb.py:158 +msgid "Pairing type" +msgstr "Парные игры" + +#: pysollib/gamedb.py:159 +msgid "Raglan type" +msgstr "Игры типа Реглан (Raglan)" + +#: pysollib/gamedb.py:161 +msgid "Spider type" +msgstr "Игры типа Паук (Spider)" + +#: pysollib/gamedb.py:162 +msgid "Terrace type" +msgstr "Игры типа Терраса (Terrace)" + +#: pysollib/gamedb.py:163 +msgid "Yukon type" +msgstr "Игры типа Юкон (Yukon)" + +#: pysollib/gamedb.py:188 pysollib/gamedb.py:196 +msgid "French type" +msgstr "Классические" + +#: pysollib/gamedb.py:189 pysollib/gamedb.py:197 pysollib/gamedb.py:206 +msgid "Ganjifa type" +msgstr "Игры типа Ганджифа (Ganjifa)" + +#: pysollib/gamedb.py:190 pysollib/gamedb.py:198 pysollib/gamedb.py:207 +msgid "Hanafuda type" +msgstr "Игры типа Ханафуда (Hanafuda)" + +#: pysollib/gamedb.py:191 pysollib/gamedb.py:199 pysollib/gamedb.py:214 +msgid "Hex A Deck type" +msgstr "Игры типа Hex A Deck" + +#: pysollib/gamedb.py:192 pysollib/gamedb.py:200 pysollib/gamedb.py:219 +msgid "Tarock type" +msgstr "Таро" + +#: pysollib/gamedb.py:205 +msgid "Dashavatara Ganjifa type" +msgstr "Игры типа Дашаватара Ганджифа (Dashavatara Ganjifa)" + +#: pysollib/gamedb.py:208 +msgid "Mughal Ganjifa type" +msgstr "Игры типа Мугал Ганджифа (Mughal Ganjifa)" + +#: pysollib/gamedb.py:209 +msgid "Navagraha Ganjifa type" +msgstr "Игры типа Наваграха Ганджифа (Navagraha Ganjifa)" + +#: pysollib/gamedb.py:213 +msgid "Shisen-Sho" +msgstr "" + +#: pysollib/gamedb.py:215 +msgid "Matrix type" +msgstr "Мозаика" + +#: pysollib/gamedb.py:216 +msgid "Memory type" +msgstr "Игры на запоминание" + +#: pysollib/gamedb.py:217 +msgid "Poker type" +msgstr "Покер" + +#: pysollib/gamedb.py:218 +msgid "Puzzle type" +msgstr "Пазлы" + +#: pysollib/games/auldlangsyne.py:142 pysollib/games/calculation.py:101 +#: pysollib/games/numerica.py:90 pysollib/games/numerica.py:197 +msgid "Row. Build regardless of rank and suit." +msgstr "" + +#: pysollib/games/braid.py:250 pysollib/games/napoleon.py:190 +#: pysollib/games/ultra/dashavatara.py:959 +#: pysollib/games/ultra/hanafuda1.py:256 pysollib/games/ultra/hexadeck.py:1190 +#: pysollib/games/ultra/mughal.py:802 +msgid " Ascending" +msgstr " вверх" + +#: pysollib/games/braid.py:252 pysollib/games/napoleon.py:192 +#: pysollib/games/ultra/dashavatara.py:961 +#: pysollib/games/ultra/hanafuda1.py:258 pysollib/games/ultra/hexadeck.py:1192 +#: pysollib/games/ultra/mughal.py:804 +msgid " Descending" +msgstr " вниз" + +#: pysollib/games/calculation.py:135 pysollib/games/calculation.py:230 +msgid "" +"1: 2 3 4 5 6 7 8 9 T J Q K\n" +"2: 4 6 8 T Q A 3 5 7 9 J K\n" +"3: 6 9 Q 2 5 8 J A 4 7 T K\n" +"4: 8 Q 3 7 J 2 6 T A 5 9 K" +msgstr "" +"1: 2 3 4 5 6 7 8 9 10 В Д К\n" +"2: 4 6 8 10 Д Т 3 5 7 9 В К\n" +"3: 6 9 Д 2 5 8 В Т 4 7 10 К\n" +"4: 8 Д 3 7 В 2 6 10 Т 5 9 К" + +#: pysollib/games/curdsandwhey.py:58 +msgid "Row. Build down by suit or of the same rank." +msgstr "" + +#: pysollib/games/fan.py:279 +msgid "Draw" +msgstr "Снять" + +#: pysollib/games/fan.py:279 +msgid "X" +msgstr "Х" + +#: pysollib/games/fortythieves.py:393 pysollib/games/klondike.py:148 +msgid "Row. Build down in any suit but the same." +msgstr "" + +#: pysollib/games/golf.py:114 pysollib/games/golf.py:413 +#: pysollib/stack.py:1740 +msgid "Row. No building." +msgstr "" + +#: pysollib/games/golf.py:381 +msgid "Balance $%4d" +msgstr "Баланс $%4d" + +#: pysollib/games/golf.py:497 pysollib/stack.py:1673 +msgid "Foundation. Build up regardless of suit." +msgstr "" + +#: pysollib/games/klondike.py:115 +msgid "Balance $%d" +msgstr "Баланс $%d" + +#: pysollib/games/klondike.py:387 +msgid "Reserve. Only Kings are acceptable." +msgstr "" + +#: pysollib/games/matriarchy.py:125 +msgid "Round %d/%d" +msgstr "Раунд %d/%d" + +#: pysollib/games/matriarchy.py:127 +msgid "Deal %d" +msgstr "Сдача %d" + +#: pysollib/games/numerica.py:184 +msgid "Foundation. Build up by color." +msgstr "" + +#: pysollib/games/poker.py:82 +msgid "" +"Royal Flush\n" +"Straight Flush\n" +"Four of a Kind\n" +"Full House\n" +"Flush\n" +"Straight\n" +"Three of a Kind\n" +"Two Pair\n" +"One Pair" +msgstr "" +"Флешь Ройал\n" +"Флешь Стрит\n" +"Четыре одинаковых\n" +"Полный дом\n" +"Флешь\n" +"Стрит\n" +"Три одинаковых\n" +"Две пары\n" +"Пара" + +#: pysollib/games/poker.py:189 pysollib/games/special/memory.py:181 +msgid "" +"WON\n" +"\n" +msgstr "" +"Выигрыш\n" +"\n" + +#: pysollib/games/poker.py:191 pysollib/games/special/memory.py:178 +msgid "Points: %d" +msgstr "Очков: %d" + +#: pysollib/games/poker.py:193 pysollib/games/special/memory.py:182 +msgid "Total: %d" +msgstr "Всего: %d" + +#: pysollib/games/special/tarock.py:222 +msgid "Coin" +msgstr "Монеты" + +#: pysollib/games/special/tarock.py:222 +msgid "Cup" +msgstr "Чаши" + +#: pysollib/games/special/tarock.py:222 +msgid "Sword" +msgstr "Мечи" + +#: pysollib/games/special/tarock.py:222 +msgid "Trump" +msgstr "Козырь" + +#: pysollib/games/special/tarock.py:222 +msgid "Wand" +msgstr "Жезлы" + +#: pysollib/games/special/tarock.py:223 +#: pysollib/games/ultra/dashavatara.py:351 +#: pysollib/games/ultra/hexadeck.py:273 pysollib/games/ultra/mughal.py:254 +#: pysollib/stack.py:1190 pysollib/util.py:80 +msgid "Ace" +msgstr "Туз" + +#: pysollib/games/special/tarock.py:224 +msgid "Page" +msgstr "Паж" + +#: pysollib/games/special/tarock.py:224 +msgid "Valet" +msgstr "Валет" + +#: pysollib/games/special/tarock.py:224 pysollib/stack.py:1188 +#: pysollib/util.py:81 +msgid "Queen" +msgstr "Королева" + +#: pysollib/games/special/tarock.py:224 pysollib/stack.py:1189 +#: pysollib/util.py:81 +msgid "King" +msgstr "Король" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Boar" +msgstr "Боров" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Dwarf" +msgstr "Гном" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Fish" +msgstr "Рыба" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Lion" +msgstr "Лев" + +#: pysollib/games/ultra/dashavatara.py:349 +msgid "Tortoise" +msgstr "Черепаха" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Arrow" +msgstr "Стрела" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Axe" +msgstr "Топор" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Horse" +msgstr "Конь" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Lotus" +msgstr "Лотос" + +#: pysollib/games/ultra/dashavatara.py:350 +msgid "Plow" +msgstr "Плуг" + +#: pysollib/games/ultra/dashavatara.py:352 pysollib/games/ultra/mughal.py:255 +msgid "Pradhan" +msgstr "Прадхана" + +#: pysollib/games/ultra/dashavatara.py:352 pysollib/games/ultra/mughal.py:255 +msgid "Raja" +msgstr "Раджа" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Black" +msgstr "Черный" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Brown" +msgstr "Коричневый" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Red" +msgstr "Красный" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:256 +msgid "Yellow" +msgstr "Желтый" + +#: pysollib/games/ultra/dashavatara.py:353 pysollib/games/ultra/mughal.py:257 +#: pysollib/tk/selecttile.py:86 +msgid "Green" +msgstr "Зеленый" + +#: pysollib/games/ultra/dashavatara.py:354 +msgid "Crimson" +msgstr "Темно-красный" + +#: pysollib/games/ultra/dashavatara.py:354 +msgid "White" +msgstr "Белый" + +#: pysollib/games/ultra/dashavatara.py:354 pysollib/games/ultra/mughal.py:257 +msgid "Grey" +msgstr "Серый" + +#: pysollib/games/ultra/dashavatara.py:354 pysollib/games/ultra/mughal.py:257 +#: pysollib/tk/selecttile.py:89 +msgid "Orange" +msgstr "Оранжевый" + +#: pysollib/games/ultra/dashavatara.py:354 pysollib/tk/selecttile.py:88 +msgid "Olive" +msgstr "Оливковый" + +#: pysollib/games/ultra/dashavatara.py:355 pysollib/games/ultra/mughal.py:258 +msgid "Strong" +msgstr "Сильный" + +#: pysollib/games/ultra/dashavatara.py:355 pysollib/games/ultra/mughal.py:258 +msgid "Weak" +msgstr "Слабый" + +#: pysollib/games/ultra/hanafuda.py:373 +msgid "Rising" +msgstr "Вверх" + +#: pysollib/games/ultra/hanafuda.py:375 +msgid "Setting" +msgstr "Вниз" + +#: pysollib/games/ultra/hanafuda.py:511 +msgid "Filled" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid " Deck" +msgstr " колода" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "nd" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "rd" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "st" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:513 +msgid "th" +msgstr "" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "East" +msgstr "Восток" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "North" +msgstr "Север" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "South" +msgstr "Юг" + +#: pysollib/games/ultra/hanafuda.py:563 +msgid "West" +msgstr "Запад" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "NE" +msgstr "СВ" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "NW" +msgstr "СЗ" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "SE" +msgstr "ЮВ" + +#: pysollib/games/ultra/hanafuda.py:564 +msgid "SW" +msgstr "ЮЗ" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Cherry" +msgstr "Вишня" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Pine" +msgstr "Сосна" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Plum" +msgstr "Слива" + +#: pysollib/games/ultra/hanafuda_common.py:67 +msgid "Wisteria" +msgstr "Глициния" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Bush Clover" +msgstr "Клевер" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Eularia" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Iris" +msgstr "Ирис" + +#: pysollib/games/ultra/hanafuda_common.py:68 +msgid "Peony" +msgstr "Пеон" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Chrysanthemum" +msgstr "Хризантема" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Maple" +msgstr "Клен" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Paulownia" +msgstr "" + +#: pysollib/games/ultra/hanafuda_common.py:69 +msgid "Willow" +msgstr "Ива" + +#: pysollib/games/ultra/larasgame.py:157 pysollib/stack.py:1368 +msgid "Round %d" +msgstr "Раунд %d" + +#: pysollib/games/ultra/mughal.py:252 +#, fuzzy +msgid "Crown" +msgstr "Коричневый" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Saber" +msgstr "" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Servant" +msgstr "" + +#: pysollib/games/ultra/mughal.py:252 +msgid "Silver" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +msgid "Document" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +msgid "Gold" +msgstr "" + +#: pysollib/games/ultra/mughal.py:253 +#, fuzzy +msgid "Harp" +msgstr "Черви" + +#: pysollib/games/ultra/mughal.py:253 +#, fuzzy +msgid "Stores" +msgstr "Настроить цвета" + +#: pysollib/games/ultra/mughal.py:257 +msgid "Tan" +msgstr "" + +#: pysollib/games/ultra/threepeaks.py:217 +msgid "Score:\tThis hand: " +msgstr "" + +#: pysollib/games/ultra/threepeaks.py:218 +msgid "\tThis game: " +msgstr "" + +#: pysollib/games/yukon.py:145 +msgid "" +"Row. Build down in any suit but the same, can move any face-up cards " +"regardless of sequence." +msgstr "" + +#: pysollib/games/yukon.py:201 +msgid "" +"Row. Build up or down by suit, can move any face-up cards regardless of " +"sequence." +msgstr "" + +#: pysollib/games/yukon.py:218 +msgid "" +"Row. Build up or down by alternate color, can move any face-up cards " +"regardless of sequence." +msgstr "" + +#: pysollib/games/yukon.py:320 +msgid "" +"Club: A 2 3 4 5 6 7 8 9 T J Q K\n" +"Spade: 2 4 6 8 T Q A 3 5 7 9 J K\n" +"Heart: 3 6 9 Q 2 5 8 J A 4 7 T K\n" +"Diamond: 4 8 Q 3 7 J 2 6 T A 5 9 K" +msgstr "" +"Треф: Т 2 3 4 5 6 7 8 9 10 В Д К\n" +"Пики: 2 4 6 8 10 Д Т 3 5 7 9 В К\n" +"Черви: 3 6 9 Д 2 5 8 В Т 4 7 10 К\n" +"Буби: 4 8 Д 3 7 В 2 6 10 Т 5 9 К" + +#: pysollib/help.py:64 +msgid "A Python Solitaire Game Collection\n" +msgstr "Коллекция питоновских пасьянсев\n" + +#: pysollib/help.py:66 +msgid "A World Domination Project\n" +msgstr "Всемирный непревзойденный проект\n" + +#: pysollib/help.py:67 +msgid "Credits..." +msgstr "Благодарности..." + +#: pysollib/help.py:67 +msgid "Nice" +msgstr "Отлично" + +#: pysollib/help.py:69 +msgid "Enjoy" +msgstr "Наслаждайтесь" + +#: pysollib/help.py:71 +msgid "" +"Version %s\n" +"\n" +msgstr "" +"Версия %s\n" +"\n" + +#: pysollib/help.py:72 +msgid "About " +msgstr "О программе " + +#: pysollib/help.py:73 +msgid "" +"PySol Fan Club edition\n" +"%s%s\n" +"Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003\n" +"Markus F.X.J. Oberhumer\n" +"Copyright (C) 2003 Mt. Hood Playing Card Co.\n" +"Copyright (C) 2005 Skomoroh (Fan Club edition)\n" +"All Rights Reserved.\n" +"\n" +"PySol is free software distributed under the terms\n" +"of the GNU General Public License.\n" +"\n" +"For more information about this application visit\n" +"%s" +msgstr "" +"PySol Fan Club edition\n" +"%s%s\n" +"Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003\n" +"Markus F.X.J. Oberhumer\n" +"Copyright (C) 2003 Mt. Hood Playing Card Co.\n" +"Copyright (C) 2005 Skomoroh (Fan Club edition)\n" +"All Rights Reserved.\n" +"\n" +"PySol свободное программное обеспечение,\n" +"распространяющееся по лицензии GPL\n" +"\n" +"Для получения дополнительной информации\n" +"об этом приложении посетите сайт\n" +"%s" + +#: pysollib/help.py:102 +msgid "Credits" +msgstr "Благодарности" + +#: pysollib/help.py:103 +msgid "" +" credits go to:\n" +"\n" +"Volker Weidner for getting me into Solitaire\n" +"Guido van Rossum for the initial example program\n" +"T. Kirk for lots of contributed games and cardsets\n" +"Carl Larsson for the background music\n" +"The Gnome AisleRiot team for parts of the documentation\n" +"Natascha\n" +"\n" +"The Python, %s, SDL & Linux crews\n" +"for making this program possible" +msgstr "" + +#: pysollib/help.py:138 +msgid " HTML Problem" +msgstr " проблема с HTML" + +#: pysollib/help.py:139 +msgid "Cannot find help document\n" +msgstr "Не найден файл помощи\n" + +#: pysollib/help.py:152 +msgid " Help" +msgstr " Помощь" + +#: pysollib/main.py:68 pysollib/main.py:310 +msgid " installation error" +msgstr " проблема с установкой" + +#: pysollib/main.py:69 +msgid "" +"No %ss were found !!!\n" +"\n" +"Main data directory is:\n" +"%s\n" +"\n" +"Please check your %s installation.\n" +msgstr "" + +#: pysollib/main.py:76 pysollib/main.py:319 pysollib/tk/toolbar.py:197 +msgid "Quit" +msgstr "Выйти" + +#: pysollib/main.py:95 +msgid "" +"%s: %s\n" +"try %s --help for more information" +msgstr "" +"%s: %s\n" +"попробуйте %s --help для получения более подробной информаци" + +#: pysollib/main.py:120 +msgid "" +"Usage: %s [OPTIONS] [FILE]\n" +" --fg --foreground=COLOR foreground color\n" +" --bg --background=COLOR background color\n" +" --fn --font=FONT default font\n" +" --nosound disable sound support\n" +" --noplugins disable load plugins\n" +" -h --help display this help and exit\n" +"\n" +" FILE - file name of a saved game\n" +msgstr "" +"Испльзование: %s [OPTIONS] [FILE]\n" +" --fg --foreground=COLOR цвет текста\n" +" --bg --background=COLOR цвет фона\n" +" --fn --font=FONT шрифт по умолчанию\n" +" --nosound отключить звук\n" +" --noplugins отключить загрузку плагинов\n" +" -h --help показать это сообщение и выйти\n" +"\n" +" FILE - имя файла сохраненной игры\n" + +#: pysollib/main.py:133 +msgid "" +"%s: too many files\n" +"try %s --help for more information" +msgstr "" +"\"%s: слишком много файлов\n" +"попробуйте %s --help для получения более подробной информаци" + +#: pysollib/main.py:137 +msgid "" +"%s: invalide file name\n" +"try %s --help for more information" +msgstr "" +"%s: неправильное имя файла\n" +"попробуйте %s --help для получения более подробной информаци" + +#: pysollib/main.py:311 +msgid "" +"\n" +"No games were found !!!\n" +"\n" +"Main data directory is:\n" +"%s\n" +"\n" +"Please check your %s installation.\n" +msgstr "" + +#: pysollib/main.py:397 pysollib/main.py:402 +msgid " installation problem" +msgstr "" + +#: pysollib/main.py:398 +msgid "" +"Your Python installation is compiled without thread support.\n" +"\n" +"Sounds and background music will be disabled." +msgstr "" + +#: pysollib/main.py:403 +msgid "" +"The pysolsoundserver module was not found.\n" +"\n" +"Sounds and background music will be disabled." +msgstr "" +"Модуль pysolsoundserver не найден\n" +"\n" +"Звук и фоновая музыка будут недоступны" + +#: pysollib/main.py:407 +msgid "Welcome to " +msgstr "Добро пожаловать в " + +#: pysollib/settings.py:58 +msgid "Top 10" +msgstr "Top 10" + +#: pysollib/stack.py:1184 +msgid "Base card - %s." +msgstr "" + +#: pysollib/stack.py:1185 +msgid "Empty row cannot be filled." +msgstr "" + +#: pysollib/stack.py:1186 +msgid "any card" +msgstr "" + +#: pysollib/stack.py:1187 pysollib/util.py:81 +msgid "Jack" +msgstr "Валет" + +#: pysollib/stack.py:1196 +msgid "No cards" +msgstr "Нет карт" + +#: pysollib/stack.py:1197 +msgid "1 card" +msgstr "1 карта" + +#: pysollib/stack.py:1198 +msgid " cards" +msgstr " карт" + +#: pysollib/stack.py:1377 pysollib/stack.py:1379 pysollib/stack.py:1410 +msgid "Redeal" +msgstr "Сдать" + +#: pysollib/stack.py:1379 +msgid "Stop" +msgstr "Стоп" + +#: pysollib/stack.py:1430 +msgid "Variable redeals." +msgstr "Переменное количество пересдач." + +#: pysollib/stack.py:1431 +msgid "Unlimited redeals." +msgstr "Неограниченное количество пересдач." + +#: pysollib/stack.py:1432 +msgid "No redeals." +msgstr "Без пересдачи." + +#: pysollib/stack.py:1433 +msgid "One redeal." +msgstr "1 пересдача." + +#: pysollib/stack.py:1434 +msgid " redeals." +msgstr " пересдачи." + +#: pysollib/stack.py:1436 +msgid "Talon." +msgstr "" + +#: pysollib/stack.py:1611 pysollib/stack.py:2035 +msgid "Reserve. No building." +msgstr "" + +#: pysollib/stack.py:1657 +msgid "Foundation. Build up by suit." +msgstr "" + +#: pysollib/stack.py:1658 +msgid "Foundation. Build down by suit." +msgstr "" + +#: pysollib/stack.py:1659 pysollib/stack.py:1675 pysollib/stack.py:1697 +msgid "Foundation. Build by same rank." +msgstr "" + +#: pysollib/stack.py:1674 +msgid "Foundation. Build down regardless of suit." +msgstr "" + +#: pysollib/stack.py:1695 +msgid "Foundation. Build up by alternate color." +msgstr "" + +#: pysollib/stack.py:1696 +msgid "Foundation. Build down by alternate color." +msgstr "" + +#: pysollib/stack.py:1770 +msgid "Row. Build up by alternate color." +msgstr "" + +#: pysollib/stack.py:1771 +msgid "Row. Build down by alternate color." +msgstr "" + +#: pysollib/stack.py:1772 pysollib/stack.py:1782 pysollib/stack.py:1791 +#: pysollib/stack.py:1800 pysollib/stack.py:1828 +msgid "Row. Build by same rank." +msgstr "" + +#: pysollib/stack.py:1780 +msgid "Row. Build up by color." +msgstr "" + +#: pysollib/stack.py:1781 +msgid "Row. Build down by color." +msgstr "" + +#: pysollib/stack.py:1789 +msgid "Row. Build up by suit." +msgstr "" + +#: pysollib/stack.py:1790 +msgid "Row. Build down by suit." +msgstr "" + +#: pysollib/stack.py:1798 pysollib/stack.py:1826 +msgid "Row. Build up regardless of suit." +msgstr "" + +#: pysollib/stack.py:1799 pysollib/stack.py:1827 +msgid "Row. Build down regardless of suit." +msgstr "" + +#: pysollib/stack.py:1849 +msgid "" +"Row. Build up by alternate color, can move any face-up cards regardless of " +"sequence." +msgstr "" + +#: pysollib/stack.py:1850 +msgid "" +"Row. Build down by alternate color, can move any face-up cards regardless of " +"sequence." +msgstr "" + +#: pysollib/stack.py:1851 pysollib/stack.py:1862 +msgid "" +"Row. Build by same rank, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1860 +msgid "" +"Row. Build up by suit, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1861 +msgid "" +"Row. Build down by suit, can move any face-up cards regardless of sequence." +msgstr "" + +#: pysollib/stack.py:1894 +msgid "Row. Build up or down by color." +msgstr "" + +#: pysollib/stack.py:1905 +msgid "Row. Build up or down by alternate color." +msgstr "" + +#: pysollib/stack.py:1916 +msgid "Row. Build up or down by suit." +msgstr "" + +#: pysollib/stack.py:1927 +msgid "Row. Build up or down regardless of suit." +msgstr "" + +#: pysollib/stack.py:1938 +msgid "Waste." +msgstr "" + +#: pysollib/stack.py:2036 +msgid "Free cell." +msgstr "Свободная ячейка." + +#: pysollib/stats.py:120 pysollib/tk/tkstats.py:78 +msgid "Demo games" +msgstr "Демо игры" + +#: pysollib/stats.py:121 +msgid "Played" +msgstr "Играл" + +#: pysollib/stats.py:122 pysollib/stats.py:202 +msgid "Won" +msgstr "Выиграл" + +#: pysollib/stats.py:123 pysollib/stats.py:202 +msgid "Lost" +msgstr "Проиграл" + +#: pysollib/stats.py:124 pysollib/tk/statusbar.py:134 +msgid "Playing time" +msgstr "Время игры" + +#: pysollib/stats.py:125 +msgid "Moves" +msgstr "Ходов" + +#: pysollib/stats.py:126 +msgid "% won" +msgstr "% побед" + +#: pysollib/stats.py:155 +msgid "Total (%d out of %d games)" +msgstr "Всего (%d из %d игр)" + +#: pysollib/stats.py:164 +msgid "Game" +msgstr "Игра" + +#: pysollib/stats.py:164 +msgid "Started at " +msgstr "Игра начата " + +#: pysollib/stats.py:164 +msgid "Status" +msgstr "Статус" + +#: pysollib/stats.py:164 pysollib/tk/statusbar.py:136 +#: pysollib/tk/tkstats.py:734 +msgid "Game number" +msgstr "Номер игры" + +#: pysollib/stats.py:187 +msgid "** UNKNOWN %d **" +msgstr "" + +#: pysollib/stats.py:195 +msgid "** ERROR **" +msgstr "" + +#: pysollib/stats.py:202 +msgid "Loaded" +msgstr "Загружал" + +#: pysollib/stats.py:202 +msgid "Not won" +msgstr "Не выиграл" + +#: pysollib/stats.py:202 +msgid "Perfect" +msgstr "Великолепная" + +#: pysollib/tk/colorsdialog.py:73 +msgid "Text foreground:" +msgstr "Цвет текста:" + +#: pysollib/tk/colorsdialog.py:79 pysollib/tk/colorsdialog.py:97 +#: pysollib/tk/fontsdialog.py:185 +msgid "Change..." +msgstr "Изменить..." + +#: pysollib/tk/colorsdialog.py:85 pysollib/tk/timeoutsdialog.py:68 +msgid "Highlight piles:" +msgstr "Подсветка групп:" + +#: pysollib/tk/colorsdialog.py:86 +msgid "Highlight cards 1:" +msgstr "Подсветка карт 1:" + +#: pysollib/tk/colorsdialog.py:87 +msgid "Highlight cards 2:" +msgstr "Подсветка карт 2:" + +#: pysollib/tk/colorsdialog.py:88 +msgid "Highlight same rank 1:" +msgstr "Подсветка карт одного достоинства 1:" + +#: pysollib/tk/colorsdialog.py:89 +msgid "Highlight same rank 2:" +msgstr "Подсветка карт одного достоинства 2:" + +#: pysollib/tk/colorsdialog.py:90 +msgid "Hint arrow:" +msgstr "Стрелка подсказки:" + +#: pysollib/tk/colorsdialog.py:91 +msgid "Highlight not matching:" +msgstr "Подсветка отсутствия совпадения:" + +#: pysollib/tk/colorsdialog.py:123 +msgid "Select color" +msgstr "Выбрать цвет" + +#: pysollib/tk/demooptionsdialog.py:66 +msgid "Display floating Demo logo" +msgstr "Показывать демо лого" + +#: pysollib/tk/demooptionsdialog.py:69 +msgid "Show score in statusbar" +msgstr "Показывать счет в строке состояния" + +#: pysollib/tk/demooptionsdialog.py:73 +msgid "Set demo delay in seconds" +msgstr "Установить задуржку демо в секундах" + +#: pysollib/tk/fontsdialog.py:85 +msgid "abcdefghABCDEFGH" +msgstr "abcdeABCDE абвгдАБВГД" + +#: pysollib/tk/fontsdialog.py:94 +msgid "Bold" +msgstr "Жирный" + +#: pysollib/tk/fontsdialog.py:97 +msgid "Italic" +msgstr "Наклонный" + +#: pysollib/tk/fontsdialog.py:195 +msgid "Select font" +msgstr "Выбрать шрифт" + +#: pysollib/tk/menubar.py:75 +msgid "Style" +msgstr "Стиль" + +#: pysollib/tk/menubar.py:84 +msgid "Relief" +msgstr "Рельеф" + +#: pysollib/tk/menubar.py:85 +msgid "Flat" +msgstr "Плоский" + +#: pysollib/tk/menubar.py:89 +msgid "Raised" +msgstr "Выпуклый" + +#: pysollib/tk/menubar.py:94 +msgid "Compound" +msgstr "Компоновка" + +#: pysollib/tk/menubar.py:100 +msgid "Hide" +msgstr "Спрятать" + +#: pysollib/tk/menubar.py:103 +msgid "Top" +msgstr "Сверху" + +#: pysollib/tk/menubar.py:106 +msgid "Bottom" +msgstr "Внизу" + +#: pysollib/tk/menubar.py:109 +msgid "Left" +msgstr "Слева" + +#: pysollib/tk/menubar.py:112 +msgid "Right" +msgstr "Справа" + +#: pysollib/tk/menubar.py:116 +msgid "Small icons" +msgstr "Маленькие пиктограммы" + +#: pysollib/tk/menubar.py:119 +msgid "Large icons" +msgstr "Большие пиктограммы" + +#: pysollib/tk/menubar.py:125 +msgid "Customize toolbar" +msgstr "Настроить панель инструментов" + +#: pysollib/tk/menubar.py:248 +msgid "&File" +msgstr "&Файл" + +#: pysollib/tk/menubar.py:249 +msgid "&New game" +msgstr "&Новая игра" + +#: pysollib/tk/menubar.py:250 +msgid "R&ecent games" +msgstr "Выбрать н&едавнюю игру" + +#: pysollib/tk/menubar.py:252 +msgid "Select &random game" +msgstr "С&лучайная игра" + +#: pysollib/tk/menubar.py:253 +msgid "&All games" +msgstr "&Все игры" + +#: pysollib/tk/menubar.py:254 +msgid "Games played and &won" +msgstr "&Выигранные игры" + +#: pysollib/tk/menubar.py:255 +msgid "Games played and ¬ won" +msgstr "&Невыигранные игры" + +#: pysollib/tk/menubar.py:256 +msgid "Games not &played" +msgstr "Не&сыгранные игры" + +#: pysollib/tk/menubar.py:257 +msgid "Select game by nu&mber..." +msgstr "Выбрать игру по &номеру..." + +#: pysollib/tk/menubar.py:259 +msgid "Fa&vorite games" +msgstr "&Избранные игры" + +#: pysollib/tk/menubar.py:260 +msgid "A&dd to favorites" +msgstr "&Добавить в избранное" + +#: pysollib/tk/menubar.py:261 +msgid "R&emove from favorites" +msgstr "&Удалить из избранных" + +#: pysollib/tk/menubar.py:263 +msgid "&Open..." +msgstr "&Открыть..." + +#: pysollib/tk/menubar.py:264 +msgid "&Save" +msgstr "&Сохранить" + +#: pysollib/tk/menubar.py:265 +msgid "Save &as..." +msgstr "Сохранить &как..." + +#: pysollib/tk/menubar.py:267 +msgid "&Hold and quit" +msgstr "Со&храниться и выйти" + +#: pysollib/tk/menubar.py:268 +msgid "&Quit" +msgstr "В&ыход" + +#: pysollib/tk/menubar.py:270 +msgid "&Select" +msgstr "&Выбрать" + +#: pysollib/tk/menubar.py:273 +msgid "&Edit" +msgstr "Р&едактировать" + +#: pysollib/tk/menubar.py:274 +msgid "&Undo" +msgstr "&Отмена" + +#: pysollib/tk/menubar.py:275 +msgid "&Redo" +msgstr "&Повтор" + +#: pysollib/tk/menubar.py:276 +msgid "Redo &all" +msgstr "Вернуть все" + +#: pysollib/tk/menubar.py:279 +msgid "&Set bookmark" +msgstr "Установить &закладку" + +#: pysollib/tk/menubar.py:281 pysollib/tk/menubar.py:285 +msgid "Bookmark %d" +msgstr "Закладка %d" + +#: pysollib/tk/menubar.py:283 +msgid "Go&to bookmark" +msgstr "&Перейти к закладке" + +#: pysollib/tk/menubar.py:288 +msgid "&Clear bookmarks" +msgstr "О&чистить закладки" + +#: pysollib/tk/menubar.py:291 +msgid "Restart &game" +msgstr "&Начать с начала" + +#: pysollib/tk/menubar.py:293 +msgid "&Game" +msgstr "&Игра" + +#: pysollib/tk/menubar.py:294 +msgid "&Deal cards" +msgstr "&Сдать карты" + +#: pysollib/tk/menubar.py:295 pysollib/tk/menubar.py:324 +msgid "&Auto drop" +msgstr "С&бросить карты" + +#: pysollib/tk/menubar.py:296 +msgid "&Pause" +msgstr "&Пауза" + +#: pysollib/tk/menubar.py:299 +msgid "S&tatus..." +msgstr "С&татус" + +#: pysollib/tk/menubar.py:300 +msgid "&Comments..." +msgstr "&Комментарии..." + +#: pysollib/tk/menubar.py:302 +msgid "&Statistics" +msgstr "Ст&атистика" + +#: pysollib/tk/menubar.py:303 pysollib/tk/menubar.py:311 +msgid "Current game..." +msgstr "Текущая игра..." + +#: pysollib/tk/menubar.py:304 pysollib/tk/menubar.py:312 +#: pysollib/tk/tkstats.py:289 +msgid "All games..." +msgstr "Все игры..." + +#: pysollib/tk/menubar.py:306 pysollib/tk/tkstats.py:647 +msgid "Session log..." +msgstr "Лог сессии..." + +#: pysollib/tk/menubar.py:307 pysollib/tk/tkstats.py:663 +msgid "Full log..." +msgstr "Полный лог..." + +#: pysollib/tk/menubar.py:310 +msgid "D&emo statistics" +msgstr "Статистика демо" + +#: pysollib/tk/menubar.py:314 +msgid "&Assist" +msgstr "&Подсказка" + +#: pysollib/tk/menubar.py:315 +msgid "&Hint" +msgstr "Подсказать &ход" + +#: pysollib/tk/menubar.py:316 +msgid "Highlight p&iles" +msgstr "П&оказать группы" + +#: pysollib/tk/menubar.py:318 +msgid "&Demo" +msgstr "&Демо" + +#: pysollib/tk/menubar.py:319 +msgid "Demo (&all games)" +msgstr "Демо (&все игры)" + +#: pysollib/tk/menubar.py:320 +msgid "&Options" +msgstr "&Настройка" + +#: pysollib/tk/menubar.py:321 +msgid "&Player options..." +msgstr "Настройки &игрока..." + +#: pysollib/tk/menubar.py:322 +msgid "&Automatic play" +msgstr "Настройки &автоматической игры" + +#: pysollib/tk/menubar.py:323 +msgid "Auto &face up" +msgstr "Автоматически переворачивать" + +#: pysollib/tk/menubar.py:325 +msgid "Auto &deal" +msgstr "Автоматически &сдавать карты" + +#: pysollib/tk/menubar.py:327 +msgid "&Quick play" +msgstr "&Быстрая игра" + +#: pysollib/tk/menubar.py:328 +msgid "Assist &level" +msgstr "&Уровень подсказки" + +#: pysollib/tk/menubar.py:329 +msgid "Enable &undo" +msgstr "Разрешить &возврат хода" + +#: pysollib/tk/menubar.py:330 +msgid "Enable &bookmarks" +msgstr "Разрешить &закладки" + +#: pysollib/tk/menubar.py:331 +msgid "Enable &hint" +msgstr "Разрешить &подсказки" + +#: pysollib/tk/menubar.py:332 +msgid "Enable highlight p&iles" +msgstr "Разрешить показывать к&учи" + +#: pysollib/tk/menubar.py:333 +msgid "Enable highlight &cards" +msgstr "Разрешить показывать &карты" + +#: pysollib/tk/menubar.py:334 +msgid "Enable highlight same &rank" +msgstr "Разрешить показывать карты &одного достоинства" + +#: pysollib/tk/menubar.py:335 +msgid "Highlight &no matching" +msgstr "Подсветка отсутствия &совпадения:" + +#: pysollib/tk/menubar.py:337 +msgid "Show removed tiles (in Mahjongg games)" +msgstr "Показывать удаленные (в Маджонгг)" + +#: pysollib/tk/menubar.py:338 +msgid "Show hint arrow (in Shisen-Sho games)" +msgstr "Показывать стрелку (в Shisen-Sho)" + +#: pysollib/tk/menubar.py:340 +msgid "&Sound" +msgstr "&Звук" + +#: pysollib/tk/menubar.py:350 +msgid "Cards&et..." +msgstr "Коло&да..." + +#: pysollib/tk/menubar.py:351 +msgid "Table t&ile..." +msgstr "&Игровой стол..." + +#: pysollib/tk/menubar.py:353 +msgid "Card &background" +msgstr "&Рубашка карты" + +#: pysollib/tk/menubar.py:354 +msgid "Card &view" +msgstr "&Вид карты" + +#: pysollib/tk/menubar.py:355 +msgid "Card shado&w" +msgstr "Тень карты" + +#: pysollib/tk/menubar.py:356 +msgid "Shade &legal moves" +msgstr "Подсвечивать &разрешенные ходы" + +#: pysollib/tk/menubar.py:357 +msgid "&Negative card bottom" +msgstr "&Негативные контуры карты" + +#: pysollib/tk/menubar.py:358 +msgid "A&nimations" +msgstr "&Анимация" + +#: pysollib/tk/menubar.py:359 +msgid "&None" +msgstr "&Нет" + +#: pysollib/tk/menubar.py:360 +msgid "&Timer based" +msgstr "Базирующаяся на &таймере" + +#: pysollib/tk/menubar.py:361 +msgid "&Fast" +msgstr "&Быстрая" + +#: pysollib/tk/menubar.py:362 +msgid "&Slow" +msgstr "&Медленная" + +#: pysollib/tk/menubar.py:363 +msgid "&Very slow" +msgstr "&Очень медленная" + +#: pysollib/tk/menubar.py:364 +msgid "Stick&y mouse" +msgstr "&Липкая мышь" + +#: pysollib/tk/menubar.py:368 +msgid "&Fonts..." +msgstr "&Шрифты..." + +#: pysollib/tk/menubar.py:369 +msgid "&Colors..." +msgstr "&Цвета..." + +#: pysollib/tk/menubar.py:370 +msgid "Time&outs..." +msgstr "Тайма&уты..." + +#: pysollib/tk/menubar.py:372 +msgid "&Toolbar" +msgstr "Панель &инструментов" + +#: pysollib/tk/menubar.py:374 +msgid "Stat&usbar" +msgstr "Панель состояния" + +#: pysollib/tk/menubar.py:375 +msgid "Show &statusbar" +msgstr "Показывать панель состояния" + +#: pysollib/tk/menubar.py:376 +msgid "Show &number of cards" +msgstr "Показывать количество карт" + +#: pysollib/tk/menubar.py:377 +msgid "Show &help bar" +msgstr "Показывать панель помощи" + +#: pysollib/tk/menubar.py:378 +msgid "&Demo logo" +msgstr "&Демо лого" + +#: pysollib/tk/menubar.py:379 +msgid "Startup splash sc&reen" +msgstr "Окно &запуска" + +#: pysollib/tk/menubar.py:383 +msgid "&Help" +msgstr "&Помощь" + +#: pysollib/tk/menubar.py:384 +msgid "&Contents" +msgstr "&Содержание" + +#: pysollib/tk/menubar.py:385 +msgid "&How to play" +msgstr "Как &играть" + +#: pysollib/tk/menubar.py:386 +msgid "&Rules for this game" +msgstr "&Правила текущей игры" + +#: pysollib/tk/menubar.py:387 +msgid "&License terms" +msgstr "&Лицензия" + +#: pysollib/tk/menubar.py:390 +msgid "&About " +msgstr "&О программе " + +#: pysollib/tk/menubar.py:498 +msgid "All &games..." +msgstr "&Все игры..." + +#: pysollib/tk/menubar.py:499 +msgid "Playable pre&view..." +msgstr "Играемый &предпросмотр..." + +#: pysollib/tk/menubar.py:501 +msgid "&Popular games" +msgstr "&Популярные игры" + +#: pysollib/tk/menubar.py:504 +msgid "&French games" +msgstr "&Классические игры" + +#: pysollib/tk/menubar.py:507 +msgid "&Mahjongg games" +msgstr "Игры маджонг" + +#: pysollib/tk/menubar.py:510 +msgid "&Oriental games" +msgstr "&Восточные игры" + +#: pysollib/tk/menubar.py:514 +msgid "&Special games" +msgstr "&Особые игры" + +#: pysollib/tk/menubar.py:518 +msgid "All games by name" +msgstr "Все игры по имени" + +#: pysollib/tk/menubar.py:855 pysollib/tk/menubar.py:857 +#: pysollib/tk/selectcardset.py:240 +msgid "Load" +msgstr "Загрузить" + +#: pysollib/tk/menubar.py:857 +msgid "Info..." +msgstr "Информация..." + +#: pysollib/tk/menubar.py:860 +msgid "Select " +msgstr "Выбрать " + +#: pysollib/tk/menubar.py:920 +msgid "Select table background" +msgstr "Выбрать фоновое изображение" + +#: pysollib/tk/menubar.py:932 pysollib/tk/selecttile.py:176 +msgid "Select table color" +msgstr "Выбрать цвет" + +#: pysollib/tk/playeroptionsdialog.py:113 +msgid "" +"\n" +"Please enter your name" +msgstr "" +"\n" +"Пожалуйста введите Ваше имя" + +#: pysollib/tk/playeroptionsdialog.py:121 +msgid "Select..." +msgstr "Выбрать..." + +#: pysollib/tk/playeroptionsdialog.py:125 +msgid "Confirm quit" +msgstr "Подтверждение выхода" + +#: pysollib/tk/playeroptionsdialog.py:129 +msgid "Update statistics and logs" +msgstr "Обнавлять статистику и лог" + +#: pysollib/tk/playeroptionsdialog.py:146 +msgid "Select name" +msgstr "Выбрать имя" + +#: pysollib/tk/selectcardset.py:81 pysollib/tk/selectcardset.py:146 +msgid "(no cardsets)" +msgstr "(нет колод)" + +#: pysollib/tk/selectcardset.py:91 pysollib/tk/selectcardset.py:154 +msgid "by Type" +msgstr "По типу" + +#: pysollib/tk/selectcardset.py:101 pysollib/tk/selectcardset.py:112 +#: pysollib/tk/selectcardset.py:123 +msgid "Uncategorized" +msgstr "Неопределенный" + +#: pysollib/tk/selectcardset.py:102 +msgid "by Style" +msgstr "По стилю" + +#: pysollib/tk/selectcardset.py:113 +msgid "by Nationality" +msgstr "По национальности" + +#: pysollib/tk/selectcardset.py:124 +msgid "by Date" +msgstr "По дате" + +#: pysollib/tk/selectcardset.py:127 +msgid "All Cardsets" +msgstr "Все колоды" + +#: pysollib/tk/selectcardset.py:128 +msgid "by Size" +msgstr "По размеру" + +#: pysollib/tk/selectcardset.py:129 +msgid "Tiny cardsets" +msgstr "Очень маленькие колоды" + +#: pysollib/tk/selectcardset.py:130 +msgid "Small cardsets" +msgstr "Маленькие колоды" + +#: pysollib/tk/selectcardset.py:131 +msgid "Medium cardsets" +msgstr "Средние колоды" + +#: pysollib/tk/selectcardset.py:132 +msgid "Large cardsets" +msgstr "Большие колоды" + +#: pysollib/tk/selectcardset.py:133 +msgid "XLarge cardsets" +msgstr "Очень большие колоды" + +#: pysollib/tk/selectcardset.py:319 +msgid "About cardset" +msgstr "О наборе карт" + +#: pysollib/tk/selectcardset.py:335 pysollib/tk/selectgame.py:367 +msgid "Type:" +msgstr "Тип:" + +#: pysollib/tk/selectcardset.py:336 +msgid "Styles:" +msgstr "Стиль:" + +#: pysollib/tk/selectcardset.py:337 +msgid "Nationality:" +msgstr "Национальность:" + +#: pysollib/tk/selectcardset.py:338 +msgid "Year:" +msgstr "Год:" + +#: pysollib/tk/selectcardset.py:340 +msgid "Size:" +msgstr "Размер:" + +#: pysollib/tk/selectgame.py:100 +msgid "(no games)" +msgstr "(нет игр)" + +#: pysollib/tk/selectgame.py:118 +msgid "French games" +msgstr "Классические игры" + +#: pysollib/tk/selectgame.py:121 +msgid "Oriental Games" +msgstr "Восточные игры" + +#: pysollib/tk/selectgame.py:124 +msgid "Special Games" +msgstr "Особые игры" + +#: pysollib/tk/selectgame.py:127 +msgid "Original Games" +msgstr "Оригинальные игры" + +#: pysollib/tk/selectgame.py:141 +msgid "by Compatibility" +msgstr "По совместимости с другими программами" + +#: pysollib/tk/selectgame.py:159 +msgid "New games in v" +msgstr "Новые игры в версии " + +#: pysollib/tk/selectgame.py:162 +msgid "by PySol version" +msgstr "По версии PySol" + +#: pysollib/tk/selectgame.py:169 +msgid "All Games" +msgstr "Все игры" + +#: pysollib/tk/selectgame.py:170 +msgid "Alternate Names" +msgstr "Другие имена" + +#: pysollib/tk/selectgame.py:171 +msgid "Popular Games" +msgstr "Популярные игры" + +#: pysollib/tk/selectgame.py:172 +msgid "Mahjongg Games" +msgstr "Игры маджонг" + +#: pysollib/tk/selectgame.py:178 +msgid "by Game Feature" +msgstr "По особенностям игры" + +#: pysollib/tk/selectgame.py:179 +msgid "by Number of Cards" +msgstr "По количеству карт" + +#: pysollib/tk/selectgame.py:180 +msgid "32 cards" +msgstr "32 карты" + +#: pysollib/tk/selectgame.py:181 +msgid "48 cards" +msgstr "48 карт" + +#: pysollib/tk/selectgame.py:182 +msgid "52 cards" +msgstr "52 карты" + +#: pysollib/tk/selectgame.py:183 +msgid "64 cards" +msgstr "64 карты" + +#: pysollib/tk/selectgame.py:184 +msgid "78 cards" +msgstr "78 карт" + +#: pysollib/tk/selectgame.py:185 +msgid "104 cards" +msgstr "104 карты" + +#: pysollib/tk/selectgame.py:186 +msgid "144 cards" +msgstr "144 карты" + +#: pysollib/tk/selectgame.py:187 +msgid "Other number" +msgstr "Другое количество" + +#: pysollib/tk/selectgame.py:189 +msgid "by Number of Decks" +msgstr "По количеству колод" + +#: pysollib/tk/selectgame.py:190 +msgid "1 deck games" +msgstr "Игры с 1 колодой" + +#: pysollib/tk/selectgame.py:191 +msgid "2 deck games" +msgstr "Игры с 2 колодами" + +#: pysollib/tk/selectgame.py:192 +msgid "3 deck games" +msgstr "Игры с 3 колодами" + +#: pysollib/tk/selectgame.py:193 +msgid "4 deck games" +msgstr "Игры с 4 колодами" + +#: pysollib/tk/selectgame.py:195 +msgid "by Number of Redeals" +msgstr "По количеству пересдач" + +#: pysollib/tk/selectgame.py:196 +msgid "No redeal" +msgstr "Без пересдачи" + +#: pysollib/tk/selectgame.py:197 +msgid "1 redeal" +msgstr "1 пересдача" + +#: pysollib/tk/selectgame.py:198 +msgid "2 redeals" +msgstr "2 пересдачи" + +#: pysollib/tk/selectgame.py:199 +msgid "3 redeals" +msgstr "3 пересдачи" + +#: pysollib/tk/selectgame.py:200 +msgid "Unlimited redeals" +msgstr "Неограниченное количество пересдач" + +#: pysollib/tk/selectgame.py:202 +msgid "Other number of redeals" +msgstr "Другое количество пересдач" + +#: pysollib/tk/selectgame.py:207 +msgid "Other Categories" +msgstr "Другие категории" + +#: pysollib/tk/selectgame.py:208 +msgid "Games for Children (very easy)" +msgstr "Игры для детей (очень легкие)" + +#: pysollib/tk/selectgame.py:209 +msgid "Games with Scoring" +msgstr "Игры со счётом" + +#: pysollib/tk/selectgame.py:210 +msgid "Games with Separate Decks" +msgstr "Игры с раздельными колодами" + +#: pysollib/tk/selectgame.py:211 +msgid "Open Games (all cards visible)" +msgstr "Открытые игры (все карты видны)" + +#: pysollib/tk/selectgame.py:212 +msgid "Relaxed Variants" +msgstr "Облегченные варианты" + +#: pysollib/tk/selectgame.py:351 +msgid "About game" +msgstr "Об игре " + +#: pysollib/tk/selectgame.py:364 +msgid "Name:" +msgstr "Имя:" + +#: pysollib/tk/selectgame.py:365 +msgid "Alternate names:" +msgstr "Другие имена:" + +#: pysollib/tk/selectgame.py:366 +msgid "Category:" +msgstr "Категория:" + +#: pysollib/tk/selectgame.py:368 +msgid "Decks:" +msgstr "Колод:" + +#: pysollib/tk/selectgame.py:369 +msgid "Redeals:" +msgstr "Пересдач:" + +#: pysollib/tk/selectgame.py:371 +msgid "Played:" +msgstr "Играл:" + +#: pysollib/tk/selectgame.py:372 pysollib/tk/tkstats.py:111 +#: pysollib/tk/tkstats.py:163 +msgid "Won:" +msgstr "Выиграл:" + +#: pysollib/tk/selectgame.py:373 pysollib/tk/tkstats.py:112 +#: pysollib/tk/tkstats.py:164 +msgid "Lost:" +msgstr "Проиграл:" + +#: pysollib/tk/selectgame.py:374 pysollib/tk/tkstats.py:804 +msgid "Playing time:" +msgstr "Игровое время:" + +#: pysollib/tk/selectgame.py:375 pysollib/tk/tkstats.py:811 +msgid "Moves:" +msgstr "Ходов:" + +#: pysollib/tk/selectgame.py:376 +msgid "% won:" +msgstr "% побед:" + +#: pysollib/tk/selectgame.py:409 +msgid "Select" +msgstr "Выбрать" + +#: pysollib/tk/selectgame.py:409 pysollib/tk/toolbar.py:195 +msgid "Rules" +msgstr "Правила" + +#: pysollib/tk/selectgame.py:490 +msgid "Playable Preview - " +msgstr "Играемый предпросмотр - " + +#: pysollib/tk/selectgame.py:538 +msgid "variable" +msgstr "переменное кол-во" + +#: pysollib/tk/selectgame.py:539 +msgid "unlimited" +msgstr "неограниченное кол-во" + +#: pysollib/tk/selecttile.py:80 +msgid "(no tiles)" +msgstr "(нет плитки)" + +#: pysollib/tk/selecttile.py:84 +msgid "Solid Colors" +msgstr "Монотонный цвет" + +#: pysollib/tk/selecttile.py:85 +msgid "Blue" +msgstr "Голубой" + +#: pysollib/tk/selecttile.py:87 +msgid "Navy" +msgstr "Синий" + +#: pysollib/tk/selecttile.py:90 +msgid "Teal" +msgstr "Чайный" + +#: pysollib/tk/selecttile.py:92 +msgid "All Backgrounds" +msgstr "Все фоновые изображения" + +#: pysollib/tk/selecttile.py:158 +msgid "Solid color..." +msgstr "Монотонный цвет..." + +#: pysollib/tk/soundoptionsdialog.py:76 +msgid "Sound enabled" +msgstr "Звук доступен" + +#: pysollib/tk/soundoptionsdialog.py:82 +msgid "Use DirectX for sound playing" +msgstr "Использовать DirectX длы вывода звука" + +#: pysollib/tk/soundoptionsdialog.py:88 +msgid "Sample volume" +msgstr "Уровень звуков" + +#: pysollib/tk/soundoptionsdialog.py:94 +msgid "Music volume" +msgstr "Уровень музыки" + +#: pysollib/tk/soundoptionsdialog.py:106 +msgid "Apply" +msgstr "Применить" + +#: pysollib/tk/soundoptionsdialog.py:106 pysollib/tk/soundoptionsdialog.py:108 +msgid "Mixer..." +msgstr "Миксер..." + +#: pysollib/tk/soundoptionsdialog.py:155 +msgid "Sound preferences info" +msgstr "Информация о настройках звука" + +#: pysollib/tk/soundoptionsdialog.py:156 +msgid "" +"Changing DirectX settings will take effect\n" +"the next time you restart " +msgstr "" +"Изменения установок DirectX вступят в силу\n" +"при следующем запуске " + +#: pysollib/tk/statusbar.py:135 +msgid "Moves/Total moves" +msgstr "Ходов/Всего ходов" + +#: pysollib/tk/statusbar.py:137 +msgid "Games played: won/lost" +msgstr "Игр: выиграно/проиграно" + +#: pysollib/tk/timeoutsdialog.py:65 +msgid "Demo:" +msgstr "Демо:" + +#: pysollib/tk/timeoutsdialog.py:66 +msgid "Hint:" +msgstr "Подсказка:" + +#: pysollib/tk/timeoutsdialog.py:67 +msgid "Raise card:" +msgstr "Подъем карты:" + +#: pysollib/tk/timeoutsdialog.py:69 +msgid "Highlight cards:" +msgstr "Подсветка карты:" + +#: pysollib/tk/timeoutsdialog.py:70 +msgid "Highlight same rank:" +msgstr "Подсветка одинаковых карт:" + +#: pysollib/tk/tkconst.py:104 +msgid "Icons only" +msgstr "Только пиктограммы" + +#: pysollib/tk/tkconst.py:105 +msgid "Text below icons" +msgstr "Текст под пиктограммами" + +#: pysollib/tk/tkconst.py:106 +msgid "Text beside icons" +msgstr "Текст рядом с пиктограммами" + +#: pysollib/tk/tkconst.py:107 +msgid "Text only" +msgstr "Только текст" + +#: pysollib/tk/tkhtml.py:280 +msgid "Index" +msgstr "Индекс" + +#: pysollib/tk/tkhtml.py:284 +msgid "Back" +msgstr "Назад" + +#: pysollib/tk/tkhtml.py:288 +msgid "Forward" +msgstr "Вперед" + +#: pysollib/tk/tkhtml.py:292 +msgid "Close" +msgstr "Закрыть" + +#: pysollib/tk/tkhtml.py:394 +msgid "" +" HTML limitation:\n" +"The %s protocol is not supported yet.\n" +"\n" +"Please use your standard web browser\n" +"to open the following URL:\n" +"%s\n" +msgstr "" +"Ограничения HTML:\n" +"Протокол %s не поддерживается.\n" +"\n" +"Пожалуйста воспользуйтесь Вашим стандартным браузером\n" +"чтобы открыть URL:\n" +"%s\n" + +#: pysollib/tk/tkhtml.py:419 pysollib/tk/tkhtml.py:423 +msgid "Unable to service request:\n" +msgstr "" + +#: pysollib/tk/tkstats.py:95 +msgid "Total" +msgstr "Всего" + +#: pysollib/tk/tkstats.py:97 +msgid "Current session" +msgstr "Текущая сессия" + +#: pysollib/tk/tkstats.py:113 pysollib/tk/tkstats.py:165 +msgid "Total:" +msgstr "Всего:" + +#: pysollib/tk/tkstats.py:278 +msgid "No games" +msgstr "Нет игр" + +#: pysollib/tk/tkstats.py:291 +msgid "Reset..." +msgstr "Очистить..." + +#: pysollib/tk/tkstats.py:574 pysollib/tk/tkstats.py:647 +#: pysollib/tk/tkstats.py:663 +msgid "Save to file" +msgstr "Сохранить в файл" + +#: pysollib/tk/tkstats.py:575 +msgid "Reset all..." +msgstr "Очистить все..." + +#: pysollib/tk/tkstats.py:625 +msgid "No entries for player " +msgstr "Нет записей для игрока " + +#: pysollib/tk/tkstats.py:642 +#, fuzzy +msgid "No log entries for %s\n" +msgstr "Нет записей для " + +#: pysollib/tk/tkstats.py:658 +#, fuzzy +msgid "No current session log entries for %s\n" +msgstr "В текущем сеансе нет записей для " + +#: pysollib/tk/tkstats.py:678 +msgid "Highlight piles: " +msgstr "Подсветка групп: " + +#: pysollib/tk/tkstats.py:679 +msgid "Highlight cards: " +msgstr "Подсветка карт: " + +#: pysollib/tk/tkstats.py:680 +msgid "Highlight same rank: " +msgstr "Подсветка карт одного достоинства: " + +#: pysollib/tk/tkstats.py:683 +msgid "" +"\n" +"Redeals: " +msgstr "" +"\n" +"Раздач: " + +#: pysollib/tk/tkstats.py:684 +msgid "" +"\n" +"Cards in Talon: " +msgstr "" +"\n" +"Карт в колоде: " + +#: pysollib/tk/tkstats.py:686 +msgid "" +"\n" +"Cards in Waste: " +msgstr "" +"\n" +"Карт в сбросе: " + +#: pysollib/tk/tkstats.py:688 +msgid "" +"\n" +"Cards in Foundations: " +msgstr "" +"\n" +"Карт в игре: " + +#: pysollib/tk/tkstats.py:691 +msgid "Game status" +msgstr "Статус игры" + +#: pysollib/tk/tkstats.py:694 +msgid "Playing time: " +msgstr "Игровое время: " + +#: pysollib/tk/tkstats.py:695 +msgid "Started at: " +msgstr "Игра начата: " + +#: pysollib/tk/tkstats.py:696 +msgid "Moves: " +msgstr "Ходов: " + +#: pysollib/tk/tkstats.py:697 +msgid "Undo moves: " +msgstr "Отменено ходов: " + +#: pysollib/tk/tkstats.py:698 +msgid "Bookmark moves: " +msgstr "Ходов по закладкам: " + +#: pysollib/tk/tkstats.py:699 +msgid "Demo moves: " +msgstr "Демо ходов: " + +#: pysollib/tk/tkstats.py:700 +msgid "Total player moves: " +msgstr "Всего ходов игрока:" + +#: pysollib/tk/tkstats.py:701 +msgid "Total moves in this game: " +msgstr "Всего ходов в этой игре: " + +#: pysollib/tk/tkstats.py:702 +msgid "Hints: " +msgstr "Подсказок: " + +#: pysollib/tk/tkstats.py:706 +msgid "Statistics..." +msgstr "Статистика..." + +#: pysollib/tk/tkstats.py:731 +msgid "N" +msgstr "N" + +#: pysollib/tk/tkstats.py:737 +msgid "Started at" +msgstr "Игра начата" + +#: pysollib/tk/tkstats.py:740 +msgid "Result" +msgstr "Результат" + +#: pysollib/tk/tkstats.py:796 +msgid "Minimum" +msgstr "Минимум" + +#: pysollib/tk/tkstats.py:797 +msgid "Maximum" +msgstr "Максимум" + +#: pysollib/tk/tkstats.py:798 +msgid "Average" +msgstr "Среднее" + +#: pysollib/tk/tkstats.py:818 +msgid "Total moves:" +msgstr "Всего ходов:" + +#: pysollib/tk/tkstats.py:849 +#, fuzzy +msgid "No TOP for this game" +msgstr "Не имеется" + +#: pysollib/tk/toolbar.py:183 +msgid "New" +msgstr "Новая" + +#: pysollib/tk/toolbar.py:184 +msgid "" +"Restart the\n" +"current game" +msgstr "" +"Начать текущую игру\n" +"с начала" + +#: pysollib/tk/toolbar.py:186 +msgid "Open" +msgstr "Открыть" + +#: pysollib/tk/toolbar.py:186 +msgid "" +"Open a\n" +"saved game" +msgstr "" +"Открыть\n" +"сохраненную игру" + +#: pysollib/tk/toolbar.py:187 +msgid "Save" +msgstr "Сохранить" + +#: pysollib/tk/toolbar.py:187 +msgid "Save game" +msgstr "Сохранить игру" + +#: pysollib/tk/toolbar.py:189 +msgid "Undo" +msgstr "Отмена" + +#: pysollib/tk/toolbar.py:189 +msgid "Undo last move" +msgstr "Отменить последний ход" + +#: pysollib/tk/toolbar.py:190 +msgid "Redo" +msgstr "Повтор" + +#: pysollib/tk/toolbar.py:190 +msgid "Redo last move" +msgstr "Вернуть ход" + +#: pysollib/tk/toolbar.py:191 +msgid "Auto drop cards" +msgstr "Автоматически сбросить карты" + +#: pysollib/tk/toolbar.py:191 +msgid "Autodrop" +msgstr "Сбросить" + +#: pysollib/tk/toolbar.py:192 +msgid "Pause" +msgstr "Пауза" + +#: pysollib/tk/toolbar.py:192 +msgid "Pause game" +msgstr "Пауза в игре" + +#: pysollib/tk/toolbar.py:194 +msgid "View statistics" +msgstr "Посмотреть статистику" + +#: pysollib/tk/toolbar.py:195 +msgid "Rules for this game" +msgstr "Правила текущей игры" + +#: pysollib/tk/toolbar.py:209 +msgid "Player" +msgstr "Игрок" + +#: pysollib/tk/toolbar.py:210 +msgid "Player options" +msgstr "Установки игрока" + +#: pysollib/tk/toolbar.py:428 +msgid "Toolbar" +msgstr "Панель инструментов" + +#: pysollib/util.py:76 +msgid "Club" +msgstr "Треф" + +#: pysollib/util.py:76 +msgid "Diamond" +msgstr "Буби" + +#: pysollib/util.py:76 +msgid "Heart" +msgstr "Черви" + +#: pysollib/util.py:76 +msgid "Spade" +msgstr "Пики" + +#: pysollib/util.py:77 +msgid "black" +msgstr "черный" + +#: pysollib/util.py:77 +msgid "red" +msgstr "красный" + +#: pysollib/util.py:102 +msgid "cardset" +msgstr "набор карт" + +#~ msgid "" +#~ "No Free\n" +#~ "Matching\n" +#~ "Pairs" +#~ msgstr "" +#~ "Нет\n" +#~ "свободных\n" +#~ "пар" + +#~ msgid "" +#~ "1 Free\n" +#~ "Matching\n" +#~ "Pair" +#~ msgstr "" +#~ "1\n" +#~ "свободная\n" +#~ "пара" + +#~ msgid "" +#~ " Free\n" +#~ "Matching\n" +#~ "Pairs" +#~ msgstr "" +#~ " \n" +#~ "свободных\n" +#~ "пар" + +#~ msgid "" +#~ "\n" +#~ "Tiles\n" +#~ "Removed\n" +#~ "\n" +#~ msgstr "" +#~ "\n" +#~ "удалено\n" +#~ "\n" + +#~ msgid "" +#~ "\n" +#~ "Tiles\n" +#~ "Remaining\n" +#~ "\n" +#~ msgstr "" +#~ "\n" +#~ "осталось\n" +#~ "\n" + +#~ msgid "Playable Area" +#~ msgstr "Область игры" + +#~ msgid "Alignment" +#~ msgstr "Компоновка" + +#~ msgid "What's &new ?" +#~ msgstr "&Новости" + +#~ msgid "Playing Time:" +#~ msgstr "Время игры:" + +#~ msgid "Enable highlight ¬ matching cards" +#~ msgstr "Разрешить показывать &отсутствие совпадение карт" + +#~ msgid "Invalid or damaged " +#~ msgstr "Поврежденный " + +#~ msgid "Balance %d/%d" +#~ msgstr "Баланс %d/%d" + +#~ msgid "No" +#~ msgstr "Нет" + +#~ msgid "" +#~ " Free\n" +#~ "Matching\n" +#~ "Pair" +#~ msgstr "" +#~ " свободных\n" +#~ "пар" diff --git a/pysol b/pysol new file mode 100755 index 0000000000..b841ac6841 --- /dev/null +++ b/pysol @@ -0,0 +1,66 @@ +#! /usr/bin/env python +## -*- coding: iso-8859-1 -*- +## +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +import sys, os + +if os.name == 'nt': + try: + import locale + l = locale.getdefaultlocale() + os.environ['LANG'] = l[0] + except: + pass +##locale.setlocale(locale.LC_ALL, '') + +import gettext +##locale_dir = 'locale' +locale_dir = None +d = os.path.join(sys.path[0], 'locale') +if os.path.exists(d) and os.path.isdir(d): + locale_dir = d +if os.name == 'nt': + if sys.path[0] and not os.path.isdir(sys.path[0]): # i.e. library.zip + locale_dir = os.path.join(os.path.split(sys.path[0])[0], 'locale') +##if locale_dir: locale_dir = os.path.normpath(locale_dir) +gettext.install('pysol', locale_dir, unicode=True) + +from pysollib.main import main +#import pychecker.checker + +sys.exit(main(sys.argv)) + diff --git a/pysollib/__init__.py b/pysollib/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pysollib/acard.py b/pysollib/acard.py new file mode 100644 index 0000000000..81ac4b4a15 --- /dev/null +++ b/pysollib/acard.py @@ -0,0 +1,143 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports + +# PySol imports +from mfxutil import SubclassResponsibility + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class AbstractCard: + # A playing card. + # + # A card doesn't record to which stack it belongs; only the stack + # records this (it turns out that we always know this from the + # context, and this saves a ``double update'' with potential for + # inconsistencies). + # + # Public methods: + # + # moveTo(x, y) -- move the card to an absolute position + # moveBy(dx, dy) -- move the card by a relative offset + # tkraise() -- raise the card to the top of its stack + # showFace(), showBack() -- turn the card face up or down & raise it + # + # Public read-only instance variables: + # + # suit, rank, color -- the card's suit, rank and color + # face_up -- true when the card is shown face up, else false + # + # Semi-public read-only instance variables: + # + # item -- the CanvasItem representing the card + # x, y -- the position of the card's top left corner + # + + def __init__(self, id, deck, suit, rank, game, x=0, y=0): + # The card is created at position (x, y), with its face down. + # Adding it to a stack will position it according to that + # stack's rules. + self.id = id + self.deck = deck + self.suit = suit + self.color = suit / 2 + self.rank = rank + self.x = x + self.y = y + self.item = None + self.face_up = 0 + # To improve display speed, we move cards out of the visible canvas. + # Because the whole area that will be passed by a move will get + # updated by Tk, we must choose an optimal way off the screen. + self.hide_stack = None + self.hide_x = self.hide_y = 0 + + def __str__(self): + # Return a string for debug print statements. + return "Card(%d, %d, %d, %d)" % (self.id, self.deck, self.suit, self.rank) + + def isHidden(self): + return self.hide_stack is not None + + def moveTo(self, x, y): + # Move the card to absolute position (x, y). + # The card remains hidden. + self.moveBy(x - self.x + self.hide_x, y - self.y + self.hide_y) + + def moveBy(self, dx, dy): + # Move the card by (dx, dy). + dx, dy = int(dx), int(dy) + if dx or dy: + self.x = self.x + dx + self.y = self.y + dy + ##print "moveBy:", self.id, dx, dy, self.item.coords() + self.item.move(dx, dy) + + def tkraise(self, unhide=1): + # Raise the card above all other objects in its group (i.e. stack). + if unhide: + self.unhide() + self.item.tkraise() + + + # + # abstract methods + # + + def hide(self, stack): + pass + + def unhide(self): + pass + + def setSelected(self, s, group=None): + pass + + def showFace(self, unhide=1): + # Turn the card's face up. + raise SubclassResponsibility + + def showBack(self, unhide=1): + # Turn the card's face down. + raise SubclassResponsibility + + def updateCardBackground(self, image): + raise SubclassResponsibility + diff --git a/pysollib/actions.py b/pysollib/actions.py new file mode 100644 index 0000000000..aabdad7a0b --- /dev/null +++ b/pysollib/actions.py @@ -0,0 +1,1118 @@ +## -*- coding: utf-8 -*- +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import os, re, sys, string, time, types, locale + +# PySol imports +from mfxutil import EnvError, SubclassResponsibility +from mfxutil import Struct, destruct, openURL +from pysolrandom import constructRandom +from version import VERSION +from settings import PACKAGE, PACKAGE_URL +from settings import TOP_TITLE + +# stats imports +from stats import PysolStatsFormatter +from pysoltk import SingleGame_StatsDialog, AllGames_StatsDialog +from pysoltk import FullLog_StatsDialog, SessionLog_StatsDialog +from pysoltk import Status_StatsDialog, Top_StatsDialog +from pysoltk import GameInfoDialog + +# toolkit imports +from pysoltk import EVENT_HANDLED, EVENT_PROPAGATE +from pysoltk import MfxDialog, MfxSimpleEntry +from pysoltk import MfxExceptionDialog +from pysoltk import BooleanVar, IntVar, StringVar +from pysoltk import PlayerOptionsDialog +from pysoltk import SoundOptionsDialog +from pysoltk import DemoOptionsDialog +#from pysoltk import HintOptionsDialog +from pysoltk import TimeoutsDialog +from pysoltk import ColorsDialog +from pysoltk import FontsDialog +from pysoltk import EditTextDialog +from pysoltk import TOOLBAR_BUTTONS +from help import helpAbout, helpHTML + +gettext = _ + +# /*********************************************************************** +# // menubar +# ************************************************************************/ + +class PysolMenubarActions: + def __init__(self, app, top): + self.app = app + self.top = top + self.game = None + # enabled/disabled - this is set by updateMenuState() + self.menustate = Struct( + save = 0, + save_as = 0, + hold_and_quit = 0, + undo = 0, + redo = 0, + restart = 0, + deal = 0, + hint = 0, + autofaceup = 0, + autodrop = 0, + autodeal = 0, + quickplay = 0, + demo = 0, + highlight_piles = 0, + rules = 0, + pause = 0, + ) + # structure to convert menu-options to Toolkit variables + self.tkopt = Struct( + gameid = IntVar(), + gameid_popular = IntVar(), + comment = BooleanVar(), + autofaceup = BooleanVar(), + autodrop = BooleanVar(), + autodeal = BooleanVar(), + quickplay = BooleanVar(), + undo = BooleanVar(), + bookmarks = BooleanVar(), + hint = BooleanVar(), + highlight_piles = BooleanVar(), + highlight_cards = BooleanVar(), + highlight_samerank = BooleanVar(), + highlight_not_matching = BooleanVar(), + mahjongg_show_removed = BooleanVar(), + shisen_show_hint = BooleanVar(), + sound = BooleanVar(), + cardback = IntVar(), + tabletile = IntVar(), + animations = IntVar(), + shadow = BooleanVar(), + shade = BooleanVar(), + toolbar = IntVar(), + toolbar_style = StringVar(), + toolbar_relief = StringVar(), + toolbar_compound = StringVar(), + toolbar_size = IntVar(), + statusbar = BooleanVar(), + num_cards = BooleanVar(), + helpbar = BooleanVar(), + splashscreen = BooleanVar(), + demo_logo = BooleanVar(), + sticky_mouse = BooleanVar(), + negative_bottom = BooleanVar(), + pause = BooleanVar(), + toolbar_vars = {}, + ) + + for w in TOOLBAR_BUTTONS: + self.tkopt.toolbar_vars[w] = BooleanVar() + + + def connectGame(self, game): + self.game = game + if game is None: + return + assert self.app is game.app + tkopt, opt = self.tkopt, self.app.opt + # set state of the menu items + tkopt.gameid.set(game.id) + tkopt.gameid_popular.set(game.id) + tkopt.comment.set(bool(game.gsaveinfo.comment)) + tkopt.autofaceup.set(opt.autofaceup) + tkopt.autodrop.set(opt.autodrop) + tkopt.autodeal.set(opt.autodeal) + tkopt.quickplay.set(opt.quickplay) + tkopt.undo.set(opt.undo) + tkopt.hint.set(opt.hint) + tkopt.bookmarks.set(opt.bookmarks) + tkopt.highlight_piles.set(opt.highlight_piles) + tkopt.highlight_cards.set(opt.highlight_cards) + tkopt.highlight_samerank.set(opt.highlight_samerank) + tkopt.highlight_not_matching.set(opt.highlight_not_matching) + tkopt.mahjongg_show_removed.set(opt.mahjongg_show_removed) + tkopt.shisen_show_hint.set(opt.shisen_show_hint) + tkopt.sound.set(opt.sound) + tkopt.cardback.set(self.app.cardset.backindex) + tkopt.tabletile.set(self.app.tabletile_index) + tkopt.animations.set(opt.animations) + tkopt.shadow.set(opt.shadow) + tkopt.shade.set(opt.shade) + tkopt.toolbar.set(opt.toolbar) + tkopt.toolbar_style.set(opt.toolbar_style) + tkopt.toolbar_relief.set(opt.toolbar_relief) + tkopt.toolbar_compound.set(opt.toolbar_compound) + tkopt.toolbar_size.set(opt.toolbar_size) + tkopt.toolbar_relief.set(opt.toolbar_relief) + tkopt.statusbar.set(opt.statusbar) + tkopt.num_cards.set(opt.num_cards) + tkopt.helpbar.set(opt.helpbar) + tkopt.demo_logo.set(opt.demo_logo) + tkopt.splashscreen.set(opt.splashscreen) + tkopt.sticky_mouse.set(opt.sticky_mouse) + tkopt.negative_bottom.set(opt.negative_bottom) + + for w in TOOLBAR_BUTTONS: + tkopt.toolbar_vars[w].set(opt.toolbar_vars[w]) + + # will get called after connectGame() + def updateRecentGamesMenu(self, gameids): + pass + + def updateBookmarkMenuState(self): + pass + + # will get called after a new cardset has been loaded + def updateBackgroundImagesMenu(self): + pass + + + # + # delegation to Game + # + + def _finishDrag(self): + return self.game is None or self.game._finishDrag() + + def _cancelDrag(self, break_pause=True): + return self.game is None or self.game._cancelDrag(break_pause=break_pause) + + def changed(self, *args, **kw): + assert self.game is not None + return apply(self.game.changed, args, kw) + + + # + # menu updates + # + + def setMenuState(self, state, path): + raise SubclassResponsibility + + def setToolbarState(self, state, path): + raise SubclassResponsibility + + def _clearMenuState(self): + ms = self.menustate + for k, v in ms.__dict__.items(): + if type(v) is types.ListType: + ms.__dict__[k] = [0] * len(v) + else: + ms.__dict__[k] = 0 + + # update self.menustate for menu items and toolbar + def _updateMenuState(self): + self._clearMenuState() + game = self.game + assert game is not None + opt = self.app.opt + ms = self.menustate + # 0 = DISABLED, 1 = ENABLED + ms.save_as = game.canSaveGame() + ms.hold_and_quit = ms.save_as + if game.filename and ms.save_as: + ms.save = 1 + if opt.undo: + if game.canUndo() and game.moves.index > 0: + ms.undo = 1 + if game.canRedo() and game.moves.index < len(game.moves.history): + ms.redo = 1 + if game.moves.index > 0: + ms.restart = 1 + if game.canDealCards(): + ms.deal = 1 + if game.getHintClass() is not None: + if opt.hint: + ms.hint = 1 + ###if not game.demo: # if not already running + ms.demo = 1 + autostacks = game.getAutoStacks() + if autostacks[0]: + ms.autofaceup = 1 + if autostacks[1] and game.s.foundations: + ms.autodrop = 1 + if game.s.waste: + ms.autodeal = 1 + if autostacks[2]: + ms.quickplay = 1 + ms.highlight_piles = 0 + if opt.highlight_piles and game.getHighlightPilesStacks(): + ms.highlight_piles = 1 + if game.app.getGameRulesFilename(game.id): # note: this may return "" + ms.rules = 1 + if not game.finished: + ms.pause = 1 + + # update menu items and toolbar + def _updateMenus(self): + if self.game is None: + return + ms = self.menustate + # File menu + self.setMenuState(ms.save, "file.save") + self.setMenuState(ms.save_as, "file.saveas") + self.setMenuState(ms.hold_and_quit, "file.holdandquit") + # Edit menu + self.setMenuState(ms.undo, "edit.undo") + self.setMenuState(ms.redo, "edit.redo") + self.setMenuState(ms.redo, "edit.redoall") + self.updateBookmarkMenuState() + self.setMenuState(ms.restart, "edit.restartgame") + # Game menu + self.setMenuState(ms.deal, "game.dealcards") + self.setMenuState(ms.autodrop, "game.autodrop") + self.setMenuState(ms.pause, "game.pause") + # Assist menu + self.setMenuState(ms.hint, "assist.hint") + self.setMenuState(ms.highlight_piles, "assist.highlightpiles") + self.setMenuState(ms.demo, "assist.demo") + self.setMenuState(ms.demo, "assist.demoallgames") + # Options menu + self.setMenuState(ms.autofaceup, "options.automaticplay.autofaceup") + self.setMenuState(ms.autodrop, "options.automaticplay.autodrop") + self.setMenuState(ms.autodeal, "options.automaticplay.autodeal") + self.setMenuState(ms.quickplay, "options.automaticplay.quickplay") + # Help menu + self.setMenuState(ms.rules, "help.rulesforthisgame") + # Toolbar + self.setToolbarState(ms.restart, "restart") + self.setToolbarState(ms.save_as, "save") + self.setToolbarState(ms.undo, "undo") + self.setToolbarState(ms.redo, "redo") + self.setToolbarState(ms.autodrop, "autodrop") + self.setToolbarState(ms.pause, "pause") + self.setToolbarState(ms.rules, "rules") + # + self.tkopt.comment.set(bool(self.game.gsaveinfo.comment)) + #self.setToolbarState(ms.pause, "pause") + + # update menu items and toolbar + def updateMenus(self): + if self.game is None: + return + self._updateMenuState() + self._updateMenus() + + # disable menu items and toolbar + def disableMenus(self): + if self.game is None: + return + self._clearMenuState() + self._updateMenus() + + + # + # File menu + # + + def mNewGame(self, *args): + if self._cancelDrag(): return + if self.changed(): + if not self.game.areYouSure(_("New game")): return + if self.game.nextGameFlags(self.game.id) == 0: + self.game.endGame() + self.game.newGame() + else: + self.game.endGame() + self.game.quitGame(self.game.id) + + def _mSelectGame(self, id, random=None): + if self._cancelDrag(): return + if self.game.id == id: + return + if self.changed(): + if not self.game.areYouSure(_("Select game")): + # restore radiobutton settings + self.tkopt.gameid.set(self.game.id) + self.tkopt.gameid_popular.set(self.game.id) + return + self.game.endGame() + self.game.quitGame(id, random=random) + + def mSelectGame(self, *args): + self._mSelectGame(self.tkopt.gameid.get()) + + def mSelectGamePopular(self, *args): + self._mSelectGame(self.tkopt.gameid_popular.get()) + + def _mNewGameBySeed(self, seed, origin): + try: + random = constructRandom(seed) + if random is None: + return + id = self.game.id + if not self.app.getGameInfo(id): + raise ValueError + except (ValueError, TypeError), ex: + d = MfxDialog(self.top, title=_("Invalid game number"), + text=_("Invalid game number\n") + str(seed), + bitmap="error") + return + f = self.game.nextGameFlags(id, random) + if f & 17 == 0: + return + random.origin = origin + if f & 15 == 0: + self.game.endGame() + self.game.newGame(random=random) + else: + self.game.endGame() + self.game.quitGame(id, random=random) + + def mNewGameWithNextId(self, *args): + if self._cancelDrag(): return + if self.changed(): + if not self.game.areYouSure(_("Select next game number")): return + r = self.game.random + seed = r.increaseSeed(r.initial_seed) + seed = r.str(seed) + self._mNewGameBySeed(seed, self.game.random.ORIGIN_NEXT_GAME) + + def mSelectGameById(self, *args): + if self._cancelDrag(break_pause=False): return + id, f = None, self.game.getGameNumber(format=0) + d = MfxSimpleEntry(self.top, _("Select new game number"), + _("\n\nEnter new game number"), f, + strings=(_("OK"), _("Next number"), _("Cancel")), + default=0, e_width=25) + if d.status != 0: return + if d.button == 2: return + if d.button == 1: + self.mNewGameWithNextId() + return + if self.changed(): + if not self.game.areYouSure(_("Select new game number")): return + self._mNewGameBySeed(d.value, self.game.random.ORIGIN_SELECTED) + + + + def mSelectRandomGame(self, type='all'): + if self._cancelDrag(): return + if self.changed(): + if not self.game.areYouSure(_("Select random game")): return + game_id = None + for i in range(1000): # just in case, don't loop forever + gi = self.app.getGameInfo(self.app.getRandomGameId()) + if gi is None: + continue + if 1 and gi.id == self.game.id: + # force change of game + continue + if 1 and gi.category != self.game.gameinfo.category: + # don't change game category + continue + if type == 'all': + game_id = gi.id + break + won, lost = self.app.stats.getStats(self.app.opt.player, gi.id) + if type == 'won' and won > 0: + game_id = gi.id + break + if type == 'not won' and won == 0 and lost > 0: + game_id = gi.id + break + if type == 'not played' and won+lost == 0: + game_id = gi.id + break + if game_id and game_id != self.game.id: + self.game.endGame() + self.game.quitGame(gi.id) + + def _mSelectNextGameFromList(self, gl, step): + if self._cancelDrag(): return + id = self.game.id + gl = list(gl) + if len(gl) < 2 or not id in gl: + return + if self.changed(): + if not self.game.areYouSure(_("Select next game")): return + index = (gl.index(id) + step) % len(gl) + self.game.endGame() + self.game.quitGame(gl[index]) + + def mSelectNextGameById(self, *args): + self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedById(), 1) + + def mSelectPrevGameById(self, *args): + self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedById(), -1) + + def mSelectNextGameByName(self, *args): + self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedByName(), 1) + + def mSelectPrevGameByName(self, *args): + self._mSelectNextGameFromList(self.app.gdb.getGamesIdSortedByName(), -1) + + def mSave(self, *args): + if self._cancelDrag(break_pause=False): return + if self.menustate.save_as: + if self.game.filename: + self.game.saveGame(self.game.filename) + else: + self.mSaveAs() + + def mHoldAndQuit(self, *args): + if self._cancelDrag(): return + self.game.endGame(holdgame=1) + self.game.quitGame(holdgame=1) + + def mQuit(self, *args): + if self._cancelDrag(): return + if self.changed(): + if not self.game.areYouSure(_("Quit ") + PACKAGE): return + self.game.endGame() + self.game.quitGame() + + + # + # Edit menu + # + + def mUndo(self, *args): + if self._cancelDrag(): return + if self.menustate.undo: + self.game.playSample("undo") + self.game.undo() + + def mRedo(self, *args): + if self._cancelDrag(): return + if self.menustate.redo: + self.game.playSample("redo") + self.game.redo() + self.game.checkForWin() + + def mRedoAll(self, *args): + if self._cancelDrag(): return + if self.menustate.redo: + self.game.playSample("redo", loop=1) + while self.game.moves.index < len(self.game.moves.history): + self.game.redo() + if self.game.checkForWin(): + break + self.game.stopSamples() + + def mSetBookmark(self, n, confirm=1): + if self._cancelDrag(): return + if not self.app.opt.bookmarks: return + if not (0 <= n <= 8): return + self.game.setBookmark(n, confirm=confirm) + self.game.updateMenus() + + def mGotoBookmark(self, n, confirm=-1): + if self._cancelDrag(): return + if not self.app.opt.bookmarks: return + if not (0 <= n <= 8): return + self.game.gotoBookmark(n, confirm=confirm) + self.game.updateMenus() + + def mClearBookmarks(self, *args): + if self._cancelDrag(): return + if not self.app.opt.bookmarks: return + if not self.game.gsaveinfo.bookmarks: return + if not self.game.areYouSure(_("Clear bookmarks"), + _("Clear all bookmarks ?")): + return + self.game.gsaveinfo.bookmarks = {} + self.game.updateMenus() + + def mRestart(self, *args): + if self._cancelDrag(): return + if self.game.moves.index == 0: + return + if self.changed(restart=1): + if not self.game.areYouSure(_("Restart game"), + _("Restart this game ?")): + return + self.game.restartGame() + + + # + # Game menu + # + + def mDeal(self, *args): + if self._cancelDrag(): return + self.game.dealCards() + + def mDrop(self, *args): + if self._cancelDrag(): return + self.game.autoPlay(autofaceup=-1, autodrop=1) + + def mDrop1(self, *args): + if self._cancelDrag(): return + self.game.autoPlay(autofaceup=1, autodrop=1) + + def mStatus(self, *args): + if self._cancelDrag(break_pause=False): return + self.mPlayerStats(mode=100) + + def mTop10(self, *args): + if self._cancelDrag(break_pause=False): return + self.mPlayerStats(mode=105) + + def mGameInfo(self, *args): + if self._cancelDrag(break_pause=False): return + self.mPlayerStats(mode=106) + + def mEditGameComment(self, *args): + if self._cancelDrag(break_pause=False): return + game, gi = self.game, self.game.gameinfo + t = " " + game.getGameNumber(format=1) + cc = _("Comments for %s:\n\n") % (gi.name + t) + c = game.gsaveinfo.comment or cc + d = EditTextDialog(game.top, _("Comments for ")+t, text=c) + if d.status == 0 and d.button == 0: + text = d.text + if text.strip() == cc.strip(): + game.gsaveinfo.comment = "" + else: + game.gsaveinfo.comment = d.text + # save to file + fn = os.path.join(self.app.dn.config, "comments.txt") + fn = os.path.normpath(fn) + if not text.endswith(os.linesep): + text += os.linesep + enc = locale.getpreferredencoding() + try: + fd = open(fn, 'a') + fd.write(text.encode(enc, 'replace')) + except Exception, err: + d = MfxExceptionDialog(self.top, err, + text=_("Error while writing to file")) + else: + if fd: fd.close() + d = MfxDialog(self.top, title=PACKAGE+_(" Info"), bitmap="info", + text=_("Comments were appended to\n\n") + fn) + self.tkopt.comment.set(bool(game.gsaveinfo.comment)) + + + def mPause(self, *args): + if not self.game.pause: + if self._cancelDrag(): return + self.game.doPause() + self.tkopt.pause.set(self.game.pause) + + # + # Game menu - statistics + # + + def _mStatsSave(self, player, header, filename, write_method): + file = None + if player is None: + text = _("Demo statistics") + filename = filename + "_demo" + else: + text = _("Your statistics") + filename = os.path.join(self.app.dn.config, filename + ".txt") + filename = os.path.normpath(filename) + try: + file = open(filename, "a") + a = PysolStatsFormatter(self.app) + writer = a.FileWriter(file) + apply(write_method, (a, writer, player, header)) + destruct(a) + except EnvError, ex: + if file: file.close() + d = MfxExceptionDialog(self.top, ex, + text=_("Error while writing to file")) + else: + if file: file.close() + d = MfxDialog(self.top, title=PACKAGE+_(" Info"), bitmap="info", + text=text + _(" were appended to\n\n") + filename) + + + def mPlayerStats(self, *args, **kw): + mode = kw.get("mode", 101) + demo = 0 + while mode > 0: + if mode > 1000: + demo = not demo + mode = mode % 1000 + # + d = Struct(status=-1, button=-1) + if demo: + player = None + p0, p1, p2 = PACKAGE+_(" Demo"), PACKAGE+_(" Demo "), "" + else: + player = self.app.opt.player + p0, p1, p2 = player, "", _(" for ") + player + n = gettext(self.game.gameinfo.short_name) + # + if mode == 100: + d = Status_StatsDialog(self.top, game=self.game) + elif mode == 101: + header = p1 + _("Statistics for ") + n + d = SingleGame_StatsDialog(self.top, header, self.app, player, gameid=self.game.id) + elif mode == 102: + header = p1 + _("Statistics") + p2 + d = AllGames_StatsDialog(self.top, header, self.app, player) + elif mode == 103: + header = p1 + _("Full log") + p2 + d = FullLog_StatsDialog(self.top, header, self.app, player) + elif mode == 104: + header = p1 + _("Session log") + p2 + d = SessionLog_StatsDialog(self.top, header, self.app, player) + elif mode == 105: + header = p1 + TOP_TITLE + _(" for ") + n + d = Top_StatsDialog(self.top, header, self.app, player, gameid=self.game.id) + elif mode == 106: + header = _("Game Info") + d = GameInfoDialog(self.top, header, self.app) + elif mode == 202: + # print stats to file + header = _("Statistics for ") + p0 + write_method = PysolStatsFormatter.writeStats + self._mStatsSave(player, header, "stats", write_method) + elif mode == 203: + # print full log to file + header = _("Full log for ") + p0 + write_method = PysolStatsFormatter.writeFullLog + self._mStatsSave(player, header, "log", write_method) + elif mode == 204: + # print session log to file + header = _("Session log for ") + p0 + write_method = PysolStatsFormatter.writeSessionLog + self._mStatsSave(player, header, "log", write_method) + elif mode == 301: + # reset all player stats + if self.game.areYouSure(_("Reset all statistics"), + _("Reset ALL statistics and logs for player\n%s ?") % p0, + confirm=1, default=1): + self.app.stats.resetStats(player, 0) + self.game.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.game.id)) + elif mode == 302: + # reset player stats for current game + if self.game.areYouSure(_("Reset game statistics"), + _('Reset statistics and logs for player\n%s\nand game\n%s ?') % (p0, n), + confirm=1, default=1): + self.app.stats.resetStats(player, self.game.id) + self.game.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.game.id)) + elif mode == 401: + # start a new game with a gameid + ## TODO + pass + elif mode == 402: + # start a new game with a gameid / gamenumber + ## TODO + pass + else: + print "stats problem:", mode, demo, player + pass + if d.status != 0: + break + mode = d.button + + + # + # Assist menu + # + + def mHint(self, *args): + if self._cancelDrag(): return + if self.app.opt.hint: + if self.game.showHint(0, self.app.opt.hint_sleep): + self.game.stats.hints = self.game.stats.hints + 1 + + def mHint1(self, *args): + if self._cancelDrag(): return + if self.app.opt.hint: + if self.game.showHint(1, self.app.opt.hint_sleep): + self.game.stats.hints = self.game.stats.hints + 1 + + def mHighlightPiles(self, *args): + if self._cancelDrag(): return + if self.app.opt.highlight_piles: + if self.game.highlightPiles(self.app.opt.highlight_piles_sleep): + self.game.stats.highlight_piles = self.game.stats.highlight_piles + 1 + + def mDemo(self, *args): + if self._cancelDrag(): return + if self.game.getHintClass() is not None: + self._mDemo(mixed=0) + + def mMixedDemo(self, *args): + if self._cancelDrag(): return + self._mDemo(mixed=1) + + def _mDemo(self, mixed): + if self._cancelDrag(): return + if self.changed(): + # only ask if there have been no demo moves or hints yet + if self.game.stats.demo_moves == 0 and self.game.stats.hints == 0: + if not self.game.areYouSure(_("Play demo")): return + ##self.app.demo_counter = 0 + self.game.startDemo(mixed=mixed) + + + # + # Options menu + # + + def mOptPlayerOptions(self, *args): + if self._cancelDrag(break_pause=False): return + d = PlayerOptionsDialog(self.top, _("Set player options"), self.app) + if d.status == 0 and d.button == 0: + self.app.opt.confirm = bool(d.confirm) + self.app.opt.update_player_stats = bool(d.update_stats) + self.app.opt.win_animation = bool(d.win_animation) + ##n = string.strip(d.player) + n = d.player[:30].strip() + if 0 < len(n) <= 30: + self.app.opt.player = n + self.game.updateStatus(player=self.app.opt.player) + self.game.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.game.id)) + + def mOptAutoFaceUp(self, *args): + if self._cancelDrag(): return + self.app.opt.autofaceup = self.tkopt.autofaceup.get() + if self.app.opt.autofaceup: + self.game.autoPlay() + + def mOptAutoDrop(self, *args): + if self._cancelDrag(): return + self.app.opt.autodrop = self.tkopt.autodrop.get() + if self.app.opt.autodrop: + self.game.autoPlay() + + def mOptAutoDeal(self, *args): + if self._cancelDrag(): return + self.app.opt.autodeal = self.tkopt.autodeal.get() + if self.app.opt.autodeal: + self.game.autoPlay() + + def mOptQuickPlay(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.quickplay = self.tkopt.quickplay.get() + + def mOptEnableUndo(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.undo = self.tkopt.undo.get() + self.game.updateMenus() + + def mOptEnableBookmarks(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.bookmarks = self.tkopt.bookmarks.get() + self.game.updateMenus() + + def mOptEnableHint(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.hint = self.tkopt.hint.get() + self.game.updateMenus() + + def mOptEnableHighlightPiles(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.highlight_piles = self.tkopt.highlight_piles.get() + self.game.updateMenus() + + def mOptEnableHighlightCards(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.highlight_cards = self.tkopt.highlight_cards.get() + self.game.updateMenus() + + def mOptEnableHighlightSameRank(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.highlight_samerank = self.tkopt.highlight_samerank.get() + ##self.game.updateMenus() + + def mOptEnableHighlightNotMatching(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.highlight_not_matching = self.tkopt.highlight_not_matching.get() + ##self.game.updateMenus() + + def mOptMahjonggShowRemoved(self, *args): + if self._cancelDrag(): return + self.app.opt.mahjongg_show_removed = self.tkopt.mahjongg_show_removed.get() + ##self.game.updateMenus() + self.game.endGame(bookmark=1) + self.game.quitGame(bookmark=1) + + def mOptShisenShowHint(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.shisen_show_hint = self.tkopt.shisen_show_hint.get() + ##self.game.updateMenus() + + def mOptSound(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.sound = self.tkopt.sound.get() + if not self.app.opt.sound: + self.app.audio.stopAll() + + def mOptSoundDialog(self, *args): + if self._cancelDrag(break_pause=False): return + d = SoundOptionsDialog(self.top, _("Sound settings"), self.app) + self.tkopt.sound.set(self.app.opt.sound) + + def mOptAnimations(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.animations = self.tkopt.animations.get() + + def mOptShadow(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.shadow = self.tkopt.shadow.get() + + def mOptShade(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.shade = self.tkopt.shade.get() + + def mOptIrregularPiles(self, *args): + if self._cancelDrag(): return + self.app.opt.irregular_piles = self.tkopt.irregular_piles.get() + + def mOptDemoOptions(self, *args): + if self._cancelDrag(break_pause=False): return + d = DemoOptionsDialog(self.top, _("Set demo options"), self.app) + if d.status == 0 and d.button == 0: + self.app.opt.demo_logo = d.demo_logo + self.app.opt.demo_score = d.demo_score + self.app.opt.demo_sleep = d.demo_sleep + +## def mOptHintOptions(self, *args): +## if self._cancelDrag(break_pause=False): return +## d = HintOptionsDialog(self.top, "Set hint options", self.app) +## if d.status == 0 and d.button == 0: +## self.app.opt.hint_sleep = d.hint_sleep + + def mOptColorsOptions(self, *args): + if self._cancelDrag(break_pause=False): return + d = ColorsDialog(self.top, _("Set colors"), self.app) + table_text_color = self.app.opt.table_text_color + table_text_color_value = self.app.opt.table_text_color_value + if d.status == 0 and d.button == 0: + self.app.opt.table_text_color = d.table_text_color + self.app.opt.table_text_color_value = d.table_text_color_value + ##self.app.opt.table_color = d.table_color + self.app.opt.highlight_piles_colors = d.highlight_piles_colors + self.app.opt.highlight_cards_colors = d.highlight_cards_colors + self.app.opt.highlight_samerank_colors = d.highlight_samerank_colors + self.app.opt.hintarrow_color = d.hintarrow_color + self.app.opt.highlight_not_matching_color = d.highlight_not_matching_color + # + if table_text_color != self.app.opt.table_text_color \ + or table_text_color_value != self.app.opt.table_text_color_value: + self.app.setTile(self.tkopt.tabletile.get(), 1) + + def mOptFontsOptions(self, *args): + if self._cancelDrag(break_pause=False): return + d = FontsDialog(self.top, _("Set fonts"), self.app) + if d.status == 0 and d.button == 0: + self.app.opt.fonts.update(d.fonts) + self._cancelDrag() + self.game.endGame(bookmark=1) + self.game.quitGame(bookmark=1) + + def mOptTimeoutsOptions(self, *args): + if self._cancelDrag(break_pause=False): return + d = TimeoutsDialog(self.top, _("Set timeouts"), self.app) + if d.status == 0 and d.button == 0: + self.app.opt.demo_sleep = d.demo_sleep + self.app.opt.hint_sleep = d.hint_sleep + self.app.opt.raise_card_sleep = d.raise_card_sleep + self.app.opt.highlight_piles_sleep = d.highlight_piles_sleep + self.app.opt.highlight_cards_sleep = d.highlight_cards_sleep + self.app.opt.highlight_samerank_sleep = d.highlight_samerank_sleep + + def mOptSave(self, *args): + if self._cancelDrag(break_pause=False): return + try: + self.app.saveOptions() + except Exception, ex: + d = MfxExceptionDialog(self.top, ex, + text=_("Error while saving options")) + else: + # tell the player where their config files reside + d = MfxDialog(self.top, title=PACKAGE+_(" Info"), + text=_("Options were saved to\n\n") + self.app.fn.opt, + bitmap="info") + + # + # Help menu + # + + def mHelp(self, *args): + if self._cancelDrag(break_pause=False): return + helpHTML(self.app, "index.html", "html") + + def mHelpHowToPlay(self, *args): + if self._cancelDrag(break_pause=False): return + helpHTML(self.app, "howtoplay.html", "html") + + def mHelpRules(self, *args): + if self._cancelDrag(break_pause=False): return + if not self.menustate.rules: + return + dir = os.path.join("html", "rules") + ## FIXME: plugins + helpHTML(self.app, self.app.getGameRulesFilename(self.game.id), dir) + + def mHelpLicense(self, *args): + if self._cancelDrag(break_pause=False): return + helpHTML(self.app, "license.html", "html") + + def mHelpNews(self, *args): + if self._cancelDrag(break_pause=False): return + helpHTML(self.app, "news.html", "html") + + def mHelpWebSite(self, *args): + openURL(PACKAGE_URL) + + def mHelpAbout(self, *args): + if self._cancelDrag(break_pause=False): return + helpAbout(self.app) + + # + # misc + # + + def mScreenshot(self, *args): + if self._cancelDrag(): return + f = os.path.join(self.app.dn.config, "screenshots") + if not os.path.isdir(f): + return + f = os.path.join(f, self.app.getGameSaveName(self.game.id)) + i = 1 + while 1: + fn = "%s-%d.ppm" % (f, i) + if not os.path.exists(fn): + break + i = i + 1 + if i >= 10000: # give up + return + self.top.screenshot(fn) + + def mPlayNextMusic(self, *args): + if self._cancelDrag(break_pause=False): return + if self.app.audio and self.app.opt.sound_music_volume > 0: + self.app.audio.playNextMusic() + if 1 and self.app.debug: + index = self.app.audio.getMusicInfo() + music = self.app.music_manager.get(index) + if music: + print "playing music:", music.filename + + def mIconify(self, *args): + self.top.wm_iconify() + + +# /*********************************************************************** +# // toolbar +# ************************************************************************/ + +class PysolToolbarActions: + def __init__(self): + self.game = None + self.menubar = None + + # + # public methods + # + + def connectGame(self, game, menubar): + self.game = game + self.menubar = menubar + + + # + # button event handlers - delegate to menubar + # + + def _busy(self): + raise SubclassResponsibility + + def mNewGame(self, *args): + if not self._busy(): + self.menubar.mNewGame() + return 1 + + def mOpen(self, *args): + if not self._busy(): + self.menubar.mOpen() + return 1 + + def mRestart(self, *args): + if not self._busy(): + self.menubar.mRestart() + return 1 + + def mSave(self, *args): + if not self._busy(): + self.menubar.mSaveAs() + return 1 + + def mUndo(self, *args): + if not self._busy(): + self.menubar.mUndo() + return 1 + + def mRedo(self, *args): + if not self._busy(): + self.menubar.mRedo() + return 1 + + def mDrop(self, *args): + if not self._busy(): + self.menubar.mDrop() + return 1 + + def mPause(self, *args): + if not self._busy(): + self.menubar.mPause() + return 1 + + def mStatus(self, *args): + if not self._busy(): + self.menubar.mStatus() + return 1 + + def mPlayerStats(self, *args): + if not self._busy(): + self.menubar.mPlayerStats() + return 1 + + def mHelpRules(self, *args): + if not self._busy(): + self.menubar.mHelpRules() + return 1 + + def mQuit(self, *args): + if not self._busy(): + self.menubar.mQuit() + return 1 + + def mOptPlayerOptions(self, *args): + if not self._busy(): + self.menubar.mOptPlayerOptions() + return 1 + diff --git a/pysollib/app.py b/pysollib/app.py new file mode 100644 index 0000000000..0c9c84a425 --- /dev/null +++ b/pysollib/app.py @@ -0,0 +1,1613 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, os, re, time, types +import traceback + +# PySol imports +from mfxutil import destruct, Struct +from mfxutil import pickle, unpickle, Unpickler, UnpicklingError +from mfxutil import getusername, gethomedir, getprefdir, EnvError +from mfxutil import latin1_to_ascii +from util import Timer +from util import CARDSET, IMAGE_EXTENSIONS +from version import VERSION, VERSION_TUPLE +from settings import PACKAGE, PACKAGE_URL +from resource import CSI, CardsetConfig, Cardset, CardsetManager +from resource import Tile, TileManager +from resource import Sample, SampleManager +from resource import Music, MusicManager +from images import Images, SubsampledImages +from pysolrandom import PysolRandom +from game import Game +from gamedb import GI, GAME_DB, loadGame +from settings import TOP_SIZE, TOP_TITLE + +# Toolkit imports +from pysoltk import tkname, tkversion, wm_withdraw, loadImage +from pysoltk import bind, unbind_destroy +from pysoltk import MfxDialog, MfxExceptionDialog +from pysoltk import TclError, MfxRoot, MfxCanvas, MfxScrolledCanvas +from pysoltk import PysolMenubar +from pysoltk import PysolProgressBar +from pysoltk import PysolToolbar +from pysoltk import PysolStatusbar, HelpStatusbar +from pysoltk import SelectCardsetByTypeDialogWithPreview +from pysoltk import SelectDialogTreeData +from pysoltk import TOOLBAR_BUTTONS +from help import helpAbout + +gettext = _ + +# /*********************************************************************** +# // Options +# ************************************************************************/ + +class Options: + def __init__(self): + self.version_tuple = VERSION_TUPLE + self.saved = 0 + # options menu: + self.player = _("Unknown") + self.confirm = 1 + self.update_player_stats = 1 + self.autofaceup = 1 + self.autodrop = 0 + self.autodeal = 1 + self.quickplay = 1 + self.undo = 1 + self.bookmarks = 1 + self.hint = 1 + self.highlight_piles = 1 + self.highlight_cards = 1 + self.highlight_samerank = 1 + self.highlight_not_matching = 0 + self.mahjongg_show_removed = False + self.mahjongg_create_solvable = True + self.shisen_show_hint = True + self.animations = 2 # default to Timer based + self.shadow = 1 + self.shade = 1 + self.demo_logo = 1 + self.demo_score = 0 + self.toolbar = 1 + ##self.toolbar_style = 'default' + self.toolbar_style = 'crystal' + self.toolbar_relief = 'flat' + self.toolbar_compound = 'none' # icons only + self.toolbar_size = 0 + self.toolbar_vars = {} + for w in TOOLBAR_BUTTONS: + self.toolbar_vars[w] = True + self.statusbar = 1 + self.num_cards = 0 + self.helpbar = 0 + self.sound = 1 + self.sound_mode = 1 + self.sound_sample_volume = 128 + self.sound_music_volume = 128 + # fonts + self.fonts = {"default" : None, + #"default" : ("helvetica", 12), + "sans" : ("times", 14), # for html + "fixed" : ("courier", 14), # for html & log + "small" : ("helvetica", 12), + "canvas_default" : ("helvetica", 12), + #"canvas_card" : ("helvetica", 12), + "canvas_fixed" : ("courier", 12), + "canvas_large" : ("helvetica", 18), + "canvas_small" : ("helvetica", 12), # not used? + #"tree_small" : ("helvetica", 12), + } + if os.name == 'posix': + self.fonts["sans"] = ("helvetica", 12) + if os.name == 'nt': + self.fonts["sans"] = ("times new roman", 14) + self.fonts["fixed"] = ("courier new", 10) + # colors + self.table_color = "#008200" + self.highlight_piles_colors = (None, "#ffc000") + self.highlight_cards_colors = (None, "#ffc000", None, "#0000ff") + self.highlight_samerank_colors = (None, "#ffc000", None, "#0000ff") + self.hintarrow_color = "#303030" + self.highlight_not_matching_color = '#ff0000' + self.table_text_color = 0 + self.table_text_color_value = '#ffffff' + # delays + self.hint_sleep = 1.0 + self.demo_sleep = 1.0 + self.raise_card_sleep = 1.0 + self.highlight_piles_sleep = 1.0 + self.highlight_cards_sleep = 1.0 + self.highlight_samerank_sleep = 1.0 + # additional startup information + self.recent_gameid = [] + self.favorite_gameid = [] + self.last_gameid = 0 # last game played + self.last_player = None # last player + self.last_save_dir = None # last directory for load/save + self.game_holded = 0 + self.wm_maximized = 0 + # + self.splashscreen = True + self.sticky_mouse = False + self.negative_bottom = False + self.cache_carsets = True + # defaults & constants + self.setDefaults() + self.setConstants() + + def setDefaults(self, top=None): + sw, sh, sd = 0, 0, 8 + if top: + sw, sh, sd = top.winfo_screenwidth(), top.winfo_screenheight(), top.winfo_screendepth() + if sd > 8: + #self.tabletile_name = "Fade_Green.ppm" # basename + self.tabletile_name = "Nostalgy.gif" # basename + else: + self.tabletile_name = None + # + #c = "Oxymoron" + c = "Standard" + if sw < 800 or sh < 600: + c = "2000" +## elif sw >= 1024 and sh >= 768 and sd > 8: +## c = "Dondorf Whist A" + self.cardset = { + 0: (c, ""), + CSI.TYPE_FRENCH: (c, ""), + CSI.TYPE_HANAFUDA: ("Kintengu", ""), + CSI.TYPE_MAHJONGG: ("Crystal Mahjongg", ""), + CSI.TYPE_TAROCK: ("Vienna 2K", ""), + CSI.TYPE_HEXADECK: ("Hex A Deck", ""), + CSI.TYPE_MUGHAL_GANJIFA: ("Mughal Ganjifa", ""), + ##CSI.TYPE_MUGHAL_GANJIFA: ("Dashavatara Ganjifa", ""), + ##CSI.TYPE_NAVAGRAHA_GANJIFA: ("Navagraha Ganjifa", ""), + CSI.TYPE_NAVAGRAHA_GANJIFA: ("Dashavatara Ganjifa", ""), + CSI.TYPE_DASHAVATARA_GANJIFA: ("Dashavatara Ganjifa", ""), + CSI.TYPE_TRUMP_ONLY: ("Matrix", ""), + } + + # not changeable options + def setConstants(self): + self.win_animation = 1 + self.dragcursor = 1 + + def copy(self): + opt = Options() + opt.__dict__.update(self.__dict__) + opt.setConstants() + return opt + + +# /*********************************************************************** +# // Statistics +# ************************************************************************/ + +class _GameStatResult: + def __init__(self): + self.min = 0 + self.max = 0 + self.top = [] + self.num = 0 + self.total = 0 # sum of all values + self.average = 0 + + def update(self, value, game_number, game_start_time): + # update min & max + if not self.min or value < self.min: + self.min = value + if not self.max or value > self.max: + self.max = value + # calculate position & update top + position = None + n = 0 + for i in self.top: + if value < i.value: + position = n+1 + v = Struct(value=value, + game_number=game_number, + game_start_time=game_start_time) + self.top.insert(n, v) + del self.top[TOP_SIZE:] + break + n += 1 + if not position and len(self.top) < TOP_SIZE: + v = Struct(value=value, + game_number=game_number, + game_start_time=game_start_time) + self.top.append(v) + position = len(self.top) + # update average + self.total += value + self.num += 1 + self.average = float(self.total)/self.num + return position + + +class GameStat: + def __init__(self, id): + self.gameid = id + # + self.num_total = 0 + #self.num_not_won = 0 + self.num_lost = 0 + self.num_won = 0 + self.num_perfect = 0 + # + self.time_result = _GameStatResult() + self.moves_result = _GameStatResult() + self.total_moves_result = _GameStatResult() + self.score_result = _GameStatResult() + self.score_casino_result = _GameStatResult() + + def update(self, game, status): + # + game_number = game.getGameNumber(format=0) + game_start_time = game.gstats.start_time + # update number of games + # status: + # 0 - LOST + # 1 - WON + # 2 - PERFECT + self.num_total += 1 + assert status in (0, 1, 2) + if status == 0: + self.num_lost += 1 + return + elif status == 1: + self.num_won += 1 + else: # status == 2 + self.num_perfect += 1 + + score = game.getGameScore() + ##print 'GameScore:', score + score_p = None + if not score is None: + score_p = self.score_result.update( + score, game_number, game_start_time) + score = game.getGameScoreCasino() + ##print 'GameScoreCasino:', score + score_casino_p = None + if not score is None: + score_casino_p = self.score_casino_result.update( + score, game_number, game_start_time) + + if status == 0: + return + + game.updateTime() + time_p = self.time_result.update( + game.stats.elapsed_time, game_number, game_start_time) + moves_p = self.moves_result.update( + game.moves.index, game_number, game_start_time) + total_moves_p = self.total_moves_result.update( + game.stats.total_moves, game_number, game_start_time) + + return time_p, moves_p, total_moves_p, score_p, score_casino_p + + +class Statistics: + def __init__(self): + self.version_tuple = VERSION_TUPLE + self.saved = 0 + # a dictionary of dictionaries of GameStat (keys: player and gameid) + self.games_stats = {} + # a dictionary of lists of tuples (key: player) + self.prev_games = {} + self.all_prev_games = {} + self.session_games = {} + # some simple balance scores (key: gameid) + self.total_balance = {} # a dictionary of integers + self.session_balance = {} # reset per session + self.gameid_balance = 0 # reset when changing the gameid + + def new(self): + return Statistics() + + # + # player & demo statistics + # + + def resetStats(self, player, gameid): + self.__resetPrevGames(player, self.prev_games, gameid) + self.__resetPrevGames(player, self.session_games, gameid) + if not self.games_stats.has_key(player): + return + if gameid == 0: + # remove all games + try: del self.games_stats[player] + except KeyError: pass + else: + try: del self.games_stats[player][gameid] + except KeyError: pass + + def __resetPrevGames(self, player, games, gameid): + if not games.has_key(player): + return + if gameid == 0: + del games[player] + else: + games[player] = filter(lambda a, b=gameid: a[0] != b, games[player]) + + def getStats(self, player, gameid): + # returned (won, lost) + return self.getFullStats(player, gameid)[:2] + + def getFullStats(self, player, gameid): + # returned (won, lost, playing time, moves) + stats = self.games_stats + if stats.has_key(player) and stats[player].has_key(gameid): + s = self.games_stats[player][gameid] + return (s.num_won+s.num_perfect, + s.num_lost, + s.time_result.average, + s.moves_result.average,) + return (0, 0, 0, 0) + + def getSessionStats(self, player, gameid): + g = self.session_games.get(player, []) + g = filter(lambda a, b=gameid: a[0] == b, g) + won = len(filter(lambda a, b=gameid: a[2] > 0, g)) + lost = len(filter(lambda a, b=gameid: a[2] == 0, g)) + return won, lost + + def updateStats(self, player, game, status): + return self.updateLog(player, game, status) + + def updateLog(self, player, game, status): + ret = None + log = (game.id, game.getGameNumber(format=0), status, + game.gstats.start_time, game.gstats.total_elapsed_time, + VERSION_TUPLE, game.getGameScore(), game.getGameScoreCasino(), + game.GAME_VERSION) + # full log + if player is not None and status >= 0: + if not self.prev_games.has_key(player): + self.prev_games[player] = [] + self.prev_games[player].append(log) + if not self.all_prev_games.has_key(player): + self.all_prev_games[player] = [] + self.all_prev_games[player].append(log) + ret = self.updateGameStat(player, game, status) + # session log + if not self.session_games.has_key(player): + self.session_games[player] = [] + self.session_games[player].append(log) + return ret + + def updateGameStat(self, player, game, status): + # + if not self.games_stats.has_key(player): + self.games_stats[player] = {} + if not self.games_stats[player].has_key(game.id): + game_stat = GameStat(game.id) + self.games_stats[player][game.id] = game_stat + else: + game_stat = self.games_stats[player][game.id] + return game_stat.update(game, status) + + +# /*********************************************************************** +# // Comments +# ************************************************************************/ + +class Comments: + def __init__(self): + self.version_tuple = VERSION_TUPLE + self.saved = 0 + # + self.comments = {} + + def new(self): + return Comments() + + def setGameComment(self, gameid, text): + player = None + key = (1, gameid, player) + self.comments[key] = str(text) + + def getGameComment(self, gameid): + player = None + key = (1, gameid, player) + return self.comments.get(key, "") + + +# /*********************************************************************** +# // Application +# // This is the glue between the toplevel window and a Game. +# // Also handles all global resources. +# ************************************************************************/ + +class Application: + def __init__(self): + ##self.starttimer = Timer("Application.__init__") + self.gdb = GAME_DB + self.opt = Options() + self.startup_opt = self.opt.copy() + self.stats = Statistics() + self.comments = Comments() + self.splashscreen = 1 + self.debug = 0 + # visual components + self.top = None # the root toplevel window + self.top_bg = None # default background + self.top_palette = [None, None] # from command line [fg, bg] + self.top_cursor = None # default cursor + self.menubar = None + self.toolbar = None + self.canvas = None + self.statusbar = None + self.cardsets_cache = {} + # + self.game = None + self.dataloader = None + self.audio = None + self.images = None + self.subsampled_images = None + self.gimages = Struct( # global images + border = [], + demo = [], # demo logos + pause = [], # pause logos + logos = [], + redeal = [], + ##shade = [], + ##stats = [], + ) + #self.progress_bg = None + self.progress_images = [] + self.cardset_manager = CardsetManager() + self.cardset = None # current cardset + self.tabletile_manager = TileManager() + self.tabletile_index = 0 # current table tile + self.sample_manager = SampleManager() + self.music_manager = MusicManager() + self.music_playlist = [] + self.intro = Struct( + progress = None, # progress bar + ) + # directory names + home = os.path.normpath(gethomedir()) + config = os.path.normpath(getprefdir(PACKAGE, home)) + self.dn = Struct( + home = home, + config = config, + plugins = os.path.join(config, "plugins"), + savegames = os.path.join(config, "savegames"), + maint = os.path.join(config, "maint"), # debug + ) + for k, v in self.dn.__dict__.items(): +## if os.name == "nt": +## v = os.path.normcase(v) + v = os.path.normpath(v) + self.dn.__dict__[k] = v + # file names + self.fn = Struct( + opt = os.path.join(self.dn.config, "options.dat"), + stats = os.path.join(self.dn.config, "statistics.dat"), + holdgame = os.path.join(self.dn.config, "holdgame.dat"), + comments = os.path.join(self.dn.config, "comments.dat"), + ) + for k, v in self.dn.__dict__.items(): + if os.name == "nt": + v = os.path.normcase(v) + v = os.path.normpath(v) + self.fn.__dict__[k] = v + # random generators + self.gamerandom = PysolRandom() + self.miscrandom = PysolRandom() + # player + player = getusername() + if not player: + player = "unknown" + player = player[:30] + self.opt.player = player + # misc + self.nextgame = Struct( + id = 0, # start this game + random = None, # use this random generator + loadedgame = None, # data for loaded game + startdemo = 0, # start demo ? + cardset = None, # use this cardset + holdgame = 0, # hold this game on exit ? + bookmark = None, # goto this bookmark (load new cardset) + ) + self.commandline = Struct( + loadgame = None, # load a game ? + ) + self.demo_counter = 0 + + + # the PySol mainloop + def mainloop(self): + # copy startup options + self.startup_opt = self.opt.copy() + # try to load statistics + try: + self.loadStatistics() + except: + pass + # try to load comments + try: + self.loadComments() + except: + pass + # startup information + if self.getGameClass(self.opt.last_gameid): + self.nextgame.id = self.opt.last_gameid + # load a holded or saved game + id = self.gdb.getGamesIdSortedByName()[0] + tmpgame = self.constructGame(id) + if self.opt.game_holded > 0 and not self.nextgame.loadedgame: + game = None + try: + game = tmpgame._loadGame(self.fn.holdgame, self) + except: + game = None + if game: + if game.id == self.opt.game_holded and game.gstats.holded: + game.gstats.loaded = game.gstats.loaded - 1 + game.gstats.holded = 0 + self.nextgame.loadedgame = game + else: + # not a holded game + game.destruct() + destruct(game) + game = None + if self.commandline.loadgame and not self.nextgame.loadedgame: + try: + self.nextgame.loadedgame = tmpgame._loadGame(self.commandline.loadgame, self) + self.nextgame.loadedgame.gstats.holded = 0 + except: + self.nextgame.loadedgame = None + self.opt.game_holded = 0 + tmpgame.destruct() + destruct(tmpgame) + tmpgame = None + # + # widgets + # + # create the menubar + self.menubar = PysolMenubar(self, self.top) + # create the statusbar(s) + self.statusbar = PysolStatusbar(self.top) + self.statusbar.show(self.opt.statusbar) + self.helpbar = HelpStatusbar(self.top) + self.helpbar.show(self.opt.helpbar) + # create the canvas + self.scrolled_canvas = MfxScrolledCanvas(self.top) + self.canvas = self.scrolled_canvas.canvas + self.scrolled_canvas.grid(row=1, column=1, sticky='nsew') + self.top.grid_columnconfigure(1, weight=1) + self.top.grid_rowconfigure(1, weight=1) + self.setTile(self.tabletile_index, force=True) + # create the toolbar + dir = self.getToolbarImagesDir() + self.toolbar = PysolToolbar(self.top, dir=dir, + size=self.opt.toolbar_size, + relief=self.opt.toolbar_relief, + compound=self.opt.toolbar_compound) + self.toolbar.show(self.opt.toolbar) + for w, v in self.opt.toolbar_vars.items(): + self.toolbar.config(w, v) + # + if self.intro.progress: self.intro.progress.update(step=1) + # + try: + # this is the mainloop + while 1: + assert self.cardset is not None + id, random = self.nextgame.id, self.nextgame.random + self.nextgame.id, self.nextgame.random = 0, None + self.runGame(id, random) + if self.nextgame.holdgame: + assert self.nextgame.id <= 0 + try: + self.game.gstats.holded = 1 + self.game._saveGame(self.fn.holdgame) + self.opt.game_holded = self.game.id + except: + pass + self.freeGame() + # + if self.nextgame.id <= 0: + break + # load new cardset + if self.nextgame.cardset is not self.cardset: + self.loadCardset(self.nextgame.cardset, id=self.nextgame.id, update=7+256) + else: + self.requestCompatibleCardsetType(self.nextgame.id) + finally: + # update options + self.opt.last_gameid = id +## if self.debug: +## self.wm_save_state() +## # save options +## self.saveOptions() +## # save statistics +## self.saveStatistics() +## # save comments +## self.saveComments() +## # shut down audio +## self.audio.destroy() +## else: + try: self.wm_save_state() + except: + pass + # save options + try: self.saveOptions() + except: + pass + # save statistics + try: self.saveStatistics() + except: + pass + # save comments + try: self.saveComments() + except: + pass + # shut down audio + try: self.audio.destroy() + except: + pass + + + def runGame(self, id, random=None): + self.top.connectApp(self) + # create game instance + g = self.getGameClass(id) + if g is None: + id = 2 # start Klondike as default game + random = None + g = self.getGameClass(id) + if g is None: + # start first available game + id = self.gdb.getGamesIdSortedByName()[0] + g = self.getGameClass(id) + gi = self.getGameInfo(id) + #assert g and type(g) is types.ClassType and id > 0 + assert gi is not None and gi.id == id + self.game = self.constructGame(id) + self.gdb.setSelected(id) + self.game.busy = 1 + # create stacks and layout + self.game.create(self) + # connect with game + self.menubar.connectGame(self.game) + self.toolbar.connectGame(self.game, self.menubar) + self.game.updateStatus(player=self.opt.player) + # update "Recent games" menubar entry + while 1: + try: + self.opt.recent_gameid.remove(id) + except ValueError: + break + self.opt.recent_gameid.insert(0, id) + del self.opt.recent_gameid[15:] + self.menubar.updateRecentGamesMenu(self.opt.recent_gameid) + self.menubar.updateFavoriteGamesMenu() + # delete intro progress bar + if self.intro.progress: + self.intro.progress.destroy() + destruct(self.intro.progress) + self.intro.progress = None + # prepare game + autoplay = 0 + if self.nextgame.loadedgame is not None: + self.stats.gameid_balance = 0 + self.game.restoreGame(self.nextgame.loadedgame) + destruct(self.nextgame.loadedgame) + elif self.nextgame.bookmark is not None: + self.game.restoreGameFromBookmark(self.nextgame.bookmark) + else: + self.stats.gameid_balance = 0 + self.game.newGame(random=random, autoplay=0) + autoplay = 1 + self.nextgame.loadedgame = None + self.nextgame.bookmark = None + # splash screen + if self.opt.splashscreen and self.splashscreen > 0: + status = helpAbout(self, timeout=20000, sound=0) + if status == 2: # timeout - start a demo + if autoplay: + self.nextgame.startdemo = 1 + self.splashscreen = 0 + # start demo/autoplay + if self.nextgame.startdemo: + self.nextgame.startdemo = 0 + self.game.startDemo() + self.game.createDemoInfoText() + elif autoplay: + self.game.autoPlay() + self.game.stats.player_moves = 0 + # enter the Tk mainloop + self.game.busy = 0 + self.top.mainloop() + + + # free game + def freeGame(self): + # disconnect from game + self.toolbar.connectGame(None, None) + self.menubar.connectGame(None) + # clean up the canvas + unbind_destroy(self.canvas) + self.canvas.deleteAllItems() + self.canvas.update_idletasks() + # destruct the game + if self.game: + self.game.destruct() + destruct(self.game) + self.game = None + self.top.connectApp(None) + + + # + # UI support + # + + def wm_save_state(self): + if self.top: + s = self.top.wm_state() + ##print "wm_save_state", s + if s == "zoomed": + self.opt.wm_maximized = 1 + elif s == "normal": + self.opt.wm_maximized = 0 + + def wm_withdraw(self): + if self.intro.progress: + self.intro.progress.destroy() + destruct(self.intro.progress) + self.intro.progress = None + if self.top: + wm_withdraw(self.top) + self.top.busyUpdate() + + def loadImages1(self): + dir = os.path.join("images", "logos") + for f in ("joker07_40_774", + "joker08_40_774", + "joker07_50_774", + "joker08_50_774", + "joker11_100_774", + "joker10_100", + "pysol_40",): + self.gimages.logos.append(self.dataloader.findImage(f, dir)) + dir = "images" + ##for f in ("noredeal", "redeal",): + for f in ("stopsign", "redeal",): + self.gimages.redeal.append(self.dataloader.findImage(f, dir)) + + def loadImages2(self): + dir = os.path.join("images", "demo") + for f in ("demo01", "demo02", "demo03", "demo04", "demo05",): + self.gimages.demo.append(self.dataloader.findImage(f, dir)) + dir = os.path.join("images", "pause") + for f in ("pause01", "pause02",): + self.gimages.pause.append(self.dataloader.findImage(f, dir)) + ##dir = os.path.join("images", "stats") + ##for f in ("barchart",): + ## self.gimages.stats.append(self.dataloader.findImage(f, dir)) + + def loadImages3(self): + MfxDialog.img = [] + #dir = os.path.join('images', 'dialog', 'default') + dir = os.path.join('images', 'dialog', 'bluecurve') + for f in ('error', 'info', 'question', 'warning'): + fn = self.dataloader.findImage(f, dir) + im = loadImage(fn) + MfxDialog.img.append(im) + SelectDialogTreeData.img = [] + dir = os.path.join('images', 'tree') + for f in ('folder', 'openfolder', 'node', 'emptynode'): + fn = self.dataloader.findImage(f, dir) + im = loadImage(fn) + SelectDialogTreeData.img.append(im) + + def loadImages4(self): + # load all remaining images + for k, v in self.gimages.__dict__.items(): + if type(v) is types.ListType: + for i in range(len(v)): + if type(v[i]) is types.StringType: + v[i] = loadImage(v[i]) + if self.intro.progress: + self.intro.progress.update(step=1) + self.gimages.__dict__[k] = tuple(v) + + def _getImagesDir(self, *dirs, **kwargs): + check = kwargs.get('check', True) + d = os.path.join(self.dataloader.dir, 'images', *dirs) + if check: + if os.path.exists(d): + return d + return None + return d + + def getToolbarImagesDir(self): + if self.opt.toolbar_size: + size = 'large' + else: + size = 'small' + style = self.opt.toolbar_style + d = self._getImagesDir('toolbar', style, size) + if d: + return d + return self._getImagesDir('toolbar', 'default', size, check=False) + + def setTile(self, i, force=0): + if self.scrolled_canvas.setTile(self, i, force): + tile = self.tabletile_manager.get(i) + if i == 0: + self.opt.table_color = tile.color + self.opt.tabletile_name = None + else: + self.opt.tabletile_name = tile.basename + self.tabletile_index = i + self.tabletile_manager.setSelected(i) + return True + return False + + def getFont(self, name): + return self.opt.fonts.get(name) + + + # + # cardset + # + + def updateCardset(self, id=0, update=7): + cs = self.images.cs + self.cardset = cs + self.nextgame.cardset = cs + # update settings + self.cardset_manager.setSelected(cs.index) + # update options + self.images.setNegative(self.opt.negative_bottom) + self.subsampled_images.setNegative(self.opt.negative_bottom) + if update & 1: + self.opt.cardset[0] = (cs.name, cs.backname) + if update & 2: + self.opt.cardset[cs.si.type] = (cs.name, cs.backname) + gi = self.getGameInfo(id) + if gi: + if update & 256: + try: + del self.opt.cardset[(1, gi.id)] + except KeyError: + pass + t = self.checkCompatibleCardsetType(gi, cs) + if not t[1]: + if update & 4: + self.opt.cardset[gi.category] = (cs.name, cs.backname) + if update & 8: + self.opt.cardset[(1, gi.id)] = (cs.name, cs.backname) + #from pprint import pprint + #pprint(self.opt.cardset) + + def loadCardset(self, cs, id=0, update=7, progress=None): + #print 'loadCardset', cs.ident + r = 0 + if cs is None or cs.error: + return 0 + if cs is self.cardset: + self.updateCardset(id, update=update) + return 1 + # cache carsets + # self.cardsets_cache: + # key: Cardset.type + # value: (Cardset.ident, Images, SubsampledImages) + c = self.cardsets_cache.get(cs.type) + if c and c[0] == cs.ident: + #print 'load from cache', c + self.images, self.subsampled_images = c[1], c[2] + self.updateCardset(id, update=update) + if self.menubar is not None: + self.menubar.updateBackgroundImagesMenu() + return 1 + # + if progress is None: + self.wm_save_state() + self.wm_withdraw() + title = _("Loading %s %s...") % (CARDSET, cs.name) + color = self.opt.table_color + if self.tabletile_index > 0: + color = "#008200" + progress = PysolProgressBar(self, self.top, title=title, + color=color, + images=self.progress_images) + images = Images(self.dataloader, cs) + try: + if not images.load(app=self, progress=progress): + raise Exception, "Invalid or damaged "+CARDSET + simages = SubsampledImages(images) + if self.opt.cache_carsets: + c = self.cardsets_cache.get(cs.type) + if c: + ##c[1].destruct() + destruct(c[1]) + self.cardsets_cache[cs.type] = (cs.ident, images, simages) + elif self.images is not None: + ##self.images.destruct() + destruct(self.images) + # update + self.images = images + self.subsampled_images = simages + self.updateCardset(id, update=update) + r = 1 + except (Exception, TclError, UnpicklingError), ex: + traceback.print_exc() + cs.error = 1 + # restore settings + self.nextgame.cardset = self.cardset + if self.cardset: + self.cardset_manager.setSelected(self.cardset.index) + ##images.destruct() + destruct(images) + d = MfxExceptionDialog(self.top, ex, title=CARDSET+_(" load error"), + text=_("Error while loading ")+CARDSET) + self.intro.progress = progress + if r and self.menubar is not None: + self.menubar.updateBackgroundImagesMenu() + return r + + def checkCompatibleCardsetType(self, gi, cs): + assert gi is not None + assert cs is not None + gc = gi.category + cs_type = cs.si.type + t0, t1 = None, None + if gc == GI.GC_FRENCH: + t0 = "French" + if cs_type not in (CSI.TYPE_FRENCH, + ##CSI.TYPE_TAROCK, + ): + t1 = t0 + elif gc == GI.GC_HANAFUDA: + t0 = "Hanafuda" + if cs_type not in (CSI.TYPE_HANAFUDA,): + t1 = t0 + elif gc == GI.GC_TAROCK: + t0 = "Tarock" + if cs_type not in (CSI.TYPE_TAROCK,): + t1 = t0 + elif gc == GI.GC_MAHJONGG: + t0 = "Mahjongg" + if cs_type not in (CSI.TYPE_MAHJONGG,): + t1 = t0 + elif gc == GI.GC_HEXADECK: + t0 = "Hex A Deck" + if cs_type not in (CSI.TYPE_HEXADECK,): + t1 = t0 + elif gc == GI.GC_MUGHAL_GANJIFA: + t0 = "Mughal Ganjifa" + if cs_type not in (CSI.TYPE_MUGHAL_GANJIFA, + CSI.TYPE_NAVAGRAHA_GANJIFA, + CSI.TYPE_DASHAVATARA_GANJIFA,): + t1 = t0 + elif gc == GI.GC_NAVAGRAHA_GANJIFA: + t0 = "Navagraha Ganjifa" + if cs_type not in (CSI.TYPE_NAVAGRAHA_GANJIFA, + CSI.TYPE_DASHAVATARA_GANJIFA,): + t1 = t0 + elif gc == GI.GC_DASHAVATARA_GANJIFA: + t0 = "Dashavatara Ganjifa" + if cs_type not in (CSI.TYPE_DASHAVATARA_GANJIFA,): + t1 = t0 + elif gc == GI.GC_TRUMP_ONLY: + t0 = "Trump only" + if cs_type not in (CSI.TYPE_TRUMP_ONLY,): + t1 = t0 + elif len(cs.trumps) < gi.ncards: # not enough cards + t1 = t0 + else: + # we should not come here + t0 = t1 = "Unknown" + return t0, t1 + + def getCompatibleCardset(self, gi, cs): + if gi is None: + return cs, 1 + # try current + if cs: + t = self.checkCompatibleCardsetType(gi, cs) + if not t[1]: + return cs, 1 + # try by gameid / category + for key, flag in (((1, gi.id), 8), (gi.category, 4)): + c = self.opt.cardset.get(key) + if not c or len(c) != 2: + continue + cs = self.cardset_manager.getByName(c[0]) + if not cs: + continue + t = self.checkCompatibleCardsetType(gi, cs) + if not t[1]: + cs.updateCardback(backname=c[1]) + return cs, flag + # ask + return None, 0 + + def requestCompatibleCardsetType(self, id): + gi = self.getGameInfo(id) + # + cs, cs_update_flag = self.getCompatibleCardset(gi, self.cardset) + if cs is self.cardset: + return 0 + if cs is not None: + self.loadCardset(cs, update=1) + return 1 + # + t = self.checkCompatibleCardsetType(gi, self.cardset) + d = MfxDialog(self.top, title=_("Incompatible ")+CARDSET, + bitmap="warning", + text=_('''The currently selected %s %s +is not compatible with the game +%s + +Please select a %s type %s. +''') % (CARDSET, self.cardset.name, gi.name, t[0], CARDSET), + strings=(_("OK"),), default=0) + cs = self.__selectCardsetDialog(t) + if cs is None: + return -1 + self.loadCardset(cs, id=id) + return 1 + + def __selectCardsetDialog(self, t): + key = self.cardset.index + d = SelectCardsetByTypeDialogWithPreview( + self.top, title=_("Please select a %s type %s") % (t[0], CARDSET), + app=self, manager=self.cardset_manager, key=key, + strings=(None, _("OK"), _("Cancel")), default=1) + if d.status != 0 or d.button != 1: + return None + cs = self.cardset_manager.get(d.key) + if cs is None or d.key == key: + return None + return cs + + + # + # load & save options, statistics and comments + # + + def loadOptions(self): + self.opt.setDefaults(self.top) + if not os.path.exists(self.fn.opt): + return + opt = unpickle(self.fn.opt) + if opt: + ##import pprint + ##pprint.pprint(opt.__dict__) + #cardset = self.opt.cardset + #cardset.update(opt.cardset) + self.opt.__dict__.update(opt.__dict__) + #self.opt.cardset = cardset + self.opt.setConstants() + + def loadStatistics(self): + stats = unpickle(self.fn.stats) + if stats: + ##print "loaded:", stats.__dict__ + self.stats.__dict__.update(stats.__dict__) + # start a new session + self.stats.session_games = {} + self.stats.session_balance = {} + self.stats.gameid_balance = 0 + + def loadComments(self): + comments = unpickle(self.fn.comments) + if comments: + ##print "loaded:", comments.__dict__ + self.comments.__dict__.update(comments.__dict__) + + def __saveObject(self, obj, fn): + obj.version_tuple = VERSION_TUPLE + obj.saved = obj.saved + 1 + pickle(obj, fn, binmode=1) + + def saveOptions(self): + self.__saveObject(self.opt, self.fn.opt) + + def saveStatistics(self): + self.__saveObject(self.stats, self.fn.stats) + + def saveComments(self): + self.__saveObject(self.comments, self.fn.comments) + + + # + # access games database + # + + def constructGame(self, id): + gi = self.gdb.get(id) + if gi is None: + raise Exception, "Unknown game (id %d)" % id + return gi.gameclass(gi) + + def getGamesIdSortedById(self): + return self.gdb.getGamesIdSortedById() + + def getGamesIdSortedByName(self): + return self.gdb.getGamesIdSortedByName() + + ## + def getGamesIdSortedByPlayed(self): + def _cmp(a, b): + wa, la, ta, ma = self.stats.getFullStats(self.opt.player, a) + wb, lb, tb, mb = self.stats.getFullStats(self.opt.player, b) + return cmp(wb+lb, wa+la) # reverse + games = list(self.gdb.getGamesIdSortedByName()) + games.sort(_cmp) + return games + + def getGamesIdSortedByWon(self): + def _cmp(a, b): + wa, la, ta, ma = self.stats.getFullStats(self.opt.player, a) + wb, lb, tb, mb = self.stats.getFullStats(self.opt.player, b) + return cmp(wb, wa) # reverse + games = list(self.gdb.getGamesIdSortedByName()) + games.sort(_cmp) + return games + + def getGamesIdSortedByLost(self): + def _cmp(a, b): + wa, la, ta, ma = self.stats.getFullStats(self.opt.player, a) + wb, lb, tb, mb = self.stats.getFullStats(self.opt.player, b) + return cmp(lb, la) # reverse + games = list(self.gdb.getGamesIdSortedByName()) + games.sort(_cmp) + return games + + def getGamesIdSortedByPercent(self): + def _cmp(a, b): + wa, la, ta, ma = self.stats.getFullStats(self.opt.player, a) + wb, lb, tb, mb = self.stats.getFullStats(self.opt.player, b) + if wa+la == 0 or wb+lb == 0: + return cmp(wb+lb, wa+la) # reverse + return cmp(float(wb)/(wb+lb), + float(wa)/(wa+la)) # reverse + games = list(self.gdb.getGamesIdSortedByName()) + games.sort(_cmp) + return games + + def getGamesIdSortedByPlayingTime(self): + def _cmp(a, b): + wa, la, ta, ma = self.stats.getFullStats(self.opt.player, a) + wb, lb, tb, mb = self.stats.getFullStats(self.opt.player, b) + return cmp(tb, ta) # reverse + games = list(self.gdb.getGamesIdSortedByName()) + games.sort(_cmp) + return games + + def getGamesIdSortedByMoves(self): + def _cmp(a, b): + wa, la, ta, ma = self.stats.getFullStats(self.opt.player, a) + wb, lb, tb, mb = self.stats.getFullStats(self.opt.player, b) + return cmp(mb, ma) # reverse + games = list(self.gdb.getGamesIdSortedByName()) + games.sort(_cmp) + return games + + + def getGameInfo(self, id): + return self.gdb.get(id) + + def getGameClass(self, id): + gi = self.gdb.get(id) + if gi is None: return None + return gi.gameclass + + def getGameTitleName(self, id): + gi = self.gdb.get(id) + if gi is None: return None + return gettext(gi.name) + + def getGameMenuitemName(self, id): + gi = self.gdb.get(id) + if gi is None: return None + return gi.short_name + + def getGameRulesFilename(self, id): + gi = self.gdb.get(id) + if gi is None: return None + if gi.rules_filename is not None: + return gi.rules_filename + n = gi.name + n = re.sub(r"[\[\(].*$", "", n) + n = latin1_to_ascii(n) + n = re.sub(r"[^\w]", "", n) + n = n.lower() + ".html" + f = os.path.join(self.dataloader.dir, "html", "rules", n) + if not os.path.exists(f): + n = '' + gi.rules_filename = n # cache the filename for next use + return n + + def getGameSaveName(self, id): + n = self.getGameTitleName(id) + if not n: return None + m = re.search(r"^(.*)([\[\(](\w+).*[\]\)])\s*$", n) + if m: + n = m.group(1) + "_" + m.group(2).lower() + n = latin1_to_ascii(n) + return re.sub(r"[^\w\-]", "", n) + + def getRandomGameId(self): + return self.miscrandom.choice(self.gdb.getGamesIdSortedById()) + + def getAllUserNames(self): + names = [] + for n in self.stats.games_stats.keys(): + if self.stats.games_stats[n]: + names.append(n) + names.sort() + return names + + # + # plugins + # + + def loadPlugins(self, dir): + if not dir or not os.path.isdir(dir): + return + names = os.listdir(dir) + names = map(os.path.normcase, names) + names.sort() + for name in names: + m = re.search(r"^(.+)\.py$", name) + n = os.path.join(dir, name) + if m and os.path.isfile(n): + p = sys.path[:] + try: + loadGame(m.group(1), n) + except Exception, ex: + print "Error loading plugin " + n + ": " + str(ex) + sys.stdout.flush() + sys.path = p + + + # + # init cardsets + # + + # read & parse a cardset config.txt file - see class Cardset in resource.py + def _readCardsetConfig(self, dir, filename): + f = None + try: + f = open(filename, "r") + lines = f.readlines() + finally: + if f: f.close() + lines = [l.strip() for l in lines] + if lines[0].find("PySol") != 0: + return None + config = CardsetConfig() + if not self._parseCardsetConfig(config, lines): + ##print filename, 'invalide config' + return None + if config.CARDD > self.top.winfo_screendepth(): + return None + cs = Cardset() + cs.dir = dir + cs.update(config.__dict__) + return cs + + def _parseCardsetConfig(self, cs, line): + _debug = True + def print_err(line, field=None, msg=''): + if field: + print '_parseCardsetConfig error: line #%d, fields#%d %s' \ + % (line, field, msg) + else: + print '_parseCardsetConfig error: line #%d: %s' \ + % (line, msg) + if len(line) < 6: + if _debug: print_err(1, msg='number of lines') + return 0 + # line[0]: magic identifier, possible version information + fields = [f.strip() for f in line[0].split(';')] + if len(fields) >= 2: + m = re.search(r"^(\d+)$", fields[1]) + if m: cs.version = int(m.group(1)) + if cs.version >= 3: + if len(fields) < 5: + if _debug: print_err(1, msg='number of fields') + return 0 + cs.ext = fields[2] + m = re.search(r"^(\d+)$", fields[3]) + if not m: + if _debug: print_err(1, 3, 'not integer') + return 0 + cs.type = int(m.group(1)) + m = re.search(r"^(\d+)$", fields[4]) + if not m: + if _debug: print_err(1, 4, 'not integer') + return 0 + cs.ncards = int(m.group(1)) + if cs.version >= 4: + if len(fields) < 6: + if _debug: print_err(1, msg='number of fields') + return 0 + styles = fields[5].split(",") + for s in styles: + m = re.search(r"^\s*(\d+)\s*$", s) + if not m: + if _debug: print_err(1, 5, 'not integer') + return 0 + s = int(m.group(1)) + if not s in cs.styles: + cs.styles.append(s) + if cs.version >= 5: + if len(fields) < 7: + if _debug: print_err(1, msg='number of fields') + return 0 + m = re.search(r"^(\d+)$", fields[6]) + if not m: + if _debug: print_err(1, 6, 'not integer') + return 0 + cs.year = int(m.group(1)) + if len(cs.ext) < 2 or cs.ext[0] != ".": + if _debug: print_err(1, msg='invalide extention') + return 0 + # line[1]: identifier/name + if not line[1]: + if _debug: print_err(2, msg='empty line') + return 0 + cs.ident = line[1] + m = re.search(r"^(.*;)?([^;]+)$", cs.ident) + if not m: + if _debug: print_err(2, msg='invalide format') + return 0 + cs.name = m.group(2).strip() + # line[2]: CARDW, CARDH, CARDD + m = re.search(r"^(\d+)\s+(\d+)\s+(\d+)", line[2]) + if not m: + if _debug: print_err(3, msg='invalide format') + return 0 + cs.CARDW, cs.CARDH, cs.CARDD = int(m.group(1)), int(m.group(2)), int(m.group(3)) + # line[3]: CARD_UP_YOFFSET, CARD_DOWN_YOFFSET, SHADOW_XOFFSET, SHADOW_YOFFSET + m = re.search(r"^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", line[3]) + if not m: + if _debug: print_err(4, msg='invalide format') + return 0 + cs.CARD_XOFFSET = int(m.group(1)) + cs.CARD_YOFFSET = int(m.group(2)) + cs.SHADOW_XOFFSET = int(m.group(3)) + cs.SHADOW_YOFFSET = int(m.group(4)) + # line[4]: default background + back = line[4] + if not back: + if _debug: print_err(5, msg='empty line') + return 0 + # line[5]: all available backgrounds + cs.backnames = [f.strip() for f in line[5].split(';')] + if back in cs.backnames: + cs.backindex = cs.backnames.index(back) + else: + cs.backnames.insert(0, back) + cs.backindex = 0 + ##if cs.type != 1: print cs.type, cs.name + return 1 + + def initCardsets(self): + manager = self.cardset_manager + # find all available cardsets + dirs = manager.getSearchDirs(self, ("cardsets", ""), "PYSOL_CARDSETS") + if self.debug: + dirs = dirs + manager.getSearchDirs(self, "cardsets-*") + try: + dirs = dirs + manager.getRegistryDirs(self, ("PySol_Cardsets", "Cardsets")) + except: + pass + ##print dirs + found, t = [], {} + for dir in dirs: + dir = dir.strip() + try: + names = [] + if dir and os.path.isdir(dir) and not t.has_key(dir): + t[dir] = 1 + names = os.listdir(dir) + names.sort() + for name in names: + if not name.startswith('cardset-'): continue + d = os.path.join(dir, name) + if not os.path.isdir(d): continue + f1 = os.path.join(d, "config.txt") + f2 = os.path.join(d, "COPYRIGHT") + if os.path.isfile(f1) and os.path.isfile(f2): + try: + cs = self._readCardsetConfig(d, f1) + if cs: + ##from pprint import pprint + ##print cs.name + ##pprint(cs.__dict__) + back = cs.backnames[cs.backindex] + f1 = os.path.join(d, back) + f2 = os.path.join(d, "shade" + cs.ext) + if (cs.ext in IMAGE_EXTENSIONS and + os.path.isfile(f1) and os.path.isfile(f2)): + found.append(cs) + #print '+', cs.name + else: + print 'fail _readCardsetConfig:', d, f1 + pass + except Exception, err: + ##traceback.print_exc() + pass + except EnvError, ex: + pass + # register cardsets + for obj in found: + if not manager.getByName(obj.name): + manager.register(obj) + ##print obj.index, obj.name + + + # + # init tiles + # + + def initTiles(self): + manager = self.tabletile_manager + # find all available tiles + dirs = manager.getSearchDirs(self, + ("tiles-*", os.path.join("tiles", 'stretch')), + "PYSOL_TILES") + try: + dirs = dirs + manager.getRegistryDirs(self, "Tiles") + except: + pass + ##print dirs + s = "((\\" + ")|(\\".join(IMAGE_EXTENSIONS) + "))$" + ext_re = re.compile(s, re.I) + text_color_re = re.compile(r"^(.+)-([0-9A-Fa-f]{6})$") + found, t = [], {} + for dir in dirs: + dir = dir.strip() + try: + names = [] + if dir and os.path.isdir(dir): + names = os.listdir(dir) + names.sort() + for name in names: + if not name or not ext_re.search(name): + continue + f = os.path.join(dir, name) + if not os.path.isfile(f): + continue + tile = Tile() + tile.filename = f + n = ext_re.sub("", name.strip()) + if os.path.split(dir)[-1] == 'stretch': + tile.stretch = 1 + elif n.find('-stretch') > 0: + # stretch? + tile.stretch = 1 + n = n.replace('-stretch', '') + #else: + # tile.stretch = 0 + m = text_color_re.search(n) + if m: + n = m.group(1) + tile.text_color = "#" + m.group(2).lower() + #n = re.sub("[-_]", " ", n) + n = n.replace('_', ' ') + tile.name = n + key = n.lower() + if not t.has_key(key): + t[key] = 1 + found.append((n, tile)) + except EnvError, ex: + pass + # register tiles + found.sort() + for f in found: + obj = f[1] + if not manager.getByName(obj.name): + manager.register(obj) + + + # + # init samples / music + # + + def initResource(self, manager, dirs, ext_re, Resource_Class): + found, t = [], {} + for dir in dirs: + dir = dir.strip() + if dir: + dir = os.path.normpath(dir) + try: + names = [] + if dir and os.path.isdir(dir): + names = os.listdir(dir) + names = map(os.path.normcase, names) + names.sort() + for name in names: + if not name or not ext_re.search(name): + continue + f = os.path.join(dir, name) + f = os.path.normpath(f) + if not os.path.isfile(f): + continue + obj = Resource_Class() + obj.filename = f + n = ext_re.sub("", name.strip()) + obj.name = n + key = n.lower() + if not t.has_key(key): + t[key] = 1 + found.append((n, obj)) + except EnvError, ex: + pass + # register songs + found.sort() + if manager: + for f in found: + obj = f[1] + if not manager.getByName(obj.name): + manager.register(obj) + return found + + + def initSamples(self): + manager = self.sample_manager + # find all available samples + dirs = manager.getSearchDirs(self, ("sound", os.path.join("sound", "extra"))) + ##print dirs + ext_re = re.compile(r"\.((wav))$", re.I) + self.initResource(manager, dirs, ext_re, Sample) + + + def initMusic(self): + manager = self.music_manager + # find all available music songs + dirs = manager.getSearchDirs(self, "music-*", "PYSOL_MUSIC") + try: + dirs = dirs + manager.getRegistryDirs(self, "Music") + except: + pass + ##print dirs + ext_re = re.compile(r"\.((it)|(mod)|(mp3)|(pym)|(s3m)|(xm))$", re.I) + self.initResource(manager, dirs, ext_re, Music) + + diff --git a/pysollib/game.py b/pysollib/game.py new file mode 100644 index 0000000000..fe193ef360 --- /dev/null +++ b/pysollib/game.py @@ -0,0 +1,2427 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import time, types +from cStringIO import StringIO + +# PySol imports +from mfxutil import Pickler, Unpickler, UnpicklingError +from mfxutil import destruct, Struct, SubclassResponsibility +from mfxutil import UnpicklingError, uclock, usleep +from mfxutil import format_time +from util import get_version_tuple, Timer +from util import ACE, QUEEN, KING +from version import VERSION, VERSION_TUPLE +from settings import PACKAGE +from settings import TOP_TITLE +from gamedb import GI +from resource import CSI +from pysolrandom import PysolRandom, LCRandom31 +from pysoltk import EVENT_HANDLED, EVENT_PROPAGATE +from pysoltk import CURSOR_WATCH, ANCHOR_SW, ANCHOR_SE +from pysoltk import tkname, bind, wm_map +from pysoltk import after, after_idle, after_cancel +from pysoltk import MfxDialog, MfxExceptionDialog +from pysoltk import MfxCanvasText, MfxCanvasImage +from pysoltk import MfxCanvasLine, MfxCanvasRectangle +from pysoltk import Card +from move import AMoveMove, AFlipMove, ATurnStackMove +from move import ANextRoundMove, ASaveSeedMove, AShuffleStackMove +from move import AUpdateStackMove, AFlipAllMove, ASaveStateMove +from hint import DefaultHint +from help import helpAbout + +PLAY_TIME_TIMEOUT = 200 + +# /*********************************************************************** +# // Base class for all solitaire games +# // +# // Handles: +# // load/save +# // undo/redo (using a move history) +# // hints/demo +# ************************************************************************/ + +class Game: + # for self.gstats.updated + U_PLAY = 0 + U_WON = -2 + U_LOST = -3 + U_PERFECT = -4 + + # for self.moves.state + S_INIT = 0x00 + S_DEAL = 0x10 + S_FILL = 0x20 + S_PLAY = 0x30 + S_UNDO = 0x40 + S_REDO = 0x50 + + # for loading and saving - subclasses should override if + # the format for a saved game changed (see also canLoadGame()) + GAME_VERSION = 1 + + + # + # game construction + # + + # only basic initialization here + def __init__(self, gameinfo): + self.preview = 0 + self.random = None + self.gameinfo = gameinfo + self.id = gameinfo.id + assert self.id > 0 + self.busy = 0 + self.pause = False + self.finished = False + self.version = VERSION + self.version_tuple = VERSION_TUPLE + self.cards = [] + self.stackmap = {} # dict with (x,y) tuples as key + self.allstacks = [] + self.demo_logo = None + self.pause_logo = None + self.s = Struct( # stacks + talon = None, + waste = None, + foundations = [], + rows = [], + reserves = [], + internals = [], + ) + self.sg = Struct( # stack-groups + openstacks = [], # for getClosestStack(): only on these stacks the player can place a card + talonstacks = [], # for Hint + dropstacks = [], # for Hint & getAutoStacks() + reservestacks = [], # for Hint +## hint = Struct(), # extra info for class Hint + hp_stacks = [], # for getHightlightPilesStacks() + ) + self.regions = Struct( # for getClosestStack() + # set by optimizeRegions(): + info = [], # list of tuples(stacks, rect) + remaining = [], # list of stacks in no region + # + data = [], # raw data + ) + self.reset() + + # main constructor + def create(self, app): + ##timer = Timer("Game.create") + old_busy = self.busy + self.__createCommon(app) + self.setCursor(cursor=CURSOR_WATCH) + #print 'gameid:', self.id + self.top.wm_title(PACKAGE + " - " + self.getTitleName()) + self.top.wm_iconname(PACKAGE + " - " + self.getTitleName()) + # create the game + if self.app.intro.progress: self.app.intro.progress.update(step=1) + ##print timer + self.createGame() + ##print timer + # set some defaults + self.sg.openstacks = filter(lambda s: s.cap.max_accept >= s.cap.min_accept, self.sg.openstacks) + self.sg.hp_stacks = filter(lambda s: s.cap.max_move >= 2, self.sg.dropstacks) + # convert stackgroups to tuples (speed) + self.allstacks = tuple(self.allstacks) + self.s.foundations = tuple(self.s.foundations) + self.s.rows = tuple(self.s.rows) + self.s.reserves = tuple(self.s.reserves) + self.s.internals = tuple(self.s.internals) + self.sg.openstacks = tuple(self.sg.openstacks) + self.sg.talonstacks = tuple(self.sg.talonstacks) + self.sg.dropstacks = tuple(self.sg.dropstacks) + self.sg.reservestacks = tuple(self.sg.reservestacks) + self.sg.hp_stacks = tuple(self.sg.hp_stacks) + # init the stack view + for stack in self.allstacks: + stack.prepareStack() + stack.assertStack() + if self.s.talon: + assert hasattr(self.s.talon, "round") + assert hasattr(self.s.talon, "max_rounds") + # optimize regions + self.optimizeRegions() + # create cards + ##print timer + if not self.cards: + self.cards = self.createCards(progress=self.app.intro.progress) + self.initBindings() + self.top.bind('', self.top._sleepEvent) + self.top.bind('<3>', self.top._sleepEvent) + ##print timer + # update display properties + self.top.wm_geometry("") # cancel user-specified geometry + self.canvas.setInitialSize(self.width, self.height) + self.stats.update_time = time.time() + self.busy = old_busy + ##print timer + self.showHelp() # just in case + + def initBindings(self): + # note: a Game is only allowed to bind self.canvas and not to self.top + bind(self.canvas, "<1>", self.clickHandler) + bind(self.canvas, "<2>", self.clickHandler) + bind(self.canvas, "<3>", self.clickHandler) + bind(self.top, '', self._unmapHandler) + + def __createCommon(self, app): + self.busy = 1 + self.app = app + self.top = app.top + self.canvas = app.canvas + self.filename = "" + self.drag = Struct( + event = None, # current event + timer = None, # current event timer + start_x = 0, # X coord of initial drag event + start_y = 0, # Y coord of initial drag event + stack = None, # + cards = [], # + shadows = [], # list of canvas images + shade_stack = None, # stack currently shaded + shade_img = None, # canvas image + canshade_stacks = [], # list of stacks already tested + noshade_stacks = [], # for this drag + ) + if self.gstats.start_player is None: + self.gstats.start_player = self.app.opt.player + # optional MfxCanvasText items + self.texts = Struct( + info = None, # misc info text + help = None, # a static help text + misc = None, # + score = None, # for displaying the score + base_rank = None, # for displaying the base_rank + ) + + def createPreview(self, app): + ##timer = Timer("Game.createPreview") + old_busy = self.busy + self.__createCommon(app) + self.preview = max(1, self.canvas.preview) + # create game + self.createGame() + ##print timer + # set some defaults + self.sg.openstacks = filter(lambda s: s.cap.max_accept >= s.cap.min_accept, self.sg.openstacks) + self.sg.hp_stacks = filter(lambda s: s.cap.max_move >= 2, self.sg.dropstacks) + # init the stack view + for stack in self.allstacks: + stack.prepareStack() + stack.assertStack() + # optimize regions + self.optimizeRegions() + # create cards + self.cards = self.createCards() + # + self.canvas.setInitialSize(self.width, self.height) + self.busy = old_busy + + def destruct(self): + # help breaking circular references + for obj in self.cards: + destruct(obj) + for obj in self.allstacks: + obj.destruct() + destruct(obj) + + # Do not destroy game structure (like stacks and cards) here ! + def reset(self, restart=0): + self.filename = "" + self.demo = None + self.hints = Struct( + list = None, # list of hints for the current move + index = -1, + level = -1, + ) + self.saveinfo = Struct( # needed for saving a game + stack_caps = [], + ) + self.loadinfo = Struct( # used when loading a game + stacks = None, + talon_round = 1, + ncards = 0, + ) + # local statistics are reset on each game restart + self.stats = Struct( + hints = 0, # number of hints consumed + highlight_piles = 0, # number of highlight piles consumed + highlight_cards = 0, # number of highlight matching cards consumed + highlight_samerank = 0, # number of highlight same rank consumed + undo_moves = 0, # number of undos + redo_moves = 0, # number of redos + total_moves = 0, # number of total moves in this game + player_moves = 0, # number of moves + demo_moves = 0, # number of moves while in demo mode + autoplay_moves = 0, # number of moves + quickplay_moves = 0, # number of quickplay moves + goto_bookmark_moves = 0, # number of goto bookmark + demo_updated = 0, # did this game already update the demo stats ? + update_time = time.time(), # for updateTime() + elapsed_time = 0.0, + pause_start_time = 0.0, + ) + self.startMoves() + if restart: + return + # global statistics survive a game restart + self.gstats = Struct( + holded = 0, # is this a holded game + loaded = 0, # number of times this game was loaded + saved = 0, # number of times this game was saved + restarted = 0, # number of times this game was restarted + goto_bookmark_moves = 0, # number of goto bookmark + updated = self.U_PLAY, # did this game already update the player stats ? + start_time = time.time(), # game start time + total_elapsed_time = 0.0, + start_player = None, + ) + # global saveinfo survives a game restart + self.gsaveinfo = Struct( + bookmarks = {}, + comment = "", + ) + + def getTitleName(self): + return self.app.getGameTitleName(self.id) + + def getGameNumber(self, format): + s = str(self.random) + if format: return "#" + s + return s + + # this is called from within createGame() + def setSize(self, w, h): + self.width, self.height = int(round(w)), int(round(h)) + + def setCursor(self, cursor): + if self.canvas: + self.canvas.config(cursor=cursor) + ##self.canvas.update_idletasks() + if self.app and self.app.toolbar: + self.app.toolbar.setCursor(cursor=cursor) + + + # + # game creation + # + + # start a new name + def newGame(self, random=None, restart=0, autoplay=1): + self.finished = False + old_busy, self.busy = self.busy, 1 + self.setCursor(cursor=CURSOR_WATCH) + self.disableMenus() + self.reset(restart=restart) + self.resetGame() + self.createRandom(random) + ##print self.random, self.random.__dict__ + self.shuffle() + assert len(self.s.talon.cards) == self.gameinfo.ncards + ##print self.app.starttimer + for stack in self.allstacks: + stack.updateText() + self.updateText() + self.updateStatus(player=self.app.opt.player, + gamenumber=self.getGameNumber(format=1), + moves=(0, 0), + stats=self.app.stats.getStats(self.app.opt.player, self.id)) + # unhide toplevel when we use a progress bar + if not self.preview: + wm_map(self.top, maximized=self.app.opt.wm_maximized) + self.top.busyUpdate() + self.stopSamples() + # let's go + self.moves.state = self.S_INIT + self.startGame() + if self.gameinfo.si.game_flags & GI.GT_OPEN: + if self.s.talon: + assert len(self.s.talon.cards) == 0 + for stack in self.allstacks: + if stack.is_visible: + for c in stack.cards: + assert c.face_up + self.startMoves() + for stack in self.allstacks: + stack.updateText() + self.updateText() + self.updateStatus(moves=(0, 0)) + self.updateMenus() + self.stopSamples() + if autoplay: + self.autoPlay() + self.stats.player_moves = 0 + self.setCursor(cursor=self.app.top_cursor) + self.stats.update_time = time.time() + if not self.preview: + self.startPlayTimer() + self.busy = old_busy + + # restore a loaded game (see load/save below) + def restoreGame(self, game, reset=1): + old_busy, self.busy = self.busy, 1 + if reset: + self.reset() + self.resetGame() + # 1) copy loaded variables + self.filename = game.filename + self.version = game.version + self.version_tuple = game.version_tuple + self.random = game.random + self.moves = game.moves + self.stats = game.stats + self.gstats = game.gstats + # 2) copy extra save-/loadinfo + self.saveinfo = game.saveinfo + self.gsaveinfo = game.gsaveinfo + self.s.talon.round = game.loadinfo.talon_round + self.finished = game.finished + # 3) move cards to stacks + assert len(self.allstacks) == len(game.loadinfo.stacks) + for i in range(len(self.allstacks)): + for t in game.loadinfo.stacks[i]: + card_id, face_up = t + card = self.cards[card_id] + if face_up: + card.showFace() + else: + card.showBack() + self.allstacks[i].addCard(card) + # 4) update settings + for stack_id, cap in self.saveinfo.stack_caps: + ##print stack_id, cap + self.allstacks[stack_id].cap.update(cap.__dict__) + # 5) subclass settings + self._restoreGameHook(game) + # 6) update view + for stack in self.allstacks: + stack.updateText() + self.updateText() + self.updateStatus(player=self.app.opt.player, + gamenumber=self.getGameNumber(format=1), + moves=(self.moves.index, self.stats.total_moves), + stats=self.app.stats.getStats(self.app.opt.player, self.id)) + if not self.preview: + self.updateMenus() + wm_map(self.top, maximized=self.app.opt.wm_maximized) + self.setCursor(cursor=self.app.top_cursor) + self.stats.update_time = time.time() + self.busy = old_busy + # + self.startPlayTimer() + + # restore a bookmarked game (e.g. after changing the cardset) + def restoreGameFromBookmark(self, bookmark): + old_busy, self.busy = self.busy, 1 + file = StringIO(bookmark) + p = Unpickler(file) + game = self._undumpGame(p, self.app) + assert game.id == self.id + self.restoreGame(game, reset=0) + destruct(game) + self.busy = old_busy + + def resetGame(self): + self.hints.list = None + self.s.talon.removeAllCards() + for stack in self.allstacks: + stack.resetGame() + if self.preview <= 1: + for t in (self.texts.score, self.texts.base_rank,): + if t: + t.config(text="") + + def nextGameFlags(self, id, random=None): + f = 0 + if id != self.id: + f = f | 1 + if self.app.nextgame.cardset is not self.app.cardset: + f = f | 2 + if random is not None: + if random.__class__ is not self.random.__class__: + f = f | 16 + elif random.initial_seed != self.random.initial_seed: + f = f | 16 + return f + + # quit to outer mainloop in class App, possibly restarting + # with another game from there + def quitGame(self, id=0, random=None, loadedgame=None, + startdemo=0, bookmark=0, holdgame=0): + self.updateTime() + if bookmark: + id, random = self.id, self.random + file = StringIO() + p = Pickler(file, 1) + self._dumpGame(p, bookmark=1) + self.app.nextgame.bookmark = file.getvalue() + if id > 0: + self.setCursor(cursor=CURSOR_WATCH) + self.app.nextgame.id = id + self.app.nextgame.random = random + self.app.nextgame.loadedgame = loadedgame + self.app.nextgame.startdemo = startdemo + self.app.nextgame.holdgame = holdgame + self.updateStatus(time=None, moves=None, gamenumber=None, stats=None) + self.top.mainquit() + + # This should be called directly before newGame(), + # restoreGame(), restoreGameFromBookmark() and quitGame(). + def endGame(self, restart=0, bookmark=0, holdgame=0): + if self.preview: + return + self.app.wm_save_state() + if holdgame: + return + if bookmark: + return + if restart: + if self.moves.index > 0 and self.getPlayerMoves() > 0: + self.gstats.restarted = self.gstats.restarted + 1 + return + self.updateStats() + stats = self.app.stats + if self.shallUpdateBalance(): + b = self.getGameBalance() + if b: + stats.total_balance[self.id] = stats.total_balance.get(self.id, 0) + b + stats.session_balance[self.id] = stats.session_balance.get(self.id, 0) + b + stats.gameid_balance = stats.gameid_balance + b + + # restart the current game + def restartGame(self): + self.endGame(restart=1) + self.newGame(restart=1, random=self.random) + + def createRandom(self, random): + if random is None: + if isinstance(self.random, PysolRandom): + state = self.random.getstate() + self.app.gamerandom.setstate(state) + # we want at least 17 digits + seed = self.app.gamerandom.randrange(10000000000000000L, + PysolRandom.MAX_SEED) + self.random = PysolRandom(seed) + self.random.origin = self.random.ORIGIN_RANDOM + else: + self.random = random + self.random.reset() + ##print 'createRandom:', self.random + ##print "createRandom: origin =", self.random.origin + + def enterState(self, state): + old_state = self.moves.state + if state < old_state: + self.moves.state = state + return old_state + + def leaveState(self, old_state): + self.moves.state = old_state + + + # + # card creation & shuffling + # + + # Create all cards for the game. + def createCards(self, progress=None): + ##timer = Timer("Game.createCards") + gi = self.gameinfo + pstep = 0 + if progress: + pstep = (100.0 - progress.percent) / gi.ncards + cards = [] + id = 0 + x, y = self.s.talon.x, self.s.talon.y + for deck in range(gi.decks): + for suit in gi.suits: + for rank in gi.ranks: + card = self._createCard(id, deck, suit, rank, x=x, y=y) + if card is None: + continue + cards.append(card) + id = id + 1 + if progress: progress.update(step=pstep) + trump_suit = len(gi.suits) + for rank in gi.trumps: + card = self._createCard(id, deck, trump_suit, rank, x=x, y=y) + if card is None: + continue + cards.append(card) + id = id + 1 + if progress: progress.update(step=pstep) + if progress: progress.update(percent=100) + assert len(cards) == gi.ncards + ##print timer + return cards + + def _createCard(self, id, deck, suit, rank, x, y): + return Card(id, deck, suit, rank, game=self, x=x, y=y) + + # shuffle cards + def shuffle(self): + # get a fresh copy of the original game-cards + cards = list(self.cards) + # init random generator + if isinstance(self.random, LCRandom31) and len(cards) == 52: + # FreeCell mode + fcards = [] + for i in range(13): + for j in (0, 39, 26, 13): + fcards.append(cards[i + j]) + cards = fcards + self.random.reset() # reset to initial seed + # shuffle + self.random.shuffle(cards) + # subclass hook + cards = self._shuffleHook(cards) + # finally add the shuffled cards to the Talon + for card in cards: + self.s.talon.addCard(card, update=0) + card.showBack(unhide=0) + + # shuffle cards, but keep decks together + def shuffleSeparateDecks(self): + cards = [] + self.random.reset() + n = self.gameinfo.ncards / self.gameinfo.decks + for deck in range(self.gameinfo.decks): + i = deck * n + deck_cards = list(self.cards)[i:i+n] + self.random.shuffle(deck_cards) + cards.extend(deck_cards) + cards = self._shuffleHook(cards) + for card in cards: + self.s.talon.addCard(card, update=0) + card.showBack(unhide=0) + + # subclass overrideable (must use self.random) + def _shuffleHook(self, cards): + return cards + + # utility for use by subclasses + def _shuffleHookMoveToTop(self, cards, func, ncards=999999): + # move cards to top of the Talon (i.e. first cards to be dealt) + cards, scards = self._shuffleHookMoveSorter(cards, func, ncards) + return cards + scards + + def _shuffleHookMoveToBottom(self, cards, func, ncards=999999): + # move cards to bottom of the Talon (i.e. last cards to be dealt) + cards, scards = self._shuffleHookMoveSorter(cards, func, ncards) + return scards + cards + + def _shuffleHookMoveSorter(self, cards, func, ncards): + # note that we reverse the cards, so that smaller sort_orders + # will be nearer to the top of the Talon + sitems, i = [], len(cards) + for c in cards[:]: + select, sort_order = func(c) + if select: + cards.remove(c) + sitems.append((sort_order, i, c)) + if len(sitems) >= ncards: + break + i = i - 1 + sitems.sort() + sitems.reverse() + scards = map(lambda item: item[2], sitems) + return cards, scards + + + # + # menu support + # + + def _finishDrag(self): + if self.demo: + self.stopDemo() + if self.busy: return 1 + if self.drag.stack: + self.drag.stack.finishDrag() + return 0 + + def _cancelDrag(self, break_pause=True): + if self.demo: + self.stopDemo() + if break_pause and self.pause: + self.doPause() + self.interruptSleep() + if self.busy: return 1 + if self.drag.stack: + self.drag.stack.cancelDrag() + return 0 + + def updateMenus(self): + if not self.preview: + self.app.menubar.updateMenus() + + def disableMenus(self): + if not self.preview: + self.app.menubar.disableMenus() + + + # + # UI & graphics support + # + + def clickHandler(self, *args): + self.interruptSleep() + if self.demo: + self.stopDemo() + return EVENT_PROPAGATE + + def updateStatus(self, **kw): + if self.preview: + return + tb, sb = self.app.toolbar, self.app.statusbar + for k, v in kw.items(): + if k == "gamenumber": + if v is None: + if sb: sb.updateText(gamenumber="") + continue + if type(v) is types.StringType: + if sb: sb.updateText(gamenumber=v) + continue + if k == "info": + ##print 'updateStatus info:', v + if v is None: + if sb: sb.updateText(info="") + continue + if type(v) is types.StringType: + if sb: sb.updateText(info=v) + continue + if k == "moves": + if v is None: + ##if tb: tb.updateText(moves="Moves\n") + if sb: sb.updateText(moves="") + continue + if type(v) is types.TupleType: + ##if tb: tb.updateText(moves="Moves\n%d/%d" % v) + if sb: sb.updateText(moves="%d/%d" % v) + continue + if type(v) is types.IntType: + ##if tb: tb.updateText(moves="Moves\n%d" % v) + if sb: sb.updateText(moves="%d" % v) + continue + if type(v) is types.StringType: + ##if tb: tb.updateText(moves=v) + if sb: sb.updateText(moves=v) + continue + if k == "player": + if v is None: + if tb: tb.updateText(player=_("Player\n")) + continue + if type(v) in types.StringTypes: + if tb: + #if self.app.opt.toolbar_size: + if self.app.toolbar.getSize(): + tb.updateText(player=_("Player\n") + v) + else: + tb.updateText(player=v) + continue + if k == "stats": + if v is None: + if sb: sb.updateText(stats="") + continue + if type(v) is types.TupleType: + t = "%d: %d/%d" % (v[0]+v[1], v[0], v[1]) + if sb: sb.updateText(stats=t) + continue + if k == "time": + if v is None: + if sb: sb.updateText(time='') + if type(v) in types.StringTypes: + if sb: sb.updateText(time=v) + continue + raise AttributeError, k + + def _unmapHandler(self, event): + # pause game if root window has been iconified + if event.widget is self.top and not self.pause: + self.doPause() + + + # + # sound support + # + + def playSample(self, name, priority=0, loop=0): + ##print "playSample:", name, priority, loop + if self.app.audio: + return self.app.audio.playSample(name, priority=priority, loop=loop) + return 0 + + def stopSamples(self): + if self.app.audio: + self.app.audio.stopSamples() + + def stopSamplesLoop(self): + if self.app.audio: + self.app.audio.stopSamplesLoop() + + def startDealSample(self, loop=999999): + a = self.app.opt.animations + if a and not self.preview: + self.canvas.update_idletasks() + if self.app.audio and self.app.opt.sound: + if a in (1, 2, 5): + self.playSample("deal01", priority=100, loop=loop) + elif a == 3: + self.playSample("deal04", priority=100, loop=loop) + elif a == 4: + self.playSample("deal08", priority=100, loop=loop) + + + # + # misc. methods + # + + def areYouSure(self, title=None, text=None, confirm=-1, default=0): + if self.preview: + return 1 + if confirm < 0: + confirm = self.app.opt.confirm + if confirm: + if not title: title = PACKAGE + if not text: text = _("Discard current game ?") + self.playSample("areyousure") + d = MfxDialog(self.top, title=title, text=text, + bitmap="question", + Default=default, strings=(_("OK"), _("Cancel"))) + if d.status != 0 or d.button != 0: + return 0 + return 1 + + def notYetImplemented(self): + # don't used + d = MfxDialog(self.top, title="Not yet implemented", + text="This function is\nnot yet implemented.", + bitmap="error") + + # main animation method + def animatedMoveTo(self, from_stack, to_stack, cards, x, y, tkraise=1, frames=-1, shadow=-1): + if self.app.opt.animations == 0 or frames == 0: + return + if self.app.debug and not self.top.winfo_ismapped(): + return + # init timer - need a high resolution for this to work + clock, delay, skip = None, 1, 1 + if self.app.opt.animations >= 2: + clock = uclock + SPF = 0.15 / 8 # animation speed - seconds per frame + if frames < 0: + frames = 8 + assert frames >= 2 + if self.app.opt.animations == 3: # slow + frames = frames * 8 + SPF = SPF / 2 + elif self.app.opt.animations == 4: # very slow + frames = frames * 16 + SPF = SPF / 2 + elif self.app.opt.animations == 5: + # this is used internally in game preview to speed up + # the initial dealing + if self.moves.state == self.S_INIT and frames > 4: + frames = frames / 2 + if shadow < 0: + shadow = self.app.opt.shadow + shadows = () + # start animation + if tkraise: + for card in cards: + card.tkraise() + c0 = cards[0] + dx, dy = (x - c0.x) / float(frames), (y - c0.y) / float(frames) + tx, ty = 0, 0 + i = 1 + if clock: starttime = clock() + while i < frames: + mx, my = int(round(dx * i)) - tx, int(round(dy * i)) - ty + tx, ty = tx + mx, ty + my + for s in shadows: + s.move(mx, my) + for card in cards: + card.moveBy(mx, my) + if i == 1 and shadow and from_stack: + # create shadows in the first frame + sx, sy = self.app.images.SHADOW_XOFFSET, self.app.images.SHADOW_YOFFSET + shadows = from_stack.createShadows(cards, sx, sy) + self.canvas.update_idletasks() + step = 1 + if clock: + endtime = starttime + i*SPF + sleep = endtime - clock() + if delay and sleep >= 0.005: + # we're fast - delay + ##print "Delay frame", i, sleep + usleep(sleep) + elif skip and sleep <= -0.75*SPF: + # we're slow - skip 1 or 2 frames + ##print "Skip frame", i, sleep + step = step + 1 + if frames > 4 and sleep < -1.5*SPF: step = step + 1 + ##print i, step, mx, my; time.sleep(0.5) + i = i + step + # last frame: delete shadows, move card to final position + for s in shadows: + s.delete() + dx, dy = x - c0.x, y - c0.y + for card in cards: + card.moveBy(dx, dy) + self.canvas.update_idletasks() + + def winAnimation(self, perfect=0): + # Stupid animation when you win a game. + # FIXME: make this interruptible by a key- or mousepress +### if not self.app.opt.win_animation: +### return + if not self.app.opt.animations: + return + if self.app.debug and not self.top.winfo_ismapped(): + return + self.top.busyUpdate() + old_a = self.app.opt.animations + if old_a == 0: + self.app.opt.animations = 1 # timer based + elif old_a == 4: # very slow + self.app.opt.animations = 3 # slow + cards = [] + for s in self.allstacks: + if s is not self.s.talon: + for c in s.cards: + cards.append((c,s)) + # select some random cards + acards = [] + for i in range(16): + c, s = self.app.miscrandom.choice(cards) + if not c in acards: + acards.append(c) + # animate + sx, sy = self.s.talon.x, self.s.talon.y + w, h = self.width, self.height + while cards: + # get and un-tuple a random card + t = self.app.miscrandom.choice(cards) + c, s = t + s.removeCard(c, update=0) + # animation + if c in acards or len(cards) <= 2: + self.animatedMoveTo(s, None, [c], w/2, h/2, tkraise=0, shadow=0) + self.animatedMoveTo(s, None, [c], sx, sy, tkraise=0, shadow=0) + else: + c.moveTo(sx, sy) + cards.remove(t) + self.app.opt.animations = old_a + + def sleep(self, seconds): +## if 0 and self.canvas: +## self.canvas.update_idletasks() + if seconds > 0: + if self.top: + self.top.interruptSleep() + self.top.sleep(seconds) + else: + time.sleep(seconds) + + def interruptSleep(self): + if self.top: + self.top.interruptSleep() + + # + # card image support + # + + def getCardFaceImage(self, deck, suit, rank): +## if self.app.cardset.type == CSI.TYPE_TAROCK: +## if rank >= 10: +## rank = rank + 1 + return self.app.images.getFace(deck, suit, rank) + + def getCardBackImage(self, deck, suit, rank): + return self.app.images.getBack(deck, suit, rank) + + + # + # layout support + # + + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + # Since we only compare distances, + # we don't bother to take the square root. + for stack in stacks: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + closest, cdist = stack, dist + return closest + + def getClosestStack(self, card, dragstack): + cx, cy = card.x, card.y + for stacks, rect in self.regions.info: + if cx >= rect[0] and cx < rect[2] and cy >= rect[1] and cy < rect[3]: + return self._getClosestStack(cx, cy, stacks, dragstack) + return self._getClosestStack(cx, cy, self.regions.remaining, dragstack) + + # define a region for use in getClosestStack() + def setRegion(self, stacks, rect, priority=0): + assert len(stacks) > 0 + assert len(rect) == 4 and rect[0] < rect[2] and rect[1] < rect[3] + ##MfxCanvasRectangle(self.canvas, rect[0], rect[1], rect[2], rect[3], + ## width=2, fill=None, outline='red') + for s in stacks: + assert s and s in self.allstacks + # verify that the stack lies within the rectangle + x, y, r = s.x, s.y, rect + ##print x, y, r + assert r[0] <= x <= r[2] and r[1] <= y <= r[3] + # verify that the stack is not already in another region + # with the same priority + for d in self.regions.data: + if priority == d[0]: + assert not s in d[2] + # add to regions + self.regions.data.append((priority, -len(self.regions.data), tuple(stacks), tuple(rect))) + + # as getClosestStack() is called within the mouse motion handler + # event it is worth optimizing a little bit + def optimizeRegions(self): + # sort regions.data by priority + self.regions.data.sort() + self.regions.data.reverse() + # copy (stacks, rect) to regions.info + self.regions.info = [] + for d in self.regions.data: + self.regions.info.append((d[2], d[3])) + self.regions.info = tuple(self.regions.info) + # determine remaining stacks + remaining = list(self.sg.openstacks) + for stacks, rect in self.regions.info: + for stack in stacks: + while stack in remaining: + remaining.remove(stack) + self.regions.remaining = tuple(remaining) + ##print self.regions.info + + + # + # Game - subclass overridable actions - IMPORTANT FOR GAME LOGIC + # + + # create the game (create stacks, texts, etc.) + def createGame(self): + raise SubclassResponsibility + + # start the game (i.e. deal initial cards) + def startGame(self): + raise SubclassResponsibility + + # can we deal cards ? + def canDealCards(self): + # default: ask the Talon + return self.s.talon and self.s.talon.canDealCards() + + # deal cards - return number of cards dealt + def dealCards(self, sound=1): + # default: set state to deal and pass dealing to Talon + if self.s.talon and self.canDealCards(): + self.finishMove() + old_state = self.enterState(self.S_DEAL) + n = self.s.talon.dealCards(sound=sound) + self.leaveState(old_state) + self.finishMove() + if not self.checkForWin(): + self.autoPlay() + return n + return 0 + + # fill a stack if rules require it (e.g. Picture Gallery) + def fillStack(self, stack): + pass + + # the actual hint class (or None) + Hint_Class = DefaultHint + + def getHintClass(self): + return self.Hint_Class + + def getStrictness(self): + return 0 + + # can we save outself ? + def canSaveGame(self): + return 1 + # can we load this game ? + def canLoadGame(self, version_tuple, game_version): + return self.GAME_VERSION == game_version + # can we set a bookmark ? + def canSetBookmark(self): + return self.canSaveGame() + + # can we undo/redo ? + def canUndo(self): + return 1 + def canRedo(self): + return self.canUndo() + + + # + # Game - stats handlers + # + + # game changed - i.e. should we ask the player to discard the game + def changed(self, restart=0): + if self.gstats.updated < 0: + return 0 # already won or lost + if self.gstats.loaded > 0: + return 0 # loaded games account for no stats + if not restart: + if self.gstats.restarted > 0: + return 1 # game was restarted - always ask + if self.gstats.goto_bookmark_moves > 0: + return 1 + if self.moves.index == 0 or self.getPlayerMoves() == 0: + return 0 + return 2 + + def getWinStatus(self): + won = self.isGameWon() != 0 + if not won or self.stats.hints > 0 or self.stats.demo_moves > 0: + # sorry, you lose + return won, 0, self.U_LOST + if (self.stats.undo_moves == 0 and + self.stats.goto_bookmark_moves == 0 and +### self.stats.quickplay_moves == 0 and + self.stats.highlight_piles == 0 and + self.stats.highlight_cards == 0): + # perfect ! + return won, 2, self.U_PERFECT + return won, 1, self.U_WON + + # update statistics when a game was won/ended/canceled/... + def updateStats(self, demo=0): + if self.preview: + return '' + if not demo: + self.stopPlayTimer() + won, status, updated = self.getWinStatus() + if demo and self.getPlayerMoves() == 0: + # a pure demo game - update demo stats + self.stats.demo_updated = updated + self.app.stats.updateStats(None, self, won) + return '' + elif self.changed(): + # must update player stats + self.gstats.updated = updated + if self.app.opt.update_player_stats: + ret = self.app.stats.updateStats(self.app.opt.player, self, status) + self.updateStatus(stats=self.app.stats.getStats(self.app.opt.player, self.id)) + top_msg = '' + if ret: + if ret[0]: # playing time + top_msg = _('\nYou have reached\n#%d in the %s of playing time') % (ret[0], TOP_TITLE) + if 1 and ret[1]: # moves + if top_msg: + top_msg += _('\nand #%d in the %s of moves') % (ret[1], TOP_TITLE) + else: + top_msg = _('\nYou have reached\n#%d in the %s of moves') % (ret[1], TOP_TITLE) + if 0 and ret[2]: # total moves + if top_msg: + top_msg += _('\nand #%d in the %s of total moves') % (ret[1], TOP_TITLE) + else: + top_msg = _('\nYou have reached\n#%d in the %s of total moves') % (ret[1], TOP_TITLE) + return top_msg + elif not demo: + # only update the session log + if self.app.opt.update_player_stats: + if self.gstats.loaded: + self.app.stats.updateLog(self.app.opt.player, self, -2) + elif self.gstats.updated == 0 and self.stats.demo_updated == 0: + self.app.stats.updateLog(self.app.opt.player, self, -1) + return '' + + def checkForWin(self): + won, status, updated = self.getWinStatus() + if not won: + return 0 + self.finishMove() # just in case + if self.preview: + return 1 + if self.finished: + return 1 + if self.demo: + return status + if status == 2: + top_msg = self.updateStats() + time = self.getTime() + self.finished = True + self.playSample("winperfect", priority=1000) + d = MfxDialog(self.top, title=_("Game won"), + text=_(''' +Congratulations, this +was a truly perfect game ! + +Your playing time is %s +for %d moves. +%s +''') % (time, self.moves.index, top_msg), + strings=(_("New game"), None, _("Cancel")), + image=self.app.gimages.logos[5], separatorwidth=2) + elif status == 1: + top_msg = self.updateStats() + time = self.getTime() + self.finished = True + self.playSample("winwon", priority=1000) + d = MfxDialog(self.top, title=_("Game won"), + text=_(''' +Congratulations, you did it ! + +Your playing time is %s +for %d moves. +%s +''') % (time, self.moves.index, top_msg), + strings=(_("New game"), None, _("Cancel")), + image=self.app.gimages.logos[4], separatorwidth=2) + elif self.gstats.updated < 0: + self.playSample("winfinished", priority=1000) + d = MfxDialog(self.top, title=_("Game finished"), bitmap="info", + text=_("\nGame finished\n"), + strings=(_("New game"), None, _("Cancel"))) + else: + self.playSample("winlost", priority=1000) + d = MfxDialog(self.top, title=_("Game finished"), bitmap="info", + text=_("\nGame finished, but not without my help...\n"), + strings=(_("New game"), _("Restart"), _("Cancel"))) + self.updateMenus() + if d.status == 0 and d.button == 0: + # new game + self.endGame() + if status == 2: + self.winAnimation(perfect=1) + elif status == 1: + self.winAnimation() + self.newGame() + elif d.status == 0 and d.button == 1: + # restart game + self.restartGame() + return 1 + + + # + # Game - subclass overridable methods (but usually not) + # + + def isGameWon(self): + # default: all Foundations must be filled + c = 0 + for s in self.s.foundations: + c = c + len(s.cards) + return c == len(self.cards) + + def getFoundationDir(self): + for s in self.s.foundations: + if len(s.cards) >= 2: + return s.getRankDir() + return 0 + + # determine the real number of player_moves + def getPlayerMoves(self): + player_moves = self.stats.player_moves +## if self.moves.index > 0 and self.stats.demo_moves == self.moves.index: +## player_moves = 0 + return player_moves + + def updateTime(self): + if self.finished: + return + if self.pause: + return + t = time.time() + d = t - self.stats.update_time + if d > 0: + self.stats.elapsed_time += d + self.gstats.total_elapsed_time += d + self.stats.update_time = t + + def getTime(self): + self.updateTime() + t = int(self.stats.elapsed_time) + return format_time(t) + + + # + # Game - subclass overridable intelligence + # + + def getAutoStacks(self, event=None): + # returns (flipstacks, dropstacks, quickplaystacks) + # default: sg.dropstacks + return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks) + + # handles autofaceup, autodrop and autodeal + def autoPlay(self, autofaceup=-1, autodrop=-1, autodeal=-1, sound=1): + if self.demo: + return 0 + old_busy, self.busy = self.busy, 1 + if autofaceup < 0: autofaceup = self.app.opt.autofaceup + if autodrop < 0: autodrop = self.app.opt.autodrop + if autodeal < 0: autodeal = self.app.opt.autodeal + moves = self.stats.total_moves + n = self._autoPlay(autofaceup, autodrop, autodeal, sound=sound) + self.finishMove() + self.stats.autoplay_moves = self.stats.autoplay_moves + (self.stats.total_moves - moves) + self.busy = old_busy + return n + + def _autoPlay(self, autofaceup, autodrop, autodeal, sound): + flipstacks, dropstacks, quickstacks = self.getAutoStacks() + done_something = 1 + while done_something: + done_something = 0 + # a) flip top cards face-up + if autofaceup and flipstacks: + for s in flipstacks: + if s.canFlipCard(): + if sound: + self.playSample("autoflip", priority=5) + s.flipMove() + done_something = 1 + # each single flip is undo-able unless opt.autofaceup + self.finishMove() + if self.checkForWin(): + return 1 + # b) drop cards + if autodrop and dropstacks: + for s in dropstacks: + to_stack, ncards = s.canDropCards(self.s.foundations) + if to_stack: + # each single drop is undo-able (note that this call + # is before the acutal move) + self.finishMove() + if sound: + self.playSample("autodrop", priority=30) + s.moveMove(ncards, to_stack) + done_something = 1 + if self.checkForWin(): + return 1 + # c) deal + if autodeal: + if self._autoDeal(sound=sound): + done_something = 1 + self.finishMove() + if self.checkForWin(): + return 1 + return 0 + + def _autoDeal(self, sound=1): + # default: deal a card to the waste if the waste is empty + w = self.s.waste + if w and len(w.cards) == 0 and self.canDealCards(): + return self.dealCards(sound=sound) + return 0 + + + ### highlight all moveable piles + def getHighlightPilesStacks(self): + # default: dropstacks with min pile length = 2 + if self.sg.hp_stacks: + return ((self.sg.hp_stacks, 2),) + return () + + def _highlightCards(self, info, sleep=1.5): + if not info: + return 0 + items = [] + for s, c1, c2, color in info: + assert c1 in s.cards and c2 in s.cards + sy0 = s.CARD_YOFFSET[0] + if sy0 >= 0: + x1, y1 = s.getPositionFor(c1) + x2, y2 = s.getPositionFor(c2) + if c2 is not s.cards[-1] and sy0 > 0: + y2 = y2 + sy0 + else: + y2 = y2 + self.app.images.CARDH + else: + x1, y1 = s.getPositionFor(c2) + x2, y2 = s.getPositionFor(c1) + y2 = y2 + self.app.images.CARDH + if c2 is not s.cards[-1]: + y1 = y1 + (self.app.images.CARDH + sy0) + x2 = x2 + self.app.images.CARDW + ##print c1, c2, x1, y1, x2, y2 + r = MfxCanvasRectangle(self.canvas, x1-1, y1-1, x2+1, y2+1, + width=4, fill=None, outline=color) + r.tkraise(c2.item) + items.append(r) + if not items: + return 0 + self.canvas.update_idletasks() + self.sleep(sleep) + items.reverse() + for r in items: + r.delete() + self.canvas.update_idletasks() + return EVENT_HANDLED + + def highlightNotMatching(self): + if self.demo: + return + if not self.app.opt.highlight_not_matching: + return + # compute visible geometry + x = int(int(self.canvas.cget('width'))*(self.canvas.xview()[0])) + y = int(int(self.canvas.cget('height'))*(self.canvas.yview()[0])) + w, h = self.canvas.winfo_width(), self.canvas.winfo_height() + # + color = self.app.opt.highlight_not_matching_color + width = 6 + r = MfxCanvasRectangle(self.canvas, x+width/2, y+width/2, + x+w-width/2, y+h-width/2, + width=width, fill=None, outline=color) + self.canvas.update_idletasks() + self.sleep(self.app.opt.highlight_cards_sleep) + r.delete() + self.canvas.update_idletasks() + + def highlightPiles(self, stackinfo, sleep=1.5): + stackinfo = self.getHighlightPilesStacks() + if not stackinfo: + return 0 + col = self.app.opt.highlight_piles_colors + hi = [] + for si in stackinfo: + for s in si[0]: + pile = s.getPile() + if pile and len(pile) >= si[1]: + hi.append((s, pile[0], pile[-1], col[1])) + return self._highlightCards(hi, sleep) + + + ### highlight matching cards + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return 0 + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + # prefer non-empty piles in to_stack + return (len(to_stack.cards) != 0) + + + # + # Score (I really don't like scores in Patience games...) + # + + # update game-related canvas texts (i.e. self.texts) + def updateText(self): + pass + + def getGameScore(self): + return None + + # casino type scoring + def getGameScoreCasino(self): + v = -len(self.cards) + for s in self.s.foundations: + v = v + 5 * len(s.cards) + return v + + def shallUpdateBalance(self): + # Update the balance unless this is a loaded game or + # a manually selected game number. + if self.gstats.loaded: + return 0 + if self.random.origin == self.random.ORIGIN_SELECTED: + return 0 + return 1 + + def getGameBalance(self): + return 0 + + + # + # Hint - uses self.getHintClass() + # + + # compute all hints for the current position + # this is the only method that actually uses class Hint + def getHints(self, level, taken_hint=None): + hint_class = self.getHintClass() + if hint_class is None: + return None + hint = hint_class(self, level) # call constructor + return hint.getHints(taken_hint) # and return all hints + + # give a hint + def showHint(self, level=0, sleep=1.5, taken_hint=None): + if self.getHintClass() is None: + self.highlightNotMatching() + return None + # reset list if level has changed + if level != self.hints.level: + self.hints.level = level + self.hints.list = None + # compute all hints + if self.hints.list is None: + self.hints.list = self.getHints(level, taken_hint) + ###print self.hints.list + self.hints.index = 0 + # get next hint from list + if not self.hints.list: + self.highlightNotMatching() + return None + h = self.hints.list[self.hints.index] + self.hints.index = self.hints.index + 1 + if self.hints.index >= len(self.hints.list): + self.hints.index = 0 + # paranoia - verify hint + score, pos, ncards, from_stack, to_stack, text_color, forced_move = h + assert from_stack and len(from_stack.cards) >= ncards + if ncards == 0: + # a deal move, should not happen with level=0/1 + assert level >= 2 + assert from_stack is self.s.talon + return h + elif from_stack == to_stack: + # a flip move, should not happen with level=0/1 + assert level >= 2 + assert ncards == 1 and len(from_stack.cards) >= ncards + return h + else: + # a move move + assert to_stack + assert 1 <= ncards <= len(from_stack.cards) + assert to_stack.acceptsCards(from_stack, from_stack.cards[-ncards:]) + if sleep <= 0.0: + return h + info = (level == 1) or (level > 1 and self.app.opt.demo_score) + if info and self.app.statusbar and self.app.opt.statusbar: + self.app.statusbar.configLabel("info", text=_("Score %6d") % (score), fg=text_color) + else: + info = 0 + self.drawHintArrow(from_stack, to_stack, ncards, sleep) + if info: + self.app.statusbar.configLabel("info", text="", fg="#000000") + return h + + + def drawHintArrow(self, from_stack, to_stack, ncards, sleep): + # compute position for arrow + images = self.app.images + x1, y1 = from_stack.getPositionFor(from_stack.cards[-ncards]) + x2, y2 = to_stack.getPositionFor(to_stack.getCard()) + x1, y1 = x1 + images.CARD_DX, y1 + images.CARD_DY + x2, y2 = x2 + images.CARD_DX, y2 + images.CARD_DY + if ncards == 1: + x1 = x1 + images.CARDW / 2 + y1 = y1 + images.CARDH / 2 + elif from_stack.CARD_XOFFSET[0]: + x1 = x1 + from_stack.CARD_XOFFSET[0] / 2 + y1 = y1 + images.CARDH / 2 + else: + x1 = x1 + images.CARDW / 2 + y1 = y1 + from_stack.CARD_YOFFSET[0] / 2 + x2 = x2 + images.CARDW / 2 + y2 = y2 + images.CARDH / 2 + # draw the hint + arrow = MfxCanvasLine(self.canvas, x1, y1, x2, y2, width=7, + fill=self.app.opt.hintarrow_color, + arrow="last", arrowshape=(30,30,10)) + self.canvas.update_idletasks() + # wait + self.sleep(sleep) + # delete the hint + if arrow is not None: + arrow.delete() + self.canvas.update_idletasks() + + + # + # Demo - uses showHint() + # + + # start a demo + def startDemo(self, mixed=1, level=2): + assert level >= 2 # needed for flip/deal hints + if not self.top: + return + self.demo = Struct( + level = level, + mixed = mixed, + sleep = self.app.opt.demo_sleep, + last_deal = [], + hint = None, + keypress = None, + start_demo_moves = self.stats.demo_moves, + info_text = None, + ) + self.hints.list = None + self.createDemoInfoText() + self.createDemoLogo() + after_idle(self.top, self.demoEvent) # schedule first move + + # stop the demo + def stopDemo(self, event=None): + if not self.demo: + return + self.canvas.setTopImage(None) + self.demo_logo = None + self.demo = None + self.updateMenus() + + # demo event - play one demo move and check for win/loss + def demoEvent(self): + # note: other events are allowed to stop self.demo at any time + if not self.demo or self.demo.keypress: + self.stopDemo() + #self.updateMenus() + return + finished = self.playOneDemoMove(self.demo) + self.finishMove() + self.top.update_idletasks() + self.hints.list = None + player_moves = self.getPlayerMoves() + d, status = None, 0 + bitmap = "info" + timeout = 10000 + if player_moves == 0: + timeout = 5000 + if 0 and self.app.debug and self.demo.mixed: + timeout = 1000 + if self.isGameWon(): + finished = 1 + self.stopPlayTimer() + if not self.top.winfo_ismapped(): + status = 2 + elif player_moves == 0: + self.playSample("autopilotwon") + s = self.app.miscrandom.choice((_("Great"), _("Cool"), _("Yeah"), _("Wow"))) + d = MfxDialog(self.top, title=PACKAGE+_(" Autopilot"), + text=_("\nGame solved in %d moves.\n") % self.moves.index, + image=self.app.gimages.logos[4], strings=(s,), + separatorwidth=2, timeout=timeout) + status = d.status + else: + s = self.app.miscrandom.choice((_("OK"), _("OK"))) + text = _("\nGame finished\n") + if self.app.debug: + text = text + "\n%d %d\n" % (self.stats.player_moves, self.stats.demo_moves) + d = MfxDialog(self.top, title=PACKAGE+_(" Autopilot"), + text=text, bitmap=bitmap, strings=(s,), + padx=30, timeout=timeout) + status = d.status + elif finished: + ##self.stopPlayTimer() + if not self.top.winfo_ismapped(): + status = 2 + else: + if player_moves == 0: + self.playSample("autopilotlost") + s = self.app.miscrandom.choice((_("Oh well"), _("That's life"), _("Hmm"))) + d = MfxDialog(self.top, title=PACKAGE+_(" Autopilot"), + text=_("\nThis won't come out...\n"), + bitmap=bitmap, strings=(s,), + padx=30, timeout=timeout) + status = d.status + if finished: + self.updateStats(demo=1) + if self.demo and status == 2 and not self.app.debug: + # timeout in dialog + if self.stats.demo_moves > self.demo.start_demo_moves: + # we only increase the splash-screen counter if the last + # demo actually made a move + self.app.demo_counter = self.app.demo_counter + 1 + if self.app.demo_counter % 3 == 0: + if self.top.winfo_ismapped(): + status = helpAbout(self.app, timeout=10000) + if self.demo and status == 2: + # timeout in dialog - start another demo + demo = self.demo + id = self.id + if 1 and demo.mixed and self.app.debug: + # debug - advance game id to make sure we hit all games + gl = self.app.gdb.getGamesIdSortedById() + ##gl = self.app.gdb.getGamesIdSortedByName() + gl = list(gl) + index = (gl.index(self.id) + 1) % len(gl) + id = gl[index] + elif demo.mixed: + # choose a random game + gl = self.app.gdb.getGamesIdSortedById() + while len(gl) > 1: + id = self.app.getRandomGameId() + if 0 or id != self.id: # force change of game + break + if self.nextGameFlags(id) == 0: + self.endGame() + self.newGame(autoplay=0) + self.startDemo(mixed=demo.mixed) + else: + self.endGame() + self.stopDemo() + self.quitGame(id, startdemo=1) + else: + self.stopDemo() + if 0 and self.app.debug: + # debug - only for testing winAnimation() + self.endGame() + self.winAnimation() + self.newGame() + else: + # game not finished yet + self.top.busyUpdate() + if self.demo: + after_idle(self.top, self.demoEvent) # schedule next move + + # play one demo move while in the demo event + def playOneDemoMove(self, demo): + if self.moves.index > 2000: + # we're probably looping because of some bug in the hint code + return 1 + sleep = demo.sleep + if self.app.debug: + if not self.top.winfo_ismapped(): + sleep = -1.0 + # first try to deal cards to the Waste (unless there was a forced move) + if not demo.hint or not demo.hint[6]: + if self._autoDeal(sound=0): + return 0 + # display a hint + h = self.showHint(demo.level, sleep, taken_hint=demo.hint) + demo.hint = h + if not h: + return 1 + # now actually play the hint + score, pos, ncards, from_stack, to_stack, text_color, forced_move = h + if ncards == 0: + # a deal-move + if self.dealCards() == 0: + return 1 + # do not let games like Klondike and Canfield deal forever + c = self.s.talon.getCard() + if c in demo.last_deal: + # We went through the whole Talon. Give up. + return 1 + # Note that `None' is a valid entry in last_deal[] + # (this means that all cards are on the Waste). + demo.last_deal.append(c) + elif from_stack == to_stack: + # a flip-move + from_stack.flipMove() + demo.last_deal = [] + else: + # a move-move + from_stack.moveMove(ncards, to_stack, frames=-1) + demo.last_deal = [] + ##print self.moves.index + return 0 + + def createDemoInfoText(self): + ## TODO - the text placement is not fully ok + return + if not self.demo or self.demo.info_text or self.preview: + return + tinfo = [ + ("sw", 8, self.height - 8), + ("se", self.width - 8, self.height - 8), + ("nw", 8, 8), + ("ne", self.width - 8, 8), + ] + ta = self.getDemoInfoTextAttr(tinfo) + if ta: + font = self.app.getFont("canvas_large") + self.demo.info_text = MfxCanvasText(self.canvas, ta[1], ta[2], anchor=ta[0], + font=font, text=self.getDemoInfoText()) + + def getDemoInfoText(self): + return self.gameinfo.short_name + + def getDemoInfoTextAttr(self, tinfo): + items1, items2 = [], [] + for s in self.allstacks: + if s.is_visible: + items1.append(s) + items1.extend(list(s.cards)) + if not s.cards and s.cap.max_accept > 0: + items2.append(s) + else: + items2.extend(list(s.cards)) + ti = self.__checkFreeSpaceForDemoInfoText(items1) + if ti < 0: + ti = self.__checkFreeSpaceForDemoInfoText(items2) + if ti < 0: + return None + return tinfo[ti] + + def __checkFreeSpaceForDemoInfoText(self, items): + CW, CH = self.app.images.CARDW, self.app.images.CARDH + # note: these are translated by (-CW/2, -CH/2) + x1, x2 = 3*CW/2, self.width - 5*CW/2 + y1, y2 = CH/2, self.height - 3*CH/2 + # + m = [1, 1, 1, 1] + for c in items: + cx, cy = c.x, c.y + if cy >= y2: + if cx <= x1: + m[0] = 0 + elif cx >= x2: + m[1] = 0 + elif cy <= y1: + if cx <= x1: + m[2] = 0 + elif cx >= x2: + m[3] = 0 + for mm in m: + if mm: + return mm + return -1 + + + def createDemoLogo(self): + if not self.app.gimages.demo: + return + if self.demo_logo or not self.app.opt.demo_logo: + return + if self.width <= 100 or self.height <= 100: + return + ##self.demo_logo = self.app.miscrandom.choice(self.app.gimages.demo) + n = self.random.initial_seed % len(self.app.gimages.demo) + self.demo_logo = self.app.gimages.demo[int(n)] + self.canvas.setTopImage(self.demo_logo) + + + # + # Handle moves (with move history for undo/redo) + # Actual move is handled in a subclass of AtomicMove. + # + # Note: + # All playing moves (user actions, demo games) must get routed + # to Stack.moveMove() because the stack may add important + # triggers to a move (most notably fillStack and updateModel). + # + # Only low-level game (Game.startGame, Game.dealCards, Game.fillStack) + # or stack methods (Stack.moveMove) should call the functions below + # directly. + # + + def startMoves(self): + self.moves = Struct( + state = self.S_PLAY, + history = [], # list of lists of atomic moves + index = 0, + current = [], # atomic moves for the current move + ) + # reset statistics + self.stats.undo_moves = 0 + self.stats.redo_moves = 0 + self.stats.player_moves = 0 + self.stats.demo_moves = 0 + self.stats.total_moves = 0 + self.stats.quickplay_moves = 0 + self.stats.goto_bookmark_moves = 0 + + def __storeMove(self, am): + if self.S_DEAL <= self.moves.state <= self.S_PLAY: + self.moves.current.append(am) + + # move type 1 + def moveMove(self, ncards, from_stack, to_stack, frames=-1, shadow=-1): + assert from_stack and to_stack and from_stack is not to_stack + assert 0 < ncards <= len(from_stack.cards) + am = AMoveMove(ncards, from_stack, to_stack, frames, shadow) + self.__storeMove(am) + am.do(self) + self.hints.list = None + + # move type 2 + def flipMove(self, stack): + assert stack + am = AFlipMove(stack) + self.__storeMove(am) + am.do(self) + self.hints.list = None + + # move type 3 + def turnStackMove(self, from_stack, to_stack, update_flags=1): + assert from_stack and to_stack and (from_stack is not to_stack) + assert len(to_stack.cards) == 0 + am = ATurnStackMove(from_stack, to_stack, update_flags=update_flags) + self.__storeMove(am) + am.do(self) + self.hints.list = None + + # move type 4 + def nextRoundMove(self, stack): + assert stack + am = ANextRoundMove(stack) + self.__storeMove(am) + am.do(self) + self.hints.list = None + + # move type 5 + def saveSeedMove(self): + am = ASaveSeedMove(self) + self.__storeMove(am) + am.do(self) + ##self.hints.list = None + + # move type 6 + def shuffleStackMove(self, stack): + assert stack + am = AShuffleStackMove(stack, self) + self.__storeMove(am) + am.do(self) + self.hints.list = None + + # move type 7 + def updateStackMove(self, stack, flags): + assert stack + am = AUpdateStackMove(stack, flags) + self.__storeMove(am) + am.do(self) + ##self.hints.list = None + + # move type 8 + def flipAllMove(self, stack): + assert stack + am = AFlipAllMove(self, stack) + self.__storeMove(am) + am.do(self) + self.hints.list = None + + # move type 9 + def saveStateMove(self, flags): + am = ASaveStateMove(self, flags) + self.__storeMove(am) + am.do(self) + ##self.hints.list = None + + + # Finish the current move. + def finishMove(self): + current, moves, stats = self.moves.current, self.moves, self.stats + if not current: + return 0 + # invalidate hints + self.hints.list = None + # update stats + if self.demo: + stats.demo_moves += 1 + if moves.index == 0: + stats.player_moves = 0 # clear all player moves + else: + stats.player_moves += 1 + if moves.index == 0: + stats.demo_moves = 0 # clear all demo moves + stats.total_moves += 1 + + # try to detect a redo move in order to keep our history + redo = 0 + if moves.index + 1 < len(moves.history): + l, m = len(current), moves.history[moves.index] + if l == len(m): + for i in range(l): + a1 = current[i] + a2 = m[i] + if a1.__class__ is not a2.__class__ or a1.cmpForRedo(a2) != 0: + break + else: + redo = 1 + # add current move to history (which is a list of lists) + if redo: + ###print "detected redo:", current + # overwrite existing entry because minor things like + # shadow/frames may have changed + moves.history[moves.index] = current + moves.index += 1 + else: + # resize (i.e. possibly shorten list from previous undos) + moves.history[moves.index : ] = [current] + moves.index += 1 + assert moves.index == len(moves.history) + + moves.current = [] + # update view + self.updateText() + self.updateStatus(moves=(moves.index, self.stats.total_moves)) + self.updateMenus() + self.updatePlayTime(do_after=0) + + return 1 + + + # + # undo/redo layer + # + + def undo(self): + assert self.canUndo() + assert self.moves.state == self.S_PLAY and len(self.moves.current) == 0 + assert 0 <= self.moves.index <= len(self.moves.history) + if self.moves.index == 0: + return + self.moves.index -= 1 + m = self.moves.history[self.moves.index] + m = m[:] + m.reverse() + self.moves.state = self.S_UNDO + for atomic_move in m: + atomic_move.undo(self) + self.moves.state = self.S_PLAY + self.stats.undo_moves += 1 + self.stats.total_moves += 1 + self.hints.list = None + self.updateText() + self.updateStatus(moves=(self.moves.index, self.stats.total_moves)) + self.updateMenus() + + def redo(self): + assert self.canRedo() + assert self.moves.state == self.S_PLAY and len(self.moves.current) == 0 + assert 0 <= self.moves.index <= len(self.moves.history) + if self.moves.index == len(self.moves.history): + return + m = self.moves.history[self.moves.index] + self.moves.index += 1 + if self.moves.index == len(self.moves.history): + mm = self.moves.current + else: + mm = self.moves.history[self.moves.index] + self.moves.state = self.S_REDO + for atomic_move in m: + atomic_move.redo(self) + self.moves.state = self.S_PLAY + self.stats.redo_moves += 1 + self.stats.total_moves += 1 + self.hints.list = None + self.updateText() + self.updateStatus(moves=(self.moves.index, self.stats.total_moves)) + self.updateMenus() + + + # + # subclass hooks + # + + def setState(self, state): + # restore saved vars (from undo/redo) + pass + def getState(self): + # save vars (for undo/redo) + return [] + + + # + # bookmarks + # + + def setBookmark(self, n, confirm=1): + self.finishMove() # just in case + if not self.canSetBookmark(): + return 0 + if confirm < 0: + confirm = self.app.opt.confirm + if confirm and self.gsaveinfo.bookmarks.get(n): + if not self.areYouSure(_("Set bookmark"), + _("Replace existing bookmark %d ?") % (n+1)): + return 0 + file = StringIO() + p = Pickler(file, 1) + try: + self._dumpGame(p, bookmark=2) + bm = (file.getvalue(), self.moves.index) + except: + pass + else: + self.gsaveinfo.bookmarks[n] = bm + return 1 + return 0 + + def gotoBookmark(self, n, confirm=-1, update_stats=1): + self.finishMove() # just in case + bm = self.gsaveinfo.bookmarks.get(n) + if not bm: + return + if confirm < 0: + confirm = self.app.opt.confirm + if confirm: + if not self.areYouSure(_("Goto bookmark"), + _("Goto bookmark %d ?") % (n+1)): + return + try: + s, moves_index = bm + self.setCursor(cursor=CURSOR_WATCH) + file = StringIO(s) + p = Unpickler(file) + game = self._undumpGame(p, self.app) + assert game.id == self.id + # save state for undoGotoBookmark + self.setBookmark(-1, confirm=0) + except: + del self.gsaveinfo.bookmarks[n] + self.setCursor(cursor=self.app.top_cursor) + else: + if update_stats: + self.stats.goto_bookmark_moves = self.stats.goto_bookmark_moves + 1 + self.gstats.goto_bookmark_moves = self.gstats.goto_bookmark_moves + 1 + self.restoreGame(game, reset=0) + destruct(game) + + def undoGotoBookmark(self): + self.gotoBookmark(-1, update_stats=0) + + + # + # load/save + # + + def loadGame(self, filename): + if self.changed(): + if not self.areYouSure(_("Open game")): return + self.finishMove() # just in case + game = None + self.setCursor(cursor=CURSOR_WATCH) + self.disableMenus() + try: + game = self._loadGame(filename, self.app) + game.gstats.holded = 0 + except AssertionError, ex: + self.updateMenus() + self.setCursor(cursor=self.app.top_cursor) + d = MfxDialog(self.top, title=_("Load game error"), bitmap="error", + text=_("""\ +Error while loading game. + +Probably the game file is damaged, +but this could also be a bug you might want to report.""")) + except (Exception, UnpicklingError), ex: + self.updateMenus() + self.setCursor(cursor=self.app.top_cursor) + d = MfxExceptionDialog(self.top, ex, title=_("Load game error"), + text=_("Error while loading game")) + except: + self.updateMenus() + self.setCursor(cursor=self.app.top_cursor) + d = MfxDialog(self.top, title=_("Load game error"), bitmap="error", + text=_("""\ +Internal error while loading game. + +Please report this bug.""")) + else: + self.filename = filename + game.filename = filename + # now start the new game + ##print game.__dict__ + if self.nextGameFlags(game.id) == 0: + self.endGame() + self.restoreGame(game) + destruct(game) + else: + self.endGame() + self.quitGame(game.id, loadedgame=game) + + + def saveGame(self, filename, binmode=1): + self.finishMove() # just in case + self.setCursor(cursor=CURSOR_WATCH) + try: + self._saveGame(filename, binmode) + except Exception, ex: + self.setCursor(cursor=self.app.top_cursor) + d = MfxExceptionDialog(self.top, ex, title=_("Save game error"), + text=_("Error while saving game")) + else: + self.filename = filename + self.setCursor(cursor=self.app.top_cursor) + + + # + # low level load/save + # + + def _loadGame(self, filename, app): + game = None + f = None + try: + f = open(filename, "rb") + p = Unpickler(f) + game = self._undumpGame(p, app) + game.gstats.loaded = game.gstats.loaded + 1 + finally: + if f: f.close() + return game + + def _getUndumpVersion(self, version_tuple): + if version_tuple > (4, 41): return 4 + if version_tuple > (4, 30): return 3 + if version_tuple > (4, 20): return 2 + if version_tuple > (3, 20): return 1 + if version_tuple >= (2, 99): return 0 + return -1 + + def _undumpGame(self, p, app): + self.updateTime() + # + err_txt = "Invalid or damaged %s save file" % PACKAGE + # + def pload(t=None, p=p): + obj = p.load() + if type(t) is types.TypeType: + assert type(obj) is t, err_txt + return obj + # + package = pload() + assert type(package) is types.StringType and package == PACKAGE, err_txt + version = pload() + assert type(version) is types.StringType and len(version) <= 20, err_txt + version_tuple = get_version_tuple(version) + v = self._getUndumpVersion(version_tuple) + assert v >= 0 and version_tuple <= VERSION_TUPLE, "Cannot load games saved with\n" + PACKAGE + " version " + version + game_version = 1 + bookmark = 0 + if v >= 2: + vt = pload() + assert type(vt) is types.TupleType and vt == version_tuple, err_txt + bookmark = pload() + assert type(bookmark) is types.IntType and 0 <= bookmark <= 2, "Incompatible savegame format" + game_version = pload() + assert type(game_version) is types.IntType and game_version > 0, err_txt + if v <= 3: + bookmark = 0 + # + id = pload() + assert type(id) is types.IntType and id > 0, err_txt + if not GI.PROTECTED_GAMES.has_key(id): + game = app.constructGame(id) + if game: + if not game.canLoadGame(version_tuple, game_version): + destruct(game) + game = None + assert game is not None, "Cannot load this game from version " + version + "\nas the game rules have changed\nin the current implementation." + game.version = version + game.version_tuple = version_tuple + # + #game.random = pload() + #assert isinstance(game.random, PysolRandom), err_txt + initial_seed = pload() + assert type(initial_seed) is types.LongType + if initial_seed <= 32000: + game.random = LCRandom31(initial_seed) + else: + game.random = PysolRandom(initial_seed) + state = pload() + game.random.setstate(state) + #if not hasattr(game.random, "origin"): + # game.random.origin = game.random.ORIGIN_UNKNOWN + game.loadinfo.stacks = [] + game.loadinfo.ncards = 0 + nstacks = pload() + #assert type(nstacks) is types.IntType and 1 <= nstacks <= 255, err_txt + assert type(nstacks) is types.IntType and 1 <= nstacks, err_txt + for i in range(nstacks): + stack = [] + ncards = pload() + assert type(ncards) is types.IntType and 0 <= ncards <= 1024, err_txt + for j in range(ncards): + card_id = pload(types.IntType) + face_up = pload(types.IntType) + stack.append((card_id, face_up)) + game.loadinfo.stacks.append(stack) + game.loadinfo.ncards = game.loadinfo.ncards + ncards + assert game.loadinfo.ncards == game.gameinfo.ncards, err_txt + game.loadinfo.talon_round = pload() + game.finished = pload() + if 0 <= bookmark <= 1: + if v >= 3: + saveinfo = pload() + assert isinstance(saveinfo, Struct), err_txt + game.saveinfo.__dict__.update(saveinfo.__dict__) + if v >= 4: + gsaveinfo = pload() + assert isinstance(gsaveinfo, Struct), err_txt + game.gsaveinfo.__dict__.update(gsaveinfo.__dict__) + elif v >= 1: + # not used + talon_base_cards = pload(types.ListType) + moves = pload() + assert isinstance(moves, Struct), err_txt + game.moves.__dict__.update(moves.__dict__) + if 0 <= bookmark <= 1: + gstats = pload() + assert isinstance(gstats, Struct), err_txt + game.gstats.__dict__.update(gstats.__dict__) + stats = pload() + assert isinstance(stats, Struct), err_txt + game.stats.__dict__.update(stats.__dict__) + game._loadGameHook(p) + if v >= 4: + dummy = pload(types.StringType) + assert dummy == "EOF", err_txt + if bookmark == 2: + # copy back all variables that are not saved + game.stats = self.stats + game.gstats = self.gstats + game.saveinfo = self.saveinfo + game.gsaveinfo = self.gsaveinfo + return game + + def _saveGame(self, filename, binmode=1): + f = None + try: + if not self.canSaveGame(): + raise Exception, "Cannot save this game." + f = open(filename, "wb") + p = Pickler(f, binmode) + self._dumpGame(p) + finally: + if f: f.close() + + def _dumpGame(self, p, bookmark=0): + self.updateTime() + assert 0 <= bookmark <= 2 + p.dump(PACKAGE) + p.dump(VERSION) + p.dump(VERSION_TUPLE) + p.dump(bookmark) + p.dump(self.GAME_VERSION) + p.dump(self.id) + # + #p.dump(self.random) + p.dump(self.random.initial_seed) + p.dump(self.random.getstate()) + # + p.dump(len(self.allstacks)) + for stack in self.allstacks: + p.dump(len(stack.cards)) + for card in stack.cards: + p.dump(card.id) + p.dump(card.face_up) + p.dump(self.s.talon.round) + p.dump(self.finished) + if 0 <= bookmark <= 1: + p.dump(self.saveinfo) + p.dump(self.gsaveinfo) + p.dump(self.moves) + if 0 <= bookmark <= 1: + if bookmark == 0: + self.gstats.saved = self.gstats.saved + 1 + p.dump(self.gstats) + p.dump(self.stats) + self._saveGameHook(p) + p.dump("EOF") + + # + # Playing time + # + + def startPlayTimer(self): + self.updateStatus(time=None) + self.stopPlayTimer() + self.play_timer = after(self.top, PLAY_TIME_TIMEOUT, self.updatePlayTime) + + def stopPlayTimer(self): + if hasattr(self, 'play_timer') and self.play_timer: + after_cancel(self.play_timer) + self.play_timer = None + self.updatePlayTime(do_after=0) + + def updatePlayTime(self, do_after=1): + if not self.top: return + if self.pause or self.finished: return + if do_after: + self.play_timer = after(self.top, PLAY_TIME_TIMEOUT, self.updatePlayTime) + d = time.time() - self.stats.update_time + self.stats.elapsed_time + self.updateStatus(time=format_time(d)) + + + # + # Pause + # + + def doPause(self): + if self.finished: + return + if self.demo: + self.stopDemo() + if not self.pause: + self.updateTime() + self.pause = not self.pause + if self.pause: + ##self.updateTime() + self.canvas.hideAllItems() + n = self.random.initial_seed % len(self.app.gimages.pause) + self.pause_logo = self.app.gimages.pause[int(n)] + self.canvas.setTopImage(self.pause_logo) + else: + self.stats.update_time = time.time() + self.updatePlayTime() + self.canvas.setTopImage(None) + self.pause_logo = None + self.canvas.showAllItems() + + # + # Help + # + + def showHelp(self, *args): + if self.preview: + return + kw = dict([(args[i], args[i+1]) for i in range(0, len(args), 2)]) + if not kw: + kw = {'info': '', 'help': ''} + if kw.has_key('info') and self.app.opt.statusbar and self.app.opt.num_cards: + self.app.statusbar.updateText(info=kw['info']) + if kw.has_key('help') and self.app.opt.helpbar: + self.app.helpbar.updateText(info=kw['help']) + + + # + # subclass hooks + # + + def _restoreGameHook(self, game): + pass + + def _loadGameHook(self, p): + pass + + def _saveGameHook(self, p): + pass + diff --git a/pysollib/gamedb.py b/pysollib/gamedb.py new file mode 100644 index 0000000000..d889c3ffd7 --- /dev/null +++ b/pysollib/gamedb.py @@ -0,0 +1,581 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, imp, os, types + +# PySol imports +from mfxutil import Struct, latin1_to_ascii +from resource import CSI + +gettext = _ +n_ = lambda x: x + +# /*********************************************************************** +# // constants +# ************************************************************************/ + +# GameInfo constants +class GI: + # game category - these *must* match the cardset CSI.TYPE_xxx + GC_FRENCH = CSI.TYPE_FRENCH + GC_HANAFUDA = CSI.TYPE_HANAFUDA + GC_TAROCK = CSI.TYPE_TAROCK + GC_MAHJONGG = CSI.TYPE_MAHJONGG + GC_HEXADECK = CSI.TYPE_HEXADECK + GC_MUGHAL_GANJIFA = CSI.TYPE_MUGHAL_GANJIFA + GC_NAVAGRAHA_GANJIFA = CSI.TYPE_NAVAGRAHA_GANJIFA + GC_DASHAVATARA_GANJIFA = CSI.TYPE_DASHAVATARA_GANJIFA + GC_TRUMP_ONLY = CSI.TYPE_TRUMP_ONLY + + # game type + GT_1DECK_TYPE = 0 + GT_2DECK_TYPE = 1 + GT_3DECK_TYPE = 2 + GT_4DECK_TYPE = 3 + GT_BAKERS_DOZEN = 4 + GT_BELEAGUERED_CASTLE = 5 + GT_CANFIELD = 6 + GT_DASHAVATARA_GANJIFA = 7 + GT_FAN_TYPE = 8 + GT_FORTY_THIEVES = 9 + GT_FREECELL = 10 + GT_GOLF = 11 + GT_GYPSY = 12 + GT_HANAFUDA = 13 + GT_HEXADECK = 14 + GT_KLONDIKE = 15 + GT_MAHJONGG = 16 + GT_MATRIX = 17 + GT_MEMORY = 18 + GT_MONTANA = 19 + GT_MUGHAL_GANJIFA = 20 + GT_NAPOLEON = 21 + GT_NAVAGRAHA_GANJIFA = 22 + GT_NUMERICA = 23 + GT_PAIRING_TYPE = 24 + GT_POKER_TYPE = 25 + GT_PUZZLE_TYPE = 26 + GT_RAGLAN = 27 + GT_ROW_TYPE = 28 + GT_SIMPLE_TYPE = 29 + GT_SPIDER = 30 + GT_TAROCK = 31 + GT_TERRACE = 32 + GT_YUKON = 33 + GT_SHISEN_SHO = 34 + # extra flags + GT_BETA = 1 << 12 # beta version of game driver + GT_CHILDREN = 1 << 13 # *not used* + GT_CONTRIB = 1 << 14 # contributed games under the GNU GPL + GT_HIDDEN = 1 << 15 # not visible in menus, but games can be loaded + GT_OPEN = 1 << 16 + GT_ORIGINAL = 1 << 17 + GT_POPULAR = 1 << 18 # *not used* + GT_RELAXED = 1 << 19 + GT_SCORE = 1 << 20 # game has some type of scoring + GT_SEPARATE_DECKS = 1 << 21 + GT_XORIGINAL = 1 << 22 # original games by other people, not playable + TYPE_NAMES = { + GT_BAKERS_DOZEN: n_("Baker's Dozen"), + GT_BELEAGUERED_CASTLE: n_("Beleaguered Castle"), + GT_CANFIELD: n_("Canfield"), + GT_FAN_TYPE: n_("Fan"), + GT_FORTY_THIEVES: n_("Forty Thieves"), + GT_FREECELL: n_("FreeCell"), + GT_GOLF: n_("Golf"), + GT_GYPSY: n_("Gypsy"), + GT_KLONDIKE: n_("Klondike"), + GT_MONTANA: n_("Montana"), + GT_NAPOLEON: n_("Napoleon"), + GT_NUMERICA: n_("Numerica"), + GT_PAIRING_TYPE: n_("Pairing"), + GT_RAGLAN: n_("Raglan"), + GT_SIMPLE_TYPE: n_("Simple games"), + GT_SPIDER: n_("Spider"), + GT_TERRACE: n_("Terrace"), + GT_YUKON: n_("Yukon"), + GT_1DECK_TYPE: n_("One-Deck games"), + GT_2DECK_TYPE: n_("Two-Deck games"), + GT_3DECK_TYPE: n_("Three-Deck games"), + GT_4DECK_TYPE: n_("Four-Deck games"), + } + +## SELECT_GAME_BY_TYPE = [] +## for gt, name in TYPE_NAMES.items(): +## if not name.endswith('games'): +## name = name+n_(' type') +## SELECT_GAME_BY_TYPE.append( +## (name, lambda gi, gt=gt: gi.si.game_type == gt)) +## SELECT_GAME_BY_TYPE = tuple(SELECT_GAME_BY_TYPE) + + SELECT_GAME_BY_TYPE = ( + (n_("Baker's Dozen type"),lambda gi, gt=GT_BAKERS_DOZEN: gi.si.game_type == gt), + (n_("Beleaguered Castle type"),lambda gi, gt=GT_BELEAGUERED_CASTLE: gi.si.game_type == gt), + (n_("Canfield type"), lambda gi, gt=GT_CANFIELD: gi.si.game_type == gt), + (n_("Fan type"), lambda gi, gt=GT_FAN_TYPE: gi.si.game_type == gt), + (n_("Forty Thieves type"),lambda gi, gt=GT_FORTY_THIEVES: gi.si.game_type == gt), + (n_("FreeCell type"), lambda gi, gt=GT_FREECELL: gi.si.game_type == gt), + (n_("Golf type"), lambda gi, gt=GT_GOLF: gi.si.game_type == gt), + (n_("Gypsy type"), lambda gi, gt=GT_GYPSY: gi.si.game_type == gt), + (n_("Klondike type"), lambda gi, gt=GT_KLONDIKE: gi.si.game_type == gt), + (n_("Montana type"), lambda gi, gt=GT_MONTANA: gi.si.game_type == gt), + (n_("Napoleon type"), lambda gi, gt=GT_NAPOLEON: gi.si.game_type == gt), + (n_("Numerica type"), lambda gi, gt=GT_NUMERICA: gi.si.game_type == gt), + (n_("Pairing type"), lambda gi, gt=GT_PAIRING_TYPE: gi.si.game_type == gt), + (n_("Raglan type"), lambda gi, gt=GT_RAGLAN: gi.si.game_type == gt), + (n_("Simple games"), lambda gi, gt=GT_SIMPLE_TYPE: gi.si.game_type == gt), + (n_("Spider type"), lambda gi, gt=GT_SPIDER: gi.si.game_type == gt), + (n_("Terrace type"), lambda gi, gt=GT_TERRACE: gi.si.game_type == gt), + (n_("Yukon type"), lambda gi, gt=GT_YUKON: gi.si.game_type == gt), + (n_("One-Deck games"),lambda gi, gt=GT_1DECK_TYPE: gi.si.game_type == gt), + (n_("Two-Deck games"),lambda gi, gt=GT_2DECK_TYPE: gi.si.game_type == gt), + (n_("Three-Deck games"),lambda gi, gt=GT_3DECK_TYPE: gi.si.game_type == gt), + (n_("Four-Deck games"),lambda gi, gt=GT_4DECK_TYPE: gi.si.game_type == gt), + ) + + + +## SELECT_SPECIAL_GAME_BY_TYPE = ( +## ("Dashavatara Ganjifa type", lambda gi, gt=GT_DASHAVATARA_GANJIFA: gi.si.game_type == gt), +## ("Ganjifa type", lambda gi, gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA,): gi.si.game_type in gt), +## ("Hanafuda type", lambda gi, gt=GT_HANAFUDA: gi.si.game_type == gt), +## ("Hex A Deck type", lambda gi, gt=GT_HEXADECK: gi.si.game_type == gt), +## ("Mahjongg type", lambda gi, gt=GT_MAHJONGG: gi.si.game_type == gt), +## ("Matrix type", lambda gi, gt=GT_MATRIX: gi.si.game_type == gt), +## ("Mughal Ganjifa type", lambda gi, gt=GT_MUGHAL_GANJIFA: gi.si.game_type == gt), +## ("Navagraha Ganjifa type", lambda gi, gt=GT_NAVAGRAHA_GANJIFA: gi.si.game_type == gt), +## ("Memory type", lambda gi, gt=GT_MEMORY: gi.si.game_type == gt), +## ("Poker type", lambda gi, gt=GT_POKER_TYPE: gi.si.game_type == gt), +## ("Puzzle type", lambda gi, gt=GT_PUZZLE_TYPE: gi.si.game_type == gt), +## ("Tarock type", lambda gi, gt=GT_TAROCK: gi.si.game_type == gt), +## ) + + SELECT_ORIGINAL_GAME_BY_TYPE = ( + (n_("French type"), lambda gi, gf=GT_ORIGINAL, gt=(GT_HANAFUDA, GT_HEXADECK, GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA, GT_TAROCK,): gi.si.game_flags & gf and gi.si.game_type not in gt), + (n_("Ganjifa type"), lambda gi, gf=GT_ORIGINAL, gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA,): gi.si.game_flags & gf and gi.si.game_type in gt), + (n_("Hanafuda type"), lambda gi, gf=GT_ORIGINAL, gt=GT_HANAFUDA: gi.si.game_flags & gf and gi.si.game_type == gt), + (n_("Hex A Deck type"), lambda gi, gf=GT_ORIGINAL, gt=GT_HEXADECK: gi.si.game_flags & gf and gi.si.game_type == gt), + (n_("Tarock type"), lambda gi, gf=GT_ORIGINAL, gt=GT_TAROCK: gi.si.game_flags & gf and gi.si.game_type == gt), + ) + + SELECT_CONTRIB_GAME_BY_TYPE = ( + (n_("French type"), lambda gi, gf=GT_CONTRIB, gt=(GT_HANAFUDA, GT_HEXADECK, GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA, GT_TAROCK,): gi.si.game_flags & gf and gi.si.game_type not in gt), + (n_("Ganjifa type"), lambda gi, gf=GT_CONTRIB, gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA,): gi.si.game_flags & gf and gi.si.game_type in gt), + (n_("Hanafuda type"), lambda gi, gf=GT_CONTRIB, gt=GT_HANAFUDA: gi.si.game_flags & gf and gi.si.game_type == gt), + (n_("Hex A Deck type"), lambda gi, gf=GT_CONTRIB, gt=GT_HEXADECK: gi.si.game_flags & gf and gi.si.game_type == gt), + (n_("Tarock type"), lambda gi, gf=GT_CONTRIB, gt=GT_TAROCK: gi.si.game_flags & gf and gi.si.game_type == gt), + ) + + # ----- + SELECT_ORIENTAL_GAME_BY_TYPE = ( + (n_("Dashavatara Ganjifa type"), lambda gi, gt=GT_DASHAVATARA_GANJIFA: gi.si.game_type == gt), + (n_("Ganjifa type"), lambda gi, gt=(GT_MUGHAL_GANJIFA, GT_NAVAGRAHA_GANJIFA, GT_DASHAVATARA_GANJIFA,): gi.si.game_type in gt), + (n_("Hanafuda type"), lambda gi, gt=GT_HANAFUDA: gi.si.game_type == gt), + (n_("Mughal Ganjifa type"), lambda gi, gt=GT_MUGHAL_GANJIFA: gi.si.game_type == gt), + (n_("Navagraha Ganjifa type"), lambda gi, gt=GT_NAVAGRAHA_GANJIFA: gi.si.game_type == gt), + ) + + SELECT_SPECIAL_GAME_BY_TYPE = ( + (n_("Shisen-Sho"), lambda gi, gt=GT_SHISEN_SHO: gi.si.game_type == gt), + (n_("Hex A Deck type"), lambda gi, gt=GT_HEXADECK: gi.si.game_type == gt), + (n_("Matrix type"), lambda gi, gt=GT_MATRIX: gi.si.game_type == gt), + (n_("Memory type"), lambda gi, gt=GT_MEMORY: gi.si.game_type == gt), + (n_("Poker type"), lambda gi, gt=GT_POKER_TYPE: gi.si.game_type == gt), + (n_("Puzzle type"), lambda gi, gt=GT_PUZZLE_TYPE: gi.si.game_type == gt), + (n_("Tarock type"), lambda gi, gt=GT_TAROCK: gi.si.game_type == gt), + ) + + + + # These obsolete gameids have been used in previous versions of + # PySol and are no longer supported because of internal changes + # (mainly rule changes). The game has been assigned a new id. + PROTECTED_GAMES = { + 22: 106, # Double Canfield + 32: 901, # La Belle Lucie (Midnight Oil) + 52: 903, # Aces Up + 72: 115, # Little Forty + 75: 126, # Red and Black + 82: 901, # La Belle Lucie (Midnight Oil) +## 155: 5034, # Mahjongg - Flying Dragon +## 156: 5035, # Mahjongg - Fortress Towers + 262: 105, # Canfield + 902: 88, # Trefoil + 904: 68, # Lexington Harp + } + + GAMES_BY_COMPATIBILITY = ( + # Atari ST Patience game v2.13 (we have 10 out of 10 games) + ("Atari ST Patience", (1, 3, 4, 7, 12, 14, 15, 16, 17, 39,)), + + ## Gnome AisleRiot 1.0.51 (we have 28 out of 32 games) + ## still missing: Camelot, Clock, Thieves, Thirteen + ##("Gnome AisleRiot 1.0.51", ( + ## 2, 8, 11, 19, 27, 29, 33, 34, 35, 40, + ## 41, 42, 43, 58, 59, 92, 93, 94, 95, 96, + ## 100, 105, 111, 112, 113, 130, 200, 201, + ##)), + ## Gnome AisleRiot 1.4.0.1 (we have XX out of XX games) + ##("Gnome AisleRiot", ( + ## 1, 2, 8, 11, 19, 27, 29, 33, 34, 35, 40, + ## 41, 42, 43, 58, 59, 92, 93, 94, 95, 96, + ## 100, 105, 111, 112, 113, 130, 200, 201, + ##)), + # Gnome AisleRiot 2.2.0 (we have 57 out of 73 games) + # still missing: + # Clock, Cover, Diamond mine, Gay gordons, Helsinki + # Isabel, Labyrinth (!), Quatorze, Scuffle, Thieves, + # Treize, Valentine, Yeld, ??? + ("Gnome AisleRiot", ( + 1, 2, 8, 9, 11, 12, 19, 24, 27, 29, 31, 33, 34, 35, 36, 40, + 41, 42, 43, 45, 48, 58, 59, 67, 89, 91, 92, 93, 94, 95, 96, + 100, 105, 111, 112, 113, 130, 139, 144, 146, 147, 148, 200, + 201, 206, 224, 225, 229, 230, 233, 257, 258, 280, 281, 282, + 283, 284, + )), + + ## KDE Patience 0.7.3 from KDE 1.1.2 (we have 6 out of 9 games) + ##("KDE Patience 0.7.3", (2, 7, 8, 18, 256, 903,)), + ## KDE Patience 2.0 from KDE 2.1.2 (we have 11 out of 13 games) + ##("KDE Patience", (1, 2, 7, 8, 18, 19, 23, 50, 256, 261, 903,)), + ## KDE Patience 2.0 from KDE 2.2beta1 (we have 12 out of 14 games) + ##("KDE Patience", (1, 2, 7, 8, 18, 19, 23, 36, 50, 256, 261, 903,)), + # KDE Patience 2.0 from KDE 3.1.1 (we have 15 out of 15 games) + ("KDE Patience", (1, 2, 7, 8, 18, 19, 23, 36, 50, + 256, 261, 277, 278, 279, 903,)), + + # xpat2 1.06 (we have 14 out of 16 games) + # still missing: Michael's Fantasy, modCanfield + ("xpat2", ( + 1, 2, 8, 9, 11, 31, 54, 63, 89, 105, 901, 256, 345, 903, + )), + ) + + GAMES_BY_PYSOL_VERSION = ( + ("1.00", (1, 2, 3, 4)), + ("1.01", (5, 6)), + ("1.02", (7, 8, 9)), + ("1.03", (10, 11, 12, 13)), + ("1.10", (14,)), + ("1.11", (15, 16, 17)), + ("2.00", (256, 257)), + ("2.01", (258, 259, 260, 261)), + ("2.02", (105,)), + ("2.90", (18, 19, 20, 21, 106, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 901, 33, 34, 35, 36)), + ("2.99", (37,)), + ("3.00", (38, 39, + 40, 41, 42, 43, 45, 46, 47, 48, 49, + 50, 51,903, 53, 54, 55, 56, 57, 58, 59, + 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71,115, 73, 74,126, 76, 77, 78, 79, + 80, 81, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, + 100, 101, 102, 103, 104, 107, 108,)), + ("3.10", (109, 110, 111, 112, 113, 114, 116, 117, 118, 119, + 120,-121,-122, 123, 124, 125, 127)), + ("3.20", (128, 129, 130, 131, 132, 133, 134, 135, 136, 137, + 138, 139, 140, 141, 142, + 12345, 12346, 12347, 12348, 12349, 12350, 12351, 12352)), + ("3.21", (143, 144)), + ("3.30", (145, 146, 147, 148, 149, 150, 151)), + ("3.40", (152, 153, 154)), + ("4.00", ( 157, 158, 159, 160, 161, 162, 163, 164)), + ("4.20", (165, 166, 167, 168, 169, 170, 171, 172, 173, 174, + 175, 176, 177, 178)), + ("4.30", (179, 180, 181, 182, 183, 184)), + ("4.41", (185, 186,-187,-188,-189,-190,-191,-192, 193,-194, + 195, 196,-197,-198, 199)), + ("4.60", (200, 201, 202, 203, 204, 205, + 206, 207, 208, 209, + 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, + 230, 231, 232, 233, 234, 235, 236)), +## ## + tuple(range(353, 370)), +## ), + ("4.70", (237,)), + ) + + # deprecated - the correct way is to or a GI.GT_XXX flag + # in the registerGame() call + _CHILDREN_GAMES = [16, 33, 55, 90, 91, 96, 97, 176, 903,] + + _OPEN_GAMES = [] + #_OPEN_GAMES = [ 5, 6, 8, 9, 26, 31, 45, 46, 50, 53, 63, 64, 77, 85, 86, + # 96, 116, 117, 118, 258, ] + + _POPULAR_GAMES = [ + 1, # Gypsy + 2, # Klondike + 7, # Picture Galary + 8, # FreeCell + 11, # Spider + 12, # Braid + 13, # Forty Thieves + 14, # Ground for a Divorce + 19, # Yukon + 31, # Baker's Dozen + 36, # Golf + 38, # Pyramid + 105, # Canfield + 158, # Imperial Trumps + 279, # Kings + 903, # Ace Up + 5034, # Mahjongg Flying Dragon + 5401, # Mahjongg Taipei + 12345, # Oonsoo + ] + + +# /*********************************************************************** +# // core games database +# ************************************************************************/ + +class GameInfoException(Exception): + pass + + +class GameInfo(Struct): + def __init__(self, id, gameclass, name, + game_type, decks, redeals, + # keyword arguments: + si={}, category=0, short_name=None, altnames=(), + suits=range(4), ranks=range(13), trumps=(), + rules_filename=None): + def to_unicode(s): + if not type(s) is unicode: + return unicode(s, 'utf-8') + return s + # + ncards = decks * (len(suits) * len(ranks) + len(trumps)) + game_flags = game_type & ~1023 + game_type = game_type & 1023 + if os.name == "mac": + name = latin1_to_ascii(name) + name = to_unicode(name) + if not short_name: + short_name = name + short_name = to_unicode(short_name) + if type(altnames) in types.StringTypes: + altnames = (altnames,) + altnames = [to_unicode(n) for n in altnames] + # + if not (1 <= category <= 9): + if game_type == GI.GT_HANAFUDA: + category = GI.GC_HANAFUDA + elif game_type == GI.GT_TAROCK: + category = GI.GC_TAROCK + elif game_type == GI.GT_MAHJONGG: + category = GI.GC_MAHJONGG + elif game_type == GI.GT_HEXADECK: + category = GI.GC_HEXADECK + elif game_type == GI.GT_MUGHAL_GANJIFA: + category = GI.GC_MUGHAL_GANJIFA + elif game_type == GI.GT_NAVAGRAHA_GANJIFA: + category = GI.GC_NAVAGRAHA_GANJIFA + elif game_type == GI.GT_DASHAVATARA_GANJIFA: + category = GI.GC_DASHAVATARA_GANJIFA + else: + category = GI.GC_FRENCH + # + if not (1 <= id <= 999999): + raise GameInfoException, name + ": invalid game ID " + str(id) + if category == GI.GC_MAHJONGG: + if decks%4: + raise GameInfoException, name + ": invalid number of decks " + str(id) + else: + if not (1 <= decks <= 4): + raise GameInfoException, name + ": invalid number of decks " + str(id) + ##if not name or not (2 <= len(short_name) <= 30): + ## raise GameInfoException, name + ": invalid game name" + if not name: + raise GameInfoException, name + ": invalid game name" + if GI.PROTECTED_GAMES.get(id): + raise GameInfoException, name + ": protected game ID " + str(id) + # + for f, l in ((GI.GT_CHILDREN, GI._CHILDREN_GAMES), + (GI.GT_OPEN, GI._OPEN_GAMES), + (GI.GT_POPULAR, GI._POPULAR_GAMES)): + if (game_flags & f) and (id not in l): + l.append(id) + elif not (game_flags & f) and (id in l): + game_flags = game_flags | f + # si is the SelectionInfo struct that will be queried by + # the "select game" dialogs. It can be freely modified. + gi_si = Struct(game_type=game_type, game_flags=game_flags, + decks=decks, redeals=redeals, ncards=ncards) + gi_si.update(si) + # + Struct.__init__(self, id=id, gameclass=gameclass, + name=name, short_name=short_name, + altnames=tuple(altnames), + decks=decks, redeals=redeals, ncards=ncards, + category=category, + suits=tuple(suits), ranks=tuple(ranks), trumps=tuple(trumps), + si=gi_si, rules_filename=rules_filename, plugin=0) + + +class GameManager: + def __init__(self): + self.__selected_key = -1 + self.__games = {} + self.__gamenames = {} + self.__games_by_id = None + self.__games_by_name = None + self.__games_by_short_name = None + self.__games_by_altname = None + self.__all_games = {} # includes hidden games + self.__all_gamenames = {} # includes hidden games + self.loading_plugin = 0 + self.registered_game_types = {} + + def getSelected(self): + return self.__selected_key + + def setSelected(self, gameid): + assert self.__all_games.has_key(gameid) + self.__selected_key = gameid + + def get(self, key): + return self.__all_games.get(key) + + def register(self, gi): + ##print gi.id, gi.short_name + if not isinstance(gi, GameInfo): + raise GameInfoException, "wrong GameInfo class" + gi.plugin = self.loading_plugin + if self.__all_games.has_key(gi.id): + raise GameInfoException, "duplicate game ID " + str(gi.id) + if self.__all_gamenames.has_key(gi.name): + raise GameInfoException, "duplicate game name " + str(gi.id) + ": " + gi.name + if 1: + for id, game in self.__all_games.items(): + if gi.gameclass is game.gameclass: + raise GameInfoException, "duplicate game class " + str(gi.id) + for n in gi.altnames: + if self.__all_gamenames.has_key(n): + raise GameInfoException, "duplicate altgame name " + str(gi.id) + ": " + n + if 0 and gi.si.game_flags & GI.GT_XORIGINAL: + return + if 0 and (206 <= gi.id <= 236): + ##print gi.id + return + ##print gi.id, gi.name + self.__all_games[gi.id] = gi + self.__all_gamenames[gi.name] = gi + for n in gi.altnames: + self.__all_gamenames[n] = gi + if not (gi.si.game_flags & GI.GT_HIDDEN): + self.__games[gi.id] = gi + self.__gamenames[gi.name] = gi + for n in gi.altnames: + self.__gamenames[n] = gi + # invalidate sorted lists + self.__games_by_id = None + self.__games_by_name = None + # update registry + k = gi.si.game_type + self.registered_game_types[k] = self.registered_game_types.get(k, 0) + 1 + + + # + # access games database - we do not expose hidden games + # + + def getAllGames(self): return self.__all_games + + def getGamesIdSortedById(self): + if self.__games_by_id is None: + l = self.__games.keys() + l.sort() + self.__games_by_id = tuple(l) + return self.__games_by_id + + def getGamesIdSortedByName(self): + if self.__games_by_name is None: + l1, l2, l3 = [], [], [] + for id, gi in self.__games.items(): + #name = latin1_to_ascii(gi.name).lower() + name = gettext(gi.name).lower() + l1.append((name, id)) + if gi.name != gi.short_name: + #name = latin1_to_ascii(gi.short_name).lower() + name = gettext(gi.short_name).lower() + l2.append((name, id)) + for n in gi.altnames: + #name = latin1_to_ascii(n).lower() + name = gettext(n).lower() + l3.append((name, id, n)) + l1.sort() + l2.sort() + l3.sort() + self.__games_by_name = tuple(map(lambda item: item[1], l1)) + self.__games_by_short_name = tuple(map(lambda item: item[1], l2)) + self.__games_by_altname = tuple(map(lambda item: item[1:], l3)) + return self.__games_by_name + + def getGamesIdSortedByShortName(self): + if self.__games_by_name is None: + self.getGamesIdSortedByName() + return self.__games_by_short_name + + # note: this contains tuples as entries + def getGamesTuplesSortedByAlternateName(self): + if self.__games_by_name is None: + self.getGamesIdSortedByName() + return self.__games_by_altname + + +# /*********************************************************************** +# // +# ************************************************************************/ + +# the global game database (the single instance of class GameManager) +GAME_DB = GameManager() + + +def registerGame(gameinfo): + GAME_DB.register(gameinfo) + return gameinfo + + +def loadGame(modname, filename, plugin=1): + ##print "load game", modname, filename + GAME_DB.loading_plugin = plugin + module = imp.load_source(modname, filename) + ##execfile(filename, globals(), globals()) + diff --git a/pysollib/games/__init__.py b/pysollib/games/__init__.py new file mode 100644 index 0000000000..e1de4c4009 --- /dev/null +++ b/pysollib/games/__init__.py @@ -0,0 +1,59 @@ +import acesup +import algerian +import auldlangsyne +import bakersdozen +import bakersgame +import beleagueredcastle +import bisley +import braid +import bristol +import buffalobill +import calculation +import camelot +import canfield +import capricieuse +import curdsandwhey +import dieboesesieben +import diplomat +import doublets +import eiffeltower +import fan +import fortythieves +import freecell +import glenwood +import golf +import grandfathersclock +import gypsy +import harp +import headsandtails +import katzenschwanz +import klondike +import labyrinth +import matriarchy +import montana +import montecarlo +import napoleon +import needle +import numerica +import osmosis +import parallels +import pasdedeux +import picturegallery +import pileon +import poker +import pushpin +import pyramid +import royalcotillion +import royaleast +import siebenbisas +import simplex +import spider +import sthelena +import sultan +import takeaway +import terrace +import tournament +import unionsquare +import wavemotion +import windmill +import yukon diff --git a/pysollib/games/acesup.py b/pysollib/games/acesup.py new file mode 100644 index 0000000000..5df310cfd1 --- /dev/null +++ b/pysollib/games/acesup.py @@ -0,0 +1,265 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Aces Up +# ************************************************************************/ + +class AcesUp_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + c = cards[0] + for s in self.game.s.rows: + if s is not from_stack and s.cards and s.cards[-1].suit == c.suit: + if s.cards[-1].rank > c.rank or s.cards[-1].rank == ACE: + # found a higher rank or an Ace on the row stacks + return c.rank != ACE + return 0 + + +class AcesUp_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + return len(self.cards) == 0 + + clickHandler = BasicRowStack.doubleclickHandler + + +class AcesUp(Game): + Talon_Class = DealRowTalonStack + RowStack_Class = StackWrapper(AcesUp_RowStack, max_accept=1) + + # + # game layout + # + + def createGame(self, rows=4, **layout): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + (rows+3)*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, "ss") + x = x + 3*l.XS/2 + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + x = x + l.XS/2 + stack = AcesUp_Foundation(x, y, self, ANY_SUIT, max_move=0, + dir=0, base_rank=ANY_RANK, max_cards=48) + l.createText(stack, "ss") + s.foundations.append(stack) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + def isGameWon(self): + if len(self.s.foundations[0].cards) != 48: + return 0 + for s in self.s.rows: + if len(s.cards) != 1 or s.cards[0].rank != ACE: + return 0 + return 1 + + def getAutoStacks(self, event=None): + if event is None: + # disable auto drop - this would ruin the whole gameplay + return (self.sg.dropstacks, (), self.sg.dropstacks) + else: + # rightclickHandler + return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks) + + +# /*********************************************************************** +# // Fortunes +# ************************************************************************/ + +class Fortunes(AcesUp): + RowStack_Class = StackWrapper(AcesUp_RowStack, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS) + + +# /*********************************************************************** +# // Russian Aces +# ************************************************************************/ + +class RussianAces_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + rows = filter(lambda s: not s.cards, self.game.s.rows) + if not rows: + rows = self.game.s.rows + return self.dealRowAvail(rows=rows, sound=sound) + + +class RussianAces(AcesUp): + Talon_Class = RussianAces_Talon + + +# /*********************************************************************** +# // Perpetual Motion +# ************************************************************************/ + +class PerpetualMotion_Talon(DealRowTalonStack): + def canDealCards(self): + ## FIXME: this is to avoid loops in the demo + if self.game.demo and self.game.moves.index >= 500: + return 0 + return not self.game.isGameWon() + + def dealCards(self, sound=0): + if self.cards: + return DealRowTalonStack.dealCards(self, sound=sound) + game, num_cards = self.game, len(self.cards) + rows = list(game.s.rows)[:] + rows.reverse() + for r in rows: + while r.cards: + num_cards = num_cards + 1 + game.moveMove(1, r, self, frames=4) + if self.cards[-1].face_up: + game.flipMove(self) + assert len(self.cards) == num_cards + return DealRowTalonStack.dealCards(self, sound=sound) + + +class PerpetualMotion_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + return isRankSequence(cards, dir=0) + + +class PerpetualMotion_RowStack(RK_RowStack): + def canDropCards(self, stacks): + pile = self.getPile() + if not pile or len(pile) != 4: + return (None, 0) + for s in stacks: + if s is not self and s.acceptsCards(self, pile): + return (s, 4) + return (None, 0) + + +class PerpetualMotion(Game): + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 7*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + s.talon = PerpetualMotion_Talon(x, y, self, max_rounds=-1) + l.createText(s.talon, "ss") + x = x + 3*l.XS/2 + for i in range(4): + s.rows.append(PerpetualMotion_RowStack(x, y, self, dir=0, base_rank=NO_RANK)) + x = x + l.XS + x = l.XM + 6*l.XS + stack = PerpetualMotion_Foundation(x, y, self, ANY_SUIT, base_rank=ANY_RANK, + max_cards=52, max_move=0, + min_accept=4, max_accept=4) + l.createText(stack, "ss") + s.foundations.append(stack) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class AcesUp5(AcesUp): + + def createGame(self): + AcesUp.createGame(self, rows=5) + + def isGameWon(self): + return len(self.s.foundations[0].cards) == 48 + + +# register the game +registerGame(GameInfo(903, AcesUp, "Aces Up", # was: 52 + GI.GT_1DECK_TYPE, 1, 0, + altnames=("Aces High", "Drivel") )) +registerGame(GameInfo(206, Fortunes, "Fortunes", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(213, RussianAces, "Russian Aces", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(130, PerpetualMotion, "Perpetual Motion", + GI.GT_1DECK_TYPE, 1, -1, + altnames="First Law")) +registerGame(GameInfo(353, AcesUp5, "Aces Up 5", + GI.GT_1DECK_TYPE, 1, 0)) diff --git a/pysollib/games/algerian.py b/pysollib/games/algerian.py new file mode 100644 index 0000000000..a7e209619d --- /dev/null +++ b/pysollib/games/algerian.py @@ -0,0 +1,169 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Carthage +# ************************************************************************/ + +class Carthage_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + if sound: + self.game.startDealSample() + if len(self.cards) == len(self.game.s.rows): + n = self.dealRowAvail(rows=self.game.s.rows, sound=0) + else: + n = self.dealRowAvail(rows=self.game.s.reserves, sound=0) + n += self.dealRowAvail(rows=self.game.s.reserves, sound=0) + if sound: + self.game.stopSamples() + return n + + +class Carthage(Game): + + Hint_Class = CautiousDefaultHint + Talon_Class = Carthage_Talon + Foundation_Classes = (SS_FoundationStack, + SS_FoundationStack) + RowStack_Class = StackWrapper(SS_RowStack, max_move=1) + + # + # game layout + # + + def createGame(self, rows=8, reserves=6, playcards=12): + # create layout + l, s = Layout(self), self.s + + # set window + decks = self.gameinfo.decks + foundations = decks*4 + max_rows = max(foundations, rows) + w, h = l.XM+(max_rows+1)*l.XS, l.YM+3*l.YS+playcards*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM+l.XS+(max_rows-foundations)*l.XS/2, l.YM + for fclass in self.Foundation_Classes: + for i in range(4): + s.foundations.append(fclass(x, y, self, suit=i)) + x += l.XS + + x, y = l.XM+l.XS+(max_rows-rows)*l.XS/2, l.YM+l.YS + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1)) + x += l.XS + self.setRegion(s.rows, (-999, y-l.CH/2, 999999, h-l.YS-l.CH/2)) + + d = (w-reserves*l.XS)/reserves + x, y = l.XM, h-l.YS + for i in range(reserves): + stack = ReserveStack(x, y, self) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 2, 0 + s.reserves.append(stack) + x += l.XS+d + + s.talon = self.Talon_Class(l.XM, l.YM, self) + l.createText(s.talon, "ss") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.rows, frames=0) + for i in range(5): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or + (card2.rank + 1) % 13 == card1.rank)) + + +# /*********************************************************************** +# // Algerian Patience +# ************************************************************************/ + +class AlgerianPatience(Carthage): + + Foundation_Classes = (SS_FoundationStack, + StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1)) + RowStack_Class = StackWrapper(UD_SS_RowStack, mod=13) + + def _shuffleHook(self, cards): + # move 4 Kings to top of the Talon + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == KING and c.deck == 0, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations[4:], frames=0) + Carthage.startGame(self) + + +class AlgerianPatience3(Carthage): + Foundation_Classes = (SS_FoundationStack, + SS_FoundationStack, + SS_FoundationStack) + RowStack_Class = StackWrapper(UD_SS_RowStack, mod=13) + + def createGame(self): + Carthage.createGame(self, rows=8, reserves=8, playcards=20) + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, (c.deck, c.suit))) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + Carthage.startGame(self) + + + +# register the game +registerGame(GameInfo(321, Carthage, "Carthage", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(322, AlgerianPatience, "Algerian Patience", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(457, AlgerianPatience3, "Algerian Patience (3 decks)", + GI.GT_3DECK_TYPE, 3, 0)) + diff --git a/pysollib/games/auldlangsyne.py b/pysollib/games/auldlangsyne.py new file mode 100644 index 0000000000..a12f2d8db2 --- /dev/null +++ b/pysollib/games/auldlangsyne.py @@ -0,0 +1,460 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Tam O'Shanter +# ************************************************************************/ + +class TamOShanter(Game): + Talon_Class = DealRowTalonStack + RowStack_Class = StackWrapper(BasicRowStack, max_move=1, max_accept=0) + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 6*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, "ss") + x, y = l.XM+2*l.XS, l.YM + for i in range(4): + s.foundations.append(RK_FoundationStack(x, y, self)) + x += l.XS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(4): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + +# /*********************************************************************** +# // Auld Lang Syne +# ************************************************************************/ + +class AuldLangSyne(TamOShanter): + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + +# /*********************************************************************** +# // Strategy +# ************************************************************************/ + +class Strategy_Foundation(SS_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # we only accept cards if there are no cards in the talon + return len(self.game.s.talon.cards) == 0 + + +class Strategy_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts any one card from the Talon + return from_stack is self.game.s.talon and len(cards) == 1 + + def canMoveCards(self, cards): + if self.game.s.talon.cards: + return 0 + return BasicRowStack.canMoveCards(self, cards) + + def clickHandler(self, event): + if self.game.s.talon.cards: + self.game.s.talon.playMoveMove(1, self) + return 1 + return BasicRowStack.clickHandler(self, event) + + def doubleclickHandler(self, event): + if self.game.s.talon.cards: + self.game.s.talon.playMoveMove(1, self) + return 1 + return BasicRowStack.doubleclickHandler(self, event) + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + def getHelp(self): + return _('Row. Build regardless of rank and suit.') + + +class Strategy(Game): + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + s.talon = OpenTalonStack(x, y, self) + l.createText(s.talon, "se") + for i in range(4): + x, y = l.XM + (i+2)*l.XS, l.YM + s.foundations.append(Strategy_Foundation(x, y, self, suit=i, max_move=0)) + for i in range(8): + x, y = l.XM + i*l.XS, l.YM + l.YS + s.rows.append(Strategy_RowStack(x, y, self, max_move=1, max_accept=1)) + x = x + l.XS + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.fillStack() + + +# /*********************************************************************** +# // Interregnum +# ************************************************************************/ + +class Interregnum_Foundation(RK_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not RK_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if len(self.cards) == 12: + # the final card must come from the reserve above the foundation + return from_stack.id == self.id - 8 + else: + # card must come from rows + return from_stack in self.game.s.rows + + +class Interregnum(Game): + GAME_VERSION = 2 + + # + # game layout + # + + def createGame(self, rows=8): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + max(9,rows)*l.XS, l.YM + 5*l.YS) + + # extra settings + self.base_cards = None + + # create stacks + for i in range(8): + x, y, = l.XM + i*l.XS, l.YM + s.reserves.append(ReserveStack(x, y, self, max_accept=0)) + for i in range(8): + x, y, = l.XM + i*l.XS, l.YM + l.YS + s.foundations.append(Interregnum_Foundation(x, y, self, mod=13, max_move=0)) + for i in range(rows): + x, y, = l.XM + (2*i+8-rows)*l.XS/2, l.YM + 2*l.YS + s.rows.append(BasicRowStack(x, y, self, max_accept=0, max_move=1)) + s.talon = DealRowTalonStack(self.width-l.XS, self.height-l.YS, self) + l.createText(s.talon, "nn") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + # deal base_cards to reserves, update foundations cap.base_rank + self.base_cards = [] + for i in range(8): + self.base_cards.append(self.s.talon.getCard()) + self.s.foundations[i].cap.base_rank = (self.base_cards[i].rank + 1) % 13 + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.reserves[i]) + # deal other cards + self.s.talon.dealRow() + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank) + + def _restoreGameHook(self, game): + self.base_cards = [None] * 8 + for i in range(8): + id = game.loadinfo.base_card_ids[i] + self.base_cards[i] = self.cards[id] + self.s.foundations[i].cap.base_rank = (self.base_cards[i].rank + 1) % 13 + + def _loadGameHook(self, p): + ids = [] + for i in range(8): + ids.append(p.load()) + self.loadinfo.addattr(base_card_ids=ids) # register extra load var. + + def _saveGameHook(self, p): + for c in self.base_cards: + p.dump(c.id) + +# /*********************************************************************** +# // Colorado +# ************************************************************************/ + +class Colorado_RowStack(OpenStack): + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts any one card from the Waste + return from_stack is self.game.s.waste and len(cards) == 1 + + +class Colorado(Game): + + Foundation_Class = SS_FoundationStack + RowStack_Class = Colorado_RowStack + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+10*l.XS, 3*l.YM+4*l.YS) + + # create stacks + x, y, = l.XS, l.YM + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, + suit=i, max_move=0)) + x = x + l.XS + x += 2*l.XM + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, + suit=i, max_move=0, base_rank=KING, dir=-1)) + x = x + l.XS + + y = l.YM+l.YS + for i in range(2): + x = l.XM + for j in range(10): + stack = self.RowStack_Class(x, y, self, + max_move=1, max_accept=1) + s.rows.append(stack) + stack.CARD_XOFFSET = stack.CARD_YOFFSET = 0 + x += l.XS + y += l.YS + + x, y = l.XM+9*l.XS, l.YM+3*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x -= l.XS + s.waste = WasteStack(x, y, self, max_cards=1) + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow() + self.s.talon.dealCards() + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, lambda c: (c.deck == 0 and c.rank in (0, 12), (c.rank, c.suit)), 8) + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + + +# /*********************************************************************** +# // Amazons +# ************************************************************************/ + +class Amazons_Talon(DealRowTalonStack): + def canDealCards(self): + ## FIXME: this is to avoid loops in the demo + if self.game.demo and self.game.moves.index >= 100: + return False + if self.round == self.max_rounds: + return False + return not self.game.isGameWon() + + def dealCards(self, sound=0): + self.game.startDealSample() + if not self.cards: + self.game.nextRoundMove(self) + n = self._moveAllToTalon() + self.game.stopSamples() + return n + n = self.dealRowAvail() + self.game.stopSamples() + return n + + def dealRowAvail(self, rows=None, flip=1, reverse=0, frames=-1, sound=0): + if rows is None: + rows = [] + i = 0 + for f in self.game.s.foundations: + if len(f.cards) < 7: + rows.append(self.game.s.rows[i]) + i += 1 + return DealRowTalonStack.dealRowAvail(self, rows=rows, flip=flip, + reverse=reverse, frames=frames, sound=sound) + + def _moveAllToTalon(self): + # move all cards to the Talon + num_cards = 0 + for r in self.game.s.rows: + for i in range(len(r.cards)): + num_cards += 1 + self.game.moveMove(1, r, self, frames=4) + self.game.flipMove(self) + return num_cards + + +class Amazons_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return False + if from_stack not in self.game.s.rows: + return False + if cards[0].rank == ACE: + return True + if not self.cards: + return False + rank = self.cards[-1].rank + if rank == ACE: + rank = 5 + if (rank + self.cap.dir) % self.cap.mod != cards[0].rank: + return False + i = list(self.game.s.foundations).index(self) + j = list(self.game.s.rows).index(from_stack) + return i == j + + +class Amazons(Game): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 6*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + s.talon = Amazons_Talon(x, y, self, max_rounds=-1) + l.createText(s.talon, "ss") + x, y = l.XM+2*l.XS, l.YM + for i in range(4): + s.foundations.append(Amazons_Foundation(x, y, self, suit=i)) + x += l.XS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(4): + s.rows.append(BasicRowStack(x, y, self, max_move=1, max_accept=0)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + +# register the game +registerGame(GameInfo(172, TamOShanter, "Tam O'Shanter", + GI.GT_NUMERICA, 1, 0)) +registerGame(GameInfo(95, AuldLangSyne, "Auld Lang Syne", + GI.GT_NUMERICA, 1, 0)) +registerGame(GameInfo(173, Strategy, "Strategy", + GI.GT_NUMERICA, 1, 0)) +registerGame(GameInfo(123, Interregnum, "Interregnum", + GI.GT_NUMERICA, 2, 0)) +registerGame(GameInfo(296, Colorado, "Colorado", + GI.GT_NUMERICA, 2, 0)) +registerGame(GameInfo(406, Amazons, "Amazons", + GI.GT_NUMERICA, 1, -1, + ranks=(0, 6, 7, 8, 9, 10, 11), + )) + diff --git a/pysollib/games/bakersdozen.py b/pysollib/games/bakersdozen.py new file mode 100644 index 0000000000..f3cbf8d74f --- /dev/null +++ b/pysollib/games/bakersdozen.py @@ -0,0 +1,344 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Castles in Spain +# ************************************************************************/ + +class CastlesInSpain(Game): + Layout_Method = Layout.bakersDozenLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + Hint_Class = CautiousDefaultHint + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=13, playcards=9) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + max_move=1, max_accept=1)) + # default + l.defaultAll() + + def startGame(self, flip=(0, 0, 0)): + for f in flip: + self.s.talon.dealRow(flip=f, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Martha +# ************************************************************************/ + +class Martha_RowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return 0 + # when empty, only accept a single card + return self.cards or len(cards) == 1 + + +class Martha(CastlesInSpain): + RowStack_Class = FullStackWrapper(Martha_RowStack) + + def createGame(self): + CastlesInSpain.createGame(self, rows=12, playcards=13) + + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + CastlesInSpain.startGame(self, flip=(0, 1, 0)) + self.s.talon.dealRow(rows=self.s.foundations) + + +# /*********************************************************************** +# // Baker's Dozen +# ************************************************************************/ + +class BakersDozen(CastlesInSpain): + RowStack_Class = StackWrapper(RK_RowStack, base_rank=NO_RANK) + + def _shuffleHook(self, cards): + # move Kings to bottom of each stack + i, n = 0, len(self.s.rows) + kings = [] + for c in cards: + if c.rank == KING: + kings.append(i) + i = i + 1 + for i in kings: + j = i % n + while j < i: + if cards[j].rank != KING: + cards[i], cards[j] = cards[j], cards[i] + break + j = j + n + cards.reverse() + return cards + + def startGame(self): + CastlesInSpain.startGame(self, flip=(1, 1, 1)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank) + + +# /*********************************************************************** +# // Spanish Patience +# ************************************************************************/ + +class SpanishPatience(BakersDozen): + Foundation_Class = AC_FoundationStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Portuguese Solitaire +# ************************************************************************/ + +class PortugueseSolitaire(BakersDozen): + RowStack_Class = StackWrapper(RK_RowStack, base_rank=KING) + + +# /*********************************************************************** +# // Good Measure +# ************************************************************************/ + +class GoodMeasure(BakersDozen): + def createGame(self): + CastlesInSpain.createGame(self, rows=10) + + def _shuffleHook(self, cards): + cards = BakersDozen._shuffleHook(self, cards) + # move 2 Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit), 2) + + def startGame(self): + CastlesInSpain.startGame(self, flip=(1, 1, 1, 1)) + for i in range(2): + c = self.s.talon.cards[-1] + assert c.rank == ACE + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.foundations[c.suit]) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Cruel +# ************************************************************************/ + +class Cruel_Talon(TalonStack): + def canDealCards(self): + ## FIXME: this is to avoid loops in the demo + if self.game.demo and self.game.moves.index >= 100: + return False + if self.round == self.max_rounds: + return False + return not self.game.isGameWon() + + def dealCards(self, sound=0): + lr = len(self.game.s.rows) + # move all cards to the Talon and redeal (no shuffling) + num_cards = 0 + assert len(self.cards) == 0 + rows = list(self.game.s.rows)[:] + rows.reverse() + for r in rows: + for i in range(len(r.cards)): + num_cards = num_cards + 1 + self.game.moveMove(1, r, self, frames=0) + assert len(self.cards) == num_cards + if num_cards == 0: # game already finished + return 0 + # redeal in packs of 4 cards + self.game.nextRoundMove(self) + n, i = num_cards, 0 + deal = [4] * lr + extra_cards = n - 4 * lr + while extra_cards > 0: + # note: this can only happen in Tarock games like Nasty + deal[i] = deal[i] + 1 + i = (i + 1) % lr + extra_cards = extra_cards - 1 + ##print n, deal + self.game.startDealSample() + for i in range(lr): + k = min(deal[i], n) + frames = (0, 4)[n <= 3*4] + for j in range(k): + self.game.moveMove(1, self, self.game.s.rows[i], frames=frames) + n = n - k + if n == 0: + break + # done + self.game.stopSamples() + assert n == len(self.cards) == 0 + return num_cards + + +class Cruel(CastlesInSpain): + Talon_Class = StackWrapper(Cruel_Talon, max_rounds=-1) + RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK) + + def createGame(self): + CastlesInSpain.createGame(self, rows=12) + + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + CastlesInSpain.startGame(self, flip=(1, 1, 1)) + self.s.talon.dealRow(rows=self.s.foundations) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + +# /*********************************************************************** +# // Royal Family +# ************************************************************************/ + +class RoyalFamily(Cruel): + + Foundation_Class = StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1) + Talon_Class = StackWrapper(Cruel_Talon, max_rounds=2) + RowStack_Class = UD_AC_RowStack + + def _shuffleHook(self, cards): + # move Kings to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 12, c.suit)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Perseverance +# ************************************************************************/ + +class Perseverance(Cruel, BakersDozen): + Talon_Class = StackWrapper(Cruel_Talon, max_rounds=3) + RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK, dir=-1, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS) + + def _shuffleHook(self, cards): + # move Kings to bottom of each stack (???) + #cards = BakersDozen._shuffleHook(self, cards) + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + cards = Cruel._shuffleHook(self, cards) + return cards + +## def dealCards(self, sound=1): +## Cruel.dealCards(self, sound) + + +# /*********************************************************************** +# // Ripple Fan +# ************************************************************************/ + +class RippleFan(CastlesInSpain): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + Layout.bakersDozenLayout(l, rows=13) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = Cruel_Talon(l.s.talon.x, l.s.talon.y, self, max_rounds=-1) + for r in l.s.foundations: + s.foundations.append(SS_FoundationStack(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(SS_RowStack(r.x, r.y, self, base_rank=NO_RANK)) + # default + l.defaultAll() + + def startGame(self): + CastlesInSpain.startGame(self, flip=(1, 1, 1)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +# register the game +registerGame(GameInfo(83, CastlesInSpain, "Castles in Spain", + GI.GT_BAKERS_DOZEN, 1, 0)) +registerGame(GameInfo(84, Martha, "Martha", + GI.GT_BAKERS_DOZEN, 1, 0)) +registerGame(GameInfo(31, BakersDozen, "Baker's Dozen", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(85, SpanishPatience, "Spanish Patience", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(86, GoodMeasure, "Good Measure", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(104, Cruel, "Cruel", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, -1)) +registerGame(GameInfo(291, RoyalFamily, "Royal Family", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 1)) +registerGame(GameInfo(308, PortugueseSolitaire, "Portuguese Solitaire", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(404, Perseverance, "Perseverance", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 1, 2)) +registerGame(GameInfo(369, RippleFan, "Ripple Fan", + GI.GT_BAKERS_DOZEN, 1, -1)) + diff --git a/pysollib/games/bakersgame.py b/pysollib/games/bakersgame.py new file mode 100644 index 0000000000..6ea1147db8 --- /dev/null +++ b/pysollib/games/bakersgame.py @@ -0,0 +1,366 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import FreeCellType_Hint, FreeCellSolverWrapper + + +# /*********************************************************************** +# // Baker's Game +# ************************************************************************/ + +# To simplify playing we also consider the number of free rows. +# Note that this only is legal if the game.s.rows have a +# cap.base_rank == ANY_RANK. +# See also the "SuperMove" section in the FreeCell FAQ. +class BakersGame_RowStack(SS_RowStack): + def _getMaxMove(self, to_stack_ncards): + max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1 + n = getNumberOfFreeStacks(self.game.s.rows) + if to_stack_ncards == 0: + n = n - 1 + while n > 0 and max_move < 1000: + max_move = max_move * 2 + n = n - 1 + return max_move + + def canMoveCards(self, cards): + max_move = self._getMaxMove(1) + return len(cards) <= max_move and SS_RowStack.canMoveCards(self, cards) + + def acceptsCards(self, from_stack, cards): + max_move = self._getMaxMove(len(self.cards)) + return len(cards) <= max_move and SS_RowStack.acceptsCards(self, from_stack, cards) + + +class BakersGame(Game): + Layout_Method = Layout.freeCellLayout + Foundation_Class = SS_FoundationStack + RowStack_Class = BakersGame_RowStack + ##Hint_Class = FreeCellType_Hint + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'sbb' : "suit" }) + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=8, reserves=4, texts=0) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = InitialDealTalonStack(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + self.s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + for r in l.s.reserves: + self.s.reserves.append(ReserveStack(r.x, r.y, self)) + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + r = self.s.rows + ##self.s.talon.dealRow(rows=(r[0], r[1], r[6], r[7])) + self.s.talon.dealRow(rows=r[:4]) + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class KingOnlyBakersGame(BakersGame): + RowStack_Class = StackWrapper(FreeCell_SS_RowStack, base_rank=KING) + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'sbb' : "suit", 'esf' : "kings" }) + + +# /*********************************************************************** +# // Eight Off (Baker's Game in a different layout) +# ************************************************************************/ + +class EightOff(KingOnlyBakersGame): + + # + # game layout + # + + def createGame(self, rows=8, reserves=8): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 16 cards are playable without overlap in default window size) + h = max(2*l.YS, l.YS+(16-1)*l.YOFFSET) + maxrows = max(rows, reserves) + self.setSize(l.XM + maxrows*l.XS, l.YM + l.YS + h + l.YS) + + # create stacks + x, y = l.XM + (maxrows-4)*l.XS/2, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i)) + x = x + l.XS + x, y = l.XM + (maxrows-rows)*l.XS/2, y + l.YS + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + x, y = l.XM + (maxrows-reserves)*l.XS/2, self.height - l.YS + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + self.setRegion(s.reserves, (-999, y - l.CH / 2, 999999, 999999)) + s.talon = InitialDealTalonStack(l.XM, l.YM, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + r = self.s.reserves + self.s.talon.dealRow(rows=[r[0],r[2],r[4],r[6]]) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Seahaven Towers (Baker's Game in a different layout) +# ************************************************************************/ + +class SeahavenTowers(KingOnlyBakersGame): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable in default window size) + h = max(3*l.YS, 20*l.YOFFSET) + self.setSize(l.XM + 10*l.XS, l.YM + l.YS + h) + + # create stacks + x, y = l.XM, l.YM + for i in range(4): + s.reserves.append(ReserveStack(x + (i+3)*l.XS, y, self)) + for suit in range(4): + i = (9, 0, 1, 8)[suit] + s.foundations.append(SS_FoundationStack(x + i*l.XS, y, self, suit)) + x, y = l.XM, l.YM + l.YS + for i in range(10): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + self.setRegion(s.rows, (-999, y - l.YM / 2, 999999, 999999)) + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # define stack-groups + self.sg.openstacks = s.foundations + s.rows + s.reserves + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + s.reserves + self.sg.reservestacks = s.reserves + + # + # game overrides + # + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=(self.s.reserves[1:3])) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class RelaxedSeahavenTowers(SeahavenTowers): + RowStack_Class = KingSS_RowStack + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'sbb' : "suit", 'esf' : "kings", 'sm' : "unlimited",}) + + +# /*********************************************************************** +# // Penguin +# // Opus +# ************************************************************************/ + +class Penguin(Game): + GAME_VERSION = 2 + + RowStack_Class = SS_RowStack + Hint_Class = FreeCellType_Hint + + # + # game layout + # + + def createGame(self, rows=7, reserves=7): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 16 cards are playable without overlap in default window size) + h = max(3*l.YS, l.YS+(16-1)*l.YOFFSET) + maxrows = max(rows, reserves) + self.setSize(l.XM + (maxrows+1)*l.XS, l.YM + h + l.YS) + + # extra settings + self.base_card = None + + # create stacks + x, y = self.width - l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, mod=13, max_move=0)) + y = y + l.YS + self.setRegion(s.foundations, (x - l.CW/2, -999, 999999, 999999)) + x, y = l.XM + (maxrows-rows)*l.XS/2, l.YM + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self, mod=13)) + x = x + l.XS + x, y = l.XM + (maxrows-reserves)*l.XS/2, self.height - l.YS + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + self.setRegion(s.reserves, (-999, y - l.CH / 2, 999999, 999999)) + s.talon = InitialDealTalonStack(l.XM+1, y, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move base cards to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c, rank=cards[-1].rank: (c.rank == rank, 0)) + + def startGame(self): + self.base_card = self.s.talon.cards[-4] + self._updateStacks() + # deal base cards to Foundations + for i in range(3): + c = self.s.talon.getCard() + assert c.rank == self.base_card.rank + to_stack = self.s.foundations[c.suit * self.gameinfo.decks] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack, frames=0) + # deal rows + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + self._updateStacks() + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + # + # game extras + # + + def _updateStacks(self): + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + for s in self.s.rows: + s.cap.base_rank = (self.base_card.rank - 1) % 13 + + +class Opus(Penguin): + def createGame(self): + Penguin.createGame(self, reserves=5) + + +# register the game +registerGame(GameInfo(45, BakersGame, "Baker's Game", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(26, KingOnlyBakersGame, "King Only Baker's Game", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(258, EightOff, "Eight Off", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(9, SeahavenTowers, "Seahaven Towers", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0, + altnames=("Sea Towers", "Towers") )) +registerGame(GameInfo(6, RelaxedSeahavenTowers, "Relaxed Seahaven Towers", + GI.GT_FREECELL | GI.GT_RELAXED | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(64, Penguin, "Penguin", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0, + altnames=("Beak and Flipper",) )) +registerGame(GameInfo(427, Opus, "Opus", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) diff --git a/pysollib/games/beleagueredcastle.py b/pysollib/games/beleagueredcastle.py new file mode 100644 index 0000000000..b8012d3bc4 --- /dev/null +++ b/pysollib/games/beleagueredcastle.py @@ -0,0 +1,659 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import CautiousDefaultHint, FreeCellType_Hint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class BeleagueredCastleType_Hint(CautiousDefaultHint): + # FIXME: demo is not too clever in this game + pass + + +# /*********************************************************************** +# // Streets and Alleys +# ************************************************************************/ + +class StreetsAndAlleys(Game): + Hint_Class = BeleagueredCastleType_Hint + + # + # game layout + # + + def createGame(self, playcards=13, reserves=0): + # create layout + l, s = Layout(self), self.s + + # set window + # (set size so that at least 13 cards are fully playable) + w = max(3*l.XS, l.XS+(playcards-1)*l.XOFFSET) + x0 = l.XM + x1 = x0 + w + 2*l.XM + x2 = x1 + l.XS + 2*l.XM + x3 = x2 + w + l.XM + self.setSize(x3, l.YM + (4+int(reserves!=0))*l.YS) + + # create stacks + y = l.YM + if reserves: + x = x1 - int(l.XS*(reserves-1)/2) + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x += l.XS + y += l.YS + x = x1 + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, max_move=0)) + y = y + l.YS + for x in (x0, x2): + y = l.YM+l.YS*int(reserves!=0) + for i in range(4): + stack = RK_RowStack(x, y, self, max_move=1, max_accept=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + y = y + l.YS + x, y = self.width - l.XS, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + if reserves: + l.setRegion(s.rows[:4], (-999, l.YM+l.YS-l.CH/2, x1-l.CW/2, 999999)) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRowAvail() + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank - card2.rank) == 1 + + +# /*********************************************************************** +# // Beleaguered Castle +# ************************************************************************/ + +class BeleagueredCastle(StreetsAndAlleys): + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(2): + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Citadel +# ************************************************************************/ + +class Citadel(StreetsAndAlleys): + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + # move cards to the Foundations during dealing + def startGame(self): + frames = 4 + talon = self.s.talon + self.startDealSample() + talon.dealRow(rows=self.s.foundations, frames=frames) + while talon.cards: + for r in self.s.rows: + self.flipMove(talon) + for s in self.s.foundations: + if s.acceptsCards(self, talon.cards[-1:]): + self.moveMove(1, talon, s, frames=frames) + break + else: + self.moveMove(1, talon, r, frames=frames) + if not talon.cards: + break + + +# /*********************************************************************** +# // Fortress +# ************************************************************************/ + +class Fortress(Game): + Layout_Method = Layout.klondikeLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = UD_SS_RowStack + Hint_Class = BeleagueredCastleType_Hint + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=10, waste=0, texts=0, playcards=16) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + if l.s.waste: + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + return l + + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRowAvail() + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or + (card2.rank + 1) % 13 == card1.rank)) + +# /*********************************************************************** +# // Bastion +# // Ten by One +# ************************************************************************/ + +class Bastion(Game): + Layout_Method = Layout.freeCellLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = UD_SS_RowStack + ReserveStack_Class = ReserveStack + Hint_Class = BeleagueredCastleType_Hint + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=10, reserves=2, texts=0, playcards=16) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + for r in l.s.reserves: + s.reserves.append(self.ReserveStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + return l + + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(2): + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + + +class TenByOne(Bastion): + def createGame(self): + Bastion.createGame(self, reserves=1) + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRowAvail() + + +# /*********************************************************************** +# // Chessboard +# ************************************************************************/ + +class Chessboard_Foundation(SS_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=13, min_cards=1, max_move=0) + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not self.cards: + if len(cards) != 1 or not cards[0].face_up: + return 0 + if cards[0].suit != self.cap.base_suit: + return 0 + for s in self.game.s.foundations: + if s.cards: + return cards[0].rank == s.cards[0].rank + return 1 + return SS_FoundationStack.acceptsCards(self, from_stack, cards) + + +class Chessboard_RowStack(UD_SS_RowStack): + def canDropCards(self, stacks): + if self.game.demo: + return UD_SS_RowStack.canDropCards(self, stacks) + for s in self.game.s.foundations: + if s.cards: + return UD_SS_RowStack.canDropCards(self, stacks) + return (None, 0) + + +class Chessboard(Fortress): + Foundation_Class = Chessboard_Foundation + RowStack_Class = StackWrapper(Chessboard_RowStack, mod=13) + + def createGame(self): + l = Fortress.createGame(self) + tx, ty, ta, tf = l.getTextAttr(self.s.foundations[-1], "e") + font = self.app.getFont("canvas_default") + self.texts.info = MfxCanvasText(self.canvas, tx + l.XM, ty, anchor=ta, font=font) + + def updateText(self): + if self.preview > 1: + return + t = "" + for s in self.s.foundations: + if s.cards: + t = RANKS[s.cards[0].rank] + break + self.texts.info.config(text=t) + + +# /*********************************************************************** +# // Stronghold +# // Fastness +# ************************************************************************/ + +class Stronghold(StreetsAndAlleys): + Hint_Class = FreeCellType_Hint + def createGame(self): + StreetsAndAlleys.createGame(self, reserves=1) + +class Fastness(StreetsAndAlleys): + Hint_Class = FreeCellType_Hint + def createGame(self): + StreetsAndAlleys.createGame(self, reserves=2) + + +# /*********************************************************************** +# // Zerline +# ************************************************************************/ + +class Zerline_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return False + return not from_stack is self.game.s.waste + +class Zerline(Game): + Hint_Class = BeleagueredCastleType_Hint + + # + # game layout + # + + def createGame(self, rows=8, playcards=13, reserve_max_cards=4): + # create layout + l, s = Layout(self), self.s + decks = self.gameinfo.decks + + # set window + # (set size so that at least 13 cards are fully playable) + w = max(3*l.XS, l.XS+playcards*l.XOFFSET) + self.setSize(l.XM+2*w+decks*l.XS, 4*l.YM + (rows/2+1)*l.YS) + + # create stacks + y = l.YM + x = l.XM + w + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + x += l.XS + stack = Zerline_ReserveStack(x, y, self, max_cards=reserve_max_cards) + s.reserves.append(stack) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + l.createText(stack, "ss") + x = l.XM + w + for j in range(decks): + y = 4*l.YM+l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, + base_rank=KING, dir=1, max_move=0, mod=13)) + y += l.YS + x += l.XS + x = l.XM + for j in range(2): + y = 4*l.YM+l.YS + for i in range(rows/2): + stack = RK_RowStack(x, y, self, max_move=1, max_accept=1, base_rank=QUEEN) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + y += l.YS + x += l.XM + w +decks*l.XS + + l.setRegion(s.rows[:4], (-999, 4*l.YM+l.YS-l.CH/2, w-l.CW/2, 999999)) + + # define stack-groups + l.defaultStackGroups() + # set regions + l.defaultRegions() + + # + # game overrides + # + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank - card2.rank) == 1 + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + return int(to_stack in self.s.rows) + + +class Zerline3Decks(Zerline): + def createGame(self): + Zerline.createGame(self, rows=8, reserve_max_cards=6) + + +# /*********************************************************************** +# // Chequers +# ************************************************************************/ + +class Chequers(Fortress): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (set size so that at least 7 cards are fully playable) + dx = l.XM+l.XS+7*l.XOFFSET + w = l.XM+max(5*dx, 9*l.XS+2*l.XM) + h = l.YM+6*l.YS + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + s.talon = TalonStack(x, y, self) + l.createText(s.talon, "se") + x = max(l.XS+3*l.XM, (self.width-l.XM-8*l.XS)/2) + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=KING, dir=-1)) + x += l.XS + y = l.YM+l.YS + for i in range(5): + x = l.XM + for j in range(5): + stack = UD_SS_RowStack(x, y, self) + s.rows.append(stack) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + x += dx + y += l.YS + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def fillStack(self, stack): + if self.s.talon.cards and stack in self.s.rows and not stack.cards: + self.s.talon.dealToStacks([stack]) + + +# /*********************************************************************** +# // Castle of Indolence +# ************************************************************************/ + +class CastleOfIndolence(Game): + Hint_Class = BeleagueredCastleType_Hint + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (set size so that at least 13 cards are fully playable) + w = max(3*l.XS, l.XS+13*l.XOFFSET) + self.setSize(l.XM+2*w+2*l.XS, l.YM + 5*l.YS) + + # create stacks + x, y = l.XM, l.YM+4*l.YS + s.talon = InitialDealTalonStack(x, y, self) + x, y = l.XM+w-l.XS, l.YM+4*l.YS + for i in range(4): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + x += l.XS + + x = l.XM + w + for x in (l.XM + w, l.XM + w + l.XS): + y = l.YM + for i in range(4): + s.foundations.append(RK_FoundationStack(x, y, self, + max_move=0)) + y += l.YS + + for x in (l.XM, l.XM + w +2*l.XS): + y = l.YM + for i in range(4): + stack = RK_RowStack(x, y, self, max_move=1, max_accept=1, base_rank=ANY_RANK) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + y += l.YS + l.setRegion(s.rows[:4], (-999, -999, w-l.CW/2, l.YM+4*l.YS-l.CH/2)) + + # define stack-groups + l.defaultStackGroups() + # set regions + l.defaultRegions() + + def startGame(self): + for i in range(13): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow() + self.s.talon.dealRowAvail() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank - card2.rank) == 1 + + +# /*********************************************************************** +# // Rittenhouse +# ************************************************************************/ + +class Rittenhouse_Foundation(RK_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not RK_FoundationStack.acceptsCards(self, from_stack, cards): + return False + if from_stack in self.game.s.rows: + ri = list(self.game.s.rows).index(from_stack) + fi = list(self.game.s.foundations).index(self) + if ri < 4: + return ri == fi + if ri == 4: + return True + return ri-1 == fi + return False + + +class Rittenhouse(Game): + Hint_Class = BeleagueredCastleType_Hint + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+9*l.XS, l.YM+3*l.YS+12*l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + for i in range(4): + s.foundations.append(Rittenhouse_Foundation(x, y, self, max_move=0)) + x += l.XS + x += l.XS + for i in range(4): + s.foundations.append(Rittenhouse_Foundation(x, y, self, + base_rank=KING, dir=-1, max_move=0)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(9): + s.rows.append(UD_RK_RowStack(x, y, self)) + x += l.XS + + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # default + l.defaultAll() + + + def startGame(self): + # move cards to the Foundations during dealing + talon = self.s.talon + self.startDealSample() + while talon.cards: + talon.dealRowAvail() + self.fillAll() + + def fillAll(self): + while True: + if not self._fillOne(): + break + + def _fillOne(self): + for r in self.s.rows: + for s in self.s.foundations: + if s.acceptsCards(r, r.cards[-1:]): + self.moveMove(1, r, s) + return 1 + return 0 + + def fillStack(self, stack): + self.fillAll() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank - card2.rank) == 1 + + + +# register the game +registerGame(GameInfo(146, StreetsAndAlleys, "Streets and Alleys", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(34, BeleagueredCastle, "Beleaguered Castle", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(145, Citadel, "Citadel", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(147, Fortress, "Fortress", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(148, Chessboard, "Chessboard", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(300, Stronghold, "Stronghold", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(301, Fastness, "Fastness", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(306, Zerline, "Zerline", + GI.GT_BELEAGUERED_CASTLE, 2, 0)) +registerGame(GameInfo(324, Bastion, "Bastion", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(325, TenByOne, "Ten by One", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(351, Chequers, "Chequers", + GI.GT_BELEAGUERED_CASTLE, 2, 0)) +registerGame(GameInfo(393, CastleOfIndolence, "Castle of Indolence", + GI.GT_BELEAGUERED_CASTLE, 2, 0)) +registerGame(GameInfo(395, Zerline3Decks, "Zerline (3 decks)", + GI.GT_BELEAGUERED_CASTLE, 3, 0)) +registerGame(GameInfo(400, Rittenhouse, "Rittenhouse", + GI.GT_BELEAGUERED_CASTLE | GI.GT_OPEN, 2, 0)) + diff --git a/pysollib/games/bisley.py b/pysollib/games/bisley.py new file mode 100644 index 0000000000..559baf5715 --- /dev/null +++ b/pysollib/games/bisley.py @@ -0,0 +1,257 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Bisley +# ************************************************************************/ + +class Bisley(Game): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = 2*l.XM+8*l.XS, max(2*(l.YM+l.YS+8*l.YOFFSET), l.YM+5*l.YS) + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(6): + s.rows.append(UD_SS_RowStack(x, y, self, base_rank=NO_RANK)) + x += l.XS + x, y = l.XM, l.YM+l.YS+8*l.YOFFSET + for i in range(6): + s.rows.append(UD_SS_RowStack(x, y, self, base_rank=NO_RANK)) + x += l.XS + y = l.YM + for i in range(4): + x = l.XM+6*l.XS+l.XM + s.foundations.append(SS_FoundationStack(x, y, self, i, max_move=0)) + x += l.XS + s.foundations.append(SS_FoundationStack(x, y, self, i, + base_rank=KING, max_move=0, dir=-1)) + y += l.YS + + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations[::2]) + + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == ACE, c.suit)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Double Bisley +# ************************************************************************/ + +class DoubleBisley(Bisley): + + Hint_Class = CautiousDefaultHint + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+(8+4)*l.XS, l.YM+max(3*(l.YS+8*l.YOFFSET), 8*l.YS) + self.setSize(w, h) + + # create stacks + y = l.YM + for i in range(3): + x = l.XM + for j in range(8): + s.rows.append(UD_SS_RowStack(x, y, self, base_rank=NO_RANK)) + x += l.XS + y += l.YS+8*l.YOFFSET + + y = l.YM + for j in range(2): + for i in range(4): + x = l.XM+8*l.XS + s.foundations.append(SS_FoundationStack(x, y, self, + suit=j*2+i/2, max_move=0)) + x += l.XS + s.foundations.append(SS_FoundationStack(x, y, self, + suit=j*2+i/2, base_rank=KING, max_move=0, dir=-1)) + y += l.YS + + s.talon = InitialDealTalonStack(l.XM, h-l.YS, self) + + # default + l.defaultAll() + + +# /*********************************************************************** +# // Gloria +# ************************************************************************/ + +class Gloria(Game): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+12*l.XS, l.YM+2*l.YS+2*(l.YS+5*l.YOFFSET) + self.setSize(w, h) + + # create stacks + y = l.YM+2*l.YS + for i in range(2): + x = l.XM + for j in range(12): + s.rows.append(BasicRowStack(x, y, self, max_accept=0)) + x += l.XS + y += l.YS+5*l.YOFFSET + + x = l.XM+2*l.XS + for j in range(2): + for i in range(4): + y = l.YM + s.foundations.append(SS_FoundationStack(x, y, self, suit=j*2+i/2)) + y += l.YS + s.foundations.append(SS_FoundationStack(x, y, self, + suit=j*2+i/2, base_rank=KING, dir=-1)) + x += l.XS + + s.reserves.append(ReserveStack(l.XM, l.YM, self)) + s.reserves.append(ReserveStack(w-l.XS, l.YM, self)) + + s.talon = InitialDealTalonStack(l.XM, l.YM+l.YS, self) + + # default + l.defaultAll() + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations[1::2]) + + def _shuffleHook(self, cards): + # move Kings to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == KING, c.suit)) + + +# /*********************************************************************** +# // Adelaide +# // Mancunian +# ************************************************************************/ + +class Adelaide(Game): + + Hint_Class = CautiousDefaultHint + RowStack_Class = StackWrapper(UD_AC_RowStack, base_rank=NO_RANK) + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+8*l.XS, l.YM+2*l.YS+15*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = 2*l.XM, l.YM+l.YS + for i in range(8): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + y = l.YM + for i in range(4): + x = l.XM+i*l.XS + s.foundations.append(SS_FoundationStack(x, y, self, i, max_move=0)) + x += 2*l.XM+4*l.XS + s.foundations.append(SS_FoundationStack(x, y, self, i, + base_rank=KING, max_move=0, dir=-1)) + + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRowAvail() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + +class Mancunian(Adelaide): + RowStack_Class = StackWrapper(UD_RK_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank-card2.rank) == 1 + + +# register the game +registerGame(GameInfo(290, Bisley, "Bisley", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(372, DoubleBisley, "Double Bisley", + GI.GT_2DECK_TYPE | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(373, Gloria, "Gloria", + GI.GT_2DECK_TYPE | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(374, Adelaide, "Adelaide", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(375, Mancunian, "Mancunian", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/braid.py b/pysollib/games/braid.py new file mode 100644 index 0000000000..ea59bf4b01 --- /dev/null +++ b/pysollib/games/braid.py @@ -0,0 +1,382 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys, math + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Braid_Hint(DefaultHint): + # FIXME: demo is not too clever in this game + pass + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Braid_Foundation(AbstractFoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=13, dir=0, base_rank=NO_RANK, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return 1 + stack_dir = self.game.getFoundationDir() + if stack_dir == 0: + card_dir = self.getRankDir(cards=(self.cards[-1], cards[0])) + return card_dir in (1, -1) + else: + return (self.cards[-1].rank + stack_dir) % self.cap.mod == cards[0].rank + + +class Braid_BraidStack(OpenStack): + def __init__(self, x, y, game, sine=0): + OpenStack.__init__(self, x, y, game) + self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET + CW = self.game.app.images.CARDW + if sine: + # use a sine wave for the x offsets + self.CARD_XOFFSET = [] + n = 9 + dx = 0.4 * CW * (2*math.pi/n) + last_x = 0 + for i in range(n): + x = int(round(dx * math.sin(i + 1))) + ##print x, x - last_x + self.CARD_XOFFSET.append(x - last_x) + last_x = x + else: + self.CARD_XOFFSET = (-0.45*CW, 0.35*CW, 0.55*CW, -0.45*CW) + + +class Braid_RowStack(ReserveStack): + def fillStack(self): + if not self.cards and self.game.s.braid.cards: + self.game.moveMove(1, self.game.s.braid, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Braid_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if from_stack is self.game.s.braid or from_stack in self.game.s.rows: + return 0 + return ReserveStack.acceptsCards(self, from_stack, cards) + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +# /*********************************************************************** +# // Braid +# ************************************************************************/ + +class Braid(Game): + Hint_Class = Braid_Hint + Foundation_Class_1 = Braid_Foundation + Foundation_Class_2 = Braid_Foundation + + BRAID_CARDS = 20 + RANKS = RANKS # pull into class Braid + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable - needed for Braid_BraidStack) + h = max(4*l.YS + 30, l.YS+(self.BRAID_CARDS-1)*l.YOFFSET) + self.setSize(10*l.XS+l.XM, l.YM + h) + + # extra settings + self.base_card = None + + # create stacks + s.addattr(braid=None) # register extra stack variable + x, y = l.XM, l.YM + for i in range(2): + s.rows.append(Braid_RowStack(x + 0.5*l.XS, y, self)) + s.rows.append(Braid_RowStack(x + 4.5*l.XS, y, self)) + y = y + 3 * l.YS + y = l.YM + l.YS + for i in range(2): + s.rows.append(Braid_ReserveStack(x, y, self)) + s.rows.append(Braid_ReserveStack(x + l.XS, y, self)) + s.rows.append(Braid_ReserveStack(x, y + l.YS, self)) + s.rows.append(Braid_ReserveStack(x + l.XS, y + l.YS, self)) + x = x + 4 * l.XS + x, y = l.XM + l.XS * 5/2, l.YM + s.braid = Braid_BraidStack(x, y, self) + x, y = l.XM + 7 * l.XS, l.YM + l.YS * 3/2 + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + x = x - l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + x = l.XM + 8 * l.XS + y = l.YM + for i in range(4): + s.foundations.append(self.Foundation_Class_1(x, y, self, suit=i)) + s.foundations.append(self.Foundation_Class_2(x + l.XS, y, self, suit=i)) + y = y + l.YS + self.texts.info = MfxCanvasText(self.canvas, + x + l.CW + l.XM / 2, y, + anchor="n", + font=self.app.getFont("canvas_default")) + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.foundations + s.rows + self.sg.dropstacks = [s.braid] + s.rows + [s.waste] + + + # + # game overrides + # + + def _shuffleHook(self, cards): + # do not play a trump as the base_card + n = m = -1 - self.BRAID_CARDS - len(self.s.rows) + while cards[n].suit >= len(self.gameinfo.suits): + n = n - 1 + cards[n], cards[m] = cards[m], cards[n] + return cards + + def startGame(self): + self.base_card = None + self.updateText() + self.startDealSample() + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows=[self.s.braid], frames=4) + self.s.talon.dealRow(frames=4) + # deal base_card to foundations + self.base_card = self.s.talon.cards[-1] + to_stack = self.s.foundations[2 * self.base_card.suit] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + self.updateText() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + # deal first card to WasteStack + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + + # + # game extras + # + + def updateText(self): + if self.preview > 1 or not self.texts.info: + return + if not self.base_card: + t = "" + else: + t = self.RANKS[self.base_card.rank] + dir = self.getFoundationDir() + if dir == 1: + t = t + _(" Ascending") + elif dir == -1: + t = t + _(" Descending") + self.texts.info.config(text=t) + + +class LongBraid(Braid): + BRAID_CARDS = 24 + + +# /*********************************************************************** +# // Fort +# ************************************************************************/ + +class Fort(Braid): + + Foundation_Class_1 = SS_FoundationStack + Foundation_Class_2 = StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1) + + BRAID_CARDS = 21 + + def _shuffleHook(self, cards): + # move 4 Kings and 4 Aces to top of the Talon + # (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (ACE, KING) and c.deck == 0, (c.suit, c.rank))) + + def _restoreGameHook(self, game): + pass + def _loadGameHook(self, p): + pass + def _saveGameHook(self, p): + pass + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows=[self.s.braid], frames=4) + self.s.talon.dealRow(frames=4) + self.s.talon.dealCards() + + +# /*********************************************************************** +# // Backbone +# ************************************************************************/ + +class Backbone_BraidStack(OpenStack): + def __init__(self, x, y, game, **cap): + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET + + def basicIsBlocked(self): + return len(self.game.s.reserves[2].cards) != 0 + + +class Backbone(Game): + + Hint_Class = CautiousDefaultHint + + def createGame(self, rows=8): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+(rows+2)*l.XS, l.YM+3*l.XS+10*l.YOFFSET + self.setSize(w, h) + + # create stacks + y = l.YM + for i in range(4): + x = l.XM+(rows-8)*l.XS/2 +i*l.XS + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x = l.XM+(rows/2+2)*l.XS +i*l.XS + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + + x, y = l.XM+rows*l.XS/2, l.YM + s.reserves.append(Backbone_BraidStack(x, y, self, max_accept=0)) + x += l.XS + s.reserves.append(Backbone_BraidStack(x, y, self, max_accept=0)) + x, y = l.XM+(rows+1)*l.XS/2, l.YM+11*l.YOFFSET + s.reserves.append(BasicRowStack(x, y, self, max_accept=0)) + + x, y = l.XM, l.YM+l.YS + for i in range(rows/2): + s.rows.append(SS_RowStack(x, y, self, max_move=1)) + x += l.XS + x, y = l.XM+(rows/2+2)*l.XS, l.YM+l.YS + for i in range(rows/2): + s.rows.append(SS_RowStack(x, y, self, max_move=1)) + x += l.XS + + x, y = l.XM+rows*l.XS/2, h-l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "nn") + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "nn") + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + for i in range(10): + self.s.talon.dealRow(rows=self.s.reserves[:2], frames=0) + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +class BackbonePlus(Backbone): + def createGame(self): + Backbone.createGame(self, rows=10) + + +# register the game +registerGame(GameInfo(12, Braid, "Braid", + GI.GT_NAPOLEON, 2, 2, + altnames=("Der Zopf", "Plait", "Pigtail") )) +registerGame(GameInfo(175, LongBraid, "Long Braid", + GI.GT_NAPOLEON, 2, 2, + altnames=("Der lange Zopf",) )) +registerGame(GameInfo(358, Fort, "Fort", + GI.GT_NAPOLEON, 2, 2)) +registerGame(GameInfo(376, Backbone, "Backbone", + GI.GT_NAPOLEON, 2, 0)) +registerGame(GameInfo(377, BackbonePlus, "Backbone +", + GI.GT_NAPOLEON, 2, 0)) diff --git a/pysollib/games/bristol.py b/pysollib/games/bristol.py new file mode 100644 index 0000000000..e98f637739 --- /dev/null +++ b/pysollib/games/bristol.py @@ -0,0 +1,327 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Bristol_Hint(CautiousDefaultHint): + # FIXME: demo is not too clever in this game + + BONUS_CREATE_EMPTY_ROW = 0 # 0..9000 + BONUS_CAN_DROP_ALL_CARDS = 0 # 0..4000 + BONUS_CAN_CREATE_EMPTY_ROW = 0 # 0..4000 + + # Score for moving a pile from stack r to stack t. + # Increased score must be in range 0..9999 + def _getMovePileScore(self, score, color, r, t, pile, rpile): + # prefer reserves + if not r in self.game.s.reserves: + score = score - 10000 + # an empty pile doesn't gain anything + if len(pile) == len(r.cards): + return -1, color + return CautiousDefaultHint._getMovePileScore(self, score, color, r, t, pile, rpile) + + +# /*********************************************************************** +# // Bristol +# ************************************************************************/ + +class Bristol_Talon(TalonStack): + def dealCards(self, sound=0): + return self.dealRowAvail(rows=self.game.s.reserves, sound=sound) + + +class Bristol(Game): + Layout_Method = Layout.klondikeLayout + Hint_Class = Bristol_Hint + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 10*l.XS, l.YM + 5*l.YS) + + # create stacks + x, y, = l.XM + 3*l.XS, l.YM + for i in range(4): + s.foundations.append(RK_FoundationStack(x, y, self, max_move=0)) + x = x + l.XS + for i in range(2): + y = l.YM + (i*2+3)*l.YS/2 + for j in range(4): + x = l.XM + (j*5)*l.XS/2 + stack = RK_RowStack(x, y, self, base_rank=NO_RANK, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + x, y, = l.XM + 3*l.XS, l.YM + 4*l.YS + s.talon = Bristol_Talon(x, y, self) + l.createText(s.talon, "sw") + for i in range(3): + x = x + l.XS + s.reserves.append(ReserveStack(x, y, self, max_accept=0, max_cards=UNLIMITED_CARDS)) + + # define stack-groups + self.sg.openstacks = s.foundations + s.rows + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + s.reserves + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move Kings to bottom of each stack + i, n = 0, len(self.s.rows) + kings = [] + for c in cards[:24]: # search the first 24 cards only + if c.rank == KING: + kings.append(i) + i = i + 1 + for i in kings: + j = i % n # j = card index of rowstack bottom + while j < i: + if cards[j].rank != KING: + cards[j], cards[i] = cards[i], cards[j] + break + j = j + n + cards.reverse() + return cards + + def startGame(self): + r = self.s.rows + for i in range(2): + self.s.talon.dealRow(rows=r, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=r) + self.s.talon.dealCards() # deal first cards to Reserves + + +# /*********************************************************************** +# // Belvedere +# ************************************************************************/ + +class Belvedere(Bristol): + def _shuffleHook(self, cards): + # remove 1 Ace + for c in cards: + if c.rank == 0: + cards.remove(c) + break + # move Kings to bottom + cards = Bristol._shuffleHook(self, cards) + # re-insert Ace + return cards[:-24] + [c] + cards[-24:] + + def startGame(self): + r = self.s.rows + for i in range(2): + self.s.talon.dealRow(rows=r, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=r) + assert self.s.talon.cards[-1].rank == ACE + self.s.talon.dealRow(rows=self.s.foundations[:1]) + self.s.talon.dealCards() # deal first cards to Reserves + + +# /*********************************************************************** +# // Dover +# ************************************************************************/ + +class Dover_RowStack(RK_RowStack): + + def acceptsCards(self, from_stack, cards): + if not self.cards and from_stack in self.game.s.reserves: + return True + return RK_RowStack.acceptsCards(self, from_stack, cards) + + +class Dover(Bristol): + + Talon_Class = Bristol_Talon + Foundation_Class = SS_FoundationStack + RowStack_Class = Dover_RowStack + ReserveStack_Class = StackWrapper(ReserveStack, max_accept=0, max_cards=UNLIMITED_CARDS) + + def createGame(self, text=False): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(2*l.XM+9*l.XS, l.YM+20+5*l.YS) + + # create stacks + x, y, = l.XM+l.XM+l.XS, l.YM + for i in range(8): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i/2, max_move=0)) + x += l.XS + if text: + x, y = l.XM+8*l.XS, l.YM + tx, ty, ta, tf = l.getTextAttr(None, "s") + tx, ty = x+tx+l.XM, y+ty + font = self.app.getFont("canvas_default") + self.texts.info = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=font) + + x, y = l.XM+l.XM, l.YM+l.YS + if text: + y += 20 + for i in range(8): + x += l.XS + stack = self.RowStack_Class(x, y, self, base_rank=NO_RANK, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.rows.append(stack) + x, y, = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, "s") + y += 20 + for i in range(3): + y += l.YS + s.reserves.append(self.ReserveStack_Class(x, y, self)) + + # define stack-groups + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return cards + + +# /*********************************************************************** +# // New York +# ************************************************************************/ + +class NewYork_Talon(OpenTalonStack): + rightclickHandler = OpenStack.rightclickHandler + doubleclickHandler = OpenStack.doubleclickHandler + + +class NewYork_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return False + return from_stack is self.game.s.talon + + +class NewYork_RowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return False + if not self.cards: + return (from_stack is self.game.s.talon or + from_stack in self.game.s.reserves) + return True + + +class NewYork(Dover): + + Foundation_Class = StackWrapper(SS_FoundationStack, mod=13) + Talon_Class = NewYork_Talon + RowStack_Class = StackWrapper(NewYork_RowStack, base_rank=ANY_RANK, mod=13) + ReserveStack_Class = StackWrapper(NewYork_ReserveStack, max_accept=1, max_cards=UNLIMITED_CARDS, mod=13) + + def createGame(self): + # extra settings + self.base_card = None + Dover.createGame(self, text=True) + self.sg.dropstacks.append(self.s.talon) + + def updateText(self): + if self.preview > 1: + return + if not self.base_card: + t = "" + else: + t = RANKS[self.base_card.rank] + self.texts.info.config(text=t) + + def startGame(self): + self.startDealSample() + self.base_card = None + self.updateText() + # deal base_card to Foundations, update foundations cap.base_rank + self.base_card = self.s.talon.getCard() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + n = self.base_card.suit * self.gameinfo.decks + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.foundations[n]) + ##self.updateText() + self.s.talon.dealRow() + self.s.talon.fillStack() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + ((card1.rank + 1) % 13 == card2.rank or + (card2.rank + 1) % 13 == card1.rank)) + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + +# register the game +registerGame(GameInfo(42, Bristol, "Bristol", + GI.GT_FAN_TYPE, 1, 0)) +registerGame(GameInfo(214, Belvedere, "Belvedere", + GI.GT_FAN_TYPE, 1, 0)) +registerGame(GameInfo(266, Dover, "Dover", + GI.GT_FAN_TYPE, 2, 0)) +registerGame(GameInfo(425, NewYork, "New York", + GI.GT_FAN_TYPE, 2, 0)) + diff --git a/pysollib/games/buffalobill.py b/pysollib/games/buffalobill.py new file mode 100644 index 0000000000..aad131ee48 --- /dev/null +++ b/pysollib/games/buffalobill.py @@ -0,0 +1,111 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Buffalo Bill +# // Little Billie +# ************************************************************************/ + +class BuffaloBill(Game): + + # + # game layout + # + + def createGame(self, rows=(7, 7, 7, 5)): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+max(max(rows)*(l.XS+3*l.XOFFSET), 9*l.XS), l.YM+(len(rows)+2)*l.YS + self.setSize(w, h) + + # create stacks + x, y = l.XM+(w-l.XM-8*l.XS)/2, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, + base_rank=KING, suit=i, dir=-1)) + x += l.XS + n = 0 + y = l.YM+l.YS + for i in rows: + x = l.XM + for j in range(i): + stack = BasicRowStack(x, y, self, max_move=1, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + x += l.XS+3*l.XOFFSET + n += 1 + y += l.YS + + x, y = l.XM+(w-l.XM-8*l.XS)/2, h-l.YS + for i in range(8): + s.reserves.append(ReserveStack(x, y, self)) + x += l.XS + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class LittleBillie(BuffaloBill): + def createGame(self): + #BuffaloBill.createGame(self, rows=(8, 8, 8)) + BuffaloBill.createGame(self, rows=(6,6,6,6)) + def startGame(self): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + BuffaloBill.startGame(self) + + +# register the game +registerGame(GameInfo(338, BuffaloBill, "Buffalo Bill", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(421, LittleBillie, "Little Billie", + GI.GT_2DECK_TYPE, 2, 0)) + + diff --git a/pysollib/games/calculation.py b/pysollib/games/calculation.py new file mode 100644 index 0000000000..35de5cb4d0 --- /dev/null +++ b/pysollib/games/calculation.py @@ -0,0 +1,278 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Calculation_Hint(DefaultHint): + # FIXME: demo logic is a complete nonsense + def _getMoveWasteScore(self, score, color, r, t, pile, rpile): + assert r is self.game.s.waste and len(pile) == 1 + score = 30000 + if len(t.cards) == 0: + score = score - (KING - r.cards[0].rank) * 1000 + elif t.cards[-1].rank < r.cards[0].rank: + score = 10000 + t.cards[-1].rank - len(t.cards) + elif t.cards[-1].rank == r.cards[0].rank: + score = 20000 + else: + score = score - (t.cards[-1].rank - r.cards[0].rank) * 1000 + return score, color + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class BetsyRoss_Foundation(RK_FoundationStack): + def updateText(self): + if self.game.preview > 1: + return + if self.texts.misc: + if len(self.cards) == 0: + rank = self.cap.base_rank + self.texts.misc.config(text=RANKS[rank]) + elif len(self.cards) == self.cap.max_cards: + self.texts.misc.config(text="") + else: + rank = (self.cards[-1].rank + self.cap.dir) % self.cap.mod + self.texts.misc.config(text=RANKS[rank]) + + +class Calculation_Foundation(BetsyRoss_Foundation): + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + +class Calculation_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts any one card from the Waste pile + return from_stack is self.game.s.waste and len(cards) == 1 + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + def getHelp(self): + return _('Row. Build regardless of rank and suit.') + + +# /*********************************************************************** +# // Calculation +# ************************************************************************/ + +class Calculation(Game): + Hint_Class = Calculation_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable in default window size) + h = max(2*l.YS, 20*l.YOFFSET) + self.setSize(5.5*l.XS+l.XM+200, l.YM + l.YS + 30 + h) + + # create stacks + x0 = l.XM + l.XS * 3 / 2 + x, y = x0, l.YM + for i in range(4): + stack = Calculation_Foundation(x, y, self, base_rank=i, mod=13, dir=i+1) + s.foundations.append(stack) + stack.texts.misc = MfxCanvasText(self.canvas, + x + l.CW / 2, y + l.YS, + anchor="n", + font=self.app.getFont("canvas_default")) + x = x + l.XS + help = (_('''\ +1: 2 3 4 5 6 7 8 9 T J Q K +2: 4 6 8 T Q A 3 5 7 9 J K +3: 6 9 Q 2 5 8 J A 4 7 T K +4: 8 Q 3 7 J 2 6 T A 5 9 K''')) + self.texts.help = MfxCanvasText(self.canvas, x + l.XM, y + l.CH / 2, text=help, + anchor="w", font=self.app.getFont("canvas_fixed")) + x = x0 + y = l.YM + l.YS + 30 + for i in range(4): + s.rows.append(Calculation_RowStack(x, y, self, max_move=1, max_accept=1)) + x = x + l.XS + self.setRegion(s.rows, (-999, y, 999999, 999999)) + x = l.XM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "nn") + y = y + l.YS + s.waste = WasteStack(x, y, self, max_cards=1) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # prepare first cards + topcards = [ None ] * 4 + for c in cards[:]: + if c.rank <= 3 and topcards[c.rank] is None: + topcards[c.rank] = c + cards.remove(c) + topcards.reverse() + return cards + topcards + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealCards() # deal first card to WasteStack + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# // Hopscotch +# ************************************************************************/ + +class Hopscotch(Calculation): + def _shuffleHook(self, cards): + # prepare first cards + topcards = [ None ] * 4 + for c in cards[:]: + if c.suit == 0 and c.rank <= 3 and topcards[c.rank] is None: + topcards[c.rank] = c + cards.remove(c) + topcards.reverse() + return cards + topcards + + +# /*********************************************************************** +# // Betsy Ross +# ************************************************************************/ + +class BetsyRoss(Calculation): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(5.5*l.XS+l.XM+200, l.YM + l.YS + 30 + 3*l.YS) + + # create stacks + x0 = l.XM + l.XS * 3 / 2 + x, y = x0, l.YM + for i in range(4): + stack = BetsyRoss_Foundation(x, y, self, base_rank=i, + max_cards=1, max_move=0, max_accept=0) + s.foundations.append(stack) + x = x + l.XS + x = x0 + y = l.YM + l.YS + 30 + for i in range(4): + stack = BetsyRoss_Foundation(x, y, self, base_rank=2*i+1, mod=13, dir=i+1, + max_cards=12, max_move=0) + stack.texts.misc = MfxCanvasText(self.canvas, x + l.CW / 2, y - l.YM, + anchor="s", font=self.app.getFont("canvas_default")) + s.foundations.append(stack) + x = x + l.XS + help = (_('''\ +1: 2 3 4 5 6 7 8 9 T J Q K +2: 4 6 8 T Q A 3 5 7 9 J K +3: 6 9 Q 2 5 8 J A 4 7 T K +4: 8 Q 3 7 J 2 6 T A 5 9 K''')) + self.texts.help = MfxCanvasText(self.canvas, x + l.XM, y + l.CH / 2, text=help, + anchor="w", font=self.app.getFont("canvas_fixed")) + x = l.XM + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "nn") + y = y + l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def _shuffleHook(self, cards): + # prepare first cards + topcards = [ None ] * 8 + for c in cards[:]: + if c.rank <= 3 and topcards[c.rank] is None: + topcards[c.rank] = c + cards.remove(c) + elif c.rank in (1, 3, 5, 7): + i = 4 + (c.rank - 1) / 2 + if topcards[i] is None: + topcards[i] = c + cards.remove(c) + topcards.reverse() + return cards + topcards + + +# register the game +registerGame(GameInfo(256, Calculation, "Calculation", + GI.GT_1DECK_TYPE, 1, 0, + altnames=("Progression",) )) +registerGame(GameInfo(94, Hopscotch, "Hopscotch", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(134, BetsyRoss, "Betsy Ross", + GI.GT_1DECK_TYPE, 1, 2, + altnames=("Fairest", "Four Kings", "Musical Patience", + "Quadruple Alliance", "Plus Belle") )) + diff --git a/pysollib/games/camelot.py b/pysollib/games/camelot.py new file mode 100644 index 0000000000..16ed9edbed --- /dev/null +++ b/pysollib/games/camelot.py @@ -0,0 +1,219 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Camelot +# ************************************************************************/ + +class Camelot_Hint(AbstractHint): + + def computeHints(self): + game = self.game + if game.is_fill: + nhints = 0 + i = 0 + for r in game.s.rows: + i += 1 + if not r.cards: + continue + if r.cards[0].rank == 9: + self.addHint(5000, 1, r, game.s.foundations[0]) + nhints += 1 + continue + for t in game.s.rows[i:]: + if t.acceptsCards(r, [r.cards[0]]): + self.addHint(5000, 1, r, t) + nhints += 1 + if nhints: + return + if game.s.talon.cards: + for r in game.s.rows: + if r.acceptsCards(game.s.talon, [game.s.talon.cards[-1]]): + self.addHint(5000, 1, game.s.talon, r) + + +class Camelot_RowStack(ReserveStack): + + def acceptsCards(self, from_stack, cards): + if from_stack is self.game.s.talon: + if len(self.cards) > 0: + return False + cr = cards[0].rank + if cr == KING: + return self.id in (0, 3, 12, 15) + elif cr == QUEEN: + return self.id in (1, 2, 13, 14) + elif cr == JACK: + return self.id in (4, 7, 8, 11) + return True + else: + if len(self.cards) == 0: + return False + return self.cards[-1].rank + cards[0].rank == 8 + + def canMoveCards(self, cards): + if not self.game.is_fill: + return False + return cards[0].rank not in (KING, QUEEN, JACK) + + def clickHandler(self, event): + game = self.game + if game.is_fill and self.cards and self.cards[0].rank == 9: + game.playSample("autodrop", priority=20) + self.playMoveMove(1, game.s.foundations[0], sound=0) + self.fillStack() + return True + return False + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + if not to_stack is self.game.s.foundations[0]: + self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow) + else: + ReserveStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + f = game.s.foundations[0] + game.updateStackMove(game.s.talon, 2|16) # for undo + if not game.demo: + game.playSample("droppair", priority=200) + game.moveMove(n, self, f, frames=frames, shadow=shadow) + game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + self.fillStack() + other_stack.fillStack() + game.updateStackMove(game.s.talon, 1|16) # for redo + game.leaveState(old_state) + + +class Camelot_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + return True + + +class Camelot_Talon(OpenTalonStack): + def fillStack(self): + old_state = self.game.enterState(self.game.S_FILL) + self.game.saveStateMove(2|16) # for undo + self.game.is_fill = self.game.isRowsFill() + self.game.saveStateMove(1|16) # for redo + self.game.leaveState(old_state) + OpenTalonStack.fillStack(self) + + +class Camelot(Game): + + Talon_Class = Camelot_Talon + RowStack_Class = StackWrapper(Camelot_RowStack, max_move=0) + Hint_Class = Camelot_Hint + + # game variables + is_fill = False + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + # set window + w = l.XS + self.setSize(l.XM + w + 4*l.XS + w + l.XS, l.YM + 4*l.YS) + # create stacks + for i in range(4): + for j in range(4): + k = i+j*4 + x, y = l.XM + w + j*l.XS, l.YM + i*l.YS + s.rows.append(self.RowStack_Class(x, y, self)) + x, y = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + x, y = l.XM + w + 4*l.XS + w, l.YM + s.foundations.append(Camelot_Foundation(x, y, self, + suit=ANY_SUIT, dir=0, base_rank=ANY_RANK, + max_accept=0, max_move=0, max_cards=52)) + # define stack-groups + l.defaultStackGroups() + return l + + # + # game overrides + # + + def startGame(self): + self.is_fill = False + self.nnn = 0 + self.s.talon.fillStack() + + + def isGameWon(self): + for i in (5, 6, 9, 10): + if len(self.s.rows[i].cards) != 0: + return False + return len(self.s.talon.cards) == 0 + + + def isRowsFill(self): + for i in range(16): + if len(self.s.rows[i].cards) == 0: + return False + return True + + def _restoreGameHook(self, game): + self.is_fill = game.loadinfo.is_fill + + def _loadGameHook(self, p): + self.loadinfo.addattr(is_fill=p.load()) + + def _saveGameHook(self, p): + p.dump(self.is_fill) + + def getAutoStacks(self, event=None): + return ((), (), ()) + + def setState(self, state): + # restore saved vars (from undo/redo) + self.is_fill = state[0] + + def getState(self): + # save vars (for undo/redo) + return [self.is_fill] + + + +# register the game +registerGame(GameInfo(280, Camelot, "Camelot", + GI.GT_1DECK_TYPE, 1, 0)) + diff --git a/pysollib/games/canfield.py b/pysollib/games/canfield.py new file mode 100644 index 0000000000..13d8538bb6 --- /dev/null +++ b/pysollib/games/canfield.py @@ -0,0 +1,710 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Andrew Csillag +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Canfield_Hint(CautiousDefaultHint): + # FIXME: demo is not too clever in this game + + # Score for moving a pile (usually a single card) from the WasteStack. + def _getMoveWasteScore(self, score, color, r, t, pile, rpile): + score, color = CautiousDefaultHint._getMovePileScore(self, score, color, r, t, pile, rpile) + # we prefer moving cards from the waste over everything else + return score + 100000, color + + +# /*********************************************************************** +# // a Canfield row stack only accepts a full other row stack +# // (cannot move part of a sequence from row to row) +# ************************************************************************/ + +class Canfield_AC_RowStack(AC_RowStack): + def basicAcceptsCards(self, from_stack, cards): + if from_stack in self.game.s.rows: + if len(cards) != 1 and len(cards) != len(from_stack.cards): + return 0 + return AC_RowStack.basicAcceptsCards(self, from_stack, cards) + + +class Canfield_SS_RowStack(SS_RowStack): + def basicAcceptsCards(self, from_stack, cards): + if from_stack in self.game.s.rows: + if len(cards) != 1 and len(cards) != len(from_stack.cards): + return 0 + return SS_RowStack.basicAcceptsCards(self, from_stack, cards) + + +class Canfield_RK_RowStack(RK_RowStack): + def basicAcceptsCards(self, from_stack, cards): + if from_stack in self.game.s.rows: + if len(cards) != 1 and len(cards) != len(from_stack.cards): + return 0 + return RK_RowStack.basicAcceptsCards(self, from_stack, cards) + + +# /*********************************************************************** +# // Canfield +# ************************************************************************/ + +class Canfield(Game): + Foundation_Class = SS_FoundationStack + RowStack_Class = StackWrapper(Canfield_AC_RowStack, mod=13) + ReserveStack_Class = OpenStack + Hint_Class = Canfield_Hint + + INITIAL_RESERVE_CARDS = 13 + INITIAL_RESERVE_FACEUP = 0 + FILL_EMPTY_ROWS = 1 + + # + # game layout + # + + def createGame(self, rows=4, max_rounds=-1, num_deal=3, text=True): + # create layout + l, s = Layout(self), self.s + decks = self.gameinfo.decks + + # set window + # (piles up to 20 cards are playable in default window size) + h = max(3*l.YS, l.YS+self.INITIAL_RESERVE_CARDS*l.YOFFSET) + self.setSize(l.XM + (2+max(rows, 4*decks))*l.XS + l.XM, l.YM + l.YS + 20 + h) + + # extra settings + self.base_card = None + + # create stacks + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds, num_deal=num_deal) + l.createText(s.talon, "s") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "s") + x = x + l.XM + for i in range(4): + for j in range(decks): + x = x + l.XS + s.foundations.append(self.Foundation_Class(x, y, self, i, mod=13, max_move=0)) + if text: + if rows >= 4 * decks: + tx, ty, ta, tf = l.getTextAttr(None, "se") + tx, ty = x + tx + l.XM, y + ty + else: + tx, ty, ta, tf = l.getTextAttr(None, "s") + tx, ty = x + tx, y + ty + l.YM + font = self.app.getFont("canvas_default") + self.texts.info = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=font) + x, y = l.XM, l.YM + l.YS + 20 + s.reserves.append(self.ReserveStack_Class(x, y, self)) + if self.INITIAL_RESERVE_FACEUP == 1: + s.reserves[0].CARD_YOFFSET = l.YOFFSET ##min(l.YOFFSET, 14) + else: + s.reserves[0].CARD_YOFFSET = 10 + x = l.XM + 2 * l.XS + l.XM + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + + # define stack-groups + l.defaultStackGroups() + + # + # game extras + # + + def updateText(self): + if self.preview > 1: + return + if not self.texts.info: + return + if not self.base_card: + t = "" + else: + t = RANKS[self.base_card.rank] + self.texts.info.config(text=t) + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.base_card = None + self.updateText() + # deal base_card to Foundations, update foundations cap.base_rank + self.base_card = self.s.talon.getCard() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + n = self.base_card.suit * self.gameinfo.decks + if self.s.foundations[n].cards: + assert self.gameinfo.decks > 1 + n = n + 1 + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.foundations[n]) + self.updateText() + # fill the Reserve + for i in range(self.INITIAL_RESERVE_CARDS): + if self.INITIAL_RESERVE_FACEUP: + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.reserves[0], frames=4, shadow=0) + if self.s.reserves[0].canFlipCard(): + self.flipMove(self.s.reserves[0]) + self.s.talon.dealRow(reverse=1) + self.s.talon.dealCards() # deal first 3 cards to WasteStack + + def fillStack(self, stack): + if stack in self.s.rows and self.s.reserves: + if self.FILL_EMPTY_ROWS: + if not stack.cards and self.s.reserves[0].cards: + if not self.s.reserves[0].cards[-1].face_up: + self.s.reserves[0].flipMove() + self.s.reserves[0].moveMove(1, stack) + elif stack in self.s.reserves: + if stack.canFlipCard(): + stack.flipMove() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + +# /*********************************************************************** +# // Superior Canfield +# ************************************************************************/ + +class SuperiorCanfield(Canfield): + INITIAL_RESERVE_FACEUP = 1 + FILL_EMPTY_ROWS = 0 + + +# /*********************************************************************** +# // Rainfall +# ************************************************************************/ + +class Rainfall(Canfield): + def createGame(self): + Canfield.createGame(self, max_rounds=3, num_deal=1) + + +# /*********************************************************************** +# // Rainbow +# ************************************************************************/ + +class Rainbow(Canfield): + RowStack_Class = StackWrapper(Canfield_RK_RowStack, mod=13) + + def createGame(self): + Canfield.createGame(self, max_rounds=1, num_deal=1) + + +# /*********************************************************************** +# // Storehouse (aka Straight Up) +# ************************************************************************/ + +class Storehouse(Canfield): + RowStack_Class = StackWrapper(Canfield_SS_RowStack, mod=13) + + def createGame(self): + Canfield.createGame(self, max_rounds=3, num_deal=1) + + def _shuffleHook(self, cards): + # move Twos to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 1, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations[:3]) + Canfield.startGame(self) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + def updateText(self): + pass + + +# /*********************************************************************** +# // Chameleon (aka Kansas) +# ************************************************************************/ + +class Chameleon(Canfield): + RowStack_Class = StackWrapper(Canfield_RK_RowStack, mod=13) + + INITIAL_RESERVE_CARDS = 12 + + def createGame(self): + Canfield.createGame(self, rows=3, max_rounds=1, num_deal=1) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank) + + +# /*********************************************************************** +# // Double Canfield (Canfield with 2 decks and 5 rows) +# ************************************************************************/ + +class DoubleCanfield(Canfield): + def createGame(self): + Canfield.createGame(self, rows=5) + + +# /*********************************************************************** +# // American Toad +# ************************************************************************/ + +class AmericanToad(Canfield): + RowStack_Class = StackWrapper(Canfield_SS_RowStack, mod=13) + + INITIAL_RESERVE_CARDS = 20 + INITIAL_RESERVE_FACEUP = 1 + + def createGame(self): + Canfield.createGame(self, rows=8, max_rounds=2, num_deal=1) + + +# /*********************************************************************** +# // Variegated Canfield +# ************************************************************************/ + +class VariegatedCanfield(Canfield): + RowStack_Class = Canfield_AC_RowStack + + INITIAL_RESERVE_FACEUP = 1 + + def createGame(self): + Canfield.createGame(self, rows=5, max_rounds=3) + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations[:7]) + Canfield.startGame(self) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + ((card1.rank + 1) == card2.rank or (card2.rank + 1) == card1.rank)) + + def updateText(self): + pass + + +# /*********************************************************************** +# // Eagle Wing +# ************************************************************************/ + +class EagleWing_ReserveStack(OpenStack): + def canFlipCard(self): + return len(self.cards) == 1 and not self.cards[-1].face_up + + +class EagleWing(Canfield): + RowStack_Class = StackWrapper(SS_RowStack, mod=13, max_move=1, max_cards=3) + ReserveStack_Class = EagleWing_ReserveStack + + def createGame(self): + ##Canfield.createGame(self, rows=8, max_rounds=3, num_deal=1) + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 9*l.XS + l.XM, l.YM + 4*l.YS) + + # extra settings + self.base_card = None + + # create stacks + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=3, num_deal=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + for i in range(4): + x = l.XM + (i+3)*l.XS + s.foundations.append(self.Foundation_Class(x, y, self, i, mod=13, max_move=0)) + tx, ty, ta, tf = l.getTextAttr(None, "se") + tx, ty = x + tx + l.XM, y + ty + font = self.app.getFont("canvas_default") + self.texts.info = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=font) + ry = l.YM + 2*l.YS + for i in range(8): + x = l.XM + (i + (i >= 4))*l.XS + y = ry - (0.2, 0.4, 0.6, 0.4, 0.4, 0.6, 0.4, 0.2)[i]*l.CH + s.rows.append(self.RowStack_Class(x, y, self)) + x, y = l.XM + 4*l.XS, ry + s.reserves.append(self.ReserveStack_Class(x, y, self)) + ##s.reserves[0].CARD_YOFFSET = 0 + l.createText(s.reserves[0], "ss") + + # define stack-groups + l.defaultStackGroups() + + +# /*********************************************************************** +# // Gate +# // Little Gate +# ************************************************************************/ + +class Gate(Game): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+max(8*l.XS, 6*l.XS+8*l.XOFFSET), l.YM+3*l.YS+12*l.YOFFSET + self.setSize(w, h) + + # create stacks + y = l.YM + for x in (l.XM+(w-(l.XM+8*l.XS))/2, w-l.XS-4*l.XOFFSET): + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.reserves.append(stack) + x, y = l.XM+2*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(8): + s.rows.append(AC_RowStack(x, y, self)) + x += l.XS + s.talon = WasteTalonStack(l.XM, h-l.YS, self, max_rounds=1) + l.createText(s.talon, "n") + s.waste = WasteStack(l.XM+l.XS, h-l.YS, self) + l.createText(s.waste, "n") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def fillStack(self, stack): + r1, r2 = self.s.reserves + if stack in self.s.rows and not stack.cards: + from_stack = None + if r1.cards or r2.cards: + from_stack = r1 + if len(r1.cards) < len(r2.cards): + from_stack = r2 + elif self.s.waste.cards: + from_stack = self.s.waste + if from_stack: + from_stack.moveMove(1, stack) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + abs(card1.rank-card2.rank) == 1) + + +class LittleGate(Gate): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+7*l.XS, l.YM+2*l.YS+12*l.YOFFSET + self.setSize(w, h) + + # create stacks + y = 4*l.YM+l.YS + for x in (l.XM, w-l.XS): + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.reserves.append(stack) + x, y = l.XM+3*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + x, y = int(l.XM+1.5*l.XS), 4*l.YM+l.YS + for i in range(4): + s.rows.append(AC_RowStack(x, y, self)) + x += l.XS + s.talon = WasteTalonStack(l.XM, l.YM, self, max_rounds=1) + l.createText(s.talon, "s") + s.waste = WasteStack(l.XM+l.XS, l.YM, self) + l.createText(s.waste, "s") + + # define stack-groups + l.defaultStackGroups() + + +# /*********************************************************************** +# // Munger +# ************************************************************************/ + +class Munger(Canfield): + + RowStack_Class = StackWrapper(AC_RowStack, base_rank=KING) + + FILL_EMPTY_ROWS = 0 + + def createGame(self): + Canfield.createGame(self, rows=7, max_rounds=1, num_deal=1) + + def startGame(self): + self.s.talon.dealRow(frames=0, flip=0) + self.s.talon.dealRow(frames=0) + self.s.talon.dealRow(frames=0, flip=0) + self.startDealSample() + self.s.talon.dealRow() + for i in range(7): + self.moveMove(1, self.s.talon, self.s.reserves[0], frames=4, shadow=0) + self.flipMove(self.s.reserves[0]) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + abs(card1.rank-card2.rank) == 1) + + def _restoreGameHook(self, game): + pass + def _loadGameHook(self, p): + pass + def _saveGameHook(self, p): + pass + + +# /*********************************************************************** +# // Triple Canfield +# ************************************************************************/ + +class TripleCanfield(Canfield): + INITIAL_RESERVE_CARDS = 26 + def createGame(self): + Canfield.createGame(self, rows=7) + + +# /*********************************************************************** +# // Acme +# ************************************************************************/ + +class Acme(Canfield): + Foundation_Class = SS_FoundationStack + RowStack_Class = StackWrapper(SS_RowStack, max_move=1) + Hint_Class = Canfield_Hint + + def createGame(self): + Canfield.createGame(self, max_rounds=2, num_deal=1) + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + for i in range(13): + self.moveMove(1, self.s.talon, self.s.reserves[0], frames=4, shadow=0) + self.flipMove(self.s.reserves[0]) + self.s.talon.dealRow(reverse=1) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + abs(card1.rank-card2.rank) == 1) + + def updateText(self): + pass + def _restoreGameHook(self, game): + pass + def _loadGameHook(self, p): + pass + def _saveGameHook(self, p): + pass + + +# /*********************************************************************** +# // Duke +# ************************************************************************/ + +class Duke(Game): + + def createGame(self): + l, s = Layout(self), self.s + + w, h = l.XM+6*l.XS+4*l.XOFFSET, l.YM+2*l.YS+12*l.YOFFSET + self.setSize(w, h) + + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, 's') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 's') + x += l.XS+4*l.XOFFSET + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + x0, y0, w = l.XM, 3*l.YM+l.YS, l.XS+2*l.XOFFSET + for i, j in ((0,0), (0,1), (1,0), (1,1)): + x, y = x0+i*w, y0+j*l.YS + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.reserves.append(stack) + x, y = l.XM+2*l.XS+4*l.XOFFSET, l.YM+l.YS + for i in range(4): + s.rows.append(AC_RowStack(x, y, self)) + x += l.XS + + l.defaultStackGroups() + + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + abs(card1.rank-card2.rank) == 1) + + +# /*********************************************************************** +# // Minerva +# ************************************************************************/ + +class Minerva(Canfield): + RowStack_Class = StackWrapper(AC_RowStack, base_rank=KING) + + INITIAL_RESERVE_CARDS = 11 + INITIAL_RESERVE_FACEUP = 1 + FILL_EMPTY_ROWS = 0 + + def createGame(self): + Canfield.createGame(self, rows=7, max_rounds=2, num_deal=1, text=False) + + def startGame(self): + for i in range(self.INITIAL_RESERVE_CARDS): + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.reserves[0], frames=0, shadow=0) + flip = False + for i in range(3): + self.s.talon.dealRow(flip=flip, frames=0) + flip = not flip + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + +# register the game +registerGame(GameInfo(105, Canfield, "Canfield", # was: 262 + GI.GT_CANFIELD | GI.GT_CONTRIB, 1, -1)) +registerGame(GameInfo(101, SuperiorCanfield, "Superior Canfield", + GI.GT_CANFIELD, 1, -1)) +registerGame(GameInfo(99, Rainfall, "Rainfall", + GI.GT_CANFIELD | GI.GT_ORIGINAL, 1, 2)) +registerGame(GameInfo(108, Rainbow, "Rainbow", + GI.GT_CANFIELD, 1, 0)) +registerGame(GameInfo(100, Storehouse, "Storehouse", + GI.GT_CANFIELD, 1, 2, + altnames=("Provisions", "Straight Up", "Thirteen Up") )) +registerGame(GameInfo(43, Chameleon, "Chameleon", + GI.GT_CANFIELD, 1, 0, + altnames="Kansas")) +registerGame(GameInfo(106, DoubleCanfield, "Double Canfield", # was: 22 + GI.GT_CANFIELD, 2, -1)) +registerGame(GameInfo(103, AmericanToad, "American Toad", + GI.GT_CANFIELD, 2, 1)) +registerGame(GameInfo(102, VariegatedCanfield, "Variegated Canfield", + GI.GT_CANFIELD, 2, 2)) +registerGame(GameInfo(112, EagleWing, "Eagle Wing", + GI.GT_CANFIELD, 1, 2)) +registerGame(GameInfo(315, Gate, "Gate", + GI.GT_CANFIELD, 1, 0)) +registerGame(GameInfo(316, LittleGate, "Little Gate", + GI.GT_CANFIELD, 1, 0)) +registerGame(GameInfo(360, Munger, "Munger", + GI.GT_CANFIELD, 1, 0)) +registerGame(GameInfo(396, TripleCanfield, "Triple Canfield", + GI.GT_CANFIELD, 3, -1)) +registerGame(GameInfo(403, Acme, "Acme", + GI.GT_CANFIELD, 1, 1)) +registerGame(GameInfo(413, Duke, "Duke", + GI.GT_CANFIELD, 1, 2)) +registerGame(GameInfo(422, Minerva, "Minerva", + GI.GT_CANFIELD, 1, 1)) + diff --git a/pysollib/games/capricieuse.py b/pysollib/games/capricieuse.py new file mode 100644 index 0000000000..dac3860d41 --- /dev/null +++ b/pysollib/games/capricieuse.py @@ -0,0 +1,145 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Capricieuse +# ************************************************************************/ + +class Capricieuse_Talon(TalonStack): + + def canDealCards(self): + if self.round == self.max_rounds: + return False + return not self.game.isGameWon() + + def dealCards(self, sound=0): + # move all cards to the Talon, shuffle and redeal + lr = len(self.game.s.rows) + num_cards = 0 + assert len(self.cards) == 0 + for r in self.game.s.rows[::-1]: + for i in range(len(r.cards)): + num_cards = num_cards + 1 + self.game.moveMove(1, r, self, frames=0) + assert len(self.cards) == num_cards + if num_cards == 0: # game already finished + return 0 + # shuffle + self.game.shuffleStackMove(self) + # redeal + self.game.nextRoundMove(self) + self.game.startDealSample() + for i in range(lr): + k = min(lr, len(self.cards)) + for j in range(k): + self.game.moveMove(1, self, self.game.s.rows[j], frames=4) + # done + self.game.stopSamples() + assert len(self.cards) == 0 + return num_cards + + +class Capricieuse(Game): + + Talon_Class = StackWrapper(Capricieuse_Talon, max_rounds=3) + RowStack_Class = UD_SS_RowStack + + # + # game layout + # + + def createGame(self, **layout): + + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+12*l.XS, l.YM+l.YS+20*l.YOFFSET) + + # create stacks + x, y, = l.XM+2*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x = x + l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=KING, dir=-1)) + x = x + l.XS + x, y, = l.XM, y + l.YS + for i in range(12): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1)) + x = x + l.XS + s.talon = self.Talon_Class(l.XM, l.YM, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(7): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(self.s.foundations) + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToBottom(cards, lambda c: (c.deck == 0 and c.rank in (0, 12), (c.rank, c.suit)), 8) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % stack1.cap.mod == card2.rank or + (card2.rank + 1) % stack1.cap.mod == card1.rank)) + + +# /*********************************************************************** +# // Nationale +# ************************************************************************/ + +class Nationale(Capricieuse): + Talon_Class = InitialDealTalonStack + RowStack_Class = StackWrapper(UD_SS_RowStack, mod=13) + + +# register the game +registerGame(GameInfo(292, Capricieuse, "Capricieuse", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 2, 2)) +registerGame(GameInfo(293, Nationale, "Nationale", + GI.GT_BAKERS_DOZEN | GI.GT_OPEN, 2, 0)) + diff --git a/pysollib/games/contrib/__init__.py b/pysollib/games/contrib/__init__.py new file mode 100644 index 0000000000..bec3b91444 --- /dev/null +++ b/pysollib/games/contrib/__init__.py @@ -0,0 +1 @@ +import sanibel diff --git a/pysollib/games/contrib/sanibel.py b/pysollib/games/contrib/sanibel.py new file mode 100644 index 0000000000..6760987f89 --- /dev/null +++ b/pysollib/games/contrib/sanibel.py @@ -0,0 +1,75 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## Copyright (C) 1998-2000 Markus Franz Xaver Johannes Oberhumer +## +## Sanibel +## Copyright (C) 1998,2000 John Stoneham +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.games.gypsy import Gypsy +from pysollib.games.yukon import Yukon_Hint + +# /************************************************************************ +# // Sanibel +# // play similar to Yukon +# *************************************************************************/ + +class Sanibel(Gypsy): + Layout_Method = Layout.klondikeLayout + Talon_Class = StackWrapper(WasteTalonStack, max_rounds=1) + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + RowStack_Class = Yukon_AC_RowStack + Hint_Class = Yukon_Hint + + def createGame(self): + Gypsy.createGame(self, rows=10, waste=1, playcards=23) + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(flip=0, frames=0) + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def getHighlightPilesStacks(self): + return () + + +registerGame(GameInfo(201, Sanibel, "Sanibel", + GI.GT_YUKON | GI.GT_CONTRIB | GI.GT_ORIGINAL, 2, 0)) + diff --git a/pysollib/games/curdsandwhey.py b/pysollib/games/curdsandwhey.py new file mode 100644 index 0000000000..26235fdb9a --- /dev/null +++ b/pysollib/games/curdsandwhey.py @@ -0,0 +1,327 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Curds and Whey +# // Miss Muffet +# // Nordic +# ************************************************************************/ + +class CurdsAndWhey_RowStack(BasicRowStack): + + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return False + if not self.cards: + return True + c1, c2 = self.cards[-1], cards[0] + if c1.suit == c2.suit: + return c1.rank == c2.rank+1 + return c1.rank == c2.rank + + def canMoveCards(self, cards): + return isSameSuitSequence(cards) or isRankSequence(cards, dir=0) + + def getHelp(self): + return _('Row. Build down by suit or of the same rank.') + + +class CurdsAndWhey(Game): + + Hint_Class = CautiousDefaultHint + RowStack_Class = StackWrapper(CurdsAndWhey_RowStack, base_rank=KING, + max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS) + + # + # game layout + # + + def createGame(self, rows=13): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+rows*l.XS, l.YM+l.YS+16*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(rows): + stack = self.RowStack_Class(x, y, self) + s.rows.append(stack) + x += l.XS + + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def isGameWon(self): + for s in self.s.rows: + if s.cards: + if len(s.cards) != 13 or not isSameSuitSequence(s.cards): + return False + return True + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank or ( + card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1) + + +class MissMuffet(CurdsAndWhey): + + def createGame(self): + CurdsAndWhey.createGame(self, rows=10) + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.s.talon.dealRow(rows=[self.s.rows[0], self.s.rows[-1]], frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class Nordic(MissMuffet): + RowStack_Class = StackWrapper(CurdsAndWhey_RowStack, base_rank=ANY_RANK, + max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS) + + +# /*********************************************************************** +# // Dumfries +# // Galloway +# // Robin +# ************************************************************************/ + +class Dumfries_TalonStack(OpenTalonStack): + rightclickHandler = OpenStack.rightclickHandler + +class Dumfries_RowStack(BasicRowStack): + + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return False + if not self.cards: + return True + c1, c2 = self.cards[-1], cards[0] + if c1.color == c2.color: + return False + return c1.rank == c2.rank or c1.rank == c2.rank+1 + + def canMoveCards(self, cards): + return len(cards) == 1 or len(cards) == len(self.cards) + +class Dumfries(Game): + + ##Hint_Class = KlondikeType_Hint + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=0, texts=1, playcards=20) + apply(Layout.klondikeLayout, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = Dumfries_TalonStack(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(SS_FoundationStack(r.x, r.y, self, + suit=r.suit)) + for r in l.s.rows: + s.rows.append(Dumfries_RowStack(r.x, r.y, self, + max_move=UNLIMITED_MOVES, + max_accept=UNLIMITED_ACCEPTS)) + # default + l.defaultAll() + self.sg.dropstacks.append(s.talon) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.fillStack() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) in (0, 1) + + +class Galloway(Dumfries): + def createGame(self): + Dumfries.createGame(self, rows=7) + + +class Robin(Dumfries): + def createGame(self): + Dumfries.createGame(self, rows=12) + + + +# /*********************************************************************** +# // Arachnida +# ************************************************************************/ + +class Arachnida_RowStack(BasicRowStack): + + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return False + if not self.cards: + return True + c1, c2 = self.cards[-1], cards[0] + if c1.rank == c2.rank+1: + return True + return c1.rank == c2.rank + + def canMoveCards(self, cards): + return isSameSuitSequence(cards) or isRankSequence(cards, dir=0) + + +class Arachnida(CurdsAndWhey): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+11*l.XS, l.YM+l.YS+16*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + s.talon = DealRowTalonStack(x, y, self) + l.createText(s.talon, "ss") + x += l.XS + for i in range(10): + stack = Arachnida_RowStack(x, y, self, base_rank=ANY_RANK, + max_move=UNLIMITED_MOVES, + max_accept=UNLIMITED_ACCEPTS) + s.rows.append(stack) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(flip=0, frames=0) + self.s.talon.dealRow(rows=self.s.rows[:4], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank or abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // German Patience +# // Bavarian Patience +# ************************************************************************/ + +class GermanPatience(Game): + + def createGame(self, rows=8): + + l, s = Layout(self), self.s + + w, h = l.XM+rows*l.XS, l.YM+2*l.YS+14*l.YOFFSET + self.setSize(w, h) + + x, y = l.XM, l.YM + for i in range(rows): + s.rows.append(RK_RowStack(x, y, self, max_cards=13, mod=13, dir=1, max_move=1)) + x += l.XS + x, y = l.XM, h-l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, 'nn') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'nn') + + l.defaultStackGroups() + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + def isGameWon(self): + if self.s.waste.cards or self.s.talon.cards: + return False + for s in self.s.rows: + if s.cards: + if len(s.cards) != 13: # or not isRankSequence(s.cards): + return False + return True + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1) % 13 == card2.rank or + (card2.rank + 1) % 13 == card1.rank) + + +class BavarianPatience(GermanPatience): + def createGame(self, rows=10): + GermanPatience.createGame(self, rows=10) + + +# register the game +registerGame(GameInfo(294, CurdsAndWhey, "Curds and Whey", + GI.GT_SPIDER | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(311, Dumfries, "Dumfries", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(312, Galloway, "Galloway", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(313, Robin, "Robin", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(348, Arachnida, "Arachnida", + GI.GT_SPIDER, 2, 0)) +registerGame(GameInfo(349, MissMuffet, "Miss Muffet", + GI.GT_SPIDER | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(352, Nordic, "Nordic", + GI.GT_SPIDER | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(414, GermanPatience, "German Patience", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(415, BavarianPatience, "Bavarian Patience", + GI.GT_2DECK_TYPE, 2, 0)) + diff --git a/pysollib/games/dieboesesieben.py b/pysollib/games/dieboesesieben.py new file mode 100644 index 0000000000..2fb6f884bf --- /dev/null +++ b/pysollib/games/dieboesesieben.py @@ -0,0 +1,126 @@ +## -*- coding: utf-8 -*- +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +from pysollib.games.gypsy import DieKoenigsbergerin_Talon, DieRussische_Foundation + +# /*********************************************************************** +# // Die böse Sieben +# ************************************************************************/ + +class DieBoeseSieben_Talon(DieKoenigsbergerin_Talon): + def canDealCards(self): + return len(self.cards) or self.round != self.max_rounds + + def dealCards(self, sound=0): + if self.cards: + return DieKoenigsbergerin_Talon.dealCards(self, sound=sound) + game, num_cards = self.game, len(self.cards) + for r in game.s.rows: + while r.cards: + num_cards = num_cards + 1 + if r.cards[-1].face_up: + game.flipMove(r) + game.moveMove(1, r, self, frames=0) + assert len(self.cards) == num_cards + if sound: + game.startDealSample() + # shuffle + game.shuffleStackMove(self) + # redeal + game.nextRoundMove(self) + n = len(game.s.rows) + flip = (num_cards / n) & 1 + while self.cards: + if len(self.cards) <= n: + flip = 1 + self.dealRow(flip=flip) + flip = not flip + # done + if sound: + game.stopSamples() + return num_cards + + +class DieBoeseSieben(Game): + # + # game layout + # + + def createGame(self, rows=7): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + max(8,rows)*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in range(8): + x, y, = l.XM + i*l.XS, l.YM + s.foundations.append(DieRussische_Foundation(x, y, self, i/2, max_move=0)) + for i in range(rows): + x, y, = l.XM + (2*i+8-rows)*l.XS/2, l.YM + l.YS + s.rows.append(AC_RowStack(x, y, self)) + s.talon = DieBoeseSieben_Talon(l.XM, self.height-l.YS, self, max_rounds=2) + l.createText(s.talon, "se") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + for flip in (1, 0, 1, 0, 1, 0, 1): + self.s.talon.dealRow(flip=flip) + + +# register the game +registerGame(GameInfo(120, DieBoeseSieben, "Bad Seven", + GI.GT_2DECK_TYPE, 2, 1, + ranks=(0, 6, 7, 8, 9, 10, 11, 12), + altnames=("Die böse Sieben",) )) + diff --git a/pysollib/games/diplomat.py b/pysollib/games/diplomat.py new file mode 100644 index 0000000000..3e05366d01 --- /dev/null +++ b/pysollib/games/diplomat.py @@ -0,0 +1,183 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +from pysollib.games.fortythieves import FortyThieves_Hint + +# /*********************************************************************** +# // Diplomat +# ************************************************************************/ + +class Diplomat(Game): + Foundation_Class = SS_FoundationStack + RowStack_Class = StackWrapper(RK_RowStack, max_move=1) + Hint_Class = FortyThieves_Hint + + DEAL = (3, 1) + FILL_EMPTY_ROWS = 0 + + # + # game layout + # + + def createGame(self, max_rounds=1): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 5*l.YS) + + # create stacks + x, y = l.XM, l.YM + for i in range(8): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i/2)) + x = x + l.XS + x, y = l.XM, y + l.YS + for i in range(8): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + x, y, = l.XM, self.height - l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds) + l.createText(s.talon, "nn") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "nn") + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def startGame(self): + for i in range(self.DEAL[0]): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(self.DEAL[1]): + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def fillStack(self, stack): + if self.FILL_EMPTY_ROWS and stack in self.s.rows and not stack.cards: + old_state = self.enterState(self.S_FILL) + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + elif self.s.talon.canDealCards(): + self.s.talon.dealCards() + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank + + +# /*********************************************************************** +# // Lady Palk +# ************************************************************************/ + +class LadyPalk(Diplomat): + RowStack_Class = RK_RowStack + + +# /*********************************************************************** +# // Congress +# ************************************************************************/ + +class Congress(Diplomat): + DEAL = (0, 1) + FILL_EMPTY_ROWS = 1 + + # + # game layout (just rearrange the stacks a little bit) + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 7*l.XS, l.YM + 4*l.YS) + + # create stacks + for i in range(4): + for j in range(2): + x, y = l.XM + (4+j)*l.XS, l.YM + i*l.YS + s.foundations.append(self.Foundation_Class(x, y, self, suit=i)) + for i in range(4): + for j in range(2): + x, y = l.XM + (3+3*j)*l.XS, l.YM + i*l.YS + stack = self.RowStack_Class(x, y, self) + stack.CARD_YOFFSET = 0 + s.rows.append(stack) + x, y, = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # define stack-groups + l.defaultStackGroups() + + +# /*********************************************************************** +# // Rows of Four +# ************************************************************************/ + +class RowsOfFour(Diplomat): + def createGame(self): + Diplomat.createGame(self, max_rounds=3) + + +# register the game +registerGame(GameInfo(149, Diplomat, "Diplomat", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(151, LadyPalk, "Lady Palk", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(150, Congress, "Congress", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(433, RowsOfFour, "Rows of Four", + GI.GT_FORTY_THIEVES, 2, 2)) + diff --git a/pysollib/games/doublets.py b/pysollib/games/doublets.py new file mode 100644 index 0000000000..6b1f1ed3f8 --- /dev/null +++ b/pysollib/games/doublets.py @@ -0,0 +1,142 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // Doublets +# ************************************************************************/ + +class Doublets_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if self.cards: + # check the rank + if (2 * self.cards[-1].rank + 1) % self.cap.mod != cards[0].rank: + return 0 + return 1 + + +class Doublets(Game): + Hint_Class = CautiousDefaultHint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 5.5*l.XS, l.YM + 4*l.YS) + + # create stacks + for dx, dy in ((0, 0), (1, 0), (2, 0), (0, 1), (2, 1), (0, 2), (2, 2)): + x, y = l.XM + (2*dx+5)*l.XS/2, l.YM + (2*dy+1)*l.YS/2 + s.rows.append(ReserveStack(x, y, self)) + dx, dy = 1, 2 + x, y = l.XM + (2*dx+5)*l.XS/2, l.YM + (2*dy+1)*l.YS/2 + s.foundations.append(Doublets_Foundation(x, y, self, ANY_SUIT, + dir=0, mod=13, + max_move=0, max_cards=48)) + l.createText(s.foundations[0], "ss") +## help = "A, 2, 4, 8, 3, 6, Q, J, 9, 5, 10, 7, A, ..." +## self.texts.help = MfxCanvasText(self.canvas, x + l.CW/2, y + l.YS + l.YM, anchor="n", text=help) + x, y = l.XM, l.YM + 3*l.YS/2 + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move all Kings in the first 8 cards to the bottom + kings, topcards = [], [] + for c in cards[:]: + cards.remove(c) + if c.rank == KING: + kings.append(c) + else: + topcards.append(c) + if len(topcards) == 8: + break + return kings + cards + topcards + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealCards() # deal first card to WasteStack + + def isGameWon(self): + if self.s.talon.cards or self.s.waste.cards: + return 0 + return len(self.s.foundations[0].cards) == 48 + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards: + old_state = self.enterState(self.S_FILL) + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + elif self.s.talon.canDealCards(): + self.s.talon.dealCards() + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + +# register the game +registerGame(GameInfo(111, Doublets, "Doublets", + GI.GT_1DECK_TYPE, 1, 2)) + diff --git a/pysollib/games/eiffeltower.py b/pysollib/games/eiffeltower.py new file mode 100644 index 0000000000..9e53595a76 --- /dev/null +++ b/pysollib/games/eiffeltower.py @@ -0,0 +1,126 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Eiffel Tower +# ************************************************************************/ + +class EiffelTower_RowStack(OpenStack): + def __init__(self, x, y, game): + OpenStack.__init__(self, x, y, game, max_move=0, max_accept=1) + self.CARD_YOFFSET = 1 + + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + return self.cards[-1].rank + cards[0].rank == 12 + + +class EiffelTower(Game): + Talon_Class = WasteTalonStack + Waste_Class = WasteStack + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8.5*l.XS, l.YM + 6*l.YS) + + # create stacks + y = l.YM + for d in ((1, 2.5), (2, 2), (3, 1.5), (4, 1), (5, 0.5), (5, 0.5)): + x = l.XM + d[1] * l.XS + for i in range(d[0]): + s.rows.append(EiffelTower_RowStack(x, y, self)) + x = x + l.XS + y = y + l.YS + x = l.XM + 6 * l.XS + y = l.YM + 5 * l.YS / 2 + s.waste = self.Waste_Class(x, y, self) + l.createText(s.waste, "ss") + x = x + l.XS + s.talon = self.Talon_Class(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def isGameWon(self): + return len(self.s.talon.cards) == 0 and len(self.s.waste.cards) == 0 + + def getAutoStacks(self, event=None): + return ((), (), ()) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + card2.rank == 12 + + +# /*********************************************************************** +# // Strict Eiffel Tower +# ************************************************************************/ + +class StrictEiffelTower(EiffelTower): + Waste_Class = StackWrapper(WasteStack, max_cards=2) + + +# register the game +registerGame(GameInfo(16, EiffelTower, "Eiffel Tower", + GI.GT_PAIRING_TYPE, 2, 0)) +##registerGame(GameInfo(801, StrictEiffelTower, "Strict Eiffel Tower", +## GI.GT_PAIRING_TYPE, 2, 0)) + diff --git a/pysollib/games/fan.py b/pysollib/games/fan.py new file mode 100644 index 0000000000..cc02c7838c --- /dev/null +++ b/pysollib/games/fan.py @@ -0,0 +1,598 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Fan_Hint(CautiousDefaultHint): + # FIXME: demo is not too clever in this game + pass + + +# /*********************************************************************** +# // Fan +# ************************************************************************/ + +class Fan(Game): + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + Foundation_Class_2 = None + ReserveStack_Class = ReserveStack + RowStack_Class = KingSS_RowStack + Hint_Class = Fan_Hint + + # + # game layout + # + + def createGame(self, rows=(5,5,5,3), playcards=9, reserves=0): + # create layout + l, s = Layout(self), self.s + + # set window + # (set size so that at least 9 cards are fully playable) + w = max(2*l.XS, l.XS+(playcards-1)*l.XOFFSET) + w = min(3*l.XS, w) + w = (w + 1) & ~1 + ##print 2*l.XS, w + self.setSize(l.XM + max(rows)*w, l.YM + (1+len(rows))*l.YS) + + # create stacks + if reserves: + x, y = l.XM, l.YM + for r in range(reserves): + s.reserves.append(self.ReserveStack_Class(x, y, self)) + x += l.XS + x = (self.width - self.gameinfo.decks*4*l.XS - 2*l.XS) / 2 + dx = l.XS + else: + dx = (self.width - self.gameinfo.decks*4*l.XS)/(self.gameinfo.decks*4+1) + x, y = l.XM + dx, l.YM + dx += l.XS + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i)) + x += dx + if self.gameinfo.decks == 2: + for i in range(4): + s.foundations.append(self.Foundation_Class_2(x, y, self, suit=i)) + x += dx + for i in range(len(rows)): + x, y = l.XM, y + l.YS + for j in range(rows[i]): + stack = self.RowStack_Class(x, y, self, max_move=1, max_accept=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + x += w + x, y = self.width - l.XS, self.height - l.YS + s.talon = self.Talon_Class(x, y, self) + + # define stack-groups + l.defaultStackGroups() + return l + + # + # game overrides + # + + def startGame(self): + for i in range(2): + self.s.talon.dealRow(rows=self.s.rows[:17], frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# // Scotch Patience +# ************************************************************************/ + +class ScotchPatience(Fan): + Foundation_Class = AC_FoundationStack + RowStack_Class = StackWrapper(RK_RowStack, base_rank=NO_RANK) + + +# /*********************************************************************** +# // Shamrocks +# ************************************************************************/ + +class Shamrocks(Fan): + RowStack_Class = StackWrapper(UD_RK_RowStack, base_rank=NO_RANK, max_cards=3) + + def createGame(self): + Fan.createGame(self, playcards=4) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // La Belle Lucie (Midnight Oil) +# ************************************************************************/ + +class LaBelleLucie_Talon(TalonStack): + def canDealCards(self): + return self.round != self.max_rounds and not self.game.isGameWon() + + def dealCards(self, sound=0): + n = self.redealCards1() + if n == 0: + return 0 + self.redealCards2() + if sound: + self.game.startDealSample() + self.redealCards3() + if sound: + self.game.stopSamples() + return n + + # redeal step 1) - collect all cards, move them to the Talon + def redealCards1(self): + assert len(self.cards) == 0 + num_cards = 0 + for r in self.game.s.rows: + if r.cards: + num_cards = num_cards + len(r.cards) + self.game.moveMove(len(r.cards), r, self, frames=0) + assert len(self.cards) == num_cards + return num_cards + + # redeal step 2) - shuffle + def redealCards2(self): + assert self.round != self.max_rounds + assert self.cards + self.game.shuffleStackMove(self) + self.game.nextRoundMove(self) + + # redeal step 3) - redeal cards to stacks + def redealCards3(self, face_up=1): + # deal 3 cards to each row, and 1-3 cards to last row + to_stacks = self.game.s.rows + n = min(len(self.cards), 3*len(to_stacks)) + for i in range(3): + j = (n/3, (n+1)/3, (n+2)/3) [i] + frames = (0, 0, 4) [i] + for r in to_stacks[:j]: + if self.cards[-1].face_up != face_up: + self.game.flipMove(self) + self.game.moveMove(1, self, r, frames=frames) + + +class LaBelleLucie(Fan): + Talon_Class = StackWrapper(LaBelleLucie_Talon, max_rounds=3) + RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK) + + +# /*********************************************************************** +# // Super Flower Garden +# ************************************************************************/ + +class SuperFlowerGarden(LaBelleLucie): + RowStack_Class = StackWrapper(RK_RowStack, base_rank=NO_RANK) + + +# /*********************************************************************** +# // Three Shuffles and a Draw +# ************************************************************************/ + +class ThreeShufflesAndADraw_RowStack(SS_RowStack): + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + game, r = self.game, self.game.s.reserves[0] + if to_stack is not r: + SS_RowStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + return + f = self._canDrawCard() + assert f and game.draw_done == 0 and ncards == 1 + # 1) top card from self to reserve + game.updateStackMove(r, 2|16) # update view for undo + game.moveMove(1, self, r, frames=frames, shadow=shadow) + game.updateStackMove(r, 3|64) # update model + game.updateStackMove(r, 1|16) # update view for redo + # 2) second card from self to foundation/row + if 1 or not game.demo: + game.playSample("drop", priority=200) + if frames == 0: + frames = -1 + game.moveMove(1, self, f, frames=frames, shadow=shadow) + # 3) from reserve back to self + # (need S_FILL because the move is normally not valid) + old_state = game.enterState(game.S_FILL) + game.moveMove(1, r, self, frames=frames, shadow=shadow) + game.leaveState(old_state) + + def _canDrawCard(self): + if len(self.cards) >= 2: + pile = self.cards[-2:-1] + for s in self.game.s.foundations + self.game.s.rows: + if s is not self and s.acceptsCards(self, pile): + return s + return None + + +class ThreeShufflesAndADraw_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + if not from_stack in self.game.s.rows: + return 0 + if self.game.draw_done or not from_stack._canDrawCard(): + return 0 + return 1 + + def updateModel(self, undo, flags): + assert undo == self.game.draw_done + self.game.draw_done = not self.game.draw_done + + def updateText(self): + if self.game.preview > 1 or self.texts.misc is None: + return + t = (_("X"), _("Draw")) [self.game.draw_done == 0] + self.texts.misc.config(text=t) + + def prepareView(self): + ReserveStack.prepareView(self) + if not self.is_visible or self.game.preview > 1: + return + images = self.game.app.images + x, y = self.x + images.CARDW/2, self.y + images.CARDH/2 + self.texts.misc = MfxCanvasText(self.game.canvas, x, y, + anchor="center", + font=self.game.app.getFont("canvas_default")) + + +class ThreeShufflesAndADraw(LaBelleLucie): + RowStack_Class = StackWrapper(ThreeShufflesAndADraw_RowStack, base_rank=NO_RANK) + + def createGame(self): + l = LaBelleLucie.createGame(self) + s = self.s + # add a reserve stack + x, y = s.rows[3].x, s.rows[-1].y + s.reserves.append(ThreeShufflesAndADraw_ReserveStack(x, y, self)) + # redefine the stack-groups + l.defaultStackGroups() + # extra settings + self.draw_done = 0 + + def startGame(self): + self.draw_done = 0 + self.s.reserves[0].updateText() + LaBelleLucie.startGame(self) + + def _restoreGameHook(self, game): + self.draw_done = game.loadinfo.draw_done + + def _loadGameHook(self, p): + self.loadinfo.addattr(draw_done=p.load()) + + def _saveGameHook(self, p): + p.dump(self.draw_done) + + +# /*********************************************************************** +# // Trefoil +# ************************************************************************/ + +class Trefoil(LaBelleLucie): + GAME_VERSION = 2 + Foundation_Class = StackWrapper(SS_FoundationStack, min_cards=1) + + def createGame(self): + return Fan.createGame(self, rows=(5,5,5,1)) + + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + for i in range(2): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + + +# /*********************************************************************** +# // Intelligence +# ************************************************************************/ + +class Intelligence_Talon(LaBelleLucie_Talon): + # all Aces go to Foundations + dealToStacks = TalonStack.dealToStacksOrFoundations + + # redeal step 1) - collect all cards, move them to the Talon (face down) + def redealCards1(self): + assert len(self.cards) == 0 + r = self.game.s.reserves[0] + num_cards = len(r.cards) + if num_cards > 0: + self.game.moveMove(len(r.cards), r, self, frames=0) + for r in self.game.s.rows: + num_cards = num_cards + len(r.cards) + while r.cards: + self.game.moveMove(1, r, self, frames=0) + self.game.flipMove(self) + assert len(self.cards) == num_cards + return num_cards + + # redeal step 3) - redeal cards to stacks + def redealCards3(self, face_up=1): + for r in self.game.s.rows: + while len(r.cards) < 3: + self.dealToStacks([r], frames=4) + if not self.cards: + return + # move all remaining cards to the reserve + self.game.moveMove(len(self.cards), self, self.game.s.reserves[0], frames=0) + + +# up or down in suit +class Intelligence_RowStack(UD_SS_RowStack): + def fillStack(self): + if not self.cards: + r = self.game.s.reserves[0] + if r.cards: + r.dealRow((self,self,self), sound=1) + + +class Intelligence_ReserveStack(ReserveStack, DealRow_StackMethods): + # all Aces go to Foundations (used in r.dealRow() above) + dealToStacks = DealRow_StackMethods.dealToStacksOrFoundations + + def canFlipCard(self): + return 0 + + +class Intelligence(Fan): + + Foundation_Class = SS_FoundationStack + Foundation_Class_2 = SS_FoundationStack + Talon_Class = StackWrapper(Intelligence_Talon, max_rounds=3) + RowStack_Class = StackWrapper(Intelligence_RowStack, base_rank=NO_RANK) + + def createGame(self, rows=(5,5,5,3)): + l = Fan.createGame(self, rows) + s = self.s + # add a reserve stack + x, y = s.talon.x - l.XS, s.talon.y + s.reserves.append(Intelligence_ReserveStack(x, y, self, max_move=0, max_accept=0, max_cards=UNLIMITED_CARDS)) + l.createText(s.reserves[0], "sw") + # redefine the stack-groups + l.defaultStackGroups() + + def startGame(self): + talon = self.s.talon + for i in range(2): + talon.dealRow(frames=0) + self.startDealSample() + talon.dealRow() + # move all remaining cards to the reserve + self.moveMove(len(talon.cards), talon, self.s.reserves[0], frames=0) + + +class IntelligencePlus(Intelligence): + def createGame(self): + Intelligence.createGame(self, rows=(5,5,5,4)) + + +# /*********************************************************************** +# // House in the Wood +# // House on the Hill +# // (2 decks variants of Fan) +# ************************************************************************/ + +class HouseInTheWood(Fan): + Foundation_Class = Foundation_Class_2 = SS_FoundationStack + RowStack_Class = UD_SS_RowStack + + def createGame(self): + Fan.createGame(self, rows=(6,6,6,6,6,5)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.rows[:34], frames=0) + self.s.talon.dealRow(rows=self.s.rows[:35], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:35]) + assert len(self.s.talon.cards) == 0 + +class HouseOnTheHill(HouseInTheWood): + Foundation_Class = SS_FoundationStack + Foundation_Class_2 = StackWrapper(SS_FoundationStack, base_rank=KING, dir=-1) + + +# /*********************************************************************** +# // Clover Leaf +# ************************************************************************/ + +class CloverLeaf_RowStack(UD_SS_RowStack): + def acceptsCards(self, from_stack, cards): + if not UD_SS_RowStack.acceptsCards(self, from_stack, cards): + return False + if not self.cards: + return cards[0].rank in (ACE, KING) + return True + + +class CloverLeaf(Game): + + Hint_Class = Fan_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + playcards = 7 + w, h = l.XM+l.XS+4*(l.XS+(playcards-1)*l.XOFFSET), l.YM+4*l.YS + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(2): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + y += l.YS + for i in range(2): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i+2, + base_rank=KING, dir=-1)) + y += l.YS + + x = l.XM+l.XS + for i in range(4): + y = l.YM + for j in range(4): + stack = CloverLeaf_RowStack(x, y, self, + max_move=1, max_accept=1) + s.rows.append(stack) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + y += l.YS + x += l.XS+(playcards-1)*l.XOFFSET + + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + #self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToBottom(cards, + lambda c: ((c.rank == ACE and c.suit in (0,1)) or + (c.rank == KING and c.suit in (2,3)), + c.suit)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + abs(card1.rank-card2.rank) == 1) + + +# /*********************************************************************** +# // Free Fan +# ************************************************************************/ + +class FreeFan(Fan): + def createGame(self): + Fan.createGame(self, reserves=2) + + +# /*********************************************************************** +# // Box Fan +# ************************************************************************/ + +class BoxFan(Fan): + + RowStack_Class = KingAC_RowStack + + def createGame(self): + Fan.createGame(self, rows=(4,4,4,4)) + + def startGame(self): + for i in range(2): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# register the game +registerGame(GameInfo(56, Fan, "Fan", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(87, ScotchPatience, "Scotch Patience", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(57, Shamrocks, "Shamrocks", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(901, LaBelleLucie, "La Belle Lucie", # was: 32, 82 + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 2, + altnames=("Fair Lucy", "Midnight Oil") )) +registerGame(GameInfo(132, SuperFlowerGarden, "Super Flower Garden", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 2)) +registerGame(GameInfo(128, ThreeShufflesAndADraw, "Three Shuffles and a Draw", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 2)) +registerGame(GameInfo(88, Trefoil, "Trefoil", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 2)) +registerGame(GameInfo(227, Intelligence, "Intelligence", + GI.GT_FAN_TYPE, 2, 2)) +registerGame(GameInfo(340, IntelligencePlus, "Intelligence +", + GI.GT_FAN_TYPE, 2, 2)) +registerGame(GameInfo(268, HouseInTheWood, "House in the Wood", + GI.GT_FAN_TYPE | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(317, HouseOnTheHill, "House on the Hill", + GI.GT_FAN_TYPE | GI.GT_OPEN, 2, 0, + rules_filename='houseinthewood.html')) +registerGame(GameInfo(320, CloverLeaf, "Clover Leaf", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(347, FreeFan, "Free Fan", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(385, BoxFan, "Box Fan", + GI.GT_FAN_TYPE | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/fortythieves.py b/pysollib/games/fortythieves.py new file mode 100644 index 0000000000..fd67d7b215 --- /dev/null +++ b/pysollib/games/fortythieves.py @@ -0,0 +1,786 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class FortyThieves_Hint(CautiousDefaultHint): + # FIXME: demo is not too clever in this game + pass + + +# /*********************************************************************** +# // Forty Thieves +# // rows build down by suit +# ************************************************************************/ + +class FortyThieves(Game): + Foundation_Class = SS_FoundationStack + RowStack_Class = SS_RowStack + Hint_Class = FortyThieves_Hint + + FOUNDATION_MAX_MOVE = 1 + ROW_MAX_MOVE = 1 + DEAL = (0, 4) + FILL_EMPTY_ROWS = 0 + + # + # game layout + # + + def createGame(self, max_rounds=1, num_deal=1, rows=10, playcards=12, XCARDS=64, XOFFSET=None): + # create layout + XM = (10, 4)[rows > 10] + if XOFFSET is None: + l, s = Layout(self, XM=XM, YBOTTOM=16), self.s + else: + l, s = Layout(self, XM=XM, XOFFSET=XOFFSET, YBOTTOM=16), self.s + + # set window + # (compute best XOFFSET - up to 64/72 cards can be in the Waste) + decks = self.gameinfo.decks + maxrows = max(rows, 4*decks+2) + w1, w2 = maxrows*l.XS+l.XM, 2*l.XS + if w2 + XCARDS * l.XOFFSET > w1: + l.XOFFSET = int((w1 - w2) / XCARDS) + # (piles up to 12 cards are playable without overlap in default window size) + h = max(2*l.YS, l.YS+(playcards-1)*l.YOFFSET) + self.setSize(w1, l.YM + l.YS + h + l.YS + l.YBOTTOM) + + # create stacks + x = l.XM + (maxrows - 4*decks) * l.XS / 2 + y = l.YM + for i in range(4*decks): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i/decks, max_move=self.FOUNDATION_MAX_MOVE)) + x = x + l.XS + x = l.XM + (maxrows - rows) * l.XS / 2 + y = l.YM + l.YS + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self, max_move=self.ROW_MAX_MOVE)) + x = x + l.XS + x = self.width - l.XS + y = self.height - l.YS - l.YBOTTOM + s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds, num_deal=num_deal) + l.createText(s.talon, "s") + if max_rounds > 1: + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + x = x - l.XS + s.waste = WasteStack(x, y, self) + s.waste.CARD_XOFFSET = -l.XOFFSET + l.createText(s.waste, "s") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(self.DEAL[0]): + self.s.talon.dealRow(flip=0, frames=0) + for i in range(self.DEAL[1] - 1): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def fillStack(self, stack): + if self.FILL_EMPTY_ROWS and stack in self.s.rows and not stack.cards: + old_state = self.enterState(self.S_FILL) + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + elif self.s.talon.canDealCards(): + self.s.talon.dealCards() + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Busy Aces +# // Limited +# // Courtyard +# // Waning Moon +# // Lucas +# // Napoleon's Square +# // Carre Napoleon +# // Josephine +# // rows build down by suit +# ************************************************************************/ + +class BusyAces(FortyThieves): + DEAL = (0, 1) + + def createGame(self): + FortyThieves.createGame(self, rows=12) + + +class Limited(BusyAces): + DEAL = (0, 3) + + +class Courtyard(BusyAces): + ROW_MAX_MOVE = UNLIMITED_MOVES + FILL_EMPTY_ROWS = 1 + + +class WaningMoon(FortyThieves): + def createGame(self): + FortyThieves.createGame(self, rows=13) + + +class Lucas(WaningMoon): + ROW_MAX_MOVE = UNLIMITED_MOVES + + +class NapoleonsSquare(FortyThieves): + ROW_MAX_MOVE = UNLIMITED_MOVES + def createGame(self): + FortyThieves.createGame(self, rows=12) + + +class CarreNapoleon(FortyThieves): + RowStack_Class = StackWrapper(SS_RowStack, base_rank=KING) + + def createGame(self): + FortyThieves.createGame(self, rows=12) + + def _fillOne(self): + for r in self.s.rows: + if r.cards: + c = r.cards[-1] + for f in self.s.foundations: + if f.acceptsCards(r, [c]): + self.moveMove(1, r, f, frames=4, shadow=0) + return 1 + return 0 + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + for i in range(4): + self.s.talon.dealRow() + while True: + if not self._fillOne(): + break + self.s.talon.dealCards() + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == 0, c.suit)) + + +class Josephine(FortyThieves): + ROW_MAX_MOVE = UNLIMITED_MOVES + + +# /*********************************************************************** +# // Deuces +# ************************************************************************/ + +class Deuces(FortyThieves): + Foundation_Class = StackWrapper(SS_FoundationStack, mod=13, base_rank=1) + RowStack_Class = StackWrapper(SS_RowStack, mod=13) + + DEAL = (0, 1) + + def _shuffleHook(self, cards): + # move Twos to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 1, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + FortyThieves.startGame(self) + + +# /*********************************************************************** +# // Corona +# // Quadrangle +# ************************************************************************/ + +class Corona(FortyThieves): + FOUNDATION_MAX_MOVE = 0 + DEAL = (0, 3) + FILL_EMPTY_ROWS = 1 + + def createGame(self): + FortyThieves.createGame(self, rows=12) + + +class Quadrangle(Corona): + Foundation_Class = StackWrapper(SS_FoundationStack, mod=13, base_rank=NO_RANK) + RowStack_Class = StackWrapper(SS_RowStack, mod=13) + + def startGame(self): + FortyThieves.startGame(self) + self.s.talon.dealSingleBaseCard() + + +# /*********************************************************************** +# // Forty and Eight +# ************************************************************************/ + +class FortyAndEight(FortyThieves): + def createGame(self): + FortyThieves.createGame(self, max_rounds=2, rows=8, XCARDS=72) + + +# /*********************************************************************** +# // Little Forty +# ************************************************************************/ + +class LittleForty(FortyThieves): + RowStack_Class = Spider_SS_RowStack + + ROW_MAX_MOVE = UNLIMITED_MOVES + FILL_EMPTY_ROWS = 1 + + def createGame(self): + FortyThieves.createGame(self, max_rounds=4, num_deal=3, XOFFSET=0) + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + if to_stack.cards: + return int(from_stack.cards[-1].suit == to_stack.cards[-1].suit)+1 + return 0 + + +# /*********************************************************************** +# // Streets +# // Maria +# // Number Ten +# // Rank and File +# // Emperor +# // Triple Line +# // rows build down by alternate color +# ************************************************************************/ + +class Streets(FortyThieves): + RowStack_Class = AC_RowStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +class Maria(Streets): + def createGame(self): + Streets.createGame(self, rows=9) + + +class NumberTen(Streets): + ROW_MAX_MOVE = UNLIMITED_MOVES + DEAL = (2, 2) + + +class RankAndFile(Streets): + ROW_MAX_MOVE = UNLIMITED_MOVES + DEAL = (3, 1) + + +class Emperor(Streets): + DEAL = (3, 1) + + +class TripleLine(Streets): + GAME_VERSION = 2 + + FOUNDATION_MAX_MOVE = 0 + ROW_MAX_MOVE = UNLIMITED_MOVES + DEAL = (0, 3) + FILL_EMPTY_ROWS = 1 + + def createGame(self): + Streets.createGame(self, max_rounds=2, rows=12) + + +# /*********************************************************************** +# // Red and Black +# // Zebra +# // rows build down by alternate color, foundations up by alternate color +# ************************************************************************/ + +class RedAndBlack(Streets): + Foundation_Class = AC_FoundationStack + + ROW_MAX_MOVE = UNLIMITED_MOVES + DEAL = (0, 1) + + def createGame(self): + FortyThieves.createGame(self, rows=8) + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + Streets.startGame(self) + + +class Zebra(RedAndBlack): + FOUNDATION_MAX_MOVE = 0 + ROW_MAX_MOVE = 1 + FILL_EMPTY_ROWS = 1 + + def createGame(self): + FortyThieves.createGame(self, max_rounds=2, rows=8, XOFFSET=0) + + +# /*********************************************************************** +# // Indian +# // Midshipman +# // Mumbai +# // rows build down by any suit but own +# ************************************************************************/ + +class Indian_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isAnySuitButOwnSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + return _('Row. Build down in any suit but the same.') + + +class Indian(FortyThieves): + RowStack_Class = Indian_RowStack + DEAL = (1, 2) + + def createGame(self): + FortyThieves.createGame(self, XCARDS=74) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit != card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +class Midshipman(Indian): + DEAL = (2, 2) + + def createGame(self): + FortyThieves.createGame(self, rows=9) + + +class Mumbai(Indian): + def createGame(self): + FortyThieves.createGame(self, XCARDS=84, rows=13) + + +# /*********************************************************************** +# // Napoleon's Exile +# // Double Rail +# // Single Rail (1 deck) +# // rows build down by rank +# ************************************************************************/ + +class NapoleonsExile(FortyThieves): + RowStack_Class = RK_RowStack + + DEAL = (0, 4) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank + + +class DoubleRail(NapoleonsExile): + ROW_MAX_MOVE = UNLIMITED_MOVES + DEAL = (0, 1) + + def createGame(self): + FortyThieves.createGame(self, rows=5) + + +class SingleRail(DoubleRail): + def createGame(self): + FortyThieves.createGame(self, rows=4, XCARDS=48) + + +# /*********************************************************************** +# // Octave +# ************************************************************************/ + +class Octave_Talon(WasteTalonStack): + + def dealCards(self, sound=0): + if self.round == self.max_rounds: + # last round + old_state = self.game.enterState(self.game.S_DEAL) + num_cards = 0 + wastes = [self.waste]+list(self.game.s.reserves) + if self.cards: + if sound and not self.game.demo: + self.game.startDealSample() + num_cards = min(len(self.cards), 8) + for i in range(num_cards): + if not self.cards[-1].face_up: + self.game.flipMove(self) + self.game.moveMove(1, self, wastes[i], frames=4, shadow=0) + if sound and not self.game.demo: + self.game.stopSamples() + self.game.leaveState(old_state) + return num_cards + return WasteTalonStack.dealCards(self, sound) + + +class Octave(Game): + + # + # game layout + # + + def createGame(self): + + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+9*l.XS, l.YM+3*l.YS+12*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, + suit=int(i/2), max_cards=10)) + x += l.XS + + x, y = l.XM, l.YM+l.YS + for i in range(8): + s.rows.append(AC_RowStack(x, y, self, + base_rank=ANY_RANK, max_move=1)) + x += l.XS + + x, y = l.XM, h-l.YS + s.talon = Octave_Talon(x, y, self, max_rounds=2) + l.createText(s.talon, "n") + x += l.XS + s.waste = WasteStack(x, y, self) + x += l.XS + for i in range(7): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + for i in range(2): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def isGameWon(self): + for s in self.s.foundations: + if len(s.cards) != 10: + return False + for s in self.s.reserves: + if s.cards: + return False + return not self.s.waste.cards + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + def _autoDeal(self, sound=1): + ncards = len(self.s.waste.cards) + sum([len(i.cards) for i in self.s.reserves]) + if ncards == 0: + return self.dealCards(sound=sound) + return 0 + + +# /*********************************************************************** +# // Fortune's Favor +# ************************************************************************/ + +class FortunesFavor(Game): + + def createGame(self): + + l, s = Layout(self), self.s + + w, h = l.XM+7*l.XS, 2*l.YM+3*l.YS + self.setSize(w, h) + + x, y = l.XM+3*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, 's') + y += l.YS+2*l.YM + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 's') + y = 2*l.YM+l.YS + for i in range(2): + x = l.XM+l.XS + for j in range(6): + stack = SS_RowStack(x, y, self, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0 + s.rows.append(stack) + x += l.XS + y += l.YS + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, c.suit)) + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + + def fillStack(self, stack): + if len(stack.cards) == 0: + if stack is self.s.waste and self.s.talon.cards: + self.s.talon.dealCards() + elif stack in self.s.rows and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Octagon +# ************************************************************************/ + +class Octagon(Game): + Hint_Class = CautiousDefaultHint + + def createGame(self): + + l, s = Layout(self), self.s + + w1 = l.XS+12*l.XOFFSET + w, h = l.XM+2*l.XS+2*w1, l.YM+3*l.YS + self.setSize(w, h) + + for x, y in ((l.XM, l.YM), + (l.XM+w1+2*l.XS+l.XM, l.YM), + (l.XM, l.YM+2*l.YS), + (l.XM+w1+2*l.XS+l.XM, l.YM+2*l.YS),): + stack = SS_RowStack(x, y, self, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + i = 0 + for x, y in ((l.XM+w1, l.YM), + (l.XM+w1+l.XS, l.YM), + (l.XM+w1-2*l.XS-l.XM, l.YM+l.YS), + (l.XM+w1-l.XS-l.XM, l.YM+l.YS), + (l.XM+w1+2*l.XS+l.XM, l.YM+l.YS), + (l.XM+w1+3*l.XS+l.XM, l.YM+l.YS), + (l.XM+w1, l.YM+2*l.YS), + (l.XM+w1+l.XS, l.YM+2*l.YS),): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i%4)) + i += 1 + x, y = l.XM+w1, l.YM+l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=4) + x += l.XS + s.waste = WasteStack(x, y, self) + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, (c.deck, c.suit))) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + for i in range(5): + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards: + if not self.s.waste.cards: + self.s.talon.dealCards() + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Squadron +# ************************************************************************/ + +class Squadron(FortyThieves): + + def createGame(self): + l, s = Layout(self), self.s + + self.setSize(l.XM+12*l.XS, l.YM+max(4.5*l.YS, 2*l.YS+12*l.YOFFSET)) + + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, 's') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 's') + x += 2*l.XS + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i/2)) + x += l.XS + x, y = l.XM, l.YM+l.YS*3/2 + for i in range(3): + s.reserves.append(ReserveStack(x, y, self)) + y += l.YS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(10): + s.rows.append(SS_RowStack(x, y, self, max_move=1)) + x += l.XS + + l.defaultStackGroups() + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + + +# register the game +registerGame(GameInfo(13, FortyThieves, "Forty Thieves", + GI.GT_FORTY_THIEVES, 2, 0, + altnames=("Napoleon at St.Helena", + "Big Forty", "Le Cadran"))) +registerGame(GameInfo(80, BusyAces, "Busy Aces", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(228, Limited, "Limited", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(79, WaningMoon, "Waning Moon", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(125, Lucas, "Lucas", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(109, Deuces, "Deuces", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(196, Corona, "Corona", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(195, Quadrangle, "Quadrangle", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(110, Courtyard, "Courtyard", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(23, FortyAndEight, "Forty and Eight", + GI.GT_FORTY_THIEVES, 2, 1)) +registerGame(GameInfo(115, LittleForty, "Little Forty", # was: 72 + GI.GT_FORTY_THIEVES, 2, 3)) +registerGame(GameInfo(76, Streets, "Streets", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(73, Maria, "Maria", + GI.GT_FORTY_THIEVES, 2, 0, + altnames=("Maria Luisa",) )) +registerGame(GameInfo(70, NumberTen, "Number Ten", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(71, RankAndFile, "Rank and File", + GI.GT_FORTY_THIEVES, 2, 0, + altnames=("Dress Parade") )) +registerGame(GameInfo(197, TripleLine, "Triple Line", + GI.GT_FORTY_THIEVES | GI.GT_XORIGINAL, 2, 1)) +registerGame(GameInfo(126, RedAndBlack, "Red and Black", # was: 75 + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(113, Zebra, "Zebra", + GI.GT_FORTY_THIEVES, 2, 1)) +registerGame(GameInfo(69, Indian, "Indian", + GI.GT_FORTY_THIEVES, 2, 0, + altnames=("Indian Patience",) )) +registerGame(GameInfo(74, Midshipman, "Midshipman", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(198, NapoleonsExile, "Napoleon's Exile", + GI.GT_FORTY_THIEVES | GI.GT_XORIGINAL, 2, 0)) +registerGame(GameInfo(131, DoubleRail, "Double Rail", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(199, SingleRail, "Single Rail", + GI.GT_FORTY_THIEVES, 1, 0)) +registerGame(GameInfo(295, NapoleonsSquare, "Napoleon's Square", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(310, Emperor, "Emperor", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(323, Octave, "Octave", + GI.GT_FORTY_THIEVES, 2, 1)) +registerGame(GameInfo(332, Mumbai, "Mumbai", + GI.GT_FORTY_THIEVES, 3, 0)) +registerGame(GameInfo(411, CarreNapoleon, "Carre Napoleon", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(416, FortunesFavor, "Fortune's Favor", + GI.GT_FORTY_THIEVES, 1, 0)) +registerGame(GameInfo(426, Octagon, "Octagon", + GI.GT_FORTY_THIEVES, 2, 3)) +registerGame(GameInfo(440, Squadron, "Squadron", + GI.GT_FORTY_THIEVES, 2, 0)) +registerGame(GameInfo(462, Josephine, "Josephine", + GI.GT_FORTY_THIEVES, 2, 0)) diff --git a/pysollib/games/freecell.py b/pysollib/games/freecell.py new file mode 100644 index 0000000000..dce7fd75c7 --- /dev/null +++ b/pysollib/games/freecell.py @@ -0,0 +1,530 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import FreeCellType_Hint, FreeCellSolverWrapper + +from spider import Spider_AC_Foundation + + +# /*********************************************************************** +# // FreeCell +# ************************************************************************/ + +# To simplify playing we also consider the number of free rows. +# Note that this only is legal if the game.s.rows have a +# cap.base_rank == ANY_RANK. +# See also the "SuperMove" section in the FreeCell FAQ. +class FreeCell_RowStack(AC_RowStack): + def _getMaxMove(self, to_stack_ncards): + max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1 + n = getNumberOfFreeStacks(self.game.s.rows) + if to_stack_ncards == 0: + n = n - 1 + while n > 0 and max_move < 1000: + max_move = max_move * 2 + n = n - 1 + return max_move + + def canMoveCards(self, cards): + max_move = self._getMaxMove(1) + return len(cards) <= max_move and AC_RowStack.canMoveCards(self, cards) + + def acceptsCards(self, from_stack, cards): + max_move = self._getMaxMove(len(self.cards)) + return len(cards) <= max_move and AC_RowStack.acceptsCards(self, from_stack, cards) + + +class FreeCell(Game): + Layout_Method = Layout.freeCellLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = FreeCell_RowStack + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {}) + + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=8, reserves=4, texts=0) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + r = self.s.rows + ##self.s.talon.dealRow(rows=(r[0], r[2], r[4], r[6])) + self.s.talon.dealRow(rows=r[:4]) + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Relaxed FreeCell +# ************************************************************************/ + +class RelaxedFreeCell(FreeCell): + RowStack_Class = AC_RowStack + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'sm' : "unlimited"}) + + +# /*********************************************************************** +# // ForeCell +# ************************************************************************/ + +class ForeCell(FreeCell): + RowStack_Class = StackWrapper(FreeCell_AC_RowStack, base_rank=KING) + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'esf' : "kings"}) + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Challenge FreeCell +# // Super Challenge FreeCell +# ************************************************************************/ + +class ChallengeFreeCell(FreeCell): + def _shuffleHook(self, cards): + # move Aces and Twos to top of the Talon + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (ACE, 1), (-c.rank, c.suit))) + +class SuperChallengeFreeCell(ChallengeFreeCell): + RowStack_Class = StackWrapper(FreeCell_AC_RowStack, base_rank=KING) + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'esf' : "kings"}) + + +# /*********************************************************************** +# // Stalactites +# ************************************************************************/ + +class Stalactites(FreeCell): + Foundation_Class = StackWrapper(RK_FoundationStack, suit=ANY_SUIT, mod=13, min_cards=1) + RowStack_Class = StackWrapper(BasicRowStack, max_move=1, max_accept=0) + Hint_Class = FreeCellType_Hint + + def createGame(self): + FreeCell.createGame(self, reserves=2) + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + assert len(self.s.talon.cards) == 0 + self._restoreGameHook(None) + + def _restoreGameHook(self, game): + for s in self.s.foundations: + s.cap.base_rank = s.cards[0].rank + + +# /*********************************************************************** +# // Double Freecell +# ************************************************************************/ + +class DoubleFreecell(FreeCell): + Hint_Class = FreeCellType_Hint + + # + # game layout + # + + def createGame(self): + + # create layout + l, s = Layout(self), self.s + + # set window + w, h = 3*l.XM+10*l.XS, 2*l.YM+2*l.YS+16*l.YOFFSET + self.setSize(w, h) + + # create stacks + s.talon = self.Talon_Class(l.XM, h-l.YS, self) + x, y = 3*l.XM + 6*l.XS, l.YM + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i, mod=13, max_cards=26)) + x += l.XS + x, y = 2*l.XM, l.YM + l.YS + l.YM + for i in range(10): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + x, y = l.XM, l.YM + for i in range(6): + s.reserves.append(ReserveStack(x, y, self)) + x += l.XS + # default + l.defaultAll() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move 4 Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, + lambda c: (c.rank == ACE and c.deck == 0, c.suit)) + + def startGame(self): + for i in range(9): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Triple Freecell +# ************************************************************************/ + +class TripleFreecell(FreeCell): + Hint_Class = FreeCellType_Hint + + # + # game layout + # + + def createGame(self, rows=13, reserves=10, playcards=20): + + # create layout + l, s = Layout(self), self.s + + # set window + max_rows = max(12, rows, reserves) + w, h = l.XM+max_rows*l.XS, l.YM+3*l.YS+playcards*l.YOFFSET + self.setSize(w, h) + + # create stacks + s.talon = self.Talon_Class(l.XM, h-l.YS, self) + + x, y = l.XM+(max_rows-12)*l.XS/2, l.YM + for i in range(3): + for j in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=j)) + x += l.XS + x, y = l.XM+(max_rows-reserves)*l.XS/2, l.YM+l.YS + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x += l.XS + x, y = l.XM+(max_rows-rows)*l.XS/2, l.YM+2*l.YS + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(11): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class Cell11(TripleFreecell): + def createGame(self): + TripleFreecell.createGame(self, rows=12, reserves=11) + + def startGame(self): + for i in range(12): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[1:-1]) + self.s.talon.dealRow(rows=[self.s.reserves[0],self.s.reserves[-1]]) + + +# /*********************************************************************** +# // Spidercells +# ************************************************************************/ + +class Spidercells_RowStack(FreeCell_RowStack): + def canMoveCards(self, cards): + if len(cards) == 13 and isAlternateColorSequence(cards): + return True + return FreeCell_RowStack.canMoveCards(self, cards) + + +class Spidercells(FreeCell): + + Hint_Class = FreeCellType_Hint + Foundation_Class = Spider_AC_Foundation + RowStack_Class = Spidercells_RowStack + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=8, reserves=4, texts=0) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=ANY_SUIT)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + # default + l.defaultAll() + + +# /*********************************************************************** +# // Seven by Four +# // Seven by Five +# // Bath +# ************************************************************************/ + +class SevenByFour(FreeCell): + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {}) + #Hint_Class = FreeCellType_Hint + def createGame(self): + FreeCell.createGame(self, rows=7) + def startGame(self): + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.rows[:3]) + +class SevenByFive(SevenByFour): + def createGame(self): + FreeCell.createGame(self, rows=7, reserves=5) + +class Bath(FreeCell): + Hint_Class = FreeCellSolverWrapper(FreeCellType_Hint, {'esf' : 'kings'}) + #Hint_Class = FreeCellType_Hint + RowStack_Class = StackWrapper(FreeCell_RowStack, base_rank=KING) + def createGame(self): + FreeCell.createGame(self, rows=10, reserves=2) + def startGame(self): + for i in range(6): + self.s.talon.dealRow(rows=self.s.rows[i:], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[6:]) + self.s.talon.dealRow(rows=self.s.rows[7:]) + + +# /*********************************************************************** +# // Clink +# ************************************************************************/ + +class Clink(FreeCell): + Hint_Class = FreeCellType_Hint + + def createGame(self): + # create layout + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+2*l.YS+12*l.YOFFSET) + # create stacks + x, y = l.XM, self.height-l.YS + s.talon = InitialDealTalonStack(x, y, self) + x, y = l.XM+l.XS, l.YM + for i in range(2): + s.reserves.append(ReserveStack(x, y, self)) + x += l.XS + x += 2*l.XS + for i in range(2): + s.foundations.append(AC_FoundationStack(x, y, self, suit=ANY_SUIT, + max_cards=26, mod=13, max_move=0)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(8): + s.rows.append(AC_RowStack(x, y, self)) + x += l.XS + # default + l.defaultAll() + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow(rows=self.s.foundations) + + def _shuffleHook(self, cards): + # move two Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, + lambda c: (c.rank == ACE and c.suit in (0, 2), (c.suit))) + + +# /*********************************************************************** +# // Repair +# ************************************************************************/ + +class Repair(FreeCell): + Hint_Class = FreeCellType_Hint + RowStack_Class = AC_RowStack + + def createGame(self): + FreeCell.createGame(self, rows=10, reserves=4, playcards=26) + + def startGame(self): + for i in range(9): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + + +# /*********************************************************************** +# // Four Colours +# ************************************************************************/ + +class FourColours_RowStack(AC_RowStack): + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + +class FourColours(FreeCell): + Hint_Class = FreeCellType_Hint + RowStack_Class = AC_RowStack + + def createGame(self): + # create layout + l, s = Layout(self), self.s + self.setSize(l.XM+9*l.XS, l.YM+2*l.YS+12*l.YOFFSET) + # create stacks + x, y = self.width-l.XS, self.height-l.YS + s.talon = InitialDealTalonStack(x, y, self) + x, y = l.XM, l.YM + for i in range(4): + s.reserves.append(ReserveStack(x, y, self, base_suit=i)) + x += l.XS + x += l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(7): + s.rows.append(FourColours_RowStack(x, y, self)) + x += l.XS + # default + l.defaultAll() + + def dealOne(self, frames): + suit = self.s.talon.cards[-1].suit + self.s.talon.dealRow(rows=[self.s.rows[suit]], frames=frames) + + def startGame(self): + for i in range(40): + self.dealOne(frames=0) + self.startDealSample() + while self.s.talon.cards: + self.dealOne(frames=-1) + + + +# register the game +registerGame(GameInfo(5, RelaxedFreeCell, "Relaxed FreeCell", + GI.GT_FREECELL | GI.GT_RELAXED | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(8, FreeCell, "FreeCell", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(46, ForeCell, "ForeCell", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(77, Stalactites, "Stalactites", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0, + altnames=("Grampus", "Old Mole") )) +registerGame(GameInfo(264, DoubleFreecell, "Double FreeCell", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(265, TripleFreecell, "Triple FreeCell", + GI.GT_FREECELL | GI.GT_OPEN, 3, 0)) +registerGame(GameInfo(336, ChallengeFreeCell, "Challenge FreeCell", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0, + rules_filename='freecell.html')) +registerGame(GameInfo(337, SuperChallengeFreeCell, "Super Challenge FreeCell", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(363, Spidercells, "Spidercells", + GI.GT_SPIDER | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(364, SevenByFour, "Seven by Four", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(365, SevenByFive, "Seven by Five", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(383, Bath, "Bath", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(394, Clink, "Clink", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(448, Repair, "Repair", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(451, Cell11, "Cell 11", + GI.GT_FREECELL | GI.GT_OPEN, 3, 0)) +registerGame(GameInfo(464, FourColours, "Four Colours", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/glenwood.py b/pysollib/games/glenwood.py new file mode 100644 index 0000000000..fd54bf72dc --- /dev/null +++ b/pysollib/games/glenwood.py @@ -0,0 +1,186 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +from canfield import Canfield_Hint + +# /*********************************************************************** +# // Glenwood +# ************************************************************************/ + +class Glenwood_Talon(WasteTalonStack): + def canDealCards(self): + if self.game.base_rank is None: + return False + return WasteTalonStack.canDealCards(self) + +class Glenwood_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if self.game.base_rank is None: + return 1 + if not self.cards: + return cards[-1].rank == self.game.base_rank + # check the rank + return (self.cards[-1].rank + self.cap.dir) % self.cap.mod == cards[0].rank + +class Glenwood_RowStack(AC_RowStack): + def canMoveCards(self, cards): + if self.game.base_rank is None: + return False + if not AC_RowStack.canMoveCards(self, cards): + return False + if len(cards) == 1 or len(self.cards) == len(cards): + return True + return False + + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards and from_stack is self.game.s.waste: + for stack in self.game.s.reserves: + if stack.cards: + return False + return True + if from_stack in self.game.s.rows and len(cards) != len(from_stack.cards): + return False + return True + + +class Glenwood_ReserveStack(OpenStack): + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + OpenStack.moveMove(self, ncards, to_stack, frames, shadow) + if self.game.base_rank is None and to_stack in self.game.s.foundations: + old_state = self.game.enterState(self.game.S_FILL) + self.game.saveStateMove(2|16) # for undo + self.game.base_rank = to_stack.cards[-1].rank + self.game.saveStateMove(1|16) # for redo + self.game.leaveState(old_state) + + +class Glenwood(Game): + + Foundation_Class = Glenwood_Foundation + RowStack_Class = Glenwood_RowStack + ReserveStack_Class = Glenwood_ReserveStack #OpenStack + Hint_Class = Canfield_Hint + + base_rank = None + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS + l.XM, 3*l.YM + 5*l.YS) + + # create stacks + x, y = l.XM, l.YM + s.talon = Glenwood_Talon(x, y, self, max_rounds=2, num_deal=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + for i in range(4): + x = 2*l.XM + (i+2)*l.XS + s.foundations.append(self.Foundation_Class(x, y, self, i, dir=1, + mod=13, base_rank=ANY_RANK, max_move=0)) + + tx, ty, ta, tf = l.getTextAttr(None, "se") + tx, ty = x + tx + l.XM, y + ty + font = self.app.getFont("canvas_default") + self.texts.info = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=font) + + for i in range(4): + x = 2*l.XM + (i+2)*l.XS + y = 3*l.YM + l.YS + s.rows.append(self.RowStack_Class(x, y, self, mod=13)) + for i in range(4): + x = l.XM + y = 3*l.YM + (i+1)*l.YS + stack = self.ReserveStack_Class(x, y, self) + s.reserves.append(stack) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.base_rank = None + for i in range(3): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + # + # game extras + # + + def updateText(self): + if self.preview > 1: + return + if self.base_rank is None: + t = "" + else: + t = RANKS[self.base_rank] + self.texts.info.config(text=t) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color + and ((card1.rank + 1) % 13 == card2.rank + or (card2.rank + 1) % 13 == card1.rank)) + + def _restoreGameHook(self, game): + self.base_rank = game.loadinfo.base_rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_rank=p.load()) + + def _saveGameHook(self, p): + p.dump(self.base_rank) + + def setState(self, state): + # restore saved vars (from undo/redo) + self.base_rank = state[0] + + def getState(self): + # save vars (for undo/redo) + return [self.base_rank] + + +# register the game +registerGame(GameInfo(282, Glenwood, "Glenwood", + GI.GT_CANFIELD, 1, 1)) + diff --git a/pysollib/games/golf.py b/pysollib/games/golf.py new file mode 100644 index 0000000000..648dd1c7dc --- /dev/null +++ b/pysollib/games/golf.py @@ -0,0 +1,634 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys, types + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Golf_Hint(AbstractHint): + # FIXME: this is very simple + + def computeHints(self): + game = self.game + # for each stack + for r in game.sg.dropstacks: + # try if we can drop a card to the Waste + w, ncards = r.canDropCards(game.s.foundations) + if not w: + continue + # this assertion must hold for Golf + assert ncards == 1 + # clone the Waste (including the card that will be dropped) to + # form our new foundations + ww = (self.ClonedStack(w, stackcards=w.cards+[r.cards[-1]]), ) + # now search for a stack that would benefit from this card + score, color = 10000 + r.id, None + for t in game.sg.dropstacks: + if not t.cards: + continue + if t is r: + t = self.ClonedStack(r, stackcards=r.cards[:-1]) + if t.canFlipCard(): + score = score + 100 + elif t.canDropCards(ww)[0]: + score = score + 100 + # add hint + self.addHint(score, ncards, r, w, color) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Golf_Talon(WasteTalonStack): + def canDealCards(self): + if not WasteTalonStack.canDealCards(self): + return 0 + return not self.game.isGameWon() + + +class Golf_Waste(WasteStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=0, max_accept=1) + apply(WasteStack.__init__, (self, x, y, game), cap) + + def acceptsCards(self, from_stack, cards): + if not WasteStack.acceptsCards(self, from_stack, cards): + return 0 + # check cards + r1, r2 = self.cards[-1].rank, cards[0].rank + if self.game.getStrictness() == 1: + # nothing on a King + if r1 == KING: + return 0 + return (r1 + 1) % self.cap.mod == r2 or (r2 + 1) % self.cap.mod == r1 + + +class Golf_RowStack(BasicRowStack): + def clickHandler(self, event): + return self.doubleclickHandler(event) + def getHelp(self): + return _('Row. No building.') + + +# /*********************************************************************** +# // Golf +# ************************************************************************/ + +class Golf(Game): + Waste_Class = Golf_Waste + Hint_Class = Golf_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w1, w2 = 8*l.XS+l.XM, 2*l.XS + if w2 + 52*l.XOFFSET > w1: + l.XOFFSET = int((w1 - w2) / 52) + self.setSize(w1, 4*l.YS+l.YM) + + # create stacks + x, y = l.XM + l.XS / 2, l.YM + for i in range(7): + s.rows.append(Golf_RowStack(x, y, self)) + x = x + l.XS + x, y = l.XM, self.height - l.YS + s.talon = Golf_Talon(x, y, self, max_rounds=1) + l.createText(s.talon, "nn") + x = x + l.XS + s.waste = self.Waste_Class(x, y, self) + s.waste.CARD_XOFFSET = l.XOFFSET + l.createText(s.waste, "nn") + # the Waste is also our only Foundation in this game + s.foundations.append(s.waste) + + # define stack-groups (non default) + self.sg.openstacks = [s.waste] + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + + # + # game overrides + # + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def isGameWon(self): + for r in self.s.rows: + if r.cards: + return 0 + return 1 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank) + + def getHighlightPilesStacks(self): + return () + + def getAutoStacks(self, event=None): + if event is None: + # disable auto drop - this would ruin the whole gameplay + return (self.sg.dropstacks, (), ()) + else: + # rightclickHandler + return (self.sg.dropstacks, self.sg.dropstacks, ()) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class DeadKingGolf(Golf): + def getStrictness(self): + return 1 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if card1.rank == KING: + return 0 + return Golf.shallHighlightMatch(self, stack1, card1, stack2, card2) + + +class RelaxedGolf(Golf): + Waste_Class = StackWrapper(Golf_Waste, mod=13) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank) + + +# /*********************************************************************** +# // Elevator - Relaxed Golf in a Pyramid layout +# ************************************************************************/ + +class Elevator_RowStack(Golf_RowStack): + STEP = (1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6) + + def basicIsBlocked(self): + r, step = self.game.s.rows, self.STEP + i, n, l = self.id, 1, len(step) + while i < l: + i = i + step[i] + n = n + 1 + for j in range(i, i+n): + if r[j].cards: + return 1 + return 0 + + +class Elevator(RelaxedGolf): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(9*l.XS+l.XM, 4*l.YS+l.YM) + + # create stacks + for i in range(7): + x = l.XM + (8-i) * l.XS / 2 + y = l.YM + i * l.YS / 2 + for j in range(i+1): + s.rows.append(Elevator_RowStack(x, y, self)) + x = x + l.XS + x, y = l.XM, l.YM + s.talon = Golf_Talon(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = self.Waste_Class(x, y, self) + l.createText(s.waste, "ss") + # the Waste is also our only Foundation in this game + s.foundations.append(s.waste) + + # define stack-groups (non default) + self.sg.openstacks = [s.waste] + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:21], flip=0) + self.s.talon.dealRow(rows=self.s.rows[21:]) + self.s.talon.dealCards() # deal first card to WasteStack + +class Escalator(Elevator): + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + +# /*********************************************************************** +# // Tri Peaks - Relaxed Golf in a Pyramid layout +# ************************************************************************/ + +class TriPeaks_RowStack(Elevator_RowStack): + STEP = (1, 2, 2, 3+12, 3+12, 3+12, + 1, 2, 2, 3+ 9, 3+ 9, 3+ 9, + 1, 2, 2, 3+ 6, 3+ 6, 3+ 6) + + +class TriPeaks(RelaxedGolf): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + l.XOFFSET = int(l.XS * 9 / self.gameinfo.ncards) + self.setSize(10*l.XS+l.XM, 4*l.YS+l.YM) + + # extra settings + self.talon_card_ids = {} + + # create stacks + for i in range(3): + for d in ((2, 0), (1, 1), (3, 1), (0, 2), (2, 2), (4, 2)): + x = l.XM + (6*i+1+d[0]) * l.XS / 2 + y = l.YM + d[1] * l.YS / 2 + s.rows.append(TriPeaks_RowStack(x, y, self)) + x, y = l.XM, 3*l.YS/2 + for i in range(10): + s.rows.append(Golf_RowStack(x, y, self)) + x = x + l.XS + x, y = l.XM, self.height - l.YS + s.talon = Golf_Talon(x, y, self, max_rounds=1) + l.createText(s.talon, "nn") + x = x + l.XS + s.waste = self.Waste_Class(x, y, self) + s.waste.CARD_XOFFSET = l.XOFFSET + l.createText(s.waste, "nn") + # the Waste is also our only Foundation in this game + s.foundations.append(s.waste) + + self.texts.score = MfxCanvasText(self.canvas, + self.width - 8, self.height - 8, + anchor="se", + font=self.app.getFont("canvas_large")) + + # define stack-groups (non default) + self.sg.openstacks = [s.waste] + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:18], flip=0) + self.s.talon.dealRow(rows=self.s.rows[18:]) + # extra settings: remember cards from the talon + self.talon_card_ids = {} + for c in self.s.talon.cards: + self.talon_card_ids[c.id] = 1 + self.s.talon.dealCards() # deal first card to WasteStack + + def getGameScore(self): + v = -24 * 5 + if not self.s.waste.cards: + return v + # compute streaks for cards on the waste + streak = 0 + for c in self.s.waste.cards: + if self.talon_card_ids.get(c.id): + streak = 0 + else: + streak = streak + 1 + v = v + streak + # each cleared peak gains $15 bonus, and all 3 gain extra $30 + extra = 30 + for i in (0, 6, 12): + if not self.s.rows[i].cards: + v = v + 15 + else: + extra = 0 + return v + extra + + getGameBalance = getGameScore + + def updateText(self): + if self.preview > 1: + return + b1, b2 = self.app.stats.gameid_balance, 0 + if self.shallUpdateBalance(): + b2 = self.getGameBalance() + t = _("Balance $%4d") % (b1 + b2) + self.texts.score.config(text=t) + + def _restoreGameHook(self, game): + self.talon_card_ids = game.loadinfo.talon_card_ids + + def _loadGameHook(self, p): + self.loadinfo.addattr(talon_card_ids=p.load(types.DictType)) + + def _saveGameHook(self, p): + p.dump(self.talon_card_ids) + + +# /*********************************************************************** +# // Black Hole +# ************************************************************************/ + +class BlackHole_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + if self.cards: + r1, r2 = self.cards[-1].rank, cards[0].rank + return (r1 + 1) % self.cap.mod == r2 or (r2 + 1) % self.cap.mod == r1 + return 1 + + +class BlackHole_RowStack(ReserveStack): + def clickHandler(self, event): + return self.doubleclickHandler(event) + def getHelp(self): + return _('Row. No building.') + + +class BlackHole(Game): + RowStack_Class = StackWrapper(BlackHole_RowStack, max_accept=0, max_cards=3) + Hint_Class = Golf_Hint + + # + # game layout + # + + def createGame(self, playcards=5): + # create layout + l, s = Layout(self), self.s + + # set window + w = max(2*l.XS, l.XS+(playcards-1)*l.XOFFSET) + self.setSize(l.XM + 5*w, l.YM + 4*l.YS) + + # create stacks + y = l.YM + for i in range(5): + x = l.XM + i*w + s.rows.append(self.RowStack_Class(x, y, self)) + for i in range(2): + y = y + l.YS + for j in (0, 1, 3, 4): + x = l.XM + j*w + s.rows.append(self.RowStack_Class(x, y, self)) + y = y + l.YS + for i in range(4): + x = l.XM + i*w + s.rows.append(self.RowStack_Class(x, y, self)) + for r in s.rows: + r.CARD_XOFFSET = l.XOFFSET + r.CARD_YOFFSET = 0 + x, y = l.XM + 2*w, l.YM + 3*l.YS/2 + s.foundations.append(BlackHole_Foundation(x, y, self, ANY_SUIT, dir=0, mod=13, max_move=0, max_cards=52)) + l.createText(s.foundations[0], "ss") + x, y = l.XM + 4*w, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move Ace to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.id == 13, c.suit), 1) + + def startGame(self): + for i in range(2): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + + def getAutoStacks(self, event=None): + if event is None: + # disable auto drop - this would ruin the whole gameplay + return ((), (), self.sg.dropstacks) + else: + # rightclickHandler + return ((), self.sg.dropstacks, self.sg.dropstacks) + + + +# /*********************************************************************** +# // Four Leaf Clovers +# ************************************************************************/ + +class FourLeafClovers_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + if self.cards: + r1, r2 = self.cards[-1].rank, cards[0].rank + return (r1 + 1) % self.cap.mod == r2 + return 1 + def getHelp(self): + return _('Foundation. Build up regardless of suit.') + +class FourLeafClovers(Game): + + Hint_Class = CautiousDefaultHint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + h = l.YS + 6*l.YOFFSET + self.setSize(l.XM + 7*l.XS, l.YM + 2*h) + + # create stacks + y = l.YM + for i in range(7): + x = l.XM + i*l.XS + s.rows.append(UD_RK_RowStack(x, y, self, mod=13, base_rank=NO_RANK)) + y = l.YM+h + for i in range(6): + x = l.XM + i*l.XS + s.rows.append(UD_RK_RowStack(x, y, self, mod=13, base_rank=NO_RANK)) + + s.foundations.append(FourLeafClovers_Foundation(l.XM+6*l.XS, self.height-l.YS, self, ANY_SUIT, dir=0, mod=13, max_move=0, max_cards=52)) + x, y = l.XM + 7*l.XS, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // All in a Row +# ************************************************************************/ + + + +class AllInARow(BlackHole): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + h = l.YM+l.YS+4*l.YOFFSET + self.setSize(l.XM+7*l.XS, 3*l.YM+2*h+l.YS) + + # create stacks + x, y = l.XM, l.YM + for i in range(7): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + x, y = l.XM, l.YM+h + for i in range(6): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + for r in s.rows: + r.CARD_XOFFSET, r.CARD_YOFFSET = 0, l.YOFFSET + + x, y = l.XM, self.height-l.YS + stack = BlackHole_Foundation(x, y, self, ANY_SUIT, dir=0, mod=13, max_move=0, max_cards=52, base_rank=ANY_RANK) + s.foundations.append(stack) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = (self.width-l.XS)/51, 0 + l.createText(stack, 'n') + x = self.width-l.XS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Robert +# ************************************************************************/ + +class Robert(Game): + + def createGame(self): + l, s = Layout(self), self.s + self.setSize(l.XM+4*l.XS, l.YM+2*l.YS) + x, y = l.XM+3*l.XS/2, l.YM + s.foundations.append(BlackHole_Foundation(x, y, self, ANY_SUIT, dir=0, mod=13, max_move=0, max_cards=52)) + x, y = l.XM+l.XS, l.YM+l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, 'sw') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'se') + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealCards() + + +# register the game +registerGame(GameInfo(36, Golf, "Golf", + GI.GT_GOLF, 1, 0)) +registerGame(GameInfo(259, DeadKingGolf, "Dead King Golf", + GI.GT_GOLF, 1, 0)) +registerGame(GameInfo(260, RelaxedGolf, "Relaxed Golf", + GI.GT_GOLF | GI.GT_RELAXED, 1, 0)) +registerGame(GameInfo(40, Elevator, "Elevator", + GI.GT_GOLF, 1, 0, + altnames=("Egyptian Solitaire", "Pyramid Golf") )) +registerGame(GameInfo(237, TriPeaks, "Tri Peaks", + GI.GT_GOLF | GI.GT_SCORE, 1, 0)) +registerGame(GameInfo(98, BlackHole, "Black Hole", + GI.GT_GOLF | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(267, FourLeafClovers, "Four Leaf Clovers", + GI.GT_GOLF | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(281, Escalator, "Escalator", + GI.GT_GOLF, 1, 0)) +registerGame(GameInfo(405, AllInARow, "All in a Row", + GI.GT_GOLF | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(432, Robert, "Robert", + GI.GT_GOLF, 1, 2)) diff --git a/pysollib/games/grandfathersclock.py b/pysollib/games/grandfathersclock.py new file mode 100644 index 0000000000..d2993ada4c --- /dev/null +++ b/pysollib/games/grandfathersclock.py @@ -0,0 +1,142 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class GrandfathersClock_Hint(CautiousDefaultHint): + # FIXME: demo is not too clever in this game + + def _getDropCardScore(self, score, color, r, t, ncards): + # drop all cards immediately + return 92000, color + + +# /*********************************************************************** +# // Grandfather's Clock +# ************************************************************************/ + +class GrandfathersClock(Game): + Hint_Class = GrandfathersClock_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 9 cards are fully playable in default window size) + dh = max(3*l.YS/2+l.CH, l.YS+(9-1)*l.YOFFSET) + self.setSize(10*l.XS+l.XM, l.YM+2*dh) + + # create stacks + for i in range(2): + x, y = l.XM, l.YM + i*dh + for j in range(4): + s.rows.append(RK_RowStack(x, y, self, max_move=1, max_accept=1)) + x = x + l.XS + y = l.YM + dh - l.CH / 2 + self.setRegion(s.rows[:4], (-999, -999, x - l.XM / 2, y)) + self.setRegion(s.rows[4:], (-999, y, x - l.XM / 2, 999999)) + d = [ (0,0), (1,0.15), (2,0.5), (2.5,1.5), (2,2.5), (1,2.85) ] + for i in range(len(d)): + d.append( (0 - d[i][0], 3 - d[i][1]) ) + x0, y0 = l.XM, l.YM + dh - l.CH + for i in range(12): + j = (i + 5) % 12 + x = int(round(x0 + ( 6.5+d[j][0]) * l.XS)) + y = int(round(y0 + (-1.5+d[j][1]) * l.YS)) + suit = (1, 2, 0, 3) [i % 4] + s.foundations.append(SS_FoundationStack(x, y, self, suit, + base_rank=i+1, mod=13, + max_move=0)) + s.talon = InitialDealTalonStack(self.width-l.XS, self.height-l.YS, self) + + # define stack-groups + self.sg.openstacks = s.foundations + s.rows + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + + # + # game overrides + # + def _shuffleHook(self, cards): + # move clock cards to bottom of the Talon (i.e. last cards to be dealt) + C, S, H, D = 0*13, 1*13, 2*13, 3*13 + ids = (1+S, 2+H, 3+C, 4+D, 5+S, 6+H, 7+C, 8+D, 9+S, 10+H, 11+C, 12+D) + clocks = [] + for c in cards[:]: + if c.id in ids: + clocks.append(c) + cards.remove(c) + # sort clocks reverse by rank + clocks.sort(lambda a, b: cmp(b.rank, a.rank)) + return clocks + cards + + def startGame(self): + self.playSample("grandfathersclock", loop=1) + for i in range(4): + self.s.talon.dealRow(frames=0) + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.foundations) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank + + def getHighlightPilesStacks(self): + return () + + def getAutoStacks(self, event=None): + # disable auto drop - this would ruin the whole gameplay + return ((), (), ()) + + +# register the game +registerGame(GameInfo(261, GrandfathersClock, "Grandfather's Clock", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/gypsy.py b/pysollib/games/gypsy.py new file mode 100644 index 0000000000..cf6ff610cf --- /dev/null +++ b/pysollib/games/gypsy.py @@ -0,0 +1,510 @@ +## -*- coding: utf-8 -*- +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import KlondikeType_Hint, YukonType_Hint + +# /*********************************************************************** +# // Gypsy +# ************************************************************************/ + +class Gypsy(Game): + Layout_Method = Layout.gypsyLayout + Talon_Class = DealRowTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + Hint_Class = KlondikeType_Hint + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=0, texts=1) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + if l.s.waste: + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + + def startGame(self): + for i in range(2): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Giant +# ************************************************************************/ + +class Giant_Foundation(SS_FoundationStack): + def canMoveCards(self, cards): + if not SS_FoundationStack.canMoveCards(self, cards): + return 0 + # can only move cards if the Talon is empty + return len(self.game.s.talon.cards) == 0 + + +class Giant(Gypsy): + Foundation_Class = Giant_Foundation + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Irmgard +# ************************************************************************/ + +class Irmgard_Talon(TalonStack): + # A single click deals 9 (or 7) new cards to the RowStacks. + def dealCards(self, sound=0): + if self.cards: + if len(self.cards) > 7: + c = self.dealRow(sound=sound) + else: + c = self.dealRow(self.game.s.rows[1:8], sound=sound) + return c + return 0 + + +class Irmgard(Gypsy): + GAME_VERSION = 2 + + Layout_Method = Layout.harpLayout + Talon_Class = Irmgard_Talon + RowStack_Class = KingAC_RowStack + + def createGame(self): + Gypsy.createGame(self, rows=9, playcards=19) + + def startGame(self): + r = self.s.rows + for i in range(1, 5): + self.s.talon.dealRow(rows=r[i:len(r)-i], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Die Königsbergerin +# ************************************************************************/ + +class DieKoenigsbergerin_Talon(DealRowTalonStack): + # all Aces go to Foundations + dealToStacks = DealRowTalonStack.dealToStacksOrFoundations + + +class DieKoenigsbergerin(Gypsy): + Talon_Class = DieKoenigsbergerin_Talon + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + + def startGame(self): + self.startDealSample() + for i in range(3): + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Die Russische +# ************************************************************************/ + +class DieRussische_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if self.cards: + # check the rank - an ACE equals a Six + rank = self.cards[-1].rank + if rank == ACE: + rank = 5 + if (rank + self.cap.dir) % self.cap.mod != cards[0].rank: + return 0 + return 1 + + +class DieRussische_RowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return 0 + # when empty, only accept a single card + return self.cards or len(cards) == 1 + + +class DieRussische(Gypsy): + Talon_Class = InitialDealTalonStack + Foundation_Class = StackWrapper(DieRussische_Foundation, min_cards=1) + RowStack_Class = DieRussische_RowStack + + def createGame(self): + Gypsy.createGame(self, rows=7, texts=0) + + def _shuffleHook(self, cards): + # move one Ace to bottom of the Talon (i.e. last card to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit), 1) + + def startGame(self): + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRow() + c = self.s.talon.cards[-1] + self.s.talon.dealRow(rows=(self.s.foundations[c.suit*2],)) + + +# /*********************************************************************** +# // Miss Milligan +# ************************************************************************/ + +class MissMilligan_ReserveStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return 0 + # Note that this reserve stack accepts sequences if both + # the reserve stack and the Talon are empty. + return len(self.cards) == 0 and len(self.game.s.talon.cards) == 0 + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class MissMilligan(Gypsy): + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + RowStack_Class = KingAC_RowStack + ReserveStack_Class = MissMilligan_ReserveStack + + def createGame(self, rows=8, reserves=1): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + (1+max(8,rows))*l.XS, l.YM + (1+max(4, reserves))*l.YS) + + # create stacks + x, y = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + for i in range(8): + x = x + l.XS + s.foundations.append(self.Foundation_Class(x, y, self, suit=i/2)) + x, y = l.XM, y + l.YS + rx, ry = x + l.XS - l.XM/2, y - l.YM/2 + for i in range(reserves): + s.reserves.append(self.ReserveStack_Class(x, y, self)) + y = y + l.YS + if s.reserves: + self.setRegion(s.reserves, (-999, ry, rx - 1, 999999)) + else: + l.createText(s.talon, "ss") + rx = -999 + x, y = l.XM + (8-rows)*l.XS/2, l.YM + l.YS + for i in range(rows): + x = x + l.XS + s.rows.append(self.RowStack_Class(x, y, self)) + self.setRegion(s.rows, (rx, ry, 999999, 999999)) + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Nomad +# ************************************************************************/ + +class Nomad(MissMilligan): + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + ReserveStack_Class = ReserveStack + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Milligan Cell +# ************************************************************************/ + +class MilliganCell(MissMilligan): + ReserveStack_Class = ReserveStack + + def createGame(self): + MissMilligan.createGame(self, reserves=4) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Milligan Harp +# // Carlton +# ************************************************************************/ + +class MilliganHarp(Gypsy): + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + + def startGame(self, flip=0): + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=flip, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class Carlton(MilliganHarp): + def startGame(self): + MilliganHarp.startGame(self, flip=1) + + +# /*********************************************************************** +# // Lexington Harp +# // Brunswick +# // Mississippi +# // Griffon +# ************************************************************************/ + +class LexingtonHarp(MilliganHarp): + GAME_VERSION = 2 + RowStack_Class = Yukon_AC_RowStack + Hint_Class = YukonType_Hint + + +class Brunswick(LexingtonHarp): + def startGame(self): + LexingtonHarp.startGame(self, flip=1) + + +class Mississippi(LexingtonHarp): + def createGame(self): + LexingtonHarp.createGame(self, rows=7) + + +class Griffon(Mississippi): + def startGame(self): + Mississippi.startGame(self, flip=1) + + +# /*********************************************************************** +# // Blockade +# ************************************************************************/ + +class Blockade(Gypsy): + Layout_Method = Layout.klondikeLayout + RowStack_Class = SS_RowStack + + def createGame(self): + Gypsy.createGame(self, rows=12) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards and self.s.talon.cards: + old_state = self.enterState(self.S_FILL) + self.s.talon.flipMove() + self.s.talon.moveMove(1, stack) + self.leaveState(old_state) + + +# /*********************************************************************** +# // Cone +# ************************************************************************/ + +class Cone_Talon(DealRowTalonStack): + def canDealCards(self): + if not DealRowTalonStack.canDealCards(self): + return False + for r in self.game.s.rows: + if not r.cards: + return False + return True + + def dealCards(self, sound=0): + rows = self.game.s.rows + if len(self.cards) == 4: + rows = self.game.s.reserves + self.dealRowAvail(rows=rows, sound=sound) + + +class Cone(Gypsy): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+9*l.XS, 3*l.YM+5*l.YS) + + # create stacks + x, y = l.XM, l.YM + s.talon = Cone_Talon(x, y, self) + l.createText(s.talon, 's') + y += l.YS+2*l.YM + for i in range(4): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + y += l.YS + x, y = l.XM+l.XS, l.YM + for i in range(7): + s.rows.append(AC_RowStack(x, y, self, mod=13)) + x += l.XS + #y += l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + mod=13, max_cards=26)) + y += l.YS + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + for i in (1, 2, 3): + self.s.talon.dealRow(rows=self.s.rows[i:-i]) + + +# /*********************************************************************** +# // Surprise +# ************************************************************************/ + +class Surprise_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return False + return len(self.game.s.talon.cards) == 0 + +class Surprise(Gypsy): + + def createGame(self, rows=8, reserves=1): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+11*l.XS, l.YM+2*l.YS+12*l.YOFFSET+20) + + # create stacks + x, y = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, 's') + x += l.XS + stack = Surprise_ReserveStack(x, y, self, max_cards=3) + xoffset = min(l.XOFFSET, l.XS/3) + stack.CARD_XOFFSET = xoffset + s.reserves.append(stack) + x += 2*l.XS + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i/2)) + x += l.XS + x, y = l.XM, l.YM+l.YS+20 + for i in range(11): + s.rows.append(KingAC_RowStack(x, y, self)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + for i in range(1, 6): + self.s.talon.dealRow(rows=self.s.rows[i:-i], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# register the game +registerGame(GameInfo(1, Gypsy, "Gypsy", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(65, Giant, "Giant", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(3, Irmgard, "Irmgard", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(119, DieKoenigsbergerin, "Die Königsbergerin", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(174, DieRussische, "Russian Patience", + GI.GT_2DECK_TYPE | GI.GT_OPEN, 2, 0, + ranks=(0, 6, 7, 8, 9, 10, 11, 12), + altnames=("Die Russische",) )) +registerGame(GameInfo(62, MissMilligan, "Miss Milligan", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(200, Nomad, "Nomad", + GI.GT_GYPSY | GI.GT_CONTRIB | GI.GT_ORIGINAL, 2, 0)) +registerGame(GameInfo(78, MilliganCell, "Milligan Cell", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(217, MilliganHarp, "Milligan Harp", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(218, Carlton, "Carlton", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(68, LexingtonHarp, "Lexington Harp", + GI.GT_YUKON, 2, 0)) +registerGame(GameInfo(154, Brunswick, "Brunswick", + GI.GT_YUKON, 2, 0)) +registerGame(GameInfo(121, Mississippi, "Mississippi", + GI.GT_YUKON | GI.GT_XORIGINAL, 2, 0)) +registerGame(GameInfo(122, Griffon, "Griffon", + GI.GT_YUKON | GI.GT_XORIGINAL, 2, 0)) +registerGame(GameInfo(226, Blockade, "Blockade", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(412, Cone, "Cone", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(463, Surprise, "Surprise", + GI.GT_GYPSY, 2, 0)) + diff --git a/pysollib/games/harp.py b/pysollib/games/harp.py new file mode 100644 index 0000000000..c7fd281c29 --- /dev/null +++ b/pysollib/games/harp.py @@ -0,0 +1,185 @@ +# -*- mode: python; coding: utf-8 -*- +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import KlondikeType_Hint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Double Klondike (Klondike with 2 decks and 9 rows) +# ************************************************************************/ + +class DoubleKlondike(Game): + Layout_Method = Layout.harpLayout + RowStack_Class = KingAC_RowStack + Hint_Class = KlondikeType_Hint + + def createGame(self, max_rounds=-1, num_deal=1, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=9, waste=1, texts=1, playcards=19) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = WasteTalonStack(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(SS_FoundationStack(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + # extra + if max_rounds > 1: + assert s.talon.texts.rounds is None + tx, ty, ta, tf = l.getTextAttr(s.talon, "nn") + if layout.get("texts"): + ty = ty - 2*l.YM + s.talon.texts.rounds = MfxCanvasText(self.canvas, tx, ty, + anchor=ta, + font=self.app.getFont("canvas_default")) + return l + + def startGame(self): + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Double Klondike by Threes +# ************************************************************************/ + +class DoubleKlondikeByThrees(DoubleKlondike): + def createGame(self): + DoubleKlondike.createGame(self, num_deal=3) + + +# /*********************************************************************** +# // Gargantua (Double Klondike with one redeal) +# ************************************************************************/ + +class Gargantua(DoubleKlondike): + def createGame(self): + DoubleKlondike.createGame(self, max_rounds=2) + + +# /*********************************************************************** +# // Harp (Double Klondike with 10 non-king rows and no redeal) +# ************************************************************************/ + +class BigHarp(DoubleKlondike): + RowStack_Class = AC_RowStack + + def createGame(self): + DoubleKlondike.createGame(self, max_rounds=1, rows=10) + + # + # game overrides + # + + # no real need to override, but this way the layout + # looks a little bit different + def startGame(self): + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Steps (Harp with 7 rows) +# ************************************************************************/ + +class Steps(DoubleKlondike): + RowStack_Class = AC_RowStack + + def createGame(self): + DoubleKlondike.createGame(self, max_rounds=2, rows=7) + + +# /*********************************************************************** +# // Triple Klondike +# ************************************************************************/ + +class TripleKlondike(DoubleKlondike): + def createGame(self): + DoubleKlondike.createGame(self, rows=13) + + +# /*********************************************************************** +# // Triple Klondike by Threes +# ************************************************************************/ + +class TripleKlondikeByThrees(DoubleKlondike): + def createGame(self): + DoubleKlondike.createGame(self, rows=13, num_deal=3) + + +# register the game +registerGame(GameInfo(21, DoubleKlondike, "Double Klondike", + GI.GT_KLONDIKE, 2, -1)) +registerGame(GameInfo(28, DoubleKlondikeByThrees, "Double Klondike by Threes", + GI.GT_KLONDIKE, 2, -1)) +registerGame(GameInfo(25, Gargantua, "Gargantua", + GI.GT_KLONDIKE, 2, 1)) +registerGame(GameInfo(15, BigHarp, "Big Harp", + GI.GT_KLONDIKE, 2, 0, + altnames=("Die große Harfe",) )) +registerGame(GameInfo(51, Steps, "Steps", + GI.GT_KLONDIKE, 2, 1)) +registerGame(GameInfo(273, TripleKlondike, "Triple Klondike", + GI.GT_KLONDIKE, 3, -1)) +registerGame(GameInfo(274, TripleKlondikeByThrees, "Triple Klondike by Threes", + GI.GT_KLONDIKE, 3, -1)) + diff --git a/pysollib/games/headsandtails.py b/pysollib/games/headsandtails.py new file mode 100644 index 0000000000..77d8c70338 --- /dev/null +++ b/pysollib/games/headsandtails.py @@ -0,0 +1,142 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Heads and Tails +# ************************************************************************/ + +class HeadsAndTails_Reserve(TalonStack): + def clickHandler(self, event): + # no deal + return True + + +class HeadsAndTails(Game): + + # + # game layout + # + + def createGame(self): + + # create layout + l, s = Layout(self), self.s + + # set window + h = l.YS + 7*l.YOFFSET + self.setSize(l.XM+10*l.XS, l.YM+l.YS+2*h) + + # create stacks + x, y = self.width - l.XS, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + + x, y = l.XM+l.XS, l.YM + for i in range(8): + s.rows.append(SS_RowStack(x, y, self, + dir=1, max_move=1, max_accept=1)) + x += l.XS + x, y = l.XM+l.XS, l.YM+l.YS+h + for i in range(8): + s.rows.append(SS_RowStack(x, y, self, + dir=-1, max_move=1, max_accept=1)) + x += l.XS + + x, y = l.XM+l.XS, l.YM+h + for i in range(8): + stack = HeadsAndTails_Reserve(x, y, self) + s.reserves.append(stack) + l.createText(stack, "n") + x += l.XS + + x, y = l.XM, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + y += l.YS + x, y = l.XM+9*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, + suit=i, base_rank=KING, dir=-1)) + y += l.YS + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(11): + self.s.talon.dealRow(rows=self.s.reserves, frames=0, flip=0) + self.startDealSample() + self.s.talon.dealRow() + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards: + reserves = self.s.reserves + si = list(self.s.rows).index(stack)%8 + from_stack = None + if reserves[si].cards: + from_stack = reserves[si] + else: + for i in range(1, 8): + n = si+i + if n < 8 and reserves[n].cards: + from_stack = reserves[n] + break + n = si-i + if n >= 0 and reserves[n].cards: + from_stack = reserves[n] + break + if not from_stack: + return + old_state = self.enterState(self.S_FILL) + from_stack.moveMove(1, stack) + #stack.flipMove() + self.leaveState(old_state) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank - card2.rank) == 1 + + +# register the game + +registerGame(GameInfo(307, HeadsAndTails, "Heads and Tails", + GI.GT_2DECK_TYPE, 2, 0)) + + + diff --git a/pysollib/games/katzenschwanz.py b/pysollib/games/katzenschwanz.py new file mode 100644 index 0000000000..b987ab508f --- /dev/null +++ b/pysollib/games/katzenschwanz.py @@ -0,0 +1,352 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import DefaultHint, FreeCellType_Hint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class DerKatzenschwanz(Game): + RowStack_Class = StackWrapper(AC_RowStack, base_rank=NO_RANK) + Hint_Class = FreeCellType_Hint + + # + # game layout + # + + def createGame(self, rows=9, reserves=8): + # create layout + l, s = Layout(self), self.s + + # set size + maxrows = max(rows, reserves) + self.setSize(l.XM + (maxrows+2)*l.XS, l.YM + 6*l.YS) + + # + playcards = 4*l.YS / l.YOFFSET + xoffset, yoffset = [], [] + for i in range(playcards): + xoffset.append(0) + yoffset.append(l.YOFFSET) + for i in range(104-playcards): + xoffset.append(l.XOFFSET) + yoffset.append(0) + + # create stacks + x, y = l.XM + (maxrows-reserves)*l.XS/2, l.YM + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x, y = l.XM + (maxrows-rows)*l.XS/2, l.YM + l.YS + self.setRegion(s.reserves, (-999, -999, 999999, y - l.XM / 2)) + for i in range(rows): + stack = self.RowStack_Class(x, y, self) + stack.CARD_XOFFSET = xoffset + stack.CARD_YOFFSET = yoffset + s.rows.append(stack) + x = x + l.XS + x, y = l.XM + maxrows*l.XS, l.YM + for suit in range(4): + for i in range(2): + s.foundations.append(SS_FoundationStack(x+i*l.XS, y, self, suit=suit)) + y = y + l.YS + self.setRegion(self.s.foundations, (x - l.CW / 2, -999, 999999, y), priority=1) + s.talon = InitialDealTalonStack(self.width-3*l.XS/2, self.height-l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + if self.s.talon.cards[-1].rank == KING: + if self.s.rows[i].cards: + i = i + 1 + self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + # must look at cards + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + for stack in stacks: + if stack.cards and stack is not dragstack: + dist = (stack.cards[-1].x - cx)**2 + (stack.cards[-1].y - cy)**2 + else: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + closest, cdist = stack, dist + return closest + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class DieSchlange(DerKatzenschwanz): + + RowStack_Class = StackWrapper(FreeCell_AC_RowStack, base_rank=NO_RANK) + + def createGame(self): + DerKatzenschwanz.createGame(self, rows=9, reserves=7) + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + c = self.s.talon.cards[-1] + if c.rank == ACE: + to_stack = self.s.foundations[c.suit*2] + if to_stack.cards: + to_stack = self.s.foundations[c.suit*2+1] + else: + if c.rank == KING and self.s.rows[i].cards: + i = i + 1 + to_stack = self.s.rows[i] + self.s.talon.dealRow(rows=(to_stack,), frames=4) + + +# /*********************************************************************** +# // Kings +# ************************************************************************/ + +class Kings(DerKatzenschwanz): + + ##RowStack_Class = StackWrapper(AC_RowStack, base_rank=NO_RANK) + RowStack_Class = StackWrapper(FreeCell_AC_RowStack, base_rank=NO_RANK) + + def createGame(self): + return DerKatzenschwanz.createGame(self, rows=8, reserves=8) + + def _shuffleHook(self, cards): + for c in cards[:]: + if c.rank == 12: + cards.remove(c) + break + cards.append(c) + return cards + + +# /*********************************************************************** +# // Retinue +# ************************************************************************/ + +class Retinue(DieSchlange, Kings): + + ##RowStack_Class = StackWrapper(AC_RowStack, base_rank=NO_RANK) + RowStack_Class = StackWrapper(FreeCell_AC_RowStack, base_rank=NO_RANK) + + def createGame(self): + return DerKatzenschwanz.createGame(self, rows=8, reserves=8) + def _shuffleHook(self, cards): + return Kings._shuffleHook(self, cards) + def startGame(self): + return DieSchlange.startGame(self) + + +# /*********************************************************************** +# // Salic Law +# ************************************************************************/ + +class SalicLaw_Hint(DefaultHint): + + # Score for dropping ncards from stack r to stack t. + def _getDropCardScore(self, score, color, r, t, ncards): + return score+len(r.cards), color + + +class SalicLaw_Talon(OpenTalonStack): + + def canDealCards(self): + return True + + def canFlipCard(self): + return False + + def dealCards(self, sound=0): + if len(self.cards) == 0: + return 0 + old_state = self.game.enterState(self.game.S_DEAL) + rows = self.game.s.rows + c = self.cards[-1] + ri = len([r for r in rows if r.cards]) + if c.rank == KING: + to_stack = rows[ri] + else: + to_stack = rows[ri-1] + ##frames = (3, 4)[ri > 4] + frames = 3 + if not self.game.demo: + self.game.startDealSample() + self.game.moveMove(1, self, to_stack, frames=frames) + self.game.flipMove(to_stack) + if not self.game.demo: + self.game.stopSamples() + self.game.leaveState(old_state) + return 1 + + +class SalicLaw(DerKatzenschwanz): + + Hint_Class = SalicLaw_Hint + + # + # game layout + # + + def createGame(self): #, rows=9, reserves=8): + # create layout + l, s = Layout(self), self.s + + # set size + self.setSize(l.XM + 10*l.XS, l.YM + 7*l.YS) + + # + playcards = 4*l.YS / l.YOFFSET + xoffset, yoffset = [], [] + for i in range(playcards): + xoffset.append(0) + yoffset.append(l.YOFFSET) + for i in range(104-playcards): + xoffset.append(l.XOFFSET) + yoffset.append(0) + + # create stacks + x, y = l.XM, l.YM + for i in range(8): + s.foundations.append(AbstractFoundationStack(x, y, self, + suit=ANY_SUIT, max_cards=1, max_move=0, base_rank=QUEEN)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(8): + s.foundations.append(RK_FoundationStack(x, y, self, + suit=ANY_SUIT, base_rank=ACE, max_cards=11)) + x += l.XS + + x, y = l.XM, l.YM+2*l.YS + self.setRegion(s.foundations[8:], (-999, -999, 999999, y - l.XM / 2)) + for i in range(8): + stack = OpenStack(x, y, self, max_move=1) + stack.CARD_XOFFSET = xoffset + stack.CARD_YOFFSET = yoffset + s.rows.append(stack) + x += l.XS + s.talon = SalicLaw_Talon(l.XM+9*l.XS, l.YM, self) + l.createText(s.talon, "ss") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + for c in cards[:]: + if c.rank == KING: + cards.remove(c) + break + cards.append(c) + return cards + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(self.s.rows[:1]) # deal King + + def isGameWon(self): + for s in self.s.foundations[8:]: + if len(s.cards) != 11: + return False + return True + + def getAutoStacks(self, event=None): + if event is None: + # disable auto drop + return (self.sg.dropstacks, (), self.sg.dropstacks) + else: + # rightclickHandler + return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks) + + +# /*********************************************************************** +# // Deep +# ************************************************************************/ + +class Deep(DerKatzenschwanz): + RowStack_Class = StackWrapper(AC_RowStack, base_rank=ANY_RANK) + + def createGame(self): + return DerKatzenschwanz.createGame(self, rows=8, reserves=8) + + def startGame(self): + for i in range(12): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + + +# register the game +registerGame(GameInfo(141, DerKatzenschwanz, "Cat's Tail", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0, + altnames=("Der Katzenschwanz",) )) +registerGame(GameInfo(142, DieSchlange, "Snake", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0, + altnames=("Die Schlange",) )) +registerGame(GameInfo(279, Kings, "Kings", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(286, Retinue, "Retinue", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(299, SalicLaw, "Salic Law", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(442, Deep, "Deep", + GI.GT_FREECELL | GI.GT_OPEN, 2, 0)) + + diff --git a/pysollib/games/klondike.py b/pysollib/games/klondike.py new file mode 100644 index 0000000000..f24009e536 --- /dev/null +++ b/pysollib/games/klondike.py @@ -0,0 +1,1040 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault, Struct +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import KlondikeType_Hint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Klondike +# ************************************************************************/ + +class Klondike(Game): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = KingAC_RowStack + Hint_Class = KlondikeType_Hint + + def createGame(self, max_rounds=-1, num_deal=1, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=7, waste=1, texts=1, playcards=16) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + if l.s.waste: + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + return l + + def startGame(self, flip=0, reverse=1): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=flip, frames=0, reverse=reverse) + self.startDealSample() + self.s.talon.dealRow(reverse=reverse) + if self.s.waste: + self.s.talon.dealCards() # deal first card to WasteStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Vegas Klondike +# ************************************************************************/ + +class VegasKlondike(Klondike): + getGameScore = Game.getGameScoreCasino + getGameBalance = Game.getGameScoreCasino + + def createGame(self, max_rounds=1): + Klondike.createGame(self, max_rounds=max_rounds) + self.texts.score = MfxCanvasText(self.canvas, + 8, self.height - 8, anchor="sw", + font=self.app.getFont("canvas_large")) + + def updateText(self): + if self.preview > 1: + return + b1, b2 = self.app.stats.gameid_balance, 0 + if self.shallUpdateBalance(): + b2 = self.getGameBalance() + if 0 and self.app.debug: + t = "Balance %d/%d" % (b1, b2) + else: + t = _("Balance $%d") % (b1 + b2) + self.texts.score.config(text=t) + + def getDemoInfoTextAttr(self, tinfo): + return tinfo[1] # "se" corner + + +# /*********************************************************************** +# // Casino Klondike +# ************************************************************************/ + +class CasinoKlondike(VegasKlondike): + def createGame(self): + VegasKlondike.createGame(self, max_rounds=3) + + +# /*********************************************************************** +# // Klondike by Threes +# ************************************************************************/ + +class KlondikeByThrees(Klondike): + def createGame(self): + Klondike.createGame(self, num_deal=3) + + +# /*********************************************************************** +# // Thumb and Pouch +# ************************************************************************/ + +class ThumbAndPouch_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isAnySuitButOwnSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + return _('Row. Build down in any suit but the same.') + + +class ThumbAndPouch(Klondike): + RowStack_Class = ThumbAndPouch_RowStack + + def createGame(self): + Klondike.createGame(self, max_rounds=1) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit != card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Whitehead +# ************************************************************************/ + +class Whitehead(Klondike): + RowStack_Class = SC_RowStack + Hint_Class = CautiousDefaultHint + + def createGame(self): + Klondike.createGame(self, max_rounds=1) + + def startGame(self): + Klondike.startGame(self, flip=1) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Small Harp (Klondike in a different layout) +# ************************************************************************/ + +class SmallHarp(Klondike): + Layout_Method = Layout.gypsyLayout + + def startGame(self): + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Eastcliff +# // Easthaven +# ************************************************************************/ + +class Eastcliff(Klondike): + RowStack_Class = AC_RowStack + + def createGame(self): + Klondike.createGame(self, max_rounds=1) + + def startGame(self): + for i in range(2): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + if self.s.waste: + self.s.talon.dealCards() # deal first card to WasteStack + + +class Easthaven(Eastcliff): + Talon_Class = DealRowTalonStack + def createGame(self): + Klondike.createGame(self, max_rounds=1, waste=0) + +class DoubleEasthaven(Easthaven): + def createGame(self): + Klondike.createGame(self, rows=8, max_rounds=1, waste=0, playcards=20) + +class TripleEasthaven(Easthaven): + def createGame(self): + Klondike.createGame(self, rows=12, max_rounds=1, waste=0, playcards=26) + + +# /*********************************************************************** +# // Westcliff +# // Westhaven +# ************************************************************************/ + +class Westcliff(Eastcliff): + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + + def createGame(self): + Klondike.createGame(self, max_rounds=1, rows=10) + + +class Westhaven(Westcliff): + Talon_Class = DealRowTalonStack + + def createGame(self): + Klondike.createGame(self, max_rounds=1, rows=10, waste=0) + + +# /*********************************************************************** +# // Pas Seul +# ************************************************************************/ + +class PasSeul(Eastcliff): + def createGame(self): + Klondike.createGame(self, max_rounds=1, rows=6) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Blind Alleys +# ************************************************************************/ + +class BlindAlleys(Eastcliff): + def createGame(self): + Klondike.createGame(self, max_rounds=2, rows=6) + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + Eastcliff.startGame(self) + + +# /*********************************************************************** +# // Somerset +# // Morehead +# ************************************************************************/ + +class Somerset(Klondike): + Talon_Class = InitialDealTalonStack + RowStack_Class = StackWrapper(AC_RowStack, max_move=1) + Hint_Class = CautiousDefaultHint + + def createGame(self): + Klondike.createGame(self, max_rounds=1, rows=10, waste=0, texts=0) + + def startGame(self): + for i in range(6): + self.s.talon.dealRow(rows=self.s.rows[i:], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[6:]) + self.s.talon.dealRow(rows=self.s.rows[7:]) + + +class Morehead(Somerset): + RowStack_Class = StackWrapper(ThumbAndPouch_RowStack, max_move=1) + + +# /*********************************************************************** +# // Canister +# ************************************************************************/ + +class Canister(Klondike): + Talon_Class = InitialDealTalonStack + RowStack_Class = RK_RowStack + ###Hint_Class = CautiousDefaultHint + + def createGame(self): + Klondike.createGame(self, max_rounds=1, rows=8, waste=0, texts=0) + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.rows[2:6]) + + +# /*********************************************************************** +# // Agnes Sorel +# ************************************************************************/ + +class AgnesSorel(Klondike): + Talon_Class = DealRowTalonStack + Foundation_Class = StackWrapper(SS_FoundationStack, mod=13, base_rank=NO_RANK, max_move=0) + RowStack_Class = StackWrapper(SC_RowStack, mod=13, base_rank=NO_RANK) + + def createGame(self): + Klondike.createGame(self, max_rounds=1, waste=0) + + def startGame(self): + Klondike.startGame(self, flip=1) + c = self.s.talon.dealSingleBaseCard() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color == card2.color and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + +# /*********************************************************************** +# // 8 x 8 +# // Achtmal Acht +# ************************************************************************/ + +class EightTimesEight(Klondike): + Layout_Method = Layout.gypsyLayout + RowStack_Class = AC_RowStack + + def createGame(self): + Klondike.createGame(self, rows=8) + + def startGame(self): + for i in range(7): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +class AchtmalAcht(EightTimesEight): + def createGame(self): + l = Klondike.createGame(self, rows=8, max_rounds=3) + s = self.s + x, y = s.waste.x - l.XM, s.waste.y + s.talon.texts.rounds = MfxCanvasText(self.canvas, x, y, + anchor="ne", + font=self.app.getFont("canvas_default")) + + +# /*********************************************************************** +# // Batsford +# ************************************************************************/ + +class Batsford_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + # must be a King + return cards[0].rank == KING + def getHelp(self): + return _('Reserve. Only Kings are acceptable.') + +class Batsford(Klondike): + def createGame(self, **layout): + l = Klondike.createGame(self, rows=10, max_rounds=1, playcards=22) + s = self.s + x, y = l.XM, self.height - l.YS + s.reserves.append(Batsford_ReserveStack(x, y, self, max_cards=3)) + self.setRegion(s.reserves, (-999, y - l.YM, x + l.XS, 999999), priority=1) + l.createText(s.reserves[0], "se") + l.defaultStackGroups() + + +# /*********************************************************************** +# // Jumbo +# ************************************************************************/ + +class Jumbo(Klondike): + def createGame(self): + Klondike.createGame(self, rows=9, max_rounds=2) + + def startGame(self, flip=0): + for i in range(9): + self.s.talon.dealRow(rows=self.s.rows[:i], flip=flip, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + +class OpenJumbo(Jumbo): + def startGame(self): + Jumbo.startGame(self, flip=1) + + +# /*********************************************************************** +# // Stonewall +# // Flower Garden +# ************************************************************************/ + +class Stonewall(Klondike): + Talon_Class = InitialDealTalonStack + RowStack_Class = AC_RowStack + + DEAL = (0, 1, 0, 1, -1, 0, 1) + + def createGame(self): + l = Klondike.createGame(self, rows=6, max_rounds=1, texts=0) + s = self.s + h = max(self.height, l.YM+4*l.YS) + self.setSize(self.width + l.XM+4*l.XS, h) + for i in range(4): + for j in range(4): + x, y = self.width + (j-4)*l.XS, l.YM + i*l.YS + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + l.defaultStackGroups() + + def startGame(self): + frames = 0 + for flip in self.DEAL: + if flip < 0: + frames = -1 + self.startDealSample() + else: + self.s.talon.dealRow(flip=flip, frames=frames) + self.s.talon.dealRow(rows=self.s.reserves) + assert len(self.s.talon.cards) == 0 + + +class FlowerGarden(Stonewall): + RowStack_Class = StackWrapper(RK_RowStack, max_move=1) + Hint_Class = CautiousDefaultHint + + DEAL = (1, 1, 1, 1, -1, 1, 1) + + +# /*********************************************************************** +# // King Albert +# // Raglan +# // Brigade +# ************************************************************************/ + +class KingAlbert(Klondike): + Talon_Class = InitialDealTalonStack + RowStack_Class = StackWrapper(AC_RowStack, max_move=1) + Hint_Class = CautiousDefaultHint + + ROWS = 9 + RESERVES = (2, 2, 2, 1) + + def createGame(self): + l = Klondike.createGame(self, max_rounds=1, rows=self.ROWS, waste=0, texts=0) + s = self.s + rw, rh = max(self.RESERVES), len(self.RESERVES) + h = max(self.height, l.YM+rh*l.YS) + self.setSize(self.width + 2*l.XM+rw*l.XS, h) + for i in range(rh): + for j in range(self.RESERVES[i]): + x, y = self.width + (j-rw)*l.XS, l.YM + i*l.YS + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + l.defaultStackGroups() + + def startGame(self): + Klondike.startGame(self, flip=1, reverse=0) + self.s.talon.dealRow(rows=self.s.reserves) + + +class Raglan(KingAlbert): + RESERVES = (2, 2, 2) + + def _shuffleHook(self, cards): + # move Aces to bottom of the Talon (i.e. last cards to be dealt) + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + for i in range(6): + self.s.talon.dealRow(rows=self.s.rows[i:], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[6:]) + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow(rows=self.s.foundations) + + +class Brigade(Raglan): + RowStack_Class = StackWrapper(RK_RowStack, max_move=1) + + ROWS = 7 + RESERVES = (4, 4, 4, 1) + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow(rows=self.s.foundations) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank) + + +# /*********************************************************************** +# // Jane +# // Agnes Bernauer +# ************************************************************************/ + +class Jane_Talon(OpenTalonStack): + def canFlipCard(self): + return 0 + + def canDealCards(self): + return len(self.cards) >= 2 + + def dealCards(self, sound=0): + c = 0 + if len(self.cards) > 2: + c = self.dealRow(self.game.s.reserves, sound=sound) + if len(self.cards) == 2: + self.game.flipMove(self) + self.game.moveMove(1, self, self.game.s.waste, frames=4, shadow=0) + self.game.flipMove(self) + c = c + 1 + return c + + +class Jane(Klondike): + Talon_Class = Jane_Talon + Foundation_Class = StackWrapper(SS_FoundationStack, mod=13, base_rank=NO_RANK, min_cards=1) + RowStack_Class = StackWrapper(AC_RowStack, mod=13, base_rank=NO_RANK) + + def createGame(self, max_rounds=1, reserves=7, **layout): + kwdefault(layout, texts=0) + l = apply(Klondike.createGame, (self, max_rounds), layout) + s = self.s + h = max(self.height, l.YM+4*l.YS) + self.setSize(self.width + l.XM+2*l.XS, h) + x0, y = self.width - 2*l.XS, l.YM + for i in range(reserves): + x = x0 + ((i+1) & 1) * l.XS + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_YOFFSET = l.YM / 3 + stack.is_open = 1 + s.reserves.append(stack) + y = y + l.YS / 2 + # not needed, as no cards may be placed on the reserves + ##self.setRegion(s.reserves, (x0-l.XM/2, -999, 999999, 999999), priority=1) + l.defaultStackGroups() + self.sg.dropstacks.append(s.talon) + x, y = l.XM, self.height - l.YM + # ??? + self.texts.info = MfxCanvasText(self.canvas, x, y, anchor="sw", + font=self.app.getFont("canvas_default")) + + def startGame(self, flip=0, reverse=1): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=flip, frames=0, reverse=reverse) + self.startDealSample() + self.s.talon.dealRow(reverse=reverse) + self.s.talon.dealRow(rows=self.s.reserves) + c = self.s.talon.dealSingleBaseCard() + # update base rank of row stacks + cap = Struct(base_rank=(c.rank - 1) % 13) + for s in self.s.rows: + s.cap.update(cap.__dict__) + self.saveinfo.stack_caps.append((s.id, cap)) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + def _autoDeal(self, sound=1): + return 0 + + +class AgnesBernauer_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + return self.dealRowAvail(self.game.s.reserves, sound=sound) + + +class AgnesBernauer(Jane): + Talon_Class = AgnesBernauer_Talon + Foundation_Class = StackWrapper(SS_FoundationStack, mod=13, base_rank=NO_RANK, max_move=0) + + def createGame(self): + Jane.createGame(self, max_rounds=1, waste=0, texts=1) + + def startGame(self): + Jane.startGame(self, flip=1) + + +# /*********************************************************************** +# // Senate +# ************************************************************************/ + +class Senate(Jane): + + def createGame(self, rows=4): + + playcards = 10 + + l, s = Layout(self), self.s + self.setSize(3*l.XM+(rows+6)*l.XS, l.YM+2*(l.YS+playcards*l.YOFFSET)) + + x, y = l.XM, l.YM + for i in range(rows): + s.rows.append(SS_RowStack(x, y, self)) + x += l.XS + + for y in l.YM, l.YM+l.YS+playcards*l.YOFFSET: + x = 2*l.XM+rows*l.XS + for i in range(4): + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.reserves.append(stack) + x += l.XS + x = 3*l.XM+(rows+4)*l.XS + for i in range(2): + y = l.YM+l.YS + for j in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=j)) + y += l.YS + x += l.XS + x, y = 3*l.XM+(rows+5)*l.XS, l.YM + s.talon = AgnesBernauer_Talon(x, y, self) + l.createText(s.talon, 'sw') + + l.defaultStackGroups() + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow() + + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, (c.deck, c.suit))) + +class SenatePlus(Senate): + def createGame(self): + Senate.createGame(self, rows=5) + +# /*********************************************************************** +# // Phoenix +# // Arizona +# ************************************************************************/ + +class Phoenix(Klondike): + + Hint_Class = CautiousDefaultHint + RowStack_Class = AC_RowStack + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM + 10*l.XS, l.YM + 4*(l.YS+l.YM)) + + for i in range(2): + x = l.XM + i*l.XS + for j in range(4): + y = l.YM + j*(l.YS+l.YM) + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + for i in range(2): + x = l.XM + (8+i)*l.XS + for j in range(4): + y = l.YM + j*(l.YS+l.YM) + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + for i in range(4): + s.foundations.append(SS_FoundationStack(l.XM+(3+i)*l.XS, l.YM, self, i)) + for i in range(6): + s.rows.append(self.RowStack_Class(l.XM+(2+i)*l.XS, l.YM+l.YS, self)) + s.talon = InitialDealTalonStack(l.XM+int(4.5*l.XS), l.YM+3*(l.YS+l.YM), self) + + l.defaultStackGroups() + + def startGame(self): + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + + +class Arizona(Phoenix): + RowStack_Class = RK_RowStack + + +# /*********************************************************************** +# // Alternation +# ************************************************************************/ + +class Alternation(Klondike): + + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + RowStack_Class = StackWrapper(AC_RowStack, base_rank=ANY_RANK) + + def createGame(self): + Klondike.createGame(self, max_rounds=1) + + def startGame(self): + for i in range(6): + self.s.talon.dealRow(rows=self.s.rows, flip=(i+1)%2, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Lanes +# ************************************************************************/ + +class Lanes(Klondike): + + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + RowStack_Class = StackWrapper(AC_RowStack, base_rank=ANY_RANK, max_move=1) + + def createGame(self): + Klondike.createGame(self, rows=6, max_rounds=2) + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + for i in range(2): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Thirty Six +# ************************************************************************/ + +class ThirtySix(Klondike): + + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + RowStack_Class = StackWrapper(RK_RowStack, base_rank=ANY_RANK) + + def createGame(self): + Klondike.createGame(self, rows=6, max_rounds=1) + + def _fillOne(self): + for r in self.s.rows: + if r.cards: + c = r.cards[-1] + for f in self.s.foundations: + if f.acceptsCards(r, [c]): + self.moveMove(1, r, f, frames=4, shadow=0) + return 1 + return 0 + + def startGame(self): + self.startDealSample() + for i in range(6): + self.s.talon.dealRow() + while True: + if not self._fillOne(): + break + self.s.talon.dealCards() # deal first card to WasteStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Q.C. +# ************************************************************************/ + +class Q_C_(Klondike): + + Hint_Class = CautiousDefaultHint + Foundation_Class = StackWrapper(SS_FoundationStack, max_move=0) + RowStack_Class = StackWrapper(SS_RowStack, base_rank=ANY_RANK, max_move=1) + + def createGame(self): + Klondike.createGame(self, rows=6, max_rounds=2) + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + self.fillAll() + + def fillOne(self, stack): + if stack.cards: + c = stack.cards[-1] + for f in self.s.foundations: + if f.acceptsCards(stack, [c]): + stack.moveMove(1, f) + return 1 + return 0 + + def fillAll(self): + # rows + for r in self.s.rows: + if self.fillOne(r): + self.fillAll() + return + # waste + if self.fillOne(self.s.waste): + self.fillAll() + + def fillStack(self, stack): + if stack in self.s.rows: + if not stack.cards and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + self.fillAll() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Northwest Territory +# ************************************************************************/ + +class NorthwestTerritory(KingAlbert): + RowStack_Class = StackWrapper(AC_RowStack, base_rank=KING) + RESERVES = (4, 4, 4, 4) + ROWS = 8 + + def startGame(self): + Klondike.startGame(self, flip=0, reverse=0) + self.s.talon.dealRow(rows=self.s.reserves) + + +# /*********************************************************************** +# // Aunt Mary +# ************************************************************************/ + +class AuntMary(Klondike): + def createGame(self): + Klondike.createGame(self, rows=6, max_rounds=1) + def startGame(self): + for i in range(5): + j = i+1 + self.s.talon.dealRow(rows=self.s.rows[:j], frames=0, flip=1) + self.s.talon.dealRow(rows=self.s.rows[j:], frames=0, flip=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + +# /*********************************************************************** +# // Double Dot +# ************************************************************************/ + +class DoubleDot(Klondike): + Talon_Class = DealRowTalonStack + RowStack_Class = StackWrapper(RK_RowStack, dir=-2, mod=13) + Foundation_Class = StackWrapper(SS_FoundationStack, dir=2, mod=13) + + def createGame(self): + Klondike.createGame(self, max_rounds=1, rows=8, waste=0) + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: ((c.rank == ACE and c.suit in (0,1)) or + (c.rank == 1 and c.suit in (2,3)), c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Seven Devils +# ************************************************************************/ + +class SevenDevils_RowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return False + return not from_stack in self.game.s.reserves + + +class SevenDevils(Klondike): + + Hint_Class = CautiousDefaultHint + RowStack_Class = StackWrapper(SevenDevils_RowStack, max_move=1) + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM + 10*l.XS, l.YM+3*l.YS+12*l.YOFFSET) + + x, y = l.XM, l.YM + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i/2)) + x += l.XS + x, y = l.XM+l.XS/2, l.YM+l.YS + for i in range(7): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + x0, y = self.width - 2*l.XS, l.YM + for i in range(7): + x = x0 + ((i+1) & 1) * l.XS + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + y = y + l.YS / 2 + x, y = l.XM, self.height-l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, 'n') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'n') + + l.defaultStackGroups() + + + def startGame(self, flip=0, reverse=1): + Klondike.startGame(self) + self.s.talon.dealRow(rows=self.s.reserves) + + + +# register the game +registerGame(GameInfo(2, Klondike, "Klondike", + GI.GT_KLONDIKE, 1, -1)) +registerGame(GameInfo(61, CasinoKlondike, "Casino Klondike", + GI.GT_KLONDIKE | GI.GT_SCORE, 1, 2)) +registerGame(GameInfo(129, VegasKlondike, "Vegas Klondike", + GI.GT_KLONDIKE | GI.GT_SCORE, 1, 0)) +registerGame(GameInfo(18, KlondikeByThrees, "Klondike by Threes", + GI.GT_KLONDIKE, 1, -1)) +registerGame(GameInfo(58, ThumbAndPouch, "Thumb and Pouch", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(67, Whitehead, "Whitehead", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(39, SmallHarp, "Small Harp", + GI.GT_KLONDIKE, 1, -1, + altnames=("Die kleine Harfe",) )) +registerGame(GameInfo(66, Eastcliff, "Eastcliff", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(224, Easthaven, "Easthaven", + GI.GT_GYPSY, 1, 0)) +registerGame(GameInfo(33, Westcliff, "Westcliff", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(225, Westhaven, "Westhaven", + GI.GT_GYPSY, 1, 0)) +registerGame(GameInfo(107, PasSeul, "Pas Seul", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(81, BlindAlleys, "Blind Alleys", + GI.GT_KLONDIKE, 1, 1)) +registerGame(GameInfo(215, Somerset, "Somerset", + GI.GT_KLONDIKE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(231, Canister, "Canister", + GI.GT_KLONDIKE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(229, AgnesSorel, "Agnes Sorel", + GI.GT_GYPSY, 1, 0)) +registerGame(GameInfo(4, EightTimesEight, "8 x 8", + GI.GT_KLONDIKE, 2, -1)) +registerGame(GameInfo(127, AchtmalAcht, "Eight Times Eight", + GI.GT_KLONDIKE, 2, 2, + altnames=("Achtmal Acht",) )) +registerGame(GameInfo(133, Batsford, "Batsford", + GI.GT_KLONDIKE, 2, 0)) +registerGame(GameInfo(221, Stonewall, "Stonewall", + GI.GT_RAGLAN, 1, 0)) +registerGame(GameInfo(222, FlowerGarden, "Flower Garden", + GI.GT_RAGLAN | GI.GT_OPEN, 1, 0, + altnames=("The Bouquet", "The Garden",) )) +registerGame(GameInfo(233, KingAlbert, "King Albert", + GI.GT_RAGLAN | GI.GT_OPEN, 1, 0, + altnames=("Idiot's Delight",) )) +registerGame(GameInfo(232, Raglan, "Raglan", + GI.GT_RAGLAN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(223, Brigade, "Brigade", + GI.GT_RAGLAN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(230, Jane, "Jane", + GI.GT_RAGLAN, 1, 0)) +registerGame(GameInfo(236, AgnesBernauer, "Agnes Bernauer", + GI.GT_RAGLAN, 1, 0)) +registerGame(GameInfo(263, Phoenix, "Phoenix", + GI.GT_RAGLAN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(283, Jumbo, "Jumbo", + GI.GT_KLONDIKE, 2, 1)) +registerGame(GameInfo(333, OpenJumbo, "Open Jumbo", + GI.GT_KLONDIKE, 2, 1)) +registerGame(GameInfo(297, Alternation, "Alternation", + GI.GT_KLONDIKE, 2, 0)) +registerGame(GameInfo(326, Lanes, "Lanes", + GI.GT_KLONDIKE, 1, 1)) +registerGame(GameInfo(327, ThirtySix, "Thirty Six", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(350, Q_C_, "Q.C.", + GI.GT_KLONDIKE, 2, 1)) +registerGame(GameInfo(361, NorthwestTerritory, "Northwest Territory", + GI.GT_RAGLAN, 1, 0)) +registerGame(GameInfo(362, Morehead, "Morehead", + GI.GT_KLONDIKE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(388, Senate, "Senate", + GI.GT_RAGLAN, 2, 0)) +registerGame(GameInfo(389, SenatePlus, "Senate +", + GI.GT_RAGLAN, 2, 0)) +registerGame(GameInfo(390, Arizona, "Arizona", + GI.GT_RAGLAN | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(407, AuntMary, "Aunt Mary", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(420, DoubleDot, "Double Dot", + GI.GT_KLONDIKE, 1, 0)) +registerGame(GameInfo(434, SevenDevils, "Seven Devils", + GI.GT_RAGLAN, 2, 0)) +registerGame(GameInfo(452, DoubleEasthaven, "Double Easthaven", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(453, TripleEasthaven, "Triple Easthaven", + GI.GT_GYPSY, 3, 0)) + diff --git a/pysollib/games/labyrinth.py b/pysollib/games/labyrinth.py new file mode 100644 index 0000000000..5fab2fc299 --- /dev/null +++ b/pysollib/games/labyrinth.py @@ -0,0 +1,140 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Labyrinth +# ************************************************************************/ + +class Labyrinth_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + top_stacks = [] + for i in range(8): + for r in self.game.s.rows[i::8]: + if not r.cards: + top_stacks.append(r) + break + return self.dealRowAvail(rows=top_stacks, sound=sound) + +class Labyrinth_RowStack(BasicRowStack): + + def clickHandler(self, event): + BasicRowStack.doubleclickHandler(self, event) + return True + + def basicIsBlocked(self): + if self in self.game.s.rows[:8]: + return False + if self.id+8 >= len(self.game.allstacks): + return False + r = self.game.allstacks[self.id+8] + if r in self.game.s.rows and r.cards: + return True + return False + + + +class Labyrinth(Game): + + # + # game layout + # + + def createGame(self): + + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+8*l.XS, l.YM+l.YS+20*l.YOFFSET) + + # create stacks + s.talon = Labyrinth_Talon(l.XM, l.YM, self) + + x, y, = l.XM+2*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + + x, y = l.XM, l.YM+l.YS + for i in range(6): + x = l.XM + for j in range(8): + s.rows.append(Labyrinth_RowStack(x, y, self, max_move=1)) + x += l.XS + y += l.YOFFSET + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow(rows=self.s.rows[:8]) + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def fillStack(self, stack): + if stack in self.s.rows[:8] and not stack.cards: + rows = self.s.rows + to_stack = stack + #if not self.demo: + # self.startDealSample() + old_state = self.enterState(self.S_FILL) + for r in rows[list(rows).index(stack)+8::8]: + if r.cards: + self.moveMove(1, r, to_stack, frames=0) + to_stack = r + else: + break + if not stack.cards and self.s.talon.cards: + self.s.talon.dealRow(rows=[stack]) + self.leaveState(old_state) + #if not self.demo: + # self.stopSamples() + + +# register the game + +#registerGame(GameInfo(400, Labyrinth, "Labyrinth", +# GI.GT_1DECK_TYPE, 1, 0)) + diff --git a/pysollib/games/mahjongg/__init__.py b/pysollib/games/mahjongg/__init__.py new file mode 100644 index 0000000000..f92860b0d0 --- /dev/null +++ b/pysollib/games/mahjongg/__init__.py @@ -0,0 +1,4 @@ +import mahjongg1 +import mahjongg2 +import mahjongg3 +import shisensho diff --git a/pysollib/games/mahjongg/mahjongg.py b/pysollib/games/mahjongg/mahjongg.py new file mode 100644 index 0000000000..6f26e6f08e --- /dev/null +++ b/pysollib/games/mahjongg/mahjongg.py @@ -0,0 +1,719 @@ +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, string, re +#from tkFont import Font + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault, Struct +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText, MfxCanvasImage, bind, \ + EVENT_HANDLED, ANCHOR_NW + + +def factorial(x): + if x < 1: + return 1 + a = 1 + for i in xrange(x): + a *= (i+1) + return a + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Mahjongg_Hint(AbstractHint): + # FIXME: no intelligence whatsoever is implemented here + def computeHints(self): + game = self.game + # get free stacks + stacks = [] + for r in game.s.rows: + if r.cards and not r.basicIsBlocked(): + stacks.append(r) + # find matching tiles + i = 0 + for r in stacks: + for t in stacks[i+1:]: + if game.cardsMatch(r.cards[0], t.cards[0]): + # simple scoring... + score = 10000 + r.id + t.id + self.addHint(score, 1, r, t) + i = i + 1 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +#class Mahjongg_Foundation(AbstractFoundationStack): +class Mahjongg_Foundation(OpenStack): + + def __init__(self, x, y, game, suit=ANY_SUIT, **cap): + kwdefault(cap, max_move=0, max_accept=0, max_cards=game.NCARDS) + #apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def acceptsCards(self, from_stack, cards): + # We do not accept any cards - pairs will get + # delivered by _dropPairMove() below. + return 0 + + def basicIsBlocked(self): + return 1 + + def initBindings(self): + pass + + def _position(self, card): + #AbstractFoundationStack._position(self, card) + OpenStack._position(self, card) + + fnds = self.game.s.foundations + + cols = (3, 2, 1, 0) + for i in cols: + for j in range(9): + n = i*9+j + if fnds[n].cards: + fnds[n].group.tkraise() + return + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Mahjongg_RowStack(OpenStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1, max_cards=2, + base_rank=NO_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def basicIsBlocked(self): + # any of above blocks + for stack in self.blockmap.above: + if stack.cards: + return 1 + # any of left blocks - but we can try right as well + for stack in self.blockmap.left: + if stack.cards: + break + else: + return 0 + # any of right blocks + for stack in self.blockmap.right: + if stack.cards: + return 1 + return 0 + + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + return self.game.cardsMatch(self.cards[0], cards[-1]) + + def canFlipCard(self): + return 0 + + def canDropCards(self, stacks): + return (None, 0) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow) + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + ##print 'drop:', self.id, other_stack.id + assert n == 1 and self.acceptsCards(other_stack, [other_stack.cards[-1]]) + if not self.game.demo: + self.game.playSample("droppair", priority=200) + old_state = self.game.enterState(self.game.S_FILL) + c = self.cards[0] + if c.suit == 3: + if c.rank >= 8: + i = 35 + elif c.rank >= 4: + i = 34 + else: + i = 30+c.rank + elif c.rank == 9: + i = 27+c.suit + else: + i = c.suit*9+c.rank + f = self.game.s.foundations[i] + self.game.moveMove(n, self, f, frames=frames, shadow=shadow) + self.game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + self.game.leaveState(old_state) + self.fillStack() + other_stack.fillStack() + + # + # Mahjongg special overrides + # + + # Mahjongg special: we must preserve the relative stacking order + # to keep our pseudo 3D look. + def _position(self, card): + OpenStack._position(self, card) + # + rows = filter(lambda s: s.cards, self.game.s.rows[:self.id]) + if rows: + self.group.tkraise(rows[-1].group) + return + rows = filter(lambda s: s.cards, self.game.s.rows[self.id+1:]) + if rows: + self.group.lower(rows[0].group) + return + + # In Mahjongg games type there are a lot of stacks, so we optimize + # and don't create bindings that are not used anyway. + def initBindings(self): + group = self.group + # FIXME: dirty hack to access the Stack's private methods + #bind(group, "<1>", self._Stack__clickEventHandler) + #bind(group, "<3>", self._Stack__controlclickEventHandler) + #bind(group, "", self._Stack__controlclickEventHandler) + # + bind(group, "<1>", self.__clickEventHandler) + bind(group, "<3>", self.__controlclickEventHandler) + bind(group, "", self.__controlclickEventHandler) + + def __defaultClickEventHandler(self, event, handler): + if self.game.demo: + self.game.stopDemo(event) + if self.game.busy: + return EVENT_HANDLED + handler(event) + return EVENT_HANDLED + + def __clickEventHandler(self, event): + ##print 'click:', self.id + return self.__defaultClickEventHandler(event, self.clickHandler) + + def __controlclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.controlclickHandler) + + def clickHandler(self, event): + game = self.game + drag = game.drag + # checks + if not self.cards: + return 1 + from_stack = drag.stack + if from_stack is self: + # remove selection + self.game.playSample("nomove") + self._stopDrag() + return 1 + if self.basicIsBlocked(): + ### remove selection + ##self.game.playSample("nomove") + return 1 + # possible move + if from_stack: + if self.acceptsCards(from_stack, from_stack.cards): + self._stopDrag() + # this code actually moves the tiles + from_stack.playMoveMove(1, self, frames=0, sound=1) + return 1 + drag.stack = self + self.game.playSample("startdrag") + # move or create the shade image (see stack.py, _updateShade) + if drag.shade_img: + img = drag.shade_img + img.dtag(drag.shade_stack.group) + img.moveTo(self.x, self.y) + else: + img = game.app.images.getShade() + if img is None: + return 1 + img = MfxCanvasImage(game.canvas, self.x, self.y, + image=img, anchor=ANCHOR_NW) + drag.shade_img = img + # raise/lower the shade image to the correct stacking order + img.tkraise(self.cards[-1].item) + img.addtag(self.group) + drag.shade_stack = self + return 1 + + def cancelDrag(self, event=None): + if event is None: + self._stopDrag() + + def _findCard(self, event): + # we need to override this because the shade may be hiding + # the tile (from Tk's stacking view) + return len(self.cards) - 1 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class AbstractMahjonggGame(Game): + Hint_Class = Mahjongg_Hint + RowStack_Class = Mahjongg_RowStack + + GAME_VERSION = 2 + + NCARDS = 144 + + # For i18n + text_free_matching_pairs_0 = _("No Free\nMatching\nPairs") + text_free_matching_pairs_1 = _("1 Free\nMatching\nPair") + text_free_matching_pairs_2 = _(" Free\nMatching\nPairs") + text_tiles_removed = _("\nTiles\nRemoved\n\n") + text_tiles_remaining = _("\nTiles\nRemaining\n\n") + + + def getTiles(self): + # decode tile positions + L = self.L + + assert L[0] == "0" + assert (len(L) - 1) % 3 == 0 + + tiles = [] + max_tl, max_tx, max_ty = -1, -1, -1 + t = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + for i in range(1, len(L), 3): + n = t.find(L[i]) + level, height = n / 7, n % 7 + 1 + tx = t.find(L[i+1]) + ty = t.find(L[i+2]) + assert n >= 0 and tx >= 0 and ty >= 0 + max_tl = max(level + height - 1, max_tl) + max_tx = max(tx, max_tx) + max_ty = max(ty, max_ty) + for tl in range(level, level + height): + tiles.append((tl, tx, ty)) + assert len(tiles) == self.NCARDS + #tiles.sort() + #tiles = tuple(tiles) + return tiles, max_tl, max_tx, max_ty + + + # + # game layout + # + + def createGame(self): + tiles, max_tl, max_tx, max_ty = self.getTiles() + + # start layout + l, s = Layout(self), self.s + show_removed = self.app.opt.mahjongg_show_removed + + ##dx, dy = 2, -2 + ##dx, dy = 3, -3 + cs = self.app.cardset + if cs.version >= 6: + dx = l.XOFFSET + dy = -l.YOFFSET + d_x = cs.SHADOW_XOFFSET + d_y = cs.SHADOW_YOFFSET + if self.preview: + # Fixme + dx, dy, d_x, d_y = dx/2, dy/2, d_x/2, d_y/2 + else: + dx = 3 + dy = -3 + d_x = 0 + d_y = 0 + #print dx, dy, d_x, d_y, cs.version + + font = self.app.getFont("canvas_default") + + # width of self.texts.info + #ti_width = Font(self.canvas, font).measure(_('Remaining')) + ti_width = 80 + + # set window size + dxx, dyy = abs(dx) * (max_tl+1), abs(dy) * (max_tl+1) + # foundations dxx dyy + if self.NCARDS > 144: + fdxx = abs(dx)*8 + fdyy = abs(dy)*8 + else: + fdxx = abs(dx)*4 + fdyy = abs(dy)*4 + cardw, cardh = l.CW - d_x, l.CH - d_y + if show_removed: + left_margin = l.XM + 4*cardw+fdxx+d_x + l.XM + else: + left_margin = l.XM + w = left_margin + (max_tx+2)*cardw/2+dxx+d_x + l.XM+ti_width+l.XM + # left margin | tiles | right margin + h = l.YM + dyy + (max_ty + 2) * cardh / 2 + d_y + l.YM + if show_removed: + h = max(h, l.YM+fdyy+cardh*9+d_y+l.YM) + self.setSize(w, h) + + # set game extras + self.check_dist = l.CW*l.CW + l.CH*l.CH # see _getClosestStack() + + # sort tiles (for 3D) + tiles.sort(lambda a, b: + cmp(a[0], b[0]) or + cmp(-a[1]+a[2], -b[1]+b[2]) + ) + + # create a row stack for each tile and compute the tilemap + tilemap = {} + x0 = left_margin + y0 = l.YM + dyy + for level, tx, ty in tiles: + #print level, tx, ty + x = x0 + (tx * cardw) / 2 + level * dx + y = y0 + (ty * cardh) / 2 + level * dy + stack = self.RowStack_Class(x, y, self) + ##stack.G = (level, tx, ty) + stack.CARD_XOFFSET = dx + stack.CARD_YOFFSET = dy + s.rows.append(stack) + # tilemap - each tile covers 4 positions + tilemap[(level, tx, ty)] = stack + tilemap[(level, tx+1, ty)] = stack + tilemap[(level, tx, ty+1)] = stack + tilemap[(level, tx+1, ty+1)] = stack + + # compute blockmap + for stack in s.rows: + level, tx, ty = tiles[stack.id] + above, left, right, up, bottom = {}, {}, {}, {}, {} + # above blockers + for tl in range(level+1, level+2): + above[tilemap.get((tl, tx, ty))] = 1 + above[tilemap.get((tl, tx+1, ty))] = 1 + above[tilemap.get((tl, tx, ty+1))] = 1 + above[tilemap.get((tl, tx+1, ty+1))] = 1 + # left blockers + left[tilemap.get((level, tx-1, ty))] = 1 + left[tilemap.get((level, tx-1, ty+1))] = 1 + # right blockers + right[tilemap.get((level, tx+2, ty))] = 1 + right[tilemap.get((level, tx+2, ty+1))] = 1 + # up blockers + ##up[tilemap.get((level, tx, ty-1))] = 1 + ##up[tilemap.get((level, tx+1, ty-1))] = 1 + # bottom blockers + ##bottom[tilemap.get((level, tx, ty+2))] = 1 + ##bottom[tilemap.get((level, tx+1, ty+2))] = 1 + # sanity check - assert that there are no overlapping tiles + assert tilemap.get((level, tx, ty)) is stack + assert tilemap.get((level, tx+1, ty)) is stack + assert tilemap.get((level, tx, ty+1)) is stack + assert tilemap.get((level, tx+1, ty+1)) is stack + # + above = tuple(filter(None, above.keys())) + left = tuple(filter(None, left.keys())) + right = tuple(filter(None, right.keys())) + ##up = tuple(filter(None, up.keys())) + ##bottom = tuple(filter(None, bottom.keys())) + # assemble + stack.blockmap = Struct( + above = above, + left = left, + right = right, + ##up = up, + ##bottom = bottom, + ) + + # create other stacks + for i in range(4): + for j in range(9): + if show_removed: + x = l.XM+i*cardw + y = l.YM+fdyy+j*cardh + else: + x = -l.XS + y = l.YM+dyy + stack = Mahjongg_Foundation(x, y, self) + if show_removed: + stack.CARD_XOFFSET = dx + stack.CARD_YOFFSET = dy + s.foundations.append(stack) + + self.texts.info = MfxCanvasText(self.canvas, + self.width - l.XM - ti_width, + l.YM + dyy, + anchor="nw", font=font) + # the Talon is invisble + s.talon = InitialDealTalonStack(-l.XS, self.height - dyy, self) + + # Define stack groups + l.defaultStackGroups() + + + # + # game overrides + # + + def _shuffleHook(self, cards): + if not self.app.opt.mahjongg_create_solvable: + return cards + # try to create a solvable game + old_cards = cards[:] + rows = self.s.rows + + def is_blocked(s, new_cards): + # any of above blocks + for stack in s.blockmap.above: + if new_cards[stack.id] is None: + return True + # any of left blocks - but we can try right as well + for stack in s.blockmap.left: + if new_cards[stack.id] is None: + break + else: + return False + # any of right blocks + for stack in s.blockmap.right: + if new_cards[stack.id] is None: + return True + return False + + def create_solvable(cards, new_cards): + if not cards: + return new_cards + # select two matching cards + c1 = cards[0] + del cards[0] + c2 = None + for i in xrange(len(cards)): + if self.cardsMatch(c1, cards[i]): + c2 = cards[i] + del cards[i] + break + # + free_stacks = [] + for r in rows: + if new_cards[r.id] is None and not is_blocked(r, new_cards): + free_stacks.append(r) + if len(free_stacks) < 2: + return None + # + i = factorial(len(free_stacks))/2/factorial(len(free_stacks)-2) + old_pairs = [] + for _ in xrange(i): + nc = new_cards[:] + while True: + # create uniq pair + r1 = self.random.randrange(0, len(free_stacks)) + r2 = self.random.randrange(0, len(free_stacks)-1) + if r2 >= r1: + r2 += 1 + if (r1, r2) not in old_pairs and (r2, r1) not in old_pairs: + old_pairs.append((r1, r2)) + break + s1 = free_stacks[r1] + s2 = free_stacks[r2] + nc[s1.id] = c1 + nc[s2.id] = c2 + nc = create_solvable(cards[:], nc) + if nc: + return nc + + return None + + new_cards = create_solvable(cards, [None]*len(cards)) + if new_cards: + new_cards.reverse() + return new_cards + print 'oops! can\'t create a solvable game' + return old_cards + + + def startGame(self): + assert len(self.s.talon.cards) == self.NCARDS + #self.s.talon.dealRow(rows = self.s.rows, frames = 0) + n = 12 + self.s.talon.dealRow(rows = self.s.rows[:self.NCARDS-n], frames = 0) + self.startDealSample() + self.s.talon.dealRow(rows = self.s.rows[self.NCARDS-n:]) + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + return sum([len(f.cards) for f in self.s.foundations]) == self.NCARDS + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if stack1.basicIsBlocked() or stack2.basicIsBlocked(): + return 0 + return self.cardsMatch(card1, card2) + + def getAutoStacks(self, event=None): + return ((), (), ()) + + def updateText(self): + if self.preview > 1 or self.texts.info is None: + return + # find matching tiles + stacks = [] + for r in self.s.rows: + if r.cards and not r.basicIsBlocked(): + stacks.append(r) + f, i = 0, 0 + for r in stacks: + n = 0 + for t in stacks[i+1:]: + if self.cardsMatch(r.cards[0], t.cards[0]): + n += 1 + #if n == 3: n = 1 + #elif n == 2: n = 0 + n = n % 2 + f += n + i += 1 + + if f == 0: + f = self.text_free_matching_pairs_0 + elif f == 1: + f = self.text_free_matching_pairs_1 + else: + f = str(f) + self.text_free_matching_pairs_2 + t = sum([len(i.cards) for i in self.s.foundations]) + t = str(t) + self.text_tiles_removed \ + + str(self.NCARDS - t) + self.text_tiles_remaining \ + + f + self.texts.info.config(text = t) + + # + # Mahjongg special overrides + # + + def getHighlightPilesStacks(self): + # Mahjongg special: highlight all moveable tiles + return ((self.s.rows, 1),) + + def getCardFaceImage(self, deck, suit, rank): + if suit == 3: + cs = self.app.cardset + if len(cs.ranks) >= 12 and len(cs.suits) >= 4: + # make Mahjongg type games playable with other cardsets + if rank >= 8: # flower + suit = 1 + rank = len(cs.ranks) - 2 + elif rank >= 4: # season + rank = max(10, len(cs.ranks) - 3) + else: # wind + suit = rank + rank = len(cs.ranks) - 1 + return self.app.images.getFace(deck, suit, rank) + + def getCardBackImage(self, deck, suit, rank): + # We avoid screen updates caused by flipping cards - all + # cards are face up anyway. The Talon should be invisible + # or else the top tile of the Talon will be visible during + # game start. + return self.getCardFaceImage(deck, suit, rank) + + def _createCard(self, id, deck, suit, rank, x, y): + ##if deck >= 1 and suit == 3 and rank >= 4: + if deck%4 and suit == 3 and rank >= 4: + return None + return Game._createCard(self, id, deck, suit, rank, x, y) + + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + # Since we only compare distances, + # we don't bother to take the square root. + for stack in stacks: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + # Mahjongg special: if the stack is very close, do + # not consider blocked stacks + if dist > self.check_dist or not stack.basicIsBlocked(): + closest, cdist = stack, dist + return closest + + # + # Mahjongg extras + # + + def cardsMatch(self, card1, card2): + if card1.suit != card2.suit: + return 0 + if card1.suit == 3: + if card1.rank >= 8: + return card2.rank >= 8 + if card1.rank >= 4: + return 7 >= card2.rank >= 4 + return card1.rank == card2.rank + +## mahjongg util +def comp_cardset(ncards): + # calc decks, ranks & trumps + assert ncards % 4 == 0 + assert 0 < ncards <= 288 # ??? + decks = 1 + cards = ncards/4 + if ncards > 144: + assert ncards % 8 == 0 + decks = 2 + cards = cards/2 + ranks, trumps = divmod(cards, 3) + if ranks > 10: + trumps += (ranks-10)*3 + ranks = 10 + if trumps > 4: + trumps = 4+(trumps-4)*4 + assert 0 <= ranks <= 10 and 0 <= trumps <= 12 + return decks, ranks, trumps + +# /*********************************************************************** +# // register a Mahjongg type game +# ************************************************************************/ + +from new import classobj + +def r(id, short_name, name=None, ncards=144, layout=None): + assert layout + if not name: + name = "Mahjongg " + short_name + classname = re.sub('\W', '', name) + # create class + gameclass = classobj(classname, (AbstractMahjonggGame,), {}) + gameclass.L = layout + gameclass.NCARDS = ncards + decks, ranks, trumps = comp_cardset(ncards) + gi = GameInfo(id, gameclass, name, + GI.GT_MAHJONGG, 4*decks, 0, + category=GI.GC_MAHJONGG, short_name=short_name, + suits=range(3), ranks=range(ranks), trumps=range(trumps), + si={"decks": decks, "ncards": ncards}) + gi.ncards = ncards + gi.rules_filename = "mahjongg.html" + registerGame(gi) + return gi + diff --git a/pysollib/games/mahjongg/mahjongg1.py b/pysollib/games/mahjongg/mahjongg1.py new file mode 100644 index 0000000000..cc4c4eed0d --- /dev/null +++ b/pysollib/games/mahjongg/mahjongg1.py @@ -0,0 +1,149 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +from mahjongg import r + +# /*********************************************************************** +# // game definitions +# ************************************************************************/ + +r(5001, "Altar", layout="0aaaacaaiaakaamaaoaaqaasaauaawaaCaaEaaacaccaicckccmccoccqccsccucawcaCcaEcaieckecmecoecqecsecueaweaigckgcmgcogcqgcsgcugawgaiiakiamiaoiaqiasiauiawiaokaqkaamacmaomaqmaCmaEmaaoacoaooaqoaCoaEohabhcbhCbhEbkpijpkipmhanhcnhCnhEnhpoobboDbobnoDnvlcvncvpcvrcvtcvlevnevpevrevtevlgwngwpgwrgvtgCocCqcCmeCoeCqeCse") +r(5002, "Arena", layout="0eaadcaceabgaaiaaqabsacuadwaeyadaccccbecagcakcbmcaocascbuccwcdyccaebceaeeameauebwecyebagacgakgbmgaogawgbygcaibciaeiamiauibwicyidakcckbekagkakkbmkaokaskbukcwkdykeamdcmcembgmaimaqmbsmcumdwmeym") +r(5003, "Arena 2", layout="0daadcabeabgaaiaakaamaaoaaqaasabuabwadyadAadaccccbecagcaucbwccycdAcdaecceaeeawecyedAedagccgaegawgcygdAgdaicciaeiawicyidAidakcckbekagkaukbwkcykdAkdamdcmbembgmaimasmbumbwmdymdAm") +# +r(5004, "Arrow", layout="0aaaaqbaacaccascaqdaudaaeaceaeeageaieakeameaoeaseaweaqfaufayfaagacgaegaggaigakgamgaogasgawgaAgaCgaqhauhayhaaiaciaeiagiaiiakiamiaoiasiawiaqjaujaakackaskaqlaamhbchrdhbehdehfehhehjehlehnehpehtehrfhvfhbghdghfghhghjghlghnghpghtghxghrhhvhhbihdihfihhihjihlihnihpihtihrjhbkoceoeeogeoieokeomeooeoqeosfocgoegoggoigokgomgoogoqgougoshocioeiogioiiokiomiooioqivfevhevjevlevnevpevfgvhgvjgvlgvngvpgvrgvfivhivjivlivnivpiCkeCmeCoeCkgCmgCogCqgCkiCmiCoi") +r(5005, "Art Moderne", layout="0acaaeaagaaiaakaamaaoaauaawaaabalcapcatcavcaxcaadaddaleapeaseauebxeaafacfalgangapgargatgavgaxgaahachaliapiasiauibxiaajadjalkapkatkavkaxkaalacmaemagmaimakmammaomaumawmhdahfahhahjahlahnahvahxahuchwchychedhldhpdhaehtehvehdfhlfhpfhaghsghughwghdhhlhhphhaihtihvihejhljhpjhukhwkhykhdmhfmhhmhjmhlmhnmhvmhxmowaoyaovcoxcozcofdokdoueoweoyeoefokfomgotgovgoehokhouiowioyiofjokjovkoxkozkowmoymvgdvjdvffvjfvlgvfhvjhvgjvjjChdCgfCifCkgCghCihChj") +r(5006, "Balance", layout="0eoaeebbgbbibbkbbmbbqbbsbbubbwbeybeoccedcydcoeccfaefcgfcwfayfcAfcogachaghawhaAhcoiaajacjaejagjaijaujawjayjaAjaCjcokadlaflaxlazlcomagoaioakoamoaooaqoasoauoawohbjhdjhfjhhjhvjhxjhzjhBjjeljylhhoijojloknokpojroitohvoocjoejogjowjoyjoAjvdjvfjvxjvzjCejCyj") +r(5007, "Bat", layout="0ecaeAaaabalbanbapbarbaCbcccaecayccAcaadandapdaCdcceaeebgeaieauebweayecAeaafanfapfaCfbcgbegaggbigakgasgbugawgbygbAgaahamhbohaqhaChbcibeiagibiiakiasibuiawibyibAiaajamjbojaqjaCjcckaekbgkaikakkaskaukbwkaykcAkaalaolaClccmaemaimakmasmaumaymcAmaanaCnecobkobsoeAohobhodhofhaghCghaihCi") +# +r(5008, "Beatle", layout="0aeaagaauaawaaicakcamcaocaqcascaeeageaieakeameaoeaqeaseaueadgafgahgajgalgangapgargatgavgaeiagiaiiakiamiaoiaqiasiauiaikakkamkaokaqkaskaemagmaumawmhhbhtbhjchlchnchpchrchdehfehhehjehlehnehpehrehteiegiggiigikgimgiogiqgisghughdihfihhihjihlihnihpihrihtihjkhlkhnkhpkhrkhhlhtloceogeoieokeomeooeoqeoseociogioiiokiomiooioqiosivbdvhevjevlevnevpevrevfgvhgvjgvlgvngvpgvrgvhivjivlivnivpivrivbjCaaCacCggCigCkgCmgCogCqgCakCam") +r(5009, "Big Hole", layout="0daadcadeadgadiadkadmadoaaaccccdecdgcdicdkccmcaocaaeccedeedkecmeaoeaagccgdegdkgcmgaogaaiccideidgidiidkicmiaoidakdckdekdgkdikdkkdmkdok") +r(5010, "Bizarre", layout="0aaaaGadkbdmbdobdqbdsbdubdwbdkdcmdcodcqdcsdcuddwddkfcmfbofbqfbsfcufdwfdkhcmhbohaqhbshcuhdwhakjbmjcojdqjcsjbujawjaklbmlcolcqlcslbulawlaknbmnbonbqnbsnbunawnakpampaopaqpaspaupawpaaqaGq") +r(5011, "Boat", layout="0alaapaataajcblcapcbtcavcahebjecleapectebveaxeafgbhgcjgdlgapgdtgcvgbxgazgadibfichidjieliapietidvicxibziaBiapkaambcmbembgmbimbkmbmmbombqmbsmbumbwmbymbAmbCmaEmadobfobhobjoblobnobpobrobtobvobxobzoaBoaiqbkqbmqboqbqqbsqbuqawq") +r(5012, "Bug", layout="0bhabnabtaajbapbavbcadaidakdamdaodaqdasdaudawdaceayeagfbifbkfbmfbofbqfbsfbufbwfaAfdegaygbchbghcihckhcmhcohcqhcshcuhbwhaAhdeiayiagjbijbkjbmjbojbqjbsjbujbwjaAjackaykcalailaklamlaolaqlaslaulawlajnapnavnbhobnobtohyhojfolfonfopforfotfovfojjoljonjopjorjotjovjvjhvlhvnhvphvrhvthCkhCmhCohCqhCsh") +r(5013, "Butterfly", layout="0dmadqaaabaebaybaCbagccocawcaadaedaidaudaydaCdaceageakedoeaseaweaAeaafaefbifamfaqfbufayfaCfacgaggbkgeogbsgawgaAgaahaehbihbmhbqhbuhayhaChaciagibkieoibsiawiaAiaajaejbijamjaqjbujayjaCjackagkakkeokaskawkaAkaalaelailaulaylaClacmagmeomawmaAmaanaenaynaCncoohgdhwdheehyehcfhgfhwfhAfhaghCghaihCihcjhgjhwjhAjhekhykhglhwl") +# +r(5014, "Castle", layout="0eaaccaceacgaciackaemacacaccaecagcaicakccmcdaeaceaeeageaieakedmeaoecagacgaegaggaigakgcmgbogaqgdaiaciaeiagiaiiakidmiaoicakackaekagkaikakkcmkeamccmcemcgmcimckmemmhddhfdhhdhjdhdfhffhhfhjfhdhhfhhhhhjhhdjhfjhhjhjjoeeogeoieoegoggoigoeiogioiivffvhfvfhvhhCgg") +r(5015, "Cat and Mouse", layout="0cfabhacjablacnabpacrabtacBacFabdbbvbbbcbxcbBccDcbFcahdajdaldbzdbaecBebDecFeahfajfalfbagahhajhalhbuhbBhbbibsibwibFibqjbBjbckbokbxkbFkcelbglcilbklcmlbsmbwmbunbAocCocEocGohiehkehighkgohdojdoldohfojfolfohhojholhoBkoFloAnvievkevigvkgvBlvFmCjdChfClfCjh") +r(5016, "Ceremonial", layout="0bcabeaajaalaanaapaaraataavabAabCabdcbfcbzcbBcaadapdaEdbeebgeanearebyebAeaafbifbkfapfbufbwfaEfbmgbsgaahaphaEhbmibsiaajbijbkjapjbujbwjaEjbekbgkankarkbykbAkaalaplaElbdmbfmbzmbBmbcobeoajoaloanoapoaroatoavobAobCohkahmahoahqahsahuahaehoehqehEehagipghEghaiipihEihakhokhqkhEkhkohmohoohqohsohuoonaopaoraopeoahoEhopkonoopoorovph") +r(5017, "Checkered", layout="0baabCaacbbebagbbibakbbmbaobbqbasbbubawbbybaAbbcdaedbgdaidbkdamdbodaqdbsdaudbwdaydbAdacfbefagfbifakfbmfaofbqfasfbufawfbyfaAfbchaehbghaihbkhamhbohaqhbshauhbwhayhbAhacjbejagjbijakjbmjaojbqjasjbujawjbyjaAjbclaelbglailbklamlbolaqlbslaulbwlaylbAlacnbenagnbinaknbmnaonbqnasnbunawnbynaAnbaobCo") +# +r(5018, "Chip", layout="0aeaaiaamaaqaatabecbgcbicbkcbmcbocbqcbscbucbwcaadbcdbydaAdbeecgecieckecmecoecqecsecuebweaagbcgbegdggbigakgamgaogaqgbsgdugbwgbygaAgbeicgiciickicmicoicqicsicuibwiaajbcjbyjaAjbekbgkbikbkkbmkbokbqkbskbukbwkaemaimammaqmaum") +r(5019, "Columns", layout="0egaaiaakaamaaoaaqaasaauaewaaebaybagcaicaocaucawceadbcdaedaydbAdeCdageekeameaoeaqeeseawebafbCfaggakgasgawgaahamheohaqhaChagiakiasiawibajbCjagkdkkamkaokaqkeskawkealbclaelaylbAleClagmaimaomaumawmaenaynegoaioakoamoaooaqoasoauoewohgfhwfjghjwhhgjhwj") +r(5020, "Crown", layout="0baabcabeabgabkabmaboabqabsabwabyabAabCabacaccaecbgcbkcamcbocaqcbscbwcaycaAcbCcbaeaeebgebkeameaqebsebweayebCebagaegbggbigbkgamgaqgbsgbugbwgaygbCgbaiaeiagiaiiakiamiaqiasiauiawiayibCibakbCkbamacmaemagmaimakmbomasmaumawmaymaAmbCmbaobcobeobgobiobkoamobooaqobsobuobwobyobAobCo") +# +r(5021, "Cupola", layout="0aiaakaamaaoaaqaasaagbaubaecawcacdaydabfeofazfaahajhalhanhapharhathaAhaajeojaAjablazlacnaynaeoawoagpaupaiqakqamqaoqaqqasqhjbhlbhnbhpbhrbhhchtchfdhvdhdehxehcghyghkhhmhhohhqhhshhbihzihckhykhdmhxmhfnhvnhhohtohjphlphnphpphrpokcomcoocoqcoidosdogeoueoefowfodholhonhophorhoxhodjoxjoelowlogmoumoinosnokoomoooooqovldvndvpdvjevrevhfvtfvfgvvgvmhvohvqhveivwivfkvvkvhlvtlvjmvrmvlnvnnvpnCifCsfCggCugCnhCphCfiCviCgkCukCilCsl") +r(5022, "Deep Well", layout="0acaaeaagaaiaakaamaaaccccceccgccicckccmcaocaaecceeeeegeeieekecmeaoeaagccgeegekgcmgaogaaiccieeiekicmiaoiaakcckeekegkeikekkcmkaokaamccmcemcgmcimckmcmmaomacoaeoagoaioakoamo") +r(5023, "Dragon", layout="0bgaaiaegceicdkccmcbocbqcbscbucawcaycaceaeeageaieakebmeboeaqeaseaueaweayeadgbfgahgajgalgangapgaEgayhaChaaiaciaeiagiaiiakiamiaoiaqiasiauiaAiaEiaCjabkadkafkahkajkalkaEkaamacmaemagmbimakmaaoacobeoagoaiockoamoixchdejhejigkkgjmghEhhbihdikhikjijliiniipihrihtihCihEjhckhgkhkkhbmhfmhboihopneocioEiobn") +r(5024, "Dude", layout="0bfabtabhbbjbblbbrbaBbatcavcaxcazcaedagdbldbndbpdbrdacebjebueayeaAeaCeaafbhfcmfcofcqfcsfbwfaEfbfgckgcugbygcphbAhbeickicuicpjbBjcjkclkcnkcrkctkcvkcjmclmcnmcrmctmcvmcpncjocvockqcmqcoqcqqcsqcuq") +#r(5025, "Eagle", layout="0cmadoacqaasbbmcbocaedagdaudawdbcebieakebmeboeaqebsebyeaefagfaufawfbcgbigakgbmgbogaqgbsgbygaehaghauhawhaaiacibmiboiayiaAibejbwjaakackbmkbokaykaAkaambkmanmbqmaAmcioclocpocsoheehgehuehweheghgghughwghbihzihbkhzkomdoododeofeoheoteoveoxeomfoofodgofgohgotgovgoxgomhoohobjomjoojozjvndveevgevuevwevnfvegvggvugvwgvnhvnjCfeCveCfgCvg") +r(5026, "Enterprise", layout="0agaaiaakaamaaoaaqaasaauaawaayaaacbccbecbgcbicbkcbmcbocbqcbscbucbwcbycbAcbCcaEcdqedogdmhaAiaajbcjcejdgjeijekjemjeojcqjayjaCjaAkhhaijailainaipairaitaivahxaiAjodcofcohcojcolconcopcorcotcovcoxcozcoBcvkavmavoavqavsavecvgcvicvkcvmcvocvqcvscvucvwcvycChcCjcClcCncCpcCrcCtcCvc") +# +r(5027, "Eye", layout="0amaaoaakbaqbaicamcaocascagdakdaqdaudaeeaieameaoeaseaweacfagfakfaqfaufayfaagaegaigamgaogasgawgaAgachaghakhaqhauhayhaeiaiiamiaoiasiawiagjakjaqjaujaikamkaokaskaklaqlammaomhlbhobhjchqchhdhldhodhsdhfehjehqehuehdfhhfhlfhofhsfhwfhfghjghqghughdhhhhhlhhohhshhwhhfihjihqihuihhjhljhojhsjhjkhqkhllhololcoocojdoqdoheoleooeoseoffojfoqfoufohgolgoogosgowgofhojhoqhouhohioliooiosiojjoqjolkookvldvodvjevqevhfvlfvofvsfvfgvjgvqgvhhvlhvohvshvjivqivljvoj") +r(5028, "F-15 Eagle", layout="0aobaqbasbaubbEcbGcandapdardatdalebDebFeajfanfapfarfalgatgavgaxgazgaBgaDgabhadhafhahhajhanhapharhaliatiaviaxiaziaBiaDiajjanjapjarjalkbDkbFkanlaplarlatlbEmbGmaonaqnasnaunhpahrahtahvahochqchschuchmehoehqehsehifhkfhmghoghqghsghughwghyghAghCgiahichjehjghjihjkhhmihoihqihsihuihwihyihAihCihijhkjhmkhokhqkhskhomhqmhsmhumhpohrohtohvoozfoBfoDfomhozjoBjoDjvAfvCfvAjvCjCBfCDfCfhChhCjhCBjCDj") +r(5029, "Farandole", layout="0beabgabmaboabqabwabyabcbbibbkbbsbbubbAbafcaxcbbdbBdckecmecqecsebbfbgfcifcufbwfbBfbegbygbahbchajhblhcnhcphbrhathbAhbChbeibyibbjbgjcijcujbwjbBjckkcmkcqkcskbblbBlafmaxmbcnbinbknbsnbunbAnbeobgobmoboobqobwobyo") +r(5030, "Fish", layout="0afaajaasaauaawabhbaobaqbaybaccamcbscbucbwcaAcakdbodbqdaydaCdaceaeeaiebmebsebuebweaEeagfbkfbofbqfayfaCfacgaegaigbmgbsgbugbwgaAgaEgakhbohbqhbyhaChaciamibsibuibwiaAiaojaqjayjahkaskaukawkbjlcemalmbcndgnbCnaaoeioaqoasodAoaEodkpbopbupdypcmqcwqhcdhcfhefhifhchoreoteolfonfopfovforgotgovhoxh") +# +r(5031, "Five Pyramids", layout="0aaaacaaeaagaayaaAaaCaaEaaacaccaecagcapcaycaAcaCcaEcaaeaceaeeageapeayeaAeaCeaEeaagacgaegaggangapgargaygaAgaCgaEgalhathaniapiariaakackaekagkapkaykaAkaCkaEkaamacmaemagmapmaymaAmaCmaEmaaoacoaeoagoayoaAoaCoaEoaaqacqaeqagqayqaAqaCqaEqhbbhdbhfbhzbhBbhDbhbdhddhfdhpdhzdhBdhDdhbfhdfhffipfhzfhBfhDfhnhhphhrhipjhblhdlhflhplhzlhBlhDlhbnhdnhfnhznhBnhDnhbphdphfphzphBphDpoccoecoAcoCcoceoeeoAeoCeoohoqhocmoemoAmoCmocooeooAooCovddvBdvphvdnvBn") +#r(5032, "Five Pyramids 2", layout="0aoaaabacbaebagbawbaybaAbaCbbocaadacdaedagdaidakdamdaqdasdaudawdaydaAdaCdcoeaafacfaefagfawfayfaAfaCfamgdogaqgadhazhagibiickidmidoidqicsibuiawiadjazjamkdokaqkaalaclaelaglawlaylaAlaClcomaanacnaenagnainaknamnaqnasnaunawnaynaAnaCnbooaapacpaepagpawpaypaApaCpaoqhbchdchfchxchzchBchbehdehfehxehzehBehbmhdmhfmhxmhzmhBmhbohdohfohxohzohBoocdoedoydoAdocnoenoynoAn") +r(5033, "Flowers", layout="0baaccaceabgaakabmaboaaqaauabwabyaaAadacdgcckccqccuccAcbaecceceebgeakebmeboeaqeauebwebyeaAeadgangaxgafhahhajhalhapharhathavhadianiaxiaakbckbekagkakkbmkbokaqkaukbwkbykaAkcamcgmckmcqmcumcAmaaobcobeoagoakobmobooaqoauobwobyoaAoonaoxaoneoxeodkonkoxkodoonooxovdavde") +r(5034, "Flying Dragon", layout="0acaaeaagaaiaakaamaaoaaqaasaauaawaayaagcbicbkcbmcbocbqcbscaucaeeagebieckecmecoecqebseaueaweacgaegaggbigckgdmgdogcqgbsgaugawgaygaahaAhaChaciaeiagibiickidmidoicqibsiauiawiayiaekagkbikckkcmkcokcqkbskaukawkagmbimbkmbmmbombqmbsmaumacoaeoagoaioakoamoaooaqoasoauoawoayoCnh") +r(5035, "Fortress Towers", layout="0faaecadeacgabiabkacmadoaeqafsaeacaccagcaicakcamcaqcescdaeaceageaieakeameaqedsedagacgaggaigakgamgaqgdsgeaiaciagiaiiakiamiaqiesifakeckdekcgkbikbkkcmkdokeqkfskhjchjehjghji") +# +r(5036, "Full Vision", layout="0aaaaiaamaaoaaqaasaawaaEaacbaebagbaybaAbaCbaacaicamcaocaqcascawcaEcacdaedagdaydaAdaCdaaeaieaweaEeaefamfasfaAfaggaigakgaugawgaygaehamhashaAhagiaiiakiauiawiayiaejamjasjaAjaakaikawkaEkaclaelaglaylaAlaClaamaimaomaqmawmaEmacnaenagnamnasnaynaAnaCnaaoaioaooaqoawoaEohpahbbhhbhnbhrbhxbhDbhdchfchpchzchBchbdhhdhxdhDdhfghlghtghzghhhhjhhvhhxhhfihlihtihzihblhhlhxlhDlhdmhfmhzmhBmhbnhhnhnnhpnhrnhxnhDnooboqboccogcoycoCcoghokhouhoyhocmogmoymoCmvpb") +# +r(5037, "Full Vision 2", layout="0aaaacaafaahaakaamaapaaraauaawaazaaBaaacaccafcahcakcamcapcarcaucawcazcaBcaaeaceafeaheakeameapeareaueaweazeaBeaagacgafgahgakgamgapgargaugawgazgaBgaajacjaejagjakjamjapjarjavjaxjazjaBjaalaclaelaglaklamlaplarlavlaxlazlaBlaeoagoaioakoamoapoaroatoavoaxohbbhgbhlbhqbhvbhAbhadhcdhfdhhdhkdhmdhpdhrdhudhwdhzdhBdhbfhgfhlfhqfhvfhAfhdjhyjhbkhfkhkkhmkhpkhrkhwkhAkhdlhylobcogcolcoqcovcoAcobeogeoleoqeoveoAeockoekolkoqkoxkozkvbdvgdvldvqdvvdvAdvdkvyk") +r(5038, "Future", layout="0cgaaiaakaamaboaaqaasaauacwaagccicakcamcbocaqcasccucawcaeeageaiebkebmeboebqebseaueaweayeacfaAfaagcegcggdigdkgdmgdogdqgdsgdugcwgcygaCgachaAhaeiagiaiibkibmiboibqibsiauiawiayiagkcikakkamkbokaqkaskcukawkcgmaimakmammbomaqmasmaumcwmhcghAgoneopeoniopiClgCngCpgCrg") +r(5039, "Garden", layout="0adaafaaoaaqaazaaBaaabaibalbatbawbaEbaccaecagcancapcarcaycaAcaCcaadaidaldatdawdaEdaceaeeageaneapeareayeaAeaCeaafaifalfatfawfaEfachaehaghanhapharhayhaAhaChaajaijaljatjawjaEjackaekagkankapkarkaykaAkaCkaalailallatlawlaElacmaemagmanmapmarmaymaAmaCmaanainalnatnawnaEnadoafoaooaqoazoaBoheahpahAahcdhedhgdhndhpdhrdhydhAdhCdhdhhfhhohhqhhzhhBhhclhelhglhnlhplhrlhylhAlhClheohpohAooddofdoodoqdozdoBdoehophoAhodloflooloqlozloBlvedvpdvAdvelvplvAl") +r(5040, "Gayle's", layout="0dcaceabgaaiaakaamaaoaaqaasabuacwadyaagcbicckccmccoccqcbscaucakebmeboeaqeacgaegaggbigbkgbmgbogbqgbsgaugawgaygaahaAhaciaeiagibiibkibmiboibqibsiauiawiayiakkbmkbokaqkagmbimckmcmmcomcqmbsmaumdcoceobgoaioakoamoaooaqoasobuocwodyoojholhonhophorhvncvmhvohvnmCnh") +r(5041, "Glade", layout="0aaaacaaCaaEaaacaccaCcaEcahdejdcldcndbpdcrdctdevdaxddhfcjfblfbnfbpfbrfbtfcvfdxfchhbjhblhanharhbthbvhcxhdhjcjjbljbnjbpjbrjbtjcvjdxjahlejlcllcnlbplcrlctlevlaxlaamacmaCmaEmaaoacoaCoaEohbahDahbchDchbmhDmhbohDoobboDbobnoDn") +r(5042, "H for Haga", layout="0aaaacaaeaagaakaamaaoaaqaaacaccaecagcakcamcaocaqcaaeaceaeeageakeameaoeaqeaifaagacgaegaggakgamgaogaqgaihaaiaciaeiagiakiamiaoiaqiaijaakackaekagkakkamkaokaqkaamacmaemagmakmammaomaqmaaoacoaeoagoakoamoaooaqohbbhdbhfbhlbhnbhpbhbdhddhfdhldhndhpdhbfhdfhffhlfhnfhpfhhghjghbhhdhhfhhlhhnhhphhhihjihbjhdjhfjhljhnjhpjhblhdlhflhllhnlhplhbnhdnhfnhlnhnnhpnoccoecomcoococeoeeomeooeocgoegomgoogoghoihokhocioeiomiooiockoekomkookocmoemommoomvddvndvdlvnl") +#r(5043, "H for Haga Traditional", layout="0acaaeaagaaiaakaamaaoaaqaasaauaawaayabgcbicakcamcaocaqcbscbucaeebgebieakeameaoeaqebsebueaweacgaegbggbigbkgbmgbogbqgbsgbugawgaygaahaAhaciaeibgibiibkibmiboibqibsibuiawiayiaekbgkbikakkamkaokaqkbskbukawkbgmbimakmammaomaqmbsmbumacoaeoagoaioakoamoaooaqoasoauoawoayoklcknckpchdhhxhklmknmkpm") +r(5044, "Helios", layout="0eaadcaduaewadacbccbucdwcbaeaceaeeaiedkedmeaoeaseauebwebagacgaegaggdigdogaqgasgaugbwgblhbaiaciaeiagidiidoiaqiasiauibwibakackaekaikdkkdmkaokaskaukbwkdambcmbumdwmeaodcoduoewohchhehhghhqhhshhuhCleCihCohClk") +r(5045, "High and Low", layout="0eaadcaceabgaaiabkacmadoaeqadaccccdecagcbicckcbmceocdqccaebceeeebgeciedkeamedoecqebagacgdegcggdigekgbmgcogbqgaaibciceidgieiidkicmiboiaqiaekagkbikakkamkahmajmhim") +# +#r(5046, "Hourglass", layout="0aaaacaaeaagaaiaakaamaaoaaqaasaauaawaayaaacamcaycacdawdaaeaeeameaueayeacfagfasfawfaagaegaigamgaqgaugaygachaghbkhbohashawhaaiaeiaiiamiaqiauiayiacjagjasjawjaakaekamkaukaykaclawlaamammaymaaoacoaeoagoaioakoamoaooaqoasoauoawoayohabhmbhybhadhmdhydhcehwehafhefhufhyfhcghgghsghwghahhehhihhqhhuhhyhhcihgihsihwihajhejhujhyjhckhwkhalhmlhylhanhmnhynoacoycoaeoyeoagoygoaioyioakoykoamoymvadvydvafvyfvahvyhvajvyjvalvylCaeCyeCagCygCaiCyiCakCyk") +r(5047, "Inca", layout="0aoaaqaaibakbambasbaubawbbocbqcaidbkdbmdbsdbudawdcoecqeaifbkfcmfcsfbufawfaagacgdogdqgaCgaEgahhbjhclhcthbvhaxhaaiacidoidqiaCiaEiahjbjjcljctjbvjaxjaakackdokdqkaCkaEkailbklcmlcslbulawlcomcqmainbknbmnbsnbunawnboobqoaipakpampaspaupawpaoqaqqhbihDiCphCpj") +r(5048, "Inner Circle", layout="0aaaacaayaaAaaaceccceccgcbicbkcamcaocbqcbsccuccwceycaAcccecyedgfcifbkfbqfcsfdufbcgbygaghbuhbcibyiegjdijckjbmjbojcqjdsjeujcckcykaamecmcemcgmbimbkmbqmbsmcumcwmdymaAmaaoacoakoaqoayoaAo") +r(5049, "Joker", layout="0aaaaAaadbafbahbajbalbanbapbarbatbavbaxbabdbddbfdbhdbjdbldbndbpdbrdbtdbvdbxdazdcbfbdfaffahfajfalfanfapfarfatfavfbxfczfcbhbdhafhavhbxhczhajiamiapiasicbjbdjafjavjbxjczjcblbdlaflahlajlallanlaplarlatlavlbxlczlabnbdnbfnbhnbjnblnbnnbpnbrnbtnbvnbxnaznadpafpahpajpalpanpapparpatpavpaxpaaqaAqhgghughgkhuk") +r(5050, "K for Kyodai", layout="0caaccaceacmacoacqacacbcccecckcbmccoccaebceceeciebkecmecagbcgcegcggbigckgcaibcibeibgiciicakbckcekcgkbikckkcambcmcemcimbkmcmmcaobcoceockobmocoocaqccqceqcmqcoqcqq") +#r(5051, "K for Kyodai Traditional", layout="0acaaeaagaaiaakaamaaoaaqaasaauaawaayaagcaicakcamcaocaqcascaucaeeageaieakeameaoeaqeaseaueaweacgaegaggaigakgamgaogaqgasgaugawgaygaahaAhaciaeiagiaiiakiamiaoiaqiasiauiawiayiaekagkaikakkamkaokaqkaskaukawkagmaimakmammaomaqmasmaumacoaeoagoaioakoamoaooaqoasoauoawoayokjckrckpdkjehgfknfhufkjghghklhhuhkjihgjknjhujkjkkplkjmkrm") +r(5052, "Km", layout="0baabcabiaakaboacqacyabAabacaccbgcaicbocaqcdscdwcaycbAcdudbaeacebeeageboeaqeaseaweayebAeeufbagacgbegaggbogaqgaygbAgduhbaiacibgiaiiboiaqiayibAibakbckbikakkbokbqkbykbAkjcfhgfoabooboAboadoodoAdoafoefoofoAfoahoohoAhoajoojoAjvacvocvAcvaevoevAevagvogvAgvaivoivAiCadCodCAdCafCofCAfCahCohCAh") +r(5053, "Kujaku", layout="0bnabpabrabtabvabxablbczbaBbbhcbjcancapcarcatcavcaxcaddbfdaldazddBdaDdaheajeabfcdfaffaAfdCfaEfbahcchaehakhamhaohashaAhdChaEhabjcdjafjaAjdCjaEjahkajkadlbflallazldBlaDlbhmbjmanmapmarmatmavmaxmblncznaBnbnobpobrobtobvobxohnghpghtghjhhnihpihtioofoqfoufoihoojoqjoujvpevrevvfvhhvvjvpkvrkCwgCCgCghCwiCCi") +r(5054, "Labyrinth", layout="0caaacaaeaagaaiaakaamaaoaaqaasaauaawaayaaAaaCacEaaacbkcbocbucaEcaaebcebeebgebkeboebsebuebyebAeaEeaagbkgbygaEgaaibeibiibkiboibqibsibuibwibyibAiaEiaakbekbokbwkaEkaambembgmbimbkmbombqmbsmbwmbAmbCmaEmaaobkobwoaEocaqacqbeqdgqdkqbmqaoqaqqasqauqbwqdyqdCqbEq") +r(5055, "Lion", layout="0bdbbfbcjbclbawbaybbbcbhcaucaAccjdcldasdaCdbaeaqecvfczfaDfbbgapgaEhcbiceichickiaoicxiaFjcckcfkcikclkbokcwkcykbulbAlaElcbmcemchmckmcnmbqmaDnccocfocioclocooaroatoavoaxoazoaBohvahxahzahtbhBbhrchDdhpehEfhoghFhhnihGjhFlhEnhsohCohuphwphyphApwkc") +r(5056, "Lost ", layout="0afaaxaabbadbahbajbblbbnbbpbbrbatbavbazbaBbafcaxcabdaddbkdcodbsdazdaBdbiebmebqebueaafacfaefbgfdofbwfayfaAfaCfaahaehbghcihckhdmhdohdqhcshcuhbwhayhaChaajacjaejbgjdojbwjayjaAjaCjbikbmkbqkbukabladlbklcolbslazlaBlafmaxmabnadnahnajnblnbnnbpnbrnatnavnaznaBnafoaxoombooboqbomnoonoqn") +r(5057, "Maya", layout="0aaaacaaeaagaaiaaqaasaauaawaayaaacaccaecagcaicaqcascaucawcaycaaeaceaeeageaieakeameaoeaqeaseaueaweayeaigakgamgaogaqgaiiakiamiaoiaqiaakackaekagkaikakkamkaokaqkaskaukawkaykaamacmaemagmaimaqmasmaumawmaymaaoacoaeoagoaioaqoasoauoawoayohcbhebhgbhsbhubhwbhcdhedhgdhsdhudhwdhkfhmfhofhkhhmhhohhkjhmjhojhclhelhglhslhulhwlhcnhenhgnhsnhunhwnoccoecogcoscoucowcolfonfolhonholjonjocmoemogmosmoumowmvdcvfcvtcvvcvmfvmhvmjvdmvfmvtmvvmCecCucCmgCmiCemCum") +r(5058, "Mesh", layout="0baabcabeabiabkabmabqabsabuabyabAabCabacbecbicbmcbqcbucbycbCcbaebcebeeagebiebkebmeaoebqebsebueawebyebAebCeaegbigbmgbqgbugaygbaibcibeiagibiibkibmiaoibqibsibuiawibyibAibCibakbekbikbmkbqkbukbykbCkbambcmbembimbkmbmmbqmbsmbumbymbAmbCm") +r(5059, "Moth", layout="0baaccaceabgaanaapaarabyacAacCabEaaibawbbccagcakccpcaucaycbCcaidamdasdawdadeakeboebqeaueaBeamfasfacgaegahgajgbogbqgavgaxgaAgaCgamhashadiakiboibqiauiaBiaijamjasjawjbckagkakkcpkaukaykbCkailawlccmcembgmbpmbymcAmcCmbanbEnhoahqahichwchmehsehdghighwghBghmihsihikhwkopaoneopeoreppgoniopiorivdavBavoevqevoivqivdmvBmCpeCpi") +r(5060, "N for Namida", layout="0caaccaceacgacqacsacuacacbccbecbgcbiccqcbsccuccaebcebeebgebiebkecqebsecuecagbcgcegbigbkgbmgcqgbsgcugcaibciceibkibmiboibqibsicuicakbckcekbmkbokbqkbskcukcamccmcemcomcqmcsmcum") +#r(5061, "N for Namida Traditional", layout="0acaaeaagaaiaakaamaaoaaqaasaauaawaayacgcaicakcbmccoccqcasccucaeecgecieakeameaoeaqeasecueaweacgaegcggcigakgcmgaogaqgasgcugawgaygaahaAhaciaeicgiaiiakicmiaoicqiasicuiawiayiaekcgkaikakkamkaokcqkaskcukawkcgmaimckmcmmbomaqmasmcumacoaeoagoaioakoamoaooaqoasoauoawoayoikfikhiohiojisjisl") +#r(5062, "Naoki Haga Traditional", layout="0acaaeaagaaiaakaamaaoaaqaasaauaawaayadgcaicakcdmcaocaqcascaucaeedgeaiedkedmeaoecqecseaueaweacgaegdggaigakgdmgaogaqgasgaugawgaygaahaAhaciaeiagiaiiakiamidoiaqiasiduiawiayiaekagkcikckkamkdokdqkdskdukawkagmaimakmammdomaqmasmdumacoaeoagoaioakoamoaooaqoasoauoawoayojidvrevjk") +# +r(5063, "New Layout", layout="0aeaagaaiaakabpaauaawaayaaAaaccaCcahdajdavdaxdaaeacealeateaCeaEeanfarfaagacgahgapgaCgaEganharhaaiacialiatiaCiaEiahjajjavjaxjackaCkaemagmaimakmbpmaumawmaymaAmhfahhahjahvahxahzahcdhidhwdhCdhkehuehafhcfhmfhofhqfhsfhCfhEfhahhchhmhhohhqhhshhChhEhhkihuihcjhijhwjhCjhfmhhmhjmhvmhxmhzmogaoiaowaoyaoceojeoveoCeolfotfoagocgongopgorgoCgoEgolhothociojiovioCiogmoimowmoymvhavxavcfvkfvufvCfvmgvogvqgvsgvchvkhvuhvChvhmvxmCcgClgCngCpgCrgCtgCCg") +r(5064, "Order", layout="0afaahaajaalaanaapaaraataaabaybaicakcamcaocaqcbadacdaedaudawdbydakebmeaoecafbcfaefaufbwfcyfaggaigakgbmgaogaqgasgcahcchbehbuhcwhcyhagiaiiakibmiaoiaqiasicajbcjaejaujbwjcyjakkbmkaokbalaclaelaulawlbylaimakmammaomaqmaanaynafoahoajoaloanoapoaroatohgahiaikaimaioahqahsahlchnchghhihhkhhohhqhhshhlmhnmhgohioikoimoioohqohsoomcpmhomm") +r(5065, "Pattern", layout="0aaaacaafaahaakaamaapaaraauabwabzaaBaaacaccafcahcakcamcapcarcbuccwcczcbBccafacfaffchfckfcmfapfarfcufawfazfcBfaahcchcfhahhakhamhcphcrhcuhawhazhcBhaakackafkahkakkcmkcpkarkcukcwkczkcBkaamacmafmahmckmammapmcrmaumcwmczmaBmibailaifbihbibciqciqfilhialihl") +#r(5066, "Phoenix", layout="0aaaacaapaaraaEaaGaaebatbaCbaacagcapcarcaAcaGcaidaydakeboebqebseaweaafacfaefamfaufaCfaEfaGfaggbpgbrgaAgaahaihamhauhayhaGhaeiakicpicriawiaCiaajamjaujaGjbpkbrkaclaelaglailamlaulaylaAlaClaElakmbpmbrmawmacnafnamnaunaBnaEnaioaooasoayoacpafpaBpaEpakqawqhbbhFbhdchDchfdhBdhhehzehjfhxfhdghlghvghDghfhhBhhhihnihtihzihjjhxjhdkhlkhvkhDkhflhnlhtlhBlhhmhzmhjnhxnhlohvohnphtponfppfprfotfoplorlvqivqlCqf") +r(5067, "Portal", layout="0accagcawcaAcaedaydaceageaweaAeamgaqgamiaqiackagkawkaAkaelaylacmagmawmaAmhbbhdbhfbhhbhvbhxbhzbhBbhbdhhdhvdhBdhbfhdfhffhhfhlfhnfhpfhrfhvfhxfhzfhBfhlhhrhhbjhdjhfjhhjhljhnjhpjhrjhvjhxjhzjhBjhblhhlhvlhBlhbnhdnhfnhhnhvnhxnhznhBnoaaocaoeaogaoiaouaowaoyaoAaoCaoacoicoucoCcoaeoieokeomeooeoqeoseoueoCeoagocgoegoggoigokgosgougowgoygoAgoCgoaiocioeiogioiiokiosiouiowioyioAioCioakoikokkomkookoqkoskoukoCkoamoimoumoCmoaoocooeoogooioouoowooyooAooCo") +r(5068, "Rocket", layout="0amaaoaaqaazaaBaaDaakbaicamcaocaqcascaxcazcaBcaDcagdakdaudaeeaieameaqeaseaweayeacfagfakfaofaufaBfaegasgawgaygaahbchbghbihbkhbmhbohcqhauhaAhaChaeiasiawiayiacjagjakjaojaujaBjaekaikamkaqkaskawkaykaglaklaulaimammaomaqmasmaxmazmaBmaDmaknamoaooaqoazoaBoaDohnahpahlbhBbhjchnchpchhdhsdhfehxehdfhsfhughehhshiwhhyhhuihdjhsjhfkhxkhhlhslhjmhnmhpmhlnhBnhnohpoonbopbosgodhofhohhojholhonhouhosionnopnvobvehvghvihvshvonCfh") +r(5069, "Scorpion", layout="0avaacbaebagbaibaacaxcazcagdaidakdaoeaseayeaAeaafacfaefagfaifakfcmgaogcqgasgcugawgbygbAgckhciidmiaoicqiasiduiawibyibAickjcmkaokcqkaskcukawkbykaalaclaelaglailaklaomasmawmagnainaknaaoacpaepagpaiphdbhfbhhbhwbhbchychhdhzehbfhdfhffhhfhjfhofhsfhohhshhwhhojhsjhwjhblhdlhflhhlhjlholhslhwlhhnhbohdphfphhpoogosgoyhooiosiowioyjookoskvohvqhvshvojvqjvsj") +r(5070, "Screw Up", layout="0ciackacmabgbbobcecbicbkcbmccqcbgdbodcceceeakeamecqecsebgfbofccgcegakgamgcqgcsgbghbohcciceiaiiakicqicsibgjbojcckcekaikakkcqkcskbglbolcembimbkmbmmcqmbgnbonciockocmoilfikhijjvbfvtfvbhvthvbjvtjCafCufCahCuhCajCuj") +# +r(5071, "Seven", layout="0aaaacaafaahaakaamaapaaraauaawaazaaBaaEaaGaaacaccafcahcakcamcapcarcaucawcazcaBcaEcaGcaaeaceafeaheakeameapeareaueaweazeaBeaEeaGeaagacgafgahgakgamgapgargaugawgazgaBgaEgaGgaaiaciafiahiakiamiapiariauiawiaziaBiaEiaGiaakackafkahkakkamkapkarkaukawkazkaBkaEkaGkaamacmafmahmakmammapmarmaumawmazmaBmaEmaGmaaoacoafoahoakoamoapoaroauoawoazoaBoaEoaGoaaqacqafqahqakqamqapqarqauqawqazqaBqaEqaGqhqchlehvehggiqghAghbihlihvihFihgkiqkhAkhlmhvmhqo") +# +r(5072, "Seven Pyramids", layout="0aaaacaaeaagaaoaaqaayaaAaaCaaEaaacaccaecagcaocaqcaycaAcaCcaEcaaeaceaeeageayeaAeaCeaEeaagacgaegaggangapgargaygaAgaCgaEganiapiariaakackaekagkankapkarkaykaAkaCkaEkaamacmaemagmaymaAmaCmaEmaaoacoaeoagoaooaqoayoaAoaCoaEoaaqacqaeqagqaoqaqqayqaAqaCqaEqhbbhdbhfbhpbhzbhBbhDbhbdhddhfdhzdhBdhDdhbfhdfhffhzfhBfhDfhohhqhhojhqjhblhdlhflhzlhBlhDlhbnhdnhfnhznhBnhDnhbphdphfphpphzphBphDpoccoecoAcoCcoceoeeoAeoCeopiocmoemoAmoCmocooeooAooCovddvBdvdnvBn") +r(5073, "Shield", layout="0aaaacaaeaagaaiaakaamaaoaaxaaacaccaecagcaicakcamcaocbxcaaeaceaeeageaieakeameaoecxeabgadgafgahgajgalgangdxgaciaeiagiaiiakiamidxietjeBjaekagkaikakkbvkexkbzkagmaimcxmahodxohcbhebhgbhibhkbhmbhcdhedhgdhidhkdhmdhcfiefigfiifikfhmfhdhifhihhijhhlhhejigjiijhkjhglhilodbofbohbojbolboddofdohdojdoldohlvfcvhcvjcvfevhevjevggvigvhiChdChf") +r(5074, "Siam", layout="0afaazaadbahbaxbaBbacdaedagdaidandardawdaydaAdaCdaleateabfadfaffahfajfavfaxfazfaBfaDfaahachaehaghaihakhamhaohaqhashauhawhayhaAhaChaEhabjadjafjahjajjavjaxjazjaBjaDjalkatkaclaelaglailanlarlawlaylaAlaCladnahnaxnaBnafoazohddhfdhhdhxdhzdhBdhcfhefhgfhifhwfhyfhAfhCfhbhhdhhfhhhhhjhhlhhnhiphhrhhthhvhhxhhzhhBhhDhhcjhejhgjhijhwjhyjhAjhCjhdlhflhhlhxlhzlhBloedogdoydoAdodfoffohfoxfozfoBfochoehoghoihowhoyhoAhoChodjofjohjoxjozjoBjoelogloyloAl") +# +#r(5075, "Space Ship", layout="0afaahaajaalaanaapaaraataavaadbaxbabcancazcaaeafeaheajealeaneapeareateaveaAeadfaxfangadhaxhaniadjaxjankadlaxlanmadnaxnanohgahiahkahmahoahqahsahuahebhwbhcchychadhmdhodhAdhgehiehkehqehsehuehmfhofhdghxghnhhdihxihnjhdkhxkhnlhdmhxmhnnohaojaolaonaopaoraotaofbovbodcoxcobdozdoheojeoleoneopeoreoteqngodhoxhqniodjoxjqnkodloxlqnmviavkavmavoavqavsavgbvubvecvwcvcdvydvievkevmevoevqevsevdivxivdkvxkCnaCjeCleCneCpeCreCdjCxj") +# +r(5076, "Square", layout="0daadcadeadgadiadkadacdccdecdgcdicdkcdaedcedeedgediedkedagdcgdegdggdigdkgdaidcideidgidiidkidakdckdekdgkdikdkk") +r(5077, "Squares", layout="0caabcaceabgaciabkacmaboacqabsacuaaacauccddafdahdajdaldandapdcrdaaeauebdfbrfaagbggcigckgcmgbogaugcdhcrhaaibgiciickicmiboiauibdjbrjaakaukcdlaflahlajlallanlaplcrlaamaumcaobcoceobgociobkocmoboocqobsocuohidikdhmdhiliklhmlvjgvlgvjivli") +r(5078, "Squaring", layout="0caaacaceaciaakacmacqaasacuacyaaAacCaaacaecaicdkcamcaqcaucaycdAcaCccaeaceceecieakecmecqeasecuecyeaAecCecahachcehcihakhcmhcqhashcuhcyhaAhcChaajdcjaejaijamjaqjdsjaujayjaCjcalaclcelcilaklcmlcqlaslculcylaAlcCl") +r(5079, "Stairs", layout="0aoaaebaybeacdccagcaicakcbmccocbqcascaucawcdAceCcaedayddaeaieaoeauedCebefbyfaagaigaogaugaCgbchcehbghakhbmhbqhashbwhcyhbAhaaiaiiaoiauiaCibejbyjdakaikaokaukdCkaelayleamdcmagmaimakmbmmcombqmasmaumawmdAmeCmaenaynaoohechychofhahkohhChhojhemhym") +r(5080, "Star Ship", layout="0eoaaabdmbdqbaCbaccckccscaAcaadbidbudaCdbceagecoeawebAeaafaefamfaqfayfaCfecgaggaigbkgdogbsgaugawgeAgaahaehamhaqhayhaChbciagicoiawibAiaajbijbujaCjackckkcskaAkaaldmldqlaCleomhachCchaehCehaghegimgiqghyghCghaihCihakhCkoadoCdoafoCfoahoChoajoCjvaevCevagvCgvaivCiCafCCfCahCCh") +# +r(5081, "Step Pyramid", layout="0aaaacaaeaagaaiaakaamaaoaaqaaacaccaecagcaicakcamcaocaqcaaeaceaoeaqeaagacgaogaqgaaiaciaoiaqiaakackaekagkaikakkamkaokaqkaamacmaemagmaimakmammaomaqmhbbhdbhfbhhbhjbhlbhnbhpbhbdhddhfdhhdhjdhldhndhpdhbfhdfhnfhpfhbhhdhhnhhphhbjhdjhfjhhjhjjhljhnjhpjhblhdlhflhhlhjlhllhnlhplpccoecogcoicokcomcpococepeepgepiepkepmeooeocgpegpmgoogocipeipgipiipkipmiooipckoekogkoikokkomkpokCffChfCjfClfCfhChhCjhClh") +r(5082, "Stonehenge", layout="0cdachackacoacracvacyacCacaccFcajeaneareavecagcFgddhdhhdlhdphdthdxhdBhcajcFjajkankarkavkcancFncdpchpckpcopcrpcvpcypcCpveavgavlavnavsavuavzavBavadvFdvafvFfvakvFkvamvFmvepvgpvlpvnpvspvupvzpvBpCehCghCihCkhCmhCohCqhCshCuhCwhCyhCAh") +r(5083, "SunMoon", layout="0dgaciabkaamabyadebbrbbBbdccbvccaddcecheckecnebDecafbtfbAfdcgdjgdlgbxgcahchhcnhdcidjidlibribDicajbvjdckchkckkcnkbAkcalbsldcmbxmdenbBndgociobkoamobuovaevagvaivakCkh") +r(5084, "Temple", layout="0baaacaaeaalaanaapaaraataaAaaCabEaaacaccalcbncbpcbrcatcaCcaEcajdavdaaeblebnebpebrebteaEeaffahfajfavfaxfazfblgbngbpgbrgbtgadhafhahhajhavhaxhazhaBhblibnibpibribtiafjahjajjavjaxjazjaakblkbnkbpkbrkbtkaEkajlavlaamacmalmbnmbpmbrmatmaCmaEmbaoacoaeoaloanoapoaroatoaAoaCobEohhghjghvghxghhihjihvihxiooeoqeokgomgoogoqgosgougokiomiooioqiosiouiookoqkvpgvpi") +# +#r(5085, "Teotihucan", layout="0aaaacaaeaagaaiaakaamaaoaaqaasaaacascaaeaseaagcggckgcogasgaaicgickicoiasiaakaskaamasmaaoacoaeoagoaioakoamoaooaqoasoajqhbbhdbhfbhhbhjbhlbhnbhpbhrbhbdhrdhbfhrfhbhhrhhbjhrjhblhrlhbnhdnhfnhhnhjnhlnhnnhpnhrnhjpoccoecogcoicokcomcoocoqcoceoqeocgoqgocioqiockoqkocmoemogmoimokmommoomoqmojovddvfdvhdvjdvldvndvpdvdfvffvhfvjfvlfvnfvpfvdhwfhvhhwjhvlhwnhvphvdjvfjvhjvjjvljvnjvpjvdlvflvhlvjlvllvnlvplvjn") +r(5086, "The Door", layout="0amaaoaaqaeicekcemceoceqcesceucagediedueaweaegaggdigdugawgaygaeibgidiiduibwiayiackaekcgkdikakkaskdukcwkaykaAkaamacmbemcgmdimakmasmdumcwmbymaAmaCmaaobcobeocgodioakoasoduocwobyobAoaCo") +# +r(5087, "The Great Wall", layout="0aaaacaaeaagaaiaakaamaaoaaqaasaauaawaayaaAaaCaaEaaacaccaecagcaicakcamcaocaqcascaucawcaycaAcaCcaEcaaeaceaeeageaieakeameaoeaqeaseaueaweayeaAeaCeaEeaagacgaegaggaigakgamgaogaqgasgaugawgaygaAgaCgaEgaaiaciaeiagiaiiakiamiaoiaqiasiauiawiayiaAiaCiaEiaakackaekagkaikakkamkaokaqkaskaukawkaykaAkaCkaEkaamacmaemagmaimakmammaomaqmasmaumawmaymaAmaCmaEmaaoacoaeoagoaioakoamoaooaqoasoauoawoayoaAoaCoaEoaaqacqaeqagqaiqakqamqaoqaqqasqauqawqayqaAqaCqaEq") +r(5088, "Theater", layout="0baaccaceabgaaiaamaaqabsacuacwabyacaccccbecagcakcbmcaocascbuccwccyccaebceaeeaiebkebmeboeaqeauebwecyebagacgaggaigakgbmgaogaqgasgawgbygcaibciaeiaiibkibmiboiaqiauibwicyicakcckbekagkakkbmkaokaskbukcwkcykbamccmcembgmaimammaqmbsmcumcwmbym") +r(5089, "Tile Fighter", layout="0bfaahaatabvadccbecakcbmcbocaqcbwcdycbaecceaiebkebmeboebqeasecyebAebagbigckgamgaogcqgbsgbAgcchaehaghauhawhcyhbaibiickiamiaoicqibsibAibakcckaikbkkbmkbokbqkaskcykbAkdcmbemakmbmmbomaqmbwmcymbfoahoatobvohnhonepafpAfpahpAhpajpAjonk") +r(5090, "Tilepiles", layout="0aaaacaaeaagaaiaaobaqbasbaubaybaAbaCbaEbahcajcalcacdaedardatdaxdazdaBdakeameaoeaffahfaufawfayfangapgargaihakhaxhazhaqiasiauiajjaljanjaAjaCjatkavkaxkaelaglailaklaolaqlawmaymaAmaCmaEmabnadnafnahnajnhbahdahfahhahpbhrbhtbhzbhBbhDbhichkchddhsdhydhAdhlehnehgfhvfhxfhoghqghjhhyhhrihtihkjhmjhBjhukhwkhflhhlhjlhplhxmhzmhBmhDmhcnhenhgnhinocaoeaogaoqbosboAboCbojcozdomeowfopgosioljovkogloiloymoAmoCmodnofnohnvdavfavrbvBbvhlvzmvBmvenvgnCeaCAmCfn") +r(5091, "Time Tunnel", layout="0aaabcaceaegaeiaekaemacoabqaasaaacccceeceoccqcascaaecceeeeeoecqeaseaagccgeegeogcqgasgaaiccieeieoicqiasiaakbckcekegkeikekkemkcokbqkaskvcdvqdwcfwqfvchvqh") +r(5092, "Tomb", layout="0eaabcabeabgabiabkabmaboabqaesabaccccceccgccicckccmccoccqcbscaaedcebeeageaieakeameboedqeasebagccgcegeggaigakgemgcogcqgbsgdaibcibeidgiaiiakidmiboibqidsibgkaikakkbmkaimakmhjevfcvhcvjcvlcvncCgcCicCkcCmc") +# +#r(5093, "Tower and Walls", layout="0ekadmaeoadqaesadkccmccoccqcdscdaeecedeeegedieekecmedoecqeesedueewedyeeAedCedkgcmgcogcqgdsgekidmieoidqiesi") +r(5094, "Traditional Reviewed", layout="0acaaeaaiaakaamaaoaaqaasaawaayaagcaicbkccmccocbqcascaucaeeagebiebkecmecoebqebseaueaweacgaegbggcigckgcmgcogcqgcsgbugawgaygaahaAhaciaeibgiciickicmicoicqicsibuiawiayiaekagkbikbkkcmkcokbqkbskaukawkagmaimbkmcmmcombqmasmaumacoaeoaioakoamoaooaqoasoawoayovnfvlhwnhvphvnj") +r(5095, "Tree of Life", layout="0ababdacfadhacjablaanaapabractadvacxabzaaBaaccaAcaadbfdajdaldandapdardatdbxdaCdaceaAeaafaefagfaifbkfbsfaufawfayfaCfacgamgaqgaAgaehaihauhayhaliboiariagjawjblkaokbrkaambcmcembgmaimclmaomcrmaumbwmcymbAmaCmacoagocloaoocroawoaAoaiqakqamqcoqaqqasqauqhoaicdimdiqdiAdhdfiffhhfixfhzfilqirq") +# +r(5096, "Twin Temples", layout="0aaaacaaeaagaaiaakaaqaasaauaawaayaaAaaacakcaqcaAcamdaodaaeakeaqeaAeagfaifamfaofasfaufaagakgaqgaAgamhaohaaiakiaqiaAiaakackaekagkaikakkaqkaskaukawkaykaAkhbbhdbhfbhhbhjbhrbhtbhvbhxbhzbhbdhjdhldhpdhrdhzdhbfhffhvfhzfhbhhjhhlhhphhrhhzhhbjhdjhfjhhjhjjhrjhtjhvjhxjhzjoccoecogcoicoscoucowcoycokdoqdoceoieoseoyeocgoigosgoygokhoqhocioeiogioiiosiouiowioyivddvfdvhdvjdvrdvtdvvdvxdvdfvhfvjfvrfvtfvxfvdhvfhvhhvjhvrhvthvvhvxhCeeCgeCueCweCegCggCugCwg") +r(5097, "Vi", layout="0aaaaEaaacaccaCcaEcbaeaceaeeaAeaCebEecagbcgaegaggaygaAgbCgcEgcaibcibeiagiaiiawiayibAibCicEicakcckbekbgkaikakkaukawkbykbAkcCkcEkdamccmcembgmbimakmammasmaumbwmbymcAmcCmdEmeaodcoceocgobiobkoamoaooaqoasobuobwocyocAodCoeEo") +r(5098, "Victory Arrow", layout="0ataaabbcbbebbgbbibbkbambavbaxcaadamdbvdazdadebfebheajeaBeaafamfaofbvfbxfbzfaDfadgajgaqgaahaghamhaohbshbuhbwhbyhbAhbChbEhadiajiaqiaajamjaojbvjbxjbzjaDjadkbfkbhkajkaBkaalamlbvlazlaxmaanbcnbenbgnbinbknamnavnatohachmchaehmehdfhjfhaghmghoghdhhjhhqhhaihmihoihdjhjjhakhmkhamhmmodbofbohbojboadomdoafomfoahonhophorhothovhoxhozhoBhoajomjoalomlodnofnohnojn") +r(5099, "Wavelets", layout="0agaaqaaAaagcaqcaAccaeaeeaieaoeaseayeaCecGeaggaqgaAgcaiaeiaiiaoiasiayiaCicGiagkaqkaAkcamaemaimaomasmaymaCmcGmagoaqoaAoagqaqqaAqhgbhqbhAbhdehjehnehtehxehDehghhqhhAhhdihjihnihtihxihDihgjhqjhAjhdmhjmhnmhtmhxmhDmhgphqphApogcoqcoAcoceokeomeoueoweoEeoggoqgoAgociokiomiouiowioEiogkoqkoAkocmokmommoumowmoEmogooqooAovgdvqdvAdvbevlevvevFevgfvqfvAfvghvqhvAhvbivlivvivFivgjvqjvAjvglvqlvAlvbmvlmvvmvFmvgnvqnvAn") +r(5100, "Well", layout="0aiaakaamaaoaagcaicakcamcaocaqcacebeeegeeieekeemeeoeeqebseaueaafawfacgbegeggaigakgamgaogeqgbsgaugaahawhacibeiegiaiiakiamiaoieqibsiauiaajawjackbekegkeikekkemkeokeqkbskaukbimakmammbomaioakoamoaoohcfhufhchhuhhcjhuj") +#r(5101, "What a Pyramid", layout="0aaaacaaeaagaaiaakaamaaoaaqaasaauaawaaacaccbecbgcbicbkcbmcbocbqcbscaucawcaceaeebgeciedkedmecoebqeaseaueaegbggdigbkgbmgdogbqgasgaeibgidiibkibmidoibqiasiackaekbgkcikdkkdmkcokbqkaskaukaamacmbembgmbimbkmbmmbombqmbsmaumawmaaoacoaeoagoaioakoamoaooaqoasoauoawo") +r(5102, "Yummy", layout="0aoaaibakbbmbbqbasbaubaocagdbidbkdbmdbqdbsdbudawdaoeaefbgfcifckfdmfdqfcsfcufbwfayfaogaahachbehcghbihakhashbuhcwhbyhaAhaChaoiaejbgjcijckjdmjdqjcsjcujbwjayjaokaglbilbklbmlbqlbslbulawlaomainaknbmnbqnasnaunaooiobiodkofkohkojiolion") diff --git a/pysollib/games/mahjongg/mahjongg2.py b/pysollib/games/mahjongg/mahjongg2.py new file mode 100644 index 0000000000..bd60994447 --- /dev/null +++ b/pysollib/games/mahjongg/mahjongg2.py @@ -0,0 +1,138 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +# This layouts converted from Kyodai Mahjongg game +# http://www.kyodai.com/index.en.html +# http://files.cyna.net/layouts.zip + +from mahjongg import r + +# /*********************************************************************** +# // game definitions +# ************************************************************************/ + +# +r(5200, "Another Round", ncards=140, layout="0aagaaihbhacfachacjhdghdiaecaeeaegoehaeiaekaemhfdhffhfhhfjhflagaagcageogeaggoggagiogiagkogkagmagohhbhhdhhfhhhhhjhhlhhnaiaaicoicaieoieaigoigaiioiiaikoikaimoimaiohjbhjdhjfhjhhjjhjlhjnakaakcakeakgakiakkakmakoamaamcammamoaoaaocaoeaogaoiaokaomaoohpbhpdhpfhphhpjhplhpnaqaaqcoqcaqeoqeaqgoqgaqioqiaqkoqkaqmoqmaqohrbhrdhrfhrhhrjhrlhrnasaascaseoseasgosgasiosiaskoskasmasohtdhtfhthhtjhtlaucaueaugouhauiaukaumhvghviawfawhawjhxhaygayi") +r(5201, "Aqab's", layout="0caedagcaicccbcgcckceabegcembggahdahjbigbkabkeckgbkibkmbmabmccmedmgcmibmkbmmboacocdoedogdoicokbombqabqccqedqgcqibqkbqmbsabsecsgbsibsmbugawdbwgawjcyabygcymcAcbAgcAkcCedCgcCi") +# +r(5202, "Big Mountain", layout="0aaaaaqaeihfiaghogiagjhhhvhihhjaigoihaiiCiioijaikhjgvjhhjivjjhjkakfokgakhCkhokiakjCkjokkaklhlfvlghlhvlihljvlkhllameomfamgomhCmhamiomjCmjamkomlammhnehngvnghnivnihnkvnkhnmaodaofoofaohoohCohaojoojCojaoloolaonhpehpgvpghpivpihpkvpkhpmaqdaqfoqfaqhoqhCqhaqjoqjCqjaqloqlaqnhrehrgvrghrivrihrkvrkhrmaseosfasgoshCshasiosjCsjaskoslasmhtfvtghthvtihtjvtkhtlaufougauhCuhouiaujCujoukaulhvgvvhhvivvjhvkawgowhawiCwiowjawkhxhvxihxjayhoyiayjhziaAiaGaaGq") +# +r(5203, "Bridge", layout="0aaaaacaaeaagaaihbahbchbehbghbiocaoccoceocgociwdavdcvdevdgwdioeboedoefoehvfahfcvfchfevfehfgvfgvfiogbagdogdagfogfoghvhahhcvhchhevhehhgvhgvhioibaidoidaifoifoihvjahjcvjchjevjehjgvjgvjiokbokdokfokhvlavlcvlevlgvliCmaCmivnavncvnevngvniooboodoofoohvpahpcvpchpevpehpgvpgvpioqbaqdoqdaqfoqfoqhvrahrcvrchrevrehrgvrgvriosbasdosdasfosfoshvtahtcvtchtevtehtgvtgvtiouboudoufouhwvavvcvvevvgwviowaowcoweowgowihxahxchxehxghxiayaaycayeaygayi") +r(5204, "Butterfly 2", layout="0aaeaagabcabiadbadjhefvefaenafaafeofeafgofgaflafphgdvgdwgfhghvghahaaheoheahgohgahkahqhifvifcinbjbbjjajqblcbliblqbmocndcnhcnmapbbpdapfbphapjbplapnhqbpqdiqfpqhiqjpqlhqnarbbrdarfbrharjbrlarnctdcthctmbuobvcbvibvqbxbbxjaxqhyfvyfcynazaazeozeazgozgazkazqhAdvAdwAfhAhvAhaBaaBeoBeaBgoBgaBlaBphCfvCfaCnaDbaDjaFcaFiaGeaGg") +r(5205, "ChessMania", layout="0aaaaacaaeaagaajaalaanaapacaacgbcmaeaaegaejaelaenaepaibbidaifbihaijbilainbipbkbakdbkfakhbkjaklbknakpambbmdamfbmhamjbmlamnbmponfonhonjonlbobaodbofaohbojaolbonaopopfppioplaqbbqdaqfbqhaqjbqlaqnbqporforlbsbasdbsfashbsjaslbsnaspaubbudaufbuhaujbulaunbupbwbawdbwfawhbwjawlbwnawpaBaaBcaBgaBjaBlaBpaDabDdaDgaDjbDmaDpaFaaFeaFgaFjaFnaFp") +r(5206, "Cross", layout="0baebagbaiaccdcebcgdciackaeacecdeebegdeicekaemcgadgcdgebggdgidgkcgmbiabicaiebigaiibikbimbkabkcakebkgakibkkbkmcmadmcdmebmgdmidmkcmmaoacocdoebogdoicokaomaqcdqebqgdqiaqkbsebsgbsi") +r(5207, "Cupido's Heart", layout="0aadaalbbfbbjcchaddadlbefcehbejcghdhfdidcihdjbdjjckhdkldlacmhdmndnbdodcohdopeqedqqdsddspdtbdundvadwldxbdxjdyddyhdzfcAhaCecChaCkbEfcEhbEjcGh") +r(5208, "Diamond", ncards=140, layout="0aaiaakacgbcibckacmaeebegceicekbemaeoagcbgecggdgidgkcgmbgoagqaiabicciedigeiieikdimciobiqaisakabkcckedkgekiekkdkmckobkqaksamcbmecmgdmidmkcmmbmoamqaoebogcoicokbomaooaqgbqibqkaqmasiask") +r(5209, "Dragon 2", layout="0bafbbdobeobgbbhbcfbcmbdkodlodnbdobecaegbemofcbgabgcbghbgjaglohavhbohcbiabicbijbilojbbjhojjojlbkbbkfokhbkjvkjbklvklolbolfblholjollbmbcmdbmfbmjbmlboioojbokaoohpobqhbqjaqobrforhorjasdosfbshvshbsjvsjbslbsnhtdbtfothotjaubaudbuhbujbwgbwkbwmbydoyebyfbymayohzobAcaAobBjbCdoCebCfoCgbCh") +r(5210, "Empty Pyramids", layout="0aaiabghbiabkaccacehcgocihckacmacoadghdiadkaeiahiaighiiaikajehjgojihjkajmakchkeokgvkiokkhkmakoalahlcolevlgClivlkolmhloalqhmaamchmeomgvmiomkhmmamohmqonahncanehngonihnkanmhnoonqvoaoochoeaoghoiaokhomooovoqCpavpcopehpgapiopihpkopmvpoCpqvqaoqchqeaqghqiaqkhqmoqovqqorahrcarehrgorihrkarmhroorqhsaaschseosgvsioskhsmasohsqatahtcotevtgCtivtkotmhtoatqauchueougvuioukhumauoavehvgovihvkavmawghwiawkaxiaAiaBghBiaBkaCcaCehCgoCihCkaCmaCoaDghDiaDkaEi") +r(5211, "Fish face", layout="0bajbciocjbckvdjbehoeibejoekbelcggcgmchichkcifcincjhcjlckebkjckoclgclmcmebmiomjbmkcmocnccnqcoeboioojbokcoobpbcpgcpmbprcqebqjcqoaracrhcrlarsbsfbsnbtiotjbtkbugbumbwhbwlbyibykcAjcBhcBlbDgbDmaFfaFn") +r(5212, "Floating City", layout="0oagoaiocdocfochocjoclocphdahdchdmhdoaeboebaedaefaehaejaelaenoenhfahfcvfchfmvfmhfoagbagnahdvheahfahhahjvhkahlaibainvjgvjiakbCkhaknhlfhlhhljambamdamfomgamhCmhomiamjamlamnomphnfhnhhnjjoaaobCobaodCodaofCofoogaohCohooiaojCojaolColaonConjooCopooqhpfhphhpjaqbaqdaqfoqgaqhCqhoqiaqjaqlaqnoqphrfhrhhrjasbCshasnvtgvtiaubaunavdvveavfavhavjvvkavlawbawnhxahxcvxchxmvxmhxoayboybaydayfayhayjaylaynoynhzahzchzmhzooAdoAfoAhoAjoAloApoCgoCi") +#r(5213, "Flowers 2", layout="0aaiacgbciackadcadoaeiafabfcafeafmbfoafqahcahoaihaijhjiakfakhakjaklhlghlihlkamdamfamhomhamjomjamlamnhnehnghnivnihnkhnmaobaodaofoofaohoohaojoojaoloolaonaophpchpehpgvpghpivpihpkvpkhpmhpoaqbaqdoqdaqfoqfaqhoqhCqhaqjoqjCqjaqloqlaqnoqnaqphrchrehrgvrghrivrihrkvrkhrmhroasbasdasfosfashoshasjosjasloslasnasphtehtghtivtihtkhtmaudaufauhouhaujoujaulaunhvghvihvkawfawhawjawlhxiayhayjazcazoaBabBcaBeaBmbBoaBqaCiaDcaDoaEgbEiaEkaGi") +#r(5214, "Full Vision 3", layout="0aaeaagaaihbehbiacbhccacdacfhcgachacjhckaclacnhcoacpaeahebaecaeeaegaeiaekhelaemagbhgcagdagfhggaghagjhgkaglagnhgoagphhehhiaieaigaiiainhioaiphjgakeakgakiaknhkoakphlehliambhmcamdamfhmgamhamjhmkamlamnhmoampaoahobaocaoehofaogaoiaokholaomaqbhqcaqdaqfhqgaqhaqjhqkaqlaqnhqoaqphrehriaseasgasiasnhsoasphtgaueaugauiaunhuoauphvehviawbhwcawdawfhwgawhawjhwkawlawnhwoawpayahybaycayeaygayiaykhylaymaAbhAcaAdaAfhAgaAhaAjhAkaAlaAnhAoaAphBehBiaCeaCgaCi") +r(5215, "Hidden Words", layout="0haahachaehaghalabaabcobdabeabgabjablbbnabphcahcchceocghchhckhcqadgadmodohefheihemheoafgafjofjaflafnafphgfogghgkhgmhgohichiehikhinajaojaajcojdajeajghjhajjajlajnajphkbhkfokjhklhkpalaalghlialjalmombhmchmehmnanaancanebnganjanlannbnphochoiholhqchqfhqihqkaraarcarearjhschshhslhsnhspatgatjatlatnatphuchuhhunavaavcaveavjhvkhwdhwfhxihxmhxqayahybaycayeaygayjaylaynhyoayphzfhzkaAabAdaAghAhaAjaAmaAphBlhBnhBqaCahCbaCghCiaCjaCphDfhDp") +r(5216, "Hovercraft", layout="0aadaafaahaajjbgdccdceacgdcidckjdgaedaefaehaejhfgagfpggaghhhgaigajajjgajmhkaakghkmalaolaalmolmhmavmaemghmmvmmanaonaCnaenceneenienkanmonmCnmhoavoaeoghomvomapaopaapmopmhqaaqghqmarajrgarmasghtgaufpugauhhvgawdawfawhawjjxgdycdyeaygdyidykjzgaAdaAfaAhaAj") +r(5217, "Hurdles", layout="0aaaaacaaeaagaaiaakaamaaohbahbchbehbghbihbkhbmhboacaocaaccoccaceoceacgocgaciociackockacmocmacoocohdahdchdehdghdihdkhdmhdoaeaaecaeeaegaeiaekaemaeoagaagcageaggagiagkagmagohhahhchhehhghhihhkhhmhhoaiaoiaaicoicaieoieaigoigaiioiiaikoikaimoimaiooiohjahjchjehjghjihjkhjmhjoakaakcakeakgakiakkakmakoamaamcameamgamiamkammamohnahnchnehnghnihnkhnmhnoaoaooaaocoocaoeooeaogoogaoiooiaokookaomoomaooooohpahpchpehpghpihpkhpmhpoaqaaqcaqeaqgaqiaqkaqmaqo") +r(5218, "Hurricane", layout="0babaadaambaoabibegbekofdbfeoffafiofioflbfmofnbgchgibgoahaahiohiahqhibhiihipajbajeijfajgvjgajiojiajkvjkijlajmajphkbhkihkpalcalialohmchmoandineanfanianlinmannapbipdapevpeipfapgapkiplapmvpmipnappardirearfariarlirmarnhschsoatcatiatohubhuihupavbaveivfavgvvgavioviavkvvkivlavmavphwbhwihwpaxaaxioxiaxqbychyibyoozdbzeozfazioziozlbzmoznbAgbAkaDibEbaEdaEmbEo") +# +r(5219, "IloveU", layout="0caddafcahdaldandapdcbcciceacejcgacgkdibcilckdckmcmecmndngdnpcoeconcqdcqmdsbcslcuacukcwacwjdybcyidzldzndzpcAddAfcAhdBpdDldDndDp") +r(5220, "Inazuma", layout="0caaaaocaqcccacmccoacqceebeiaekcemaeoagacggcgkagmciaaicciibimakackcakeckkamccmeamgcmmaoecogaoicoocqaaqgdqiaqkcqqcscasicskasmcueaukcumauocwgawmcwoawqbyecyiayocyqaAecAgcAkaAqaCccCeaCgbCicCmaEacEcaEecEocGaaGccGq") +r(5221, "JPs", layout="0baabakbbmbcabckbcobdmbdqbeabeobfqbgabhobhqbiabicbiebigbiibikbimbjobkabkcbkebkgbkibkkbkmamqbqabqcbqebqgbqibqkbqmbqobqqbsabscbsebsgbsibskbsmbsobsqbuabuhbujbwabwhbwjaxqbyabyhbyjbAabAcbAgbAibCabCcbCebCgbCibEabEcbEebEgbEibGe") +r(5222, "Japan", ncards=96, layout="0baabacbaebagbaibcaacebcibeaaeebeibgabgcbgebggbgiahoajkajoalgalialkhllalmaloangbnibnkanmapebpgbpiapkapmbrebrgariarkatehtfatgatiavaavcavebwibwoaxaaxcbxmbyiaykazabAgbAibAkbAmbAobCiaCkbDmbEibEo") +r(5223, "Krebs", layout="0aaaaacaaeaagbaibalaanaapaaraatacaactaeaaetagaagtaiaCikaitvjkakaCkjokkCklaktvljhlkvllamaCmiomjamkCmkomlCmmamtvnihnjvnkhnlvnmaoaCohooiaojCojookaolColoomConaotvphhpivpjhpkvplhpmvpnbqaCqgoqhaqiCqioqjaqkCqkoqlaqmCqmoqnCqobqtvrhhrivrjhrkvrlhrmvrnCshosiasjCsjoskaslCslosmCsnbtavtihtjvtkhtlvtmbttCuioujaukCukoulCumavavvjhvkvvlavtCwjowkCwlaxavxkaxtCykazaaztaBaaBtaDaaDtaFaaFtaHaaHcaHeaHgdHidHlaHnaHpaHraHt") +r(5224, "Kumo", layout="0caadaccaecagbaibamdaqdcacccacebcgbckdcoceaaecceebeidembeqcgabgccggdgkbgobiabiediibimbiqbkcdkgckkbkockqbmadmebmicmmamocmqdocbogbokaomcoodoqdqabqebqicqkcqmdqocqq") +r(5225, "Kyodai 14", layout="0aaiachhciacjodiaefhegaehheiveiaejhekaelofhCfiofjagchgdagehgfagghghagivgihgjagkhglagmhgnagoohiaibhicaidaihhiiaijainhioaipakcbkgokhbkivkiokjbkkakohlchloamcbmfbmlamoaoahobaochodaoeooehofvofaogooghohvohaoiooihojvojaokookholvolaomoomhonaoohopaoqaqcbqfbqlaqohrchroascbsgoshbsivsiosjbskasoaubhucaudauhhuiaujaunhuoaupoviawchwdawehwfawghwhawivwihwjawkhwlawmhwnawooxhCxioxjayfhygayhhyivyiayjhykayloziaAhhAiaAjaCi") +r(5226, "Kyodai 17", layout="0daacaccaecagcaicakdamccaccgccmceacegcemcgacgccgecggcgicgkcgmciadigcimckadkgckmcmacmccmecmgcmicmkcmmcoacogcomcqacqgcqmdsacsccsecsgcsicskdsm") +# +r(5227, "Kyodai 18", layout="0daidchdcjdegdekdgfdgldiedimdkdakidkndmcamhamjdmodobaogaoiaokdopdqaaqfaqhaqjaqldqqdsbasgasiaskdspducauhaujduodwdawidwndyedymdAfdAldCgdCkdEhdEjdGi") +r(5228, "Kyodai 20", layout="0aaeaagaaiaakaamaaohbjacdaciackacpaecbehbelaeqagbaggagmagraiaaifhigaihoihhiiviiaijoijCijhikvikailoilhimainaisakaakebkjakoaksamaamdcmhamjcmlampamsaoahobaocoochodvodaoeooeCoehofvofaogooghohaoikojaokholaomoomhonvonaoooooCoohopvopaoqooqhoraosaqaaqdcqhaqjcqlaqpaqsasaasebsjasoassauaaufhugauhouhhuivuiaujoujCujhukvukauloulhumaunausawbawgawmawraycbyhbylayqaAdaAiaAkaAphBjaCeaCgaCiaCkaCmaCo") +# +r(5229, "Kyodai 23", layout="0aaehbeacdoceacfhdevdeaecaeeoeeaeghfdvfehffagaagcageogeaggagihhbhhdhhfhhhaiaaicoicaieoieaigoigaiihjbhjdvjdhjfvjfhjhakaakcokcakeokeCkeakgokgakihlbhldvldhlfvlfhlhamaamcomcameomeamgomgamihnbhndhnfhnhaoaaocaoeooeaogaoihpdhpfaqaaqcaqeoqeaqgaqihrbhrdhrfhrhasaascoscaseoseasgosgasihtbhtdvtdhtfvtfhthauaaucoucaueoueCueaugougauihvbhvdvvdhvfvvfhvhawaawcowcaweoweawgowgawihxbhxdhxfhxhayaaycayeoyeaygayihzdvzehzfaAcaAeoAeaAghBevBeaCdoCeaCfhDeaEe") +# +r(5230, "Kyodai 24", layout="0aaaiabaacaaejafaagaaiiajaakvbcibdCbfibhvbiacaicbaccacevceicfacgvcgaciicjackvdciddCddCdfidhCdhvdiaeaiebaecaeeveeiefaegvegaeiiejaekvfcifdCfdifhCfhvfiagaigbagcagevgeigfaggvggagiigjagkvhcihdChdihhChhvhiaiaiibaicaievieiifaigvigaiiiijaikvjcijdCjdCjfijhCjhvjiakaikbakcakevkeikfakgvkgakiikjakkvlcildClfilhvliamaimbamcamejmfamgamiimjamk") +r(5231, "Kyodai 25", layout="0cagbaicakbcgbckodgodkbegbekcggcgkbieoifbigciibikoilbimbkiolicmabmicmqboabogdoibokboqcqabqcoqdbqedqgdqkbqmoqnbqocqqbsabsgdsibskbsqcuabuicuqovibwibyeoyfbygcyibykoylbymcAgcAkbCgbCkoDgoDkbEgbEkcGgbGicGk") +# +r(5232, "Kyodai 26", layout="0aahhbhacgacihdghdiaefaehaejhffhfhhfjageaggagiagkhhehhghhihhkaidaifaihaijailhjdhjhhjlakcakeakgakiakkakmhlchlehlghlihlkhlmambamdamfamhamjamlamnhnbhnfhnhhnjhnnaoaaocaoeaogaoiaokaomaoohpahpchpehpghpihpkhpmhpoaqaaqcaqeaqgaqiaqkaqmaqohrahrchrehrghrihrkhrmhroasaascaseasgasiaskasmasohtbhtfhthhtjhtnaubaudaufauhaujaulaunhvchvehvghvihvkhvmawcaweawgawiawkawmhxdhxhhxlaydayfayhayjaylhzehzghzihzkaAeaAgaAiaAkhBfhBhhBjaCfaChaCjhDghDiaEgaEihFhaGh") +# +r(5233, "Kyodai 27", layout="0aagacfhcgachaeehefaegoeghehaeivfgagdhgeagfogfhggCggaghoghhgiagjvhfvhhaichidaieoiehifCifaigoighihCihaiioiihijaikvjevjgvjiakbhkcakdokdhkeCkeakfokfhkgCkgakhokhhkiCkiakjokjhkkaklvldvlfvlhvljamahmbamcomchmdameomehmfCmfamgomghmhCmhamiomihmjamkomkhmlammvndvnfvnhvnjaobhocaodoodhoeCoeaofoofhogCogaohoohhoiCoiaojoojhokaolvpevpgvpiaqchqdaqeoqehqfCqfaqgoqghqhCqhaqioqihqjaqkvrfvrhasdhseasfosfhsgCsgashoshhsiasjvtgauehufaugoughuhauiawfhwgawhayg") +# +r(5234, "Kyodai 28", layout="0baibbgbbkbcebcibcmbdcbdobeabeibeqbgacggvghcgiCgivgjcgkbgqbiacifciicilbiqbkackeakhakjckmbkqhlhhljbmacmdamgamiomiamkcmnbmqhnhhnjboacoeaohaojcomboqbqacqfcqicqlbqqbsacsgvshcsiCsivsjcskbsqbuabuibuqbvcbvobwebwibwmbxgbxkbyi") +# +r(5235, "Kyodai 41", layout="0CaeCagCaivbevbgvbiCcdoceocgociCcjvddhdevdfhdgCdgvdhhdivdjCecaeeoeeCeeaegoegaeioeiCeiCekCfavfbofchfdvfdhffvffCfghfhvfhhfjvfjofkvflCfmCgdageogeaggoggagiogiCgjChavhbohchhdvhdhhfvhfChghhhvhhhhjvhjohkvhlChmCicaieoieCieaigoigaiioiiCiiCikCjavjbojchjdvjdhjfvjfCjghjhvjhhjjvjjojkvjlCjmCkdakeokeakgokgakiokiCkjClavlbolchldvldhlfvlfClghlhvlhhljvljolkvllClmCmcameomeCmeamgomgamiomiCmiCmkvndhnevnfhngCngvnhhnivnjCodooeoogooiCojvpevpgvpiCqeCqgCqi") +# +r(5236, "Kyodai 42", layout="0oaboadCagoajoalhbahbcvbchbeobfvbgobhhbihbkvbkhbmacbacdacjaclhdbhddodevdfCdgvdhodihdjhdlaecaekhfchfeoffvfgofhhfihfkagdCggagjhhdhhfhhhhhjaieaiihjehjghjiakfCkgakhhlfvlghlhCmfamgomgCmhhnfvnghnhaogCoghpfhphCqcvqdoqeCqeaqgoqgoqiCqivqjCqkhrfhrhasgCsghtfvtghthCufaugougCuhhvfvvghvhawfCwgawhhxehxghxiayeayihzdhzfhzhhzjaAdCAgaAjhBchBeoBfvBgoBhhBihBkaCcaCkhDbhDdoDevDfCDgvDhoDihDjhDlaEbaEdaEjaElhFahFcvFchFeoFfvFgoFhhFihFkvFkhFmoGboGdCGgoGjoGl") +r(5237, "Lattice", layout="0aaiacebciacmaecbeeaegbeiaekbemaeoagecgiagmaicbieaigciiaikbimaioakeckiakmamcbmebmgdmibmkbmmamoboeeoibomaqabqccqeeqgdqieqkcqmbqoaqqbseesibsmaucbuebugduibukbumauoawecwiawmaycbyeaygcyiaykbymayoaAecAiaAmaCcbCeaCgbCiaCkbCmaCoaEebEiaEmaGi") +# +r(5238, "Leo", layout="0aapabiablhbphcfacghchhclacnocpadjodladpvdpheeaefheiaelvelhepCepofihflCflafnofphgdagevgiagjoglagpvgphhiChiahlvhlhhpChphicaidoiihilCilainoipvjiajjojlajpvjpbkabkchkiCkiaklvklhkpCkpolbolihllalnolpbmabmcvmiamjomlampvmphnianlhnpooiholaonoophpfaphapjappaqfhqiaqlhqphrdarnasehsqhtcatpaudbumhuphvbcvgavqawccwlhwqhxbcxjaxpayccylhyphzbczgazqaAdbAmhAqhBcaBpaCehCphDeaDgaDohEgaEiaEmhEohFiaFkhFmhGk") +# +r(5239, "Loose Ends", layout="0aaaoabaaioapaaqhbahbihbqacboccachociacjocoacphdbhdivdihdpaecoedaegaeioeiaekoenaeohfchfivfihfoagdogeaghogiagjogmagnhhdhhivhihhnaieoifaiioiioilaimhjfhjihjlakgokgakiakkokkhlholihljamahmbamcomchmdvmdameomehmfvmfamgvmhamiCmivmjamkhmlvmlammommhmnvmnamoomohmpamqhnhonihnjaogoogaoiaokookhpfhpihplaqeoqfaqioqioqlaqmhrdhrivrihrnasdoseashosiasjosmasnhtchtivtihtoaucoudaugauiouiaukounauohvbhvivvihvpawbowcawhowiawjowoawphxahxihxqayaoybayioypayq") +r(5240, "Mini Traditional", ncards=48, layout="0aaeacdacfhdeaecaeeoeeaeghfdvfehffagbagdogeagfaghhhchhevhehhgaiaaicoicaieoieaigoigaiihjchjevjehjgakbakdokeakfakhhldvlehlfamcameomeamghneaodaofaqe") +r(5241, "Mini-Layout", ncards=8, layout="0aabaadacahcbhcdaceaebaed") +r(5242, "Mission Impossible", layout="0baabamaapccaccmacpdeacecbeeaegbeicekdemaepcgacgmagpbiabimaipakpbmacmcdmeemgdmicmkbmmampcocaopdqeaqpcscaspbuacucdueeugduicukbumaupawpbyabycbyebygbyibykbymaypcAacAgaApdCadCgaCpeEaaEp") +# +r(5243, "Multi X", layout="0aaaaaiaaqhbbhbhhbjhbpoccocgockocovddvdfvdlvdnceeCeecemCemvfdvffvflvfnogcoggogkogohhbhhhhhjhhpaiaaiiaiqhjbojcvjdCjevjfojghjhojihjjojkvjlCjmvjnojohjpakaakiakqhlbhlhhljhlpomcomgomkomovndvnfvnlvnncoeCoecomComvpdvpfvplvpnoqcoqgoqkoqohrbhrhhrjhrpasaasiasqhtbotcvtdCtevtfotghthotihtjotkvtlCtmvtnotohtpauaauiauqhvbhvhhvjhvpowcowgowkowovxdvxfvxlvxncyeCyecymCymvzdvzfvzlvznoAcoAgoAkoAohBbhBhhBjhBpaCaaCiaCq") +#r(5244, "New Layout 2", layout="0CabCadCafacapcahccvccacepcehcgvcgheaveaaecpecheeveeaegpegCfaCfcCfeCfgagapgahgcvgcagepgehggvggChaChcCheChghiaviaaicpichievieaigpigakaqkahkcwkcakeqkehkgwkghmawmaamcqmchmewmeamgqmgaoaqoahocwocaoeqoehogwoghqavqaaqcpqchqevqeaqgpqgCraCrcCreCrgasapsahscvscasepsehsgvsgCtaCtcCteCtghuavuaaucpuchuevueaugpugawapwahwcvwcawepwehwgvwgCybCydCyf") +r(5245, "Okie's Nitemare", layout="0aaoaaqbbeabmhbpacoacqcddbdgadmhdpaeoaeqbfccffafmhfpagoagqbhehhpaiiaioaiqhjihjqakiakqalohlqammhmoamqandankhnmanoonohnqaobaoihokaompomhooaoqapghpiapkopkhpmapoopohpqaqabqcoqdbqeoqfhqgaqioqihqkvqkCqlaqmpqmhqoaqqarghriarkorkhrmaroorohrqasbasihskasmpsmhsoasqatdatkhtmatootohtqaumhuoauqavohvqawiawqhxihxqbyeayiayoayqhzpbAccAfaAoaAqaBmhBpcCdbCgaCoaCqaDmhDpbEeaEoaEqaFmhFpaGoaGq") +r(5246, "Orbital", ncards=84, layout="0dafdahdajdchcehbghbihbkhclablfbljclocnabncbnebnkbnmcnocpabpfbpjcpobqhbshbuhcwhdyhdAfdAhdAj") +r(5247, "Owl", layout="0baebagbaibakbambcdbcncecbejbeocgbbghcgjbglbgpcicbijbiobkdbknclablpbmebmmcnbanpcodaofbohbojbolbonhopipfappoppcqdaqfbqhbqjbqlbqnhqpcrbarpbsebsmctabtpbudbuncwcbwjbwocybbyhcyjbylbypcAcbAjbAobCdbCnbEebEgbEibEkbEm") +r(5248, "Pantheon", layout="0baebcebdgbdqbeeaeiaekaemaeobfcbfgbfqbgebhcbiebjcojdbjgbjqbkabkeakiakkakmakoolbblcoldblgblqbmabmeonbbncboaoodboeopbbpcbpgbpqbqabqeaqiaqkaqmaqoorbbrcbrgbrqbsaosdbseotbbtcbuabueovbbvcovdbvgbvqbwabweawiawkawmawobxcoxdbxgbxqbyebzcbAebBcbBgbBqbCeaCiaCkaCmaCobDgbDqbEebGe") +# +r(5249, "Papillon", layout="0bagbaibakobhobjbcfbchbcjbclodhodjbecbeebegbeibekbembeoofdofnbgdbgnbiebimojeojmbkdbkfbklbknbmcbmgbmkbmobobbohbojbopopibqabqibqqoribsbbshbsjbspbucbugbukbuobwdbwfbwlbwnoxeoxmbyebymbAdbAnoBdoBnbCcbCebCgbCibCkbCmbCooDhoDjbEfbEhbEjbEloFhoFjbGgbGibGk") +r(5250, "Pyramid 1", layout="0aagaaiaceacghchaciackaecbeebegbeibekaemagabgcbgecggcgibgkbgmagoaiabicciecigvihciicikbimaioakabkcckedkgdkickkbkmakoamabmccmedmgdmicmkbmmamoaoaboccoecogvohcoicokbomaooaqabqcbqecqgcqibqkbqmaqoascbsebsgbsibskasmaueaughuhauiaukawgawi") +r(5251, "Pyramid 2", layout="0aaeaagaaiaccbcebcgbciackaeabecbeeoefbegoehbeibekaemagacgcdgedggdgicgkagmbiadiceieeigeiidikbimbkadkcekeekgekidkkbkmamacmcdmedmgdmicmkammaoabocboeoofbogoohboibokaomaqcbqebqgbqiaqkaseasgasi") +# +r(5252, "Quad", layout="0baabacbaeaagbaibakbamobbobdobjoblbcabccvccbceacgbcibckvckbcmodboddodjodlbeabecvecbeeaegbeibekvekbemofbofdofjoflbgabgcbgeaggbgibgkbgmaiaaicaiebigaiiaikaimbkabkcbkeakgbkibkkbkmolboldoljollbmabmcvmcbmeamgbmibmkvmkbmmonbondonjonlboabocvocboeaogboibokvokbomopbopdopjoplbqabqcbqeaqgbqibqkbqm") +# +r(5253, "Rectangle", layout="0daadacdaedagdcadccdcedcgdeadecdeedegdgadgcdgedggdiadicdiedigdkadkcdkedkgdmadmcdmedmgdoadocdoedogdqadqcdqedqg") +r(5254, "Reindeer", ncards=64, layout="0haeabdocchdbadnaecheehemaffaflafohgkahfahhahjajfajjalfalhaljallhmmanfanjonnaooapfaphapjarfarjaslatfathatjhtmouahufounhvbhvjavoawchwfawkhxdaxghxhhxlayioymazevznaAiaBchBdaBgaBohCbhChaCioDaoDivEh") +r(5255, "Rings", layout="0aahabfhbhabjacdhcfachochhcjaclhddadfodfhdhvdhadjodjhdlaebaedhefaehoehhejaelaenhfcaffhfhafjhfmagcaghagmhhchhmaicaihaimhjcajfhjhajjhjmakbakdhkfakhokhhkjaklaknhldalfolfhlhvlhaljoljhllamdhmfamhomhhmjamlanfhnhanjaoaaohaooaqaaqhaqoarfhrharjasdhsfashoshhsjaslhtdatfotfhthvthatjotjhtlaubaudhufauhouhhujaulaunhvcavfhvhavjhvmawcawhawmhxchxmaycayhaymhzcazfhzhazjhzmaAbaAdhAfaAhoAhhAjaAlaAnhBdaBfoBfhBhvBhaBjoBjhBlaCdhCfaChoChhCjaClaDfhDhaDjaEh") +r(5256, "River Bridge", ncards=116, layout="0aafaalacfachacjaclhdfhdhhdjhdloefoehoejoelvffvfloggogiogkvhfhhhhhjvhloigaiioiioikvjfhjhhjjvjlajoakcokgakiokiokkakmakqalavlfhlhhljvllomgamiomiomkvnfhnhhnjvnloogaoiooiookvpfhphhpjvploqgaqioqioqkvrfhrhhrjvrlosgasiosioskvtfhthhtjvtlaucougauiouioukauoavavvfhvhhvjvvlavmavqowgawiowiowkvxfhxhhxjvxloygoyioykvzfvzloAfoAhoAjoAlhBfhBhhBjhBlaCfaChaCjaClaEfaEl") +# +r(5257, "Roman Arena", layout="0CaaCacCaeCagCaivbbvbdvbfvbhCcaoccoceocgCcivdbhddadehdfvdhCeaoecoegCeivfbhfdafehffvfhCgaogcoggCgivhbhhdahehhfvhhCiaoicoigCiivjbhjdajehjfvjhCkaokcokgCkivlbhldalehlfvlhCmaomcCmcCmeomgCmgCmivnbhndvndanehnfvnfvnhCoaoocooeCoeoogCoivpbhpdvpdapehpfvpfvphCqaoqcCqcCqeoqgCqgCqivrbhrdarehrfvrhCsaoscosgCsivtbhtdatehtfvthCuaoucougCuivvbhvdavehvfvvhCwaowcowgCwivxbhxdaxehxfvxhCyaoycoygCyivzbhzdazehzfvzhCAaoAcoAeoAgCAivBbvBdvBfvBhCCaCCcCCeCCgCCi") +r(5258, "Rugby", layout="0aafaahaceacgaciaecaeeaegaeiaekagaagcagehgfagghghagiagkagmaiaaichidaiehifaighihaiihijaikaimakahkbakchkdakeikfakgikhakihkjakkhklakmamahmbamchmdameimfamgvmgimhamihmjamkhmlammondonjaoahobaochodaoevoeiofaogvogiohaoivoihojaokholaomopdopjaqahqbaqchqdaqeiqfaqgvqgiqhaqihqjaqkhqlaqmasahsbaschsdaseisfasgishasihsjaskhslasmauaauchudauehufaughuhauihujaukaumawaawcawehwfawghwhawiawkawmaycayeaygayiaykaAeaAgaAiaCfaCh") +r(5259, "Shapeshifter", layout="0aaoacmhcnacoaekhemaenheoaepagihgkaglogmhgnagohgpaiaaighiiaijoijhilaimoinbiohjaakaokaakehkgakhokhhkjakkokkokmhknakohkphlavlaamaomaamchmeamfomghmhamiomiomkhmlammomnbmohnavnavngvnivnkaoaooaCoahocaodooehofaogoogCogooiCoihojaokookCokoomhonaoohophpavpavpgvpivpkaqaoqaaqchqeaqfoqghqhaqioqioqkhqlaqmoqnbqohravraasaosaasehsgashoshhsjaskoskosmhsnasohsphtaauaaughuiaujoujhulaumounbuoawihwkawlowmhwnawohwpaykhymaynhyoaypaAmhAnaAoaCo") +# +r(5260, "Space Bridge", layout="0aaaaacaaeaagaaiaakaamaaoaaqhbbhbdhbfhbhhbjhblhbnhbpacaoccoceocgociockocmocoacqhdbvddvdfvdivdlvdnhdpaeaoecCeeCeiCemoeoaeqhfbvfdvfnhfpagaogcogoagqhhbhhpaiaoicoioaiqhjbajfajlhjpakaokchkghkkokoakqhlbvldClealholhaljoljClmvlnhlpamaomchmivmiomoamqhnbanhonhanjonjhnpaoaoochoghokoooaoqhpbapfaplhppaqaoqcoqoaqqhrbvrdvrnhrpasaoscCseCsiCsmosoasqhtbvtdvtfvtivtlvtnhtpauaoucoueougouioukoumouoauqhvbhvdhvfhvhhvjhvlhvnhvpawaawcaweawgawiawkawmawoawq") +r(5261, "Space Shuttle", layout="0aalaanacibckbcmaeebegbeibekbembgcbgecggcgicgkcgmbiacicciedigdiidikdimckadkcekeekgekiekkekmbmacmccmedmgdmidmkdmmbocboecogcoicokcomaqebqgbqibqkbqmasibskbsmaulaun") +r(5262, "Stage 1", layout="0aaebagaaiaccbceccgbciackaeabecceevefcegvehceibekaemagacgcdgedggdgicgkagmaiadicdiedigdiidikaimakadkcdkedkgdkidkkakmamacmcdmedmgdmicmkammaoaboccoevofcogvohcoibokaomaqcbqecqgbqiaqkasebsgasi") +r(5263, "Stage 2", layout="0aafaahaceacgaciaeeaegaeiagcbgebggbgiagkaiabicciecigciibikaimbkackcckeckgckickkbkmbmaombbmcomdbmepmfbmgpmhbmiomjbmkomlbmmboaoobbocoodboepofbogpohboioojbokoolbombqacqccqecqgcqicqkbqmasabsccsecsgcsibskasmaucbuebugbuiaukaweawgawiayeaygayiaAfaAh") +r(5264, "Stairs 2", layout="0aaadacaaedagaaidakacadccacedcgacidckbeadecbeedegbeidekbgacgcbgecggbgicgkciacicciecigciicikckabkcckebkgckibkkdmabmcdmebmgdmibmkdoaaocdoeaogdoiaokdqaaqcdqeaqgdqiaqk") +r(5265, "Stairs 3", layout="0eaeeageaieakeamdcfdchdcjdclcegceicekbgabghbgjbgqaicaiiaioalfaliallhmibnaanibnqaocioiaoobpaapibpqhqiarfariarlaucauiauobwabwhbwjbwqcygcyicykdAfdAhdAjdAleCeeCgeCieCkeCm") +r(5266, "Stargate", layout="0hagobeabgobgobihcehcghcjoddadeodgadiodkhechelafcofcafgpfgafkofmhgbhgghgnahaohaphgahmohohiahilhipajaojavjcvjevjgvjivjkajoojphkavkaokdokfokhokjhkpalaolaClavlchlehlghlivlkolpalqhmavmaomdamfamhomjhmpanaonaCnavnchnehnivnkanopnphoavoaoodaofaohoojhopapaopaCpavpchpehpghpivpkoppapqhqavqaoqdoqfoqhoqjhqparaoravrcvrevrgvrivrkaroorphsahslhspataotaptgatmotohubhughunavcovcavgpvgavkovmhwchwloxdaxeoxgaxioxkhyehyghyjozeazgozgozihAg") +# +r(5267, "Sukis", layout="0aaaaacaaeaagaaiaakaamaaoaaqhbbhbfhbjhbnacaaccaceacgaciackacmacoacqafaafcafeafgafiafkafmafoafqhgbhgpahaahcaheahgahiahkahmahoahqakahkbakcakeakgakiakkakmakoakqhlpamaamcameamgamiamkammamoamqapaapcapeapgapiapkapmapoapqhqbhqparaarcareargariarkarmaroarqauaaucaueaugauiaukaumauoauqhvpawahwbawcaweawgawiawkawmawoawqazaazcazeazgaziazkazmazoazqhAbhApaBaaBcaBeaBgaBiaBkaBmaBoaBqaEaaEcaEeaEgaEiaEkaEmaEoaEqhFbhFfhFjhFnaGaaGcaGeaGgaGiaGkaGmaGoaGq") +# +r(5268, "Temple 1", layout="0aaaaaeaaiabchbdabghbhacahcboccacehcfocgaciadchddodeadghdhaeaheboecaeehefoegaeiafchfdpfeafghfhagahgbogcagehgfoggagiahchhdvhdohevhfahghhhaiahiboicaiehifoigaiiajchjdvjdojeCjevjfajghjhakahkbokcakehkfokgakialchldvldoleClevlfalghlhamahmbomcamehmfomgamianchndvndoneCnevnfanghnhaoahoboocaoehofoogaoiapchpdvpdopevpfapghphaqahqboqcaqehqfoqgaqiarchrdprearghrhasahsboscasehsfosgasiatchtdoteatghthauahuboucauehufougauiavchvdavghvhawaaweawi") +# +r(5269, "Temple 2", layout="0aacaagaakabahbbabehbfabihbjaccocchcdacgocghchackadahdbadeodehdfadiodihdjaecoechedaegoeghehaekafahfbafeofehffafiofihfjagcogchgdaggpgghghagkahahhbahephehhfahiohihhjaicoichidaigpighihaikajahjbajepjehjfajiojihjjakcokchkdakgpkghkhakkalahlbaleplehlfaliolihljamcomchmdamgpmghmhamkanahnbaneonehnfanionihnjaocoochodaogooghohaokapahpbapeopehpfapiopihpjaqcoqchqdaqgoqghqhaqkarahrbareorehrfariorihrjaschsdasghshaskataateati") +r(5270, "Totally Random-Made", layout="0aaevajaaoabbhbhobioceCceacgaclCclpcmhddvddwdhhdmoecaedCedoeghejoenhffafgCfhafjofjCfkvfmagaCgfvggvgjhgkCgnahfohhChjahlohlahoaibhichieaihvihhiiojcajdojeCjiojjwjlojookmhkovkoClcClfvlgolhvliCljplkhlmvlmalpClpvmdhmghmjammCmmCncondCnganhCniankhodvodaoeCoehofhohvohvokoolvomaooCphopibplCpnvqharahreorfCrharkhrlormasfashvsiCsjhtfCthotiatjptnhtoaucoufhuhvuhauohvbavgovjCvjavkvvkhwghwjawnawpbxdaxjoxkayfaymayoaCdaCiaEiaFe") +r(5271, "Trika", layout="0hagaahiaiaajhakabfablhceicihcmaddoddodfadhvdhCdiadjvdjodladnodnheeieihemaffaflhggaghigiagjhgkciiakgokghkhakiokihkjakkokkhlfhllameomeamiammommhndhnnaocoocaogaoiaokaooooohpbhphhpjhppaqapqaaqehqfaqioqihqlaqmaqqpqqhrbhrhhrjhrpascoscasgasiaskasoosohtdhtnaueoueauiaumoumhvfhvlawgowghwhawiowihwjawkowkcyihAgaAhiAiaAjhAkaBfaBlhCeiCihCmaDdoDdoDfaDhvDhCDiaDjvDjoDlaDnoDnhEeiEihEmaFfaFlhGgaGhiGiaGjhGk") +r(5272, "Twin", layout="0aaeaagaaibccbcebcgbcibckaeabecceecegceibekaemagabgccgedggcgibgkagmaiabicciecigciibikaimbkcbkebkgbkibkkbmebmgbmibocboebogboibokaqabqccqecqgcqibqkaqmasabsccsedsgcsibskasmauabuccuecugcuibukaumbwcbwebwgbwibwkayeaygayi") +# +r(5273, "Two Domes", layout="0aaiabghbiabkacehcghckacmhdeodhodjhdmaecoefveioelaeohfdvfgvfkhfnagbogeCghCgjogmagphhcvhfvhlhhoaiaoidCigCikoinaiqhjcvjfajhvjlhjoakbokeCkhCkjokmakphldvlgvlkhlnamcomfvmiomlamohneonhonjhnmaoehoghokaomapghpiapkaqehqgoqhaqivqioqjhqkaqmarghriarkasehsghskasmhteothotjhtmaucoufvuioulauohvdvvgvvkhvnawboweCwhCwjowmawphxcvxfvxlhxoayaoydCygCykoynayqhzcvzfazhvzlhzoaAboAeCAhCAjoAmaAphBdvBgvBkhBnaCcoCfvCioClaCohDeoDhoDjhDmaEehEghEkaEmaFghFiaFkaGi") +# +r(5274, "Vagues", layout="0aacCaeaagCaiaakCamhbcvbehbgvbihbkvbmoccoceocgociockocmvdchdevdghdivdkhdmCecaeeCegaeiCekaemvfchfevfghfivfkhfmagaogcogeoggogiogkogmagohhahhcvhehhgvhihhkvhmhhooiaaicCieaigCiiaikCimoiovjahjcvjehjgvjihjkvjmvjoCkaokcokeokgokiokkokmCkovlavlchlevlghlivlkhlmvloomaCmcameCmgamiCmkammomohnavnchnevnghnivnkhnmhnoaoaoocooeoogooiookoomaoohpcvpehpgvpihpkvpmaqcCqeaqgCqiaqkCqmhrcvrehrgvrihrkvrmoscoseosgosioskosmvtchtevtghtivtkhtmCucaueCugauiCukaum") +r(5275, "Well2", layout="0aaaaacaaeaagaaiaakaamaaoacaccccceccgccicckccmacoaeadecdeedegdeidekdemaeoagadgcdgedgkdgmagoaiadicdiedikdimaioakadkcdkedkgdkidkkdkmakoamacmccmecmgcmicmkcmmamoaoaaocaoeaogaoiaokaomaoo") +# +r(5276, "Whatever", layout="0oaeoaghbdhbfhbhhcbaceoceacgocghcjadcadiheboeeoeghejafcafihgbogeogghgjahcwhfahioiahiboicoieoigoiihijoikajcvjdwjfvjhajiokahkbokcCkdokeokgCkhokihkjokkalcvldwlfvlhaliomahmbvmbomcCmdomeomgCmhomihmjvmjomkancvndwnfvnhaniooahobvoboocCodooeoogCohooihojvojookapcvpdwpfvphapioqahqboqcCqdoqeoqgCqhoqihqjoqkarcvrdwrfvrhariosahsboscoseosgosihsjoskatcwtfatihuboueoughujavcavihwboweowghwjaxcaxihybayeoyeaygoyghyjhzdhzfhzhoAeoAg") +r(5277, "Win", layout="0aaeaahaakaanbedbegbejbembepbhebhhbhkbhnbhqbjdbjgbjjbjmbjpbmcbmebmgbmibmkbmmbmococcoicoocqbcqhcqncsbcshcsncuacuccuecugcuicukcumcwbcwhcwncybcyhcyncAccAicAocCccCecCgcCicCkcCmcCo") +r(5278, "X-Files", layout="0aaaaaiaaqhbiacbacgaciociackacphdibecaeiaeoegdegneieeiidimdkfcklekpelbbmgbmkaocbohooibojaooaqahqbaqcoqchqdaqeaqiaqmhqnaqooqohqpaqqascbshosibsjasobugbukewbcwfcwlewpdyeeyidymeAdeAnaCcaCiaCohDiaEbaEgaEioEiaEkaEphFiaGaaGiaGq") +r(5279, "X-Shape", layout="0aaibbabbqcdabdcbdocdqaeicfacfcbfebfmcfocfqchabhcchebhgbhkchmbhochqbjabjecjgbjicjkbjmbjqblgdliblkbnabnecngbnicnkbnmbnqcpabpccpebpgbpkcpmbpocpqcracrcbrebrmcrocrqasictabtcbtoctqbvabvqawi") + diff --git a/pysollib/games/mahjongg/mahjongg3.py b/pysollib/games/mahjongg/mahjongg3.py new file mode 100644 index 0000000000..8d7c379741 --- /dev/null +++ b/pysollib/games/mahjongg/mahjongg3.py @@ -0,0 +1,87 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +from mahjongg import r + +# test +#r(5991, "AAA 1", ncards=4, layout="0daa") +#r(5992, "AAA 2", ncards=8, layout="0daadca") +#r(5993, "AAA 3", ncards=20, layout="0daaCabdacKbbdcaCcbdcc") +#r(5994, "AAA 4", ncards=20, layout="0daaDabdacdcaDcbdcc") + +# /*********************************************************************** +# // game definitions +# ************************************************************************/ + +r(5401, "Taipei", layout="0aagabbabdabjablhccacfachhckadbaddhdehdghdiadjadlhecaefoegaehhekafcifehfgvfgifiafkagahgcageaggoggagihgkagmhhaahcohehhfvhfhhhvhhohiahkhhmaiahidaieaigoigCigaiihijaimhjbajcojehjfvjfJjghjhvjhojiajkhjlakahkdakeakgokgCkgQkgakihkjakmhlbalcolehlfvlfJlghlhvlholialkhllamahmdameamgomgCmgamihmjammhnaanconehnfvnfhnhvnhoniankhnmaoahocaoeaogoogaoihokaomapcipehpgvpgipiapkhqcaqfoqgaqhhqkarbardhrehrghriarjarlhscasfashhskatbatdatjatlaug") +r(5402, "Hare", layout="0aacaamacabccaceackbcmacobeacecbeebekcembeoofavfcofeofkvfmofobgacgcbgebgkcgmbgoaiabicbiebikbimaioakcakebkhakkakmamebmgbmiamkbogoohboicqfcqhcqjasejsfasgjshasijsjaskCtgCtibuddufduhdujbulovdCvgCviovlbwddwfdwhdwjbwlcyfcyhcyjbAhbCh") +r(5403, "Horse", layout="0bafbahbajbcdbchbclaedbefbehbejaelagfaghagjaifhigaihhiiaijakfhkgakhhkiakjbmecmgcmibmkcodcofcohcojcolcqdcqfvqgcqhvqicqjcqlbsbcsfvsgcshvsicsjbsnotbotnbubcudcufvugcuhvuicujculbunbwbcwdcwfcwhcwjcwlbwnbycayfbyhayjbymaAbaAnaCaaCo") +r(5404, "Rat", layout="0aaabacoadbaeaagbcacccccebcgvddodgbeacecceebegagabgcbggagmbicbieaigaimckeckgckiakmblcblkcmevmfcmgvmhcmibmmamobncCngbnkhnocoevofcogvohcoibomaoobpcbpkcqecqgcqiaqmbscbseasgasmauabucbugaumbwacwccwebwgvxdoxgbyacyccyebygaAabAcoAdbAeaAg") +r(5405, "Tiger", layout="0baabacbambaobcabccbcmbcobebaeghehaeibenbgbbggbgibgnaibbidcifcihdijbilainakdhkeakfokfhkgakhpkhhkiakjokjhkkaklbmepmfbmgomhbmiomjbmkboeoofbogoohboipojbokbqeoqfbqgpqhbqioqjbqkbsddsfcshcsjbslbubbudbuhbulbunbwbbwibwnbybbygbynbAbbAibAnbCbbCgbCn") +r(5406, "Ram", layout="0aacaaeaagaaihbehbghbibccaceoceacgaciociackadaodchdehdihdkheabecaeepeeaeioeiaekafaofchfehfihfkhgabgcageogeaggagiogiagkahahhehhghhibicaieaigaiibkcblgbmcbmeamionehniankanmcocboevoebogaoiooihokhombooopehpiapkapmbqcbqeaqibrgbscbucaueaugauiavahvehvghvihwabwcaweoweawgawiowiawkaxaoxchxehxihxkhyabycayepyeayioyiaykazaozchzehzihzkbAcaAeoAeaAgaAioAiaAkhBehBghBiaCcaCeaCgaCi") +r(5407, "Wedges", layout="0aagbaicakdamacaacibckccmbeaaecaekbemcgabgcageagmdiacicbieaigekadkcckebkgakiakohlofmaemcdmecmgbmiamkammamoomohnoeoaeocdoecogaoiaoodqadqccqeaqgcsacscaseasmbuaaucaukbumawaawibwkcwmaygbyicykdym") +r(5408, "Monkey", layout="0aaahabaacoachadaaeaakbcaaceackhclacmocmhcnacoodabeabeoofoagahgbagcaghbgobicbigbiiaimhinaioojgbkcdkebkgvkgdkibkkbkmolgdmebmgvmgdmiongdoebogvogdoiaokholaomaooopghpobqcdqebqgvqgdqiaqooqoorghroasahsbascbsgasmasoauaaughuhauiawihwjawkowkhwlawmbymaAchAdaAeoAehAfvAfaAgoAgCAghAhvAhaAioAiCAihAjvAjaAkoAkhAlaAmaCahCbaCc") +r(5409, "Rooster", layout="0aaaaagabchcccceccgadcvdfadiceecegaeohfoageagoogohhoaiehifaigaimaiohjmbkeokfbkgokhbkiakkakmamccmevmfcmgvmhcmiamkanahncCnghoaaocooccoevofcogvohcoiapaopahpchqaaqcoqcbqeoqfbqgvqgoqhbqiaqkaqmaraorahrchrmhsaascbsgoshbsiaskasmasoataotahtohuaaufhugauhauoavabweowfbwgowhbwivxgayabycoydbyeoyfbygoyhbyihzaaAaaAeaAjhAkaAlhBaaCaaCehCfaCgaCl") +r(5410, "Dog", layout="0aaeaaghbehbgaccaceoceacgocgaciackhdchdehdghdihdkaecoecaeeaegaeiaekhfcagcaichidaieoiehifaigvjebkackcckeckgbkibkkvlcoliolkbmacmccmgbmibmkamoonavnconkboacoccoecogbokaomaooopavpcopkbqacqccqgbqibqkvrcoriorkbsacsccsecsgbsibskvteauchudaueouehufaugawchxcaycoycayeaygayiaykhzchzehzghzihzkaAcaAeoAeaAgoAgaAiaAkhBehBgaCeaCg") +r(5411, "Snake", layout="0bagbaiobhbcgbcibdebecbegbfebgcbhabicbiicikcimbjavjlbkcbkebkgbkickkckmakooleolgolivllhlobmcbmebmgbmicmkcmmamoomovnlhnocokcomaooooovplhpobqcbqebqgbqicqkcqmaqooreorgorivrlbscbsebsgbsicskcsmbtabucbvabwcbwebwgbwibwkbycbyebygbyibykbAjaCj") +r(5412, "Boar", layout="0aacaaehafaagoaghahaaiaakhbchbkaccoccaciackockacmhdchdkhdmaecaeeaekoekaemoemhfkhfmagiagkogkagmhhkaiiaikakcbkgbkiakmolgolibmcbmebmgbmibmkbmmonconepngpnionkonmanoaoabocvocboevoebogboibokvokbomvomhooopcopeppgppiopkopmapobqcbqebqgbqibqkbqmorgoriascbsgbsiasmauiaukhvkawiawkowkawmhxkhxmaycayeaykoykaymoymhzchzkhzmaAcoAcaAiaAkoAkaAmhBchBkaCcaCehCfaCgoCghChaCiaCk") +r(5413, "Ox", layout="0aahabeabkbcgochbciaeaaecbegbeiaemaeohfbhfnagaagcagebggbgiagkagmagoaicbiebigbiibikaimakcbkeckgckibkkakmbmecmgcmibmkaodioeaofjogaohjoiaojiokaolcqedqgdqicqkcsedsgdsicskaucbuecugcuibukaumawcbwecwgcwibwkawmayaaycayebygbyiaykaymayohzbhznaAaaAcaAhaAmaAo") +r(5414, "Bridge 2", layout="0daadacdaedagdaidakdamdaoccccceccgccicckccmbeebegbeibekaggagiaihhjhakhokhhlhvlhamfamhomhCmhhnhvnhJnhanjaofaohoohCohhphvphaqhoqhhrhashaugauibwebwgbwibwkcyccyecygcyicykcymdAadAcdAedAgdAidAkdAmdAo") + +##---------------------------------------------------------------------- + +#r(5501, "Big X", layout="0aacaamhbchbmacboccacdaclocmacnhdbhddhdlhdnaeaaecoedaeeaekoelaemaeohfchfehfkhfmagbagdogeagfagjogkaglagnhhdhhfhhjhhlaicaieoifaigaiioijaikaimhjehjghjihjkakdakfokgakhokiakjaklhlfhlhhljameamgomgamiomiamkhnfhnhhnjaofoofaohoohaojoojhpfhphhpjaqeaqgoqgaqioqiaqkhrfhrhhrjasdasfosgashosiasjaslhtehtghtihtkaucaueoufaugauioujaukaumhvdhvfhvjhvlawbawdoweawfawjowkawlawnhxchxehxkhxmayaaycoydayeaykoylaymayohzbhzdhzlhznaAboAcaAdaAloAmaAnhBchBmaCcaCm") +#r(5502, "Axis", layout="0bafcahbajbbdvbhbblcchCchbdcvdhbdmcehCehbfbvfhbfncghahaahohiahioajabjhajohkabkfbkjhkoalabldbllalohmacmhhmoanaancvnhanmanoiobcohionapbwphapniqbcqhiqnaraarcvrharmarohsacshhsoatabtdbtlatohuabufbujhuoavabvhavohwahwoaxaaxocyhbzbvzhbzncAhCAhbBcvBhbBmcChCChbDdvDhbDlbEfcEhbEj") +#r(5503, "Cobweb", layout="0aacaafhagaahoahhaiaajaamacbhccacdaclhcmacnadfhdgadhodhhdiadjaeaaeohfaafcafeafhafkafmhfoagaogaagoogohhaahcahhahmhhoaiaoiaaiooiohjaajdajhajlhjoakaakoalealhalkamaamoancanfhnganhhnianjanmaoahoboogooihonaooopbapcbpgvpgbpivpiapmopnaqahqboqgoqihqnaqoarcarfhrgarhhriarjarmasaasoateathatkauaauohvaavdavhavlhvoawaowaawoowohxaaxcaxhaxmhxoayaoyaayooyohzaazcazeazhazkazmhzoaAaaAoaBfhBgaBhoBhhBiaBjaCbhCcaCdaClhCmaCnaEcaEfhEgaEhoEhhEiaEjaEm") +#r(5504, "Pyramids", layout="0aaaaacaakaamhbbabeabgabihblacaaccackacmhdbadeadgadihdlaeaaecaekaemaffhfgafhahbaheahiahlhibhiehiihilajbojbajdojeajfajhojiajjajlojlhkbvkbhkevkehkghkivkihklvklalbolbClbaldoleClealfolgalholiClialjallollCllhmbvmbhmevmehmgvmghmivmihmlvmlanbonbCnbandoneCneanfonganhoniCnianjanlonlCnlhobvobhoevoehoghoivoiholvolapbopbapdopeapfaphopiapjaploplhqbhqehqihqlarbareariarlatfhtgathauaaucaukaumhvbaveavgavihvlawaawcawkawmhxbaxeaxgaxihxlayaaycaykaym") +#r(5505, "Wicker", layout="0bafbakbbcbbhbbmbcebcjbdbbdgbdlbedbeibenbfabffbfkbgcbghbgmbhebhjbibbigbilbjdbjibjnbkabkfbkkblcblhblmbmebmjbnbbngbnlbodboibonbpabpfbpkbqcbqhbqmbrebrjbsbbsgbslbtdbtibtnbuabufbukbvcbvhbvmbwebwjbxbbxgbxlbydbyibynbzfbzkbAh") + +##---------------------------------------------------------------------- + +r(5801, "Faro", name="Double Mahjongg Faro", ncards=288, layout="0aaahabaachadaaeoaehafaagiahaaihajaakoakhalaamhanaaoobcvbhobmacbhccvccacdacgichCchaciaclhcmvcmacnodcCdcvdhodmCdmaebhecvecaedheeaefcehCehaejhekaelhemvemaenofcvfhofmbgcagfhggaghoghhgiagjbgmahaahohiahioajapjaajccjebjhcjkajmajopjohkahkcokhhkmhkoalaalcqlcalfhlgalhvlhhlialjalmqlmalohmcomhCmhhmmanbqncandhneanfbnhvnhanjhnkanlqnmannhocooeoohookhomapcppcCpdbpevpebphwphbpkvpkCplapmppmhqcoqeoqhoqkhqmarbqrcardhrearfbrhvrharjhrkarlqrmarnhscoshCshhsmataatcqtcatfhtgathvthhtiatjatmqtmatohuahucouhhumhuoavapvaavccvebvhcvkavmavopvohwahwoaxaaxobycayfhygayhoyhhyiayjbymozcvzhozmaAbhAcvAcaAdhAeaAfcAhCAhaAjhAkaAlhAmvAmaAnoBcCBcvBhoBmCBmaCbhCcvCcaCdaCgiChCChaCiaClhCmvCmaCnoDcvDhoDmaEahEbaEchEdaEeoEehEfaEgiEhaEihEjaEkoEkhElaEmhEnaEo") +#r(5802, "Big Square", name="Double Mahjongg Big Square", ncards=288, layout="0daadacdaedagdaidakdcadccdcedcgdcidckdeadecdeedegdeidekdgadgcdgedggdgidgkdiadicdiedigdiidikdkadkcdkedkgdkidkkdmadmcdmedmgdmidmkdoadocdoedogdoidokdqadqcdqedqgdqidqkdsadscdsedsgdsidskduaducduedugduidukdwadwcdwedwgdwidwk") +r(5803, "Two Squares", name="Double Mahjongg Two Squares", ncards=288, layout="0daadacdaedagdaidakdcadccdcedcgdcidckdeadecdeedegdeidekdgadgcdgedggdgidgkdiadicdiedigdiidikdkadkcdkedkgdkidkkdoadocdoedogdoidokdqadqcdqedqgdqidqkdsadscdsedsgdsidskduaducduedugduidukdwadwcdwedwgdwidwkdyadycdyedygdyidyk") +#r(5804, "Rows", name="Double Mahjongg Rows", ncards=288, layout="0daadacCaddaeCafdagCahdaidakdcadckeeadeceeeeegdeieekegaegkeiadiceieeigdiieikekaekkemadmcemeemgdmiemkeoaeokeqadqceqeeqgdqieqkesaeskeuaduceueeugduieukewaewkeyadyceyeeygdyieykdAadAkdCadCcCCddCeCCfdCgCChdCidCk") +r(5805, "Twin Picks", name="Double Mahjongg Twin Picks", ncards=288, layout="0aacaaeaagaaiaakaamhbdhbfhbhhbjhblacaaccaceoceacgocgaciociackockacmacohdbhddhdfvdfhdhvdhhdjvdjhdlhdnaeaaecoecaeeoeeaegoegCegaeioeiCeiaekoekaemoemaeohfbhfdvfdhffvffhfhvfhhfjvfjhflvflhfnagaagcogcageogeCgeaggoggCggagiogiCgiagkogkCgkagmogmagohhbhhdvhdhhfvhfhhhvhhhhjvhjhhlvhlhhnaiaaicoicaieoieaigoigCigaiioiiCiiaikoikaimoimaiohjbhjdhjfvjfhjhvjhhjjvjjhjlhjnakaakcakeokeakgokgakiokiakkokkakmakohldhlfhlhhljhllamcameamgamiamkammapaapcapeapgapiapkapmapoascaseasgasiaskasmhtdhtfhthhtjhtlauaaucaueoueaugougauiouiaukoukaumauohvbhvdhvfvvfhvhvvhhvjvvjhvlhvnawaawcowcaweoweawgowgCwgawiowiCwiawkowkawmowmawohxbhxdvxdhxfvxfhxhvxhhxjvxjhxlvxlhxnayaaycoycayeoyeCyeaygoygCygayioyiCyiaykoykCykaymoymayohzbhzdvzdhzfvzfhzhvzhhzjvzjhzlvzlhznaAaaAcoAcaAeoAeaAgoAgCAgaAioAiCAiaAkoAkaAmoAmaAohBbhBdhBfvBfhBhvBhhBjvBjhBlhBnaCaaCcaCeoCeaCgoCgaCioCiaCkoCkaCmaCohDdhDfhDhhDjhDlaEcaEeaEgaEiaEkaEm") +r(5806, "Roost", name="Double Mahjongg Roost", ncards=288, layout="0aaahabaacoachadvadaaeoaehafvafaagoaghahvahaaioaihajaakaamaaoCbfhblhbnacbhccacdocdhcevceacfocfhcgvcgachochhciacjaclocmacnhdkhdmaeiaekoelaemaeoafaafcafehfjhflvflhfnhgchgeaghagjogkaglCglogmagnahbohcahdoheahfhhihhkvhlhhmhibhidviehifaiioijaikoilaimajaajcojdajeCjeojfajghjjvjkhjlajohkcvkdhkevkfhkgakjokkaklalbolcaldolealfClfolgalhhlkblnhmbhmdvmehmfvmghmhamkomnanaancondaneonfangCngonhanianmhnnanohochoevofhogvohhoiapbapdopeapfopgaphCphopiapjhpkaploplhpmapnhqchqevqfhqgvqhhqiaraarcordareorfargCrgorhariarmhrnarohsbhsdvsehsfvsghshaskosnatbotcatdoteatfCtfotgathhtkbtnhucvudhuevufhugaujoukaulavaavcovdaveCveovfavghvjvvkhvlavohwbhwdvwehwfawiowjawkowlawmaxboxcaxdoxeaxfhxihxkvxlhxmhychyeayhayjoykaylCyloymaynazaazcazehzjhzlvzlhznaAiaAkoAlaAmaAohBkhBmaCbhCcaCdoCdhCevCeaCfoCfhCgvCgaChoChhCiaCjaCloCmaCnCDfhDlhDnaEahEbaEcoEchEdvEdaEeoEehEfvEfaEgoEghEhvEhaEioEihEjaEkaEmaEo") +r(5807, "Castle", name="Double Mahjongg Big Castle", ncards=288, layout="0eaadacdaeeageaidakdameaodcadcocddvdecdfvdgcdhCdhvdicdjvdkcdldeadeoafdaflcgacgoahdahlciacioajdajlckahkdhklckoaldelfblheljallcmahmdhmlcmoandbnfbnjanleoahodoofoojholeooapdbpfvpfbpjvpjapleqahqdoqfoqjhqleqoardbrfbrjarlcsahsdhslcsoatdetfbthetjatlcuahudhulcuoavdavlcwacwoaxdaxlcyacyoazdazldAadAocBdvBecBfvBgcBhCBhvBicBjvBkcBldCadCoeEadEcdEeeEgeEidEkdEmeEo") +r(5808, "Eight Squares", name="Double Mahjongg Eight Squares", ncards=288, layout="0daadacdaedahdajdaldcadccdcedchdcjdcldeadecdeedehdejdeldhadhcdhedhhdhjdhldjadjcdjedjhdjjdjldladlcdledlhdljdlldoadocdoedohdojdoldqadqcdqedqhdqjdqldsadscdsedshdsjdsldvadvcdvedvhdvjdvldxadxcdxedxhdxjdxldzadzcdzedzhdzjdzl") +r(5809, "Big Flying Dragon", name="Double Mahjongg Big Flying Dragon", ncards=288, layout="0aajacaaciackacsaeaaegaeihejaekaemaesagaageaggbgibgkagmagoagsaiaaicaiebigbiibikbimaioaiqaisakabkcbkebkgbkibkkbkmbkobkqaksbmabmccmecmgcmicmkcmmcmobmqbmsboaboccoedogdoidokdomcooboqbosbqabqccqedqgeqieqkdqmcqobqqbqsJrjbsabsccsedsgesieskdsmcsobsqbssbuabuccuedugduidukdumcuobuqbusbwabwccwecwgcwicwkcwmcwobwqbwsayabycbyebygbyibykbymbyobyqaysaAaaAcaAebAgbAibAkbAmaAoaAqaAsaCaaCeaCgbCibCkaCmaCoaCsaEaaEgaEihEjaEkaEmaEsaGaaGiaGkaGsaIaaIjaIsaKj") +r(5810, "Sphere", name="Double Mahjongg Sphere", ncards=288, layout="0aajaalaanabhhbkhbmabpacfhciacjockaclocmacnhcoacraddhdgadhodivdkhdlvdmodoadphdqadtaefoegveihejaekoekaemoemhenveooeqaerafchfdhffhfhafiafohfphfrhftafuageogeaggpggpgihgjpgkbglpgmhgnpgoagqpgqagsogsahbhhchhfhhhahjahnhhphhrhhuahvaidoidvieaifoigaihoiihijoikbiloimhinoioaipoiqairvisaitoitajahjbhjdhjfhjhvjlhjphjrhjthjvajwakcokcvkdakeokeakgokgakiokiakkokkakmokmakookoakqokqaksoksvktakuokualahlbhldhlfvlfhlhvlhhljvljhllvllhlnvlnhlpvlphlrvlrhlthlvalwamcomcvmdameomeamgomgamiomiamkomkammommamoomoamqomqamsomsvmtamuomuanahnbhndhnfhnhvnlhnphnrhnthnvanwaodoodvoeaofoogaohooihojookboloomhonoooaopooqaorvosaotootapbhpchpfhphapjapnhpphprhpuapvaqeoqeaqgpqgpqihqjpqkbqlpqmhqnpqoaqqpqqaqsoqsarchrdhrfhrhariarohrphrrhrtaruasfosgvsihsjaskoskasmosmhsnvsoosqasratdhtgathotivtkhtlvtmotoatphtqattaufhuiaujoukauloumaunhuoauravhhvkhvmavpawjawlawn") + +##---------------------------------------------------------------------- + +r(5901, "Happy New Year", name="Half Mahjongg Happy New Year", ncards=72, layout="0aafaajaanaceaciacmbedbehaelofdofhhflbgdbghaglohdohhaibbidaighihaiiailhimainojmakaakeckhakjbkmbkoolmambbmdamghmhamiamlhmmamnondonhbodbohaolopdophhplbqdbqhaqlaseasiasmaufaujaun") +#r(5902, "K 2", name="Half Mahjongg K 2", ncards=72, layout="0aagabcabehbfobghbhabiabkacgvcgadbidgadlaegvegbfaifgbfmaggbhaihgbhmaigbjahjgbjmakgokgblahlgblmamgbnaingbnmaogbpaipgbpmaqgvqgarbirgarlasgvsgatcatehtfotghthatiatkaug") +#r(5903, "Abstract", name="Half Mahjongg Abstract", ncards=72, layout="0aaaaagabcabebddadgadioedhehafchfdafeafhagahhaahdahgaiahjaojbbjcajfakaalcamfamhanbhncandhngaogboiapdhqdaqiarcordarehrihsdasgasiatdauaaufhvbavcaviawaawehxeaxiaycayebyghzdaAdaAhaBbaBfhCfaCiaDcaDeaDghDhaEaaEi") +r(5904, "Smile", name="Half Mahjongg Smile", ncards=72, layout="0bagoahbaibbebbkbccbcmbebbenaffbfjbgahgfbgoahfbhkbiabiobjlbkabkobllbmabmoanfbnkboahofbooapfbpjbqbbqnbscbsmbtebtkbugouhbui") +r(5905, "Wall", name="Half Mahjongg Wall", ncards=72, layout="0eaabacbaebagbaibakbameaoacaacoaeaaeoagaagoaiaaioakaakoamaamoaoaaooaqaaqoasaasoauaauoawaawoayaayoaAaaAoaCaaCoeEabEcbEebEgbEibEkbEmeEo") + +##---------------------------------------------------------------------- + +#r(5601, "Skomoroh 1", ncards=28, layout="0aacaaeaaghbdhbfacaacdoceacfacihddhdfaebaeeoeeaehhfdhffagaagdogeagfagihhdhhfaicaieaig") +#r(5602, "Skomoroh 2", ncards=116, layout="0aaeaaghahaaiaakabaaboacfbchacjadaadoaeghehaeiafaafocghahaahcahfvhhahjahmahohidcihhilajaajdajfwjhajjajlajohkdhkgakhokhhkihklalaalcalewlhalkalmalohmfamgimhamihmjanaancanewnhankanmanohodhogaohoohhoiholapaapdapfwphapjaplapohqdcqhhqlaraarcarfvrharjarmarocshataatoaughuhauiavaavoawfbwhawjaxaaxoayeayghyhayiayk") +#r(5603, "Skomoroh 3", ncards=132, layout="0aachadaaeoaeXaehafyafaagoagXaghahaaiabaabkhcahckadaadeadgadkheahefhekafaafeafgafkhgahgfhgkahaaheahgahkhiahifhikajaajeajgajkhkahkfhkkalaalealgalkhmahmfhmkanaaneonfangankhofXofapbapdapfspfaphapjhqfXqfaraareorfargarkhsahsfhskataateatgatkhuahufhukavaaveavgavkhwahwfhwkaxaaxeaxgaxkhyahyfhykazaazeazgazkhAahAfhAkaBaaBeaBgaBkhCahCkaDaaDkaEchEdaEeoEeXEehEfyEfaEgoEgXEghEhaEi") +#r(5604, "Skomoroh 4", ncards=52, layout="0aajaalaanabhabpacfacnacraddadladtaejafcafuagiahbbhoahvaiiajaajwakjalaalwamkammanaanwaonapaapwaqoarbbriarvasoatcatuaunavdavlavtawfawjawraxhaxpayjaylayn") +#r(5605, "Skomoroh 5", ncards=208, layout="0aahaajaalaanaaphbihbkoblhbmhboaccaceacgaciackacmacoacqacsacuaecaeuagdagjaglagnagthhkhhmaieaijailoilainaishjkhjmakfakjakloklaknakrhlkhlmameamgamjamlomlamnamqamsanchndhnkhnmhntanuaoeaohaojaoloolaonaopaosapchpdhpkhpmhptapuaqeaqhaqjaqlaqnaqpaqsaraarchrdhrtaruarwaseasgasiaskasmasoasqassatahtbatchtdhtfithitjitlitnitphtrhttatuhtvatwaueaugauiaukaumauoauqausavaavchvdhvtavuavwaweawhawjawlawnawpawsaxchxdhxkhxmhxtaxuayeayhayjayloylaynaypaysazchzdhzkhzmhztazuaAeaAgaAjaAloAlaAnaAqaAshBkhBmaCfaCjaCloClaCnaCrhDkhDmaEeaEjaEloElaEnaEshFkhFmaGdaGjaGlaGnaGtaIcaIuaKcaKeaKgaKiaKkaKmaKoaKqaKsaKuhLihLkoLlhLmhLoaMhaMjaMlaMnaMp") +#r(5606, "Skomoroh 6", layout="0aadaafaahaajaalaanaapadaaddadfadhadjadladnadpadsheehegheihekhemheoafaafdaffoffafhofhafjofjafloflafnofnafpafshgehggvgghgivgihgkvgkhgmvgmhgoahaChhChjChlahsaidaifoifaihoihJiiaijoijJikailoilainoinaipajahjehjgvjgCjhhjivjihjkvjkCjlhjmvjmhjoajsakdakfokfakhokhJkiakjokjJkkakloklaknoknakpalaClhCljCllalshmehmgvmghmivmihmkvmkhmmvmmhmoanaandanfonfanhonhanjonjanlonlannonnanpanshoehoghoihokhomhooapaapdapfaphapjaplapnappapsasdasfashasjaslasnasp") +#r(5607, "Skomoroh 7", ncards=56, layout="0aabaadaafaahaajaapaaraatablabwadaadmadwafaafnafwahaahnahwajfajhajmajwakdakjalbdllalvamnamtanaankanpanrapaapjapwaraarjarwataatkatwavaavlawdawfawhawnawpawrawtawv") + diff --git a/pysollib/games/mahjongg/shisensho.py b/pysollib/games/mahjongg/shisensho.py new file mode 100644 index 0000000000..5a231de3b2 --- /dev/null +++ b/pysollib/games/mahjongg/shisensho.py @@ -0,0 +1,468 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys +#from tkFont import Font + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText, MfxCanvasLine + +from mahjongg import Mahjongg_RowStack, AbstractMahjonggGame, comp_cardset + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Shisen_Hint(AbstractHint): + # FIXME: no intelligence whatsoever is implemented here + def computeHints(self): + game = self.game + # get free stacks + stacks = [] + for r in game.s.rows: + if r.cards: + stacks.append(r) + # find matching tiles + i = 0 + for r in stacks: + for t in stacks[i+1:]: + #if game.cardsMatch(r.cards[0], t.cards[0]): + if r.acceptsCards(t, t.cards): + # simple scoring... + score = 1000 + r.rown + t.rown + self.addHint(score, 1, r, t) + i = i + 1 + + +# /*********************************************************************** +# // Shisen-Sho +# ************************************************************************/ + + +class Shisen_Foundation(AbstractFoundationStack): + def __init__(self, x, y, game, suit=ANY_SUIT, **cap): + kwdefault(cap, max_move=0, max_accept=0, max_cards=game.NCARDS) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + # We do not accept any cards - pairs will get + # delivered by _dropPairMove() below. + return 0 + + def basicIsBlocked(self): + return 1 + + def initBindings(self): + pass + + +class Shisen_RowStack(Mahjongg_RowStack): + + def basicIsBlocked(self): + return 0 + + def acceptsCards(self, from_stack, cards): + if not self.game.cardsMatch(self.cards[0], cards[-1]): + return 0 + + cols, rows = self.game.L + game_cols = self.game.cols + x1, y1 = self.coln+1, self.rown+1 + x2, y2 = from_stack.coln+1, from_stack.rown+1 + dx, dy = x2 - x1, y2 - y1 + + a = [] + for i in xrange(cols+2): + a.append([5]*(rows+2)) + + def can_move(x, y, nx, ny, direct, d, direct_chng_cnt): + if nx == x2 and ny == y2: + return 1 + if nx < 0 or ny < 0 or nx > cols+1 or ny > rows+1: + return 0 + if nx in (0, cols+1) or ny in (0, rows+1) \ + or not game_cols[nx-1][ny-1].cards: + if direct_chng_cnt == 0: + return 1 + elif direct_chng_cnt == 1: + if direct != d: + if d == 1 and dy > 0: + return 1 + elif d == 2 and dy < 0: + return 1 + elif d == 3 and dx > 0: + return 1 + elif d == 4 and dx < 0: + return 1 + else: + return 1 + elif direct_chng_cnt == 2: + if direct != d: + if d in (1, 2) and x == x2: + return 1 + elif y == y2: + return 1 + else: + if d == 1 and y < y2: + return 1 + elif d == 2 and y > y2: + return 1 + elif d == 3 and x < x2: + return 1 + elif d == 4 and x > x2: + return 1 + elif direct_chng_cnt == 3: + if direct == d: + return 1 + + return 0 + + res_path = [None] + def do_accepts(x, y, direct, direct_chng_cnt, path): + #if direct_chng_cnt > 3: + # return + if a[x][y] < direct_chng_cnt: + return + #if res_path[0]: + # return + a[x][y] = direct_chng_cnt + if x == x2 and y == y2: + res_path[0] = path + return + + if can_move(x, y, x, y+1, direct, 1, direct_chng_cnt): #### 1 + #dcc = direct == 1 and direct_chng_cnt or direct_chng_cnt+1 + p = path[:] + if direct == 1: + dcc = direct_chng_cnt + else: + dcc = direct_chng_cnt+1 + p.append((x, y)) + do_accepts(x, y+1, 1, dcc, p) + if can_move(x, y, x, y-1, direct, 2, direct_chng_cnt): #### 2 + #dcc = direct == 2 and direct_chng_cnt or direct_chng_cnt+1 + p = path[:] + if direct == 2: + dcc = direct_chng_cnt + else: + dcc = direct_chng_cnt+1 + p.append((x, y)) + do_accepts(x, y-1, 2, dcc, p) + if can_move(x, y, x+1, y, direct, 3, direct_chng_cnt): #### 3 + #dcc = direct == 3 and direct_chng_cnt or direct_chng_cnt+1 + p = path[:] + if direct == 3: + dcc = direct_chng_cnt + else: + dcc = direct_chng_cnt+1 + p.append((x, y)) + do_accepts(x+1, y, 3, dcc, p) + if can_move(x, y, x-1, y, direct, 4, direct_chng_cnt): #### 4 + #dcc = direct == 4 and direct_chng_cnt or direct_chng_cnt+1 + p = path[:] + if direct == 4: + dcc = direct_chng_cnt + else: + dcc = direct_chng_cnt+1 + p.append((x, y)) + do_accepts(x-1, y, 4, dcc, p) + + do_accepts(x1, y1, 0, 0, []) + #from pprint import pprint + #pprint(a) + + if a[x2][y2] > 3: + return None + + res_path = res_path[0] + res_path.append((x2, y2)) + #print res_path + return res_path + + return a[x2][y2] < 4 + + + def fillStack(self): + self.game.fillStack(self) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 and to_stack in self.game.s.rows + if to_stack.cards: + self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow) + else: + Mahjongg_RowStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + f = game.s.foundations[0] + game.updateStackMove(game.s.talon, 2|16) # for undo + if not game.demo: + if game.app.opt.shisen_show_hint: + self.drawArrow(other_stack, game.app.opt.hint_sleep) + game.playSample("droppair", priority=200) + # + game.moveMove(n, self, f, frames=frames, shadow=shadow) + game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + self.fillStack() + other_stack.fillStack() + game.updateStackMove(game.s.talon, 1|16) # for redo + game.leaveState(old_state) + + + def drawArrow(self, other_stack, sleep): + game = self.game + path = self.acceptsCards(other_stack, [other_stack.cards[-1]]) + #print path + x0, y0 = game.XMARGIN, game.YMARGIN + #print x0, y0 + images = game.app.images + cs = game.app.cardset + if cs.version >= 6: + cardw = images.CARDW-cs.SHADOW_XOFFSET + cardh = images.CARDH-cs.SHADOW_YOFFSET + else: + cardw = images.CARDW + cardh = images.CARDH + coords = [] + for x, y in path: + if x == 0: + coords.append(6) + elif x == game.L[0]+1: + coords.append(x0+cardw*(x-1)+10) + else: + coords.append(x0+cardw/2+cardw*(x-1)) + if y == 0: + coords.append(6) + elif y == game.L[1]+1: + coords.append(y0+cardh*(y-1)+6) + else: + coords.append(y0+cardh/2+cardh*(y-1)) + #print coords + ##s1 = min(cardw/2, cardh/2, 30) + ##w = min(s1/3, 7) + ##s2 = min(w, 10) + w = 7 + arrow = MfxCanvasLine(game.canvas, + coords, + {'width': w, + 'fill': game.app.opt.hintarrow_color, + ##'arrow': 'last', + ##'arrowshape': (s1, s1, s2) + } + ) + game.canvas.update_idletasks() + game.sleep(sleep) + if arrow is not None: + arrow.delete() + game.canvas.update_idletasks() + + +class AbstractShisenGame(AbstractMahjonggGame): + Hint_Class = Shisen_Hint + RowStack_Class = Shisen_RowStack + + #NCARDS = 144 + GRAVITY = True + + def createGame(self): + cols, rows = self.L + assert cols*rows == self.NCARDS + + # start layout + l, s = Layout(self), self.s + ##dx, dy = 3, -3 + + cs = self.app.cardset + if cs.version >= 6: + dx = l.XOFFSET + dy = -l.YOFFSET + d_x = cs.SHADOW_XOFFSET + d_y = cs.SHADOW_YOFFSET + else: + dx = 3 + dy = -3 + d_x = 0 + d_y = 0 + + font = self.app.getFont("canvas_default") + + # width of self.texts.info + #ti_width = Font(self.canvas, font).measure(_('Remaining')) + ti_width = 80 + + # set window size + dxx, dyy = abs(dx), abs(dy) + cardw, cardh = l.CW - d_x, l.CH - d_y + w = l.XM+dxx + cols*cardw+d_x + l.XM+ti_width+l.XM + h = l.YM+dyy + rows*cardh+d_y + l.YM + self.setSize(w, h) + self.XMARGIN = l.XM+dxx + self.YMARGIN = l.YM+dyy + + # set game extras + self.check_dist = l.CW*l.CW + l.CH*l.CH # see _getClosestStack() + + # + self.cols = [[] for i in xrange(cols)] + cl = range(cols) + if dx > 0: + cl.reverse() + for col in cl: + for row in xrange(rows): + x = l.XM + dxx + col * cardw + y = l.YM + dyy + row * cardh + stack = self.RowStack_Class(x, y, self) + stack.CARD_XOFFSET = 0 + stack.CARD_YOFFSET = 0 + stack.coln, stack.rown = col, row + s.rows.append(stack) + self.cols[col].append(stack) + #from pprint import pprint + #pprint(self.cols) + self.cols_len = [rows]*cols + + # create other stacks + y = l.YM + dyy + s.foundations.append(Shisen_Foundation(-l.XS, y, self)) + self.texts.info = MfxCanvasText(self.canvas, + self.width - l.XM - ti_width, y, + anchor="nw", font=font) + x = self.width + l.XS + y = self.height - l.YS - dxx + # the Talon is invisble + s.talon = InitialDealTalonStack(x, y + l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + def fillStack(self, stack): + if not self.GRAVITY: return + to_stack = stack + for from_stack in self.cols[stack.coln][stack.rown+1::-1]: + if not from_stack.cards: + continue + self.moveMove(1, from_stack, to_stack, frames=0) + to_stack = from_stack + + def updateText(self): + if self.preview > 1 or self.texts.info is None: + return + game = self.app.game + if 0: + # find matching tiles + stacks = game.s.rows + f, i = 0, 0 + for r in stacks: + i = i + 1 + if not r.cards: + continue + for t in stacks[i:]: + if not t.cards: + continue + if r.acceptsCards(t, t.cards): + f += 1 + if f == 0: + f = self.text_free_matching_pairs_0 + elif f == 1: + f = self.text_free_matching_pairs_1 + else: + f = str(f) + self.text_free_matching_pairs_2 + else: + f = '' + + t = len(self.s.foundations[0].cards) + t = str(t) + self.text_tiles_removed \ + + str(self.NCARDS - t) + self.text_tiles_remaining \ + + f + self.texts.info.config(text = t) + + + def drawHintArrow(self, from_stack, to_stack, ncards, sleep): + from_stack.drawArrow(to_stack, sleep) + + + def _shuffleHook(self, cards): + return cards + + +class Shisen_18x8(AbstractShisenGame): + L = (18, 8) + +class Shisen_14x6(AbstractShisenGame): + L = (14, 6) + NCARDS = 84 + +class Shisen_24x12(AbstractShisenGame): + L = (24, 12) + NCARDS = 288 + +class Shisen_18x8_NoGravity(AbstractShisenGame): + L = (18, 8) + GRAVITY = False + +class Shisen_14x6_NoGravity(AbstractShisenGame): + L = (14, 6) + NCARDS = 84 + GRAVITY = False + +class Shisen_24x12_NoGravity(AbstractShisenGame): + L = (24, 12) + NCARDS = 288 + GRAVITY = False + +# /*********************************************************************** +# // register a Shisen-Sho type game +# ************************************************************************/ + +def r(id, gameclass, short_name, name=None, decks=1, ranks=10, trumps=12): + if not name: + name = short_name + decks, ranks, trumps = comp_cardset(gameclass.NCARDS) + gi = GameInfo(id, gameclass, name, + GI.GT_SHISEN_SHO, 4*decks, 0, + category=GI.GC_MAHJONGG, short_name=name, + suits=range(3), ranks=range(ranks), trumps=range(trumps), + si={"decks": decks, "ncards": gameclass.NCARDS}) + gi.ncards = gameclass.NCARDS + gi.rules_filename = "shisensho.html" + registerGame(gi) + return gi + +r(11001, Shisen_14x6, "Shisen-Sho 14x6") +r(11002, Shisen_18x8, "Shisen-Sho 18x8") +r(11003, Shisen_24x12, "Shisen-Sho 24x12") +r(11004, Shisen_14x6_NoGravity, "Shisen-Sho (No Gra) 14x6") +r(11005, Shisen_18x8_NoGravity, "Shisen-Sho (No Gra) 18x8") +r(11006, Shisen_24x12_NoGravity, "Shisen-Sho (No Gra) 24x12") + +del r diff --git a/pysollib/games/matriarchy.py b/pysollib/games/matriarchy.py new file mode 100644 index 0000000000..ee7048263a --- /dev/null +++ b/pysollib/games/matriarchy.py @@ -0,0 +1,229 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Talon +# ************************************************************************/ + +class Matriarchy_Waste(WasteStack): + def updateText(self): + WasteStack.updateText(self) + if self.game.s.talon._updateMaxRounds(): + self.game.s.talon.updateText() + + +class Matriarchy_Talon(WasteTalonStack): + DEAL = (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 11, 10, 9, 8, 7, 6, 5) + + def _updateMaxRounds(self): + # recompute max_rounds + old = self.max_rounds + self.max_rounds = 11 + rows = self.game.s.rows + for i in (0, 2, 4, 6): + l1 = len(rows[i+0].cards) + len(rows[i+8].cards) + l2 = len(rows[i+1].cards) + len(rows[i+9].cards) + assert l1 + l2 <= 26 + if l1 + l2 == 26: + self.max_rounds = self.max_rounds + 2 + elif l1 >= 13 or l2 >= 13: + self.max_rounds = self.max_rounds + 1 + if self.max_rounds == 19: + # game is won + self.max_rounds = 18 + return old != self.max_rounds + + def canDealCards(self): + if self._updateMaxRounds(): + self.updateText() + if not self.cards and not self.game.s.waste.cards: + return 0 + ncards = self.DEAL[self.round-1] + assert ncards > 0 + return len(self.cards) >= ncards or self.round < self.max_rounds + + def dealCards(self, sound=0): + # get number of cards to deal + ncards = self.DEAL[self.round-1] + assert ncards > 0 + # init + waste = self.game.s.waste + n = 0 + update_flags = 1 + # deal + while n < ncards: + # from self to waste + while n < ncards: + card = self.getCard() + if not card: + break + assert not card.face_up + self.game.flipMove(self) + self.game.moveMove(1, self, waste, frames=3, shadow=0) + n = n + 1 + # turn from waste to self + if n < ncards and len(waste.cards) > 0: + assert len(self.cards) == 0 + assert self.round < self.max_rounds or update_flags == 0 + self.game.turnStackMove(waste, self, update_flags=update_flags) + # do not update self.round anymore in this deal + update_flags = 0 + assert self.round <= self.max_rounds + assert n == ncards + assert len(self.game.s.waste.cards) > 0 + # done + return n + + def updateText(self): + if self.game.preview > 1: + return + WasteTalonStack.updateText(self, update_rounds=0) + ## t = "Round %d" % self.round + t = _("Round %d/%d") % (self.round, self.max_rounds) + self.texts.rounds.config(text=t) + t = _("Deal %d") % self.DEAL[self.round-1] + self.texts.misc.config(text=t) + + +# /*********************************************************************** +# // Rows +# ************************************************************************/ + +class Matriarchy_UpRowStack(SS_RowStack): + def __init__(self, x, y, game, suit): + SS_RowStack.__init__(self, x, y, game, suit=suit, + base_rank=KING, mod=13, dir=1, + min_cards=1, max_cards=12) + self.CARD_YOFFSET = -self.CARD_YOFFSET + + def getBottomImage(self): + return self.game.app.images.getSuitBottom(self.cap.suit) + + +class Matriarchy_DownRowStack(SS_RowStack): + def __init__(self, x, y, game, suit): + SS_RowStack.__init__(self, x, y, game, suit=suit, + base_rank=QUEEN, mod=13, dir=-1, + min_cards=1, max_cards=12) + + def getBottomImage(self): + return self.game.app.images.getSuitBottom(self.cap.suit) + + +# /*********************************************************************** +# // Matriarchy +# ************************************************************************/ + +class Matriarchy(Game): + Hint_Class = CautiousDefaultHint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (set piles so that at least 2/3 of a card is visible with 12 cards) + h = max(2*l.YS, (12-1)*l.YOFFSET + l.CH*2/3) + self.setSize(10*l.XS+l.XM, h + l.YM + h) + + # create stacks + center, c1, c2 = self.height / 2, h, self.height - h + x, y = l.XM, c1 - l.CH + for i in range(8): + s.rows.append(Matriarchy_UpRowStack(x, y, self, i/2)) + x = x + l.XS + x, y = l.XM, c2 + for i in range(8): + s.rows.append(Matriarchy_DownRowStack(x, y, self, i/2)) + x = x + l.XS + x, y = x + l.XS / 2, c1 - l.CH / 2 - l.CH + tx = x + l.CW / 2 + s.waste = Matriarchy_Waste(x, y, self) + l.createText(s.waste, "ss") + y = c2 + l.CH / 2 + s.talon = Matriarchy_Talon(x, y, self, max_rounds=VARIABLE_REDEALS) + l.createText(s.talon, "nn") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + tx, y + l.YS, anchor="n", + font=self.app.getFont("canvas_default")) + s.talon.texts.misc = MfxCanvasText(self.canvas, + tx, center, anchor="center", + font=self.app.getFont("canvas_large")) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move Queens to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 11, c.suit), 8) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(self.s.rows[8:]) + self.s.talon.dealCards() # deal first cards to WasteStack + + def isGameWon(self): + return len(self.s.talon.cards) == 0 and len(self.s.waste.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if card1.rank + card2.rank == QUEEN + KING: + return 0 + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + +# register the game +registerGame(GameInfo(17, Matriarchy, "Matriarchy", + GI.GT_2DECK_TYPE, 2, VARIABLE_REDEALS)) + diff --git a/pysollib/games/montana.py b/pysollib/games/montana.py new file mode 100644 index 0000000000..4b29dee4ef --- /dev/null +++ b/pysollib/games/montana.py @@ -0,0 +1,407 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Montana_Hint(DefaultHint): + def computeHints(self): + game = self.game + RLEN, RSTEP, RBASE = game.RLEN, game.RSTEP, game.RBASE + freerows = filter(lambda s: not s.cards, game.s.rows) + # for each stack + for r in game.s.rows: + if not r.cards: + continue + assert len(r.cards) == 1 and r.cards[-1].face_up + c, pile, rpile = r.cards[0], r.cards, [] + if r.id % RSTEP > 0: + left = game.s.rows[r.id - 1] + else: + left = None + if c.rank == RBASE: + # do not move the leftmost card of a row if the rank is correct + continue + for t in freerows: + if self.shallMovePile(r, t, pile, rpile): + # FIXME: this scoring is completely simple + if left and left.cards: + # prefer low-rank left neighbours + score = 40000 + (self.K - left.cards[-1].rank) + else: + score = 50000 + self.addHint(score, 1, r, t) + + +# /*********************************************************************** +# // Montana +# ************************************************************************/ + +class Montana_Talon(TalonStack): + def canDealCards(self): + return self.round != self.max_rounds and not self.game.isGameWon() + + def dealCards(self, sound=0): + # move cards to the Talon, shuffle and redeal + game = self.game + RLEN, RSTEP, RBASE = game.RLEN, game.RSTEP, game.RBASE + num_cards = 0 + assert len(self.cards) == 0 + rows = game.s.rows + # move out-of-sequence cards from the Tableau to the Talon + stacks = [] + gaps = [None] * 4 + for g in range(4): + i = g * RSTEP + r = rows[i] + if r.cards and r.cards[-1].rank == RBASE: + in_sequence, suit = 1, r.cards[-1].suit + else: + in_sequence, suit = 0, NO_SUIT + for j in range(RSTEP): + r = rows[i + j] + if in_sequence: + if not r.cards or r.cards[-1].suit != suit or r.cards[-1].rank != RBASE + j: + in_sequence = 0 + if not in_sequence: + stacks.append(r) + if gaps[g] is None: + gaps[g] = r + if r.cards: + game.moveMove(1, r, self, frames=0) + num_cards = num_cards + 1 + assert len(self.cards) == num_cards + assert len(stacks) == num_cards + len(gaps) + if num_cards == 0: # game already finished + return 0 + if sound: + game.startDealSample() + # shuffle + game.shuffleStackMove(self) + # redeal + game.nextRoundMove(self) + spaces = self.getRedealSpaces(stacks, gaps) + for r in stacks: + if not r in spaces: + self.game.moveMove(1, self, r, frames=4) + # done + assert len(self.cards) == 0 + if sound: + game.stopSamples() + return num_cards + + def getRedealSpaces(self, stacks, gaps): + # the spaces are directly after the sorted sequence in each row + return gaps + + +class Montana_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + if self.id % self.game.RSTEP == 0: + return cards[0].rank == self.game.RBASE + left = self.game.s.rows[self.id - 1] + return left.cards and left.cards[-1].suit == cards[0].suit and left.cards[-1].rank + 1 == cards[0].rank + + def clickHandler(self, event): + if not self.cards: + return self.quickPlayHandler(event) + return BasicRowStack.clickHandler(self, event) + + # bottom to get events for an empty stack + prepareBottom = Stack.prepareInvisibleBottom + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class Montana(Game): + Talon_Class = StackWrapper(Montana_Talon, max_rounds=3) + RowStack_Class = Montana_RowStack + Hint_Class = Montana_Hint + + RLEN, RSTEP, RBASE = 52, 13, 1 + + def createGame(self): + # create layout + l, s = Layout(self, XM=4), self.s + + # set window + self.setSize(l.XM + self.RSTEP*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in range(4): + x, y, = l.XM, l.YM + i*l.YS + for j in range(self.RSTEP): + s.rows.append(self.RowStack_Class(x, y, self, max_accept=1, max_cards=1)) + x = x + l.XS + x = l.XM + (self.RSTEP-1)*l.XS/2 + s.talon = self.Talon_Class(x, self.height-l.YS, self) + if self.RBASE: + # create an invisible stack to hold the four Aces + s.internals.append(InvisibleStack(self)) + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def startGame(self): + frames = 0 + for i in range(52): + c = self.s.talon.cards[-1] + if c.rank == ACE: + self.s.talon.dealRow(rows=self.s.internals, frames=0) + else: + if frames == 0 and i >= 39: + self.startDealSample() + frames = 4 + self.s.talon.dealRow(rows=(self.s.rows[i],), frames=frames) + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + rows = self.s.rows + for i in range(0, self.RLEN, self.RSTEP): + if not rows[i].cards: + return 0 + suit = rows[i].cards[-1].suit + for j in range(self.RSTEP - 1): + r = rows[i + j] + if not r.cards or r.cards[-1].rank != self.RBASE + j or r.cards[-1].suit != suit: + return 0 + return 1 + + def getHighlightPilesStacks(self): + return () + + def getAutoStacks(self, event=None): + return (self.sg.dropstacks, (), self.sg.dropstacks) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + if from_stack.cards: + if from_stack.id % self.RSTEP == 0 and from_stack.cards[-1].rank == self.RBASE: + # do not move the leftmost card of a row if the rank is correct + return -1 + return 1 + + +# /*********************************************************************** +# // Spaces +# ************************************************************************/ + +class Spaces_Talon(Montana_Talon): + def getRedealSpaces(self, stacks, gaps): + # use four random spaces, ignore gaps + # note: the random.seed is already saved in shuffleStackMove + spaces = [] + while len(spaces) != 4: + r = self.game.random.choice(stacks) + if not r in spaces: + spaces.append(r) + return spaces + +class Spaces(Montana): + Talon_Class = StackWrapper(Spaces_Talon, max_rounds=3) + + +# /*********************************************************************** +# // Blue Moon +# ************************************************************************/ + +class BlueMoon(Montana): + RLEN, RSTEP, RBASE = 56, 14, 0 + + def startGame(self): + frames = 0 + j = 0 + for i in range(52): + if j % 14 == 0: + j = j + 1 + if i == 39: + self.startDealSample() + frames = 4 + self.s.talon.dealRow(rows=(self.s.rows[j],), frames=frames) + j = j + 1 + assert len(self.s.talon.cards) == 0 + ace_rows = filter(lambda r: r.cards and r.cards[-1].rank == ACE, self.s.rows) + j = 0 + for r in ace_rows: + self.moveMove(1, r, self.s.rows[j]) + j = j + 14 + + +# /*********************************************************************** +# // Red Moon +# ************************************************************************/ + +class RedMoon(BlueMoon): + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + frames = 0 + r = self.s.rows + self.s.talon.dealRow(rows=(r[0],r[14],r[28],r[42]), frames=frames) + for i in range(4): + if i == 3: + self.startDealSample() + frames = 4 + n = i * 14 + 2 + self.s.talon.dealRow(rows=r[n:n+12], frames=frames) + + +# /*********************************************************************** +# // Galary +# ************************************************************************/ + + +class Galary_Hint(Montana_Hint): + # TODO + shallMovePile = DefaultHint._cautiousShallMovePile + + +class Galary_RowStack(Montana_RowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + if self.id % self.game.RSTEP == 0: + return cards[0].rank == self.game.RBASE + r = self.game.s.rows + left = r[self.id - 1] + if left.cards and left.cards[-1].suit == cards[0].suit \ + and left.cards[-1].rank + 1 == cards[0].rank: + return 1 + if self.id < len(r)-1: + right = r[self.id + 1] + if right.cards and right.cards[-1].suit == cards[0].suit \ + and right.cards[-1].rank - 1 == cards[0].rank: + return 1 + return 0 + + +class Galary(RedMoon): + RowStack_Class = Galary_RowStack + Hint_Class = Galary_Hint + +# /*********************************************************************** +# // Moonlight +# ************************************************************************/ + +class Moonlight(Montana): + RowStack_Class = Galary_RowStack + Hint_Class = Galary_Hint + + +# /*********************************************************************** +# // Jungle +# ************************************************************************/ + +class Jungle_RowStack(Montana_RowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + if self.id % self.game.RSTEP == 0: + return cards[0].rank == self.game.RBASE + left = self.game.s.rows[self.id - 1] + return left.cards and left.cards[-1].rank + 1 == cards[0].rank + +class Jungle(BlueMoon): + Talon_Class = StackWrapper(Montana_Talon, max_rounds=2) + RowStack_Class = Jungle_RowStack + + +# /*********************************************************************** +# // Spaces and Aces +# ************************************************************************/ + +class SpacesAndAces_RowStack(Montana_RowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + if self.id % self.game.RSTEP == 0: + return cards[0].rank == self.game.RBASE + left = self.game.s.rows[self.id - 1] + return left.cards and left.cards[-1].suit == cards[0].suit and left.cards[-1].rank < cards[0].rank + + +class SpacesAndAces(BlueMoon): + Hint_Class = Galary_Hint + Talon_Class = InitialDealTalonStack + RowStack_Class = SpacesAndAces_RowStack + + + + +# register the game +registerGame(GameInfo(53, Montana, "Montana", + GI.GT_MONTANA | GI.GT_OPEN, 1, 2, + si={"ncards": 48}, altnames="Gaps")) +registerGame(GameInfo(116, Spaces, "Spaces", + GI.GT_MONTANA | GI.GT_OPEN, 1, 2, + si={"ncards": 48})) +registerGame(GameInfo(63, BlueMoon, "Blue Moon", + GI.GT_MONTANA | GI.GT_OPEN, 1, 2, + altnames=("Rangoon",) )) +registerGame(GameInfo(117, RedMoon, "Red Moon", + GI.GT_MONTANA | GI.GT_OPEN, 1, 2)) +registerGame(GameInfo(275, Galary, "Galary", + GI.GT_MONTANA | GI.GT_OPEN, 1, 2)) +registerGame(GameInfo(276, Moonlight, "Moonlight", + GI.GT_MONTANA | GI.GT_OPEN, 1, 2, + si={"ncards": 48})) +registerGame(GameInfo(380, Jungle, "Jungle", + GI.GT_MONTANA | GI.GT_OPEN, 1, 1)) +registerGame(GameInfo(381, SpacesAndAces, "Spaces and Aces", + GI.GT_MONTANA | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/montecarlo.py b/pysollib/games/montecarlo.py new file mode 100644 index 0000000000..708008cb6d --- /dev/null +++ b/pysollib/games/montecarlo.py @@ -0,0 +1,798 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MonteCarlo_Hint(DefaultHint): + # FIXME: demo is not too clever in this game + pass + + +# /*********************************************************************** +# // Monte Carlo +# // Monaco +# ************************************************************************/ + +class MonteCarlo_Talon(TalonStack): + def canDealCards(self): + free = 0 + for r in self.game.s.rows: + if not r.cards: + free = 1 + elif free: + return 1 + return free and len(self.cards) + + def dealCards(self, sound=0): + self.game.updateStackMove(self.game.s.talon, 2|16) # for undo + n = self.game.fillEmptyStacks() + self.game.updateStackMove(self.game.s.talon, 1|16) # for redo + return n + + +class MonteCarlo_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + if self.cards[-1].rank != cards[0].rank: + return 0 + # now look if the stacks are neighbours + return self.game.isNeighbour(from_stack, self) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 and to_stack in self.game.s.rows + if to_stack.cards: + self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow) + else: + BasicRowStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + f = game.s.foundations[0] + game.updateStackMove(game.s.talon, 2|16) # for undo + if not game.demo: + game.playSample("droppair", priority=200) + game.moveMove(n, self, f, frames=frames, shadow=shadow) + game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + self.fillStack() + other_stack.fillStack() + if self.game.FILL_STACKS_AFTER_DROP: + game.fillEmptyStacks() + game.updateStackMove(game.s.talon, 1|16) # for redo + game.leaveState(old_state) + + +class MonteCarlo(Game): + Talon_Class = MonteCarlo_Talon + Foundation_Class = StackWrapper(AbstractFoundationStack, max_accept=0) + RowStack_Class = MonteCarlo_RowStack + Hint_Class = MonteCarlo_Hint + + FILL_STACKS_AFTER_DROP = 0 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 6.5*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in range(5): + for j in range(5): + x, y = l.XM + j*l.XS, l.YM + i*l.YS + s.rows.append(self.RowStack_Class(x, y, self, + max_accept=1, max_cards=2, + dir=0, base_rank=NO_RANK)) + x, y = l.XM + 11*l.XS/2, l.YM + s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT, + max_move=0, max_cards=52, base_rank=ANY_RANK)) + l.createText(s.foundations[0], "ss") + y = y + 2*l.YS + s.talon = self.Talon_Class(x, y, self, max_rounds=1) + l.createText(s.talon, "ss", text_format="%D") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank + + + # + # game extras + # + + def isNeighbour(self, stack1, stack2): + if not (0 <= stack1.id <= 24 and 0 <= stack2.id <= 24): + return 0 + column = stack2.id % 5 + diff = stack1.id - stack2.id + if column == 0: + return diff in (-5, -4, 1, 5, 6) + elif column == 4: + return diff in (-6, -5, -1, 4, 5) + else: + return diff in (-6, -5, -4, -1, 1, 4, 5, 6) + + def fillEmptyStacks(self): + free, n = 0, 0 + self.startDealSample() + for r in self.s.rows: + assert len(r.cards) <= 1 + if not r.cards: + free = free + 1 + elif free > 0: + to_stack = self.allstacks[r.id - free] + self.moveMove(1, r, to_stack, frames=4, shadow=0) + if free > 0: + for r in self.s.rows: + if not r.cards: + if not self.s.talon.cards: + break + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, r) + n = n + 1 + self.stopSamples() + return n + + +class Monaco(MonteCarlo): + pass + + +# /*********************************************************************** +# // Weddings +# ************************************************************************/ + +class Weddings_Talon(MonteCarlo_Talon): + def canDealCards(self): + free = 0 + for r in self.game.s.rows: + if not r.cards: + free = 1 + else: + k = r.id + while k >= 5 and not self.game.allstacks[k - 5].cards: + k = k - 5 + if k != r.id: + return 1 + return free and len(self.cards) + + +class Weddings(MonteCarlo): + Talon_Class = Weddings_Talon + + def fillEmptyStacks(self): + free, n = 0, 0 + self.startDealSample() + for r in self.s.rows: + assert len(r.cards) <= 1 + if not r.cards: + free = free + 1 + else: + k = r.id + while k >= 5 and not self.allstacks[k - 5].cards: + k = k - 5 + if k != r.id: + to_stack = self.allstacks[k] + self.moveMove(1, r, to_stack, frames=4, shadow=0) + if free > 0: + for r in self.s.rows: + if not r.cards: + if not self.s.talon.cards: + break + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, r) + n = n + 1 + self.stopSamples() + return n + + +# /*********************************************************************** +# // Simple Carlo (Monte Carlo for Children, stacks do not +# // have to be neighbours) +# ************************************************************************/ + +class SimpleCarlo(MonteCarlo): + FILL_STACKS_AFTER_DROP = 1 + + def getAutoStacks(self, event=None): + return ((), (), ()) + + def isNeighbour(self, stack1, stack2): + return 0 <= stack1.id <= 24 and 0 <= stack2.id <= 24 + + +# /*********************************************************************** +# // Simple Pairs +# ************************************************************************/ + +class SimplePairs(MonteCarlo): + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 6*l.XS, l.YM + 4*l.YS) + + # create stacks + for i in range(3): + for j in range(3): + x, y = l.XM + (2*j+3)*l.XS/2, l.YM + (2*i+1)*l.YS/2 + s.rows.append(self.RowStack_Class(x, y, self, + max_accept=1, max_cards=2, + dir=0, base_rank=NO_RANK)) + x, y = l.XM, l.YM + 3*l.YS/2 + s.talon = TalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x = x + 5*l.XS + s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT, + max_move=0, max_cards=52, base_rank=ANY_RANK)) + l.createText(s.foundations[0], "ss") + + # define stack-groups + l.defaultStackGroups() + + def fillStack(self, stack): + if stack in self.s.rows: + if len(stack.cards) == 0 and len(self.s.talon.cards) > 0: + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, stack) + + def isNeighbour(self, stack1, stack2): + return 0 <= stack1.id <= 15 and 0 <= stack2.id <= 15 + + +# /*********************************************************************** +# // Neighbour +# ************************************************************************/ + +class Neighbour_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # We accept any King. Pairs will get delivered by _dropPairMove. + return cards[0].rank == KING + + +class Neighbour_RowStack(MonteCarlo_RowStack): + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + if self.cards[-1].rank + cards[0].rank != 11: + return 0 + # now look if the stacks are neighbours + return self.game.isNeighbour(from_stack, self) + + def clickHandler(self, event): + if self._dropKingClickHandler(event): + return 1 + return MonteCarlo_RowStack.clickHandler(self, event) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 + if self.cards[-1].rank == KING: + assert to_stack in self.game.s.foundations + BasicRowStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + else: + MonteCarlo_RowStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + + def _dropKingClickHandler(self, event): + if not self.cards: + return 0 + c = self.cards[-1] + if c.face_up and c.rank == KING and not self.basicIsBlocked(): + self.game.playSample("autodrop", priority=20) + self.playMoveMove(1, self.game.s.foundations[0], sound=0) + return 1 + return 0 + + def fillStack(self): + if not self.cards and self.game.s.talon.canDealCards(): + old_state = self.game.enterState(self.game.S_FILL) + self.game.s.talon.dealCards() + self.game.leaveState(old_state) + + +class Neighbour(MonteCarlo): + Foundation_Class = Neighbour_Foundation + RowStack_Class = Neighbour_RowStack + + FILL_STACKS_AFTER_DROP = 1 + + def getAutoStacks(self, event=None): + return ((), self.sg.dropstacks, ()) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + card2.rank == 11 + + +# /*********************************************************************** +# // Fourteen +# ************************************************************************/ + +class Fourteen_RowStack(MonteCarlo_RowStack): + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + return self.cards[-1].rank + cards[0].rank == 12 + + +class Fourteen(Game): + Foundation_Class = StackWrapper(AbstractFoundationStack, max_accept=0) + RowStack_Class = Fourteen_RowStack + + FILL_STACKS_AFTER_DROP = 0 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 7*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in (0, 2.5): + for j in range(6): + x, y = l.XM + j*l.XS, l.YM + i*l.YS + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1, + dir=0, base_rank=NO_RANK)) + x, y = l.XM + 6*l.XS, l.YM + s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT, + max_move=0, max_cards=52, base_rank=ANY_RANK)) + l.createText(s.foundations[0], "ss") + x, y = self.width - l.XS, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.rows[:4]) + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + card2.rank == 12 + + +# /*********************************************************************** +# // Nestor +# ************************************************************************/ + +class Nestor_RowStack(MonteCarlo_RowStack): + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + return self.cards[-1].rank == cards[0].rank + + +class Nestor(Game): + Foundation_Class = StackWrapper(AbstractFoundationStack, max_accept=0) + RowStack_Class = Nestor_RowStack + + FILL_STACKS_AFTER_DROP = 0 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+8*l.XS, l.YM+2*l.YS+12*l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + for i in range(8): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1, + dir=0, base_rank=NO_RANK)) + x += l.XS + x, y = l.XM+2*l.XS, self.height-l.YS + for i in range(4): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1, + dir=0, base_rank=NO_RANK)) + x += l.XS + x, y = self.width-l.XS, self.height-l.YS + s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT, + max_move=0, max_cards=52, base_rank=ANY_RANK)) + l.createText(s.foundations[0], "nn") + x, y = l.XM, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _checkRow(self, cards): + for i in range(len(cards)): + for j in range(i): + if cards[i].rank == cards[j].rank: + return j + return -1 + + def _shuffleHook(self, cards): + # no row will have two cards of the same rank + for i in range(8): + for t in range(1000): # just in case + j = self._checkRow(cards[i*6:(i+1)*6]) + if j < 0: + break + j += i*6 + k = self.random.choice(range((i+1)*6, 52)) + cards[j], cards[k] = cards[k], cards[j] + cards.reverse() + return cards + + def startGame(self): + for r in self.s.rows[:8]: + for j in range(6): + self.s.talon.dealRow(rows=[r], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[8:]) + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank + + +# /*********************************************************************** +# // Vertical +# ************************************************************************/ + +class Vertical(Nestor): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+9*l.XS, l.YM+2*l.YS+12*l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + for i in range(7): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1, + dir=0, base_rank=NO_RANK)) + x += l.XS + x, y = l.XM, self.height-l.YS + for i in range(9): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=1, + dir=0, base_rank=NO_RANK)) + x += l.XS + x, y = self.width-l.XS, l.YM + s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT, + max_move=0, max_cards=52, base_rank=ANY_RANK)) + l.createText(s.foundations[0], "ss") + x -= l.XS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + self.s.talon.dealRow(frames=0) + for i in range(4): + self.s.talon.dealRow(rows=self.s.rows[:7], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:7]) + self.s.talon.dealRow(rows=[self.s.rows[3]]) + + + +# /*********************************************************************** +# // The Wish +# ************************************************************************/ + +class TheWish(Game): + + FILL_STACKS_AFTER_DROP = 0 + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+6*l.XS, 2*l.YM+2*l.YS+6*l.YOFFSET) + + # create stacks + for i in range(2): + for j in range(4): + x, y = l.XM + j*l.XS, l.YM+i*(l.YM+l.YS+3*l.YOFFSET) + s.rows.append(Nestor_RowStack(x, y, self, + max_move=1, max_accept=1, + dir=0, base_rank=NO_RANK)) + + x, y = self.width - l.XS, l.YM + s.talon = InitialDealTalonStack(x, y, self) + + x, y = self.width - l.XS, self.height - l.YS + s.foundations.append(AbstractFoundationStack(x, y, self, suit=ANY_SUIT, + max_move=0, max_cards=52, max_accept=0, base_rank=ANY_RANK)) + l.createText(s.foundations[0], "nn") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def fillStack(self, stack): + if stack.cards: + self.flipMove(stack) + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank + +class TheWishOpen(TheWish): + def fillStack(self, stack): + pass + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + +# /*********************************************************************** +# // Der letzte Monarch (The last Monarch) +# ************************************************************************/ + +class DerLetzteMonarch_Foundation(SS_FoundationStack): + def acceptsCards(self, from_stack, cards): + if cards is None: + # special hack for _getDropStack() below + return SS_FoundationStack.acceptsCards(self, from_stack, from_stack.cards) + # + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # We only accept cards from a Reserve. Other cards will get + # delivered by _handlePairMove. + return from_stack in self.game.s.reserves + + +class DerLetzteMonarch_RowStack(ReserveStack): + def canDropCards(self, stacks): + return (None, 0) + + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + # must be neighbours + if not self.game.isNeighbour(from_stack, self): + return 0 + # must be able to move our card to the foundations or reserves + return self._getDropStack() is not None + + def _getDropStack(self): + if len(self.cards) != 1: + return None + for s in self.game.s.foundations: + if s.acceptsCards(self, None): # special hack + return s + for s in self.game.s.reserves: + if not s.cards: + return s + return None + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 and to_stack in self.game.s.rows + assert len(to_stack.cards) == 1 + self._handlePairMove(ncards, to_stack, frames=-1, shadow=0) + + def _handlePairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + s = other_stack._getDropStack() + assert s is not None + game.moveMove(n, other_stack, s, frames=frames, shadow=shadow) + game.moveMove(n, self, other_stack, frames=0) + game.leaveState(old_state) + + +class DerLetzteMonarch_ReserveStack(ReserveStack): + def clickHandler(self, event): + return self.doubleclickHandler(event) + + +class DerLetzteMonarch(Game): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self, XM=4), self.s + + # set window + self.setSize(l.XM + 13*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in range(4): + for j in range(13): + x, y, = l.XM + j*l.XS, l.YM + (i+1)*l.YS + s.rows.append(DerLetzteMonarch_RowStack(x, y, self, max_accept=1, max_cards=2)) + for i in range(4): + x, y, = l.XM + (i+2)*l.XS, l.YM + s.reserves.append(DerLetzteMonarch_ReserveStack(x, y, self, max_accept=0)) + for i in range(4): + x, y, = l.XM + (i+7)*l.XS, l.YM + s.foundations.append(DerLetzteMonarch_Foundation(x, y, self, i, max_move=0)) + s.talon = InitialDealTalonStack(l.XM, l.YM, self) + + # define stack-groups (non default) + self.sg.talonstacks = [s.talon] + self.sg.openstacks = s.foundations + s.rows + self.sg.dropstacks = s.rows + s.reserves + self.sg.reservestacks = s.reserves + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.rows[:39], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[39:]) + + def isGameWon(self): + c = 0 + for s in self.s.foundations: + c = c + len(s.cards) + return c == 51 + + def getAutoStacks(self, event=None): + return ((), self.s.reserves, ()) + + def getDemoInfoText(self): + return "Der letzte\nMonarch" + + + # + # game extras + # + + def isNeighbour(self, stack1, stack2): + if not (0 <= stack1.id <= 51 and 0 <= stack2.id <= 51): + return 0 + column = stack2.id % 13 + diff = stack1.id - stack2.id + if column == 0: + return diff in (-13, 1, 13) + elif column == 12: + return diff in (-13, -1, 13) + else: + return diff in (-13, -1, 1, 13) + + +# register the game +registerGame(GameInfo(89, MonteCarlo, "Monte Carlo", + GI.GT_PAIRING_TYPE, 1, 0, + altnames=("Quilt",) )) +registerGame(GameInfo(216, Monaco, "Monaco", + GI.GT_PAIRING_TYPE, 2, 0)) +registerGame(GameInfo(212, Weddings, "Weddings", + GI.GT_PAIRING_TYPE, 1, 0)) +registerGame(GameInfo(90, SimpleCarlo, "Simple Carlo", + GI.GT_PAIRING_TYPE, 1, 0)) +registerGame(GameInfo(91, SimplePairs, "Simple Pairs", + GI.GT_PAIRING_TYPE, 1, 0, + altnames=("Jamestown",))) +registerGame(GameInfo(92, Neighbour, "Neighbour", + GI.GT_PAIRING_TYPE, 1, 0)) +registerGame(GameInfo(96, Fourteen, "Fourteen", + GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(235, Nestor, "Nestor", + GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(152, DerLetzteMonarch, "The last Monarch", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0, + altnames=("Der letzte Monarch",) )) +registerGame(GameInfo(328, TheWish, "The Wish", + GI.GT_PAIRING_TYPE, 1, 0, + ranks=(0, 6, 7, 8, 9, 10, 11, 12) )) +registerGame(GameInfo(329, TheWishOpen, "The Wish (open)", + GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0, + ranks=(0, 6, 7, 8, 9, 10, 11, 12) )) +registerGame(GameInfo(368, Vertical, "Vertical", + GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/napoleon.py b/pysollib/games/napoleon.py new file mode 100644 index 0000000000..69ee4696ab --- /dev/null +++ b/pysollib/games/napoleon.py @@ -0,0 +1,273 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +from pysollib.games.braid import Braid_Foundation + + +# /*********************************************************************** +# // stacks +# ************************************************************************/ + +class Napoleon_Talon(InitialDealTalonStack): + pass + + +class Napoleon_Foundation(Braid_Foundation): + pass + + +class Napoleon_RowStack(UD_SS_RowStack): + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class Napoleon_ReserveStack(BasicRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=0) + apply(BasicRowStack.__init__, (self, x, y, game), cap) + + +class Napoleon_SingleFreeCell(ReserveStack): + def acceptsCards(self, from_stack, cards): +## if from_stack.id >= 8: +## # from_stack must be a Napoleon_RowStack +## return 0 + return ReserveStack.acceptsCards(self, from_stack, cards) + + def canMoveCards(self, cards): + if self.game.s.rows[8].cards and self.game.s.rows[9].cards: + return 0 + return ReserveStack.canMoveCards(self, cards) + + +class Napoleon_FreeCell(ReserveStack): + def canMoveCards(self, cards): + if self.game.s.rows[self.id-2].cards: + return 0 + return ReserveStack.canMoveCards(self, cards) + + +# /*********************************************************************** +# // Der kleine Napoleon +# ************************************************************************/ + +class DerKleineNapoleon(Game): + + RowStack_Class = StackWrapper(Napoleon_RowStack, mod=13) + + # + # game layout + # + + def createGame(self, reserves=1): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 2*24 + 2*l.XM + 11*l.XS, l.YM + 5*l.YS + 2*l.XM) + x0 = l.XM + 24 + 4*l.XS + x1 = x0 + l.XS + l.XM + x2 = x1 + l.XS + l.XM + + # create stacks + y = l.YM + for i in range(4): + s.rows.append(self.RowStack_Class(x0, y, self)) + s.rows.append(self.RowStack_Class(x2, y, self)) + y = y + l.YS + y = self.height - l.YS + if reserves == 1: + s.rows.append(Napoleon_ReserveStack(x0, y, self)) + s.rows.append(Napoleon_ReserveStack(x2, y, self)) + s.reserves.append(Napoleon_SingleFreeCell(x1, y, self)) + else: + s.rows.append(Napoleon_ReserveStack(x0 - l.XS, y, self)) + s.rows.append(Napoleon_ReserveStack(x2 + l.XS, y, self)) + s.reserves.append(Napoleon_FreeCell(x0, y, self)) + s.reserves.append(Napoleon_FreeCell(x2, y, self)) + # foundations + x, y = x1, l.YM + for i in range(4): + s.foundations.append(Napoleon_Foundation(x, y, self, i)) + y = y + l.YS + # talon + if reserves == 1: + ##x, y = l.XM, self.height - l.YS + y = self.height + l.YS + else: + y = self.height - l.YS + s.talon = Napoleon_Talon(x, y, self) + + # update stack building direction + for r in s.rows: + if r.id & 1 == 0: + r.CARD_XOFFSET = 4*[-l.XS] + 13*[-2] + else: + r.CARD_XOFFSET = 4*[l.XS] + 13*[2] + r.CARD_YOFFSET = 0 + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move 4 cards of the same rank to bottom of the Talon (i.e. last cards to be dealt) + rank = cards[-1].rank + return self._shuffleHookMoveToBottom(cards, lambda c, rank=rank: (c.rank == rank, c.suit)) + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(rows=self.s.rows[:8], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:8]) + for i in range(4): + self.s.talon.dealRow(rows=self.s.rows[8:]) + self.s.talon.dealBaseCards(ncards=4) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 13 == card2.rank or (card2.rank + 1) % 13 == card1.rank)) + + # + # game extras + # + + def updateText(self): + if self.preview > 1 or not self.texts.info: + return + t = "" + f = self.s.foundations[0] + if f.cards: + t = RANKS[f.cards[0].rank] + dir = self.getFoundationDir() + if dir == 1: + t = t + _(" Ascending") + elif dir == -1: + t = t + _(" Descending") + self.texts.info.config(text=t) + + +# /*********************************************************************** +# // Der freie Napoleon (completely equivalent to Der kleine Napoleon, +# // just a different layout) +# ************************************************************************/ + +class DerFreieNapoleon(DerKleineNapoleon): + + RowStack_Class = StackWrapper(Napoleon_RowStack, mod=13) + # + # game layout + # + + def createGame(self, reserves=1): + # create layout + l, s = Layout(self), self.s + + # set window + # set size so that at least 2/3 of a card is visible with 15 cards + h = l.CH*2/3 + (15-1)*l.YOFFSET + h = l.YS + max(h, 3*l.YS) + self.setSize(l.XM + 2*l.XM + 10*l.XS, l.YM + h) + x1 = l.XM + 8*l.XS + 2*l.XM + + # create stacks + y = l.YM + l.YS + for j in range(8): + x = l.XM + j*l.XS + s.rows.append(self.RowStack_Class(x, y, self)) + for j in range(2): + x = x1 + j*l.XS + s.rows.append(Napoleon_ReserveStack(x, y, self)) + self.setRegion(s.rows, (-999, y - l.YM/2, 999999, 999999)) + y = l.YM + if reserves == 1: + s.reserves.append(Napoleon_SingleFreeCell(x1 + l.XS/2, y, self)) + else: + s.reserves.append(Napoleon_FreeCell(x1, y, self)) + s.reserves.append(Napoleon_FreeCell(x1 + l.XS, y, self)) + # foundations + x = l.XM + 2*l.XS + for i in range(4): + s.foundations.append(Napoleon_Foundation(x, y, self, i)) + x = x + l.XS + tx, ty, ta, tf = l.getTextAttr(s.foundations[-1], "se") + font = self.app.getFont("canvas_default") + self.texts.info = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=font) + # talon + x, y = l.XM, self.height - l.YS + s.talon = Napoleon_Talon(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + +# /*********************************************************************** +# // Napoleon (two FreeCells instead of one SingleFreeCell) +# ************************************************************************/ + +class Napoleon(DerKleineNapoleon): + def createGame(self): + DerKleineNapoleon.createGame(self, reserves=2) + + +class FreeNapoleon(DerFreieNapoleon): + def createGame(self): + DerFreieNapoleon.createGame(self, reserves=2) + + +# register the game +registerGame(GameInfo(167, DerKleineNapoleon, "Der kleine Napoleon", + GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(168, DerFreieNapoleon, "Der freie Napoleon", + GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(169, Napoleon, "Napoleon", + GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(170, FreeNapoleon, "Free Napoleon", + GI.GT_NAPOLEON | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/needle.py b/pysollib/games/needle.py new file mode 100644 index 0000000000..f95eb6f0a8 --- /dev/null +++ b/pysollib/games/needle.py @@ -0,0 +1,121 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Needle +# // Haystack +# // Pitchfork +# ************************************************************************/ + +class Needle(Game): + + Hint_Class = CautiousDefaultHint + ReserveStack_Class = StackWrapper(ReserveStack, max_cards=18) + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+max(9*l.XS, 5*l.XS+18*l.XOFFSET), l.YM+2*l.YS+12*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + stack = self.ReserveStack_Class(x, y, self) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.reserves.append(stack) + self.setRegion(s.reserves, (-999, -999, w-4*l.XS-l.CW/2, l.YM+l.YS-l.CH/2)) + + x = w-4*l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + + x, y = l.XM+(w-(l.XM+9*l.XS))/2, l.YM+l.YS + for i in range(9): + s.rows.append(AC_RowStack(x, y, self, max_move=1)) + x += l.XS + + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(8): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + for i in (4, 4, 3, 3): + self.s.talon.dealRow(rows=self.s.rows[:i], frames=0) + self.s.talon.dealRow(rows=self.s.rows[-i:], frames=0) + self.startDealSample() + for i in (2, 2, 2, 2): + self.s.talon.dealRow(rows=self.s.rows[:i]) + self.s.talon.dealRow(rows=self.s.rows[-i:]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + if to_stack in self.s.reserves: + return 0 + return 1+int(len(to_stack.cards) != 0) + + +class Haystack(Needle): + ReserveStack_Class = StackWrapper(ReserveStack, max_cards=8) + + +class Pitchfork(Needle): + ReserveStack_Class = StackWrapper(OpenStack, max_accept=0) + + +# register the game +registerGame(GameInfo(318, Needle, "Needle", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(319, Haystack, "Haystack", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(367, Pitchfork, "Pitchfork", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0)) + diff --git a/pysollib/games/numerica.py b/pysollib/games/numerica.py new file mode 100644 index 0000000000..7c321c9aa2 --- /dev/null +++ b/pysollib/games/numerica.py @@ -0,0 +1,544 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Galen Brooks +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText +from pysollib.mfxutil import kwdefault + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Numerica_Hint(DefaultHint): + # FIXME: demo is clueless + + #def _getDropCardScore(self, score, color, r, t, ncards): + #FIXME: implement this method + + def _getMoveWasteScore(self, score, color, r, t, pile, rpile): + assert r is self.game.s.waste and len(pile) == 1 + score = 30000 + if len(t.cards) == 0: + score = score - (KING - r.cards[0].rank) * 1000 + elif t.cards[-1].rank < r.cards[0].rank: + # FIXME: add intelligence here + score = 10000 + t.cards[-1].rank - len(t.cards) + elif t.cards[-1].rank == r.cards[0].rank: + score = 20000 + else: + score = score - (t.cards[-1].rank - r.cards[0].rank) * 1000 + return score, color + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Numerica_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts any one card from the Waste pile + return from_stack is self.game.s.waste and len(cards) == 1 + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + def getHelp(self): + ##return _('Row. Accepts any one card from the Waste.') + return _('Row. Build regardless of rank and suit.') + + +# /*********************************************************************** +# // Numerica +# ************************************************************************/ + +class Numerica(Game): + Hint_Class = Numerica_Hint + Foundation_Class = StackWrapper(RK_FoundationStack, suit=ANY_SUIT) + RowStack_Class = StackWrapper(Numerica_RowStack, max_accept=1) + + # + # game layout + # + + def createGame(self, rows=4): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable in default window size) + h = max(2*l.YS, 20*l.YOFFSET) + self.setSize(l.XM+(1.5+rows)*l.XS+l.XM, l.YM + l.YS + h) + + # create stacks + x0 = l.XM + l.XS * 3 / 2 + x, y = x0 + (rows-4)*l.XS/2, l.YM + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i)) + x = x + l.XS + x, y = x0, l.YM + l.YS + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + self.setRegion(s.rows, (x0 - l.XS / 2, y, 999999, 999999)) + x = l.XM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + s.talon.texts.ncards = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + y = y + l.YS + s.waste = WasteStack(x, y, self, max_cards=1) + + # define stack-groups + self.sg.openstacks = s.foundations + s.rows + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.dropstacks = s.rows + [s.waste] + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealCards() # deal first card to WasteStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# // Lady Betty +# ************************************************************************/ + +class LadyBetty(Numerica): + Foundation_Class = SS_FoundationStack + + def createGame(self): + Numerica.createGame(self, rows=6) + + +# /*********************************************************************** +# // Puss in the Corner +# ************************************************************************/ + +class PussInTheCorner_Foundation(SS_FoundationStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, base_suit=ANY_SUIT) + apply(SS_FoundationStack.__init__, (self, x, y, game, ANY_SUIT), cap) + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return False + if self.cards: + # check the color + if cards[0].color != self.cards[-1].color: + return False + return True + def getHelp(self): + return _('Foundation. Build up by color.') + + +class PussInTheCorner_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts any one card from the Talon + return from_stack is self.game.s.talon and len(cards) == 1 + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + def getHelp(self): + ##return _('Row. Accepts any one card from the Waste.') + return _('Row. Build regardless of rank and suit.') + + +class PussInTheCorner(Numerica): + + def createGame(self, rows=4): + l, s = Layout(self), self.s + self.setSize(l.XM+4*l.XS, l.YM+4*l.YS) + for x, y in ((l.XM, l.YM ), + (l.XM+3*l.XS, l.YM ), + (l.XM, l.YM+3*l.YS), + (l.XM+3*l.XS, l.YM+3*l.YS), + ): + stack = PussInTheCorner_RowStack(x, y, self, + max_accept=1, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0 + s.rows.append(stack) + for x, y in ((l.XM+ l.XS, l.YM+ l.YS), + (l.XM+ l.XS, l.YM+2*l.YS), + (l.XM+2*l.XS, l.YM+ l.YS), + (l.XM+2*l.XS, l.YM+2*l.YS), + ): + s.foundations.append(PussInTheCorner_Foundation(x, y, self, + max_move=0)) + x, y = l.XM+3*l.XS/2, l.YM + s.talon = OpenTalonStack(x, y, self) + l.createText(s.talon, 'se') + + # define stack-groups + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, c.suit)) + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.fillStack() + + +# /*********************************************************************** +# // Frog +# // Fly +# ************************************************************************/ + +class Frog(Game): + + Hint_Class = Numerica_Hint + Foundation_Class = SS_FoundationStack + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 2*l.YS+16*l.YOFFSET) + + # create stacks + x, y, = l.XM, l.YM + for i in range(8): + if self.Foundation_Class is RK_FoundationStack: + suit = ANY_SUIT + else: + suit = int(i/2) + s.foundations.append(self.Foundation_Class(x, y, self, + suit=suit, max_move=0)) + x += l.XS + x, y = l.XM, l.YM+l.YS + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.reserves.append(stack) + x += l.XS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x += l.XS + s.waste = WasteStack(x, y, self, max_cards=1) + x += l.XS + for i in range(5): + stack = Numerica_RowStack(x, y, self, max_accept=UNLIMITED_ACCEPTS) + #stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.rows.append(stack) + x = x + l.XS + + # define stack-groups + l.defaultStackGroups() + + def _shuffleHook(self, cards): + for c in cards[:]: + if c.rank == ACE: + cards.remove(c) + cards.append(c) + return cards + + def startGame(self): + tc = self.s.talon.cards[-1] + self.startDealSample() + self.s.talon.dealRow(rows=[self.s.foundations[tc.suit*2]]) + for i in range(13): + self.s.talon.dealRow(self.s.reserves, flip=0) + self.flipMove(self.s.reserves[0]) + self.s.talon.dealCards() + + +class Fly(Frog): + + Foundation_Class = RK_FoundationStack + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + for i in range(13): + self.s.talon.dealRow(self.s.reserves, flip=0) + self.flipMove(self.s.reserves[0]) + self.s.talon.dealCards() + + +# /*********************************************************************** +# // Gnat +# ************************************************************************/ + +class Gnat(Game): + + Hint_Class = Numerica_Hint + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 2*l.YS+16*l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x += l.XS + s.waste = WasteStack(x, y, self, max_cards=1) + x += l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(4): + s.rows.append(Numerica_RowStack(x, y, self, max_accept=UNLIMITED_ACCEPTS)) + x += l.XS + x = l.XM+6*l.XS + for i in range(2): + y = l.YM + l.YS/2 + for j in range(3): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + y += l.YS + x += l.YS + + # define stack-groups + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealCards() + + +# /*********************************************************************** +# // Gloaming +# // Chamberlain +# ************************************************************************/ + +class Gloaming_RowStack(Numerica_RowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts any one card from reserves + return from_stack in self.game.s.reserves + + +class Gloaming(Game): + + Hint_Class = Numerica_Hint + Foundation_Class = SS_FoundationStack + + def createGame(self, reserves=3, rows=5): + # create layout + l, s = Layout(self), self.s + + # set window + n = 52/reserves+1 + w, h = l.XM + (reserves+rows+1)*l.XS, l.YM + 2*l.YS+n*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM+(reserves+rows+1-4)*l.XS/2, l.YM + for i in range(4): + if self.Foundation_Class is RK_FoundationStack: + suit = ANY_SUIT + else: + suit = i + s.foundations.append(self.Foundation_Class(x, y, self, + suit=suit, max_move=0)) + x += l.XS + + x, y = l.XM, l.YM+l.YS + for i in range(reserves): + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.reserves.append(stack) + x += l.XS + + x += l.XS + for i in range(rows): + s.rows.append(Gloaming_RowStack(x, y, self, max_accept=UNLIMITED_ACCEPTS)) + x += l.XS + + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + + def startGame(self): + n = 52/len(self.s.reserves)+1 + for i in range(n-3): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRowAvail(rows=self.s.reserves) + + +class Chamberlain(Gloaming): + Foundation_Class = RK_FoundationStack + def createGame(self, reserves=3, rows=5): + Gloaming.createGame(self, reserves=4, rows=3) + + +# /*********************************************************************** +# // Toad +# ************************************************************************/ + +class Toad_TalonStack(DealRowTalonStack): + def canDealCards(self): + if not DealRowTalonStack.canDealCards(self): + return False + for r in self.game.s.reserves: + if r.cards: + return False + return True + def dealCards(self, sound=0): + self.dealRow(rows=self.game.s.reserves, sound=sound) + + +class Toad(Game): + #Hint_Class = Numerica_Hint + + def createGame(self, reserves=3, rows=5): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+11*l.XS, l.YM+6*l.YS + self.setSize(w, h) + + # create stacks + x, y = w-l.XS, h-l.YS + s.talon = Toad_TalonStack(x, y, self) + l.createText(s.talon, "n") + x, y = l.XM, l.YM + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i/2)) + x += l.XS + x, y = l.XM+3*l.XS/2, l.YM+l.YS + for i in range(5): + s.rows.append(Gloaming_RowStack(x, y, self, max_accept=UNLIMITED_ACCEPTS)) + x += l.XS + y = l.YM+l.YS/2 + for i in (3, 3, 3, 3, 1): + x = l.XM+8*l.XS + for j in range(i): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + x += l.XS + y += l.YS + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + +# /*********************************************************************** +# // Shifting +# ************************************************************************/ + +class Shifting_RowStack(Numerica_RowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return False + if from_stack is self.game.s.waste: + return True + if not self.cards: + return cards[0].rank == KING + if (from_stack in self.game.s.rows and + self.cards[-1].rank-cards[0].rank == 1): + return True + return False + + +class Shifting(Numerica): + RowStack_Class = StackWrapper(Shifting_RowStack, max_accept=1) + + + +# register the game +registerGame(GameInfo(257, Numerica, "Numerica", + GI.GT_NUMERICA | GI.GT_CONTRIB, 1, 0, + altnames="Sir Tommy")) +registerGame(GameInfo(171, LadyBetty, "Lady Betty", + GI.GT_NUMERICA, 1, 0)) +registerGame(GameInfo(355, Frog, "Frog", + GI.GT_NUMERICA, 2, 0)) +registerGame(GameInfo(356, Fly, "Fly", + GI.GT_NUMERICA, 2, 0)) +registerGame(GameInfo(357, Gnat, "Gnat", + GI.GT_NUMERICA, 1, 0)) +registerGame(GameInfo(378, Gloaming, "Gloaming", + GI.GT_NUMERICA | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(379, Chamberlain, "Chamberlain", + GI.GT_NUMERICA | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(402, Toad, "Toad", + GI.GT_NUMERICA, 2, 0)) +registerGame(GameInfo(430, PussInTheCorner, "Puss in the Corner", + GI.GT_NUMERICA, 1, 0)) +registerGame(GameInfo(435, Shifting, "Shifting", + GI.GT_NUMERICA, 1, 0)) + diff --git a/pysollib/games/osmosis.py b/pysollib/games/osmosis.py new file mode 100644 index 0000000000..75d74a98df --- /dev/null +++ b/pysollib/games/osmosis.py @@ -0,0 +1,302 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Osmosis +# ************************************************************************/ + +class Osmosis_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # search foundation with max number of cards + assert len(cards) == 1 + max_s, max_cards = None, -1 + for s in self.game.s.foundations: + if len(s.cards) > max_cards: + max_s, max_cards = s, len(s.cards) + # if we have less cards, then rank must match the card in this foundation + if len(self.cards) < max_cards: + if cards[0].rank != max_s.cards[len(self.cards)].rank: + return 0 + # + return 1 + + +class Osmosis(Game): + + # + # game layout + # + + def createGame(self, max_rounds=-1, num_deal=1): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = 3*l.XM+3*l.XS+(4+13)*l.XOFFSET, l.YM+4*l.YS + self.setSize(w, h) + + # create stacks + x, y, = l.XM, l.YM + for i in range(4): + stack = BasicRowStack(x, y, self, max_move=1, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + y = y + l.YS + x, y, = 2*l.XM+l.XS+4*l.XOFFSET, l.YM + for i in range(4): + stack = Osmosis_Foundation(x, y, self, i, base_rank=ANY_RANK, max_move=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.foundations.append(stack) + y = y + l.YS + x, y, = self.width - l.XS, l.YM + l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds, num_deal=num_deal) + l.createText(s.talon, "sw") + y = y + l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "sw") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self, flip=0): + # deal first card to foundation + base_card = self.s.talon.getCard() + n = base_card.suit * self.gameinfo.decks + to_stack = self.s.foundations[n] + self.startDealSample() + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + # deal cards + for i in range(3): + self.s.talon.dealRow(flip=flip) + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Peek +# ************************************************************************/ + +class Peek(Osmosis): + def startGame(self): + Osmosis.startGame(self, flip=1) + +# /*********************************************************************** +# // Open Peek +# ************************************************************************/ + +class OpenPeek(Game): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = max(2*l.XM+2*l.XS+(5+13)*l.XOFFSET, l.XM + 8*l.XS), l.YM+8*l.YS + self.setSize(w, h) + + # create stacks + x, y, = l.XM, l.YM + for i in range(4): + stack = BasicRowStack(x, y, self, max_move=1, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + y += l.YS + x, y, = 2*l.XM+l.XS+5*l.XOFFSET, l.YM + for i in range(4): + stack = Osmosis_Foundation(x, y, self, i, base_rank=ANY_RANK, max_move=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.foundations.append(stack) + y += l.YS + y = l.YM + 4*l.YS + for i in range(4): + x = l.XM + for j in range(8): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + x += l.XS + y += l.YS + + x, y = w-l.XS, l.YM + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + + +# /*********************************************************************** +# // Genesis +# ************************************************************************/ + +class Genesis(Game): + + def createGame(self, rows=13, reserves=False): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+rows*l.XS, l.YM+2*l.YS+20*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y, = l.XM+(rows-4)*l.XS/2, l.YM + for i in range(4): + stack = Osmosis_Foundation(x, y, self, i, base_rank=ANY_RANK, max_move=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.foundations.append(stack) + x += l.XS + + x, y, = l.XM, h-2*l.YS-3*l.YOFFSET + for i in range(rows): + s.rows.append(BasicRowStack(x, y, self)) + x += l.XS + + x, y, = l.XM, h-l.YS-3*l.YOFFSET + for i in range(rows): + s.rows.append(BasicRowStack(x, y, self)) + x += l.XS + + if reserves: + s.reserves.append(ReserveStack(l.XM, l.YM, self)) + s.reserves.append(ReserveStack(w-l.XS, l.YM, self)) + + s.talon = InitialDealTalonStack(l.XM, l.YM, self) + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(rows=self.s.rows[13:], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:13]) + + +class GenesisPlus(Genesis): + def createGame(self): + Genesis.createGame(self, reserves=True) + + +# /*********************************************************************** +# // Bridesmaids +# ************************************************************************/ + +class Bridesmaids(Game): + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+3*l.XS+12*l.XOFFSET, l.YM+4*l.YS + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=UNLIMITED_REDEALS, + num_deal=3) + l.createText(s.talon, 'se') + y += l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'se') + + x, y = l.XM+2*l.XS, l.YM + for i in range(4): + stack = Osmosis_Foundation(x, y, self, suit=i, + base_rank=ANY_RANK, max_move=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.foundations.append(stack) + y = y + l.YS + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self, flip=0): + # deal first card to foundation + base_card = self.s.talon.getCard() + n = base_card.suit * self.gameinfo.decks + to_stack = self.s.foundations[n] + self.startDealSample() + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + self.s.talon.dealCards() # deal first card to WasteStack + + + + + +# register the game +registerGame(GameInfo(59, Osmosis, "Osmosis", + GI.GT_1DECK_TYPE, 1, -1, + altnames=("Treasure Trove",) )) +registerGame(GameInfo(60, Peek, "Peek", + GI.GT_1DECK_TYPE, 1, -1)) +registerGame(GameInfo(298, OpenPeek, "Open Peek", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(370, Genesis, "Genesis", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(371, GenesisPlus, "Genesis +", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(409, Bridesmaids, "Bridesmaids", + GI.GT_1DECK_TYPE, 1, -1)) + diff --git a/pysollib/games/parallels.py b/pysollib/games/parallels.py new file mode 100644 index 0000000000..1c1967884f --- /dev/null +++ b/pysollib/games/parallels.py @@ -0,0 +1,176 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Parallels +# ************************************************************************/ + +class Parallels_RowStack(BasicRowStack): + def basicIsBlocked(self): + index = self.index + rows = self.game.s.rows + if index < 10: + return False + if not rows[index-10].cards: + return False + if index >= 60: # last row + return False + if not rows[index+10].cards: + return False + return True + + +class Parallels_TalonStack(DealRowTalonStack): + def dealCards(self, sound=0): + return self.dealRow(sound=sound) + + def dealRow(self, rows=None, flip=1, reverse=0, frames=-1, sound=0): + if not rows is None: + return DealRowTalonStack.dealRowAvail(self, rows=rows, flip=flip, + reverse=reverse, frames=frames, sound=sound) + rows = self.game.s.rows + for r in rows[:10]: + if not r.cards: + return self._fillRow(frames=frames, sound=sound) + column_ncards = [] + for i in range(10): + column = [r for r in rows[i::10] if r.cards] + column_ncards.append(len(column)) + max_col = max(column_ncards) + if max(column_ncards) != min(column_ncards): + return self._fillRow(frames=frames, sound=sound) + r = rows[max_col*10:max_col*10+10] + return DealRowTalonStack.dealRowAvail(self, rows=r, flip=flip, + reverse=reverse, frames=frames, sound=sound) + + def _fillRow(self, frames=-1, sound=0): + rows = self.game.s.rows + column_ncards = [] + for i in range(10): + column = [r for r in rows[i::10] if r.cards] + column_ncards.append(len(column)) + max_col = max(column_ncards) + n = 0 + rr = self.game.s.rows[:max_col*10] + while True: + filled = False + for i in range(10): + prev_s = None + for s in rr[i::10]: + if not self.cards: + filled = False + break + if s.cards: + if prev_s: + DealRowTalonStack.dealRow(self, rows=[prev_s], + frames=frames, sound=sound) + n += 1 + filled = True + break + prev_s = s + if not filled: + break + while True: + filled = False + for i in range(10): + for s in rr[i::10]: + if not self.cards: + filled = False + break + if not s.cards: + DealRowTalonStack.dealRow(self, rows=[s], + frames=frames, sound=sound) + n += 1 + filled = True + break + if not filled: + break + + return n + + +class Parallels(Game): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + # set window + self.setSize(l.XM+12*l.XS, l.YM+7*l.YS) + # create stacks + s.talon = Parallels_TalonStack(l.XM, l.YM, self) + l.createText(s.talon, 'ss') + n = 0 + y = l.YM + for i in range(7): + x = l.XM+l.XS + for j in range(10): + stack = Parallels_RowStack(x, y, self, max_accept=0) + stack.index = n + s.rows.append(stack) + n += 1 + x += l.XS + y += l.YS + x, y = l.XM, l.YM+l.YS+l.YS/2 + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + y += l.YS + x, y = l.XM+11*l.XS, l.YM+l.YS+l.YS/2 + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=KING, dir=-1)) + y += l.YS + + # define stack-groups + l.defaultStackGroups() + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (ACE, KING) and c.deck == 0, + (c.rank, c.suit))) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:10]) + + +# register the game +registerGame(GameInfo(428, Parallels, "Parallels", + GI.GT_2DECK_TYPE, 2, 0)) + + + + + diff --git a/pysollib/games/pasdedeux.py b/pysollib/games/pasdedeux.py new file mode 100644 index 0000000000..38bab75c42 --- /dev/null +++ b/pysollib/games/pasdedeux.py @@ -0,0 +1,230 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class PasDeDeux_Hint(AbstractHint): + # FIXME: this is very simple + + def getDistance(self, stack, card): + d = 0 + if card.rank != stack.id % 13: + d = d + 1 + if card.suit != stack.id / 13: + d = d + 1 + return d + + def computeHints(self): + # find the single stack that can currently move a card + rows = [] + for r in self.game.s.rows: + if r.canMoveCards(r.cards): + rows.append(r) + break + # for each stack + for r in rows: + r1_d = self.getDistance(r, r.cards[-1]) + column, row = r.id % 13, r.id / 13 + stack_ids = range(column, 52, 13) + range(13*row, 13*row+13) + for i in stack_ids: + t = self.game.s.rows[i] + if t is r: + continue + assert t.acceptsCards(r, r.cards) + t1_d = self.getDistance(t, t.cards[-1]) + # compute distances after swap + r2_d = self.getDistance(t, r.cards[-1]) + t2_d = self.getDistance(r, t.cards[-1]) + # + rw, tw = 3, 2 + if self.game.s.talon.round >= 2: + rw, tw = 4, 2 + c = self.game.cards[t.cards[-1].id - 52] + if 1 and c in self.game.s.waste.cards: + rw = rw - 1 + # + score = int(((rw*r1_d + tw*t1_d) - (rw*r2_d + tw*t2_d)) * 1000) + if score > 0: + self.addHint(score, 1, r, t) + + +# /*********************************************************************** +# // Pas de Deux +# ************************************************************************/ + +class PasDeDeux_Waste(WasteStack): + def canFlipCard(self): + return 0 + + +class PasDeDeux_RowStack(ReserveStack): + def canMoveCards(self, cards): + if not ReserveStack.canMoveCards(self, cards): + return 0 + if not self.game.s.waste.cards: + return 0 + c = self.game.s.waste.cards[-1] + return c.face_up and cards[0].suit == c.suit and cards[0].rank == c.rank + + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + # must be neighbours + return self.game.isNeighbour(from_stack, self) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 and to_stack in self.game.s.rows + assert len(to_stack.cards) == 1 + self._swapPairMove(ncards, to_stack, frames=-1, shadow=0) + if self.game.s.talon.canDealCards(): + self.game.s.talon.dealCards() + else: + # mark game as finished by turning the Waste face down + assert self.game.s.waste.cards[-1].face_up + self.game.flipMove(self.game.s.waste) + + def _swapPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + swap = game.s.internals[0] + game.moveMove(n, self, swap, frames=0) + game.moveMove(n, other_stack, self, frames=frames, shadow=shadow) + game.moveMove(n, swap, other_stack, frames=0) + game.leaveState(old_state) + + def getBottomImage(self): + suit = self.id / 13 + return self.game.app.images.getSuitBottom(suit) + + def quickPlayHandler(self, event, from_stacks=None, to_stacks=None): + # find the single stack that can currently move a card + for r in self.game.s.rows: + if r.canMoveCards(r.cards): + if self.acceptsCards(r, r.cards): + r.playMoveMove(len(r.cards), self) + return 1 + break + return 0 + + +class PasDeDeux(Game): + Hint_Class = PasDeDeux_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self, XM=4), self.s + + # set window + self.setSize(l.XM + 13*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in range(4): + for j in range(13): + x, y, = l.XM + j*l.XS, l.YM + i*l.YS + s.rows.append(PasDeDeux_RowStack(x, y, self, max_accept=1, max_cards=2)) + x, y = self.width - 2*l.XS, self.height - l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=2) + l.createText(s.talon, "se") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.XS, y, + anchor="nw", + font=self.app.getFont("canvas_default")) + x = x - l.XS + s.waste = PasDeDeux_Waste(x, y, self, max_move=0) + l.createText(s.waste, "sw") + s.internals.append(InvisibleStack(self)) # for _swapPairMove() + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def shuffle(self): + self.shuffleSeparateDecks() + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(frames=4) + self.s.talon.dealCards() # deal first card to WasteStack + + def getAutoStacks(self, event=None): + return ((), (), (self.sg.dropstacks)) + + def isGameWon(self): + for r in self.s.rows: + if len(r.cards) != 1: + return 0 + c = r.cards[-1] + if c.suit != r.id / 13 or c.rank != r.id % 13: + return 0 + return 1 + + # + # game extras + # + + def isNeighbour(self, stack1, stack2): + column1, row1 = stack1.id % 13, stack1.id / 13 + column2, row2 = stack2.id % 13, stack2.id / 13 + return column1 == column2 or row1 == row2 + + def getHighlightPilesStacks(self): + # Pas de Deux special: highlight all moveable cards + return ((self.s.rows, 1),) + + +# register the game +registerGame(GameInfo(153, PasDeDeux, "Pas de Deux", + GI.GT_MONTANA | GI.GT_SEPARATE_DECKS, 2, 1)) + diff --git a/pysollib/games/picturegallery.py b/pysollib/games/picturegallery.py new file mode 100644 index 0000000000..f120b5eb8c --- /dev/null +++ b/pysollib/games/picturegallery.py @@ -0,0 +1,436 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class PictureGallery_Hint(AbstractHint): + def computeHints(self): + game = self.game + + # 1) try if we can drop a card (i.e. an Ace) + for r in game.sg.dropstacks: + t, n = r.canDropCards(game.s.foundations) + if t and n == 1: + c = r.getCard() + assert t is not r and c + assert c.rank == ACE + if r in game.s.tableaux: + base_score = 90000 + (4 - r.cap.base_rank) + else: + base_score = 90000 + score = base_score + 100 * (self.K - c.rank) + self.addHint(score, 1, r, t) + + # 2) try if we can move a card to the tableaux + if not self.hints: + for r in game.sg.dropstacks: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + if r in game.s.tableaux: + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + base_score = 80000 + (4 - r.cap.base_rank) + else: + base_score = 80000 + # find a stack that would accept this card + for t in game.s.tableaux: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 3) Try if we can move a card from the tableaux + # to a row stack. This can only happen if there are + # no more cards to deal. + if not self.hints: + for r in game.s.tableaux: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = 70000 + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 4) try if we can move a card within the row stacks + if not self.hints: + for r in game.s.rows: + pile = r.getPile() + if not pile or len(pile) != 1 or len(pile) == len(r.cards): + continue + base_score = 60000 + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 5) try if we can deal cards + if self.level >= 2: + if game.canDealCards(): + self.addHint(self.SCORE_DEAL, 0, game.s.talon, None) + + +# /*********************************************************************** +# // Picture Gallery +# ************************************************************************/ + +# this Foundation only accepts Aces +class PictureGallery_Foundation(RK_FoundationStack): + def __init__(self, x, y, game): + RK_FoundationStack.__init__(self, x, y, game, base_rank=ACE, dir=0, max_move=0, max_cards=8) + self.CARD_YOFFSET = min(30, self.game.app.images.CARD_YOFFSET + 10) + + def getBottomImage(self): + return self.game.app.images.getLetter(ACE) + + +class PictureGallery_TableauStack(SS_RowStack): + def __init__(self, x, y, game, base_rank, yoffset, dir=3): + SS_RowStack.__init__(self, x, y, game, base_rank=base_rank, dir=dir, max_accept=1) + self.CARD_YOFFSET = yoffset + + def acceptsCards(self, from_stack, cards): + if not SS_RowStack.acceptsCards(self, from_stack, cards): + return 0 + # check that the base card is correct + if self.cards and self.cards[0].rank != self.cap.base_rank: + return 0 + return 1 + + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + +class PictureGallery_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # check + if self.cards or self.game.s.talon.cards: + return 0 + return 1 + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class PictureGallery(Game): + Hint_Class = PictureGallery_Hint + + Foundation_Class = PictureGallery_Foundation + TableauStack_Class = PictureGallery_TableauStack + RowStack_Class = StackWrapper(PictureGallery_RowStack, max_accept=1) + Talon_Class = DealRowTalonStack + + # + # game layout + # + + def createGame(self, rows=3, waste=False, dir=3): + # create layout + l, s = Layout(self), self.s + TABLEAU_YOFFSET = min(9, max(3, l.YOFFSET / 3)) + + # set window + th = l.YS + (12/rows-1) * TABLEAU_YOFFSET + # (set piles so that at least 2/3 of a card is visible with 10 cards) + h = (10-1)*l.YOFFSET + l.CH*2/3 + self.setSize(10*l.XS+l.XM, l.YM + 3*th + l.YM + h) + + # create stacks + s.addattr(tableaux=[]) # register extra stack variable + x = l.XM + 8 * l.XS + l.XS / 2 + y = l.YM + l.CH / 2 + s.foundations.append(self.Foundation_Class(x, y, self)) + y = l.YM + for i in range(rows,0,-1): #(3, 2, 1): + x = l.XM + for j in range(8): + s.tableaux.append(self.TableauStack_Class(x, y, self, i, yoffset=TABLEAU_YOFFSET, dir=dir)) + x = x + l.XS + y = y + th + x, y = l.XM, y + l.YM + for i in range(8): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + ##self.setRegion(s.rows, (-999, -999, x - l.CW / 2, 999999)) + x = l.XM + 8 * l.XS + l.XS / 2 + y = self.height - l.YS + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, "se") + if waste: + y -= l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se") + self.setRegion(s.foundations, (x - l.CW / 2, -999, 999999, y - l.CH)) + + # define stack-groups + if waste: + ws = [s.waste] + else: + ws = [] + self.sg.openstacks = s.foundations + s.tableaux + s.rows + ws + self.sg.talonstacks = [s.talon] + ws + self.sg.dropstacks = s.tableaux + s.rows + ws + + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.tableaux, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def isGameWon(self): + if len(self.s.foundations[0].cards) != 8: + return 0 + for stack in self.s.tableaux: + if len(stack.cards) != 4: + return 0 + return 1 + + def fillStack(self, stack): + if self.s.talon.cards: + if stack in self.s.rows and len(stack.cards) == 0: + self.s.talon.dealRow(rows=[stack]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if card1.rank == ACE or card2.rank == ACE: + return 0 + return (card1.suit == card2.suit and + (card1.rank + 3 == card2.rank or card2.rank + 3 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + + +# /*********************************************************************** +# // Great Wheel +# ************************************************************************/ + +class GreatWheel_Foundation(PictureGallery_Foundation): + def acceptsCards(self, from_stack, cards): + if not PictureGallery_Foundation.acceptsCards(self, from_stack, cards): + return False + if self.cards and self.cards[-1].color == cards[0].color: + return False + return True + + +class GreatWheel_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return False + if self.game.s.talon.cards: + return False + if not self.cards: + return True + c1, c2 = self.cards[-1], cards[0] + return c1.suit == c2.suit and c1.rank == c2.rank+1 + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + + +class GreatWheel(PictureGallery): + + Foundation_Class = GreatWheel_Foundation + TableauStack_Class = PictureGallery_TableauStack + RowStack_Class = StackWrapper(GreatWheel_RowStack, max_accept=1) + Talon_Class = StackWrapper(WasteTalonStack, max_rounds=1) + + def createGame(self): + PictureGallery.createGame(self, rows=2, waste=True, dir=2) + + def fillStack(self, stack): + if stack is self.s.waste and not stack.cards : + self.s.talon.dealCards() + if self.s.talon.cards or self.s.waste.cards: + if stack in self.s.rows and len(stack.cards) == 0: + old_state = self.enterState(self.S_FILL) + for i in range(4): + if not self.s.waste.cards: + self.s.talon.dealCards() + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + def startGame(self): + self.startDealSample() + for i in range(4): + self.s.talon.dealRow() + self.s.talon.dealCards() + + + def isGameWon(self): + if len(self.s.foundations[0].cards) != 8: + return False + if self.s.talon.cards or self.s.waste.cards: + return False + for stack in self.s.rows: + if stack.cards: + return False + return True + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if card1.rank == ACE or card2.rank == ACE: + return 0 + return (card1.suit == card2.suit and + (card1.rank + 2 == card2.rank or card2.rank + 2 == card1.rank)) + +# /*********************************************************************** +# // Mount Olympus +# // Zeus +# ************************************************************************/ + +class MountOlympus_Foundation(SS_FoundationStack): + + #def getBottomImage(self): + # return self.game.app.images.getLetter(self.cap.base_rank) + + def getHelp(self): + return 'Build up in suit by twos.' + + +class MountOlympus_RowStack(SS_RowStack): + def getHelp(self): + return 'Build down in suit by twos.' + + +class MountOlympus(Game): + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+9*l.XS, l.YM+3*l.YS+12*l.YOFFSET) + + # create stacks + x, y = l.XM+l.XS, l.YM + for i in range(8): + s.foundations.append(MountOlympus_Foundation(x, y, self, + suit=i/2, base_rank=ACE, dir=2, max_move=0)) + x += l.XS + x, y = l.XM+l.XS, l.YM+l.YS + for i in range(8): + s.foundations.append(MountOlympus_Foundation(x, y, self, + suit=i/2, base_rank=1, dir=2, max_move=0)) + x += l.XS + x, y = l.XM, l.YM+2*l.YS + for i in range(9): + s.rows.append(MountOlympus_RowStack(x, y, self, dir=-2)) + x += l.XS + s.talon=DealRowTalonStack(l.XM, l.YM, self) + l.createText(s.talon, 's') + + # define stack-groups + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (ACE, 1), (c.rank, c.suit))) + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow() + + + def fillStack(self, stack): + if self.s.talon.cards: + if stack in self.s.rows and len(stack.cards) == 0: + self.s.talon.dealRow(rows=[stack]) + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 2 == card2.rank or card2.rank + 2 == card1.rank)) + + +class Zeus(MountOlympus): + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + for i in range(4): + self.s.talon.dealRow() + + +# register the game +registerGame(GameInfo(7, PictureGallery, "Picture Gallery", + GI.GT_2DECK_TYPE, 2, 0, + altnames=("Die Bildgallerie", "Mod-3") )) +registerGame(GameInfo(397, GreatWheel, "Great Wheel", + GI.GT_2DECK_TYPE, 2, 0, + ranks=range(12) # without Kings + )) +registerGame(GameInfo(398, MountOlympus, "Mount Olympus", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(399, Zeus, "Zeus", + GI.GT_2DECK_TYPE, 2, 0)) + diff --git a/pysollib/games/pileon.py b/pysollib/games/pileon.py new file mode 100644 index 0000000000..789d6ec6ba --- /dev/null +++ b/pysollib/games/pileon.py @@ -0,0 +1,140 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class PileOn_RowStack(RK_RowStack): + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class PileOn(Game): + Hint_Class = DefaultHint + ##Hint_Class = CautiousDefaultHint + TWIDTH = 4 + NSTACKS = 15 + PLAYCARDS = 4 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (set size so that at least 4 cards are fully playable) + #w = max(2*l.XS, l.XS+(self.PLAYCARDS-1)*l.XOFFSET+2*l.XM) + w = l.XS+(self.PLAYCARDS-1)*l.XOFFSET+3*l.XM + twidth, theight = self.TWIDTH, int((self.NSTACKS-1)/self.TWIDTH+1) + self.setSize(l.XM+twidth*w, l.YM+theight*l.YS) + + # create stacks + y = l.YM + for i in range(theight): + x = l.XM + for j in range(twidth): + if i*twidth+j >= self.NSTACKS: + break + stack = PileOn_RowStack(x, y, self, dir=0, max_cards=self.PLAYCARDS) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = l.XOFFSET, 0 + s.rows.append(stack) + x = x + w + y = y + l.YS + x, y = self.width - l.XS, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + + # define stack-groups + self.sg.openstacks = s.rows + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.rows + + # + # game overrides + # + + def startGame(self): + r = self.s.rows[:-2] + for i in range(self.PLAYCARDS-1): + self.s.talon.dealRow(rows=r, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=r) + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + for r in self.s.rows: + if r.cards: + if len(r.cards) != 4 or not r._isSequence(r.cards): + return 0 + return 1 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank + +class SmallPileOn(PileOn): + TWIDTH = 3 + NSTACKS = 11 + PLAYCARDS = 4 + +class PileOn2Decks(PileOn): + TWIDTH = 4 + NSTACKS = 15 + PLAYCARDS = 8 + + +# register the game +registerGame(GameInfo(41, PileOn, "PileOn", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0, + altnames=("Fifteen Puzzle",) )) +registerGame(GameInfo(289, SmallPileOn, "Small PileOn", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0, + ranks=(0, 5, 6, 7, 8, 9, 10, 11, 12), + rules_filename = "pileon.html")) +## registerGame(GameInfo(341, PileOn2Decks, "PileOn (2 decks)", +## GI.GT_2DECK_TYPE | GI.GT_OPEN,, 2, 0)) + + diff --git a/pysollib/games/poker.py b/pysollib/games/poker.py new file mode 100644 index 0000000000..daea04376d --- /dev/null +++ b/pysollib/games/poker.py @@ -0,0 +1,294 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Poker Square +# ************************************************************************/ + +class PokerSquare_RowStack(ReserveStack): + def clickHandler(self, event): + if not self.cards: + self.game.s.talon.playMoveMove(1, self) + return 1 + return ReserveStack.clickHandler(self, event) + + rightclickHandler = clickHandler + + +class PokerSquare(Game): + Talon_Class = OpenTalonStack + RowStack_Class = StackWrapper(PokerSquare_RowStack, max_move=0) + Hint_Class = None + + WIN_SCORE = 100 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # create texts 1) + ta = "ss" + x, y = l.XM, l.YM + 2*l.YS + if self.preview <= 1: + t = MfxCanvasText(self.canvas, x, y, anchor="nw", + font=self.app.getFont("canvas_default"), + text=_('''\ +Royal Flush +Straight Flush +Four of a Kind +Full House +Flush +Straight +Three of a Kind +Two Pair +One Pair''')) + bb = t.bbox() + x = bb[1][0] + 16 + h = bb[1][1] - bb[0][1] + if h >= 2*l.YS: + ta = "e" + t.move(0, -l.YS) + y = y - l.YS + t = MfxCanvasText(self.canvas, x, y, anchor="nw", + font=self.app.getFont("canvas_default"), + text="100\n75\n50\n25\n20\n15\n10\n5\n2") + x = t.bbox()[1][0] + 16 + self.texts.misc = MfxCanvasText(self.canvas, x, y, anchor="nw", + font=self.app.getFont("canvas_default"), + text="0\n"*8+"0") + x = self.texts.misc.bbox()[1][0] + 32 + + # set window + w = max(2*l.XS, x) + self.setSize(l.XM + w + 5*l.XS + 50, l.YM + 5*l.YS + 30) + + # create stacks + for i in range(5): + for j in range(5): + x, y = l.XM + w + j*l.XS, l.YM + i*l.YS + s.rows.append(self.RowStack_Class(x, y, self)) + x, y = l.XM, l.YM + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, anchor=ta) + s.internals.append(InvisibleStack(self)) # for _swapPairMove() + + # create texts 2) + if self.preview <= 1: + self.texts.addattr(hands=[]) + for i in (4, 9, 14, 19, 24): + tx, ty, ta, tf = l.getTextAttr(s.rows[i], anchor="e") + t = MfxCanvasText(self.canvas, tx+8, ty, + anchor=ta, + font=self.app.getFont("canvas_default")) + self.texts.hands.append(t) + for i in range(20, 25): + tx, ty, ta, tf = l.getTextAttr(s.rows[i], anchor="ss") + t = MfxCanvasText(self.canvas, tx, ty, anchor=ta, + font=self.app.getFont("canvas_default")) + self.texts.hands.append(t) + self.texts.score = MfxCanvasText(self.canvas, l.XM, 5*l.YS, anchor="sw", + font=self.app.getFont("canvas_large")) + + # define hands for scoring + r = s.rows + self.poker_hands = [ + r[0:5], r[5:10], r[10:15], r[15:20], r[20:25], + (r[0], r[0+5], r[0+10], r[0+15], r[0+20]), + (r[1], r[1+5], r[1+10], r[1+15], r[1+20]), + (r[2], r[2+5], r[2+10], r[2+15], r[2+20]), + (r[3], r[3+5], r[3+10], r[3+15], r[3+20]), + (r[4], r[4+5], r[4+10], r[4+15], r[4+20]), + ] + self.poker_hands = map(tuple, self.poker_hands) + + # define stack-groups + l.defaultStackGroups() + return l + + # + # game overrides + # + + def startGame(self): + self.moveMove(27, self.s.talon, self.s.internals[0], frames=0) + self.s.talon.fillStack() + + def isGameWon(self): + return len(self.s.talon.cards) == 0 and self.getGameScore() >= self.WIN_SCORE + + def getAutoStacks(self, event=None): + return ((), (), ()) + + # + # scoring + # + + def updateText(self): + if self.preview > 1: + return + score = 0 + count = [0] * 9 + for i in range(10): + type, value = self.getHandScore(self.poker_hands[i]) + if 0 <= type <= 8: + count[type] = count[type] + 1 + self.texts.hands[i].config(text=str(value)) + score = score + value + t = '\n'.join(map(str, count)) + self.texts.misc.config(text=t) + # + t = "" + if score >= self.WIN_SCORE: + t = _("WON\n\n") + if self.s.talon.cards: + t = t + _("Points: %d") % score + else: + t = t + _("Total: %d") % score + self.texts.score.config(text=t) + + def getGameScore(self): + score = 0 + for hand in self.poker_hands: + type, value = self.getHandScore(hand) + score = score + value + return score + + def getHandScore(self, hand): + same_rank = [0] * 13 + same_suit = [0] * 4 + ranks = [] + for s in hand: + if s.cards: + rank, suit = s.cards[0].rank, s.cards[0].suit + same_rank[rank] = same_rank[rank] + 1 + same_suit[suit] = same_suit[suit] + 1 + ranks.append(rank) + # + straight = 0 + if same_rank.count(1) == 5: + d = max(ranks) - min(ranks) + if d == 4: + straight = 1 # normal straight + elif d == 12 and same_rank[-4:].count(1) == 4: + straight = 2 # straight with Ace ranked high + # + if max(same_suit) == 5: + if straight: + if straight == 2: + return 0, 100 # Royal Flush + return 1, 75 # Straight Flush + return 4, 20 # Flush + # + if straight: + return 5, 15 # Straight + # + if max(same_rank) >= 2: + same_rank.sort() + if same_rank[-1] == 4: + return 2, 50 # Four of a Kind + if same_rank[-1] == 3: + if same_rank[-2] == 2: + return 3, 25 # Full House + return 6, 10 # Three of a Kind + if same_rank[-2] == 2: + return 7, 5 # Two Pairs + return 8, 2 # Pair + # + return -1, 0 + + +# /*********************************************************************** +# // Poker Shuffle +# ************************************************************************/ + +class PokerShuffle_RowStack(ReserveStack): + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + assert ncards == 1 and to_stack in self.game.s.rows + assert len(to_stack.cards) == 1 + self._swapPairMove(ncards, to_stack, frames=-1, shadow=0) + + def _swapPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + swap = game.s.internals[0] + game.moveMove(n, self, swap, frames=0) + game.moveMove(n, other_stack, self, frames=frames, shadow=shadow) + game.moveMove(n, swap, other_stack, frames=0) + game.leaveState(old_state) + + +class PokerShuffle(PokerSquare): + Talon_Class = InitialDealTalonStack + RowStack_Class = StackWrapper(PokerShuffle_RowStack, max_accept=1, max_cards=2) + + WIN_SCORE = 200 + + def createGame(self): + l = PokerSquare.createGame(self) + if self.s.talon.texts.ncards: + self.s.talon.texts.ncards.text_format="%D" + + def startGame(self): + self.moveMove(27, self.s.talon, self.s.internals[0], frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def checkForWin(self): + return 0 + + +# register the game +registerGame(GameInfo(139, PokerSquare, "Poker Square", + GI.GT_POKER_TYPE | GI.GT_SCORE, 1, 0, + si={"ncards": 25})) +registerGame(GameInfo(140, PokerShuffle, "Poker Shuffle", + GI.GT_POKER_TYPE | GI.GT_SCORE | GI.GT_OPEN, 1, 0, + si={"ncards": 25})) + diff --git a/pysollib/games/pushpin.py b/pysollib/games/pushpin.py new file mode 100644 index 0000000000..4ce6f23a27 --- /dev/null +++ b/pysollib/games/pushpin.py @@ -0,0 +1,231 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys, types + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class PushPin_Hint(AbstractHint): + + def computeHints(self): + game = self.game + rows = game.s.rows + for i in range(len(rows)-3): + r = rows[i+1] + if not rows[i+2].cards: + break + if r._checkPair(i, i+2): + self.addHint(5000, 1, r, game.s.foundations[0]) + if not rows[i+3].cards: + break + if r._checkPair(i, i+3): + self.addHint(5000, 1, r, rows[i+2]) + + +class PushPin_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + return True + +class PushPin_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + for r in self.game.s.rows: + if not r.cards: + return self.dealRowAvail(rows=[r], sound=sound) + return self.dealRowAvail(rows=[self.game.s.rows[0]], sound=sound) + def getBottomImage(self): + return None + +class PushPin_RowStack(ReserveStack): + + def _checkPair(self, ps, ns): + if ps < 0 or ns > 51: + return False + rows = self.game.allstacks + pc, nc = rows[ps].cards, rows[ns].cards + if pc and nc: + if pc[0].suit == nc[0].suit or pc[0].rank == nc[0].rank: + return True + return False + + def clickHandler(self, event): + ps, ns = self.id - 1, self.id + 1 + if self._checkPair(ps, ns): + if not self.game.demo: + self.game.playSample("autodrop", priority=20) + self.playMoveMove(1, self.game.s.foundations[0], sound=0) + return True + return False + + def acceptsCards(self, from_stack, cards): + if not self.cards: + return from_stack.id > self.id + return True + if abs(self.id - from_stack.id) != 1: + return False + ps = min(self.id, from_stack.id)-1 + ns = ps + 3 + return self._checkPair(ps, ns) + + def fillStack(self): + self.game.fillEmptyStacks() + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + if not to_stack is self.game.s.foundations[0]: + self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow) + else: + ReserveStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + old_state = game.enterState(game.S_FILL) + f = game.s.foundations[0] + game.updateStackMove(game.s.talon, 2|16) # for undo + if not game.demo: + game.playSample("droppair", priority=200) + game.moveMove(n, self, f, frames=frames, shadow=shadow) + game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + self.fillStack() + game.updateStackMove(game.s.talon, 1|16) # for redo + game.leaveState(old_state) + + def getBottomImage(self): + return None + + +class PushPin(Game): + + Hint_Class = PushPin_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + xx, yy = 9, 6 + w, h = l.XM+xx*l.XS, l.YM+yy*l.YS + self.setSize(w, h) + + # create stacks + for i in range(yy): + for j in range(xx): + n = j+xx*i + if n < 1: + continue + if n > 52: + break + k = j + if i%2: + k = xx-j-1 + x, y = l.XM + k*l.XS, l.YM + i*l.YS + s.rows.append(PushPin_RowStack(x, y, self)) + s.talon = PushPin_Talon(l.XM, l.YM, self) + s.foundations.append(PushPin_Foundation(l.XM, h-l.YS, self, + suit=ANY_SUIT, dir=0, base_rank=ANY_RANK, + max_accept=0, max_move=0, max_cards=52)) + + # define stack-groups + l.defaultStackGroups() + return l + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:3]) + + def isGameWon(self): + return len(self.s.foundations[0].cards) == 50 + + def fillEmptyStacks(self): + if not self.demo: + self.startDealSample() + rows = self.s.rows + i = 0 + for r in rows: + if not r.cards: + break + i += 1 + j = i + for r in rows[i:]: + if r.cards: + break + j += 1 + for r in rows[j:]: + if not r.cards: + break + self.moveMove(1, r, rows[i], frames=2, shadow=0) + i += 1 + if not self.demo: + self.stopSamples() + return 0 + + def getAutoStacks(self, event=None): + return ((), (), ()) + + +class RoyalMarriage(PushPin): + def _shuffleHook(self, cards): + qi, ki = -1, -1 + for i in range(len(cards)): + c = cards[i] + if c.suit == 2 and c.rank == 11: + qi = i + if c.suit == 2 and c.rank == 12: + ki = i + if qi >= 0 and ki >= 0: + break + q, k = cards[qi], cards[ki] + del cards[max(qi, ki)] + del cards[min(qi, ki)] + cards.insert(0, k) + cards.append(q) + return cards + + +class Queens(PushPin): + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + + +registerGame(GameInfo(287, PushPin, "Push Pin", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(288, RoyalMarriage, "Royal Marriage", + GI.GT_1DECK_TYPE, 1, 0)) +## registerGame(GameInfo(303, Queens, "Queens", +## GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) diff --git a/pysollib/games/pyramid.py b/pysollib/games/pyramid.py new file mode 100644 index 0000000000..2297237373 --- /dev/null +++ b/pysollib/games/pyramid.py @@ -0,0 +1,301 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Pyramid_Hint(DefaultHint): + # consider moving card to the Talon as well + def step010(self, dropstacks, rows): + rows = rows + (self.game.s.talon,) + return DefaultHint.step010(self, dropstacks, rows) + + +# /*********************************************************************** +# // basic logic for Talon, Waste and Rows +# ************************************************************************/ + +class Pyramid_StackMethods: + def acceptsCards(self, from_stack, cards): + if self.basicIsBlocked(): + return 0 + if from_stack is self or not self.cards or len(cards) != 1: + return 0 + c = self.cards[-1] + return c.face_up and cards[0].face_up and cards[0].rank + c.rank == 11 + + def _dropKingClickHandler(self, event): + if not self.cards: + return 0 + c = self.cards[-1] + if c.face_up and c.rank == KING and not self.basicIsBlocked(): + self.game.playSample("autodrop", priority=20) + self.playMoveMove(1, self.game.s.foundations[0], sound=0) + return 1 + return 0 + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + if not self.game.demo: + self.game.playSample("droppair", priority=200) + assert n == 1 and self.acceptsCards(other_stack, [other_stack.cards[-1]]) + old_state = self.game.enterState(self.game.S_FILL) + f = self.game.s.foundations[0] + self.game.moveMove(n, self, f, frames=frames, shadow=shadow) + self.game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + self.game.leaveState(old_state) + self.fillStack() + other_stack.fillStack() + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + if to_stack in self.game.s.foundations: + self.game.moveMove(ncards, self, to_stack, frames=frames, shadow=shadow) + self.fillStack() + else: + self._dropPairMove(ncards, to_stack, frames=-1, shadow=shadow) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Pyramid_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # We accept any King. Pairs will get delivered by _dropPairMove. + return cards[0].rank == KING + + +# note that this Talon can accept and drop cards +class Pyramid_Talon(Pyramid_StackMethods, FaceUpWasteTalonStack): + def clickHandler(self, event): + if self._dropKingClickHandler(event): + return 1 + return FaceUpWasteTalonStack.clickHandler(self, event) + + def canDealCards(self): + if not FaceUpWasteTalonStack.canDealCards(self): + return 0 + return not self.game.isGameWon() + + def canDropCards(self, stacks): + if self.cards: + cards = self.cards[-1:] + for s in stacks: + if s is not self and s.acceptsCards(self, cards): + return (s, 1) + return (None, 0) + + +class Pyramid_Waste(Pyramid_StackMethods, WasteStack): + def clickHandler(self, event): + if self._dropKingClickHandler(event): + return 1 + return WasteStack.clickHandler(self, event) + + +class Pyramid_RowStack(Pyramid_StackMethods, OpenStack): + def __init__(self, x, y, game): + OpenStack.__init__(self, x, y, game, max_accept=1, max_cards=2) + self.CARD_YOFFSET = 1 + + STEP = (1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6) + + def basicIsBlocked(self): + r, step = self.game.s.rows, self.STEP + i, n = self.id, 1 + while i < 21: + i = i + step[i] + n = n + 1 + for j in range(i, i+n): + if r[j].cards: + return 1 + return 0 + + def clickHandler(self, event): + if self._dropKingClickHandler(event): + return 1 + return OpenStack.clickHandler(self, event) + + +# /*********************************************************************** +# // Pyramid +# ************************************************************************/ + +class Pyramid(Game): + Hint_Class = Pyramid_Hint + + # + # game layout + # + + def createGame(self, rows=4): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 9*l.XS, l.YM + 4*l.YS) + + # create stacks + for i in range(7): + x = l.XM + (8-i) * l.XS / 2 + y = l.YM + i * l.YS / 2 + for j in range(i+1): + s.rows.append(Pyramid_RowStack(x, y, self)) + x = x + l.XS + + x, y = l.XM, l.YM + s.talon = Pyramid_Talon(x, y, self, max_rounds=3, max_accept=1) + l.createText(s.talon, "se") + tx, ty, ta, tf = l.getTextAttr(s.talon, "ne") + s.talon.texts.rounds = MfxCanvasText(self.canvas, tx, ty, + anchor=ta, + font=self.app.getFont("canvas_default")) + y = y + l.YS + s.waste = Pyramid_Waste(x, y, self, max_accept=1) + l.createText(s.waste, "se") + x, y = self.width - l.XS, l.YM + s.foundations.append(Pyramid_Foundation(x, y, self, + suit=ANY_SUIT, dir=0, base_rank=ANY_RANK, + max_move=0, max_cards=52)) + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.rows + self.sg.talonstacks + self.sg.dropstacks = s.rows + self.sg.talonstacks + + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def getAutoStacks(self, event=None): + return (self.sg.dropstacks, self.sg.dropstacks, ()) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + card2.rank == 11 + + +# /*********************************************************************** +# // Relaxed Pyramid +# ************************************************************************/ + +class RelaxedPyramid(Pyramid): + # the pyramid must be empty + def isGameWon(self): + return getNumberOfFreeStacks(self.s.rows) == len(self.s.rows) + + +# /*********************************************************************** +# // Thirteen +# // FIXME: UNFINISHED +# // (this doesn't work yet as 2 cards of the Waste should be playable) +# ************************************************************************/ +# Thirteen #89404422185320919548 + +class Thirteen(Pyramid): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(7*l.XS+l.XM, 5*l.YS+l.YM) + + # create stacks + for i in range(7): + x = l.XM + (6-i) * l.XS / 2 + y = l.YM + l.YS + i * l.YS / 2 + for j in range(i+1): + s.rows.append(Pyramid_RowStack(x, y, self)) + x = x + l.XS + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "s") + x = x + l.XS + s.waste = Pyramid_Waste(x, y, self) + l.createText(s.waste, "s") + s.waste.CARD_XOFFSET = 14 + x, y = self.width - l.XS, l.YM + s.foundations.append(Pyramid_Foundation(x, y, self, + suit=ANY_SUIT, dir=0, base_rank=ANY_RANK, + max_move=0, max_cards=UNLIMITED_CARDS)) + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.rows + self.sg.talonstacks + self.sg.dropstacks = s.rows + self.sg.talonstacks + + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:21], flip=0) + self.s.talon.dealRow(rows=self.s.rows[21:]) + self.s.talon.dealCards() # deal first card to WasteStack + + +# register the game +registerGame(GameInfo(38, Pyramid, "Pyramid", + GI.GT_PAIRING_TYPE, 1, 2)) +registerGame(GameInfo(193, RelaxedPyramid, "Relaxed Pyramid", + GI.GT_PAIRING_TYPE | GI.GT_RELAXED, 1, 2)) +##registerGame(GameInfo(44, Thirteen, "Thirteen", +## GI.GT_PAIRING_TYPE, 1, 0)) + diff --git a/pysollib/games/royalcotillion.py b/pysollib/games/royalcotillion.py new file mode 100644 index 0000000000..a1ef212228 --- /dev/null +++ b/pysollib/games/royalcotillion.py @@ -0,0 +1,510 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Royal Cotillion +# ************************************************************************/ + +class RoyalCotillion_Foundation(SS_FoundationStack): + def getBottomImage(self): + if self.cap.base_rank == 1: + return self.game.app.images.getLetter(1) + return self.game.app.images.getSuitBottom(self.cap.base_suit) + + +class RoyalCotillion(Game): + Foundation_Class = RoyalCotillion_Foundation + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 10*l.XS, l.YM + 4*l.YS) + + # create stacks + for i in range(4): + x, y, = l.XM + i*l.XS, l.YM + s.rows.append(BasicRowStack(x, y, self, max_accept=0)) + for i in range(4): + x, y, = l.XM + 4*l.XS, l.YM + i*l.YS + s.foundations.append(self.Foundation_Class(x, y, self, i, dir=2, mod=13)) + x = x + l.XS + s.foundations.append(self.Foundation_Class(x, y, self, i, dir=2, mod=13, base_rank=1)) + for i in range(4): + for j in range(4): + x, y, = l.XM + (j+6)*l.XS, l.YM + i*l.YS + s.reserves.append(ReserveStack(x, y, self, max_accept=0)) + x, y = l.XM + l.XS, self.height - l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "sw") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def fillStack(self, stack): + if not stack.cards: + old_state = self.enterState(self.S_FILL) + if stack is self.s.waste and self.s.talon.cards: + self.s.talon.dealCards() + elif stack in self.s.reserves and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + def getHighlightPilesStacks(self): + return () + + def getAutoStacks(self, event=None): + if event is None: + # disable auto drop - this would ruin the whole gameplay + return (self.sg.dropstacks, (), self.sg.dropstacks) + else: + # rightclickHandler + return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks) + + +# /*********************************************************************** +# // Odd and Even +# ************************************************************************/ + +class OddAndEven(RoyalCotillion): + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, i, dir=2, mod=13)) + x = x + l.XS + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, i, dir=2, mod=13, base_rank=1)) + x = x + l.XS + for i in range(2): + x, y, = l.XM + ((4,3)[i])*l.XS, l.YM + (i+1)*l.YS + for j in range((4,5)[i]): + s.reserves.append(ReserveStack(x, y, self, max_accept=0)) + x = x + l.XS + x, y = l.XM, self.height - l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=2) + l.createText(s.talon, "nn") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "nn") + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Kingdom +# ************************************************************************/ + +class Kingdom(RoyalCotillion): + Foundation_Class = RK_FoundationStack + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + for i in range(8): + s.foundations.append(self.Foundation_Class(x, y, self, ANY_SUIT)) + x = x + l.XS + x, y, = l.XM, y + l.YS + for i in range(8): + s.reserves.append(ReserveStack(x, y, self, max_accept=0)) + x = x + l.XS + x, y = l.XM + 3*l.XS, y + 3*l.YS/2 + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "sw") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se") + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move one Ace to top of the Talon (i.e. first card to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit), 1) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=(self.s.foundations[0],)) + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Alhambra +# ************************************************************************/ + +class Alhambra_Waste(WasteStack): + def acceptsCards(self, from_stack, cards): + if not WasteStack.acceptsCards(self, from_stack, cards): + return 0 + # check cards + if not self.cards: + return 0 + c1, c2 = self.cards[-1], cards[0] + return c1.suit == c2.suit and ((c1.rank + 1) % self.cap.mod == c2.rank or (c2.rank + 1) % self.cap.mod == c1.rank) + + +class Alhambra(Game): + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 8*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, max_move=0)) + x = x + l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, max_move=0, + base_rank=KING, dir=-1)) + x = x + l.XS + x, y, = l.XM, y + l.YS + for i in range(8): + s.reserves.append(BasicRowStack(x, y, self, max_accept=0)) + x = x + l.XS + x, y = l.XM + 3*l.XS, y + 2*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "sw") + x = x + l.XS + s.waste = Alhambra_Waste(x, y, self, mod=13, max_accept=1) + l.createText(s.waste, "se") + + # define stack-groups (non default) + s.rows.append(s.waste) + l.defaultStackGroups() + + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move one Aces and Kings of first deck to top of the Talon (i.e. first card to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.deck == 0 and c.rank in (0, 12), (c.rank, c.suit)), 8) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + for i in range(3): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealCards() + + +# /*********************************************************************** +# // Carpet +# ************************************************************************/ + +class Carpet(Game): + Foundation_Class = SS_FoundationStack + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 9*l.XS, l.YM + 4*l.YS) + + # create stacks + for i in range(4): + for j in range(5): + x, y = l.XM + (j+3)*l.XS, l.YM + i*l.YS + s.rows.append(ReserveStack(x, y, self)) + for i in range(4): + dx, dy = ((2,1), (8,1), (2,2), (8,2))[i] + x, y = l.XM + dx*l.XS, l.YM + dy*l.YS + s.foundations.append(self.Foundation_Class(x, y, self, i)) + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "se") + y = y + l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // British Constitution +# ************************************************************************/ + +class BritishConstitution_RowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not AC_RowStack.acceptsCards(self, from_stack, cards): + return False + if self in self.game.s.rows[:8] and from_stack in self.game.s.rows[8:16]: + return True + if self in self.game.s.rows[8:16] and from_stack in self.game.s.rows[16:24]: + return True + if self in self.game.s.rows[16:24] and from_stack in self.game.s.rows[24:]: + return True + if self in self.game.s.rows[24:] and from_stack is self.game.s.waste: + return True + return False + + +class BritishConstitution_Foundation(SS_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return False + if from_stack in self.game.s.rows[:8]: + return True + return False + + +class BritishConstitution(Game): + RowStack_Class = BritishConstitution_RowStack + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 9*l.XS, l.YM + 5*l.YS) + + # create stacks + x, y = l.XM+l.XS, l.YM + for i in range(8): + s.foundations.append(BritishConstitution_Foundation(x, y, self, suit=int(i/2))) + x += l.XS + + y = l.YM+l.YS + for i in range(4): + x = l.XM+l.XS + for j in range(8): + stack = self.RowStack_Class(x, y, self, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0 + s.rows.append(stack) + x += l.XS + y += l.YS + + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "s") + y += l.YS+2*l.YM + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "s") + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.rank == ACE, c.suit)) + + def fillStack(self, stack): + if not stack.cards: + if stack in self.s.rows[:24]: + return + old_state = self.enterState(self.S_FILL) + if stack is self.s.waste and self.s.talon.cards: + self.s.talon.dealCards() + elif stack in self.s.rows[24:] and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + +class NewBritishConstitution(BritishConstitution): + RowStack_Class = StackWrapper(BritishConstitution_RowStack, base_rank=JACK) + + + +# /*********************************************************************** +# // Twenty +# ************************************************************************/ + +class Twenty_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + #if not BasicRowStack.acceptsCards(self, from_stack, cards): + # return False + return len(self.cards) == 0 + +class Twenty(Game): + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+10*l.XS, l.YM+3*l.XS+10*l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + s.talon = DealRowTalonStack(x, y, self) + l.createText(s.talon, 'se') + x += 2*l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x += l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=KING, dir=-1)) + x += l.XS + + for y in (l.YM+l.YS, l.YM+2*l.XS+5*l.YOFFSET): + x = l.XM + for i in range(10): + s.rows.append(Twenty_RowStack(x, y, self)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (ACE, KING) and c.deck == 1, (c.rank, c.suit))) + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + + def fillStack(self, stack): + if not stack.cards and stack in self.s.rows and self.s.talon.cards: + old_state = self.enterState(self.S_FILL) + self.flipMove(self.s.talon) + self.s.talon.moveMove(1, stack) + self.leaveState(old_state) + + + +# register the game +registerGame(GameInfo(54, RoyalCotillion, "Royal Cotillion", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(55, OddAndEven, "Odd and Even", + GI.GT_2DECK_TYPE, 2, 1)) +registerGame(GameInfo(143, Kingdom, "Kingdom", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(234, Alhambra, "Alhambra", + GI.GT_2DECK_TYPE, 2, 2)) +registerGame(GameInfo(97, Carpet, "Carpet", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(391, BritishConstitution, "British Constitution", + GI.GT_2DECK_TYPE, 2, 0, + ranks=range(11) # without Queens and Kings + )) +registerGame(GameInfo(392, NewBritishConstitution, "New British Constitution", + GI.GT_2DECK_TYPE, 2, 0, + ranks=range(11) # without Queens and Kings + )) +registerGame(GameInfo(443, Twenty, "Twenty", + GI.GT_2DECK_TYPE, 2, 0)) + diff --git a/pysollib/games/royaleast.py b/pysollib/games/royaleast.py new file mode 100644 index 0000000000..076350b986 --- /dev/null +++ b/pysollib/games/royaleast.py @@ -0,0 +1,123 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Royal East +# ************************************************************************/ + +class RoyalEast(Game): + Hint_Class = CautiousDefaultHint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 5.5*l.XS, l.YM + 4*l.YS) + + # extra settings + self.base_card = None + + # create stacks + for i in range(4): + dx, dy = ((0, 0), (2, 0), (0, 2), (2, 2))[i] + x, y = l.XM + (2*dx+5)*l.XS/2, l.YM + (2*dy+1)*l.YS/2 + stack = SS_FoundationStack(x, y, self, i, mod=13, max_move=0) + stack.CARD_YOFFSET = 0 + s.foundations.append(stack) + for i in range(5): + dx, dy = ((1, 0), (0, 1), (1, 1), (2, 1), (1, 2))[i] + x, y = l.XM + (2*dx+5)*l.XS/2, l.YM + (2*dy+1)*l.YS/2 + stack = RK_RowStack(x, y, self, mod=13, max_move=1) + stack.CARD_YOFFSET = 0 + s.rows.append(stack) + x, y = l.XM, l.YM + 3*l.YS/2 + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.base_card = self.s.talon.cards[-1] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + # deal base card to Foundations + c = self.s.talon.getCard() + to_stack = self.s.foundations[c.suit * self.gameinfo.decks] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack, frames=0) + # deal rows + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + +# register the game +registerGame(GameInfo(93, RoyalEast, "Royal East", + GI.GT_1DECK_TYPE, 1, 0)) + diff --git a/pysollib/games/siebenbisas.py b/pysollib/games/siebenbisas.py new file mode 100644 index 0000000000..a722b9cdc9 --- /dev/null +++ b/pysollib/games/siebenbisas.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# -*- mode: python; coding: iso8859-1; -*- +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class SiebenBisAs_Hint(CautiousDefaultHint): + def computeHints(self): + game = self.game + freerows = filter(lambda s: not s.cards, game.s.rows) + # for each stack + for r in game.sg.dropstacks: + if not r.cards: + continue + assert len(r.cards) == 1 and r.cards[-1].face_up + c, pile, rpile = r.cards[0], r.cards, [] + # try if we can drop the card + t, ncards = r.canDropCards(self.game.s.foundations) + if t: + score, color = 0, None + score, color = self._getDropCardScore(score, color, r, t, ncards) + self.addHint(score, ncards, r, t, color) + # try if we can move the card + for t in freerows: + if self.shallMovePile(r, t, pile, rpile): + # FIXME: this scoring + score = 50000 + self.addHint(score, 1, r, t) + + +# /*********************************************************************** +# // Sieben bis As (Seven to Ace) +# ************************************************************************/ + +class SiebenBisAs_Foundation(SS_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts only a card from a rowstack with an empty + # left neighbour + if not from_stack in self.game.s.rows: + return 0 + if from_stack.id % 10 == 0: + return 0 + return len(self.game.s.rows[from_stack.id - 1].cards) == 0 + + +class SiebenBisAs_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + if self.id % 10 != 0: + # left neighbour + s = self.game.s.rows[self.id - 1] + if s.cards and s.cards[-1].suit == cards[0].suit and (s.cards[-1].rank + 1) % 13 == cards[0].rank: + return 1 + if self.id % 10 != 10 - 1: + # right neighbour + s = self.game.s.rows[self.id + 1] + if s.cards and s.cards[-1].suit == cards[0].suit and (s.cards[-1].rank - 1) % 13 == cards[0].rank: + return 1 + return 0 + + # bottom to get events for an empty stack + ###prepareBottom = Stack.prepareInvisibleBottom + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class SiebenBisAs(Game): + Hint_Class = SiebenBisAs_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM + 10*l.XS, l.YM + 5*l.YS) + + # create stacks + for i in range(3): + for j in range(10): + x, y, = l.XM + j*l.XS, l.YM + (i+1)*l.YS + s.rows.append(SiebenBisAs_RowStack(x, y, self, max_accept=1, max_cards=1)) + for i in range(2): + x, y, = l.XM + (i+4)*l.XS, l.YM + s.reserves.append(ReserveStack(x, y, self, max_accept=0)) + for i in range(4): + x, y, = l.XM + (i+3)*l.XS, l.YM + 4*l.YS + s.foundations.append(SiebenBisAs_Foundation(x, y, self, i, base_rank=6, mod=13, max_move=0)) + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + stacks = filter(lambda r: r.cards[-1].rank == 6, self.s.rows) + for r in stacks: + self.moveMove(1, r, self.s.foundations[r.cards[-1].suit]) + + +# /*********************************************************************** +# // Maze +# ************************************************************************/ + +class Maze_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # left neighbour + s = self.game.s.rows[(self.id - 1) % 54] + if s.cards: + if s.cards[-1].suit == cards[0].suit and s.cards[-1].rank + 1 == cards[0].rank: + return 1 + if s.cards[-1].rank == QUEEN and cards[0].rank == ACE: + return 1 + # right neighbour + s = self.game.s.rows[(self.id + 1) % 54] + if s.cards: + if s.cards[-1].suit == cards[0].suit and s.cards[-1].rank - 1 == cards[0].rank: + return 1 + return 0 + + # bottom to get events for an empty stack + prepareBottom = Stack.prepareInvisibleBottom + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class Maze(Game): + GAME_VERSION = 2 + + Hint_Class = SiebenBisAs_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self, XM=4, YM=4), self.s + + # set window + self.setSize(l.XM + 9*l.XS, l.YM + 6*l.YS) + + # create stacks + for i in range(6): + for j in range(9): + x, y, = l.XM + j*l.XS, l.YM + i*l.YS + s.rows.append(Maze_RowStack(x, y, self, max_accept=1, max_cards=1)) + ##s.talon = InitialDealTalonStack(-2*l.XS, l.YM+5*l.YS/2, self) + s.talon = InitialDealTalonStack(self.width-l.XS+1, self.height-l.YS, self) + # create an invisble stack to hold the four Kings + s.internals.append(InvisibleStack(self)) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + frames = 0 + for i in range(54): +## if i == 8 or i == 17: # these stay empty + if i >= 52: # these stay empty + continue + c = self.s.talon.cards[-1] + if c.rank == KING: + self.s.talon.dealRow(rows=self.s.internals, frames=0) + else: + if frames == 0 and i >= 36: + self.startDealSample() + frames = -1 + self.s.talon.dealRow(rows=(self.s.rows[i],), frames=frames) + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + rows = filter(lambda s: s.cards, self.s.rows) + if len(rows) != 48: + return 0 # no cards dealt yet + i = 0 + if 1: + # allow wrap around: search first Ace + while rows[i].cards[-1].rank != ACE: + i = i + 1 + rows = rows + rows + # now check for 4 sequences + for j in (i+0, i+12, i+24, i+36): + r1 = rows[j] + r2 = rows[j+11] + if (r2.id - r1.id) % 54 != 11: + # found a space within the sequence + return 0 + if r1.cards[-1].rank != ACE or r2.cards[-1].rank != QUEEN: + return 0 + pile = getPileFromStacks(rows[j:j+12]) + if not pile or not isSameSuitSequence(pile, dir=1): + return 0 + return 1 + + +# register the game +registerGame(GameInfo(118, SiebenBisAs, "Sieben bis As", + GI.GT_MONTANA | GI.GT_OPEN, 1, 0, + ranks=(0, 6, 7, 8, 9, 10, 11, 12))) +registerGame(GameInfo(144, Maze, "Maze", + GI.GT_MONTANA | GI.GT_OPEN, 1, 0, + si={"ncards": 48})) + diff --git a/pysollib/games/simplex.py b/pysollib/games/simplex.py new file mode 100644 index 0000000000..7a3cc4fc56 --- /dev/null +++ b/pysollib/games/simplex.py @@ -0,0 +1,103 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + + +# /*********************************************************************** +# // Simplex +# ************************************************************************/ + +def isSameRankSequence(cards): + c0 = cards[0] + for c in cards[1:]: + if c0.rank != c.rank: + return False + return True + + +class Simplex_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if len(cards) != 4: + return False + return isSameRankSequence(cards) + + +class Simplex_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isSameRankSequence(cards) + + +class Simplex(Game): + + def createGame(self, reserves=6): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+10*l.XS, l.YM+2*l.YS+9*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + x += l.XS + s.waste = WasteStack(x, y, self) + x += l.XS + stack = Simplex_Foundation(x, y, self, + suit=ANY_SUIT, base_rank=ANY_RANK, max_cards=52) + xoffset = (self.width-3*l.XS)/51 + stack.CARD_XOFFSET, stack.CARD_YOFFSET = xoffset, 0 + s.foundations.append(stack) + x, y = l.XM, l.YM+l.YS + for i in range(9): + s.rows.append(Simplex_RowStack(x, y, self)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank == card2.rank + + +# register the game +registerGame(GameInfo(436, Simplex, "Simplex", + GI.GT_1DECK_TYPE, 1, 0)) diff --git a/pysollib/games/special/__init__.py b/pysollib/games/special/__init__.py new file mode 100644 index 0000000000..7e17ccf0ae --- /dev/null +++ b/pysollib/games/special/__init__.py @@ -0,0 +1,4 @@ +import hanoi +import memory +import pegged +import tarock diff --git a/pysollib/games/special/hanoi.py b/pysollib/games/special/hanoi.py new file mode 100644 index 0000000000..8b39c70fe1 --- /dev/null +++ b/pysollib/games/special/hanoi.py @@ -0,0 +1,165 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Tower of Hanoy +# ************************************************************************/ + +class TowerOfHanoy_Hint(CautiousDefaultHint): + # FIXME: demo is completely clueless + pass + + +class TowerOfHanoy_RowStack(BasicRowStack): + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return 1 + return self.cards[-1].rank > cards[0].rank + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class TowerOfHanoy(Game): + RowStack_Class = TowerOfHanoy_RowStack + Hint_Class = TowerOfHanoy_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to XX cards are fully playable in default window size) + h = max(2*l.YS, l.YS + (len(self.cards)-1)*l.YOFFSET + l.YM) + self.setSize(l.XM + 5*l.XS, l.YM + l.YS + h) + + # create stacks + for i in range(3): + x, y, = l.XM + (i+1)*l.XS, l.YM + s.rows.append(self.RowStack_Class(x, y, self, max_accept=1, max_move=1)) + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + for i in range(3): + self.s.talon.dealRow() + + def isGameWon(self): + for s in self.s.rows: + if len(s.cards) == len(self.cards): + return 1 + return 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank + + def getAutoStacks(self, event=None): + return ((), (), self.sg.dropstacks) + + +# /*********************************************************************** +# // Hanoi Puzzle +# ************************************************************************/ + +class HanoiPuzzle_RowStack(TowerOfHanoy_RowStack): + def getBottomImage(self): + if self.id == len(self.game.s.rows) - 1: + return self.game.app.images.getSuitBottom() + return self.game.app.images.getReserveBottom() + + +class HanoiPuzzle4(TowerOfHanoy): + RowStack_Class = HanoiPuzzle_RowStack + + def _shuffleHook(self, cards): + # no shuffling + return self._shuffleHookMoveToTop(cards, lambda c: (1, -c.id)) + + def startGame(self): + self.startDealSample() + for i in range(len(self.cards)): + self.s.talon.dealRow(rows=self.s.rows[:1]) + + def isGameWon(self): + return len(self.s.rows[-1].cards) == len(self.cards) + + +class HanoiPuzzle5(HanoiPuzzle4): + pass + + +class HanoiPuzzle6(HanoiPuzzle4): + pass + + +# register the game +registerGame(GameInfo(124, TowerOfHanoy, "Tower of Hanoy", + GI.GT_PUZZLE_TYPE, 1, 0, + suits=(2,), ranks=range(9))) +registerGame(GameInfo(207, HanoiPuzzle4, "Hanoi Puzzle 4", + GI.GT_PUZZLE_TYPE, 1, 0, + suits=(2,), ranks=range(4), + rules_filename="hanoipuzzle.html")) +registerGame(GameInfo(208, HanoiPuzzle5, "Hanoi Puzzle 5", + GI.GT_PUZZLE_TYPE, 1, 0, + suits=(2,), ranks=range(5), + rules_filename="hanoipuzzle.html")) +registerGame(GameInfo(209, HanoiPuzzle6, "Hanoi Puzzle 6", + GI.GT_PUZZLE_TYPE, 1, 0, + suits=(2,), ranks=range(6), + rules_filename="hanoipuzzle.html")) + diff --git a/pysollib/games/special/memory.py b/pysollib/games/special/memory.py new file mode 100644 index 0000000000..2d1e18f1c7 --- /dev/null +++ b/pysollib/games/special/memory.py @@ -0,0 +1,324 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Memory_RowStack(OpenStack): + def clickHandler(self, event): + game = self.game + if len(self.cards) != 1 or self.cards[-1].face_up: + return 1 + if game.other_stack is None: + game.playSample("flip", priority=5) + self.flipMove() + game.other_stack = self + else: + assert len(game.other_stack.cards) == 1 and game.other_stack.cards[-1].face_up + c1, c2 = self.cards[-1], game.other_stack.cards[0] + self.flipMove() + if self.game.cardsMatch(c1, c2): + self._dropPairMove(1, game.other_stack) + else: + game.playSample("flip", priority=5) + game.score = game.score - 1 + game.updateStatus(moves=game.moves.index+1) # update moves now + game.updateText() + game.canvas.update_idletasks() + game.sleep(0.5) + game.other_stack.flipMove() + game.canvas.update_idletasks() + game.sleep(0.2) + self.flipMove() + game.other_stack = None + self.game.finishMove() + return 1 + + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + game.playSample("droppair", priority=200) + game.closed_cards = game.closed_cards - 2 + game.score = game.score + 5 + + rightclickHandler = clickHandler + doubleclickHandler = clickHandler + + def controlclickHandler(self, event): + return 0 + def shiftclickHandler(self, event): + return 0 + + +# /*********************************************************************** +# // Memory +# ************************************************************************/ + +class Memory24(Game): + Hint_Class = None + + COLUMNS = 6 + ROWS = 4 + WIN_SCORE = 40 + PERFECT_SCORE = 60 # 5 * (6*4)/2 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # game extras + self.other_stack = None + self.closed_cards = -1 + self.score = 0 + + # create text + x, y = l.XM, self.ROWS*l.YS + if self.preview <= 1: + self.texts.score = MfxCanvasText(self.canvas, x, y, anchor="sw", + font=self.app.getFont("canvas_large")) + x = self.texts.score.bbox()[1][0] + 16 + + # set window + w = max(2*l.XS, x) + self.setSize(l.XM + w + self.COLUMNS*l.XS, l.YM + self.ROWS*l.YS) + + # create stacks + for i in range(self.ROWS): + for j in range(self.COLUMNS): + x, y = l.XM + w + j*l.XS, l.YM + i*l.YS + s.rows.append(Memory_RowStack(x, y, self, + max_move=0, max_accept=0, max_cards=1)) + x, y = l.XM, l.YM + s.talon = InitialDealTalonStack(x, y, self) + l.createText(s.talon, anchor="nn", text_format="%D") + s.internals.append(InvisibleStack(self)) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + n = self.COLUMNS * self.ROWS + assert len(self.s.talon.cards) == n + self.other_stack = None + self.closed_cards = n + self.score = 0 + self.updateText() + n = n - self.COLUMNS + self.s.talon.dealRow(rows=self.s.rows[:n], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[n:], flip=0) + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + return self.closed_cards == 0 and self.score >= self.WIN_SCORE + + def getAutoStacks(self, event=None): + return ((), (), ()) + + # + # scoring + # + + def updateText(self): + if self.preview > 1 or not self.texts.score: + return + t = "" + if self.closed_cards: + t = _("Points: %d") % self.score + else: + if self.score >= self.WIN_SCORE: + t = _("WON\n\n") + t = t + _("Total: %d") % self.score + self.texts.score.config(text=t) + + def getGameScore(self): + return self.score + + # Memory special: check score for a perfect game + def getWinStatus(self): + won, status, updated = Game.getWinStatus(self) + if status == 2 and self.score < self.PERFECT_SCORE: + return won, 1, self.U_WON + return won, status, updated + + # + # game extras + # + + def cardsMatch(self, card1, card2): + return card1.suit == card2.suit and card1.rank == card2.rank + + def canSaveGame(self): + return 0 + + def canUndo(self): + return 0 + + def _restoreGameHook(self, game): + if game.loadinfo.other_stack_id >= 0: + self.other_stack = self.allstacks[game.loadinfo.other_stack_id] + else: + self.other_stack = None + self.closed_cards = game.loadinfo.closed_cards + self.score = game.loadinfo.score + + def _loadGameHook(self, p): + self.loadinfo.addattr(other_stack_id=p.load()) + self.loadinfo.addattr(closed_cards=p.load()) + self.loadinfo.addattr(score=p.load()) + + def _saveGameHook(self, p): + if self.other_stack: + p.dump(self.other_stack.id) + else: + p.dump(-1) + p.dump(self.closed_cards) + p.dump(self.score) + + +class Memory30(Memory24): + COLUMNS = 6 + ROWS = 5 + WIN_SCORE = 45 + PERFECT_SCORE = 75 # 5 * (6*5)/2 + + +class Memory40(Memory24): + COLUMNS = 8 + ROWS = 5 + WIN_SCORE = 50 + PERFECT_SCORE = 100 # 5 * (8*5)/2 + + +# /*********************************************************************** +# // Concentration +# ************************************************************************/ + +class Concentration_RowStack(Memory_RowStack): + def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): + game = self.game + game.playSample("droppair", priority=200) + game.closed_cards = game.closed_cards - 2 + game.score = game.score + 5 + # + old_state = game.enterState(game.S_FILL) + f = game.s.talon + game.moveMove(n, self, f, frames=frames, shadow=shadow) + game.moveMove(n, other_stack, f, frames=frames, shadow=shadow) + game.leaveState(old_state) + + +class Concentration(Memory24): + COLUMNS = 13 + ROWS = 4 + WIN_SCORE = 50 + PERFECT_SCORE = 130 # 5 * (13*4)/2 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self, XM=4), self.s + + # game extras + self.other_stack = None + self.closed_cards = -1 + self.score = 0 + + # set window + self.setSize(l.XM + self.COLUMNS*l.XS, l.YM + (self.ROWS+1)*l.YS) + + # create stacks + for i in range(self.ROWS): + for j in range(self.COLUMNS): + x, y = l.XM + j*l.XS, l.YM + i*l.YS + s.rows.append(Concentration_RowStack(x, y, self, + max_move=0, max_accept=0, max_cards=1)) + x, y = l.XM + self.COLUMNS*l.XS/2, self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + l.createText(s.talon, dx=-10, anchor="sw", text_format="%D") + + # create text + x, y = l.XM, self.height - l.YM + if self.preview <= 1: + self.texts.score = MfxCanvasText(self.canvas, x, y, + anchor="sw", + font=self.app.getFont("canvas_large")) + + # define stack-groups + l.defaultStackGroups() + + # + # game extras + # + + def cardsMatch(self, card1, card2): + return card1.rank == card2.rank + + +# register the game +registerGame(GameInfo(176, Memory24, "Memory 24", + GI.GT_MEMORY | GI.GT_SCORE, 2, 0, + suits=(0,2), ranks=(0,8,9,10,11,12))) +registerGame(GameInfo(219, Memory30, "Memory 30", + GI.GT_MEMORY | GI.GT_SCORE, 2, 0, + suits=(0,2,3), ranks=(0,9,10,11,12))) +registerGame(GameInfo(177, Memory40, "Memory 40", + GI.GT_MEMORY | GI.GT_SCORE, 2, 0, + suits=(0,2), ranks=(0,4,5,6,7,8,9,10,11,12))) +registerGame(GameInfo(178, Concentration, "Concentration", + GI.GT_MEMORY | GI.GT_SCORE, 1, 0)) + diff --git a/pysollib/games/special/pegged.py b/pysollib/games/special/pegged.py new file mode 100644 index 0000000000..08d304b4ef --- /dev/null +++ b/pysollib/games/special/pegged.py @@ -0,0 +1,261 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Pegged_Hint(AbstractHint): + # FIXME: no intelligence whatsoever is implemented here + def computeHints(self): + game = self.game + # get free stacks + stacks = filter(lambda r: not r.cards, game.s.rows) + # + for t in stacks: + for dx, dy in game.STEPS: + r = game.map.get((t.pos[0] + dx, t.pos[1] + dy)) + if not r or not r.cards or not t.acceptsCards(r, r.cards): + continue + # braindead scoring... + score = 10000 + game.app.miscrandom.randint(0, 9999) + self.addHint(score, 1, r, t) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Pegged_RowStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + return self._getMiddleStack(from_stack) is not None + + def canDropCards(self, stacks): + return (None, 0) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + other_stack = to_stack._getMiddleStack(self) + old_state = self.game.enterState(self.game.S_FILL) + f = self.game.s.foundations[0] + self.game.moveMove(ncards, self, to_stack, frames=0) + self.game.playSample("drop", priority=200) + self.game.moveMove(ncards, other_stack, f, frames=-1, shadow=shadow) + self.game.leaveState(old_state) + self.fillStack() + other_stack.fillStack() + + def _getMiddleStack(self, from_stack): + dx, dy = from_stack.pos[0] - self.pos[0], from_stack.pos[1] - self.pos[1] + if not self.game.STEP_MAP.get((dx, dy)): + return None + s = self.game.map.get((self.pos[0] + dx/2, self.pos[1] + dy/2)) + if not s or not s.cards: + return None + return s + + def copyModel(self, clone): + ReserveStack.copyModel(self, clone) + clone.pos = self.pos + + +# /*********************************************************************** +# // Pegged +# ************************************************************************/ + +class Pegged(Game): + Hint_Class = Pegged_Hint + + STEPS = ((-4, 0), (4, 0), (0, -4), (0, 4)) + ROWS = (3, 5, 7, 7, 7, 5, 3) + EMPTY_STACK_ID = -1 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + n = m = max(self.ROWS) + if self.ROWS[0] == m or self.ROWS[-1] == m: + n = n + 1 + self.setSize(l.XM + n*l.XS, l.YM + len(self.ROWS)*l.YS) + + # game extras 1) + self.map = {} + + # create stacks + for i in range(len(self.ROWS)): + r = self.ROWS[i] + for j in range(r): + d = m - r + 2*j + x, y = l.XM + d*l.XS/2, l.YM + i*l.YS + stack = Pegged_RowStack(x, y, self) + stack.pos = (d, 2*i) + ##print stack.id, stack.pos + s.rows.append(stack) + self.map[stack.pos] = stack + x, y = self.width - l.XS, l.YM + s.foundations.append(AbstractFoundationStack(x, y, self, ANY_SUIT, max_move=0, max_accept=0)) + l.createText(s.foundations[0], "ss") + y = self.height - l.YS + s.talon = InitialDealTalonStack(x, y, self) + s.internals.append(InvisibleStack(self)) + + # game extras 2) + self.STEP_MAP = {} + for step in self.STEPS: + self.STEP_MAP[step] = 1 + if self.EMPTY_STACK_ID < 0: + self.EMPTY_STACK_ID = len(s.rows) / 2 + + # Define stack groups + l.defaultStackGroups() + + + # + # game overrides + # + + def startGame(self): + n = len(self.cards) - len(self.s.rows) + 1 + if n > 0: + self.moveMove(n, self.s.talon, self.s.internals[0], frames=0) + self.startDealSample() + rows = list(self.s.rows[:]) + rows.remove(rows[self.EMPTY_STACK_ID]) + self.s.talon.dealRow(rows=rows, frames=4) + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + c = 0 + for s in self.s.foundations: + c = c + len(s.cards) + return c + 2 == self.gameinfo.si.ncards + + def getAutoStacks(self, event=None): + return ((), (), ()) + + # Pegged special: check for a perfect game + def getWinStatus(self): + won, status, updated = Game.getWinStatus(self) + if status == 2: + stacks = filter(lambda r: r.cards, self.s.rows) + assert len(stacks) == 1 + if stacks[0].id != self.EMPTY_STACK_ID: + # not perfect + return won, 1, self.U_WON + return won, status, updated + + # Pegged special: highlight all moveable cards + def getHighlightPilesStacks(self): + rows = [] + for r in self.s.rows: + if not r.cards: + continue + rx, ry = r.pos + for dx, dy in self.STEPS: + s = self.map.get((rx + dx, ry + dy)) + if s and not s.cards: + m = self.map.get((rx + dx/2, ry + dy/2)) + if m and m.cards: + rows.append(r) + return ((rows, 1),) + + +class PeggedCross1(Pegged): + ROWS = (3, 3, 7, 7, 7, 3, 3) + +class PeggedCross2(Pegged): + ROWS = (3, 3, 3, 9, 9, 9, 3, 3, 3) + +class Pegged6x6(Pegged): + EMPTY_STACK_ID = 14 + ROWS = (6, 6, 6, 6, 6, 6) + +class Pegged7x7(Pegged): + ROWS = (7, 7, 7, 7, 7, 7, 7) + + +# /*********************************************************************** +# // Pegged Triangle +# ************************************************************************/ + +class PeggedTriangle1(Pegged): + STEPS = ((-2, -4), (-2, 4), (-4, 0), (4, 0), (2, -4), (2, 4)) + ROWS = (1, 2, 3, 4, 5) + EMPTY_STACK_ID = 4 + +class PeggedTriangle2(PeggedTriangle1): + ROWS = (1, 2, 3, 4, 5, 6) + + +# /*********************************************************************** +# // register the games +# ************************************************************************/ + +def r(id, gameclass, name): + si_ncards = 0 + for n in gameclass.ROWS: + si_ncards = si_ncards + n + gi = GameInfo(id, gameclass, name, + GI.GT_PUZZLE_TYPE, 1, 0, + si={"ncards": si_ncards}, + rules_filename = "pegged.html") + registerGame(gi) + return gi + +r(180, Pegged, "Pegged") +r(181, PeggedCross1, "Pegged Cross 1") +r(182, PeggedCross2, "Pegged Cross 2") +r(183, Pegged6x6, "Pegged 6x6") +r(184, Pegged7x7, "Pegged 7x7") +r(210, PeggedTriangle1, "Pegged Triangle 1") +r(211, PeggedTriangle2, "Pegged Triangle 2") + +del r diff --git a/pysollib/games/special/tarock.py b/pysollib/games/special/tarock.py new file mode 100644 index 0000000000..f3bd38fc80 --- /dev/null +++ b/pysollib/games/special/tarock.py @@ -0,0 +1,943 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +## T. Kirk +## +## http://www.inetarena.com/~grania +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +from pysollib.games.braid import Braid_Foundation, Braid_BraidStack, \ + Braid_RowStack, Braid_ReserveStack, Braid +from pysollib.games.bakersdozen import Cruel_Talon + + +# /*********************************************************************** +# // Tarock Talon Stacks +# ************************************************************************/ + +class Wicked_Talon(Cruel_Talon): + pass + + +# /*********************************************************************** +# // Tarock Foundation Stacks +# ************************************************************************/ + +class ImperialTrump_Foundation(SS_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + return cards[-1].rank < len(self.game.s.foundations[4].cards) + + +class Ponytail_Foundation(Braid_Foundation): + pass + + +# /*********************************************************************** +# // Tarock Row Stacks +# ************************************************************************/ + +class Tarock_OpenStack(OpenStack): + def __init__(self, x, y, game, yoffset=-1, **cap): + kwdefault(cap, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS) + apply(OpenStack.__init__, (self, x, y, game), cap) + if yoffset < 0: + yoffset = game.app.images.CARD_YOFFSET + self.CARD_YOFFSET = yoffset + + +class Tarock_AC_RowStack(Tarock_OpenStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + return 1 + if cards[0].rank != self.cards[-1].rank - 1: + return 0 + elif cards[0].color == 2 or self.cards[-1].color == 2: + return 1 + else: + return cards[0].color != self.cards[-1].color + + +class Skiz_RowStack(RK_RowStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + if cards[0].suit == len(self.game.gameinfo.suits): + return cards[0].rank == len(self.game.gameinfo.trumps) - 1 + else: + return cards[0].rank == len(self.game.gameinfo.ranks) - 1 + return self.cards[-1].suit == cards[0].suit and self.cards[-1].rank - 1 == cards[0].rank + + +class Pagat_RowStack(RK_RowStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + return 1 + return self.cards[-1].suit == cards[0].suit and self.cards[-1].rank - 1 == cards[0].rank + + +class TrumpWild_RowStack(Tarock_OpenStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + if cards[0].suit == len(self.game.gameinfo.suits): + return cards[0].rank == len(self.game.gameinfo.trumps) - 1 + else: + return cards[0].rank == len(self.game.gameinfo.ranks) - 1 + if cards[0].rank != self.cards[-1].rank - 1: + return 0 + elif cards[0].color == 2 or self.cards[-1].color == 2: + return 1 + else: + return cards[0].color != self.cards[-1].color + + +class TrumpOnly_RowStack(Tarock_OpenStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + return cards[0].suit == len(self.game.gameinfo.suits) + return cards[0].color == 2 and cards[0].rank == self.cards[-1].rank - 1 + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class Excuse_RowStack(Tarock_OpenStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + return 0 + return cards[0].rank == self.cards[-1].rank - 1 + + +class WheelOfFortune_RowStack(Tarock_OpenStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.cards: + return 1 + return ((cards[0].suit == self.cards[-1].suit) + and (cards[0].rank == self.cards[-1].rank - 1)) + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class Ponytail_PonytailStack(Braid_BraidStack): + pass + + +class Ponytail_RowStack(Braid_RowStack): + pass + + +class Ponytail_ReserveStack(Braid_ReserveStack): + pass + + +class Cavalier_RowStack(Tarock_AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not Tarock_AC_RowStack.acceptsCards(self, from_stack, cards): + return 0 + return self.cards or len(cards) == 1 + + def canMoveCards(self, cards): + for i in range(len(cards) - 1): + if not cards[i].suit == 4: + if cards[i].color == cards[i + 1].color: + return 0 + if cards[i].rank - 1 != cards[i + 1].rank: + return 0 + return 1 + + +class Nasty_RowStack(SS_RowStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if self.cards: + return (cards[0].rank == self.cards[-1].rank - 1 + and cards[0].suit == self.cards[-1].suit) + return cards[0].rank == 13 + 8 * (cards[0].suit == 4) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Tarock_GameMethods: + SUITS = (_("Wand"), _("Sword"), _("Cup"), _("Coin"), _("Trump")) + RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", "10", + _("Page"), _("Valet"), _("Queen"), _("King")) + + def getCardFaceImage(self, deck, suit, rank): + return self.app.images.getFace(deck, suit, rank) + + +class AbstractTarockGame(Tarock_GameMethods, Game): + pass + + +# /*********************************************************************** +# // Wheel of Fortune +# ************************************************************************/ + +class WheelOfFortune(AbstractTarockGame): + Hint_Class = CautiousDefaultHint + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM + l.XS * 11.5, l.YM + l.YS * 5.5) + + # Create wheel + xoffset = (1, 2, 3, 3.9, 3, 2, 1, 0, -1, -2, -3, + -3.9, -3, -2, -1, 0, -2, -1, 0, 1, 2) + yoffset = (0.2, 0.5, 1.1, 2.2, 3.3, 3.9, 4.2, 4.4, + 4.2, 3.9, 3.3, 2.2, 1.1, 0.5, 0.2, 0, + 1.8, 2.1, 2.2, 2.4, 2.6) + x = l.XM + l.XS * 4 + y = l.YM + for i in range(21): + x0 = x + xoffset[i] * l.XS + y0 = y + yoffset[i] * l.YS + s.rows.append(WheelOfFortune_RowStack(x0, y0, self, yoffset=l.CH/4, + max_cards=2, max_move=1, max_accept=1)) + self.setRegion(s.rows, (-999, -999, l.XS * 9, 999999)) + + # Create foundations + x = self.width - l.XS * 2 + y = l.YM + s.foundations.append(SS_FoundationStack(x, y, self, 0, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 1, max_cards=14)) + y = y + l.YS + s.foundations.append(SS_FoundationStack(x, y, self, 3, max_cards=14)) + x = x - l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 2, max_cards=14)) + x = x + l.XS * 0.5 + y = y + l.YS + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + + # Create talon + x = self.width - l.XS + y = self.height - l.YS * 1.5 + s.talon = WasteTalonStack(x, y, self, num_deal=2, max_rounds=1) + l.createText(s.talon, "nn") + x = x - l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "nn") + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 78 + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[-5:]) + self.s.talon.dealRow(rows=self.s.rows[4:-5]) + self.s.talon.dealRow(rows=self.s.rows[:4]) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return 0 + + +# /*********************************************************************** +# // Imperial Trumps +# ************************************************************************/ + +class ImperialTrumps(AbstractTarockGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM + l.XS * 8, l.YM + l.YS * 5) + + + # Create foundations + x = l.XM + l.XS * 3 + y = l.YM + for i in range(4): + s.foundations.append(ImperialTrump_Foundation(x, y, self, i, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + + # Create talon + x = l.XM + s.talon = WasteTalonStack(x, y, self, num_deal=1, max_rounds=-1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # Create rows + x = l.XM + y = l.YM + int(round(l.YS * 1.25)) + for i in range(8): + s.rows.append(TrumpWild_RowStack(x, y, self)) + x = x + l.XS + self.setRegion(s.rows, (-999, y, 999999, 999999)) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self, reverse=1): + assert len(self.s.talon.cards) == 78 + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow(reverse=reverse) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return 0 + + +# /*********************************************************************** +# // Pagat +# ************************************************************************/ + +class Pagat(AbstractTarockGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + h = max(3 * l.YS, 20 * l.YOFFSET) + self.setSize(l.XM + 12 * l.XS, l.YM + l.YS + h) + + # Create foundations + x = l.XM + l.XS * 3.5 + y = l.YM + s.foundations.append(SS_FoundationStack(x, y, self, 0, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 1, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 2, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 3, max_cards=14)) + + # Create reserves + x = l.XM + for i in range(3): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x = x + l.XS * 6 + for i in range(3): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + + # Create rows + x = l.XM + y = l.YM + l.YS * 1.1 + for i in range(12): + s.rows.append(Pagat_RowStack(x, y, self)) + x = x + l.XS + self.setRegion(s.rows, (-999, int(y), 999999, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 78 + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[3:9]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Skiz +# ************************************************************************/ + +class Skiz(AbstractTarockGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + h = max(3 * l.YS, 20 * l.YOFFSET) + self.setSize(l.XM + 12 * l.XS, l.YM + l.YS + h) + + # Create foundations + x = l.XM + l.XS * 3.5 + y = l.YM + s.foundations.append(SS_FoundationStack(x, y, self, 0, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 1, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 2, max_cards=14)) + x = x + l.XS + s.foundations.append(SS_FoundationStack(x, y, self, 3, max_cards=14)) + + # Create reserves + x = l.XM + for i in range(3): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x = x + l.XS * 6 + for i in range(3): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + + # Create rows + x = l.XM + y = l.YM + l.YS * 1.1 + for i in range(12): + s.rows.append(Skiz_RowStack(x, y, self)) + x = x + l.XS + self.setRegion(s.rows, (-999, int(y), 999999, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 78 + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[3:9]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Fifteen Plus +# ************************************************************************/ + +class FifteenPlus(AbstractTarockGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + h = max(5 * l.YS, 20 * l.YOFFSET) + self.setSize(l.XM + 9 * l.XS, l.YM + l.YS + h) + + # Create foundations + x = self.width - l.XS + y = l.YM + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + y = y + l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, max_cards=14)) + y = y + l.YS + + # Create rows + x = l.XM + y = l.YM + for j in range(2): + for i in range(8): + s.rows.append(Tarock_AC_RowStack(x, y, self, max_move=1, max_accept=1)) + x = x + l.XS + x = l.XM + y = y + l.YS * 3 + self.setRegion(s.rows, (-999, -999, l.XM + l.XS * 8, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 78 + for i in range(2): + self.s.talon.dealRow(flip=0, frames=0) + for i in range(2): + self.s.talon.dealRow(rows=self.s.rows[:15], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Excuse +# ************************************************************************/ + +class Excuse(AbstractTarockGame): + Hint_Class = CautiousDefaultHint + GAME_VERSION = 2 + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + h = max(5 * l.YS, 20 * l.YOFFSET) + self.setSize(l.XM + 9 * l.XS, l.YM + l.YS + h) + + # Create foundations + x = self.width - l.XS + y = l.YM + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + y = y + l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, max_cards=14)) + y = y + l.YS + + # Create rows + x = l.XM + y = l.YM + for j in range(2): + for i in range(8): + s.rows.append(Excuse_RowStack(x, y, self, + max_move=1, max_accept=1, base_rank=NO_RANK)) + x = x + l.XS + x = l.XM + y = y + l.YS * 3 + self.setRegion(s.rows, (-999, -999, l.XM + l.XS * 8, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def _shuffleHook(self, cards): + # move Kings to bottom of each stack (see Baker's Dozen) + def isKing(c): + return (c.suit < 4 and c.rank == 13) or (c.suit == 4 and c.rank == 21) + i, n = 0, len(self.s.rows) + kings = [] + for c in cards: + if isKing(c): + kings.append(i) + i = i + 1 + for i in kings: + j = i % n + while j < i: + if not isKing(cards[j]): + cards[i], cards[j] = cards[j], cards[i] + break + j = j + n + cards.reverse() + return cards + + def startGame(self): + assert len(self.s.talon.cards) == 78 + for i in range(3): + self.s.talon.dealRow(frames=0) + self.s.talon.dealRow(rows=self.s.rows[:15], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:15]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank + or card1.rank - 1 == card2.rank) + + +# /*********************************************************************** +# // Grasshopper +# // Double Grasshopper +# ************************************************************************/ + +class Grasshopper(AbstractTarockGame): + GAME_VERSION = 2 + MAX_ROUNDS = 2 + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + decks = self.gameinfo.decks + self.setSize(2*l.XM + (2 + 5*decks)*l.XS, 3*l.YM + 5*l.YS) + yoffset = min(l.YOFFSET, max(10, l.YOFFSET / 2)) + + # Create talon + x = l.XM + y = l.YM + s.talon = WasteTalonStack(x, y, self, num_deal=1, max_rounds=self.MAX_ROUNDS) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # Create foundations + x = x + l.XM + l.XS + for j in range(4): + for i in range(decks): + s.foundations.append(SS_FoundationStack(x, y, self, j, max_cards=14)) + x = x + l.XS + for i in range(decks): + s.foundations.append(SS_FoundationStack(x, y, self, 4, max_cards=22)) + x = x + l.XS + + # Create reserve + x = l.XM + y = l.YM * 3 + l.YS + s.reserves.append(OpenStack(x, y, self)) + s.reserves[0].CARD_YOFFSET = (l.YOFFSET, yoffset)[decks == 2] + + # Create rows + x = x + l.XM + l.XS + for i in range(decks): + s.rows.append(TrumpOnly_RowStack(x, y, self, yoffset=yoffset)) + x = x + l.XS + for i in range(4*decks+1): + s.rows.append(Tarock_AC_RowStack(x, y, self)) + x = x + l.XS + self.setRegion(s.rows, (-999, y - l.YS, 999999, 999999)) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + decks = self.gameinfo.decks + assert len(self.s.talon.cards) == 78 * decks + self.startDealSample() + for i in range(14 * decks): + self.s.talon.dealRow(rows=self.s.reserves, flip=0, frames=4) + self.s.reserves[0].flipMove() + self.s.talon.dealRow(rows = self.s.rows[decks:]) + self.s.talon.dealCards() # deal first card to WasteStack + + def fillStack(self, stack): + r = self.s.reserves[0] + if not stack.cards and stack in self.s.rows: + if r.cards and stack.acceptsCards(r, r.cards[-1:]): + r.moveMove(1, stack) + if r.canFlipCard(): + r.flipMove() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1 == card2.rank + or card1.rank - 1 == card2.rank) + and card1.color != card2.color) + + +class DoubleGrasshopper(Grasshopper): + pass + + +# /*********************************************************************** +# // Ponytail +# ************************************************************************/ + +class Ponytail(Tarock_GameMethods, Braid): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable - needed for Ponytail_PonytailStack) + h = max(5*l.YS + 30, l.YS+(self.BRAID_CARDS-1)*l.YOFFSET) + self.setSize(10*l.XS+l.XM, l.YM + h) + + # extra settings + self.base_card = None + + # create stacks + s.addattr(braid=None) # register extra stack variable + x, y = l.XM, l.YM + for i in range(2): + s.rows.append(Ponytail_RowStack(x + 0.5 * l.XS, y, self)) + s.rows.append(Ponytail_RowStack(x + 4.5 * l.XS, y, self)) + s.rows.append(Ponytail_RowStack(x + 5.5 * l.XS, y, self)) + s.rows.append(Ponytail_RowStack(x + 6.5 * l.XS, y, self)) + y = y + 4 * l.YS + y = l.YM + l.YS + for i in range(2): + s.rows.append(Ponytail_ReserveStack(x, y, self)) + s.rows.append(Ponytail_ReserveStack(x + l.XS, y, self)) + s.rows.append(Ponytail_ReserveStack(x, y + l.YS, self)) + s.rows.append(Ponytail_ReserveStack(x + l.XS, y + l.YS, self)) + s.rows.append(Ponytail_ReserveStack(x, y + 2 * l.YS, self)) + s.rows.append(Ponytail_ReserveStack(x + l.XS, y + 2 * l.YS, self)) + x = x + 4 * l.XS + x = l.XM + 5*l.XS/2 + y = l.YM + s.braid = Ponytail_PonytailStack(x, y, self, sine=1) + x = l.XM + 7 * l.XS + y = l.YM + 2*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + x = x - l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + x = l.XM + 8 * l.XS + y = l.YM + for i in range(4): + s.foundations.append(Ponytail_Foundation(x, y, self, i, mod=14, max_cards=14)) + s.foundations.append(Ponytail_Foundation(x + l.XS, y, self, i, mod=14, max_cards=14)) + y = y + l.YS + s.foundations.append(Ponytail_Foundation(x, y, self, 4, mod=22, max_cards=22)) + s.foundations.append(Ponytail_Foundation(x + l.XS, y, self, 4, mod=22, max_cards=22)) + # ??? + self.texts.info = MfxCanvasText(self.canvas, + x + l.CW + l.XM / 2, y + l.YS, + anchor="n", + font=self.app.getFont("canvas_default")) + + # define stack-groups + self.sg.openstacks = s.foundations + s.rows + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.dropstacks = [s.braid] + s.rows + [s.waste] + + +# /*********************************************************************** +# // Cavalier +# // Five Aces +# // Wicked +# // Nasty +# ************************************************************************/ + +class Cavalier(AbstractTarockGame): + Layout_Method = Layout.bakersDozenLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = Cavalier_RowStack + + # + # Game layout + # + + def createGame(self, **layout): + # Create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=18, playcards=19) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create foundations + for r in l.s.foundations: + n = 14 + 8 * (r.suit == 4) + s.foundations.append(self.Foundation_Class(r.x, r.y, self, r.suit, + mod=n, max_cards=n)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + + # Define stack groups + l.defaultAll() + + + # + # Game over rides + # + + def startGame(self, flip=(0, 1, 0), foundations=0): + assert len(self.s.talon.cards) == 78 + for f in flip: + self.s.talon.dealRow(flip=f, frames=0) + self.startDealSample() + self.s.talon.dealRow() + if foundations: + self.s.talon.dealRow(rows=self.s.rows[0:1]) + self.s.talon.dealRow(rows=self.s.foundations) + else: + self.s.talon.dealRow(rows=self.s.rows[:6]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1 == card2.rank + or card1.rank - 1 == card2.rank) + and ((card1.suit == 4 or card2.suit == 4) + or card1.color != card2.color)) + + +class FiveAces(Cavalier): + def _shuffleHook(self, cards): + return self._shuffleHookMoveToBottom(cards, lambda c: (c.rank == 0, c.suit)) + + def startGame(self): + Cavalier.startGame(self, foundations=1) + + +class Wicked(FiveAces): + Talon_Class = StackWrapper(Wicked_Talon, max_rounds=-1) + RowStack_Class = StackWrapper(SS_RowStack, max_move=1, max_accept=1, base_rank=NO_RANK) + Hint_Class = CautiousDefaultHint + + def startGame(self): + Cavalier.startGame(self, flip=(1, 1, 1), foundations=1) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1 == card2.rank + or card1.rank - 1 == card2.rank) + and card1.suit == card2.suit) + + +class Nasty(Wicked): + RowStack_Class = StackWrapper(Nasty_RowStack, max_move=1, max_accept=1, base_rank=ANY_RANK) + + +# /*********************************************************************** +# // register the games +# ************************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_TAROCK | GI.GT_CONTRIB | GI.GT_ORIGINAL + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + ranks=range(14), trumps=range(22)) + registerGame(gi) + return gi + +r(157, WheelOfFortune, "Wheel of Fortune", GI.GT_TAROCK, 1, 0) +r(158, ImperialTrumps, "Imperial Trumps", GI.GT_TAROCK, 1, -1) +r(159, Pagat, "Pagat", GI.GT_TAROCK | GI.GT_OPEN, 1, 0) +r(160, Skiz, "Skiz", GI.GT_TAROCK | GI.GT_OPEN, 1, 0) +r(161, FifteenPlus, "Fifteen plus", GI.GT_TAROCK, 1, 0) +r(162, Excuse, "Excuse", GI.GT_TAROCK | GI.GT_OPEN, 1, 0) +r(163, Grasshopper, "Grasshopper", GI.GT_TAROCK, 1, 1) +r(164, DoubleGrasshopper, "Double Grasshopper", GI.GT_TAROCK, 2, 1) +r(179, Ponytail, "Ponytail", GI.GT_TAROCK, 2, 2) +r(202, Cavalier, "Cavalier", GI.GT_TAROCK, 1, 0) +r(203, FiveAces, "Five Aces", GI.GT_TAROCK, 1, 0) +r(204, Wicked, "Wicked", GI.GT_TAROCK | GI.GT_OPEN, 1, -1) +r(205, Nasty, "Nasty", GI.GT_TAROCK | GI.GT_OPEN, 1, -1) + +del r + diff --git a/pysollib/games/spider.py b/pysollib/games/spider.py new file mode 100644 index 0000000000..06c3d5ea1e --- /dev/null +++ b/pysollib/games/spider.py @@ -0,0 +1,1051 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import SpiderType_Hint, YukonType_Hint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Spider_Hint(SpiderType_Hint): + # FIXME: demo is not too clever in this game + + BONUS_SAME_SUIT_MOVE = 400 + + def _preferHighRankMoves(self): + return 1 + + def shallMovePile(self, r, t, pile, rpile): + if not SpiderType_Hint.shallMovePile(self, r, t, pile, rpile): + return 0 + rr = self.ClonedStack(r, stackcards=rpile) + if rr.acceptsCards(t, pile): + # the pile we are going to move from r to t + # could be moved back from t ro r - this is + # dangerous for as we can create loops... + if len(t.cards) == 0: + return 1 + if pile[0].suit == t.cards[-1].suit: + # The pile will get moved onto the correct suit + if len(rpile) == 0 or pile[0].suit != rpile[-1].suit: + return 1 + if self.level <= 1 and len(rpile) == 0: + return 1 + return 0 + return 1 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Spider_SS_Foundation(AbstractFoundationStack): + def __init__(self, x, y, game, suit=ANY_SUIT, **cap): + kwdefault(cap, dir=-1, base_rank=KING, + min_accept=13, max_accept=13, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # now check the cards + return isSameSuitSequence(cards, self.cap.mod, self.cap.dir) + + +class Spider_AC_Foundation(Spider_SS_Foundation): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # now check the cards + return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir) + + +class Spider_RowStack(Spider_SS_RowStack): + def canDropCards(self, stacks): + if len(self.cards) < 13: + return (None, 0) + cards = self.cards[-13:] + for s in stacks: + if s is not self and s.acceptsCards(self, cards): + return (s, 13) + return (None, 0) + + +# /*********************************************************************** +# // Relaxed Spider +# ************************************************************************/ + +class RelaxedSpider(Game): + Layout_Method = Layout.klondikeLayout + Talon_Class = DealRowTalonStack + Foundation_Class = Spider_SS_Foundation + RowStack_Class = Spider_RowStack + Hint_Class = Spider_Hint + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=10, waste=0, texts=1, playcards=23) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + if l.s.waste: + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=ANY_SUIT)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + + def startGame(self, flip=0): + for i in range(4): + self.s.talon.dealRow(flip=flip, frames=0) + r = self.s.rows + rows = (r[0], r[3], r[6], r[9]) + self.s.talon.dealRow(rows=rows, flip=flip, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1) % stack1.cap.mod == card2.rank or + (card2.rank + 1) % stack1.cap.mod == card1.rank) + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + if to_stack.cards: + return int(from_stack.cards[-1].suit == to_stack.cards[-1].suit)+1 + return 0 + +# /*********************************************************************** +# // Spider +# ************************************************************************/ + +class Spider(RelaxedSpider): + def canDealCards(self): + if not RelaxedSpider.canDealCards(self): + return 0 + # no row may be empty + for r in self.s.rows: + if not r.cards: + return 0 + return 1 + +class Spider1Suit(Spider): + pass +class Spider2Suits(Spider): + pass + +class OpenSpider(Spider): + def startGame(self, flip=0): + Spider.startGame(self, flip=1) + + +# /*********************************************************************** +# // Black Widow +# ************************************************************************/ + +class BlackWidow_RowStack(RK_RowStack, Spider_RowStack): + def canDropCards(self, stacks): + return Spider_RowStack.canDropCards(self, stacks) + + +class BlackWidow(Spider): + RowStack_Class = BlackWidow_RowStack + + +# /*********************************************************************** +# // Scheidungsgrund (aka Ground for a Divorce) +# ************************************************************************/ + +class GroundForADivorce_Talon(TalonStack): + # A single click deals a new cards to each non-empty row. + def dealCards(self, sound=1): + if self.cards: + rows = filter(lambda r: r.cards, self.game.s.rows) + if not rows: + # deal one card to first row if all rows are emtpy + rows = self.game.s.rows[:1] + return self.dealRowAvail(rows=rows, sound=sound) + return 0 + + +class GroundForADivorce(RelaxedSpider): + Layout_Method = Layout.harpLayout + Talon_Class = GroundForADivorce_Talon + Foundation_Class = StackWrapper(Spider_SS_Foundation, base_rank=ANY_RANK, mod=13) + RowStack_Class = StackWrapper(Spider_RowStack, mod=13) + + def createGame(self): + RelaxedSpider.createGame(self, playcards=22) + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Grandmother's Game +# ************************************************************************/ + +class GrandmothersGame(RelaxedSpider): + Layout_Method = Layout.harpLayout + + def createGame(self): + RelaxedSpider.createGame(self, playcards=22) + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Spiderette (Spider with one deck and 7 rows) +# ************************************************************************/ + +class Spiderette(Spider): + def createGame(self): + Spider.createGame(self, rows=7, playcards=20) + + def startGame(self): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Baby Spiderette +# ************************************************************************/ + +class BabySpiderette(Spiderette): + RowStack_Class = BlackWidow_RowStack + + +# /*********************************************************************** +# // Will o' the Wisp (just like Spiderette) +# ************************************************************************/ + +class WillOTheWisp(Spiderette): + def startGame(self): + for i in range(2): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Simple Simon +# ************************************************************************/ + +class SimpleSimon(Spider): + Talon_Class = InitialDealTalonStack + + def createGame(self): + Spider.createGame(self, rows=10, texts=0) + + def startGame(self): + for l in (9, 8, 7, 6, 5, 4, 3): + self.s.talon.dealRow(rows=self.s.rows[:l], frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Rachel +# ************************************************************************/ + +class Rachel(RelaxedSpider): + Talon_Class = StackWrapper(WasteTalonStack, max_rounds=1) + RowStack_Class = RK_RowStack + + def createGame(self): + RelaxedSpider.createGame(self, waste=1, rows=6, texts=1) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Scorpion - move cards like in Russian Solitaire +# // Scorpion Tail - building down by alternate color +# ************************************************************************/ + +class Scorpion_RowStack(Yukon_SS_RowStack, Spider_RowStack): + canDropCards = Spider_RowStack.canDropCards + +class Scorpion(RelaxedSpider): + + Hint_Class = YukonType_Hint + RowStack_Class = StackWrapper(Scorpion_RowStack, base_rank=KING) + + def createGame(self): + RelaxedSpider.createGame(self, rows=7, playcards=20) + + def startGame(self): + for i in (4, 4, 4, 0, 0, 0): + self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0) + self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + def getHighlightPilesStacks(self): + return () + + +class ScorpionTail_RowStack(Yukon_AC_RowStack, Spider_RowStack): + canDropCards = Spider_RowStack.canDropCards + +class ScorpionTail(Scorpion): + Foundation_Class = Spider_AC_Foundation + RowStack_Class = StackWrapper(ScorpionTail_RowStack, base_rank=KING) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + +# /*********************************************************************** +# // Wasp +# ************************************************************************/ + +class Wasp(Scorpion): + RowStack_Class = Scorpion_RowStack # anything on an empty space + + def startGame(self): + for i in (3, 3, 3, 0, 0, 0): + self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0) + self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Three Blind Mice +# ************************************************************************/ + +class ThreeBlindMice(Scorpion): + + Talon_Class = InitialDealTalonStack + + def createGame(self): + # create layout + l, s = Layout(self), self.s + # set window + w, h = l.XM+10*l.XS, l.XM+2*l.XS+15*l.YOFFSET + self.setSize(w, h) + # create stacks + s.talon = self.Talon_Class(w-l.XS, h-l.YS, self) + x, y = l.XM+6*l.XS, l.YM + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(10): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + x, y = l.XM, l.YM + for i in range(2): + s.reserves.append(OpenStack(x, y, self, max_move=1, max_accept=0)) + x += l.XS + # default + l.defaultAll() + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(rows=self.s.rows[:7], flip=1, frames=0) + self.s.talon.dealRow(rows=self.s.rows[7:], flip=0, frames=0) + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + + +# /*********************************************************************** +# // Rouge et Noir +# ************************************************************************/ + +class RougeEtNoir_RowStack(KingAC_RowStack): + def canDropCards(self, stacks): + if not self.cards: + return (None, 0) + for s in stacks: + for cards in (self.cards[-1:], self.cards[-13:]): + if s is not self and s.acceptsCards(self, cards): + return (s, len(cards)) + return (None, 0) + + +class RougeEtNoir(Game): + Layout_Method = Layout.klondikeLayout + Talon_Class = DealRowTalonStack + RowStack_Class = RougeEtNoir_RowStack + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=10, waste=0, texts=1, playcards=23) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + if l.s.waste: + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for i in range(4): + r = l.s.foundations[i] + s.foundations.append(AC_FoundationStack(r.x, r.y, self, suit=i, max_move=0)) + for i in range(4): + r = l.s.foundations[i+4] + s.foundations.append(Spider_AC_Foundation(r.x, r.y, self)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + return l + + def startGame(self, flip=0, reverse=1): + for i in range(3, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[:-i], flip=flip, frames=0, reverse=reverse) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:-1], reverse=reverse) + + +# /*********************************************************************** +# // Mrs. Mop +# ************************************************************************/ + +class MrsMop(RelaxedSpider): + + Talon_Class = InitialDealTalonStack + RowStack_Class = Spider_RowStack + + def createGame(self): + RelaxedSpider.createGame(self, rows=13, playcards=24, texts=0) + + def startGame(self): + for i in range(7): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Cicely +# ************************************************************************/ + +class Cicely_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + n = 0 + if sound: + self.game.startDealSample() + for i in range(4): + n += self.dealRow(rows=self.game.s.rows, sound=0) + if sound: + self.game.stopSamples() + return n + + +class Cicely(Game): + + Hint_Class = CautiousDefaultHint + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+10*l.XS, l.YM+max(5*l.YS, 2*l.YS+16*l.YOFFSET) + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM+l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + y += l.YS + x, y = l.XM+9*l.XS, l.YM+l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, base_rank=KING, dir=-1)) + y += l.YS + x, y = l.XM+l.XS, l.YM + for i in range(8): + s.reserves.append(ReserveStack(x, y, self)) + x += l.XS + x, y = l.XM+l.XS, l.YM+l.YS + for i in range(8): + s.rows.append(UD_SS_RowStack(x, y, self)) + x += l.XS + s.talon = Cicely_Talon(l.XM, l.YM, self) + l.setRegion(s.rows, (l.XM+l.XS-l.CW/2, l.YM+l.YS-l.CH/2, + w-l.XS-l.CW/2, 999999)) + + # default + l.defaultAll() + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Trillium +# // Lily +# ************************************************************************/ + +class Trillium(Game): + + Hint_Class = Spider_Hint + RowStack_Class = StackWrapper(AC_RowStack, base_rank=ANY_RANK) + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+13*l.XS, l.YM+l.YS+24*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(13): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + + s.talon = DealRowTalonStack(l.XM+6*l.XS, h-l.YS, self) + l.createText(s.talon, "se") + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.s.talon.dealRow(frames=0, flip=0) + self.s.talon.dealRow(frames=0) + self.s.talon.dealRow(frames=0, flip=0) + self.startDealSample() + self.s.talon.dealRow() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + def isGameWon(self): + for s in self.s.rows: + if s.cards: + if len(s.cards) != 13 or not isAlternateColorSequence(s.cards): + return False + return True + + +class Lily(Trillium): + RowStack_Class = StackWrapper(AC_RowStack, base_rank=KING) + + +# /*********************************************************************** +# // Chelicera +# ************************************************************************/ + +class Chelicera_RowStack(Yukon_SS_RowStack): + def fillStack(self): + if not self.cards: + sound = self.game.app.opt.sound and self.game.app.opt.animations + talon = self.game.s.talon + if sound: + self.game.startDealSample() + for i in range(3): + if talon.cards: + talon.dealToStacks([self], flip=1, frames=4) + if sound: + self.game.stopSamples() + + +class Chelicera(Game): + + Hint_Class = YukonType_Hint + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+8*l.XS, l.YM+l.YS+16*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + s.talon = TalonStack(x, y, self) + l.createText(s.talon, "ss") + x += l.XS + for i in range(7): + s.rows.append(Chelicera_RowStack(x, y, self, base_rank=KING)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRow(rows=self.s.rows[4:]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + def isGameWon(self): + for s in self.s.rows: + if s.cards: + if len(s.cards) != 13 or not isSameSuitSequence(s.cards): + return False + return True + + +# /*********************************************************************** +# // Scorpion Head +# ************************************************************************/ + +class ScorpionHead(Scorpion): + + Layout_Method = Layout.freeCellLayout + + def createGame(self, **layout): + + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=7, reserves=4) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # create stacks + s.talon = InitialDealTalonStack(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(Spider_SS_Foundation(r.x, r.y, self, + suit=ANY_SUIT)) + for r in l.s.rows: + s.rows.append(Scorpion_RowStack(r.x, r.y, self, + base_rank=KING)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + + # default + l.defaultAll() + + def startGame(self): + rows = self.s.rows + for i in (3,3,3,3,7,7): + self.s.talon.dealRow(rows=rows[:i], flip=1, frames=0) + self.s.talon.dealRow(rows=rows[i:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=rows[:3]) + + +# /*********************************************************************** +# // Spider Web +# ************************************************************************/ + +class SpiderWeb(RelaxedSpider): + + def createGame(self): + + # create layout + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+2*l.YS+16*l.YOFFSET) + + # create stacks + x, y = l.XM, l.YM + s.talon = DealRowTalonStack(x, y, self) + l.createText(s.talon, "ss") + x += 2*l.XS + s.reserves.append(ReserveStack(x, y, self)) + x += 2*l.XS + for r in range(4): + s.foundations.append(Spider_SS_Foundation(x, y, self, + suit=ANY_SUIT)) + x += l.XS + x, y = l.XM+l.XS, l.YM+l.YS + for r in range(7): + s.rows.append(Spider_RowStack(x, y, self, + base_rank=ANY_RANK)) + x += l.XS + + # define stack-groups + l.defaultStackGroups() + + + def startGame(self): + self.s.talon.dealRow(frames=0) + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.rows[:3]) + + +# /*********************************************************************** +# // Simon Jester +# ************************************************************************/ + +class SimonJester(Spider): + Talon_Class = InitialDealTalonStack + + def createGame(self): + Spider.createGame(self, rows=14, texts=0) + + def startGame(self): + for l in range(1, 14): + self.s.talon.dealRow(rows=self.s.rows[:l], frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[1:]) + +# /*********************************************************************** +# // Applegate +# ************************************************************************/ + +class Applegate(Game): + Hint_Class = YukonType_Hint + + def createGame(self): + + # create layout + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+max(l.YS+16*l.YOFFSET, 4*l.YS)) + + x, y = l.XM, l.YM + s.talon = InitialDealTalonStack(x, y, self) + x += l.XS + for i in range(7): + s.rows.append(Yukon_SS_RowStack(x, y, self, base_rank=KING, mod=13)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(3): + s.reserves.append(OpenStack(x, y, self, max_accept=0)) + y += l.YS + + # default + l.defaultAll() + + def startGame(self): + for i in (6, 6, 0, 0, 0): + self.s.talon.dealRow(rows=self.s.rows[:7-i], frames=0) + if i: + self.s.talon.dealRow(rows=self.s.rows[7-i:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + + def isGameWon(self): + for s in self.s.rows: + if len(s.cards) == 0: + continue + if len(s.cards) != 13 or not isSameSuitSequence(s.cards): + return False + return True + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % stack1.cap.mod == card2.rank or + (card2.rank + 1) % stack1.cap.mod == card1.rank)) + + +# /*********************************************************************** +# // Big Spider +# // Spider 3x3 +# // Ground for a Divorce (3 decks) +# // Spider (4 decks) +# // Ground for a Divorce (4 decks) +# ************************************************************************/ + +class BigSpider(Spider): + def createGame(self): + Spider.createGame(self, rows=13, playcards=28) + def startGame(self): + for l in range(5): + self.s.talon.dealRow(frames=0, flip=0) + self.startDealSample() + self.s.talon.dealRow() + + +class BigSpider1Suit(BigSpider): + pass +class BigSpider2Suits(BigSpider): + pass + + +class Spider3x3(BigSpider): + def startGame(self): + for l in range(4): + self.s.talon.dealRow(frames=0, flip=0) + self.startDealSample() + self.s.talon.dealRow() + + +class GroundForADivorce3Decks(BigSpider): + Talon_Class = GroundForADivorce_Talon + Foundation_Class = StackWrapper(Spider_SS_Foundation, base_rank=ANY_RANK, mod=13) + RowStack_Class = StackWrapper(Spider_RowStack, mod=13) + def canDealCards(self): + return Game.canDealCards(self) + + +class Spider4Decks(BigSpider): + + def createGame(self, rows=13): + + l, s = Layout(self), self.s + w, h = l.XM+(rows+2)*l.XS, l.YM+max(l.YS+24*l.YOFFSET, 9*l.YS) + self.setSize(w, h) + + x, y = l.XM, l.YM + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x += l.XS + l.setRegion(s.rows, (-999, -999, l.XM+rows*l.XS-l.CW/2, 999999)) + x = l.XM+rows*l.XS + for i in range(2): + y = l.YM + for j in range(8): + s.foundations.append(self.Foundation_Class(x, y, self)) + y += l.YS + x += l.XS + + x, y = w-1.5*l.XS, h-l.YS + s.talon = self.Talon_Class(x, y, self) + l.createText(s.talon, 'sw') + + l.defaultStackGroups() + l.defaultRegions() + + +class GroundForADivorce4Decks(Spider4Decks): + Talon_Class = GroundForADivorce_Talon + Foundation_Class = StackWrapper(Spider_SS_Foundation, base_rank=ANY_RANK, mod=13) + RowStack_Class = StackWrapper(Spider_RowStack, mod=13) + def createGame(self): + Spider4Decks.createGame(self, rows=12) + def canDealCards(self): + return Game.canDealCards(self) + + +# /*********************************************************************** +# // York +# ************************************************************************/ + +class York(RelaxedSpider): + + Talon_Class = InitialDealTalonStack + Foundation_Class = StackWrapper(Spider_SS_Foundation, base_rank=ANY_RANK, mod=13) + RowStack_Class = StackWrapper(Spider_RowStack, mod=13) + + def createGame(self): + RelaxedSpider.createGame(self, rows=12, playcards=26, texts=0) + + def startGame(self): + for i in range(8): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[2:-2]) + +class TripleYork(York): + + def createGame(self): + RelaxedSpider.createGame(self, rows=14, playcards=26, texts=0) + + def startGame(self): + for i in range(10): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=[self.s.rows[0],self.s.rows[-1]]) + +# /*********************************************************************** +# // Spidike +# // Fred's Spider +# ************************************************************************/ + +class Spidike(RelaxedSpider): + RowStack_Class = StackWrapper(Spider_SS_RowStack, base_rank=KING) + + def createGame(self, rows=7, playcards=18): + l, s = Layout(self), self.s + self.Layout_Method(l, rows=rows, waste=0, playcards=playcards) + self.setSize(l.size[0], l.size[1]) + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(SS_FoundationStack(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + l.defaultAll() + + def startGame(self): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class FredsSpider(Spidike): + RowStack_Class = Spider_SS_RowStack + + def createGame(self): + Spidike.createGame(self, rows=10, playcards=23) + + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class FredsSpider3Decks(FredsSpider): + + def createGame(self): + Spidike.createGame(self, rows=13, playcards=26) + + +# register the game +registerGame(GameInfo(10, RelaxedSpider, "Relaxed Spider", + GI.GT_SPIDER | GI.GT_RELAXED, 2, 0)) +registerGame(GameInfo(11, Spider, "Spider", + GI.GT_SPIDER, 2, 0, + altnames=("Tarantula",) )) +registerGame(GameInfo(49, BlackWidow, "Black Widow", + GI.GT_SPIDER, 2, 0, + altnames=("Scarab",) )) +registerGame(GameInfo(14, GroundForADivorce, "Ground for a Divorce", + GI.GT_SPIDER, 2, 0, + altnames=('Scheidungsgrund',) )) +registerGame(GameInfo(114, GrandmothersGame, "Grandmother's Game", + GI.GT_SPIDER, 2, 0)) +registerGame(GameInfo(24, Spiderette, "Spiderette", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(47, BabySpiderette, "Baby Spiderette", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(48, WillOTheWisp, "Will o' the Wisp", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(50, SimpleSimon, "Simple Simon", + GI.GT_SPIDER | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(194, Rachel, "Rachel", + GI.GT_SPIDER | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(29, Scorpion, "Scorpion", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(185, Wasp, "Wasp", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(220, RougeEtNoir, "Rouge et Noir", + GI.GT_GYPSY, 2, 0)) +registerGame(GameInfo(269, Spider1Suit, "Spider (1 suit)", + GI.GT_SPIDER, 2, 0, + suits=(0, 0, 0, 0), + rules_filename = "spider.html")) +registerGame(GameInfo(270, Spider2Suits, "Spider (2 suits)", + GI.GT_SPIDER, 2, 0, + suits=(0, 0, 2, 2), + rules_filename = "spider.html")) +registerGame(GameInfo(305, ThreeBlindMice, "Three Blind Mice", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(309, MrsMop, "Mrs. Mop", + GI.GT_SPIDER | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(341, Cicely, "Cicely", + GI.GT_SPIDER, 2, 0)) +registerGame(GameInfo(342, Trillium, "Trillium", + GI.GT_SPIDER, 2, 0)) +registerGame(GameInfo(343, Lily, "Lily", + GI.GT_SPIDER, 2, 0)) +registerGame(GameInfo(344, Chelicera, "Chelicera", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(345, ScorpionHead, "Scorpion Head", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(346, ScorpionTail, "Scorpion Tail", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(359, SpiderWeb, "Spider Web", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(366, SimonJester, "Simon Jester", + GI.GT_SPIDER | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(382, Applegate, "Applegate", + GI.GT_SPIDER, 1, 0)) +registerGame(GameInfo(384, BigSpider, "Big Spider", + GI.GT_SPIDER, 3, 0)) +registerGame(GameInfo(401, GroundForADivorce3Decks, + "Ground for a Divorce (3 decks)", + GI.GT_SPIDER, 3, 0)) +registerGame(GameInfo(441, York, "York", + GI.GT_SPIDER | GI.GT_OPEN, 2, 0)) +registerGame(GameInfo(444, TripleYork, "Triple York", + GI.GT_SPIDER | GI.GT_OPEN, 3, 0)) +registerGame(GameInfo(445, BigSpider1Suit, "Big Spider (1 suit)", + GI.GT_SPIDER, 3, 0, + suits=(0, 0, 0, 0), + rules_filename = "bigspider.html")) +registerGame(GameInfo(446, BigSpider2Suits, "Big Spider (2 suits)", + GI.GT_SPIDER, 3, 0, + suits=(0, 0, 2, 2), + rules_filename = "bigspider.html")) +registerGame(GameInfo(449, Spider3x3, "Spider 3x3", + GI.GT_SPIDER, 3, 0, + suits=(0, 1, 2), + rules_filename = "bigspider.html")) +registerGame(GameInfo(454, Spider4Decks, "Spider (4 decks)", + GI.GT_SPIDER, 4, 0)) +registerGame(GameInfo(455, GroundForADivorce4Decks, + "Ground for a Divorce (4 decks)", + GI.GT_SPIDER, 4, 0)) +registerGame(GameInfo(458, Spidike, "Spidike", + GI.GT_SPIDER, 1, 0)) # GT_GYPSY ? +registerGame(GameInfo(459, FredsSpider, "Fred's Spider", + GI.GT_SPIDER, 2, 0)) +registerGame(GameInfo(460, FredsSpider3Decks, "Fred's Spider (3 decks)", + GI.GT_SPIDER, 3, 0)) +registerGame(GameInfo(461, OpenSpider, "Open Spider", + GI.GT_SPIDER, 2, 0)) + diff --git a/pysollib/games/sthelena.py b/pysollib/games/sthelena.py new file mode 100644 index 0000000000..a103051392 --- /dev/null +++ b/pysollib/games/sthelena.py @@ -0,0 +1,176 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys, types + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class StHelena_Talon(TalonStack): + + def canDealCards(self): + if self.round == self.max_rounds: + return False + return not self.game.isGameWon() + + def dealCards(self, sound=0): + # move all cards to the Talon and redeal + lr = len(self.game.s.rows) + num_cards = 0 + assert len(self.cards) == 0 + for r in self.game.s.rows[::-1]: + for i in range(len(r.cards)): + num_cards = num_cards + 1 + self.game.moveMove(1, r, self, frames=0) + assert len(self.cards) == num_cards + if num_cards == 0: # game already finished + return 0 + # redeal + self.cards.reverse() + self.game.nextRoundMove(self) + self.game.startDealSample() + for i in range(lr): + k = min(lr, len(self.cards)) + for j in range(k): + self.game.moveMove(1, self, self.game.s.rows[j], frames=4) + # done + self.game.stopSamples() + assert len(self.cards) == 0 + return num_cards + + +class StHelena_FoundationStack(SS_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return False + if self.game.s.talon.round == 1: + if (self.cap.base_rank == KING and + from_stack in self.game.s.rows[6:10:]): + return False + if (self.cap.base_rank == ACE and + from_stack in self.game.s.rows[:4]): + return False + return True + + +class StHelena(Game): + + Hint_Class = CautiousDefaultHint + Talon_Class = StackWrapper(StHelena_Talon, max_rounds=3) + Foundation_Class = StHelena_FoundationStack + RowStack_Class = StackWrapper(UD_RK_RowStack, base_rank=NO_RANK) + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = 3*l.XM+6*l.XS, 3*l.YM+4*l.YS + self.setSize(w, h) + + # create stacks + lay = ( + (2, 1, 1, 0), + (2, 2, 1, 0), + (2, 3, 1, 0), + (2, 4, 1, 0), + (3, 5, 2, 1), + (3, 5, 2, 2), + (2, 4, 3, 3), + (2, 3, 3, 3), + (2, 2, 3, 3), + (2, 1, 3, 3), + (1, 0, 2, 2), + (1, 0, 2, 1), + ) + for xm, xs, ym, ys in lay: + x, y = xm*l.XM+xs*l.XS, ym*l.YM+ys*l.YS + stack = self.RowStack_Class(x, y, self, max_move=1, max_accept=1) + stack.CARD_XOFFSET = stack.CARD_YOFFSET = 0 + s.rows.append(stack) + x, y = 2*l.XM+l.XS, 2*l.YM+l.YS + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i, + base_rank=KING, dir=-1)) + x = x + l.XS + x, y = 2*l.XM+l.XS, 2*l.YM+2*l.YS + for i in range(4): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i)) + x = x + l.XS + + s.talon = self.Talon_Class(l.XM, l.YM, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToBottom(cards, lambda c: (c.deck == 0 and c.rank in (0, 12), (-c.rank, c.suit)), 8) + + def startGame(self): + for i in range(7): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(self.s.foundations) + + +# /*********************************************************************** +# // Box Kite +# ************************************************************************/ + +class BoxKite(StHelena): + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = StackWrapper(UD_RK_RowStack, base_rank=NO_RANK, mod=13) + + +# register the game +registerGame(GameInfo(302, StHelena, "St. Helena", + GI.GT_2DECK_TYPE, 2, 2, + altnames=("Napoleon's Favorite", + "Washington's Favorite") + )) +registerGame(GameInfo(408, BoxKite, "Box Kite", + GI.GT_2DECK_TYPE, 2, 0)) + diff --git a/pysollib/games/sultan.py b/pysollib/games/sultan.py new file mode 100644 index 0000000000..1d44bf3000 --- /dev/null +++ b/pysollib/games/sultan.py @@ -0,0 +1,656 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // Sultan +# ************************************************************************/ + +class Sultan(Game): + + # + # game layout + # + + def createGame(self, reserves=6): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = 3*l.XM+5*l.XS, 3*l.YM+4*l.YS + self.setSize(w, h) + + # create stacks + lay = ((0,0,0,1), + (2,0,0,1), + (0,1,1,1), + (2,1,1,1), + (1,1,2,0), + (1,2,2,1), + (0,2,3,1), + (2,2,3,1), + (1,0,2,1), + ) + for i, j, suit, max_accept in lay: + x, y = 2*l.XM+l.XS+i*l.XS, l.YM+j*l.YS + stack = SS_FoundationStack(x, y, self, suit=suit, + max_move=0, max_accept=max_accept, mod=13) + s.foundations.append(stack) + + x, y = l.XM, l.YM + for i in range(reserves/2): + s.rows.append(ReserveStack(x, y, self)) + y += l.YS + + x, y = 3*l.XM+4*l.XS, l.YM + for i in range(reserves/2): + s.rows.append(ReserveStack(x, y, self)) + y += l.YS + + x, y = 2*l.XM+1.5*l.XS, l.YM+3*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "s") + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "s") + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + cards = self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE and c.suit == 2 and c.deck == 0, c.suit)) + cards = self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == KING, c.suit)) + return cards + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def getAutoStacks(self, event=None): + return (self.sg.dropstacks, (), self.sg.dropstacks) + + +class SultanPlus(Sultan): + def createGame(self): + Sultan.createGame(self, reserves=8) + + +# /*********************************************************************** +# // Boudoir +# ************************************************************************/ + +class Boudoir(Game): + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM+5*l.XS, l.YM+4*l.YS) + + x, y = l.XM+l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, max_cards=13)) + x += l.XS + + x, y = l.XM, l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + l.createText(s.talon, "s") + x += l.XS + y += l.YM + for i in range(4): + s.rows.append(AbstractFoundationStack(x, y, self, suit=i, + max_cards=1, max_move=0, base_rank=QUEEN)) + x += l.XS + + x, y = l.XM, 2*l.YM+2*l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "s") + x += l.XS + y -= l.YM + for i in range(4): + s.rows.append(AbstractFoundationStack(x, y, self, suit=i, + max_cards=1, max_move=0, base_rank=JACK)) + x += l.XS + + x, y = l.XM+l.XS, l.YM+3*l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + mod=13, max_cards=11, base_rank=9, dir=-1)) + x += l.XS + + l.defaultStackGroups() + + def _shuffleHook(self, cards): + # move 4 Queens to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == QUEEN and c.deck == 0, c.suit)) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:4]) + self.s.talon.dealCards() # deal first card to WasteStack + + def isGameWon(self): + return (len(self.s.talon.cards) + len(self.s.waste.cards)) == 0 + + +# /*********************************************************************** +# // Captive Queens +# ************************************************************************/ + +class CaptiveQueens(Game): + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM+5*l.XS, l.YM+3*l.YS) + + x, y = l.XM, 4*l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + l.createText(s.talon, "s") + y += 2*l.YM+l.YS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "s") + + x, y = l.XM+l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + mod=13, max_cards=6, base_rank=4, dir=-1)) + x += l.XS + + x, y = l.XM+l.XS, l.YM+l.YS + for i in range(4): + s.rows.append(AbstractFoundationStack(x, y, self, suit=i, + max_cards=1, max_move=0, base_rank=QUEEN)) + x += l.XS + + x, y = l.XM+l.XS, l.YM+2*l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + mod=13, max_cards=6, base_rank=5)) + x += l.XS + + l.defaultStackGroups() + + def startGame(self): + self.startDealSample() + self.s.talon.dealCards() # deal first card to WasteStack + + def isGameWon(self): + return (len(self.s.talon.cards) + len(self.s.waste.cards)) == 0 + + +# /*********************************************************************** +# // Contradance +# ************************************************************************/ + +class Contradance(Game): + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+4*l.YS) + + x, y = l.XM, l.YM + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i/2, + base_rank=4, dir=-1, mod=13, max_cards=6)) + x += l.XS + x, y = l.XM, l.YM+l.YS + for i in range(8): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i/2, + base_rank=5, max_cards=7)) + x += l.XS + + x, y = l.XM+3*l.XS, l.YM+3*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=2) + l.createText(s.talon, 'nn') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'nn') + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + # move 5's and 6's to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (4, 5), (c.rank, c.suit))) + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Idle Aces +# ************************************************************************/ + +class IdleAces_AceFoundation(AbstractFoundationStack): + + def getBottomImage(self): + return self.game.app.images.getLetter(ACE) + + +class IdleAces(Game): + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+4*l.YS) + + x, y = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, 'ss') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'ss') + x0, y0 = l.XM+l.XS, l.YM + k = 0 + for i, j in((2, 0), (0, 1.5), (4, 1.5), (2, 3)): + x, y = x0+i*l.XS, y0+j*l.YS + s.foundations.append(RK_FoundationStack(x, y, self, + ##suit=ANY_SUIT, + base_rank=KING, dir=-1, max_move=0)) + k += 1 + k = 0 + for i, j in((2, 1), (1, 1.5), (3, 1.5), (2, 2)): + x, y = x0+i*l.XS, y0+j*l.YS + s.foundations.append(RK_FoundationStack(x, y, self, + ##suit=ANY_SUIT, + base_rank=1, max_move=0)) + k += 1 + k = 0 + for i, j in((1, 0.2), (3, 0.2), (1, 2.8), (3, 2.8)): + x, y = x0+i*l.XS, y0+j*l.YS + s.foundations.append(IdleAces_AceFoundation(x, y, self, + suit=k, max_cards=1, max_move=0)) + k += 1 + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (1, KING) and c.deck == 0, (-c.rank, c.suit))) + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations[:8]) + self.s.talon.dealCards() + + +# /*********************************************************************** +# // Lady of the Manor +# ************************************************************************/ + +class LadyOfTheManor_RowStack(BasicRowStack): + clickHandler = BasicRowStack.doubleclickHandler + + +class LadyOfTheManor_Reserve(OpenStack): + clickHandler = OpenStack.doubleclickHandler + + +class LadyOfTheManor(Game): + Foundation_Class_1 = RK_FoundationStack + Foundation_Class_2 = RK_FoundationStack + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+max(4*l.YS, 3*l.YS+14*l.YOFFSET)) + + x, y = l.XM, self.height-l.YS + for i in range(4): + suit = i + if self.Foundation_Class_1 is RK_FoundationStack: suit = ANY_SUIT + s.foundations.append(self.Foundation_Class_1(x, y, self, suit=suit)) + x += l.XS + for i in range(4): + suit = i + if self.Foundation_Class_1 is RK_FoundationStack: suit = ANY_SUIT + s.foundations.append(self.Foundation_Class_2(x, y, self, suit=suit)) + x += l.XS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(4): + s.rows.append(LadyOfTheManor_RowStack(x, y, self, max_accept=0)) + x += l.XS + for i, j in ((0,2), (0,1), (0,0), + (1,0), (2,0), (3,0), (4,0), (5,0), (6,0), + (7,0), (7,1), (7,2),): + x, y = l.XM+i*l.XS, l.YM+j*l.YS + s.reserves.append(LadyOfTheManor_Reserve(x, y, self, max_accept=0)) + + s.talon = InitialDealTalonStack(self.width-l.XS, self.height-2*l.YS, self) + + l.defaultAll() + + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, c.suit)) + + + def startGame(self, flip=False): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + for i in range(11): + self.s.talon.dealRow(frames=0, flip=flip) + self.s.talon.dealRow(frames=0) + self.startDealSample() + while self.s.talon.cards: + self.flipMove(self.s.talon) + c = self.s.talon.cards[-1] + r = self.s.reserves[c.rank-1] + self.moveMove(1, self.s.talon, r, frames=4) + + +# /*********************************************************************** +# // Matrimony +# ************************************************************************/ + +class Matrimony(Game): + + def createGame(self): + + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+4*l.YS) + + s.talon = DealRowTalonStack(l.XM, l.YM, self) + l.createText(s.talon, 'ss') + x, y = l.XM+2*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=JACK, dir=-1, mod=13)) + x += l.XS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=QUEEN, dir=1, mod=13)) + x += l.XS + y = l.YM+2*l.YS + for i in range(2): + x = l.XM + for j in range(8): + stack = LadyOfTheManor_RowStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0 + s.rows.append(stack) + x += l.XS + y += l.YS + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (JACK, QUEEN) and c.deck == 0 and c.suit == 3, + (c.rank, c.suit))) + + + def startGame(self): + self.s.talon.dealRow(rows=[self.s.foundations[3], + self.s.foundations[7]], frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Patriarchs +# ************************************************************************/ + +class Patriarchs(Game): + + def createGame(self, max_rounds=2): + + l, s = Layout(self), self.s + self.setSize(3*l.XM+5*l.XS, l.YM+4*l.YS) + + x, y = l.XM, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + y += l.YS + x, y = 3*l.XM+4*l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=KING, dir=-1)) + y += l.YS + y = l.YM + for i in range(3): + x = 2*l.XM+l.XS + for j in range(3): + s.rows.append(BasicRowStack(x, y, self, + max_cards=1, max_accept=1)) + x += l.XS + y += l.YS + x, y = 2*l.XM+l.XS+l.XS/2, l.YM+3*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds) + l.createText(s.talon, 'sw') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'se') + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (ACE, KING) and c.deck == 0, + (c.rank, c.suit))) + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards: + if not self.s.waste.cards: + self.s.talon.dealCards() + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + + +# /*********************************************************************** +# // Simplicity +# ************************************************************************/ + +class Simplicity(Game): + Hint_Class = CautiousDefaultHint + + def createGame(self, max_rounds=2): + + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+4*l.YS) + + self.base_card = None + + i = 0 + for x, y in ((l.XM, l.YM), + (l.XM+7*l.XS, l.YM), + (l.XM, l.YM+3*l.YS), + (l.XM+7*l.XS, l.YM+3*l.YS), + ): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, mod=13)) + i += 1 + y = l.YM+l.YS + for i in range(2): + x = l.XM+l.XS + for j in range(6): + stack = AC_RowStack(x, y, self, max_move=1, mod=13) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0 + s.rows.append(stack) + x += l.XS + y += l.YS + x, y = l.XM+3*l.XS, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, 'sw') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'se') + + l.defaultStackGroups() + + + def startGame(self): + self.startDealSample() + # deal base_card to Foundations, update foundations cap.base_rank + self.base_card = self.s.talon.getCard() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, self.s.foundations[self.base_card.suit]) + self.s.talon.dealRow() + self.s.talon.dealCards() + + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + ((card1.rank + 1) % 13 == card2.rank or + (card2.rank + 1) % 13 == card1.rank)) + + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + +# /*********************************************************************** +# // Sixes and Sevens +# ************************************************************************/ + +class SixesAndSevens(Game): + def createGame(self, max_rounds=2): + + l, s = Layout(self), self.s + self.setSize(l.XM+8*l.XS, l.YM+4*l.YS) + + y = l.YM + for i in range(2): + x = l.XM + for j in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=j, base_rank=6)) + x += l.XS + y += l.YS + for i in range(2): + x = l.XM + for j in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=j, + base_rank=5, dir=-1)) + x += l.XS + y += l.YS + y = l.YM + for i in range(3): + x = l.XM+5*l.XS + for j in range(3): + s.rows.append(ReserveStack(x, y, self)) + x += l.XS + y += l.YS + x, y = l.XM+5*l.XS, l.YM+3*l.YS + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, 'sw') + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, 'se') + + l.defaultStackGroups() + + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank in (5, 6), (-c.rank, c.deck, c.suit))) + + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + +# register the game +registerGame(GameInfo(330, Sultan, "Sultan", + GI.GT_2DECK_TYPE, 2, 2, + altnames=("Sultan of Turkey",) )) +registerGame(GameInfo(331, SultanPlus, "Sultan +", + GI.GT_2DECK_TYPE, 2, 2)) +registerGame(GameInfo(354, Boudoir, "Boudoir", + GI.GT_2DECK_TYPE, 2, 2)) +registerGame(GameInfo(410, CaptiveQueens, "Captive Queens", + GI.GT_1DECK_TYPE, 1, 2)) +registerGame(GameInfo(418, Contradance, "Contradance", + GI.GT_2DECK_TYPE, 2, 1)) +registerGame(GameInfo(419, IdleAces, "Idle Aces", + GI.GT_2DECK_TYPE, 2, 2)) +registerGame(GameInfo(423, LadyOfTheManor, "Lady of the Manor", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(424, Matrimony, "Matrimony", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(429, Patriarchs, "Patriarchs", + GI.GT_2DECK_TYPE, 2, 1)) +registerGame(GameInfo(437, Simplicity, "Simplicity", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(438, SixesAndSevens, "Sixes and Sevens", + GI.GT_2DECK_TYPE, 2, 0)) diff --git a/pysollib/games/takeaway.py b/pysollib/games/takeaway.py new file mode 100644 index 0000000000..e425c406e3 --- /dev/null +++ b/pysollib/games/takeaway.py @@ -0,0 +1,114 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Take Away +# ************************************************************************/ + +class TakeAway_Foundation(AbstractFoundationStack): + + def acceptsCards(self, from_stack, cards): + if not self.cards: + return True + c1, c2, mod = self.cards[-1], cards[0], self.cap.mod + return (c1.rank == (c2.rank + 1) % mod or + c2.rank == (c1.rank + 1) % mod) + +class TakeAway(Game): + + RowStack_Class = BasicRowStack + Foundation_Class = StackWrapper(TakeAway_Foundation, max_move=0, mod=13) + + # + # game layout + # + + def createGame(self, reserves=6): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = 2*l.XM+10*l.XS, l.YM+l.YS+16*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(4): + s.rows.append(self.RowStack_Class(x, y, self, + max_move=1, max_accept=0)) + x += l.XS + x += l.XM + for i in range(6): + stack = self.Foundation_Class(x, y, self, suit=ANY_SUIT, + base_rank=ANY_RANK) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.foundations.append(stack) + x += l.XS + s.talon = InitialDealTalonStack(w-l.XS, h-l.YS, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(10): + self.s.talon.dealRow(frames=0) + self.startDealSample() + for i in range(3): + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Four Stacks +# ************************************************************************/ + +class FourStacks(TakeAway): + RowStack_Class = StackWrapper(AC_RowStack, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS) + Foundation_Class = StackWrapper(AC_FoundationStack, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS, dir=-1) + + +# register the game +registerGame(GameInfo(334, TakeAway, "Take Away", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) +registerGame(GameInfo(335, FourStacks, "Four Stacks", + GI.GT_1DECK_TYPE | GI.GT_OPEN, 1, 0)) + + + + + diff --git a/pysollib/games/terrace.py b/pysollib/games/terrace.py new file mode 100644 index 0000000000..11ce931ce3 --- /dev/null +++ b/pysollib/games/terrace.py @@ -0,0 +1,279 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Terrace_Talon(WasteTalonStack): + def canDealCards(self): + if self.game.getState() == 0: + return 0 + return WasteTalonStack.canDealCards(self) + + +class Terrace_AC_Foundation(AC_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=13, min_cards=1, max_move=0) + apply(AC_FoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if self.game.getState() == 0: + if len(cards) != 1 or not cards[0].face_up: + return 0 + if cards[0].suit != self.cap.base_suit: + return 0 + return from_stack in self.game.s.rows + return AC_FoundationStack.acceptsCards(self, from_stack, cards) + + +class Terrace_SS_Foundation(SS_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=13, min_cards=1, max_move=0) + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if self.game.getState() == 0: + if len(cards) != 1 or not cards[0].face_up: + return 0 + if cards[0].suit != self.cap.base_suit: + return 0 + return from_stack in self.game.s.rows + return SS_FoundationStack.acceptsCards(self, from_stack, cards) + + +class Terrace_RowStack(AC_RowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, mod=13, max_move=1) + apply(AC_RowStack.__init__, (self, x, y, game), cap) + + def acceptsCards(self, from_stack, cards): + if self.game.getState() == 0: + return 0 + if from_stack in self.game.s.reserves: + return 0 + return AC_RowStack.acceptsCards(self, from_stack, cards) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + state = self.game.getState() + if state > 0: + AC_RowStack.moveMove(self, ncards, to_stack, frames=frames, shadow=shadow) + return + assert to_stack in self.game.s.foundations + assert ncards == 1 + assert not self.game.s.waste.cards + self.game.moveMove(ncards, self, to_stack, frames=frames, shadow=shadow) + for s in self.game.s.foundations: + s.cap.base_rank = to_stack.cards[0].rank + freerows = filter(lambda s: not s.cards, self.game.s.rows) + self.game.s.talon.dealRow(rows=freerows, sound=1) + self.game.s.talon.dealCards() # deal first card to WasteStack + + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +# /*********************************************************************** +# // Terrace +# ************************************************************************/ + +class Terrace(Game): + Foundation_Class = Terrace_AC_Foundation + RowStack_Class = Terrace_RowStack + ReserveStack_Class = OpenStack + Hint_Class = CautiousDefaultHint + + INITIAL_RESERVE_CARDS = 11 + + # + # game layout + # + + def createGame(self, rows=9, max_rounds=1, num_deal=1): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable in default window size) + maxrows = max(rows, 9) + w1, w2 = (maxrows - 8)*l.XS/2, (maxrows - rows)*l.XS/2 + h = max(3*l.YS, 20*l.YOFFSET) + self.setSize(l.XM + maxrows*l.XS + l.XM, l.YM + 2*l.YS + h) + + # extra settings + self.base_card = None + + # create stacks + x, y = l.XM + w1, l.YM + s.talon = Terrace_Talon(x, y, self, max_rounds=max_rounds, num_deal=num_deal) + l.createText(s.talon, "sw") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se", text_format="%D") + x = x + 2*l.XS + stack = self.ReserveStack_Class(x, y, self) + stack.CARD_XOFFSET = l.XOFFSET + l.createText(stack, "sw") + s.reserves.append(stack) + x, y = l.XM + w1, y + l.YS + for i in range(8): + s.foundations.append(self.Foundation_Class(x, y, self, suit=i/2)) + x = x + l.XS + x, y = l.XM + w2, y + l.YS + for i in range(rows): + s.rows.append(self.RowStack_Class(x, y, self)) + x = x + l.XS + + # define stack-groups + l.defaultStackGroups() + + # + # game extras + # + + def getState(self): + for s in self.s.foundations: + if s.cards: + return 1 + return 0 + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + for i in range(self.INITIAL_RESERVE_CARDS): + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow(rows=self.s.rows[:4]) + + def fillStack(self, stack): + if not stack.cards: + old_state = self.enterState(self.S_FILL) + if stack is self.s.waste and self.s.talon.cards: + self.s.talon.dealCards() + elif stack in self.s.rows and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + def _restoreGameHook(self, game): + for s in self.s.foundations: + s.cap.base_rank = game.loadinfo.base_rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_rank=p.load()) + + def _saveGameHook(self, p): + base_rank = NO_RANK + for s in self.s.foundations: + if s.cards: + base_rank = s.cards[0].rank + break + p.dump(base_rank) + + +# /*********************************************************************** +# // Queen of Italy +# ************************************************************************/ + +class QueenOfItaly(Terrace): + Foundation_Class = StackWrapper(Terrace_AC_Foundation, max_move=1) + def fillStack(self, stack): + pass + + +# /*********************************************************************** +# // General's Patience +# ************************************************************************/ + +class GeneralsPatience(Terrace): + Foundation_Class = Terrace_SS_Foundation + INITIAL_RESERVE_CARDS = 13 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class BlondesAndBrunettes(Terrace): + INITIAL_RESERVE_CARDS = 10 + + def startGame(self): + self.startDealSample() + for i in range(self.INITIAL_RESERVE_CARDS): + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow() + # deal base_card to Foundations + c = self.s.talon.getCard() + for s in self.s.foundations: + s.cap.base_rank = c.rank + self.s.talon.dealRow(rows=(self.s.foundations[2*c.suit],)) + self.s.talon.dealCards() # deal first card to WasteStack + + def getState(self): + return 1 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class FallingStar(BlondesAndBrunettes): + INITIAL_RESERVE_CARDS = 11 + + +# register the game +registerGame(GameInfo(135, Terrace, "Terrace", + GI.GT_TERRACE, 2, 0)) +registerGame(GameInfo(136, GeneralsPatience, "General's Patience", + GI.GT_TERRACE, 2, 0)) +registerGame(GameInfo(137, BlondesAndBrunettes, "Blondes and Brunettes", + GI.GT_TERRACE, 2, 0)) +registerGame(GameInfo(138, FallingStar, "Falling Star", + GI.GT_TERRACE, 2, 0)) +registerGame(GameInfo(431, QueenOfItaly, "Queen of Italy", + GI.GT_TERRACE, 2, 0)) + diff --git a/pysollib/games/tournament.py b/pysollib/games/tournament.py new file mode 100644 index 0000000000..fd6ee1077c --- /dev/null +++ b/pysollib/games/tournament.py @@ -0,0 +1,248 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +# /*********************************************************************** +# // +# ************************************************************************/ + + +class Tournament_Talon(TalonStack): + + def canDealCards(self): + if self.round == self.max_rounds and not self.cards: + return False + return not self.game.isGameWon() + + def dealCards(self, sound=0): + if len(self.cards) == 0: + self._redeal() + self.game.startDealSample() + n = 0 + for r in self.game.s.rows: + for i in range(4): + if not self.cards: + break + n += self.dealRow([r]) + self.game.stopSamples() + return n + + def _redeal(self): + # move all cards to the Talon + lr = len(self.game.s.rows) + num_cards = 0 + assert len(self.cards) == 0 + for r in self.game.s.rows[::-1]: + for i in range(len(r.cards)): + num_cards = num_cards + 1 + self.game.moveMove(1, r, self, frames=0) + self.game.flipMove(self) + assert len(self.cards) == num_cards + if num_cards == 0: # game already finished + return + self.game.nextRoundMove(self) + return + + +class Tournament(Game): + + ROW_YOFFSET = True + + # + # game layout + # + + def createGame(self, **layout): + + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+10*l.XS, max(l.YM+l.YS+20*l.YOFFSET, 5*l.YM+5*l.YS)) + + # create stacks + x, y, = l.XM+l.XS, l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i)) + x = x + l.XS + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + base_rank=KING, dir=-1)) + x = x + l.XS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(6): + stack = BasicRowStack(x, y, self, max_move=1, max_accept=0) + s.rows.append(stack) + if not self.ROW_YOFFSET: + stack.CARD_YOFFSET = 0 + x = x + l.XS + + x, y = l.XM, 4*l.YM+l.YS + for i in range(4): + self.s.reserves.append(ReserveStack(x, y, self)) + y += l.YS + x, y = l.XM+9*l.XS, 4*l.YM+l.YS + for i in range(4): + self.s.reserves.append(ReserveStack(x, y, self)) + y += l.YS + + s.talon = Tournament_Talon(l.XM, l.YM, self, max_rounds=3) + ##l.createText(s.talon, "ss") + tx, ty, ta, tf = l.getTextAttr(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, tx, ty, anchor=ta, + font=self.app.getFont("canvas_default")) + + # default + l.defaultAll() + + # + # game overrides + # + def _shuffleHook(self, cards): + for c in cards[-8:]: + if c.rank in (ACE, KING): + return cards + # + for c in cards: + if c.rank in (ACE, KING): + break + cards.remove(c) + return cards+[c] + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(self.s.reserves) + for r in self.s.rows: + for i in range(4): + self.s.talon.dealRow([r]) + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards: + if not self.demo: + self.startDealSample() + for i in range(4): + if not self.s.talon.cards: + break + self.s.talon.dealRow([stack]) + if not self.demo: + self.stopSamples() + + +class LaNivernaise(Tournament): + ROW_YOFFSET = False + +# /*********************************************************************** +# // Kingsdown Eights +# ************************************************************************/ + +class KingsdownEights_Talon(DealRowTalonStack): + def dealCards(self, sound=0): + if len(self.cards) == 0: + self._redeal() + self.game.startDealSample() + n = 0 + for r in self.game.s.reserves: + for i in range(4): + if not self.cards: + break + n += self.dealRow([r]) + self.game.stopSamples() + return n + +class KingsdownEights_Row(AC_RowStack): + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + +class KingsdownEights(Game): + + Hint_Class = CautiousDefaultHint + + def createGame(self, **layout): + + # create layout + l, s = Layout(self), self.s + + # set window + self.setSize(l.XM+10*l.XS, max(l.YM+2*l.YS+12*l.YOFFSET, + l.YM+5*l.YS)) + + # create stacks + x = l.XM + for i in range(2): + y = l.YM+l.YS + for j in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=j)) + y += l.YS + x += l.XS + x, y = l.XM+2*l.XS, l.YM + for i in range(8): + stack = KingsdownEights_Row(x, y, self, max_move=1) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0 + s.rows.append(stack) + x += l.XS + x, y = l.XM+2*l.XS, l.YM+l.YS + for i in range(8): + stack = OpenStack(x, y, self, max_accept=0) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + s.reserves.append(stack) + x += l.XS + s.talon = KingsdownEights_Talon(l.XM, l.YM, self, max_rounds=1) + l.createText(s.talon, "se") + + # define stack-groups + l.defaultStackGroups() + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.color != card2.color and abs(card1.rank-card2.rank) == 1 + + +# register the game +registerGame(GameInfo(303, Tournament, "Tournament", + GI.GT_2DECK_TYPE, 2, 2)) +registerGame(GameInfo(304, LaNivernaise, "La Nivernaise", + GI.GT_2DECK_TYPE, 2, 2, + altnames = ("Napoleon's Flank", ), + rules_filename = "tournament.html")) +registerGame(GameInfo(386, KingsdownEights, "Kingsdown Eights", + GI.GT_2DECK_TYPE, 2, 0)) + + + + diff --git a/pysollib/games/ultra/__init__.py b/pysollib/games/ultra/__init__.py new file mode 100644 index 0000000000..a1e0b46bc5 --- /dev/null +++ b/pysollib/games/ultra/__init__.py @@ -0,0 +1,9 @@ +import dashavatara +import hanafuda +import hanafuda1 +import hexadeck +import larasgame +import matrix +import mughal +import tarock +import threepeaks diff --git a/pysollib/games/ultra/dashavatara.py b/pysollib/games/ultra/dashavatara.py new file mode 100644 index 0000000000..98a7eeec63 --- /dev/null +++ b/pysollib/games/ultra/dashavatara.py @@ -0,0 +1,1294 @@ +## +##---------------------------------------------------------------------------## +## +## Ultrasol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, math, time + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# * Dashavatara Foundation Stacks +# ***********************************************************************/ + +class Dashavatara_FoundationStack(AbstractFoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_move=0, max_cards=12) + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + def updateText(self): + AbstractFoundationStack.updateText(self) + self.game.updateText() + + +class Journey_Foundation(AbstractFoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=12, dir=0, base_rank=NO_RANK, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return 1 + stack_dir = self.game.getFoundationDir() + if stack_dir == 0: + card_dir = (cards[0].rank - self.cards[-1].rank) % self.cap.mod + return card_dir in (1, 11) + else: + return (self.cards[-1].rank + stack_dir) % self.cap.mod == cards[0].rank + + + +class AppachansWaterfall_Foundation(AbstractFoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, base_suit=0, mod=12, max_cards=120, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not (from_stack in self.game.s.rows and + AbstractFoundationStack.acceptsCards(self, from_stack, cards)): + return 0 + pile, rank, suit = from_stack.getPile(), 0, 0 + if self.cards: + rank = (self.cards[-1].rank + 1) % 12 + suit = self.cards[-1].suit + (rank == 0) + if (not pile or len(pile) <= 11 - rank + or not isSameSuitSequence(pile[-(12 - rank):])): + return 0 + return cards[0].suit == suit and cards[0].rank == rank + + + +# /*********************************************************************** +# * Dashavatara Row Stacks +# ***********************************************************************/ + +class Dashavatara_OpenStack(OpenStack): + + def __init__(self, x, y, game, yoffset, **cap): + kwdefault(cap, max_move=UNLIMITED_MOVES, max_cards=UNLIMITED_CARDS, + max_accept=UNLIMITED_ACCEPTS, base_rank=0, dir=-1) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def currentForce(self, card): + hour = time.localtime(time.time())[3] + if hour >= 7 and hour <= 19: + strong, weak = 0, 1 + else: + strong, weak = 1, 0 + if card.suit <= 4: + return strong + else: + return weak + + def isRankSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not c1.rank + dir == c2.rank: + return 0 + c1 = c2 + return 1 + + def isAlternateColorSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not ((c1.suit + c2.suit) % 2 + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isAlternateForceSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not ((c1.suit < 4 and c2.suit > 3 + or c1.suit > 3 and c2.suit < 4) + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isSuitSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not (c1.suit == c2.suit + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + +class Dashavatara_AC_RowStack(Dashavatara_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isAlternateColorSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isAlternateColorSequence([stackcards[-1], cards[0]]) + + +class Dashavatara_AF_RowStack(Dashavatara_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isAlternateForceSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isAlternateForceSequence([stackcards[-1], cards[0]]) + + +class Dashavatara_RK_RowStack(Dashavatara_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isRankSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isRankSequence([stackcards[-1], cards[0]]) + + +class Dashavatara_SS_RowStack(Dashavatara_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isSuitSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isSuitSequence([stackcards[-1], cards[0]]) + + +class Circles_RowStack(SS_RowStack): + + def __init__(self, x, y, game, base_rank): + SS_RowStack.__init__(self, x, y, game, base_rank=base_rank, + max_accept=1, max_move=1) + self.CARD_YOFFSET = 1 + + +class Journey_BraidStack(OpenStack): + + def __init__(self, x, y, game, xoffset, yoffset): + OpenStack.__init__(self, x, y, game) + CW = self.game.app.images.CARDW + self.CARD_YOFFSET = int(self.game.app.images.CARD_YOFFSET * yoffset) + # use a sine wave for the x offsets + self.CARD_XOFFSET = [] + j = 1 + for i in range(30): + self.CARD_XOFFSET.append(int(math.sin(j) * xoffset)) + j = j + .9 + + +class Journey_StrongStack(ReserveStack): + + def fillStack(self): + if not self.cards: + if self.game.s.braidstrong.cards: + self.game.moveMove(1, self.game.s.braidstrong, self) + elif self.game.s.braidweak.cards: + self.game.moveMove(1, self.game.s.braidweak, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Journey_WeakStack(ReserveStack): + + def fillStack(self): + if not self.cards: + if self.game.s.braidweak.cards: + self.game.moveMove(1, self.game.s.braidweak, self) + elif self.game.s.braidstrong.cards: + self.game.moveMove(1, self.game.s.braidstrong, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Journey_ReserveStack(ReserveStack): + + def acceptsCards(self, from_stack, cards): + if (from_stack is self.game.s.braidstrong or + from_stack is self.game.s.braidweak or + from_stack in self.game.s.rows): + return 0 + return ReserveStack.acceptsCards(self, from_stack, cards) + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +class AppachansWaterfall_RowStack(RK_RowStack): + + def canDropCards(self, stacks): + game, pile, stack, rank = self.game, self.getPile(), stacks[0], 0 + if stack.cards: + rank = (stack.cards[-1].rank + 1) % 12 + if (not pile or len(pile) <= 11 - rank + or not isSameSuitSequence(pile[-(12 - rank):]) + or not stack.acceptsCards(self, pile[-1:])): + return (None, 0) + return (stack, 1) + + + +# /*********************************************************************** +# // Dashavatara Game Stacks +# ************************************************************************/ + +class Dashavatara_TableauStack(Dashavatara_OpenStack): + + def __init__(self, x, y, game, base_rank, yoffset, **cap): + kwdefault(cap, dir=3, max_move=99, max_cards=4, max_accept=1, base_rank=base_rank) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + # check that the base card is correct + if self.cards and self.cards[0].rank != self.cap.base_rank: + return 0 + if not self.cards: + return cards[0].rank == self.cap.base_rank + return (self.cards[-1].suit == cards[0].suit and + self.cards[-1].rank + self.cap.dir == cards[0].rank) + + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + +class Dashavatara_ReserveStack(ReserveStack): + + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_cards=1, max_accept=1, base_rank=ANY_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def acceptsCards(self, from_stack, cards): + return (ReserveStack.acceptsCards(self, from_stack, cards) + and self.game.s.talon.cards) + + +class Dashavatara_RowStack(BasicRowStack): + + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # check + return not (self.cards or self.game.s.talon.cards) + + def canMoveCards(self, cards): + return 1 + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +# /*********************************************************************** +# * +# ***********************************************************************/ + +class AbstractDashavataraGame(Game): + + SUITS = (_("Fish"), _("Tortoise"), _("Boar"), _("Lion"), _("Dwarf"), + _("Axe"), _("Arrow"), _("Plow"), _("Lotus"), _("Horse")) + RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", "10", + _("Pradhan"), _("Raja")) + COLORS = (_("Black"), _("Red"), _("Yellow"), _("Green"), _("Brown"), + _("Orange"), _("Grey"), _("White"), _("Olive"), _("Crimson")) + FORCE = (_("Strong"), _("Weak")) + + def updateText(self): + pass + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit + and (card1.rank + 1 == card2.rank + or card1.rank - 1 == card2.rank)) + + +class Journey_Hint(DefaultHint): + # FIXME: demo is not too clever in this game + pass + + +# /*********************************************************************** +# * Dashavatara Circles +# ***********************************************************************/ + +class DashavataraCircles(AbstractDashavataraGame): + Hint_Class = CautiousDefaultHint + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + w, h = l.XM + l.XS * 9, l.YM + l.YS * 7 + self.setSize(w, h) + + # Create row stacks + x = w / 2 - l.CW / 2 + y = h / 2 - l.YS / 2 + x0 = (-.7, .3, .7, -.3, + -1.7, -1.5, -.6, .6, 1.5, 1.7, 1.5, .6, -.6, -1.5, + -2.7, -2.5, -1.9, -1, 0, 1, 1.9, 2.5, 2.7, 2.5, 1.9, 1, 0, -1, -1.9, -2.5) + y0 = (-.3, -.45, .3, .45, + 0, -.8, -1.25, -1.25, -.8, 0, .8, 1.25, 1.25, .8, + 0, -.9, -1.6, -2, -2.2, -2, -1.6, -.9, 0, .9, 1.6, 2, 2.2, 2, 1.6, .9) + for i in range(30): + # FIXME: + _x, _y = x+l.XS*x0[i], y+l.YS*y0[i]+l.YM*y0[i]*2 + if _x < 0: _x = 0 + if _y < 0: _y = 0 + s.rows.append(Circles_RowStack(_x, _y, self, base_rank = ANY_RANK)) + + # Create reserve stacks + s.reserves.append(ReserveStack(l.XM, h - l.YS, self)) + s.reserves.append(ReserveStack(w - l.XS, h - l.YS, self)) + + # Create foundations + x, y = l.XM, l.YM + for j in range(2): + for i in range(5): + s.foundations.append(SS_FoundationStack(x, y, self, i + j * 5, mod=12, + max_move=0, max_cards=12)) + y = y + l.YS + x, y = w - l.XS, l.YM +## from pprint import pprint +## pprint(s.rows) +## print (l.XM + l.XS, 0, w - l.XS - l.XM, 999999) + self.setRegion(s.rows, (l.XM + l.XS, 0, w - l.XS - l.XM, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM + l.XS, l.YM, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 120 + for i in range(3): + self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=3) + self.s.talon.dealCards() + + + +# /*********************************************************************** +# * Ten Avatars +# ***********************************************************************/ + +class TenAvatars(AbstractDashavataraGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM * 3 + l.XS * 11, l.YM + l.YS * 6) + + # Create row stacks + x = l.XM + y = l.YM + for i in range(10): + s.rows.append(RK_RowStack(x, y, self, base_rank=11, + max_move=12, max_cards=99)) + x = x + l.XS + + # Create reserve stacks + x = self.width - l.XS + y = l.YM + for i in range(6): + s.reserves.append(ReserveStack(x, y, self)) + y = y + l.YS + y = y - l.YS + for i in range(6): + x = x - l.XS + s.reserves.append(ReserveStack(x, y, self)) + + self.setRegion(s.rows, (0, 0, l.XM + l.XS * 10, l.YS * 5)) + + # Create talon + s.talon = DealRowTalonStack(l.XM, self.height - l.YS, self) + l.createText(s.talon, "nn") + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 120 + for i in range(4): + self.s.talon.dealRow(flip=1, frames=0) + self.startDealSample() + self.s.talon.dealCards() + + def isGameWon(self): + if len(self.s.talon.cards): + return 0 + for s in self.s.rows: + if len(s.cards) != 12 or not isSameSuitSequence(s.cards): + return 0 + return 1 + + + +# /*********************************************************************** +# * Balarama +# ***********************************************************************/ + +class Balarama(AbstractDashavataraGame): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = Dashavatara_AC_RowStack + BASE_RANK = ANY_RANK + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=16, reserves=4, texts=0) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=12, max_cards=12)) + + # Create reserve stacks + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self, )) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, l.YOFFSET, + suit=ANY_SUIT, base_rank=self.BASE_RANK, max_cards=12)) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 120 + for i in range(6): + self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=3) + self.s.talon.dealRow(rows=self.s.rows[:8], flip=1, frames=3) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color % 2 != card2.color % 2 and + (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + + +# /*********************************************************************** +# * Hayagriva +# ***********************************************************************/ + +class Hayagriva(Balarama): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = Dashavatara_RK_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, **layout): + Balarama.createGame(self) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank) + + + +# /*********************************************************************** +# * Shanka +# ***********************************************************************/ + +class Shanka(Balarama): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = Dashavatara_RK_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, **layout): + Balarama.createGame(self, reserves=0) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if stack1 in self.s.foundations: + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + return (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank) + + + +# /*********************************************************************** +# * Surukh +# ***********************************************************************/ + +class Surukh(Balarama): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = Dashavatara_AF_RowStack + BASE_RANK = ANY_RANK + + # + # Game layout + # + + def createGame(self, **layout): + Balarama.createGame(self, reserves=4) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if card1.suit <= 4: + force0 = 0 + else: + force0 = 1 + if card2.suit <= 4: + force1 = 0 + else: + force1 = 1 + return (force0 != force1 + and (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + + +# /*********************************************************************** +# * Matsya +# ***********************************************************************/ + +class Matsya(AbstractDashavataraGame): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = RK_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, max_rounds=1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=10, waste=1) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=12, max_cards=12, max_move=0)) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=self.BASE_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 120 + for i in range(10): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank) + + + +# /*********************************************************************** +# * Kurma +# ***********************************************************************/ + +class Kurma(Matsya): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = SS_RowStack + BASE_RANK = ANY_RANK + + # + # Game layout + # + + def createGame(self, **layout): + Matsya.createGame(self, max_rounds=-1) + + + +# /*********************************************************************** +# * Varaha +# ***********************************************************************/ + +class Varaha(Matsya): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = SS_RowStack + BASE_RANK = ANY_RANK + + # + # Game layout + # + + def createGame(self, **layout): + Matsya.createGame(self, max_rounds=-1, num_deal=3) + + + +# /*********************************************************************** +# * Narasimha +# ***********************************************************************/ + +class Narasimha(Matsya): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, **layout): + Matsya.createGame(self, max_rounds=1, num_deal=1) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color % 2 != card2.color % 2 + and (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + + +# /*********************************************************************** +# * Vamana +# ***********************************************************************/ + +class Vamana(Matsya): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, **layout): + Matsya.createGame(self, max_rounds=-1, num_deal=3) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color % 2 != card2.color % 2 + and (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank)) + + + +# /*********************************************************************** +# * Parashurama +# ***********************************************************************/ + +class Parashurama(Matsya): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = RK_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, **layout): + Matsya.createGame(self, max_rounds=2, num_deal=3) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank) + + + +# /*********************************************************************** +# // Journey to Cuddapah +# ************************************************************************/ + +class Journey(AbstractDashavataraGame): + Hint_Class = Journey_Hint + + BRAID_CARDS = 15 + BRAID_OFFSET = 1 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable - needed for Braid_BraidStack) + decks = self.gameinfo.decks + h = max(5 * l.YS + 35, 2*l.YM + 2*l.YS + (self.BRAID_CARDS - 1) * l.YOFFSET*self.BRAID_OFFSET) + self.setSize(l.XM + l.XS * (7 + decks * 2), l.YM + h) + + # extra settings + self.base_card = None + + # Create foundations, rows, reserves + s.addattr(braidstrong=None) # register extra stack variable + s.addattr(braidweak=None) # register extra stack variable + x, y = l.XM, l.YM + for j in range(5): + for i in range(decks): + s.foundations.append(Journey_Foundation(x + l.XS * i, y, self, + j, mod=12, max_cards=12)) + s.rows.append(Journey_StrongStack(x + l.XS * decks, y, self)) + s.rows.append(Journey_ReserveStack(x + l.XS * (1 + decks), y, self)) + y = y + l.YS + x, y = x + l.XS * (5 + decks), l.YM + for j in range(5): + s.rows.append(Journey_ReserveStack(x, y, self)) + s.rows.append(Journey_WeakStack(x + l.XS, y, self)) + for i in range(decks, 0, -1): + s.foundations.append(Journey_Foundation(x + l.XS * (1 + i), y, self, + j + 5, mod=12, max_cards=12)) + y = y + l.YS + self.texts.info = MfxCanvasText(self.canvas, + self.width / 2, h - l.YM / 2, + anchor="center", + font = self.app.getFont("canvas_default")) + + # Create braids + x, y = l.XM + l.XS * 2.15 + l.XS * decks, l.YM + s.braidstrong = Journey_BraidStack(x, y, self, xoffset=12, yoffset=self.BRAID_OFFSET) + x = x + l.XS * 1.7 + s.braidweak = Journey_BraidStack(x, y, self, xoffset=-12, yoffset=self.BRAID_OFFSET) + + # Create talon + x, y = l.XM + l.XS * 2 + l.XS * decks, h - l.YS - l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + self.width / 2, h - l.YM * 2.5, + anchor="center", + font=self.app.getFont("canvas_default")) + x = x + l.XS * 2 + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.foundations + s.rows + self.sg.dropstacks = [s.braidstrong] + [s.braidweak] + s.rows + [s.waste] + + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.base_card = None + self.updateText() + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows = [self.s.braidstrong]) + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows = [self.s.braidweak]) + self.s.talon.dealRow() + # deal base_card to foundations, update cap.base_rank + self.base_card = self.s.talon.getCard() + to_stack = self.s.foundations[self.base_card.suit * self.gameinfo.decks] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + self.updateText() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + # deal first card to WasteStack + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 12 == card2.rank + or (card2.rank + 1) % 12 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + + # + # game extras + # + + def updateText(self): + if self.preview > 1 or not self.texts.info: + return + if not self.base_card: + t = "" + else: + t = self.RANKS[self.base_card.rank] + dir = self.getFoundationDir() % 12 + if dir == 1: + t = t + _(" Ascending") + elif dir == 11: + t = t + _(" Descending") + self.texts.info.config(text = t) + + + +# /*********************************************************************** +# // Long Journey to Cuddapah +# ************************************************************************/ + +class LongJourney(Journey): + + BRAID_CARDS = 20 + BRAID_OFFSET = .7 + + + +# /*********************************************************************** +# * Appachan's Waterfall +# ***********************************************************************/ + +class AppachansWaterfall(AbstractDashavataraGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + w, h = l.XM + l.XS * 10, l.YM + l.YS * 6 + self.setSize(w, h) + + # Create row stacks + x, y = l.XM, l.YM + for i in range(10): + s.rows.append(AppachansWaterfall_RowStack(x, y, self, base_rank=ANY_RANK, + max_move=12, max_cards=99)) + x = x + l.XS + self.setRegion(s.rows, (-999, -999, 999999, l.YM + l.YS * 5)) + + # Create foundation + x, y = w / 2 - l.CW / 2, h - l.YS + s.foundations.append(AppachansWaterfall_Foundation(x, y, self, -1)) + + # Create reserves + s.reserves.append(ReserveStack(x - l.XS * 2, y, self)) + s.reserves.append(ReserveStack(x + l.XS * 2, y, self)) + + # Create talon + s.talon = DealRowTalonStack(l.XM, y, self) + l.createText(s.talon, "nn") + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 120 + for i in range(4): + self.s.talon.dealRow(flip=1, frames=0) + self.startDealSample() + self.s.talon.dealCards() + + def isGameWon(self): + return len(self.s.foundations[0].cards) == 120 + + + +# /*********************************************************************** +# // Hiranyaksha +# ************************************************************************/ + +class Hiranyaksha(AbstractDashavataraGame): + RowStack_Class = StackWrapper(Dashavatara_RK_RowStack, base_rank=NO_RANK) + + # + # game layout + # + + def createGame(self, rows=11, reserves=10): + # create layout + l, s = Layout(self), self.s + + # set size + maxrows = max(rows, reserves) + self.setSize(l.XM + (maxrows + 2) * l.XS, l.YM + 6 * l.YS) + + # + playcards = 4 * l.YS / l.YOFFSET + xoffset, yoffset = [], [] + for i in range(playcards): + xoffset.append(0) + yoffset.append(l.YOFFSET) + for i in range(96 * self.gameinfo.decks - playcards): + xoffset.append(l.XOFFSET) + yoffset.append(0) + + # create stacks + x, y = l.XM + (maxrows - reserves) * l.XS / 2, l.YM + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x, y = l.XM + (maxrows - rows) * l.XS / 2, l.YM + l.YS + self.setRegion(s.reserves, (-999, -999, 999999, y - l.YM / 2)) + for i in range(rows): + stack = self.RowStack_Class(x, y, self, yoffset=l.YOFFSET) + stack.CARD_XOFFSET = xoffset + stack.CARD_YOFFSET = yoffset + s.rows.append(stack) + x = x + l.XS + x, y = l.XM + maxrows * l.XS, l.YM + for i in range(2): + for suit in range(5): + s.foundations.append(SS_FoundationStack(x, y, self, suit=suit + (5 * i))) + y = y + l.YS + x, y = x + l.XS, l.YM + self.setRegion(self.s.foundations, (x - l.XS * 2, -999, 999999, + self.height - (l.YS + l.YM)), priority=1) + s.talon = InitialDealTalonStack(self.width - 3 * l.XS / 2, self.height - l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + if self.s.talon.cards[-1].rank == 11: + if self.s.rows[i].cards: + i = i + 1 + self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4) + + # must look at cards + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + for stack in stacks: + if stack.cards and stack is not dragstack: + dist = (stack.cards[-1].x - cx)**2 + (stack.cards[-1].y - cy)**2 + else: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + closest, cdist = stack, dist + return closest + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isRankSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + + +# /*********************************************************************** +# // Dashavatara Hint +# ************************************************************************/ + +class Dashavatara_Hint(AbstractHint): + def computeHints(self): + game = self.game + + # 2)See if we can move a card to the tableaux + if not self.hints: + for r in game.sg.dropstacks: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + if r in game.s.tableaux: + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + base_score = 80000 + (4 - r.cap.base_suit) + else: + base_score = 80000 + # find a stack that would accept this card + for t in game.s.tableaux: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 3)See if we can move a card from the tableaux + # to a row stack. This can only happen if there are + # no more cards to deal. + if not self.hints: + for r in game.s.tableaux: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = 70000 + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 4)See if we can move a card within the row stacks + if not self.hints: + for r in game.s.rows: + pile = r.getPile() + if not pile or len(pile) != 1 or len(pile) == len(r.cards): + continue + base_score = 60000 + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 5)See if we can deal cards + if self.level >= 2: + if game.canDealCards(): + self.addHint(self.SCORE_DEAL, 0, game.s.talon, None) + + +# /*********************************************************************** +# // Dashavatara +# ************************************************************************/ + +class Dashavatara(Game): + Hint_Class = Dashavatara_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + TABLEAU_YOFFSET = min(9, max(3, l.YOFFSET / 3)) + + # set window + th = l.YS + 3 * TABLEAU_YOFFSET + # (set piles so that at least 2/3 of a card is visible with 10 cards) + h = 10 * l.YOFFSET + l.CH * 2/3 + self.setSize(11 * l.XS + l.XM * 2, l.YM + 3 * th + l.YM + h) + + # create stacks + s.addattr(tableaux=[]) # register extra stack variable + x = l.XM + 8 * l.XS + l.XS / 2 + y = l.YM + for i in range(3, 0, -1): + x = l.XM + for j in range(10): + s.tableaux.append(Dashavatara_TableauStack(x, y, self, i - 1, TABLEAU_YOFFSET)) + x = x + l.XS + x = x + l.XM + s.reserves.append(Dashavatara_ReserveStack(x, y, self)) + y = y + th + x, y = l.XM, y + l.YM + for i in range(10): + s.rows.append(Dashavatara_RowStack(x, y, self, max_accept=1)) + x = x + l.XS + x = self.width - l.XS + y = self.height - l.YS + s.talon = DealRowTalonStack(x, y, self) + l.createText(s.talon, "sw") + + # define stack-groups + self.sg.openstacks = s.tableaux + s.rows + s.reserves + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.tableaux + s.rows + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.tableaux, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def isGameWon(self): + for stack in self.s.tableaux: + if len(stack.cards) != 4: + return 0 + return 1 + + def fillStack(self, stack): + if self.s.talon.cards: + if stack in self.s.rows and len(stack.cards) == 0: + self.s.talon.dealRow(rows=[stack]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 3 == card2.rank or card2.rank + 3 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# * +# ***********************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_DASHAVATARA_GANJIFA + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + suits=range(10), ranks=range(12)) + registerGame(gi) + return gi + +r(15406, Matsya, "Matsya", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15407, Kurma, "Kurma", GI.GT_DASHAVATARA_GANJIFA, 1, -1) +r(15408, Varaha, "Varaha", GI.GT_DASHAVATARA_GANJIFA, 1, -1) +r(15409, Narasimha, "Narasimha", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15410, Vamana, "Vamana", GI.GT_DASHAVATARA_GANJIFA, 1, -1) +r(15411, Parashurama, "Parashurama", GI.GT_DASHAVATARA_GANJIFA, 1, 1) +r(15412, TenAvatars, "Ten Avatars", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15413, DashavataraCircles, "Dashavatara Circles", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15414, Balarama, "Balarama", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15415, Hayagriva, "Hayagriva", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15416, Shanka, "Shanka", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15417, Journey, "Journey to Cuddapah", GI.GT_DASHAVATARA_GANJIFA, 1, 2) +r(15418, LongJourney, "Long Journey to Cuddapah", GI.GT_DASHAVATARA_GANJIFA, 2, 2) +r(15419, Surukh, "Surukh", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15420, AppachansWaterfall, "Appachan's Waterfall", GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15421, Hiranyaksha, 'Hiranyaksha', GI.GT_DASHAVATARA_GANJIFA, 1, 0) +r(15422, Dashavatara, 'Dashavatara', GI.GT_DASHAVATARA_GANJIFA, 1, 0) + +del r diff --git a/pysollib/games/ultra/hanafuda.py b/pysollib/games/ultra/hanafuda.py new file mode 100644 index 0000000000..658f35f2d6 --- /dev/null +++ b/pysollib/games/ultra/hanafuda.py @@ -0,0 +1,1052 @@ +##---------------------------------------------------------------------------## +## +## Ultrasol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, math + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import FreeCellType_Hint +from pysollib.pysoltk import MfxCanvasText + +from hanafuda_common import * + + +# /*********************************************************************** +# * Flower Clock +# ***********************************************************************/ + +class FlowerClock(AbstractFlowerGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM + l.XS * 10.5, l.YM + l.YS * 5.5) + + # Create clock + xoffset = ( 1, 2, 2.5, 2, 1, 0, -1, -2, -2.5, -2, -1, 0 ) + yoffset = ( 0.25, 0.75, 1.9, 3, 3.5, 3.75, 3.5, 3, 1.9, 0.75, 0.25, 0 ) + x = l.XM + l.XS * 7 + y = l.CH / 3 + for i in range(12): + x0 = x + xoffset[i] * l.XS + y0 = y + yoffset[i] * l.YS + s.foundations.append(FlowerClock_Foundation(x0, y0, self, ANY_SUIT, base_rank=3)) + t = MfxCanvasText(self.canvas, x0 + l.CW / 2, y0 + l.YS, + anchor="center", font=font, + text=self.SUITS[i]) + + # Create row stacks + for j in range(2): + x, y = l.XM, l.YM + l.YS * j * 2.7 + for i in range(4): + s.rows.append(FlowerClock_RowStack(x, y, self, yoffset=l.CH/4, + max_cards=8, max_accept=8)) + x = x + l.XS + self.setRegion(s.rows, (0, 0, l.XS * 4, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(self.width - l.XS, self.height - l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + def isGameWon(self): + for i in self.s.foundations: + if len(i.cards) != 4: + return 0 + if i.cards[0].suit != i.id: + return 0 + return 1 + + def getAutoStacks(self, event=None): + if event is None: + return (self.sg.dropstacks, (), self.sg.dropstacks) + else: + return (self.sg.dropstacks, self.sg.dropstacks, self.sg.dropstacks) + + + +# /*********************************************************************** +# * Gaji +# ***********************************************************************/ + +class Gaji(AbstractFlowerGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + + # Set window size + self.setSize(l.XM + l.XS * 13, l.YM * 2 + l.YS * 6) + + # Create left foundations + x = l.XM + y = l.YM + s.foundations.append(Gaji_Foundation(x, y, self, -1, base_rank=0)) + x = x + l.XS + s.foundations.append(Gaji_Foundation(x, y, self, -1, base_rank=1)) + + # Create right foundations + x = self.width - l.XS * 2 + s.foundations.append(Gaji_Foundation(x, y, self, -1, base_rank=2)) + x = x + l.XS + s.foundations.append(Gaji_Foundation(x, y, self, -1, base_rank=3)) + + # Create row stacks + x = l.XS * 2.5 + l.XM + for i in range(8): + s.rows.append(Gaji_RowStack(x, y, self, yoffset=l.CH/2, + max_cards=12, max_accept=12)) + x = x + l.XS + self.setRegion(s.rows, (l.XM + l.XS * 2, -999, l.XM + l.XS * 10, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(self.width - l.XS, self.height - l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def _shuffleHook(self, cards): + topcards = [None, None, None, None] + for c in cards[:]: + if not topcards[c.rank]: + if not ((c.suit == 10) and (c.rank == 3)): + topcards[c.rank] = c + cards.remove(c) + return topcards + cards + + def startGame(self): + assert len(self.s.talon.cards) == 48 + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + r = self.s.rows + self.s.talon.dealRow(rows=(r[0], r[1], r[2], r[5], r[6], r[7])) + self.s.talon.dealRow(rows=(r[0], r[1], r[6], r[7])) + self.s.talon.dealRow(rows=(r[0], r[7])) + r = self.s.foundations + self.s.talon.dealRow(rows=(r[3], r[2], r[1], r[0])) + assert len(self.s.talon.cards) == 0 + + def fillStack(self, stack): + if stack in self.s.rows: + if stack.cards and not stack.cards[-1].face_up: + self.flipMove(stack) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if stack1 in self.s.foundations: + return (card1.rank == card2.rank + and ((((card1.suit + 1) % 12) == card2.suit) + or (((card1.suit - 1) % 12) == card2.suit))) + else: + return ((card1.suit == card2.suit) + and ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank))) + + + +# /*********************************************************************** +# * Oonsoo +# ***********************************************************************/ + +class Oonsoo(AbstractFlowerGame): + Layout_Method = Layout.oonsooLayout + Talon_Class = DealRowTalonStack + RowStack_Class = Oonsoo_SequenceStack + Rows = 12 + Suit = ANY_SUIT + BaseRank = 0 + Reserves = 0 + Strictness = 0 + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=self.Rows, reserves=self.Reserves, + texts=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, suit=self.Suit, + base_rank=self.BaseRank, + yoffset=l.YOFFSET)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * self.gameinfo.decks + self.startDealSample() + self.s.talon.dealRow(flip=0) + self.s.talon.dealCards() + + def isGameWon(self): + if len(self.s.talon.cards): + return 0 + for s in self.s.rows: + if (len(s.cards) != 4 or not cardsFaceUp(s.cards) + or not s.isHanafudaSequence(s.cards, self.Strictness)): + return 0 + return 1 + + + +# /*********************************************************************** +# * Oonsoo Too +# ************************************************************************/ + +class OonsooToo(Oonsoo): + Reserves = 1 + + + +# /*********************************************************************** +# * Oonsoo Strict +# ************************************************************************/ + +class OonsooStrict(Oonsoo): + RowStack_Class = Hanafuda_SequenceStack + Reserves = 2 + Strictness = 1 + + + +# /*********************************************************************** +# * Oonsoo Open +# ************************************************************************/ + +class OonsooOpen(Oonsoo): + BaseRank = ANY_RANK + + + +# /*********************************************************************** +# * Oonsoo Times Two +# ************************************************************************/ + +class OonsooTimesTwo(Oonsoo): + Rows = 24 + Reserves = 1 + + + +# /*********************************************************************** +# * Pagoda +# ***********************************************************************/ + +class Pagoda(AbstractFlowerGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM + l.XS * 11, l.YS * 6) + + # Create foundations + self.foundation_texts = [] + id = 0 + for j in range(4): + x, y = l.XM + l.XS * 8, l.YM * 3 + l.YS * j * 1.5 + for i in range(3): + s.foundations.append(Pagoda_Foundation(x, y, self, id)) + t = MfxCanvasText(self.canvas, x + l.CW / 2, y - 12, + anchor="center", font=font) + self.foundation_texts.append(t) + x = x + l.XS + id = id + 1 + + # Build pagoda + x, y = l.XM + l.XS, l.YM + d = ( 0.4, 0.25, 0, 0.25, 0.4 ) + for i in range(5): + s.reserves.append(ReserveStack(x + l.XS * i, y + l.YS * d[i], self)) + + x, y = l.XM + l.XS * 2, y + l.YS * 1.1 + d = ( 0.25, 0, 0.25 ) + for i in range(3): + s.reserves.append(ReserveStack(x + l.XS * i, y + l.YS * d[i], self)) + + x, y = l.XM, y + l.YS * 1.1 + d = ( 0.5, 0.4, 0.25, 0, 0.25, 0.4, 0.5 ) + for i in range(7): + s.reserves.append(ReserveStack(x + l.XS * i, y + l.YS * d[i], self)) + + x, y = l.XM + l.XS, y + l.YS * 1.5 + for i in range(5): + s.reserves.append(ReserveStack(x + l.XS * i, y, self)) + + # Create talon + x = l.XM + l.XS * 2.5 + y = l.YM + l.YS * 4.9 + s.talon = WasteTalonStack(x, y, self, num_deal=4, max_rounds=1) + l.createText(s.talon, "sw") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se") + + # Define stack groups + l.defaultStackGroups() + + # + # Game extras + # + + def updateText(self): + if self.preview > 1: + return + for i in range(12): + s = self.s.foundations[i] + if not len(s.cards) or len(s.cards) == 8: + text = self.SUITS[i] + elif len(s.cards) < 5: + text = _("Rising") + else: + text = _("Setting") + self.foundation_texts[i].config(text=text) + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * 2 + self.updateText() + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves, reverse=1) + self.s.talon.dealCards() + + def fillStack(self, stack): + if not stack.cards and stack is self.s.waste: + if self.canDealCards(): + self.dealCards() + + + +# /*********************************************************************** +# * Matsukiri +# ***********************************************************************/ + +class MatsuKiri(AbstractFlowerGame): + Strictness = 0 + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + + # Set window size + self.setSize(l.XM * 3 + l.XS * 9, l.YM + l.YS * 6) + + # Create row stacks + x = l.XM + y = l.YM + for i in range(8): + s.rows.append(Matsukiri_RowStack(x, y, self, yoffset=l.CH/2, + max_cards=12, max_accept=12)) + x = x + l.XS + self.setRegion(s.rows, (-999, -999, l.XM + (l.XS * 8) + 10, 999999)) + + # Create foundation + x = x + l.XM * 2 + s.foundations.append(MatsuKiri_Foundation(x, y, self, ANY_SUIT)) + self.setRegion(s.foundations, (l.XM + (l.XS * 8) + 10, -999, 999999, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(self.width - l.XS, self.height - l.YS, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + def fillStack(self, stack): + if stack in self.s.rows: + if len(stack.cards) > 0 and not stack.cards[-1].face_up: + self.flipMove(stack) + + +class MatsuKiriStrict(MatsuKiri): + Strictness = 1 + + +# /*********************************************************************** +# * Great Wall +# ***********************************************************************/ + +class GreatWall(AbstractFlowerGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + w, h = l.XM + l.XS * 15, l.YM + l.YS * 6.2 + self.setSize(w, h) + + # Create foundations + self.foundation_texts = [] + x, y = (l.XM, l.XM, w - l.XS, w - l.XS), (l.YM, h / 2, l.YM, h / 2) + for i in range(4): + s.foundations.append(GreatWall_FoundationStack(x[i], y[i] + l.YM, self, -1, base_rank=i)) + self.foundation_texts.append(MfxCanvasText(self.canvas, + x[i] + l.CW / 2, y[i], + anchor="center", font=font)) + + # Create row stacks + x = l.XM + l.XS * 1.5 + y = l.YM + for i in range(12): + s.rows.append(GreatWall_RowStack(x, y, self, yoffset=l.CH/4, + max_cards=26, max_accept=26)) + x = x + l.XS + self.setRegion(s.rows, (l.XM + l.XS * 1.25, -999, self.width - l.XS * 1.25, 999999)) + + # Create talon + x = self.width / 2 - l.CW / 2 + y = self.height - l.YS * 1.2 + s.talon = InitialDealTalonStack(x, y, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game extras + # + + def updateText(self): + if self.preview > 1: + return + for i in range(4): + l = len(self.s.foundations[i].cards) / 12 + if l == 0: + t = "" + elif l == 4: + t = _("Filled") + else: + t = str(l) + (_("st"), _("nd"), _("rd"), _("th"))[l - 1] + _(" Deck") + self.foundation_texts[i].config(text=str(t)) + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * 4 + self.updateText() + for i in range(15): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + def fillStack(self, stack): + if stack in self.s.rows: + if len(stack.cards) > 0 and not stack.cards[-1].face_up: + self.flipMove(stack) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if stack1 in self.s.foundations: + return (card1.rank == card2.rank + and ((((card1.suit + 1) % 12) == card2.suit) + or (((card1.suit - 1) % 12) == card2.suit))) + else: + return (card1.rank + 1 == card2.rank + or card1.rank - 1 == card2.rank) + + + +# /*********************************************************************** +# * Four Winds +# ************************************************************************/ + +class FourWinds(AbstractFlowerGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(7 * l.XS, 5 * l.YS + 3 * l.YM) + + # Four winds + TEXTS = (_("North"), _("East"), _("South"), _("West"), + _("NW"), _("NE"), _("SE"), _("SW")) + + # Create foundations + x = l.XM * 3 + y = l.YM + xoffset = (2.5, 5, 2.5, 0) + yoffset = (0, 2, 4, 2) + for i in range(4): + x0 = x + (xoffset[i] * l.XS) + y0 = y + (yoffset[i] * l.YS) + s.foundations.append(FourWinds_Foundation(x0, y0, self, -1, + max_cards=12, max_accept=1, base_rank=i)) + t = MfxCanvasText(self.canvas, x0 + l.CW / 2, y0 + l.YS + 5, + anchor="center", font=font, + text=TEXTS[i]) + + # Create rows + xoffset = (1.25, 3.75, 3.75, 1.25) + yoffset = (0.75, 0.75, 3, 3) + for i in range(4): + x0 = x + (xoffset[i] * l.XS) + y0 = y + (yoffset[i] * l.YS) + s.rows.append(FourWinds_RowStack(x0, y0, self, yoffset=10, + max_cards=3, max_accept=1, base_rank=ANY_RANK)) + t = MfxCanvasText(self.canvas, x0 + l.CW / 2, y0 + l.YS + 5, + anchor="center", font=font, + text=TEXTS[i+4]) + self.setRegion(s.rows, (x + l.XS, y + l.YS * 0.65, x + l.XS * 4 + 5, y + l.YS * 3 + 5)) + + # Create talon + x = x + 2 * l.XS + y = y + 2 * l.YS + s.talon = WasteTalonStack(x, y, self, num_deal=1, max_rounds=2) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # Define stack-groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 + self.startDealSample() + self.s.talon.dealCards() + + def fillStack(self, stack): + if not stack.cards and stack is self.s.waste: + if self.canDealCards(): + self.dealCards() + + + +# /*********************************************************************** +# * Sumo +# ************************************************************************/ + +class Sumo(AbstractFlowerGame): + Layout_Method = Layout.sumoLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = Hanafuda_SS_FoundationStack + RowStack_Class = Hanafuda_SequenceStack + Hint_Class = FreeCellType_Hint + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, reserves=2, texts=0, playcards=16) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=r.suit, base_rank=3)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, yoffset=l.YOFFSET)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 + for i in range(5): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + +# /*********************************************************************** +# * Big Sumo +# ************************************************************************/ + +class BigSumo(AbstractFlowerGame): + Layout_Method = Layout.sumoLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = Hanafuda_SS_FoundationStack + RowStack_Class = Hanafuda_SequenceStack + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=10, reserves=4, texts=0, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=r.suit, base_rank=3)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, yoffset=l.YOFFSET)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * 2 + for i in range(9): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[2:8]) + self.s.talon.dealCards() + + + +# /*********************************************************************** +# * Samuri +# ************************************************************************/ + +class Samuri(AbstractFlowerGame): + Layout_Method = Layout.samuriLayout + Talon_Class = WasteTalonStack + Foundation_Class = Hanafuda_SS_FoundationStack + RowStack_Class = Hanafuda_SequenceStack + Rows = 7 + + # + # Game layout + # + + def createGame(self, max_rounds=1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=self.Rows, waste=1, texts=1, playcards=21) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=r.suit, base_rank=3)) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, yoffset=l.YOFFSET)) + + # Define stack groups + l.defaultAll() + + + # + # Game over rides + # + + def startGame(self): + decks = self.gameinfo.decks + assert len(self.s.talon.cards) == 48 * decks + for i in range(1 + (decks > 1)): + self.s.talon.dealRow(flip=0, frames=0) + max_row = len(self.s.rows) + for i in range(max_row): + self.s.talon.dealRow(rows=self.s.rows[i:max_row-i], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def fillStack(self, stack): + if not stack.cards and stack is self.s.waste: + if self.canDealCards(): + self.dealCards() + + + +# /*********************************************************************** +# * Double Samuri +# ***********************************************************************/ + +class DoubleSamuri(Samuri): + Rows = 11 + + + +# /*********************************************************************** +# * Super Samuri +# ***********************************************************************/ + +class SuperSamuri(DoubleSamuri): + pass + + + +# /*********************************************************************** +# * Little Easy +# ************************************************************************/ + +class LittleEasy(AbstractFlowerGame): + Layout_Method = Layout.easyLayout + Talon_Class = WasteTalonStack + Foundation_Class = FourWinds_Foundation + RowStack_Class = Hanafuda_SequenceStack + Rows = 7 + PlayCards = 10 + + # + # Game layout + # + + def createGame(self, max_rounds=-1, num_deal=3, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=self.Rows, waste=1, texts=1, playcards=self.PlayCards) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, yoffset=l.YOFFSET)) + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * self.gameinfo.decks + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def fillStack(self, stack): + if not stack.cards and stack is self.s.waste: + if self.canDealCards(): + self.dealCards() + + + +# /*********************************************************************** +# * Easy x One +# ************************************************************************/ + +class EasyX1(LittleEasy): + + def createGame(self, **layout): + LittleEasy.createGame(self, max_rounds=2, num_deal=1) + + + +# /*********************************************************************** +# * Relax +# ************************************************************************/ + +class Relax(EasyX1): + RowStack_Class = Oonsoo_SequenceStack + + + +# /*********************************************************************** +# * Big Easy +# ************************************************************************/ + +class BigEasy(LittleEasy): + Rows = 11 + + + +# /*********************************************************************** +# * Easy Supreme +# ************************************************************************/ + +class EasySupreme(LittleEasy): + Rows = 11 + PlayCards = 14 + + + +# /*********************************************************************** +# * Just For Fun +# ************************************************************************/ + +class JustForFun(AbstractFlowerGame): + Layout_Method = Layout.funLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = FourWinds_Foundation + RowStack_Class = Hanafuda_SequenceStack + Rows = 12 + Reserves = 2 + BaseRank = 0 + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=self.Rows, reserves=self.Reserves, texts=0, playcards=22) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create stacks + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + base_rank=self.BaseRank, yoffset=l.YOFFSET)) + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self)) + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + decks = self.gameinfo.decks + assert len(self.s.talon.cards) == 48 * decks + for i in range((decks * 2) + 1): + self.s.talon.dealRow(flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:len(self.s.talon.cards)]) + self.s.talon.dealCards() + + + +# /*********************************************************************** +# * Double Your Fun +# ************************************************************************/ + +class DoubleYourFun(JustForFun): + Rows = 18 + Reserves = 4 + + + +# /*********************************************************************** +# * Firecracker +# ************************************************************************/ + +class Firecracker(JustForFun): + RowStack_Class = Oonsoo_SequenceStack + Reserves = 0 + BaseRank = ANY_RANK + + + +# /*********************************************************************** +# * Cherry Bomb +# ************************************************************************/ + +class CherryBomb(Firecracker): + Rows = 18 + + + +# /*********************************************************************** +# * Paulownia +# ************************************************************************/ + +class Paulownia(AbstractFlowerGame): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = Hanafuda_SS_FoundationStack + RowStack_Class = Hanafuda_SequenceStack + + # + # Game layout + # + + def createGame(self, max_rounds=-1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=r.suit, base_rank=3)) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + base_rank = 0, yoffset=l.YOFFSET)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 + for i in range(8): + self.s.talon.dealRow(rows=self.s.rows[i + 1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + +# /*********************************************************************** +# // Register the games +# ************************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_HANAFUDA + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + suits=range(12), ranks=range(4)) + registerGame(gi) + return gi + +r(12345, Oonsoo, "Oonsoo", GI.GT_HANAFUDA, 1, 0) +r(12346, MatsuKiri, "MatsuKiri", GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12372, MatsuKiriStrict, 'MatsuKiri Strict', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12347, Gaji, "Gaji", GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12348, FlowerClock, "Flower Clock", GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12349, Pagoda, "Pagoda", GI.GT_HANAFUDA, 2, 0) +r(12350, Samuri, "Samuri", GI.GT_HANAFUDA, 1, 0) +r(12351, GreatWall, "Great Wall", GI.GT_HANAFUDA, 4, 0) +r(12352, FourWinds, "Four Winds", GI.GT_HANAFUDA, 1, 1) +r(12353, Sumo, "Sumo", GI.GT_HANAFUDA, 1, 0) +r(12354, BigSumo, "Big Sumo", GI.GT_HANAFUDA, 2, 0) +r(12355, LittleEasy, "Little Easy", GI.GT_HANAFUDA, 1, -1) +r(12356, BigEasy, "Big Easy", GI.GT_HANAFUDA, 2, -1) +r(12357, EasySupreme, "Easy Supreme", GI.GT_HANAFUDA, 4, -1) +r(12358, JustForFun, "Just For Fun", GI.GT_HANAFUDA, 1, 0) +r(12359, Firecracker, "Firecracker", GI.GT_HANAFUDA, 1, 0) +r(12360, EasyX1, "Easy x One", GI.GT_HANAFUDA, 1, 1) +r(12361, Relax, "Relax", GI.GT_HANAFUDA, 1, 1) +r(12362, DoubleSamuri, "Double Samuri", GI.GT_HANAFUDA, 2, 0) +r(12363, SuperSamuri, "Super Samuri", GI.GT_HANAFUDA, 4, 0) +r(12364, DoubleYourFun, "Double Your Fun", GI.GT_HANAFUDA, 2, 0) +r(12365, CherryBomb, "Cherry Bomb", GI.GT_HANAFUDA, 2, 0) +r(12366, OonsooToo, "Oonsoo Too", GI.GT_HANAFUDA, 1, 0) +r(12367, OonsooStrict, "Oonsoo Strict", GI.GT_HANAFUDA, 1, 0) +r(12368, OonsooOpen, "Oonsoo Open", GI.GT_HANAFUDA, 1, 0) +r(12379, OonsooTimesTwo, "Oonsoo Times Two", GI.GT_HANAFUDA, 2, 0) + +del r diff --git a/pysollib/games/ultra/hanafuda1.py b/pysollib/games/ultra/hanafuda1.py new file mode 100644 index 0000000000..1b30e53da8 --- /dev/null +++ b/pysollib/games/ultra/hanafuda1.py @@ -0,0 +1,717 @@ +##---------------------------------------------------------------------------## +## +## Ultrasol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + + +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + +from hanafuda_common import * + +# /*********************************************************************** +# * Paulownia +# ************************************************************************/ + +class Paulownia(AbstractFlowerGame): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = Hanafuda_SS_FoundationStack + RowStack_Class = Hanafuda_SequenceStack + MaxRounds = -1 + BaseRank = 0 + NumDeal = 1 + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=self.MaxRounds, num_deal=self.NumDeal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + suit=r.suit, base_rank=3)) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + base_rank=self.BaseRank, yoffset=l.YOFFSET)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * self.gameinfo.decks + for i in range(8): + self.s.talon.dealRow(rows=self.s.rows[i + 1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + +class Pine(Paulownia): + MaxRounds = 1 + NumDeal = 3 + + +class Eularia(Paulownia): + BaseRank = ANY_RANK + + +class Peony(Eularia): + NumDeal = 3 + + +class Iris(Peony): + MaxRounds = 1 + + + +# /*********************************************************************** +# // Queue +# ************************************************************************/ + +class LesserQueue(AbstractFlowerGame): + Hint_Class = Queue_Hint + BRAID_CARDS = 20 + BRAID_OFFSET = 1 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + decks = self.gameinfo.decks + h = l.YM + l.YS * 5.5 + self.setSize(l.XM + l.XS * 10.5, l.YM + h) + + # extra settings + self.base_card = None + + # Create rows, reserves + s.addattr(braid = None) + x, x0 = l.XM + l.XS * 2, (decks - 1.5) % 2.5 + for j in range(decks / 2): + y = l.YM + for i in range(2): + s.rows.append(Queue_RowStack(x + l.XS * (x0 + j), y, self)) + s.rows.append(Queue_RowStack(x + l.XS * (4 + x0 + j + .5), y, self)) + y = y + l.YS * (3 + (decks > 2)) + y = l.YM + l.YS + for i in range(2): + s.rows.append(Queue_ReserveStack(x, y, self)) + s.rows.append(Queue_ReserveStack(x + l.XS, y, self)) + s.rows.append(Queue_ReserveStack(x, y + l.YS, self)) + s.rows.append(Queue_ReserveStack(x + l.XS, y + l.YS, self)) + if decks - 2: + s.rows.append(Queue_ReserveStack(x, y + l.YS * 2, self)) + s.rows.append(Queue_ReserveStack(x + l.XS, y + l.YS * 2, self)) + x = x + l.XS * 4.5 + + # Create braid + x, y = l.XM + l.XS * 4.25, l.YM + s.braid = Queue_BraidStack(x, y, self, yoffset=self.BRAID_OFFSET) + + # Create talon, waste + x, y = l.XM, l.YM + l.YS * 4.3 + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + self.width / 2, h - l.YM * 2.5, + anchor="center", + font=self.app.getFont("canvas_default")) + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # Create foundations + x = l.XM + for j in range(decks / 2): + y = l.YM + for i in range(4): + s.foundations.append(Queue_Foundation(x, y, self, -1, mod=12, + max_cards=12, base_suit=ANY_SUIT, base_rank=i, rank=i)) + s.foundations.append(Queue_Foundation(x + l.XS * (9.5 - j * 2), y, self, -1, mod=12, + max_cards=12, base_suit=ANY_SUIT, base_rank=i, rank=i)) + y = y + l.YS + x = x + l.XS + self.texts.info = MfxCanvasText(self.canvas, + self.width / 2, h - l.YM / 2, + anchor="center", + font=self.app.getFont("canvas_default")) + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.foundations + s.rows + s.reserves + self.sg.dropstacks = [s.braid] + s.rows + [s.waste] + s.reserves + + + # + # game overrides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * self.gameinfo.decks + self.startDealSample() + self.base_card = None + self.updateText() + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows = [self.s.braid]) + self.s.talon.dealRow() + # deal base_card to foundations, update cap.base_rank + self.base_card = self.s.talon.getCard() + to_stack = self.s.foundations[2 * self.base_card.rank] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + self.updateText() + for s in self.s.foundations: + s.cap.base_suit = self.base_card.suit + # deal first card to WasteStack + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank == card2.rank and + ((card1.suit + 1) % 12 == card2.suit or (card2.suit + 1) % 12 == card1.suit)) + + def getHighlightPilesStacks(self): + return () + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_suit = self.base_card.suit + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + + # + # game extras + # + + def updateText(self): + if self.preview > 1 or not self.texts.info: + return + if not self.base_card: + t = "" + else: + t = self.SUITS[self.base_card.suit] + dir = self.getFoundationDir() + if dir == 1: + t = t + _(" Ascending") + elif dir == 11: + t = t + _(" Descending") + self.texts.info.config(text = t) + + def getFoundationDir(self): + for s in self.s.foundations: + if len(s.cards) >= 2: + return (s.cards[-1].suit - s.cards[-2].suit) % 12 + return 0 + + + +class GreaterQueue(LesserQueue): + Hint_Class = Queue_Hint + BRAID_CARDS = 40 + BRAID_OFFSET = .5 + + + +# /*********************************************************************** +# * Japanese Garden +# ************************************************************************/ + +class JapaneseGarden(AbstractFlowerGame): + Hint_Class = CautiousDefaultHint + RowStack_Class = FlowerClock_RowStack + WIDTH = 10 + HEIGHT = 6 + XROWS = 3 + YROWS = 2 + MAX_CARDS = 6 + MAX_MOVE = 1 + XRESERVES = 6 + YRESERVES = 2 + MAX_RESERVE = 0 + INITIAL_DEAL = 6 + DEAL_RESERVES = 1 + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_card") + + # Set window size + self.setSize(l.XM + l.XS * self.WIDTH, l.YM * 3 + l.YS * self.HEIGHT) + + # Create foundations + x = self.width / 2 + l.XM / 2 - l.XS * 3 + y = l.YM + for j in range(2): + for i in range(6): + s.foundations.append(Hanafuda_SS_FoundationStack(x, y, self, i + (j * 6), + max_cards=4, max_accept=1, base_rank=3)) + x = x + l.XS + x = self.width / 2 + l.XM / 2 - l.XS * 3 + y = y + l.YS + + # Create flower beds + x = l.XM + y = l.YM * 2 + l.YS * 2 + for j in range(self.YROWS): + for i in range(self.XROWS): + row = self.RowStack_Class(x, y, self, yoffset=0, max_accept=self.MAX_MOVE, + max_move=self.MAX_MOVE, max_cards=self.MAX_CARDS, base_rank=0) + row.CARD_XOFFSET = l.CW / 2 + s.rows.append(row) + x = x + self.width / self.XROWS + x = l.XM + y = y + l.YS + self.setRegion(s.rows, (l.XM, l.YS * 2, 999999, y)) + + # Create pool + x = self.width / 2 + l.XM / 2 - (l.XS * self.XRESERVES) / 2 + for j in range(self.YRESERVES): + for i in range(self.XRESERVES): + s.reserves.append(ReserveStack(x, y, self, max_accept=self.MAX_RESERVE)) + x = x + l.XS + x = self.width / 2 + l.XM / 2 - l.XS * (self.XRESERVES / 2) + y = y + l.YS + if s.reserves: + self.setRegion(s.reserves, (l.XM, l.YS * (2 + self.YROWS), 999999, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM, l.YM, self) + + # Define stack-groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 + self.startDealSample() + for i in range(self.INITIAL_DEAL): + self.s.talon.dealRow() + if self.DEAL_RESERVES: + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealCards() + + + +class JapaneseGardenII(JapaneseGarden): + RowStack_Class = JapaneseGarden_RowStack + + + +class JapaneseGardenIII(JapaneseGardenII): + XROWS = 2 + YROWS = 4 + MAX_CARDS = 7 + XRESERVES = 0 + YRESERVES = 0 + DEAL_RESERVES = 0 + + +class SixSages(JapaneseGarden): + Hint_Class = CautiousDefaultHint + XROWS = 2 + YROWS = 3 + MAX_CARDS = 9 + XRESERVES = 1 + YRESERVES = 1 + MAX_RESERVE = 1 + INITIAL_DEAL = 8 + DEAL_RESERVES = 0 + + +class SixTengus(SixSages): + RowStack_Class = HanafudaRK_RowStack + HEIGHT = 5 + MAX_MOVE = 2 + XRESERVES = 0 + YRESERVES = 0 + + + +# /*********************************************************************** +# * Four Seasons +# ************************************************************************/ + +class FourSeasons(AbstractFlowerGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_card") + + # Set window size + self.setSize(l.XM + l.XS * 7, l.YM + l.YS * 5) + + # Create rows + x, y, offset = l.XM, l.YM, self.app.images.CARD_YOFFSET + for i in range(6): + s.rows.append(Samuri_RowStack(x, y, self, offset, max_cards=8, + max_accept=8, base_rank=0)) + x = x + l.XS + l.XM + (l.XM * (i == 2)) + x, y = l.XM, y + l.YS * 2.5 + for i in range(6): + s.rows.append(Samuri_RowStack(x, y, self, offset, max_cards=8, + max_accept=8, base_rank=0)) + x = x + l.XS + l.XM + (l.XM * (i == 2)) + self.setRegion(s.rows, (0, 0, 999999, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(-l.XS, -l.YS, self) + + # Define stack-groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 48 * self.gameinfo.decks + self.startDealSample() + for i in range(4): + self.s.talon.dealRow(flip=1) + + + # + # Game extras + # + + def isGameWon(self): + for r in self.s.rows: + cards = r.cards + if not len(cards) == 4: + return 0 + if not (cards[0].suit == r.id + and r.isHanafudaSequence(cards)): + return 0 + return 1 + + + +# /*********************************************************************** +# // Wisteria +# ************************************************************************/ + +class Wisteria(AbstractFlowerGame): + RowStack_Class = StackWrapper(Hanafuda_SequenceStack, base_rank=NO_RANK) + + # + # game layout + # + + def createGame(self, rows=13): + # create layout + l, s = Layout(self), self.s + + # set size + self.setSize(l.XM + rows * l.XS, l.YM + 6 * l.YS) + + # create stacks + x, y = self.width / 2 - l.XS * 3, l.YM + for i in range(2): + for suit in range(6): + s.foundations.append(Hanafuda_SS_FoundationStack(x, y, self, suit=suit + (6 * i))) + x = x + l.XS + x, y = self.width / 2 - l.XS * 3, y + l.YS + self.setRegion(self.s.foundations, (-999, -999, 999999, l.YM + l.YS * 2), priority=1) + x, y = l.XM, l.YM + l.YS * 2 + for i in range(rows): + stack = self.RowStack_Class(x, y, self, yoffset=l.YOFFSET) + s.rows.append(stack) + x = x + l.XS + s.talon = InitialDealTalonStack(l.XS, l.YS / 2, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + if self.s.talon.cards[-1].rank == 0: + if self.s.rows[i].cards: + i = i + 1 + self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4) + + + +# /*********************************************************************** +# // Flower Arrangement Hint +# ************************************************************************/ + +class FlowerArrangement_Hint(AbstractHint): + def computeHints(self): + game = self.game + + # 2)See if we can move a card to the tableaux + if not self.hints: + for r in game.sg.dropstacks: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + if r in game.s.tableaux: + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + base_score = 80000 + (4 - r.cap.base_suit) + else: + base_score = 80000 + # find a stack that would accept this card + for t in game.s.tableaux: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 3)See if we can move a card from the tableaux + # to a row stack. This can only happen if there are + # no more cards to deal. + if not self.hints: + for r in game.s.tableaux: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = 70000 + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 4)See if we can move a card within the row stacks + if not self.hints: + for r in game.s.rows: + pile = r.getPile() + if not pile or len(pile) != 1 or len(pile) == len(r.cards): + continue + base_score = 60000 + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 5)See if we can deal cards + if self.level >= 2: + if game.canDealCards(): + self.addHint(self.SCORE_DEAL, 0, game.s.talon, None) + + +# /*********************************************************************** +# // Flower Arrangement Stacks +# ************************************************************************/ + +class FlowerArrangement_TableauStack(Flower_OpenStack): + def __init__(self, x, y, game, yoffset, **cap): + kwdefault(cap, dir=-1, max_move=1, max_cards=4, max_accept=1, base_rank=3) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + # check that the base card is correct + suits = range(self.cap.mod, (self.cap.mod + 4)) + if self.cards and (self.cards[0].rank == 3 + and self.cards[-1].suit in suits): + return self.isHanafudaSequence([self.cards[-1], cards[0]]) + return not self.cards and cards[0].rank == 3 and cards[0].suit in suits + + def getBottomImage(self): + return self.game.app.images.getSuitBottom() + + +class FlowerArrangement_RowStack(BasicRowStack): + + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # check + return not (self.cards or self.game.s.talon.cards) + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +# /*********************************************************************** +# // Flower Arrangement +# ************************************************************************/ + +class FlowerArrangement(Game): + Hint_Class = FlowerArrangement_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + TABLEAU_YOFFSET = min(9, max(3, l.YOFFSET / 3)) + + # set window + th = l.YS + 3 * TABLEAU_YOFFSET + # (set piles so that at least 2/3 of a card is visible with 10 cards) + h = (10-1)*l.YOFFSET + l.CH*2/3 + self.setSize(10*l.XS+l.XM, l.YM + 3*th + l.YM + h) + + # create stacks + s.addattr(tableaux=[]) # register extra stack variable + x = l.XM + 8 * l.XS + l.XS / 2 + y = l.YM + for i in range(3): + x = l.XM + for j in range(8): + s.tableaux.append(FlowerArrangement_TableauStack(x, y, self, TABLEAU_YOFFSET, mod=i * 4)) + x = x + l.XS + y = y + th + x, y = l.XM, y + l.YM + for i in range(8): + s.rows.append(FlowerArrangement_RowStack(x, y, self, max_accept=1)) + x = x + l.XS + x = l.XM + 8 * l.XS + l.XS / 2 + y = self.height - l.YS + s.talon = DealRowTalonStack(x, y, self) + l.createText(s.talon, "se") + + # define stack-groups + self.sg.openstacks = s.tableaux + s.rows + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.tableaux + s.rows + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.tableaux, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def isGameWon(self): + for stack in self.s.tableaux: + if len(stack.cards) != 4: + return 0 + return 1 + + def fillStack(self, stack): + if self.s.talon.cards: + if stack in self.s.rows and len(stack.cards) == 0: + self.s.talon.dealRow(rows=[stack]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 3 == card2.rank or card2.rank + 3 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# * Register the games +# ************************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_HANAFUDA + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + suits=range(12), ranks=range(4)) + registerGame(gi) + return gi + +r(12369, Paulownia, 'Paulownia', GI.GT_HANAFUDA, 1, -1) +r(12370, LesserQueue, 'Lesser Queue', GI.GT_HANAFUDA, 2, 2) +r(12371, GreaterQueue, 'Greater Queue', GI.GT_HANAFUDA, 4, 2) +r(12373, JapaneseGarden, 'Japanese Garden', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12374, JapaneseGardenII, 'Japanese Garden II', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12375, SixSages, 'Six Sages', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12376, SixTengus, 'Six Tengus', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12377, JapaneseGardenIII, 'Japanese Garden III', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12378, FourSeasons, 'Four Seasons', GI.GT_HANAFUDA | GI.GT_OPEN, 1, 0) +r(12380, Eularia, 'Eularia', GI.GT_HANAFUDA, 1, -1) +r(12381, Peony, 'Peony', GI.GT_HANAFUDA, 1, -1) +r(12382, Iris, 'Iris', GI.GT_HANAFUDA, 1, 0) +r(12383, Pine, 'Pine', GI.GT_HANAFUDA, 1, 0) +r(12384, Wisteria, 'Wisteria', GI.GT_HANAFUDA, 1, 0) +r(12385, FlowerArrangement, 'Flower Arrangement', GI.GT_HANAFUDA, 2, 0) + +del r diff --git a/pysollib/games/ultra/hanafuda_common.py b/pysollib/games/ultra/hanafuda_common.py new file mode 100644 index 0000000000..933dc4cb40 --- /dev/null +++ b/pysollib/games/ultra/hanafuda_common.py @@ -0,0 +1,482 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [ + 'AbstractFlowerGame', + 'Queue_Hint', + 'Flower_FoundationStack', + 'Hanafuda_SS_FoundationStack', + 'FlowerClock_Foundation', + 'Gaji_Foundation', + 'Pagoda_Foundation', + 'MatsuKiri_Foundation', + 'GreatWall_FoundationStack', + 'FourWinds_Foundation', + 'Queue_Foundation', + 'Flower_OpenStack', + 'Hanafuda_SequenceStack', + 'Oonsoo_SequenceStack', + 'FlowerClock_RowStack', + 'Gaji_RowStack', + 'Matsukiri_RowStack', + 'Samuri_RowStack', + 'GreatWall_RowStack', + 'FourWinds_RowStack', + 'Queue_BraidStack', + 'Queue_RowStack', + 'Queue_ReserveStack', + 'JapaneseGarden_RowStack', + 'HanafudaRK_RowStack', + ] + + +import sys, math + +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# * +# ***********************************************************************/ + +class AbstractFlowerGame(Game): + SUITS = (_("Pine"), _("Plum"), _("Cherry"), _("Wisteria"), + _("Iris"), _("Peony"), _("Bush Clover"), _("Eularia"), + _("Chrysanthemum"), _("Maple"), _("Willow"), _("Paulownia")) + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.suit == card2.suit) + and ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank))) + +class Queue_Hint(DefaultHint): + pass + + + +# /*********************************************************************** +# * Flower Foundation Stacks +# ***********************************************************************/ + +class Flower_FoundationStack(AbstractFoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_cards=12, max_move=0, base_rank=ANY_RANK, base_suit=ANY_SUIT) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def updateText(self): + AbstractFoundationStack.updateText(self) + self.game.updateText() + + def isHanafudaSequence(self, s, strictness=1): + for i in range(len(s) - 1): + if s[i].suit != s[i + 1].suit: + return 0 + if s[i].suit == 10 or strictness: + a, b = s[i].rank, s[i + 1].rank + else: + a, b = self.swapTrashCards(s[i], s[i + 1]) + if a + 1 != b: + return 0 + return cardsFaceUp(s) + + def swapTrashCards(self, carda, cardb): + a, b = carda.rank, cardb.rank + if a == 3 and b == 2: + a, b = 2, 3 + elif a == 1 and b == 3: + b = 2 + return a, b + + +class Hanafuda_SS_FoundationStack(Flower_FoundationStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].rank == 3 + return self.isHanafudaSequence([cards[0], stackcards[-1]]) + + def getBottomImage(self): + return self.game.app.images.getSuitBottom(self.cap.suit) + + +class FlowerClock_Foundation(Flower_FoundationStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].rank == 3 + if not stackcards[-1].suit == cards[0].suit: + return 0 + return stackcards[-1].rank == cards[0].rank + 1 + + +class Gaji_Foundation(Flower_FoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_move=1, min_cards=1, max_accept=1, base_suit=ANY_SUIT) + apply(Flower_FoundationStack.__init__, (self, x, y, game, suit), cap) + self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + return ((((stackcards[-1].suit + 1) % 12) == cards[0].suit) + and (stackcards[-1].rank == cards[0].rank)) + + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + +class Pagoda_Foundation(Flower_FoundationStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].rank == 3 + a, b = stackcards[-1].rank, cards[0].rank + if len(stackcards) < 4: + return a - 1 == b + elif len(stackcards) > 4: + return a + 1 == b + else: + return a == b + + def getBottomImage(self): + return self.game.app.images.getSuitBottom(self.cap.suit) + + +class MatsuKiri_Foundation(Flower_FoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_move=0, max_cards=48, max_accept=4, min_accept=4) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + self.CARD_YOFFSET = self.game.app.images.CARDH / 10 + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + if not self.isHanafudaSequence(cards, 0): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].suit == 0 + return stackcards[-1].suit + 1 == cards[0].suit + +## def getBottomImage(self): +## return self.game.app.images.getBraidBottom() + + +class GreatWall_FoundationStack(Flower_FoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_cards=48, max_move=1, min_accept=1, max_accept=1) + apply(Flower_FoundationStack.__init__, (self, x, y, game, suit), cap) + self.CARD_YOFFSET = self.game.app.images.CARDH / 20 + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if stackcards: + return ((stackcards[-1].suit + 1) % 12 == cards[0].suit + and cards[0].rank == self.cap.base_rank) + else: + return cards[0].suit == 0 + + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + +class FourWinds_Foundation(Flower_FoundationStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not (cards[0].rank == self.cap.base_rank): + return 0 + if not stackcards: + return (cards[0].suit == 0) + else: + return (cards[0].suit == stackcards[-1].suit + 1) + +## def getBottomImage(self): +## return self.game.app.images.getLetter(self.cap.base_rank) + + +class Queue_Foundation(AbstractFoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=12, dir=0, base_suit=ANY_SUIT, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return cards[0].suit == self.game.base_card.suit + stack_dir = self.game.getFoundationDir() + if stack_dir == 0: + card_dir = (cards[0].suit - self.cards[-1].suit) % 12 + return card_dir in (1, 11) + else: + return (self.cards[-1].suit + stack_dir) % 12 == cards[0].suit + + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + + + +# /*********************************************************************** +# * Flower Row Stacks +# ***********************************************************************/ + +class Flower_OpenStack(OpenStack): + + def __init__(self, x, y, game, yoffset, **cap): + kwdefault(cap, max_move=99, max_cards=99, max_accept=99, base_rank=0, dir=1) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def isHanafudaSequence(self, cards, strictness=1): + c1 = cards[0] + for c2 in cards[1:]: + if c1.suit != c2.suit: + return 0 + if c1.suit == 10 or strictness: + a, b = c1.rank, c2.rank + else: + a, b = self.swapTrashCards(c1, c2) + if a + self.cap.dir != b: + return 0 + c1 = c2 + return 1 + + def swapTrashCards(self, carda, cardb): + a, b = carda.rank, cardb.rank + if a == 3 and b == 2: + a, b = 2, 3 + elif a == 1 and b == 3: + b = 2 + return a, b + + +class Hanafuda_SequenceStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isHanafudaSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 0 or self.cap.base_rank == ANY_RANK + return self.isHanafudaSequence([stackcards[-1], cards[0]]) + + +class Oonsoo_SequenceStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isHanafudaSequence(cards, 0)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 0 or self.cap.base_rank == ANY_RANK + return self.isHanafudaSequence([stackcards[-1], cards[0]], 0) + + +class FlowerClock_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not len(stackcards): + return 1 + return stackcards[-1].rank + 1 == cards[0].rank + + +class Gaji_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if ((not len(stackcards)) + or ((stackcards[-1].suit == 10) and (stackcards[-1].rank == 3)) + or ((cards[0].suit == 10) and (cards[0].rank == 3))): + return 1 + elif stackcards[-1].suit != cards[0].suit: + return 0 + a, b = self.swapTrashCards(stackcards[-1], cards[0]) + return a + 1 == b + + +class Matsukiri_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].rank == 0 + if cards[0].suit != stackcards[-1].suit: + return 0 + if stackcards[-1].suit == 10 or self.game.Strictness: + a, b = stackcards[-1].rank, cards[0].rank + else: + a, b = self.swapTrashCards(stackcards[-1], cards[0]) + return a + 1 == b + + def canDropCards(self, stacks): + pile = self.getPile() + if not pile or len(pile) <= 3: + return (None, 0) + f = self.game.s.foundations[0] + if not f.cards: + suit = 0 + else: + suit = f.cards[-1].suit + 1 + if not pile[-1].suit == suit or not self.isHanafudaSequence(pile[-4:], 0): + return (None, 0) + return (f, 4) + + +class Samuri_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].rank == 0 + return stackcards[-1].suit == cards[0].suit and stackcards[-1].rank + 1 == cards[0].rank + + +class GreatWall_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if not stackcards: + return cards[0].rank == 0 + if cards[0].rank == stackcards[-1].rank: + return stackcards[-1].suit == (cards[0].suit + 1) % 12 + a, b = self.swapTrashCards(stackcards[-1], cards[0]) + return a + 1 == b + + +class FourWinds_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if len(cards) - 1 or len(stackcards) >= 3: + return 0 + if not stackcards: + return 1 + return ((cards[0].rank == stackcards[-1].rank) and (cards[0].suit == stackcards[-1].suit - 1)) + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +class Queue_BraidStack(OpenStack): + + def __init__(self, x, y, game, yoffset): + OpenStack.__init__(self, x, y, game) + CW = self.game.app.images.CARDW + self.CARD_YOFFSET = int(self.game.app.images.CARD_YOFFSET * yoffset) + # use a sine wave for the x offsets + # compensate for card width + offset = self.game.app.images.CARDW / 1.7 + self.CARD_XOFFSET = [] + j = 1 + for i in range(20): + self.CARD_XOFFSET.append(int(math.sin(j) * offset)) + j = j + .9 + + +class Queue_RowStack(ReserveStack): + + def fillStack(self): + if not self.cards and self.game.s.braid.cards: + self.game.moveMove(1, self.game.s.braid, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Queue_ReserveStack(ReserveStack): + + def acceptsCards(self, from_stack, cards): + if from_stack is self.game.s.braid or from_stack in self.game.s.rows: + return 0 + return ReserveStack.acceptsCards(self, from_stack, cards) + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +class JapaneseGarden_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not from_stack in self.game.s.rows): + return 0 + stackcards = self.cards + if not len(stackcards): + return 1 + return stackcards[-1].rank + 1 == cards[0].rank + + +class HanafudaRK_RowStack(Flower_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not isRankSequence(cards, dir=1)): + return 0 + stackcards = self.cards + if not len(stackcards): + return 1 + return stackcards[-1].rank + 1 == cards[0].rank + + + + + + diff --git a/pysollib/games/ultra/hexadeck.py b/pysollib/games/ultra/hexadeck.py new file mode 100644 index 0000000000..ae236affbe --- /dev/null +++ b/pysollib/games/ultra/hexadeck.py @@ -0,0 +1,1405 @@ +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, math + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // Hex A Deck Foundation Stacks +# ************************************************************************/ + +class HexADeck_FoundationStack(SS_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_move=0, max_cards=12) + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + +class HexATrump_Foundation(HexADeck_FoundationStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + for s in self.game.s.foundations[:3]: + if len(s.cards) != 16: + return 0 + return 1 + + +class Merlins_Foundation(AbstractFoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=16, dir=0, base_rank=NO_RANK, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return 1 + stack_dir = self.game.getFoundationDir() + if stack_dir == 0: + card_dir = (cards[0].rank - self.cards[-1].rank) % self.cap.mod + return card_dir in (1, 15) + else: + return (self.cards[-1].rank + stack_dir) % self.cap.mod == cards[0].rank + + +# /*********************************************************************** +# // Hex A Deck Row Stacks +# ************************************************************************/ + +class HexADeck_OpenStack(OpenStack): + + def __init__(self, x, y, game, yoffset, **cap): + kwdefault(cap, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS, dir=-1) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def isRankSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not c1.rank + dir == c2.rank: + return 0 + c1 = c2 + return 1 + + def isAlternateColorSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if (c1.color < 2 and c1.color == c2.color + or not c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isSuitSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not ((c1.color == 2 or c2.color == 2 + or c1.suit == c2.suit) + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + +class HexADeck_RK_RowStack(HexADeck_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isRankSequence(cards)): + return 0 + if not self.cards: + return cards[0].rank == 15 or self.cap.base_rank == ANY_RANK + return self.isRankSequence([self.cards[-1], cards[0]]) + + def canMoveCards(self, cards): + return (self.basicCanMoveCards(cards) + and self.isRankSequence(cards)) + + +class HexADeck_AC_RowStack(HexADeck_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isAlternateColorSequence(cards)): + return 0 + if not self.cards: + return cards[0].rank == 15 or self.cap.base_rank == ANY_RANK + return self.isAlternateColorSequence([self.cards[-1], cards[0]]) + + def canMoveCards(self, cards): + return (self.basicCanMoveCards(cards) + and self.isAlternateColorSequence(cards)) + + +class HexADeck_SS_RowStack(HexADeck_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isSuitSequence(cards)): + return 0 + if not self.cards: + return cards[0].rank == 15 or self.cap.base_rank == ANY_RANK + return self.isSuitSequence([self.cards[-1], cards[0]]) + + def canMoveCards(self, cards): + return (self.basicCanMoveCards(cards) + and self.isSuitSequence(cards)) + + +class Bits_RowStack(ReserveStack): + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if stackcards or cards[0].suit == 4: + return 0 + i = int(self.id / 4) + for r in self.game.s.rows[i * 4:self.id]: + if not r.cards: + return 0 + return ((self.game.s.foundations[i].cards[-1].rank + 1 + >> (self.id % 4)) % 2 == (cards[0].rank + 1) % 2) + + +class Bytes_RowStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if stackcards or cards[0].suit == 4: + return 0 + id = self.id - 16 + i = int(id / 2) + for r in self.game.s.rows[16 + i * 2:self.id]: + if not r.cards: + return 0 + return self.game.s.foundations[i].cards[-1].rank == cards[0].rank + + +class HexAKlon_RowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if stackcards: + if (stackcards[-1].suit == 4 or cards[0].suit == 4): + return 1 + return AC_RowStack.acceptsCards(self, from_stack, cards) + + +class HexADeck_ACRowStack(AC_RowStack): + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + stackcards = self.cards + if stackcards: + if (stackcards[-1].suit == 4 or cards[0].suit == 4): + return stackcards[-1].rank == cards[0].rank + 1 + return AC_RowStack.acceptsCards(self, from_stack, cards) + + +class Familiar_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + # Only take Wizards + return cards[0].suit == 4 + + def getBottomImage(self): + return self.game.app.images.getSuitBottom(4) + + +class Merlins_BraidStack(OpenStack): + def __init__(self, x, y, game): + OpenStack.__init__(self, x, y, game) + CW = self.game.app.images.CARDW + self.CARD_YOFFSET = self.game.app.images.CARD_YOFFSET + # use a sine wave for the x offsets + self.CARD_XOFFSET = [] + j = 1 + for i in range(20): + self.CARD_XOFFSET.append(int(math.sin(j) * 20)) + j = j + .9 + + +class Merlins_RowStack(ReserveStack): + def fillStack(self): + if not self.cards and self.game.s.braid.cards: + self.game.moveMove(1, self.game.s.braid, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Merlins_ReserveStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if from_stack is self.game.s.braid or from_stack in self.game.s.rows: + return 0 + return ReserveStack.acceptsCards(self, from_stack, cards) + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class AbstractHexADeckGame(Game): + RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", + "A", "B", "C", "D", "E", "F", "10") + + +class Merlins_Hint(DefaultHint): + pass + + +# /*********************************************************************** +# // Bits n Bytes +# ************************************************************************/ + +class BitsNBytes(Game): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM * 4 + l.XS * 8, l.YM + l.YS * 4) + + # Create bit stacks + self.bit_texts = [] + y = l.YM + for j in range(4): + x = l.XM * 4 + l.XS * 7 + for i in range(4): + s.rows.append(Bits_RowStack(x, y, self, max_cards=1, + max_accept=1, base_suit=j, max_move=0)) + self.bit_texts.append(MfxCanvasText(self.canvas, x + l.CW / 2 , y + l.CH / 2, + anchor="center", font=font)) + x = x - l.XS + y = y + l.YS + + # Create byte stacks + y = l.YM + for j in range(4): + x = l.XM * 3 + l.XS * 3 + for i in range(2): + s.rows.append(Bytes_RowStack(x, y, self, max_cards=1, + max_accept=1, base_suit=ANY_SUIT, max_move=0)) + x = x - l.XS + y = y + l.YS + + # Create foundations + x = l.XM * 2 + l.XS + y = l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, mod=1, + max_move=0, max_cards=1)) + y = y + l.YS + self.setRegion(s.rows, (0, 0, 999999, 999999)) + + # Create talon + x = l.XM + y = l.YM + s.talon = WasteTalonStack(x, y, self, num_deal=2, max_rounds=2) + l.createText(s.talon, "ss") + y = y + l.YS + l.YM * 2 + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def updateText(self): + if self.preview > 1: + return + for j in range(4): + if not len(self.s.foundations[j].cards): + break + s = self.s.foundations[j].cards[-1].rank + 1 + for i in range(4): + self.bit_texts[i + j * 4].config(text = str(s % 2)) + s = int(s / 2) + + def _shuffleHook(self, cards): + topcards, ranks = [None] * 4, [None] * 4 + for c in cards[:]: + if not c.suit == 4: + if not topcards[c.suit]: + haverank = 0 + for i in range(4): + if c.rank == ranks[i]: + haverank = 1 + if not haverank: + topcards[c.suit] = c + ranks[c.suit] = c.rank + cards.remove(c) + cards = topcards + cards + cards.reverse() + return cards + + def startGame(self): + assert len(self.s.talon.cards) == 68 + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealCards() + + def isGameWon(self): + for s in self.s.rows: + if not s.cards: + return 0 + return 1 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return 0 + + +# /*********************************************************************** +# // Hex A Klon +# ************************************************************************/ + +class HexAKlon(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = HexAKlon_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=-1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations[:4]: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + r = l.s.foundations[4] + s.foundations.append(HexATrump_Foundation(r.x, r.y, self, 4, mod=4, + max_move=0, max_cards=4, base_rank=ANY_RANK)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Hex A Klon by Threes +# ************************************************************************/ + +class HexAKlonByThrees(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = HexAKlon_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=-1, num_deal=3, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations[:4]: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + r = l.s.foundations[4] + s.foundations.append(HexATrump_Foundation(r.x, r.y, self, 4, mod=4, + max_move=0, max_cards=4, base_rank=ANY_RANK)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // King Only Hex A Klon +# ************************************************************************/ + +class KingOnlyHexAKlon(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = HexAKlon_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=-1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations[:4]: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + r = l.s.foundations[4] + s.foundations.append(HexATrump_Foundation(r.x, r.y, self, 4, mod=4, + max_move=0, max_cards=4, base_rank=ANY_RANK)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=15)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def _shuffleHook(self, cards): + basecard = [None] + for c in cards[:]: + if c.suit == 4: + if basecard[0] == None: + basecard[0] = c + cards.remove(c) + cards = basecard + cards + cards.reverse() + return cards + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Klondike Plus 16 +# ************************************************************************/ + +class KlondikePlus16(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = HexAKlon_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=2, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=15)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // The Familiar +# ************************************************************************/ + +class TheFamiliar(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=2, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=15)) + + # Create reserve + x, y = l.XM, self.height - l.YS + s.reserves.append(Familiar_ReserveStack(x, y, self, max_cards=3)) + self.setRegion(s.reserves, (-999, y - l.YM, x + l.XS, 999999), priority=1) + l.createText(s.reserves[0], "se") + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Two Familiars +# ************************************************************************/ + +class TwoFamiliars(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=2, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=12, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=15)) + + # Create reserve + x, y = l.XM, self.height - l.YS + s.reserves.append(Familiar_ReserveStack(x, y, self, max_cards=3)) + self.setRegion(s.reserves, (-999, y - l.YM, x + l.XS, 999999), priority=1) + l.createText(s.reserves[0], "se") + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 * 2 + for i in range(len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Ten by Eight +# ************************************************************************/ + +class TenByEight(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.gypsyLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=-1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=10, waste=1, playcards=30) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 * 2 + frames = 0 + for i in range(8): + if i == 5: + frames = -1 + self.startDealSample() + self.s.talon.dealRow(frames=frames) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Drawbridge +# ************************************************************************/ + +class Drawbridge(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.harpLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=2, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=7, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(len(self.s.rows) - 1): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Double Drawbridge +# ************************************************************************/ + +class DoubleDrawbridge(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.harpLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=2, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=10, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 * 2 + for i in range(len(self.s.rows) - 1): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Hidden Passages +# ************************************************************************/ + +class HiddenPassages(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=2, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=7, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations[:4]: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + r = l.s.foundations[4] + s.foundations.append(HexATrump_Foundation(r.x, r.y, self, 4, mod=4, + max_move=0, max_cards=4, base_rank=ANY_RANK)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + def _shuffleHook(self, cards): + topcards = [None] * 4 + for c in cards[:]: + if c.rank == 0 and not c.suit == 4: + topcards[c.suit] = c + cards.remove(c) + cards = topcards + cards + cards.reverse() + return cards + + def startGame(self): + assert len(self.s.talon.cards) == 68 + self.s.talon.dealRow(rows=self.s.foundations[:4], frames=0) + for i in range(2): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Cluitjar's Lair +# ************************************************************************/ + +class CluitjarsLair(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = HexADeck_ACRowStack + + # + # Game layout + # + + def createGame(self, max_rounds=1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=7, waste=1, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations[:4]: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + r = l.s.foundations[4] + s.foundations.append(HexATrump_Foundation(r.x, r.y, self, 4, mod=4, + max_move=0, max_cards=4, base_rank=ANY_RANK)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(2): + self.s.talon.dealRow(flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Merlin's Meander +# ************************************************************************/ + +class MerlinsMeander(AbstractHexADeckGame): + Hint_Class = Merlins_Hint + MERLINS_CARDS = 20 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable - needed for Braid_BraidStack) + h = max(4*l.YS + 30, l.YS+(self.MERLINS_CARDS-1)*l.YOFFSET) + self.setSize(10*l.XS+l.XM, l.YM + h) + + # extra settings + self.base_card = None + + # Create rows, reserves + s.addattr(braid=None) # register extra stack variable + x, y = l.XM, l.YM + for i in range(2): + s.rows.append(Merlins_RowStack(x + l.XS * 0.5, y, self)) + s.rows.append(Merlins_RowStack(x + l.XS * 4.5, y, self)) + s.reserves.append(Familiar_ReserveStack(x + l.XS * 6.5, y, self, max_cards=3)) + y = y + l.YS * 3 + y = l.YM + l.YS + for i in range(2): + s.rows.append(Merlins_ReserveStack(x, y, self)) + s.rows.append(Merlins_ReserveStack(x + l.XS, y, self)) + s.rows.append(Merlins_ReserveStack(x, y + l.YS, self)) + s.rows.append(Merlins_ReserveStack(x + l.XS, y + l.YS, self)) + x = x + l.XS * 4 + + # Create braid + x, y = l.XM + l.XS * 2.2, l.YM + s.braid = Merlins_BraidStack(x, y, self) + + # Create talon, waste + x, y = l.XM + l.XS * 7, l.YM + l.YS * 1.5 + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.CW / 2, y - l.YM, + anchor="s", + font=self.app.getFont("canvas_default")) + x = x - l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # Create foundations + x, y = l.XM + l.XS * 8, l.YM + for i in range(4): + s.foundations.append(Merlins_Foundation(x, y, self, i, mod=16, + max_cards=16, base_rank=ANY_RANK)) + s.foundations.append(Merlins_Foundation(x + l.XS, y, self, i, mod=16, + max_cards=16, base_rank=ANY_RANK)) + y = y + l.YS + self.texts.info = MfxCanvasText(self.canvas, + x + l.CW + l.XM / 2, y, + anchor="n", + font=self.app.getFont("canvas_default")) + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.foundations + s.rows + s.reserves + self.sg.dropstacks = [s.braid] + s.rows + [s.waste] + s.reserves + + + # + # game overrides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 * 2 + self.startDealSample() + self.base_card = None + self.updateText() + for i in range(self.MERLINS_CARDS): + self.s.talon.dealRow(rows=[self.s.braid]) + self.s.talon.dealRow() + # deal base_card to foundations, update cap.base_rank + self.base_card = self.s.talon.getCard() + while self.base_card.suit == 4: + self.s.talon.cards.remove(self.base_card) + self.s.talon.cards.insert(0, self.base_card) + self.base_card = self.s.talon.getCard() + to_stack = self.s.foundations[2 * self.base_card.suit] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + self.updateText() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + # deal first card to WasteStack + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 16 == card2.rank or (card2.rank + 1) % 16 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + + # + # game extras + # + + def updateText(self): + if self.preview > 1 or not self.texts.info: + return + if not self.base_card: + t = "" + else: + t = self.RANKS[self.base_card.rank] + dir = self.getFoundationDir() % 16 + if dir == 1: + t = t + _(" Ascending") + elif dir == 15: + t = t + _(" Descending") + self.texts.info.config(text=t) + + def isGameWon(self): + for s in self.s.rows: + if s.cards and s.cards[0].suit != 4: + return 0 + if not len(self.s.talon.cards) and len(self.s.waste.cards) == 1: + return self.s.waste.cards[0].suit == 4 + return len(self.s.talon.cards) + len(self.s.waste.cards) == 0 + + +# /*********************************************************************** +# // Mage's Game +# ************************************************************************/ + +class MagesGame(Game): + Hint_Class = CautiousDefaultHint + Layout_Method = Layout.gypsyLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, max_rounds=1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=12, texts=0, playcards=20) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds=max_rounds, num_deal=num_deal) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=16, max_cards=16, max_move=1)) + + # Create rows + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit=ANY_SUIT, base_rank=ANY_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 68 + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.rows[2:10]) + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Convolution(AbstractHexADeckGame): + RowStack_Class = StackWrapper(HexADeck_RK_RowStack, base_rank=NO_RANK) + + # + # game layout + # + + def createGame(self, rows=9, reserves=8): + # create layout + l, s = Layout(self), self.s + + # set size + maxrows = max(rows, reserves) + self.setSize(l.XM + (maxrows + 2) * l.XS, l.YM + 6 * l.YS) + + # + playcards = 4 * l.YS / l.YOFFSET + xoffset, yoffset = [], [] + for i in range(playcards): + xoffset.append(0) + yoffset.append(l.YOFFSET) + for i in range(68 * self.gameinfo.decks - playcards): + xoffset.append(l.XOFFSET) + yoffset.append(0) + + # create stacks + x, y = l.XM + (maxrows - reserves) * l.XS / 2, l.YM + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x, y = l.XM + (maxrows - rows) * l.XS / 2, l.YM + l.YS + self.setRegion(s.reserves, (-999, -999, 999999, y - l.YM / 2)) + for i in range(rows): + stack = self.RowStack_Class(x, y, self, yoffset=yoffset) + stack.CARD_XOFFSET = xoffset + stack.CARD_YOFFSET = yoffset + s.rows.append(stack) + x = x + l.XS + x, y = l.XM + maxrows * l.XS, l.YM + for i in range(2): + for suit in range(5): + s.foundations.append(SS_FoundationStack(x, y, self, suit=suit, max_cards=16)) + y = y + l.YS + x, y = x + l.XS, l.YM + self.setRegion(self.s.foundations, (x - l.XS * 2, -999, 999999, + self.height - (l.YS + l.YM)), priority=1) + s.talon = InitialDealTalonStack(self.width - 3 * l.XS / 2, self.height - l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + if self.s.talon.cards[-1].rank == 15: + if self.s.rows[i].cards: + i = i + 1 + self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4) + + # must look at cards + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + for stack in stacks: + if stack.cards and stack is not dragstack: + dist = (stack.cards[-1].x - cx)**2 + (stack.cards[-1].y - cy)**2 + else: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + closest, cdist = stack, dist + return closest + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isRankSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Labyrinth(Convolution): + RowStack_Class = StackWrapper(HexADeck_AC_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isAlternateColorSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Snakestone(Convolution): + RowStack_Class = StackWrapper(HexADeck_SS_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isSuitSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_HEXADECK + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + suits=range(4), ranks=range(16), trumps=range(4)) + registerGame(gi) + return gi + + +r(165, BitsNBytes, 'Bits n Bytes', GI.GT_HEXADECK, 1, 1) +r(166, HexAKlon, 'Hex A Klon', GI.GT_HEXADECK, 1, -1) +r(16666, KlondikePlus16, 'Klondike Plus 16', GI.GT_HEXADECK, 1, 1) +r(16667, HexAKlonByThrees, 'Hex A Klon by Threes', GI.GT_HEXADECK, 1, -1) +r(16668, KingOnlyHexAKlon, 'King Only Hex A Klon', GI.GT_HEXADECK, 1, -1) +r(16669, TheFamiliar, 'The Familiar', GI.GT_HEXADECK, 1, 1) +r(16670, TwoFamiliars, 'Two Familiars', GI.GT_HEXADECK, 2, 1) +r(16671, TenByEight, '10 x 8', GI.GT_HEXADECK, 2, -1) +r(16672, Drawbridge, 'Drawbridge', GI.GT_HEXADECK, 1, 1) +r(16673, DoubleDrawbridge, 'Double Drawbridge', GI.GT_HEXADECK, 2, 1) +r(16674, HiddenPassages, 'Hidden Passages', GI.GT_HEXADECK, 1, 1) +r(16675, CluitjarsLair, 'Cluitjar\'s Lair', GI.GT_HEXADECK, 1, 0) +r(16676, MerlinsMeander, 'Merlin\'s Meander', GI.GT_HEXADECK, 2, 2) +r(16677, MagesGame, 'Mage\'s Game', GI.GT_HEXADECK, 1, 0) +r(16678, Convolution, 'Convolution', GI.GT_HEXADECK, 2, 0) +r(16679, Labyrinth, 'Hex Labyrinth', GI.GT_HEXADECK, 2, 0) +r(16680, Snakestone, 'Snakestone', GI.GT_HEXADECK, 2, 0) + +del r diff --git a/pysollib/games/ultra/larasgame.py b/pysollib/games/ultra/larasgame.py new file mode 100644 index 0000000000..d7124feb41 --- /dev/null +++ b/pysollib/games/ultra/larasgame.py @@ -0,0 +1,774 @@ +##---------------------------------------------------------------------------## +## +## Ultrasol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Matthew Hohlfeld +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys, types + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class LarasGame_Hint(CautiousDefaultHint): + pass + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class LarasGame_Talon(WasteTalonStack): + # Deal a card to each of the RowStacks. Then deal + # cards to the talon. Return number of cards dealt. + def dealRow(self, rows=None, flip=1, reverse=0, frames=-1): + game = self.game + if rows is None: + rows = game.s.rows + old_state = game.enterState(game.S_DEAL) + cards = self.dealToStacks(rows[:game.MAX_ROW], flip, reverse, frames) + for i in range(game.DEAL_TO_TALON): + if self.cards: + game.moveMove(1, self, game.s.rows[-1], frames=frames) + cards = cards + 1 + game.leaveState(old_state) + return cards + + def dealToStacks(self, stacks, flip=1, reverse=0, frames=-1): + game = self.game + i, move = 0, game.moveMove + for r in stacks: + if not self.cards: + return 0 + assert not self.getCard().face_up + assert r is not self + if flip: + game.flipMove(self) + move(1, self, r, frames=frames) + # Dealing has extra rules in this game type: + # If card rank == card location then add one card to talon + # If card rank == ACE then add two cards to talon + # If card rank == JACK, or higher then add one card to talon + # After all the rows have been dealt, deal cards to talon in self.dealRow + rank = r.getCard().rank + if rank == i: # Is the rank == position? + if not self.cards: + return 0 + move(1, self, game.s.rows[-1], frames=frames) + i = i + 1 + if rank == 0: # Is this an Ace? + for j in range(2): + if not self.cards: + return 0 + move(1, self, game.s.rows[-1], frames=frames) + if rank >= 10: # Is it a Jack or better? + if not self.cards: + return 0 + move(1, self, game.s.rows[-1], frames=frames) + return len(stacks) + + def dealCards(self, sound=0): + game = self.game + for r in game.s.reserves[:20]: + while r.cards: + game.moveMove(1, r, game.s.rows[game.active_row], frames=3, shadow=0) + if self.cards: + game.active_row = self.getActiveRow() + game.flipMove(self) + game.moveMove(1, self, game.s.reserves[0], frames=4, shadow=0) + ncards = len(game.s.rows[game.active_row].cards) + if ncards >= 20: + # We have encountered an extreme situation. + # In some game type variations it's possible + # to have up to 28 cards on a row stack. + # We'll have to double up on some of the reserves. + for i in range(ncards - 19): + game.moveMove(1, game.s.rows[game.active_row], + game.s.reserves[19 - i], frames=4, shadow=0) + ncards = len(game.s.rows[game.active_row].cards) + assert ncards <= 19 + for i in range(ncards): + game.moveMove(1, game.s.rows[game.active_row], + game.s.reserves[ncards - i], frames=4, shadow=0) + return len(self.cards) or self.canDealCards() + if self.round < self.max_rounds: + num_cards = 0 + rows = list(game.s.rows)[:game.MAX_ROW] + rows.reverse() + for r in rows: + while r.cards: + num_cards = num_cards + 1 + if r.cards[-1].face_up: + game.flipMove(r) + game.moveMove(1, r, self, frames=0) + assert len(self.cards) == num_cards + if num_cards == 0: + return 0 + game.nextRoundMove(self) + game.dealToRows() + if sound: + game.stopSamples() + return len(self.cards) + + def canDealCards(self): + if self.game.demo and self.game.moves.index >= 400: + return 0 + return (self.cards or (self.round < self.max_rounds and not self.game.isGameWon())) + + def updateText(self): + if self.game.preview > 1: + return + WasteTalonStack.updateText(self, update_rounds=0) + if not self.max_rounds - 1: + return + t = _("Round %d") % self.round + self.texts.rounds.config(text=t) + + def getActiveRow(self): + return self.getCard().rank + + + +class DojoujisGame_Talon(LarasGame_Talon): + + def getActiveRow(self): + card = self.getCard() + return card.rank + card.deck * 4 + + + +class DoubleKalisGame_Talon(LarasGame_Talon): + + def getActiveRow(self): + card = self.getCard() + return card.rank + card.deck * 12 + + + +class LarasGame_RowStack(OpenStack): + def __init__(self, x, y, game, yoffset = 1, **cap): + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + + +class LarasGame_ReserveStack(OpenStack): + pass + + + +class BridgetsGame_Reserve(OpenStack): + + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return from_stack in self.game.s.foundations and cards[0].suit == 4 + return from_stack in self.game.s.rows + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + + +class LarasGame_Reserve(BridgetsGame_Reserve): + + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + return from_stack in self.game.s.rows + + + +# /*********************************************************************** +# // Lara's Game +# ************************************************************************/ + +class LarasGame(Game): + Hint_Class = LarasGame_Hint + Talon_Class = LarasGame_Talon + Reserve_Class = None + DEAL_TO_TALON = 2 + MAX_ROUNDS = 1 + ROW_LENGTH = 4 + MAX_ROW = 13 + DIR = (-1, 1) + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + ROW_LENGTH = self.ROW_LENGTH + + # set window + w, h = l.XM + l.XS * (ROW_LENGTH + 5), l.YM + l.YS * (ROW_LENGTH + (ROW_LENGTH != 6)) + self.setSize(w, h) + + # extra settings + self.active_row = None + + # Create foundations + x, y = l.XM, l.YM + for j in range(2): + for i in range(ROW_LENGTH): + s.foundations.append(SS_FoundationStack(x, y, self, self.Base_Suit(i, j), + max_cards = self.Max_Cards(i), mod = self.Mod(i), + dir = self.DIR[j], base_rank = self.Base_Rank(i, j))) + y = y + l.YS * (not j) + x = x + l.XS * j + x, y = x + l.XS * 2, l.YM + + # Create rows + x, y = l.XM + l.XS, y + l.YS + for i in range(self.MAX_ROW): + s.rows.append(LarasGame_RowStack(x, y, self)) + x = x + l.XS + if i == ROW_LENGTH or i == ROW_LENGTH * 2 + 1 or i == ROW_LENGTH * 3 + 2: + x, y = l.XM + l.XS, y + l.YS + + # Create reserves + x, y = l.XM + l.XS * (ROW_LENGTH == 6), l.YM + l.YS * (ROW_LENGTH - (ROW_LENGTH == 6)) + for i in range(20): + s.reserves.append(LarasGame_ReserveStack(x, y, self, max_cards=2)) + x = x + l.XS * (i < (ROW_LENGTH + 4)) - l.XS * (i == (ROW_LENGTH + 9)) + y = y - l.YS * (i > (ROW_LENGTH + 3) and i < (ROW_LENGTH + 9)) + l.YS * (i > (ROW_LENGTH + 9)) + + # Create talon + x, y = l.XM + l.XS * (ROW_LENGTH + 2), h - l.YM - l.YS * 3 + s.talon = self.Talon_Class(x, y, self, max_rounds=self.MAX_ROUNDS) + l.createText(s.talon, "s") + if self.MAX_ROUNDS - 1: + s.talon.texts.rounds = MfxCanvasText(self.canvas, + x + l.XS / 2, y - l.YM, + anchor="center", + font=self.app.getFont("canvas_default")) + y = h - l.YS * 2 + s.rows.append(LarasGame_RowStack(x, y, self, yoffset=0)) + + # Define stack-groups (not default) + self.sg.openstacks = s.foundations + s.rows[:self.MAX_ROW] + self.sg.talonstacks = [s.talon] + s.rows[-1:] + self.sg.dropstacks = s.rows[:self.MAX_ROW] + s.reserves + + # Create relaxed reserve + if self.Reserve_Class != None: + x, y = l.XM + l.XS * (ROW_LENGTH + 2), l.YM + l.YS * .5 + s.reserves.append(self.Reserve_Class(x, y, self, + max_accept=1, max_cards=self.Reserve_Cards)) + self.sg.openstacks = self.sg.openstacks + s.reserves[19:] + self.sg.dropstacks = self.sg.dropstacks + s.reserves[19:] + self.setRegion(s.reserves[19:], (x - l.XM / 2, 0, 99999, 99999)) + + # + # Game extras + # + + def Max_Cards(self, i): + return 13 + + def Mod(self, i): + return 13 + + def Base_Rank(self, i, j): + return 12 * (not j) + + def Deal_Rows(self, i): + return 13 + + def Base_Suit(self, i, j): + return i + + # + # game overrides + # + + def startGame(self): + assert len(self.s.talon.cards) == self.gameinfo.ncards + self.dealToRows() + + def dealToRows(self): + frames, ncards = 0, len(self.s.talon.cards) + for i in range(8): + if not self.s.talon.cards: + break + if i == 4 or len(self.s.talon.cards) <= ncards / 2: + self.startDealSample() + frames = 4 + self.s.talon.dealRow(rows=self.s.rows[:self.Deal_Rows(i)], frames=frames) + self.moveMove(len(self.s.rows[-1].cards), self.s.rows[-1], self.s.talon, frames=0) + self.active_row = None + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + i, j = (stack1 in self.s.foundations), (stack2 in self.s.foundations) + if not (i or j): return 0 + if i: stack = stack1 + else: stack = stack2 + i = 0 + for f in self.s.foundations: + if f == stack: break + i = i + 1 % self.ROW_LENGTH + return (card1.suit == card2.suit and + ((card1.rank + 1) % self.Mod(i) == card2.rank + or (card1.rank - 1) % self.Mod(i) == card2.rank)) + + def getHighlightPilesStacks(self): + return () + + # Finish the current move. + # Append current active_row to moves.current. + # Append moves.current to moves.history. + def finishMove(self): + moves, stats = self.moves, self.stats + if not moves.current: + return 0 + # invalidate hints + self.hints.list = None + # resize (i.e. possibly shorten list from previous undos) + if not moves.index == 0: + m = moves.history[len(moves.history) - 1] + del moves.history[moves.index : ] + # update stats + if self.demo: + stats.demo_moves = stats.demo_moves + 1 + if moves.index == 0: + stats.player_moves = 0 # clear all player moves + else: + stats.player_moves = stats.player_moves + 1 + if moves.index == 0: + stats.demo_moves = 0 # clear all demo moves + stats.total_moves = stats.total_moves + 1 + # add current move to history (which is a list of lists) + moves.current.append(self.active_row) + moves.history.append(moves.current) + moves.index = moves.index + 1 + assert moves.index == len(moves.history) + moves.current = [] + self.updateText() + self.updateStatus(moves=moves.index) + self.updateMenus() + return 1 + + def undo(self): + assert self.canUndo() + assert self.moves.state == self.S_PLAY and self.moves.current == [] + assert 0 <= self.moves.index <= len(self.moves.history) + if self.moves.index == 0: + return + self.moves.index = self.moves.index - 1 + m = self.moves.history[self.moves.index] + m = m[:len(m) - 1] + m.reverse() + self.moves.state = self.S_UNDO + for atomic_move in m: + atomic_move.undo(self) + self.moves.state = self.S_PLAY + m = self.moves.history[max(0, self.moves.index - 1)] + self.active_row = m[len(m) - 1] + self.stats.undo_moves = self.stats.undo_moves + 1 + self.stats.total_moves = self.stats.total_moves + 1 + self.hints.list = None + self.updateText() + self.updateStatus(moves = self.moves.index) + self.updateMenus() + + def redo(self): + assert self.canRedo() + assert self.moves.state == self.S_PLAY and self.moves.current == [] + assert 0 <= self.moves.index <= len(self.moves.history) + if self.moves.index == len(self.moves.history): + return + m = self.moves.history[self.moves.index] + self.moves.index = self.moves.index + 1 + self.active_row = m[len(m) - 1] + m = m[:len(m) - 1] + self.moves.state = self.S_REDO + for atomic_move in m: + atomic_move.redo(self) + self.moves.state = self.S_PLAY + self.stats.redo_moves = self.stats.redo_moves + 1 + self.stats.total_moves = self.stats.total_moves + 1 + self.hints.list = None + self.updateText() + self.updateStatus(moves = self.moves.index) + self.updateMenus() + + def _restoreGameHook(self, game): + self.active_row = game.loadinfo.active_row + + def _loadGameHook(self, p): + self.loadinfo.addattr(active_row=0) # register extra load var. + self.loadinfo.active_row = p.load() + + def _saveGameHook(self, p): + p.dump(self.active_row) + + + +# /*********************************************************************** +# // Relaxed Lara's Game +# ************************************************************************/ + +class RelaxedLarasGame(LarasGame): + Reserve_Class = LarasGame_Reserve + Reserve_Cards = 1 + DEAL_TO_TALON = 3 + MAX_ROUNDS = 2 + + +# /*********************************************************************** +# // Double Lara's Game +# ************************************************************************/ + +class DoubleLarasGame(RelaxedLarasGame): + Reserve_Cards = 2 + MAX_ROUNDS = 3 + + # + # Game extras + # + + def Max_Cards(self, i): + return 26 + + +# /*********************************************************************** +# // Katrina's Game +# ************************************************************************/ + +class KatrinasGame(LarasGame): + DEAL_TO_TALON = 3 + MAX_ROUNDS = 2 + ROW_LENGTH = 5 + MAX_ROW = 22 + + # + # Game extras + # + + def Max_Cards(self, i): + return 14 + 8 * (i == 4) + + def Mod(self, i): + return 14 + 8 * (i == 4) + + def Base_Rank(self, i, j): + return (13 + 8 * (i == 4)) * (not j) + + def Deal_Rows(self, i): + return 14 + 8 * (i % 2) + + def Base_Suit(self, i, j): + return i + + # + # Game overrides + # + + def getCardFaceImage(self, deck, suit, rank): + return self.app.images.getFace(deck, suit, rank) + + +# /*********************************************************************** +# // Relaxed Katrina's Game +# ************************************************************************/ + +class RelaxedKatrinasGame(KatrinasGame): + Reserve_Class = LarasGame_Reserve + Reserve_Cards = 2 + + +# /*********************************************************************** +# // Double Katrina's Game +# ************************************************************************/ + +class DoubleKatrinasGame(RelaxedKatrinasGame): + Reserve_Cards = 3 + MAX_ROUNDS = 3 + + # + # Game extras + # + + def Max_Cards(self, i): + return 28 + 16 * (i == 4) + + +# /*********************************************************************** +# // Bridget's Game +# // In memory of Bridget Bishop +# // Hanged as a witch on June 10, 1692 +# // Salem Massachusetts, U. S. A. +# // and the nineteen other women +# // and men who followed her +# ************************************************************************/ + +class BridgetsGame(LarasGame): + Reserve_Class = BridgetsGame_Reserve + Reserve_Cards = 2 + MAX_ROUNDS = 2 + ROW_LENGTH = 5 + MAX_ROW = 16 + + # + # Game extras + # + + def Max_Cards(self, i): + return 16 - 12 * (i == 4) + + def Mod(self, i): + return 16 - 12 * (i == 4) + + def Base_Rank(self, i, j): + return (15 - 12 * (i == 4)) * (not j) + + def Deal_Rows(self, i): + return 16 + + def Base_Suit(self, i, j): + return i + + +# /*********************************************************************** +# // Double Bridget's Game +# ************************************************************************/ + +class DoubleBridgetsGame(BridgetsGame): + Reserve_Cards = 3 + MAX_ROUNDS = 3 + + # + # Game extras + # + + def Max_Cards(self, i): + return 32 - 24 * (i == 4) + + +# /*********************************************************************** +# // Fatimeh's Game +# ************************************************************************/ + +class FatimehsGame(LarasGame): + DEAL_TO_TALON = 5 + MAX_ROUNDS = 3 + MAX_ROW = 12 + DIR = (1, 1) + + # + # Game extras + # + + def Max_Cards(self, i): + return 12 + + def Mod(self, i): + return 12 + + def Base_Rank(self, i, j): + return 0 + + def Deal_Rows(self, i): + return 12 + + def Base_Suit(self, i, j): + return i + j * 4 + + +# /*********************************************************************** +# // Relaxed Fatimeh's Game +# ************************************************************************/ + +class RelaxedFatimehsGame(FatimehsGame): + Reserve_Class = LarasGame_Reserve + Reserve_Cards = 2 + + +# /*********************************************************************** +# // Kali's Game +# ************************************************************************/ + +class KalisGame(FatimehsGame): + DEAL_TO_TALON = 6 + ROW_LENGTH = 5 + + # + # Game extras + # + + def Base_Suit(self, i, j): + return i + j * 5 + + +# /*********************************************************************** +# // Relaxed Kali's Game +# ************************************************************************/ + +class RelaxedKalisGame(KalisGame): + Reserve_Class = LarasGame_Reserve + Reserve_Cards = 2 + + +# /*********************************************************************** +# // Double Kali's Game +# ************************************************************************/ + +class DoubleKalisGame(RelaxedKalisGame): + Talon_Class = DoubleKalisGame_Talon + Reserve_Cards = 4 + MAX_ROUNDS = 4 + MAX_ROW = 24 + + # + # Game extras + # + + def Max_Cards(self, i): + return 24 + + def Deal_Rows(self, i): + return 24 + + +# /*********************************************************************** +# // Dojouji's Game +# ************************************************************************/ + +class DojoujisGame(LarasGame): + Talon_Class = DojoujisGame_Talon + ROW_LENGTH = 6 + MAX_ROW = 8 + DIR = (-1, -1) + + # + # Game extras + # + + def Max_Cards(self, i): + return 8 + + def Mod(self, i): + return 4 + + def Base_Rank(self, i, j): + return 3 + + def Deal_Rows(self, i): + return 8 + + def Base_Suit(self, i, j): + return i + j * 6 + + + +# /*********************************************************************** +# // Double Dojouji's Game +# ************************************************************************/ + +class DoubleDojoujisGame(DojoujisGame): + MAX_ROW = 16 + + # + # Game extras + # + + def Max_Cards(self, i): + return 16 + + def Deal_Rows(self, i): + return 16 + + + +# register the game +registerGame(GameInfo(37, LarasGame, "Lara's Game", GI.GT_2DECK_TYPE, 2, 0)) + +registerGame(GameInfo(13001, KatrinasGame, "Katrina's Game", + GI.GT_TAROCK, 2, 1, + ranks = range(14), trumps = range(22))) + +registerGame(GameInfo(13002, BridgetsGame, "Bridget's Game", + GI.GT_HEXADECK, 2, 1, + ranks = range(16), trumps = range(4))) + +registerGame(GameInfo(13003, FatimehsGame, "Fatimeh's Game", + GI.GT_MUGHAL_GANJIFA, 1, 2, + suits = range(8), ranks = range(12))) + +registerGame(GameInfo(13004, KalisGame, "Kali's Game", + GI.GT_DASHAVATARA_GANJIFA, 1, 2, + suits = range(10), ranks = range(12))) + +registerGame(GameInfo(13005, DojoujisGame, "Dojouji's Game", + GI.GT_HANAFUDA, 2, 0, + suits = range(12), ranks = range(4))) + +registerGame(GameInfo(13006, RelaxedLarasGame, "Lara's Game Relaxed", + GI.GT_2DECK_TYPE, 2, 1)) + +registerGame(GameInfo(13007, DoubleLarasGame, "Lara's Game Doubled", + GI.GT_2DECK_TYPE, 4, 2)) + +registerGame(GameInfo(13008, RelaxedKatrinasGame, "Katrina's Game Relaxed", + GI.GT_TAROCK, 2, 1, + ranks = range(14), trumps = range(22))) + +registerGame(GameInfo(13009, DoubleKatrinasGame, "Katrina's Game Doubled", + GI.GT_TAROCK, 4, 2, + ranks = range(14), trumps = range(22))) + +registerGame(GameInfo(13010, DoubleBridgetsGame, "Bridget's Game Doubled", + GI.GT_HEXADECK, 4, 2, + ranks = range(16), trumps = range(4))) + +registerGame(GameInfo(13011, RelaxedKalisGame, "Kali's Game Relaxed", + GI.GT_DASHAVATARA_GANJIFA, 1, 2, + suits = range(10), ranks = range(12))) + +registerGame(GameInfo(13012, DoubleKalisGame, "Kali's Game Doubled", + GI.GT_DASHAVATARA_GANJIFA, 2, 3, + suits = range(10), ranks = range(12))) + +registerGame(GameInfo(13013, RelaxedFatimehsGame, "Fatimeh's Game Relaxed", + GI.GT_MUGHAL_GANJIFA, 1, 2, + suits = range(8), ranks = range(12))) + +registerGame(GameInfo(13014, DoubleDojoujisGame, "Dojouji's Game Doubled", + GI.GT_HANAFUDA, 4, 0, + suits = range(12), ranks = range(4))) diff --git a/pysollib/games/ultra/matrix.py b/pysollib/games/ultra/matrix.py new file mode 100644 index 0000000000..6fdaa243de --- /dev/null +++ b/pysollib/games/ultra/matrix.py @@ -0,0 +1,471 @@ +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, math + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText, MfxCanvasImage, bind, ANCHOR_NW + +from pysollib.games.special.pegged import Pegged_RowStack, WasteTalonStack, \ + Pegged, PeggedCross1, PeggedCross2, Pegged6x6, Pegged7x7 + + +## Matrix_RowStack +## NewTower_RowStack +## RockHopper_RowStack +## ThreePeaks_TalonStack +## ThreePeaks_RowStack +## Matrix3 +## Matrix4 +## Matrix5 +## Matrix6 +## Matrix7 +## Matrix8 +## Matrix9 +## Matrix10 +## Matrix20 +## NewTowerofHanoi +## RockHopper +## RockHopperCross1 +## RockHopperCross2 +## RockHopper6x6 +## RockHopper7x7 +## ThreePeaks +## ThreePeaksNoScore +## LeGrandeTeton + + +# /*********************************************************************** +# // Matrix Row Stack +# ************************************************************************/ + +class Matrix_RowStack(OpenStack): + + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1, max_cards=1, + base_rank=ANY_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def acceptsCards(self, from_stack, cards): + return OpenStack.acceptsCards(self, from_stack, cards) + + def canFlipCard(self): + return 0 + + def canDropCards(self, stacks): + return (None, 0) + + def cancelDrag(self, event=None): + if event is None: + self._stopDrag() + + def _findCard(self, event): + # we need to override this because the shade may be hiding + # the tile (from Tk's stacking view) + return len(self.cards) - 1 + + def initBindings(self): + bind(self.group, "<1>", self._Stack__clickEventHandler) + bind(self.group, "", self._Stack__controlclickEventHandler) + + def getBottomImage(self): + return None + + def blockMap(self): + ncards = self.game.gameinfo.ncards + id, sqrt = self.id, int(math.sqrt(ncards)) + line, row, column = int(id / sqrt), [], [] + for r in self.game.s.rows[line * sqrt:sqrt + line * sqrt]: + row.append(r.id) + while id >= sqrt: + id = id - sqrt + while id < ncards: + column.append(id) + id = id + sqrt + return [row, column] + + def basicIsBlocked(self): + stack_map = self.blockMap() + for j in range(2): + for i in range(len(stack_map[j])): + if not self.game.s.rows[stack_map[j][i]].cards: + return 0 + return 1 + + def clickHandler(self, event): + game = self.game + row = game.s.rows + if not self.cards or game.drag.stack is self or self.basicIsBlocked(): + return 1 + stack_map = self.blockMap() + for j in range(2): + dir = 1 + for i in range(len(stack_map[j])): + to_stack = row[stack_map[j][i]] + if to_stack is self: + dir = -1 + if not to_stack.cards: + self._stopDrag() + step = 1 + from_stack = row[stack_map[j][i + dir]] + while not from_stack is self: + from_stack.playMoveMove(1, to_stack, frames = 0, sound = 1) + to_stack = from_stack + step = step + 1 + from_stack = row[stack_map[j][i + dir * step]] + self.playMoveMove(1, to_stack, frames = 0, sound = 1) + return 1 + return 1 + + + +# /*********************************************************************** +# // New Tower Row Stack +# ************************************************************************/ + +class NewTower_RowStack(Matrix_RowStack): + + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1, max_cards=99, + base_rank=ANY_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = -max(self.game.app.images.CARD_YOFFSET, 20) + + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + if self.cards: + return self.cards[-1].rank > cards[0].rank + return 1 + + def getBottomImage(self): + # None doesn't recognize clicks. + return self.game.app.images.getLetter(0) + + def basicIsBlocked(self): + return 0 + + def clickHandler(self, event): + game = self.game + drag = game.drag + from_stack = drag.stack + if from_stack is self: + # remove selection + self._stopDrag() + return 1 + # possible move + if from_stack: + if self.acceptsCards(from_stack, from_stack.cards[-1:]): + self._stopDrag() + # this code actually moves the tiles + from_stack.playMoveMove(1, self, frames=3, sound=0) + return 1 + drag.stack = self + # move or create the shade image (see stack.py, _updateShade) + if drag.shade_img: + img = drag.shade_img + img.dtag(drag.shade_stack.group) + img.moveTo(self.x, self.y) + elif self.cards: + img = game.app.images.getShade() + if img is None: + return 1 + img = MfxCanvasImage(game.canvas, self.x, + self.y + self.CARD_YOFFSET[0] * (len(self.cards) - 1), + image=img, anchor=ANCHOR_NW) + drag.shade_img = img + if self.cards: + img.tkraise(self.cards[-1].item) + img.addtag(self.group) + drag.shade_stack = self + return 1 + + + +# /*********************************************************************** +# // Rock Hopper Row Stack +# ************************************************************************/ + +class RockHopper_RowStack(Pegged_RowStack): + + def canFlipCard(self): + return 0 + + def cancelDrag(self, event=None): + if event is None: + self._stopDrag() + + def _findCard(self, event): + # we need to override this because the shade may be hiding + # the tile (from Tk's stacking view) + return len(self.cards) - 1 + + def initBindings(self): + bind(self.group, "<1>", self._Stack__clickEventHandler) + bind(self.group, "", self._Stack__controlclickEventHandler) + + def getBottomImage(self): + # None doesn't recognize clicks. + return self.game.app.images.getReserveBottom() + + def clickHandler(self, event): + game = self.game + drag = game.drag + from_stack = drag.stack + if from_stack is self: + # remove selection + self._stopDrag() + return 1 + # possible move + if from_stack and not self.cards and self._getMiddleStack(from_stack) is not None: + self._stopDrag() + # this code actually moves the tiles + from_stack.moveMove(1, self, frames=3) + return 1 + drag.stack = self + # move or create the shade image (see stack.py, _updateShade) + if drag.shade_img and self.cards: + img = drag.shade_img + img.dtag(drag.shade_stack.group) + img.moveTo(self.x, self.y) + elif self.cards: + img = game.app.images.getShade() + if img is None: + return 1 + img = MfxCanvasImage(game.canvas, self.x, self.y, + image=img, anchor=ANCHOR_NW) + drag.shade_img = img + if self.cards: + img.tkraise(self.cards[-1].item) + img.addtag(self.group) + drag.shade_stack = self + return 1 + + + +# /*********************************************************************** +# // Matrix Game +# ************************************************************************/ + +class Matrix3(Game): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + grid = math.sqrt(self.gameinfo.ncards) + assert grid == int(grid) + grid = int(grid) + + # Set window size + w, h = l.XM * 2 + l.CW * grid, l.YM * 2 + l.CH * grid + self.setSize(w, h) + + # Create rows + for j in range(grid): + x, y = l.XM, l.YM + l.CH * j + for i in range(grid): + s.rows.append(Matrix_RowStack(x, y, self)) + x = x + l.CW + + # Create talon + x, y = l.XM - l.XS, l.YM + s.talon = InitialDealTalonStack(x, y, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game extras + # + + def shuffle(self): + cards = list(self.cards)[:] + cards.reverse() + for card in cards: + self.s.talon.addCard(card, update=0) + card.showBack(unhide=0) + + def scramble(self): + if self.gstats.restarted: + self.random.reset() + ncards, randint = self.gameinfo.ncards, self.random.randint + r = self.s.rows[ncards - int(math.sqrt(ncards))] + rc, stackmap = 1, r.blockMap() + for i in range(randint(max(200, ncards * 4), max(300, ncards * 5))): + r.clickHandler(r) + r = self.s.rows[stackmap[rc][randint(0, len(stackmap[0]) - 1)]] + rc, stackmap = (rc + 1) % 2, r.blockMap() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == self.gameinfo.ncards + self.s.talon.dealRow(rows=self.s.rows[:self.gameinfo.ncards - 1], + flip=1, frames=3) + self.scramble() + self.startDealSample() + + def isGameWon(self): + if self.busy: + return 0 + s = self.s.rows + l = len(s) - 1 + for r in s[:l]: + if not r.cards or not r.cards[0].rank == r.id: + return 0 + self.s.talon.dealRow(rows=s[l:], flip=1, frames=3) + return 1 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank)) + + + +# /*********************************************************************** +# // Size variations +# ************************************************************************/ + +class Matrix4(Matrix3): + + pass + +class Matrix5(Matrix3): + + pass + +class Matrix6(Matrix3): + + pass + +class Matrix7(Matrix3): + + pass + +class Matrix8(Matrix3): + + pass + +class Matrix9(Matrix3): + + pass + +class Matrix10(Matrix3): + + pass + +class Matrix20(Matrix3): + + pass + + +# /*********************************************************************** +# // Rock Hopper +# ************************************************************************/ + +class RockHopper(Pegged): + STACK = RockHopper_RowStack + +class RockHopperCross1(PeggedCross1): + STACK = RockHopper_RowStack + +class RockHopperCross2(PeggedCross2): + STACK = RockHopper_RowStack + +class RockHopper6x6(Pegged6x6): + STACK = RockHopper_RowStack + +class RockHopper7x7(Pegged7x7): + STACK = RockHopper_RowStack + + + +# /*********************************************************************** +# // Register a Matrix game +# ************************************************************************/ + +def r(id, gameclass, short_name): + name = short_name + ncards = int(name[:2]) * int(name[:2]) + gi = GameInfo(id, gameclass, name, + GI.GT_MATRIX, 1, 0, + category=GI.GC_TRUMP_ONLY, short_name=short_name, + suits=(), ranks=(), trumps=range(ncards), + si = {"decks": 1, "ncards": ncards}) + gi.ncards = ncards + gi.rules_filename = "matrix.html" + registerGame(gi) + return gi + +r(22223, Matrix3, " 3x3 Matrix") +r(22224, Matrix4, " 4x4 Matrix") +r(22225, Matrix5, " 5x5 Matrix") +r(22226, Matrix6, " 6x6 Matrix") +r(22227, Matrix7, " 7x7 Matrix") +r(22228, Matrix8, " 8x8 Matrix") +r(22229, Matrix9, " 9x9 Matrix") +r(22230, Matrix10, "10x10 Matrix") +#r(22240, Matrix20, "20x20 Matrix") + +del r + +def r(id, gameclass, short_name): + name = short_name + ncards = 0 + for n in gameclass.ROWS: + ncards = ncards + n + gi = GameInfo(id, gameclass, name, GI.GT_MATRIX, 1, 0, + category=GI.GC_TRUMP_ONLY, short_name=short_name, + suits=(), ranks=(), trumps=range(ncards), + si={"decks": 1, "ncards": ncards}) + gi.ncards = ncards + gi.rules_filename = "pegged.html" + registerGame(gi) + return gi + +r(22221, RockHopper, "Rock Hopper") +r(22220, RockHopperCross1, "Rock Hopper Cross 1") +r(22219, RockHopperCross2, "Rock Hopper Cross 2") +r(22218, RockHopper6x6, "Rock Hopper 6x6") +r(22217, RockHopper7x7, "Rock Hopper 7x7") + +del r diff --git a/pysollib/games/ultra/mughal.py b/pysollib/games/ultra/mughal.py new file mode 100644 index 0000000000..b4b85a8a4c --- /dev/null +++ b/pysollib/games/ultra/mughal.py @@ -0,0 +1,1174 @@ +##---------------------------------------------------------------------------## +## +## Ultrasol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, math + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint, FreeCellType_Hint +from pysollib.pysoltk import MfxCanvasText + + +# /*********************************************************************** +# * Mughal Foundation Stacks +# ***********************************************************************/ + +class Mughal_FoundationStack(AbstractFoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, max_move=0) + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + def updateText(self): + AbstractFoundationStack.updateText(self) + self.game.updateText() + + +class Triumph_Foundation(AbstractFoundationStack): + + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, mod=12, dir=0, base_rank=NO_RANK, max_move=0) + apply(AbstractFoundationStack.__init__, (self, x, y, game, suit), cap) + + def acceptsCards(self, from_stack, cards): + + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return 1 + stack_dir = self.game.getFoundationDir() + if stack_dir == 0: + card_dir = (cards[0].rank - self.cards[-1].rank) % self.cap.mod + return card_dir in (1, 11) + else: + return (self.cards[-1].rank + stack_dir) % self.cap.mod == cards[0].rank + + + +# /*********************************************************************** +# * Mughal Row Stacks +# ***********************************************************************/ + +class Mughal_OpenStack(OpenStack): + + def __init__(self, x, y, game, yoffset, **cap): + kwdefault(cap, max_move=UNLIMITED_MOVES, max_cards=UNLIMITED_CARDS, + max_accept=UNLIMITED_ACCEPTS, base_rank=0, dir=-1) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def isRankSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not c1.rank + dir == c2.rank: + return 0 + c1 = c2 + return 1 + + def isAlternateColorSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not ((c1.suit + c2.suit) % 2 + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isAlternateForceSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not ((c1.suit < 4 and c2.suit > 3 + or c1.suit > 3 and c2.suit < 4) + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isSuitSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not (c1.suit == c2.suit + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + +class Mughal_AC_RowStack(Mughal_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isAlternateColorSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isAlternateColorSequence([stackcards[-1], cards[0]]) + + +class Mughal_AF_RowStack(Mughal_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isAlternateForceSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isAlternateForceSequence([stackcards[-1], cards[0]]) + + +class Mughal_RK_RowStack(Mughal_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isRankSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isRankSequence([stackcards[-1], cards[0]]) + + +class Mughal_SS_RowStack(Mughal_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isSuitSequence(cards)): + return 0 + stackcards = self.cards + if not len(stackcards): + return cards[0].rank == 11 or self.cap.base_rank == ANY_RANK + return self.isSuitSequence([stackcards[-1], cards[0]]) + + +class Circles_RowStack(SS_RowStack): + + def __init__(self, x, y, game, base_rank, yoffset): + SS_RowStack.__init__(self, x, y, game, base_rank=base_rank, + max_accept=1, max_move=1) + self.CARD_YOFFSET = 1 + + +class Triumph_BraidStack(OpenStack): + + def __init__(self, x, y, game, xoffset, yoffset): + OpenStack.__init__(self, x, y, game) + CW = self.game.app.images.CARDW + self.CARD_YOFFSET = int(self.game.app.images.CARD_YOFFSET * yoffset) + # use a sine wave for the x offsets + self.CARD_XOFFSET = [] + j = 1 + for i in range(30): + self.CARD_XOFFSET.append(int(math.cos(j) * xoffset)) + j = j + .9 + + +class Triumph_StrongStack(ReserveStack): + + def fillStack(self): + if not self.cards: + if self.game.s.braidstrong.cards: + self.game.moveMove(1, self.game.s.braidstrong, self) + elif self.game.s.braidweak.cards: + self.game.moveMove(1, self.game.s.braidweak, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Triumph_WeakStack(ReserveStack): + + def fillStack(self): + if not self.cards: + if self.game.s.braidweak.cards: + self.game.moveMove(1, self.game.s.braidweak, self) + elif self.game.s.braidstrong.cards: + self.game.moveMove(1, self.game.s.braidstrong, self) + + def getBottomImage(self): + return self.game.app.images.getBraidBottom() + + +class Triumph_ReserveStack(ReserveStack): + + def acceptsCards(self, from_stack, cards): + if (from_stack is self.game.s.braidstrong or + from_stack is self.game.s.braidweak or + from_stack in self.game.s.rows): + return 0 + return ReserveStack.acceptsCards(self, from_stack, cards) + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + + +# /*********************************************************************** +# * +# ***********************************************************************/ + +class AbstractMughalGame(Game): + + SUITS = (_("Crown"), _("Silver"), _("Saber"), _("Servant"), + _("Harp"), _("Gold"), _("Document"), _("Stores")) + RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", "10", + _("Pradhan"), _("Raja")) + COLORS = (_("Brown"), _("Black"), _("Red"), _("Yellow"), + _("Green"), _("Grey"), _("Orange"), _("Tan")) + FORCE = (_("Strong"), _("Weak")) + + def updateText(self): + pass + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank + or card2.rank + 1 == card1.rank) + + +class Triumph_Hint(DefaultHint): + # FIXME: demo is not too clever in this game + pass + + + +# /*********************************************************************** +# * Mughal Circles +# ***********************************************************************/ + +class MughalCircles(AbstractMughalGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + w, h = l.XM + l.XS * 9, l.YM + l.YS * 7 + self.setSize(w, h) + + # Create row stacks + x = w / 2 - l.CW / 2 + y = h / 2 - l.YS / 2 + x0 = (-1, -.8, 0, .8, 1, .8, 0, -.8, + -2, -1.9, -1.5, -.8, 0, .8, 1.5, 1.9, 2, 1.9, 1.5, .8, 0, -.8, -1.5, -1.9) + y0 = (0, -.8, -1, -.8, 0, .8, 1, .8, + 0, -.8, -1.5, -1.9, -2, -1.9, -1.5, -.8, 0, .8, 1.5, 1.9, 2, 1.9, 1.5, .8) + for i in range(24): + # FIXME: + _x, _y = x+l.XS*x0[i]+l.XM*x0[i]*2, y+l.YS*y0[i]+l.YM*y0[i]*2 + if _x < 0: _x = 0 + if _y < 0: _y = 0 + s.rows.append(Circles_RowStack(_x, _y, self, base_rank=ANY_RANK, yoffset=0)) + + # Create reserve stacks + s.reserves.append(ReserveStack(l.XM, h - l.YS, self)) + s.reserves.append(ReserveStack(w - l.XS, h - l.YS, self)) + + # Create foundations + x = l.XM + y = l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i, mod = 12, + max_move = 0, max_cards = 12)) + y = y + l.YS + x = self.width - l.XS + y = l.YM + for i in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, i + 4, mod = 12, + max_move = 0, max_cards = 12)) + y = y + l.YS + # FIXME: + _x1, _x2 = l.XM + l.XS, w - l.XS - l.XM + for i in s.rows: + if i.x < _x1: i.x = _x1 + elif i.x > _x2: i.x = _x2 + self.setRegion(s.rows, (_x1, 0, _x2, 999999)) + + # Create talon + s.talon = InitialDealTalonStack(l.XM + l.XS, l.YM, self) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 96 + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.suit == card2.suit) + and ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank))) + + + +# /*********************************************************************** +# * Eight Legions +# ***********************************************************************/ + +class EightLegions(AbstractMughalGame): + + # + # Game layout + # + + def createGame(self): + l, s = Layout(self), self.s + font = self.app.getFont("canvas_default") + + # Set window size + self.setSize(l.XM * 3 + l.XS * 9, l.YM + l.YS * 6) + + # Create row stacks + x = l.XM + y = l.YM + for i in range(8): + s.rows.append(RK_RowStack(x, y, self, base_rank = 11, + max_move = 12, max_cards = 99)) + x = x + l.XS + + # Create reserve stacks + x = self.width - l.XS + y = l.YM + for i in range(6): + s.reserves.append(ReserveStack(x, y, self)) + y = y + l.YS + y = y - l.YS + for i in range(4): + x = x - l.XS + s.reserves.append(ReserveStack(x, y, self)) + + self.setRegion(s.rows, (0, 0, l.XM + l.XS * 8, l.YS * 5)) + + # Create talon + s.talon = DealRowTalonStack(l.XM, self.height - l.YS, self) + l.createText(s.talon, "nn") + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 96 + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealCards() + + def isGameWon(self): + if len(self.s.talon.cards): + return 0 + for s in self.s.rows: + if len(s.cards) != 12 or not isSameSuitSequence(s.cards): + return 0 + return 1 + + + +# /*********************************************************************** +# * Shamsher +# ***********************************************************************/ + +class Shamsher(AbstractMughalGame): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = RK_RowStack + BASE_RANK = ANY_RANK + + # + # Game layout + # + + def createGame(self, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=14, reserves=4, texts=0) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod=12, max_cards=12)) + + # Create reserve stacks + for r in l.s.reserves: + s.reserves.append(ReserveStack(r.x, r.y, self, )) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, max_cards=12, + suit=ANY_SUIT, base_rank=self.BASE_RANK)) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 96 + for i in range(6): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:12]) + self.s.talon.dealCards() + + + +# /*********************************************************************** +# * Ashrafi +# ***********************************************************************/ + +class Ashrafi(Shamsher): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = RK_RowStack + BASE_RANK = 11 + + # + # Game layout + # + + def createGame(self, **layout): + Shamsher.createGame(self) + + + +# /*********************************************************************** +# * Ghulam +# ***********************************************************************/ + +class Ghulam(Shamsher): + Layout_Method = Layout.ghulamLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = SS_RowStack + BASE_RANK = ANY_RANK + + # + # Game layout + # + + def createGame(self, **layout): + Shamsher.createGame(self) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.suit == card2.suit) + and ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank))) + + + +# /*********************************************************************** +# * Tipati +# ***********************************************************************/ + +class Tipati(AbstractMughalGame): + Layout_Method = Layout.klondikeLayout + Talon_Class = WasteTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = RK_RowStack + BASE_RANK = 11 + MAX_MOVE = 0 + + # + # Game layout + # + + def createGame(self, max_rounds=1, num_deal=1, **layout): + l, s = Layout(self), self.s + kwdefault(layout, rows=8, waste=1) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + + # Create talon + s.talon = self.Talon_Class(l.s.talon.x, l.s.talon.y, self, + max_rounds = max_rounds, num_deal = num_deal) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + + # Create foundations + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, + r.suit, mod = 12, max_cards = 12, max_move = self.MAX_MOVE)) + + # Create row stacks + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self, + suit = ANY_SUIT, base_rank = self.BASE_RANK)) + + # Define stack groups + l.defaultAll() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == 96 + for i in range(8): + self.s.talon.dealRow(rows=self.s.rows[i+1:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + + +# /*********************************************************************** +# * Ashwapati +# ***********************************************************************/ + +class Ashwapati(Tipati): + RowStack_Class = SS_RowStack + BASE_RANK = ANY_RANK + MAX_MOVE = 1 + + # + # Game layout + # + + def createGame(self, **layout): + Tipati.createGame(self, max_rounds = -1, num_deal = 1) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.suit == card2.suit) + and ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank))) + + + +# /*********************************************************************** +# * Gajapati +# ***********************************************************************/ + +class Gajapati(Tipati): + RowStack_Class = SS_RowStack + BASE_RANK = ANY_RANK + MAX_MOVE = 1 + + # + # Game layout + # + + def createGame(self, **layout): + Tipati.createGame(self, max_rounds=-1, num_deal=3) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return ((card1.suit == card2.suit) + and ((card1.rank + 1 == card2.rank) + or (card1.rank - 1 == card2.rank))) + + + +# /*********************************************************************** +# * Narpati +# ***********************************************************************/ + +class Narpati(Tipati): + RowStack_Class = AC_RowStack + MAX_MOVE = 1 + + # + # Game layout + # + + def createGame(self, **layout): + Tipati.createGame(self, max_rounds=1, num_deal=1) + + + +# /*********************************************************************** +# * Garhpati +# ***********************************************************************/ + +class Garhpati(Tipati): + RowStack_Class = AC_RowStack + + # + # Game layout + # + + def createGame(self, **layout): + Tipati.createGame(self, max_rounds=-1, num_deal=3) + + + +# /*********************************************************************** +# * Dhanpati +# ***********************************************************************/ + +class Dhanpati(Tipati): + + # + # Game layout + # + + def createGame(self, **layout): + Tipati.createGame(self, max_rounds=2, num_deal=3) + + + +# /*********************************************************************** +# // Akbar's Triumph +# ************************************************************************/ + +class AkbarsTriumph(AbstractMughalGame): + Hint_Class = Triumph_Hint + + BRAID_CARDS = 12 + BRAID_OFFSET = 1.1 + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (piles up to 20 cards are playable - needed for Braid_BraidStack) + decks = self.gameinfo.decks + h = max(5 * l.YS + 35, l.YS + (self.BRAID_CARDS - 1) * l.YOFFSET) + self.setSize(l.XM + l.XS * (7 + decks * 2), l.YM + h) + + # extra settings + self.base_card = None + + # Create foundations, rows, reserves + s.addattr(braidstrong = None) # register extra stack variable + s.addattr(braidweak = None) # register extra stack variable + x, y = l.XM, l.YM + for j in range(4): + for i in range(decks): + s.foundations.append(Triumph_Foundation(x + l.XS * i, y, self, + j, mod = 12, max_cards = 12)) + s.rows.append(Triumph_StrongStack(x + l.XS * decks, y, self)) + s.rows.append(Triumph_ReserveStack(x + l.XS * (1 + decks), y, self)) + y = y + l.YS + x, y = x + l.XS * (5 + decks), l.YM + for j in range(4): + s.rows.append(Triumph_ReserveStack(x, y, self)) + s.rows.append(Triumph_WeakStack(x + l.XS, y, self)) + for i in range(decks, 0, -1): + s.foundations.append(Triumph_Foundation(x + l.XS * (1 + i), y, self, + j + 4, mod = 12, max_cards = 12)) + y = y + l.YS + self.texts.info = MfxCanvasText(self.canvas, + self.width / 2, h - l.YM / 2, + anchor = "center", + font = self.app.getFont("canvas_default")) + + # Create braids + x, y = l.XM + l.XS * 2.3 + l.XS * decks, l.YM + s.braidstrong = Triumph_BraidStack(x, y, self, xoffset = 12, yoffset = self.BRAID_OFFSET) + x = x + l.XS * 1.4 + s.braidweak = Triumph_BraidStack(x, y, self, xoffset = -12, yoffset = self.BRAID_OFFSET) + + # Create talon + x, y = l.XM + l.XS * 2 + l.XS * decks, h - l.YS - l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds = 3) + l.createText(s.talon, "ss") + s.talon.texts.rounds = MfxCanvasText(self.canvas, + self.width / 2, h - l.YM * 2.5, + anchor = "center", + font=self.app.getFont("canvas_default")) + x = x + l.XS * 2 + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + + # define stack-groups + self.sg.talonstacks = [s.talon] + [s.waste] + self.sg.openstacks = s.foundations + s.rows + self.sg.dropstacks = [s.braidstrong] + [s.braidweak] + s.rows + [s.waste] + + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.base_card = None + self.updateText() + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows = [self.s.braidstrong]) + for i in range(self.BRAID_CARDS): + self.s.talon.dealRow(rows = [self.s.braidweak]) + self.s.talon.dealRow() + # deal base_card to foundations, update cap.base_rank + self.base_card = self.s.talon.getCard() + to_stack = self.s.foundations[self.base_card.suit * self.gameinfo.decks] + self.flipMove(self.s.talon) + self.moveMove(1, self.s.talon, to_stack) + self.updateText() + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + # deal first card to WasteStack + self.s.talon.dealCards() + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + ((card1.rank + 1) % 12 == card2.rank or (card2.rank + 1) % 12 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + def _restoreGameHook(self, game): + self.base_card = self.cards[game.loadinfo.base_card_id] + for s in self.s.foundations: + s.cap.base_rank = self.base_card.rank + + def _loadGameHook(self, p): + self.loadinfo.addattr(base_card_id=None) # register extra load var. + self.loadinfo.base_card_id = p.load() + + def _saveGameHook(self, p): + p.dump(self.base_card.id) + + + # + # game extras + # + + def updateText(self): + if self.preview > 1 or not self.texts.info: + return + if not self.base_card: + t = "" + else: + t = self.RANKS[self.base_card.rank] + dir = self.getFoundationDir() % 12 + if dir == 1: + t = t + _(" Ascending") + elif dir == 11: + t = t + _(" Descending") + self.texts.info.config(text = t) + + + +# /*********************************************************************** +# // Akbar's Conquest +# ************************************************************************/ + +class AkbarsConquest(AkbarsTriumph): + + BRAID_CARDS = 16 + BRAID_OFFSET = .9 + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Vajra(AbstractMughalGame): + RowStack_Class = StackWrapper(Mughal_RK_RowStack, base_rank=NO_RANK) + + # + # game layout + # + + def createGame(self, rows=9, reserves=8): + # create layout + l, s = Layout(self), self.s + + # set size + maxrows = max(rows, reserves) + self.setSize(l.XM + (maxrows + 2) * l.XS, l.YM + 6 * l.YS) + + # + playcards = 4 * l.YS / l.YOFFSET + xoffset, yoffset = [], [] + for i in range(playcards): + xoffset.append(0) + yoffset.append(l.YOFFSET) + for i in range(96 * self.gameinfo.decks - playcards): + xoffset.append(l.XOFFSET) + yoffset.append(0) + + # create stacks + x, y = l.XM + (maxrows - reserves) * l.XS / 2, l.YM + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x, y = l.XM + (maxrows - rows) * l.XS / 2, l.YM + l.YS + self.setRegion(s.reserves, (-999, -999, 999999, y - l.YM / 2)) + for i in range(rows): + stack = self.RowStack_Class(x, y, self, yoffset=l.YOFFSET) + stack.CARD_XOFFSET = xoffset + stack.CARD_YOFFSET = yoffset + s.rows.append(stack) + x = x + l.XS + x, y = l.XM + maxrows * l.XS, l.YM + for i in range(2): + for suit in range(4): + s.foundations.append(SS_FoundationStack(x, y, self, suit=suit + (4 * i))) + y = y + l.YS + x, y = x + l.XS, l.YM + self.setRegion(self.s.foundations, (x - l.XS * 2, -999, 999999, + self.height - (l.YS + l.YM)), priority=1) + s.talon = InitialDealTalonStack(self.width - 3 * l.XS / 2, self.height - l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + if self.s.talon.cards[-1].rank == 11: + if self.s.rows[i].cards: + i = i + 1 + self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4) + + # must look at cards + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + for stack in stacks: + if stack.cards and stack is not dragstack: + dist = (stack.cards[-1].x - cx)**2 + (stack.cards[-1].y - cy)**2 + else: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + closest, cdist = stack, dist + return closest + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isRankSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Danda(Vajra): + RowStack_Class = StackWrapper(Mughal_AF_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isAlternateForceSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Khadga(Vajra): + RowStack_Class = StackWrapper(Mughal_AC_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isAlternateColorSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Makara(Vajra): + RowStack_Class = StackWrapper(Mughal_SS_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isSuitSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + + + +# /*********************************************************************** +# // Ashta Dikapala Game Stacks +# ************************************************************************/ + +class Dikapala_TableauStack(Mughal_OpenStack): + + def __init__(self, x, y, game, base_rank, yoffset, **cap): + kwdefault(cap, dir=3, max_move=99, max_cards=4, max_accept=1, base_rank=base_rank) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = yoffset + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + # check that the base card is correct + if self.cards and self.cards[0].rank != self.cap.base_rank: + return 0 + if not self.cards: + return cards[0].rank == self.cap.base_rank + return (self.cards[-1].suit == cards[0].suit and + self.cards[-1].rank + self.cap.dir == cards[0].rank) + + def getBottomImage(self): + return self.game.app.images.getLetter(self.cap.base_rank) + + +class Dikapala_ReserveStack(ReserveStack): + + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_cards=1, max_accept=1, base_rank=ANY_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def acceptsCards(self, from_stack, cards): + return (ReserveStack.acceptsCards(self, from_stack, cards) + and self.game.s.talon.cards) + + +class Dikapala_RowStack(BasicRowStack): + + def acceptsCards(self, from_stack, cards): + if not BasicRowStack.acceptsCards(self, from_stack, cards): + return 0 + # check + return not (self.cards or self.game.s.talon.cards) + + def canMoveCards(self, cards): + return 1 + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + +# /*********************************************************************** +# // Dikapala Hint +# ************************************************************************/ + +class Dikapala_Hint(AbstractHint): + def computeHints(self): + game = self.game + + # 2)See if we can move a card to the tableaux + if not self.hints: + for r in game.sg.dropstacks: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + if r in game.s.tableaux: + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + base_score = 80000 + (4 - r.cap.base_suit) + else: + base_score = 80000 + # find a stack that would accept this card + for t in game.s.tableaux: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 3)See if we can move a card from the tableaux + # to a row stack. This can only happen if there are + # no more cards to deal. + if not self.hints: + for r in game.s.tableaux: + pile = r.getPile() + if not pile or len(pile) != 1: + continue + rr = self.ClonedStack(r, stackcards=r.cards[:-1]) + if rr.acceptsCards(None, pile): + # do not move a card that is already in correct place + continue + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = 70000 + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 4)See if we can move a card within the row stacks + if not self.hints: + for r in game.s.rows: + pile = r.getPile() + if not pile or len(pile) != 1 or len(pile) == len(r.cards): + continue + base_score = 60000 + # find a stack that would accept this card + for t in game.s.rows: + if t is not r and t.acceptsCards(r, pile): + score = base_score + 100 * (self.K - pile[0].rank) + self.addHint(score, 1, r, t) + break + + # 5)See if we can deal cards + if self.level >= 2: + if game.canDealCards(): + self.addHint(self.SCORE_DEAL, 0, game.s.talon, None) + + +# /*********************************************************************** +# // Ashta Dikapala +# ************************************************************************/ + +class AshtaDikapala(Game): + Hint_Class = Dikapala_Hint + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + TABLEAU_YOFFSET = min(9, max(3, l.YOFFSET / 3)) + + # set window + th = l.YS + 3 * TABLEAU_YOFFSET + # (set piles so that at least 2/3 of a card is visible with 10 cards) + h = 8 * l.YOFFSET + l.CH * 2/3 + self.setSize(9 * l.XS + l.XM * 2, l.YM + 3 * th + l.YM + h) + + # create stacks + s.addattr(tableaux=[]) # register extra stack variable + x = l.XM + 8 * l.XS + l.XS / 2 + y = l.YM + for i in range(3, 0, -1): + x = l.XM + for j in range(8): + s.tableaux.append(Dikapala_TableauStack(x, y, self, i - 1, TABLEAU_YOFFSET)) + x = x + l.XS + x = x + l.XM + s.reserves.append(Dikapala_ReserveStack(x, y, self)) + y = y + th + x, y = l.XM, y + l.YM + for i in range(8): + s.rows.append(Dikapala_RowStack(x, y, self, max_accept=1)) + x = x + l.XS + x = self.width - l.XS + y = self.height - l.YS + s.talon = DealRowTalonStack(x, y, self) + l.createText(s.talon, "sw") + + # define stack-groups + self.sg.openstacks = s.tableaux + s.rows + s.reserves + self.sg.talonstacks = [s.talon] + self.sg.dropstacks = s.tableaux + s.rows + + # + # game overrides + # + + def startGame(self): + self.s.talon.dealRow(rows=self.s.tableaux, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + def isGameWon(self): + for stack in self.s.tableaux: + if len(stack.cards) != 4: + return 0 + return 1 + + def fillStack(self, stack): + if self.s.talon.cards: + if stack in self.s.rows and len(stack.cards) == 0: + self.s.talon.dealRow(rows=[stack]) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 3 == card2.rank or card2.rank + 3 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_MUGHAL_GANJIFA + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + suits=range(8), ranks=range(12)) + registerGame(gi) + return gi + +r(14401, MughalCircles, 'Mughal Circles', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14402, Ghulam, 'Ghulam', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14403, Shamsher, 'Shamsher', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14404, EightLegions, 'Eight Legions', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14405, Ashrafi, 'Ashrafi', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14406, Tipati, 'Tipati', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14407, Ashwapati, 'Ashwapati', GI.GT_MUGHAL_GANJIFA, 1, -1) +r(14408, Gajapati, 'Gajapati', GI.GT_MUGHAL_GANJIFA, 1, -1) +r(14409, Narpati, 'Narpati', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(14410, Garhpati, 'Garhpati', GI.GT_MUGHAL_GANJIFA, 1, -1) +r(14411, Dhanpati, 'Dhanpati', GI.GT_MUGHAL_GANJIFA, 1, 1) +r(14412, AkbarsTriumph, 'Akbar\'s Triumph', GI.GT_MUGHAL_GANJIFA, 1, 2) +r(14413, AkbarsConquest, 'Akbar\'s Conquest', GI.GT_MUGHAL_GANJIFA, 2, 2) +r(16000, Vajra, 'Vajra', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(16001, Danda, 'Danda', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(16002, Khadga, 'Khadga', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(16003, Makara, 'Makara', GI.GT_MUGHAL_GANJIFA, 1, 0) +r(16004, AshtaDikapala, 'Ashta Dikapala', GI.GT_MUGHAL_GANJIFA, 1, 0) + +del r diff --git a/pysollib/games/ultra/tarock.py b/pysollib/games/ultra/tarock.py new file mode 100644 index 0000000000..331fbdd80f --- /dev/null +++ b/pysollib/games/ultra/tarock.py @@ -0,0 +1,274 @@ +##---------------------------------------------------------------------------## +## +## Ultrasol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys + +# Ultrasol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +#from pysollib.pysoltk import MfxCanvasText + +from pysollib.games.special.tarock import AbstractTarockGame, Grasshopper + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Tarock_OpenStack(OpenStack): + + def __init__(self, x, y, game, yoffset=-1, **cap): + kwdefault(cap, max_move=UNLIMITED_MOVES, max_accept=UNLIMITED_ACCEPTS, dir=-1) + apply(OpenStack.__init__, (self, x, y, game), cap) + if yoffset < 0: + yoffset = game.app.images.CARD_YOFFSET + self.CARD_YOFFSET = yoffset + + def isRankSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not c1.rank + dir == c2.rank: + return 0 + c1 = c2 + return 1 + + def isAlternateColorSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if (c1.color < 2 and c1.color == c2.color + or not c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isSuitSequence(self, cards, dir=None): + if not dir: + dir = self.cap.dir + c1 = cards[0] + for c2 in cards[1:]: + if not (c1.suit == c2.suit + and c1.rank + dir == c2.rank): + return 0 + c1 = c2 + return 1 + + def isHighRankCard(self, card): + maxcard = ([self.game.gameinfo.ranks[-1], self.game.gameinfo.trumps[-1]] + [(card.suit == len(self.game.gameinfo.suits))]) + return card.rank == maxcard or self.cap.base_rank == ANY_RANK + +class Tarock_RK_RowStack(Tarock_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isRankSequence(cards)): + return 0 + if not self.cards: + return self.isHighRankCard(cards[0]) + return self.isRankSequence([self.cards[-1], cards[0]]) + + def canMoveCards(self, cards): + return (self.basicCanMoveCards(cards) + and self.isRankSequence(cards)) + +class Tarock_SS_RowStack(Tarock_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isSuitSequence(cards)): + return 0 + if not self.cards: + return self.isHighRankCard(cards[0]) + return self.isSuitSequence([self.cards[-1], cards[0]]) + + def canMoveCards(self, cards): + return (self.basicCanMoveCards(cards) + and self.isSuitSequence(cards)) + +class Tarock_AC_RowStack(Tarock_OpenStack): + + def acceptsCards(self, from_stack, cards): + if (not self.basicAcceptsCards(from_stack, cards) + or not self.isAlternateColorSequence(cards)): + return 0 + if not self.cards: + return self.isHighRankCard(cards[0]) + return self.isAlternateColorSequence([self.cards[-1], cards[0]]) + + def canMoveCards(self, cards): + return (self.basicCanMoveCards(cards) + and self.isAlternateColorSequence(cards)) + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Cockroach(Grasshopper): + MAX_ROUNDS = 1 + +class DoubleCockroach(Grasshopper): + MAX_ROUNDS = 1 + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Corkscrew(AbstractTarockGame): + RowStack_Class = StackWrapper(Tarock_RK_RowStack, base_rank=NO_RANK) + + # + # game layout + # + + def createGame(self, rows=11, reserves=10): + # create layout + l, s = Layout(self), self.s + + # set size + maxrows = max(rows, reserves) + self.setSize(l.XM + (maxrows + 2) * l.XS, l.YM + 6 * l.YS) + + # + playcards = 4 * l.YS / l.YOFFSET + xoffset, yoffset = [], [] + for i in range(playcards): + xoffset.append(0) + yoffset.append(l.YOFFSET) + for i in range(78 * self.gameinfo.decks - playcards): + xoffset.append(l.XOFFSET) + yoffset.append(0) + + # create stacks + x, y = l.XM + (maxrows - reserves) * l.XS / 2, l.YM + for i in range(reserves): + s.reserves.append(ReserveStack(x, y, self)) + x = x + l.XS + x, y = l.XM + (maxrows - rows) * l.XS / 2, l.YM + l.YS + self.setRegion(s.reserves, (-999, -999, 999999, y - l.YM / 2)) + for i in range(rows): + stack = self.RowStack_Class(x, y, self, yoffset=l.YOFFSET) + stack.CARD_XOFFSET = xoffset + stack.CARD_YOFFSET = yoffset + s.rows.append(stack) + x = x + l.XS + x, y = l.XM + maxrows * l.XS, l.YM + for i in range(2): + for suit in range(5): + s.foundations.append(SS_FoundationStack(x, y, self, suit=suit, + max_cards=14 + 8 * (suit == 4))) + y = y + l.YS + x, y = x + l.XS, l.YM + self.setRegion(self.s.foundations, (x - l.XS * 2, -999, 999999, + self.height - (l.YS + l.YM)), priority=1) + s.talon = InitialDealTalonStack(self.width - 3 * l.XS / 2, self.height - l.YS, self) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + i = 0 + while self.s.talon.cards: + card = self.s.talon.cards[-1] + if card.rank == 13 + 8 * (card.suit == 4): + if self.s.rows[i].cards: + i = i + 1 + self.s.talon.dealRow(rows=[self.s.rows[i]], frames=4) + + # must look at cards + def _getClosestStack(self, cx, cy, stacks, dragstack): + closest, cdist = None, 999999999 + for stack in stacks: + if stack.cards and stack is not dragstack: + dist = (stack.cards[-1].x - cx)**2 + (stack.cards[-1].y - cy)**2 + else: + dist = (stack.x - cx)**2 + (stack.y - cy)**2 + if dist < cdist: + closest, cdist = stack, dist + return closest + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isRankSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Serpent(Corkscrew): + RowStack_Class = StackWrapper(Tarock_AC_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isAlternateColorSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Rambling(Corkscrew): + RowStack_Class = StackWrapper(Tarock_SS_RowStack, base_rank=NO_RANK) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + row = self.s.rows[0] + sequence = row.isSuitSequence + return (sequence([card1, card2]) or sequence([card2, card1])) + +# /*********************************************************************** +# // register the games +# ************************************************************************/ + +def r(id, gameclass, name, game_type, decks, redeals): + game_type = game_type | GI.GT_TAROCK | GI.GT_CONTRIB | GI.GT_ORIGINAL + gi = GameInfo(id, gameclass, name, game_type, decks, redeals, + ranks=range(14), trumps=range(22)) + registerGame(gi) + return gi + +r(13163, Cockroach, 'Cockroach', GI.GT_TAROCK, 1, 0) +r(13164, DoubleCockroach, 'Double Cockroach', GI.GT_TAROCK, 2, 0) +r(13165, Corkscrew, 'Corkscrew', GI.GT_TAROCK, 2, 0) +r(13166, Serpent, 'Serpent', GI.GT_TAROCK, 2, 0) +r(13167, Rambling, 'Rambling', GI.GT_TAROCK, 2, 0) + +del r diff --git a/pysollib/games/ultra/threepeaks.py b/pysollib/games/ultra/threepeaks.py new file mode 100644 index 0000000000..0600a54e54 --- /dev/null +++ b/pysollib/games/ultra/threepeaks.py @@ -0,0 +1,300 @@ +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 by T. Kirk +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# Imports +import sys, math + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.pysoltk import MfxCanvasText, MfxCanvasImage, bind, ANCHOR_NW + +from pysollib.games.golf import Golf_Waste, Golf_Hint + + +# /*********************************************************************** +# // Three Peaks Row Stack +# ************************************************************************/ + +class ThreePeaks_TalonStack(WasteTalonStack): + + def dealCards(self, sound=0): + game = self.game + game.sequence = 0 + old_state = game.enterState(game.S_DEAL) + num_cards = 0 + waste = self.waste + if self.cards: + if sound and not self.game.demo: + self.game.playSample("dealwaste") + num_cards = min(len(self.cards), self.num_deal) + assert len(waste.cards) + num_cards <= waste.cap.max_cards + for i in range(num_cards): + if not self.cards[-1].face_up: + game.flipMove(self) + game.moveMove(1, self, waste, frames=4, shadow=0) + self.fillStack() + elif waste.cards and self.round != self.max_rounds: + if sound: + self.game.playSample("turnwaste", priority=20) + num_cards = len(waste.cards) + game.turnStackMove(waste, self, update_flags=1) + game.leaveState(old_state) + return num_cards + + +class ThreePeaks_RowStack(OpenStack): + + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=0, max_accept=0, max_cards=1, + base_rank=ANY_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def basicIsBlocked(self): + r, step = self.game.s.rows, (3, 4, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9) + i = self.id + while i < 18: + i = i + step[i] + for j in range(2): + if r[i + j].cards: + return 1 + return 0 + + def clickHandler(self, event): + if self.basicIsBlocked(): + # remove selection + self._stopDrag() + return 1 + game = self.game + #print self.cards, game.s.waste.cards + card, waste = self.cards[0], game.s.waste.cards[-1] + mod, ranks, trumps = [], len(game.gameinfo.ranks), len(game.gameinfo.trumps) + mod.append([ranks, trumps][(card.suit == len(game.gameinfo.suits))]) + mod.append([ranks, trumps][(waste.suit == len(game.gameinfo.suits))]) + if ((card.rank + 1) % mod[0] == waste.rank + or (card.rank - 1) % mod[1] == waste.rank + or (waste.rank + 1) % mod[1] == card.rank + or (waste.rank - 1) % mod[0] == card.rank): + game.sequence = game.sequence + 1 + self._stopDrag() + self.game.playSample("autodrop", priority=20) + self.playMoveMove(1, game.s.waste, frames=-1, sound=0) + game.updateText() + return 1 + + +# /*********************************************************************** +# // Three Peaks Game +# ************************************************************************/ + +class ThreePeaks(Game): + + Waste_Class = StackWrapper(Golf_Waste, mod=13) + Hint_Class = Golf_Hint + + SCORING = 1 + + # + # Game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + # (compute best XOFFSET - up to 64/72 cards can be in the Waste) + decks = self.gameinfo.decks + l.XOFFSET = int(l.XS * 9 / self.gameinfo.ncards) + + # Set window size + w, h = l.XM + l.XS * 10, l.YM + l.YS * 4 + self.setSize(w, h) + + # Extra settings + self.game_score = 52 + self.hand_score = self.sequence = 0 + self.peaks = [0] * 3 + + # Create rows + x, y = l.XM + l.XS * 1.5, l.YM + for i in range(3): + s.rows.append(ThreePeaks_RowStack(x, y, self)) + x = x + l.XS * 3 + x, y = l.XM + l.XS, y + l.YS * .5 + for i in range(3): + s.rows.append(ThreePeaks_RowStack(x, y, self)) + x = x + l.XS + s.rows.append(ThreePeaks_RowStack(x, y, self)) + x = x + l.XS * 2 + x, y = l.XM + l.XS * .5, y + l.YS * .5 + for i in range(9): + s.rows.append(ThreePeaks_RowStack(x, y, self)) + x = x + l.XS + x, y = l.XM, y + l.YS * .5 + for i in range(10): + s.rows.append(ThreePeaks_RowStack(x, y, self)) + x = x + l.XS + + # Create talon + x, y = l.XM, y + l.YM + l.YS + s.talon = ThreePeaks_TalonStack(x, y, self, num_deal=1, max_rounds=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = self.Waste_Class(x, y, self) + s.waste.CARD_XOFFSET = l.XOFFSET + s.foundations.append(s.waste) + l.createText(s.waste, "ss") + + # Create text for scores + if self.preview <= 1: + self.texts.info = MfxCanvasText(self.canvas, + l.XM + l.XS * 3, h - l.YM, + anchor="sw", + font=self.app.getFont("canvas_default")) + + # Define stack groups + l.defaultStackGroups() + + # + # Game over rides + # + + def startGame(self): + assert len(self.s.talon.cards) == self.gameinfo.ncards + self.game_score = self.game_score + self.hand_score - 52 + self.hand_score = -52 + self.peaks = [0] * 3 + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:18], flip=0, frames=4) + self.s.talon.dealRow(rows=self.s.rows[18:], flip=1, frames=4) + self.s.talon.dealCards() + + def isGameWon(self): + for r in self.s.rows: + if r.cards: + return 0 + if self.sequence: + self.hand_score = self.hand_score + len(self.s.talon.cards) * 10 + self.updateText() + self.sequence = 0 + return 1 + + def updateText(self): + if self.preview > 1 or not self.texts.info or not self.SCORING: + return + t = _('Score:\011This hand: ') + str(self.getHandScore()) + t = t + _('\011This game: ') + str(self.game_score) + self.texts.info.config(text=t) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + if stack1 == self.s.waste or stack2 == self.s.waste: + return ((card1.rank + 1) % 13 == card2.rank + or (card1.rank - 1) % 13 == card2.rank) + return 0 + + def getHandScore(self): + score, i = self.hand_score, 1 + if self.busy: + return score + # First count the empty peaks + for r in self.s.rows[:3]: + if not r.cards: + i = i * 2 + # Now give credit for newly emptied peaks + for r in self.s.rows[:3]: + if not r.cards and not self.peaks[r.id]: + score, self.peaks[r.id] = score + 5 * i, 1 + # Now give credit for the sequence length + if self.sequence and len(self.s.waste.cards) - 1: + score = score + i * 2 ** int((self.sequence - 1) / 4) + self.hand_score = score + return score + + def canUndo(self): + return 0 + + def _restoreGameHook(self, game): + self.game_score = game.loadinfo.game_score + self.hand_score = game.loadinfo.hand_score + self.sequence = game.loadinfo.sequence + self.peaks = game.loadinfo.peaks + + def _loadGameHook(self, p): + self.loadinfo.addattr(game_score=0) + self.loadinfo.game_score = p.load() + self.loadinfo.addattr(hand_score=0) + self.loadinfo.hand_score = p.load() + self.loadinfo.addattr(sequence=0) + self.loadinfo.sequence = p.load() + self.loadinfo.addattr(peaks=[0]*3) + self.loadinfo.peaks = p.load() + + def _saveGameHook(self, p): + p.dump(self.game_score) + p.dump(self.hand_score) + p.dump(self.sequence) + p.dump(self.peaks) + + +# /*********************************************************************** +# // Three Peaks Game Non-scoring +# ************************************************************************/ + +class ThreePeaksNoScore(ThreePeaks): + SCORING = 0 + + def canUndo(self): + return 1 + + +# /*********************************************************************** +# // Three Peaks Game Non-scoring +# ************************************************************************/ + +class LeGrandeTeton(ThreePeaks): + SCORING = 0 + + def canUndo(self): + return 1 + + +registerGame(GameInfo(22216, ThreePeaks, "Three Peaks", + GI.GT_PAIRING_TYPE, 1, 0)) +registerGame(GameInfo(22231, ThreePeaksNoScore, "Three Peaks Non-scoring", + GI.GT_PAIRING_TYPE, 1, 0)) +registerGame(GameInfo(22232, LeGrandeTeton, "Le Grande Teton", + GI.GT_TAROCK, 1, 0, ranks=range(14), trumps=range(22))) + + diff --git a/pysollib/games/unionsquare.py b/pysollib/games/unionsquare.py new file mode 100644 index 0000000000..3fed5d1efc --- /dev/null +++ b/pysollib/games/unionsquare.py @@ -0,0 +1,183 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class UnionSquare_Foundation(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + # check the rank + if len(self.cards) > 12: + return cards[0].rank == 25 - len(self.cards) + else: + return cards[0].rank == len(self.cards) + + +class UnionSquare_RowStack(OpenStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, mod=8192, dir=0, base_rank=ANY_RANK, + max_accept=1, max_move=1) + apply(OpenStack.__init__, (self, x, y, game), cap) + #self.CARD_YOFFSET = 1 + + def acceptsCards(self, from_stack, cards): + if not OpenStack.acceptsCards(self, from_stack, cards): + return 0 + if not self.cards: + return 1 + if cards[0].suit != self.cards[0].suit: + return 0 + if len(self.cards) == 1: + card_dir = cards[0].rank - self.cards[-1].rank + return card_dir == 1 or card_dir == -1 + else: + stack_dir = (self.cards[1].rank - self.cards[0].rank) % self.cap.mod + return (self.cards[-1].rank + stack_dir) % self.cap.mod == cards[0].rank + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class UnionSquare(Game): + Hint_Class = CautiousDefaultHint + RowStack_Class = UnionSquare_RowStack + + # + # game layout + # + + def createGame(self, rows=16): + # create layout + l, s = Layout(self, YM=18), self.s + + # set window + self.setSize(l.XM + (5+rows/4)*l.XS, l.YM + 4*l.YS) + + # create stacks + x, y, = l.XM, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "s") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "s") + for i in range(4): + x = 3*l.XS + for j in range(rows/4): + stack = self.RowStack_Class(x, y, self) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 1 + s.rows.append(stack) + x = x + l.XS + y = y + l.YS + x, y = self.width-l.XS, l.YM + for i in range(4): + stack = UnionSquare_Foundation(x, y, self, i, max_move=0, + dir=0, max_cards=26) + l.createText(stack, "sw") + s.foundations.append(stack) + y = y + l.YS + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + def getHighlightPilesStacks(self): + return () + + +# /*********************************************************************** +# // Solid Square +# ************************************************************************/ + +class SolidSquare(UnionSquare): + RowStack_Class = StackWrapper(UD_SS_RowStack, base_rank=NO_RANK, + max_accept=1, max_move=1, mod=13) + def createGame(self): + UnionSquare.createGame(self, rows=20) + + def _shuffleHook(self, cards): + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE and c.deck == 0, c.suit)) + + def startGame(self): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + UnionSquare.startGame(self) + + def fillStack(self, stack): + if stack in self.s.rows and not stack.cards: + old_state = self.enterState(self.S_FILL) + if not self.s.waste.cards: + self.s.talon.dealCards() + if self.s.waste.cards: + self.s.waste.moveMove(1, stack) + self.leaveState(old_state) + + +# register the game +registerGame(GameInfo(35, UnionSquare, "Union Square", + GI.GT_2DECK_TYPE, 2, 0, + altnames=('British Square',), + )) +registerGame(GameInfo(439, SolidSquare, "Solid Square", + GI.GT_2DECK_TYPE, 2, 0)) diff --git a/pysollib/games/wavemotion.py b/pysollib/games/wavemotion.py new file mode 100644 index 0000000000..74b1b5efa8 --- /dev/null +++ b/pysollib/games/wavemotion.py @@ -0,0 +1,100 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // Wave Motion +# ************************************************************************/ + +class WaveMotion_RowStack(SS_RowStack): + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + +class WaveMotion(Game): + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self), self.s + + # set window + w, h = l.XM+8*l.XS, l.YM+2*l.YS+19*l.YOFFSET + self.setSize(w, h) + + # create stacks + x, y = l.XM, l.YM + for i in range(8): + stack = WaveMotion_RowStack(x, y, self, base_rank=ANY_RANK) + s.rows.append(stack) + x += l.XS + x, y = l.XM, l.YM+l.YS+12*l.YOFFSET + for i in range(8): + stack = OpenStack(x, y, self, max_accept=0) + s.reserves.append(stack) + stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, l.YOFFSET + x += l.XS + + s.talon = InitialDealTalonStack(l.XM, l.YM, self) + + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + for i in range(5): + self.s.talon.dealRow(rows=self.s.reserves, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.reserves) + self.s.talon.dealRow(rows=self.s.reserves[:4]) + + def isGameWon(self): + for s in self.s.rows: + if s.cards: + if len(s.cards) != 13 or not isSameSuitSequence(s.cards): + return False + return True + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 + + +# register the game +registerGame(GameInfo(314, WaveMotion, "Wave Motion", + GI.GT_1DECK_TYPE, 1, 0)) diff --git a/pysollib/games/windmill.py b/pysollib/games/windmill.py new file mode 100644 index 0000000000..fc9e62a70d --- /dev/null +++ b/pysollib/games/windmill.py @@ -0,0 +1,270 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Windmill_Foundation(RK_FoundationStack): + def getBottomImage(self): + if self.cap.base_rank == ACE: + return self.game.app.images.getLetter(ACE) + return RK_FoundationStack.getBottomImage(self) + + +class Windmill_RowStack(ReserveStack): + def acceptsCards(self, from_stack, cards): + if not ReserveStack.acceptsCards(self, from_stack, cards): + return 0 + # this stack accepts one card from the Waste pile + return from_stack is self.game.s.waste + + +# /*********************************************************************** +# // Windmill +# ************************************************************************/ + +class Windmill(Game): + + FILL_STACK = True + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self, XM=20), self.s + + # set window + self.setSize(7*l.XS+l.XM, 5*l.YS+l.YM+l.YM) + + # create stacks + x = l.XM + y = l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + x0, y0 = x + l.XS, y + for d in ((2,0), (2,1), (0,2), (1,2), (3,2), (4,2), (2,3), (2,4)): + x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS + s.rows.append(Windmill_RowStack(x, y, self)) + x, y = x0 + 2 * l.XS, y0 + 2 * l.YS + s.foundations.append(Windmill_Foundation(x, y, self, + mod=13, min_cards=1, max_cards=52)) + for d in ((1,0.6), (3,0.6), (1,3.4), (3,3.4)): + x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS + s.foundations.append(Windmill_Foundation(x, y, self, + base_rank=KING, dir=-1)) + + # define stack-groups + l.defaultStackGroups() + + # + # game overrides + # + + def _shuffleHook(self, cards): + for c in cards: + if c.id == 0: + break + cards.remove(c) + return cards + [c] + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=(self.s.foundations[0],)) + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def fillStack(self, stack): + if self.FILL_STACK and len(stack.cards) == 0: + if stack is self.s.waste and self.s.talon.cards: + self.s.talon.dealCards() + elif stack in self.s.rows and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank) + + def getHighlightPilesStacks(self): + return () + + def getAutoStacks(self, event=None): + # disable auto drop - this would ruin the whole gameplay + return ((), (), ()) + + + +# /*********************************************************************** +# // Napoleon's Tomb +# ************************************************************************/ + +class NapoleonsTomb(Windmill): + + FILL_STACK = False + + # + # game layout + # + + def createGame(self): + # create layout + l, s = Layout(self, XM=20, YM=20), self.s + + # set window + self.setSize(5*l.XS+l.XM, 3*l.YS+l.YM+l.YM) + + # create stacks + x = l.XM + y = l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=1) + l.createText(s.talon, "ss") + x = x + l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "ss") + x0, y0 = x + l.XS, y + for d in ((0,1), (1,0), (1,2), (2,1)): + x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS + s.rows.append(Windmill_RowStack(x, y, self)) + x, y = x0 + l.XS, y0 + l.YS + s.foundations.append(Windmill_Foundation(x, y, self, base_rank=5, + mod=6, min_cards=1, max_cards=24, + max_move=0, dir=-1)) + for d in ((0.1, 0.1), (1.9, 0.1), (0.1, 1.9), (1.9, 1.9)): + x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS + s.foundations.append(Windmill_Foundation(x, y, self, + max_cards=7, base_rank=6, max_move=0)) + + # define stack-groups + l.defaultStackGroups() + + + # + # game overrides + # + + def _shuffleHook(self, cards): + return cards + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# /*********************************************************************** +# // Corners +# ************************************************************************/ + +class Corners(Game): + + def createGame(self): + # create layout + l, s = Layout(self, XM=20, YM=20), self.s + + # set window + self.setSize(5*l.XS+l.XM, 4*l.YS+3*l.YM) + + # create stacks + x, y = l.XM+l.XS, l.YM + s.talon = WasteTalonStack(x, y, self, max_rounds=3) + l.createText(s.talon, "sw") + x += l.XS + s.waste = WasteStack(x, y, self) + l.createText(s.waste, "se") + x0, y0 = l.XM, l.YM+l.YS + i = 0 + for d in ((0,0), (4,0), (0,2), (4,2)): + x, y = x0+d[0]*l.XS, y0+d[1]*l.YS + s.foundations.append(SS_FoundationStack(x, y, self, suit=i, + max_move=0, mod=13)) + i += 1 + for d in ((2,0), (1,1), (2,1), (3,1), (2,2)): + x, y = x0+d[0]*l.XS, y0+d[1]*l.YS + s.rows.append(ReserveStack(x, y, self)) + + # define stack-groups + l.defaultStackGroups() + + + def fillStack(self, stack): + if len(stack.cards) == 0: + if stack is self.s.waste and self.s.talon.cards: + self.s.talon.dealCards() + elif stack in self.s.rows and self.s.waste.cards: + self.s.waste.moveMove(1, stack) + + + def _shuffleHook(self, cards): + suits = [] + top_cards = [] + for c in cards[:]: + if c.suit not in suits: + suits.append(c.suit) + cards.remove(c) + top_cards.append(c) + if len(suits) == 4: + break + top_cards.sort(lambda a, b: cmp(b.suit, a.suit)) + return cards+top_cards + + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + +# register the game +registerGame(GameInfo(30, Windmill, "Windmill", + GI.GT_2DECK_TYPE, 2, 0)) +registerGame(GameInfo(277, NapoleonsTomb, "Napoleon's Tomb", + GI.GT_1DECK_TYPE, 1, 0)) +registerGame(GameInfo(417, Corners, "Corners", + GI.GT_1DECK_TYPE, 1, 2)) + diff --git a/pysollib/games/yukon.py b/pysollib/games/yukon.py new file mode 100644 index 0000000000..ced4d9ef96 --- /dev/null +++ b/pysollib/games/yukon.py @@ -0,0 +1,545 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = [] + +# imports +import sys + +# PySol imports +from pysollib.gamedb import registerGame, GameInfo, GI +from pysollib.util import * +from pysollib.mfxutil import kwdefault +from pysollib.stack import * +from pysollib.game import Game +from pysollib.layout import Layout +from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint +from pysollib.hint import YukonType_Hint +from pysollib.pysoltk import MfxCanvasText + +from spider import Spider_SS_Foundation + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Yukon_Hint(YukonType_Hint): + BONUS_FLIP_CARD = 9000 + BONUS_CREATE_EMPTY_ROW = 100 + + ## FIXME: this is only a rough approximation and doesn't seem to help + ## for Russian Solitaire + def _getMovePileScore(self, score, color, r, t, pile, rpile): + s, color = YukonType_Hint._getMovePileScore(self, score, color, r, t, pile, rpile) + bonus = s - score + assert 0 <= bonus <= 9999 + # We must take care when moving piles that we won't block cards, + # i.e. if there is a card in pile which would be needed + # for a card in stack t. + tpile = t.getPile() + if tpile: + for cr in pile: + rr = self.ClonedStack(r, stackcards=[cr]) + for ct in tpile: + if rr.acceptsCards(t, [ct]): + d = bonus / 1000 + bonus = (d * 1000) + bonus % 100 + break + return score + bonus, color + + +# /*********************************************************************** +# // Yukon +# ************************************************************************/ + +class Yukon(Game): + Layout_Method = Layout.yukonLayout + Talon_Class = InitialDealTalonStack + Foundation_Class = SS_FoundationStack + RowStack_Class = StackWrapper(Yukon_AC_RowStack, base_rank=KING) + Hint_Class = Yukon_Hint + + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=7, texts=0, playcards=25) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon =self.Talon_Class(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit, + max_move=0)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + # default + l.defaultAll() + return l + + def startGame(self): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0) + for i in range(4): + self.s.talon.dealRow(rows=self.s.rows[1:], flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + def getHighlightPilesStacks(self): + return () + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.color != card2.color and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Russian Solitaire (like Yukon, but build down by suit) +# ************************************************************************/ + +class RussianSolitaire(Yukon): + RowStack_Class = StackWrapper(Yukon_SS_RowStack, base_rank=KING) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Moosehide (build down in any suit but the same) +# ************************************************************************/ + +class Moosehide_RowStack(Yukon_AC_RowStack): + def _isSequence(self, c1, c2): + return (c1.suit != c2.suit and c1.rank == c2.rank+1) + def getHelp(self): + return _('Row. Build down in any suit but the same, can move any face-up cards regardless of sequence.') + +class Moosehide(Yukon): + RowStack_Class = StackWrapper(Moosehide_RowStack, base_rank=KING) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit != card2.suit and + abs(card1.rank-card2.rank) == 1) + + +# /*********************************************************************** +# // Odessa (just like Russian Solitaire, only a different initial +# // card layout) +# ************************************************************************/ + +class Odessa(RussianSolitaire): + def startGame(self): + for i in range(3): + self.s.talon.dealRow(flip=0, frames=0) + for i in range(2): + self.s.talon.dealRow(frames=0) + for i in range(2): + self.s.talon.dealRow(rows=self.s.rows[1:6], frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Grandfather +# ************************************************************************/ + +class Grandfather(RussianSolitaire): + def startGame(self): + n = 1 + for i in (2,4,6,5,3,1): + self.s.talon.dealRow(rows=[self.s.rows[n]]*i, flip=0, frames=0) + n += 1 + n = 0 + self.startDealSample() + for i in (1,5,5,5,5,5,5): + self.s.talon.dealRow(rows=[self.s.rows[n]]*i) + n += 1 + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Alaska (like Russian Solitaire, but build up or down in suit) +# ************************************************************************/ + +class Alaska_RowStack(Yukon_SS_RowStack): + def _isSequence(self, c1, c2): + return (c1.suit == c2.suit and + ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank or + (c2.rank + self.cap.dir) % self.cap.mod == c1.rank)) + def getHelp(self): + return _('Row. Build up or down by suit, can move any face-up cards regardless of sequence.') + + +class Alaska(RussianSolitaire): + RowStack_Class = StackWrapper(Alaska_RowStack, base_rank=KING) + + +# /*********************************************************************** +# // Roslin (like Russian Solitaire, but build up or down by alternate color) +# ************************************************************************/ + +class Roslin_RowStack(Yukon_AC_RowStack): + def _isSequence(self, c1, c2): + return (c1.color != c2.color and + ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank or + (c2.rank + self.cap.dir) % self.cap.mod == c1.rank)) + def getHelp(self): + return _('Row. Build up or down by alternate color, can move any face-up cards regardless of sequence.') + + +class Roslin(Yukon): + RowStack_Class = StackWrapper(Roslin_RowStack, base_rank=KING) + + +# /*********************************************************************** +# // Chinese Discipline +# // Chinese Solitaire +# ************************************************************************/ + +class ChineseDiscipline(Yukon): + Layout_Method = Layout.klondikeLayout + Talon_Class = DealRowTalonStack + + def createGame(self): + return Yukon.createGame(self, waste=0, texts=1) + + def startGame(self): + for i in (3, 3, 3, 4, 5, 6): + self.s.talon.dealRow(rows=self.s.rows[:i], flip=1, frames=0) + self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +class ChineseSolitaire(ChineseDiscipline): + RowStack_Class = Yukon_AC_RowStack # anything on an empty space + + +# /*********************************************************************** +# // Queenie +# ************************************************************************/ + +class Queenie(Yukon): + Layout_Method = Layout.klondikeLayout + Talon_Class = DealRowTalonStack + + def createGame(self): + return Yukon.createGame(self, waste=0, texts=1) + + def startGame(self, flip=1, reverse=1): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=flip, frames=0, reverse=reverse) + self.startDealSample() + self.s.talon.dealRow(reverse=reverse) + + +# /*********************************************************************** +# // Rushdike (like Queenie, but built down by suit) +# ************************************************************************/ + +class Rushdike(RussianSolitaire): + Layout_Method = Layout.klondikeLayout + Talon_Class = DealRowTalonStack + + def createGame(self): + return RussianSolitaire.createGame(self, waste=0, texts=1) + + def startGame(self, flip=0, reverse=1): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=flip, frames=0, reverse=reverse) + self.startDealSample() + self.s.talon.dealRow(reverse=reverse) + + +# /*********************************************************************** +# // Russian Point (Rushdike in a different layout) +# ************************************************************************/ + +class RussianPoint(Rushdike): + def startGame(self): + r = self.s.rows + for i in (1, 1, 2, 2, 3, 3): + self.s.talon.dealRow(rows=r[i:len(r)-i], flip=0, frames=0) + self.startDealSample() + self.s.talon.dealRow() + + +# /*********************************************************************** +# // Abacus +# ************************************************************************/ + +class Abacus_Foundation(SS_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, base_rank=suit, mod=13, dir=suit+1, max_move=0) + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + +class Abacus_RowStack(Yukon_SS_RowStack): + def _isSequence(self, c1, c2): + dir, mod = -(c1.suit + 1), 13 + return c1.suit == c2.suit and (c1.rank + dir) % mod == c2.rank + + +class Abacus(Rushdike): + Foundation_Class = Abacus_Foundation + RowStack_Class = Abacus_RowStack + + def createGame(self): + l = Rushdike.createGame(self) + help = (_('''\ +Club: A 2 3 4 5 6 7 8 9 T J Q K +Spade: 2 4 6 8 T Q A 3 5 7 9 J K +Heart: 3 6 9 Q 2 5 8 J A 4 7 T K +Diamond: 4 8 Q 3 7 J 2 6 T A 5 9 K''')) + self.texts.help = MfxCanvasText(self.canvas, + l.XM, self.height - l.YM, text=help, + anchor="sw", + font=self.app.getFont("canvas_fixed")) + + def _shuffleHook(self, cards): + # move Twos to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, lambda c: (c.id in (0, 14, 28, 42), c.suit)) + + def startGame(self, flip=1, reverse=1): + self.s.talon.dealRow(rows=self.s.foundations, frames=0) + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=flip, frames=0, reverse=reverse) + self.startDealSample() + self.s.talon.dealRow(reverse=reverse) + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + dir, mod = -(card1.suit + 1), 13 + return (card1.suit == card2.suit and + ((card1.rank + dir) % mod == card2.rank or + (card2.rank + dir) % mod == card1.rank)) + + +# /*********************************************************************** +# // Double Yukon +# ************************************************************************/ + +class DoubleYukon(Yukon): + def createGame(self): + Yukon.createGame(self, rows=10) + def startGame(self): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0) + for i in range(5): + self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow(rows=self.s.rows[:-1]) + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Triple Yukon +# ************************************************************************/ + +class TripleYukon(Yukon): + def createGame(self): + Yukon.createGame(self, rows=13) + def startGame(self): + for i in range(1, len(self.s.rows)): + self.s.talon.dealRow(rows=self.s.rows[i:], flip=0, frames=0) + for i in range(5): + self.s.talon.dealRow(rows=self.s.rows, flip=1, frames=0) + self.startDealSample() + self.s.talon.dealRow() + assert len(self.s.talon.cards) == 0 + + +# /*********************************************************************** +# // Ten Across +# ************************************************************************/ + +class TenAcross(Yukon): + + Foundation_Class = Spider_SS_Foundation + RowStack_Class = StackWrapper(Yukon_SS_RowStack, base_rank=KING) + Layout_Method = Layout.freeCellLayout + + # + # game layout + # + + def createGame(self, **layout): + # create layout + l, s = Layout(self), self.s + kwdefault(layout, rows=10, reserves=2, texts=0) + apply(self.Layout_Method, (l,), layout) + self.setSize(l.size[0], l.size[1]) + # create stacks + s.talon = InitialDealTalonStack(l.s.talon.x, l.s.talon.y, self) + for r in l.s.foundations: + self.s.foundations.append(self.Foundation_Class(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + for r in l.s.reserves: + self.s.reserves.append(ReserveStack(r.x, r.y, self)) + # default + l.defaultAll() + + # + # game overrides + # + + def startGame(self): + n = 1 + for i in range(4): + self.s.talon.dealRow(rows=self.s.rows[:n], frames=0) + self.s.talon.dealRow(rows=self.s.rows[n:-n], frames=0, flip=0) + self.s.talon.dealRow(rows=self.s.rows[-n:], frames=0) + n += 1 + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + assert len(self.s.talon.cards) == 0 + + def shallHighlightMatch(self, stack1, card1, stack2, card2): + return (card1.suit == card2.suit and + (card1.rank + 1 == card2.rank or card2.rank + 1 == card1.rank)) + + +# /*********************************************************************** +# // Panopticon +# ************************************************************************/ + +class Panopticon(TenAcross): + + Foundation_Class = SS_FoundationStack + + def createGame(self): + TenAcross.createGame(self, rows=8, reserves=4) + + def startGame(self): + self.s.talon.dealRow(frames=0, flip=0) + n = 1 + for i in range(3): + self.s.talon.dealRow(rows=self.s.rows[:n], frames=0) + self.s.talon.dealRow(rows=self.s.rows[n:-n], frames=0, flip=0) + self.s.talon.dealRow(rows=self.s.rows[-n:], frames=0) + n += 1 + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealRow(rows=self.s.reserves) + + +# /*********************************************************************** +# // Australian Patience +# // Raw Prawn +# ************************************************************************/ + +class AustralianPatience(RussianSolitaire): + + RowStack_Class = StackWrapper(Yukon_SS_RowStack, base_rank=KING) + + def createGame(self, rows=7): + l, s = Layout(self), self.s + Layout.klondikeLayout(l, rows=rows, waste=1) + self.setSize(l.size[0], l.size[1]) + s.talon = WasteTalonStack(l.s.talon.x, l.s.talon.y, self, max_rounds=1) + s.waste = WasteStack(l.s.waste.x, l.s.waste.y, self) + for r in l.s.foundations: + s.foundations.append(SS_FoundationStack(r.x, r.y, self, suit=r.suit)) + for r in l.s.rows: + s.rows.append(self.RowStack_Class(r.x, r.y, self)) + l.defaultAll() + + def startGame(self): + for i in range(3): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + +class RawPrawn(AustralianPatience): + RowStack_Class = Yukon_SS_RowStack + + +class BimBom(AustralianPatience): + RowStack_Class = Yukon_SS_RowStack + def createGame(self): + AustralianPatience.createGame(self, rows=8) + def startGame(self): + for i in range(4): + self.s.talon.dealRow(frames=0) + self.startDealSample() + self.s.talon.dealRow() + self.s.talon.dealCards() + + + +# register the game +registerGame(GameInfo(19, Yukon, "Yukon", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(20, RussianSolitaire, "Russian Solitaire", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(27, Odessa, "Odessa", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(278, Grandfather, "Grandfather", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(186, Alaska, "Alaska", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(187, ChineseDiscipline, "Chinese Discipline", + GI.GT_YUKON | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(188, ChineseSolitaire, "Chinese Solitaire", + GI.GT_YUKON | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(189, Queenie, "Queenie", + GI.GT_YUKON | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(190, Rushdike, "Rushdike", + GI.GT_YUKON | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(191, RussianPoint, "Russian Point", + GI.GT_YUKON | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(192, Abacus, "Abacus", + GI.GT_YUKON | GI.GT_XORIGINAL, 1, 0)) +registerGame(GameInfo(271, DoubleYukon, "Double Yukon", + GI.GT_YUKON, 2, 0)) +registerGame(GameInfo(272, TripleYukon, "Triple Yukon", + GI.GT_YUKON, 3, 0)) +registerGame(GameInfo(284, TenAcross, "Ten Across", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(285, Panopticon, "Panopticon", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(339, Moosehide, "Moosehide", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(387, Roslin, "Roslin", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(447, AustralianPatience, "Australian Patience", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(450, RawPrawn, "Raw Prawn", + GI.GT_YUKON, 1, 0)) +registerGame(GameInfo(456, BimBom, "Bim Bom", + GI.GT_YUKON, 2, 0)) diff --git a/pysollib/help.py b/pysollib/help.py new file mode 100644 index 0000000000..20e232c0cd --- /dev/null +++ b/pysollib/help.py @@ -0,0 +1,171 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, os +import traceback +import Tkinter + +# PySol imports +from mfxutil import EnvError +from settings import PACKAGE, PACKAGE_URL +from version import VERSION, FC_VERSION +from pysoltk import tkname, makeHelpToplevel, wm_map, wm_set_icon +from pysoltk import MfxDialog +from pysoltk import tkHTMLViewer +from gamedb import GAME_DB + +# /*********************************************************************** +# // +# ************************************************************************/ + +class AboutDialog(MfxDialog): + def createFrames(self, kw): + top_frame, bottom_frame = MfxDialog.createFrames(self, kw) + return top_frame, bottom_frame + + +def helpAbout(app, timeout=0, sound=1): + if sound: + app.audio.playSample("about") + t = _("A Python Solitaire Game Collection\n") + if app.miscrandom.random() < 0.8: + t = _("A World Domination Project\n") + strings=(_("Nice"), _("Credits...")) + if timeout: + strings=(_("Enjoy"),) + ##version = _("Version %s (%s)\n\n") % (FC_VERSION, VERSION) + version = _("Version %s\n\n") % FC_VERSION + d = AboutDialog(app.top, title=_("About ") + PACKAGE, timeout=timeout, + text=_('''PySol Fan Club edition +%s%s +Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003 +Markus F.X.J. Oberhumer +Copyright (C) 2003 Mt. Hood Playing Card Co. +Copyright (C) 2005 Skomoroh (Fan Club edition) +All Rights Reserved. + +PySol is free software distributed under the terms +of the GNU General Public License. + +For more information about this application visit +%s''') % (t, version, PACKAGE_URL), + image=app.gimages.logos[2], + strings=strings, default=0, + separatorwidth=2) + if d.status == 0 and d.button == 1: + helpCredits(app, sound=sound) + return d.status + + +def helpCredits(app, timeout=0, sound=1): + if sound: + app.audio.playSample("credits") + t = "" + if tkname == "tk": t = "Tcl/Tk, " + elif tkname == "gnome": t = "PyGTK, " + elif tkname == "kde": t = "pyKDE, " + elif tkname == "wx": t = "wxPython, " + d = MfxDialog(app.top, title=_("Credits"), timeout=timeout, + text=PACKAGE+_(''' credits go to: + +Volker Weidner for getting me into Solitaire +Guido van Rossum for the initial example program +T. Kirk for lots of contributed games and cardsets +Carl Larsson for the background music +The Gnome AisleRiot team for parts of the documentation +Natascha + +The Python, %s, SDL & Linux crews +for making this program possible''') % t, + image=app.gimages.logos[3], image_side="right", + separatorwidth=2) + return d.status + + +# /*********************************************************************** +# // +# ************************************************************************/ + +help_html_viewer = None +help_html_index = None + +def helpHTML(app, document, dir_, top=None): + global help_html_viewer, help_html_index + if not document: + return None + if top is None: + top = app.top + try: + doc = app.dataloader.findFile(document, dir_) + if help_html_index is None: + document, dir_ = "index.html", "html" + help_html_index = app.dataloader.findFile(document, dir_) + except EnvError: + d = MfxDialog(app.top, title=PACKAGE + _(" HTML Problem"), + text=_("Cannot find help document\n") + document, + bitmap="warning") + return None + ##print doc, help_html_index + try: + viewer = help_html_viewer + #if viewer.parent.winfo_parent() != top._w: + # viewer.destroy() + # viewer = None + viewer.updateHistoryXYView() + viewer.display(doc, relpath=0) + except: + ##traceback.print_exc() + top = makeHelpToplevel(app, title=PACKAGE+_(" Help")) + if top.winfo_screenwidth() < 800 or top.winfo_screenheight() < 600: + #maximized = 1 + top.wm_minsize(300, 150) + else: + #maximized = 0 + top.wm_minsize(400, 200) + try: + wm_set_icon(top, app.dataloader.findIcon()) + except: + pass + viewer = tkHTMLViewer(top) + viewer.app = app + viewer.home = help_html_index + viewer.display(doc) + #wm_map(top, maximized=maximized) + viewer.parent.tkraise() + help_html_viewer = viewer + return viewer + diff --git a/pysollib/hint.py b/pysollib/hint.py new file mode 100644 index 0000000000..4bb2cee0e6 --- /dev/null +++ b/pysollib/hint.py @@ -0,0 +1,965 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import struct, os, sys +import traceback + +# PySol imports +from mfxutil import Struct, destruct +from util import KING + + +# /*********************************************************************** +# // HintInterface is an abstract class that defines the public +# // interface - it only consists of the constructor +# // and the getHints() method. +# // +# // The whole hint system is exclusively used by Game.getHints(). +# ************************************************************************/ + +class HintInterface: + # level == 0: show hint (key `H') + # level == 1: show hint and display score value (key `Ctrl-H') + # level == 2: demo + def __init__(self, game, level): + pass + + # Compute all hints for the current position. + # Subclass responsibility. + # + # Returns a list of "atomic hints" - an atomic hint is a 7-tuple + # (score, pos, ncards, from_stack, to_stack, text_color, forced_move). + # + # if ncards == 0: deal cards + # elif from_stack == to_stack: flip card + # else: move cards from from_stack to to_stack + # + # score, pos and text_color are only for debugging. + # A forced_move is the next move that must be taken after this move + # in order to avoid endless loops during demo play. + # + # Deal and flip may only happen if self.level >= 2 (i.e. demo). + # + # See Game.showHint() for more information. + def getHints(self, taken_hint=None): + return [] + + +# /*********************************************************************** +# // AbstractHint provides a useful framework for derived hint classes. +# // +# // Subclasses should override computeHints() +# ************************************************************************/ + +class AbstractHint(HintInterface): + def __init__(self, game, level): + self.game = game + self.level = level + self.score_flatten_value = 0 + if self.level == 0: + self.score_flatten_value = 10000 + # temporaries within getHints() + self.bonus_color = None + # + self.__clones = [] + self.reset() + + def __del__(self): + self.reset() + + def reset(self): + self.hints = [] + self.max_score = 0 + self.__destructClones() + + # + # stack cloning + # + + # Create a shallow copy of a stack. + class AClonedStack: + def __init__(self, stack, stackcards): + # copy class identity + self.__class__ = stack.__class__ + # copy model data (reference copy) + stack.copyModel(self) + # set new cards (shallow copy of the card list) + self.cards = stackcards[:] + + def ClonedStack(self, stack, stackcards): + s = self.AClonedStack(stack, stackcards) + self.__clones.append(s) + return s + + def __destructClones(self): + for s in self.__clones: + s.__class__ = self.AClonedStack # restore orignal class + destruct(s) + self.__clones = [] + + # When computing hints for level 0, the scores are flattened + # (rounded down) to a multiple of score_flatten_value. + # + # The idea is that hints will appear equal within a certain score range + # so that the player will not get confused by the demo-intelligence. + # + # Pressing `Ctrl-H' (level 1) will preserve the score. + + def addHint(self, score, ncards, from_stack, to_stack, text_color=None, forced_move=None): + if score < 0: + return + self.max_score = max(self.max_score, score) + # add an atomic hint + if self.score_flatten_value > 0: + score = (score / self.score_flatten_value) * self.score_flatten_value + if text_color is None: + text_color = self.BLACK + assert forced_move is None or len(forced_move) == 7 + # pos is used for preserving the original sort order on equal scores + pos = -len(self.hints) + ah = (int(score), pos, ncards, from_stack, to_stack, text_color, forced_move) + self.hints.append(ah) + + # clean up and return hints sorted by score + def __returnHints(self): + hints = self.hints + self.reset() + hints.sort() + hints.reverse() + return hints + + # + # getHints() default implementation: + # - handle forced moves + # - try to flip face-down cards + # - call computeHints() to do something useful + # - try to deal cards + # - clean up and return hints sorted by score + # + + # Default scores for flip and deal moves. + SCORE_FLIP = 100000 # 0..100000 + SCORE_DEAL = 0 # 0..100000 + + def getHints(self, taken_hint=None): + # 0) setup + self.reset() + game = self.game + # 1) forced moves of the prev. taken hint have absolute priority + if taken_hint and taken_hint[6]: + return [taken_hint[6]] + # 2) try if we can flip a card + if self.level >= 2: + for r in game.allstacks: + if r.canFlipCard(): + self.addHint(self.SCORE_FLIP, 1, r, r) + if self.SCORE_FLIP >= 90000: + return self.__returnHints() + # 3) ask subclass to do something useful + self.computeHints() + # 4) try if we can deal cards + if self.level >= 2: + if game.canDealCards(): + self.addHint(self.SCORE_DEAL, 0, game.s.talon, None) + return self.__returnHints() + + # subclass + def computeHints(self): + pass + + # + # utility shallMovePile() + # + + # we move the pile if it is accepted by the target stack + def _defaultShallMovePile(self, from_stack, to_stack, pile, rpile): + if from_stack is to_stack or not to_stack.acceptsCards(from_stack, pile): + return 0 + return 1 + + # same, but check for loops + def _cautiousShallMovePile(self, from_stack, to_stack, pile, rpile): + if from_stack is to_stack or not to_stack.acceptsCards(from_stack, pile): + return 0 + # now check for loops + rr = self.ClonedStack(from_stack, stackcards=rpile) + if rr.acceptsCards(to_stack, pile): + # the pile we are going to move could be moved back - + # this is dangerous as we can create endless loops... + return 0 + return 1 + + # same, but only check for loops only when in demo mode + def _cautiousDemoShallMovePile(self, from_stack, to_stack, pile, rpile): + if from_stack is to_stack or not to_stack.acceptsCards(from_stack, pile): + return 0 + if self.level >= 2: + # now check for loops + rr = self.ClonedStack(from_stack, stackcards=rpile) + if rr.acceptsCards(to_stack, pile): + # the pile we are going to move could be moved back - + # this is dangerous as we can create endless loops... + return 0 + return 1 + + shallMovePile = _defaultShallMovePile + + + # + # other utility methods + # + + def _canDropAllCards(self, from_stack, stacks, stackcards): + assert not from_stack in stacks + return 0 + # FIXME: this does not account for cards which are dropped herein + cards = pile[:] + cards.reverse() + for card in cards: + for s in stacks: + if s is not from_stack: + if s.acceptsCards(from_stack, [card]): + break + else: + return 0 + return 1 + + # + # misc. constants + # + + # score value so that the scores look nicer + K = KING + 1 + # text_color that will display the score (for debug with level 1) + BLACK = "black" + RED = "red" + BLUE = "blue" + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class DefaultHint(AbstractHint): + + # The DefaultHint is optimized for Klondike type games + # and also deals quite ok with other simple variants. + # + # But it completely lacks any specific strategy about game + # types like Forty Thieves, FreeCell, Golf, Spider, ... + # + # BTW, we do not cheat ! + + + # + # bonus scoring used in _getXxxScore() below - subclass overrideable + # + + def _preferHighRankMoves(self): + return 0 + + + # Basic bonus for moving a card. + # Bonus must be in range 0..999 + + BONUS_DROP_CARD = 300 # 0..400 + BONUS_SAME_SUIT_MOVE = 200 # 0..400 + BONUS_NORMAL_MOVE = 100 # 0..400 + + def _getMoveCardBonus(self, r, t, pile, rpile): + assert pile + bonus = 0 + if rpile: + rr = self.ClonedStack(r, stackcards=rpile) + if (rr.canDropCards(self.game.s.foundations))[0]: + # the card below the pile can be dropped + bonus = self.BONUS_DROP_CARD + if t.cards and t.cards[-1].suit == pile[0].suit: + # simple heuristics - prefer moving high-rank cards + bonus = bonus + self.BONUS_SAME_SUIT_MOVE + (1 + pile[0].rank) + elif self._preferHighRankMoves(): + # simple heuristics - prefer moving high-rank cards + bonus = bonus + self.BONUS_NORMAL_MOVE + (1 + pile[0].rank) + elif rpile: + # simple heuristics - prefer low-rank cards in rpile + bonus = bonus + self.BONUS_NORMAL_MOVE + (self.K - rpile[-1].rank) + else: + # simple heuristics - prefer moving high-rank cards + bonus = bonus + self.BONUS_NORMAL_MOVE + (1 + pile[0].rank) + return bonus + + + # Special bonus for facing up a card after the current move. + # Bonus must be in range 0..9000 + + BONUS_FLIP_CARD = 1500 # 0..9000 + + def _getFlipSpecialBonus(self, r, t, pile, rpile): + assert pile and rpile + # The card below the pile can be flipped + # (do not cheat and look at it !) + # default: prefer a short rpile + bonus = max(self.BONUS_FLIP_CARD - len(rpile), 0) + return bonus + + + # Special bonus for moving a pile from stack r to stack t. + # Bonus must be in range 0..9000 + + BONUS_CREATE_EMPTY_ROW = 9000 # 0..9000 + BONUS_CAN_DROP_ALL_CARDS = 4000 # 0..4000 + BONUS_CAN_CREATE_EMPTY_ROW = 2000 # 0..4000 + + def _getMoveSpecialBonus(self, r, t, pile, rpile): + # check if we will create an empty row + if not rpile: + return self.BONUS_CREATE_EMPTY_ROW + # check if the card below the pile can be flipped + if not rpile[-1].face_up: + return self._getFlipSpecialBonus(r, t, pile, rpile) + # check if all the cards below our pile could be dropped + if self._canDropAllCards(r, self.game.s.foundations, stackcards=rpile): + # we can drop the whole remaining pile + # (and will create an empty row in the next move) + ##print "BONUS_CAN_DROP_ALL_CARDS", r, pile, rpile + self.bonus_color = self.RED + return self.BONUS_CAN_DROP_ALL_CARDS + self.BONUS_CAN_CREATE_EMPTY_ROW + # check if the cards below our pile are a whole row + if r.canMoveCards(rpile): + # could we move the remaining pile ? + for x in self.game.s.rows: + # note: we allow x == r here, because the pile + # (currently at the top of r) will be + # available in the next move + if x is t or not x.cards: + continue + if x.acceptsCards(r, rpile): + # we can create an empty row in the next move + ##print "BONUS_CAN_CREATE_EMPTY_ROW", r, x, pile, rpile + self.bonus_color = self.BLUE + return self.BONUS_CAN_CREATE_EMPTY_ROW + return 0 + + + # + # scoring used in getHints() - subclass overrideable + # + + # Score for moving a pile from stack r to stack t. + # Increased score should be in range 0..9999 + def _getMovePileScore(self, score, color, r, t, pile, rpile): + assert pile + self.bonus_color = color + b1 = self._getMoveSpecialBonus(r, t, pile, rpile) + assert 0 <= b1 <= 9000 + b2 = self._getMoveCardBonus(r, t, pile, rpile) + assert 0 <= b2 <= 999 + return score + b1 + b2, self.bonus_color + + + # Score for moving a pile (usually a single card) from the WasteStack. + def _getMoveWasteScore(self, score, color, r, t, pile, rpile): + assert pile + self.bonus_color = color + score = 30000 + if t.cards: score = 31000 + b2 = self._getMoveCardBonus(r, t, pile, rpile) + assert 0 <= b2 <= 999 + return score + b2, self.bonus_color + + + # Score for dropping ncards from stack r to stack t. + def _getDropCardScore(self, score, color, r, t, ncards): + assert t is not r + if ncards > 1: + # drop immediately (Spider) + return 93000, color + pile = r.cards + c = pile[-1] + # compute distance to t.cap.base_rank - compare Stack.getRankDir() + if t.cap.base_rank < 0: + d = len(t.cards) + else: + d = (c.rank - t.cap.base_rank) % t.cap.mod + if d > t.cap.mod / 2: + d = d - t.cap.mod + if abs(d) <= 1: + # drop Ace and 2 immediately + score = 92000 + elif r in self.game.sg.talonstacks: + score = 25000 # less than _getMoveWasteScore() + elif len(pile) == 1: + ###score = 50000 + score = 91000 + elif self._canDropAllCards(r, self.game.s.foundations, stackcards=pile[:-1]): + score = 90000 + color = self.RED + else: + # don't drop this card too eagerly - we may need it + # for pile moving + score = 50000 + score = score + (self.K - c.rank) + return score, color + + + # + # compute hints - main hint intelligence + # + + def computeHints(self): + game = self.game + + # 1) check Tableau piles + self.step010(game.sg.dropstacks, game.s.rows) + + # 2) try if we can move part of a pile within the RowStacks + # so that we can drop a card afterwards + if not self.hints and self.level >= 1: + self.step020(game.s.rows, game.s.foundations) + + # 3) try if we should move a card from a Foundation to a RowStack + if not self.hints and self.level >= 1: + self.step030(game.s.foundations, game.s.rows, game.sg.dropstacks) + + # 4) try if we can move a card from a RowStack to a ReserveStack + if not self.hints: + self.step040(game.s.rows, game.sg.reservestacks) + + # 5) try if we should move a card from a ReserveStack to a RowStack + if not self.hints: + self.step050(game.sg.reservestacks, game.s.rows) + + # Don't be too clever and give up ;-) + + + # + # implementation of the hint steps + # + + # 1) check Tableau piles + + def step010(self, dropstacks, rows): + # for each stack + for r in dropstacks: + # 1a) try if we can drop cards + t, ncards = r.canDropCards(self.game.s.foundations) + if t: + score, color = 0, None + score, color = self._getDropCardScore(score, color, r, t, ncards) + self.addHint(score, ncards, r, t, color) + if score >= 90000: + break + # 1b) try if we can move cards to one of the RowStacks + for pile in self.step010b_getPiles(r): + if pile: + self.step010_movePile(r, pile, rows) + + def step010b_getPiles(self, stack): + # return all moveable piles for this stack, longest one first + return (stack.getPile(), ) + + def step010_movePile(self, r, pile, rows): + lp = len(pile) + lr = len(r.cards) + assert 1 <= lp <= lr + rpile = r.cards[ : (lr-lp) ] # remaining pile + + empty_row_seen = 0 + r_is_waste = r in self.game.sg.talonstacks + + for t in rows: + score, color = 0, None + if not self.shallMovePile(r, t, pile, rpile): + continue + if r_is_waste: + # moving a card from the WasteStack + score, color = self._getMoveWasteScore(score, color, r, t, pile, rpile) + else: + if not t.cards: + # the target stack is empty + if lp == lr: + # do not move a whole stack from row to row + continue + if empty_row_seen: + # only make one hint for moving to an empty stack + # (in case we have multiple empty stacks) + continue + score = 60000 + empty_row_seen = 1 + else: + # the target stack is not empty + score = 80000 + score, color = self._getMovePileScore(score, color, r, t, pile, rpile) + self.addHint(score, lp, r, t, color) + + + # 2) try if we can move part of a pile within the RowStacks + # so that we can drop a card afterwards + # score: 40000 .. 59999 + + step020_getPiles = step010b_getPiles + + def step020(self, rows, foundations): + for r in rows: + for pile in self.step020_getPiles(r): + if not pile or len(pile) < 2: + continue + # is there a card in our pile that could be dropped ? + drop_info = [] + i = 0 + for c in pile: + rr = self.ClonedStack(r, stackcards=[c]) + stack, ncards = rr.canDropCards(foundations) + if stack and stack is not r: + assert ncards == 1 + drop_info.append((c, stack, ncards, i)) + i = i + 1 + # now try to make a move so that the drop-card will get free + for di in drop_info: + c = di[0] + sub_pile = pile[di[3]+1 : ] + ##print "trying drop move", c, pile, sub_pile + ##assert r.canMoveCards(sub_pile) + if not r.canMoveCards(sub_pile): + continue + for t in rows: + if t is r or not t.acceptsCards(r, sub_pile): + continue + ##print "drop move", r, t, sub_pile + score = 40000 + score = score + 1000 + (self.K - r.getCard().rank) + # force the drop (to avoid loops) + force = (999999, 0, di[2], r, di[1], self.BLUE, None) + self.addHint(score, len(sub_pile), r, t, self.RED, forced_move=force) + + + # 3) try if we should move a card from a Foundation to a RowStack + # score: 20000 .. 29999 + + def step030(self, foundations, rows, dropstacks): + for s in foundations: + card = s.getCard() + if not card or not s.canMoveCards([card]): + continue + # search a RowStack that would accept the card + for t in rows: + if t is s or not t.acceptsCards(s, [card]): + continue + tt = self.ClonedStack(t, stackcards=t.cards+[card]) + # search a Stack that would benefit from this card + for r in dropstacks: + if r is t: + continue + pile = r.getPile() + if not pile: + continue + if not tt.acceptsCards(r, pile): + continue + # compute remaining pile in r + rpile = r.cards[ : (len(r.cards)-len(pile)) ] + rr = self.ClonedStack(r, stackcards=rpile) + if rr.acceptsCards(t, pile): + # the pile we are going to move from r to t + # could be moved back from t ro r - this is + # dangerous as we can create loops... + continue + score = 20000 + card.rank + ##print score, s, t, r, pile, rpile + # force the move from r to t (to avoid loops) + force = (999999, 0, len(pile), r, t, self.BLUE, None) + self.addHint(score, 1, s, t, self.BLUE, forced_move=force) + + + # 4) try if we can move a card from a RowStack to a ReserveStack + # score: 10000 .. 19999 + + def step040(self, rows, reservestacks): + if not reservestacks: + return + for r in rows: + card = r.getCard() + if not card or not r.canMoveCards([card]): + continue + pile = [card] + # compute remaining pile in r + rpile = r.cards[ : (len(r.cards)-len(pile)) ] + rr = self.ClonedStack(r, stackcards=rpile) + for t in reservestacks: + if t is r or not t.acceptsCards(r, pile): + continue + if rr.acceptsCards(t, pile): + # the pile we are going to move from r to t + # could be moved back from t ro r - this is + # dangerous as we can create loops... + continue + score = 10000 + score, color = self._getMovePileScore(score, None, r, t, pile, rpile) + self.addHint(score, len(pile), r, t, color) + break + + + # 5) try if we should move a card from a ReserveStack to a RowStack + + def step050(self, reservestacks, rows): + if not reservestacks: + return + ### FIXME + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class CautiousDefaultHint(DefaultHint): + shallMovePile = DefaultHint._cautiousShallMovePile + ##shallMovePile = DefaultHint._cautiousDemoShallMovePile + + def _preferHighRankMoves(self): + return 1 + + +# /*********************************************************************** +# // now some default hints for the various game types +# ************************************************************************/ + +# DefaultHint is optimized for Klondike type games anyway +class KlondikeType_Hint(DefaultHint): + pass + +# this works for Yukon, but not too well for Russian Solitaire +class YukonType_Hint(CautiousDefaultHint): + def step010b_getPiles(self, stack): + # return all moveable piles for this stack, longest one first + p = stack.getPile() + piles = [] + while p: + piles.append(p) + p = p[1:] # note: we need a fresh shallow copy + return piles + +# FIXME +class FreeCellType_Hint(CautiousDefaultHint): + pass + +class GolfType_Hint(DefaultHint): + pass + +class SpiderType_Hint(DefaultHint): + pass + + + +# /*********************************************************************** +# // +# ************************************************************************/ + +FreecellSolver = None + +## try: +## import FreecellSolver +## except: +## FreecellSolver = None + +fcs_command = 'fc-solve' +if os.name == 'nt': + if sys.path[0] and not os.path.isdir(sys.path[0]): # i.e. library.zip + fcs_command = os.path.join(os.path.split(sys.path[0])[0], 'fc-solve.exe') + fcs_command = '"%s"' % fcs_command + +try: + pin, pout, perr = os.popen3(fcs_command+' --help') + if pout.readline().startswith('fc-solve'): + FreecellSolver = True + del pin, pout, perr +except: + ##traceback.print_exc() + pass + + +class FreeCellSolverWrapper: + class FreeCellSolver_Hint(AbstractHint): + def str1(self, card): + return "A23456789TJQK"[card.rank] + "CSHD"[card.suit] + def str2(self, card): + return "CSHD"[card.suit] + "-" + "A23456789TJQK"[card.rank] + + + def computeHints(self): + ##print 'FreeCellSolver_Hint.computeHints' + + board = '' + pboard = {} + # + b = '' + #l = [] + for s in self.game.s.foundations: + if s.cards: + b = b + ' ' + self.str2(s.cards[-1]) + #l.append(self.str2(s.cards[-1])) + if b: + board = board + 'Founds:' + b + '\n' + #pboard['Founds'] = l + # + b = '' + l = [] + for s in self.game.s.reserves: + if s.cards: + cs = self.str1(s.cards[-1]) + b = b + ' ' + cs + l.append(cs) + else: + b = b + ' -' + l.append(None) + if b: + board = board + 'FC:' + b + '\n' + pboard['FC'] = l + # + n = 0 + for s in self.game.s.rows: + b = '' + l = [] + for c in s.cards: + cs = self.str1(c) + if not c.face_up: + cs = '<%s>' % cs + b = b + cs + ' ' + l.append(cs) + board = board + b.strip() + '\n' + pboard[n] = l + n += 1 + # + ##print board + # + args = [] + args += ['-sam', '-p', '--display-10-as-t'] + ##args += ['-l', 'good-intentions'] + args += ['--max-iters', 200000] + args += ['--decks-num', self.fcs_args[0], + '--stacks-num', self.fcs_args[1], + '--freecells-num', self.fcs_args[2], + ] + # + game_type = self.fcs_args[3] + if game_type.has_key('sbb'): + args += ['--sequences-are-built-by', game_type['sbb']] + if game_type.has_key('sm'): + args += ['--sequence-move', game_type['sm']] + if game_type.has_key('esf'): + args += ['--empty-stacks-filled-by', game_type['esf']] + if game_type.has_key('preset'): + args += ['--preset', game_type['preset']] + + command = fcs_command+' '+' '.join([str(i) for i in args]) + ##print command + pin, pout, perr = os.popen3(command) + pin.write(board) + pin.close() + # + stack_types = { + 'the' : self.game.s.foundations, + 'stack' : self.game.s.rows, + 'freecell' : self.game.s.reserves, + } + my_hints = [] + pboard_n = 0 + ##print pboard + for s in pout: + ##print s, + if not s.startswith('Move'): + if s.startswith('Freecells:'): + ss=s[10:] + pboard['FC'] = [ss[i:i+4].strip() for i in range(0, len(ss), 4)] + elif s.startswith(':'): + pboard[pboard_n] = s.split()[1:] + pboard_n += 1 + continue + + words = s.split() + ncards = words[1] + if ncards == 'a': + ncards = 1 + else: + ncards = int(ncards) + + sn = int(words[5]) + st = stack_types[words[4]] + src = st[sn] + + if words[7] == 'the': + # to foundation + if words[4] == 'stack': + # from rows + card = pboard[sn][-1] + else: + # from reserves + card = pboard['FC'][sn] + suit = 'CSHD'.index(card[1]) + ##dest = self.game.s.foundations[suit] + dest = None + for f in self.game.s.foundations: + if f.cap.base_suit == suit: + dest = f + break + assert dest is not None + + else: + # to rows or reserves + dt = stack_types[words[7]] + dn = int(words[8]) + dest = dt[dn] + + my_hints.append([ncards, src, dest]) + ##print src, dest, ncards + + pboard = {} + pboard_n = 0 + + my_hints.reverse() + hint = None + for i in my_hints: + hint = (999999, 0, i[0], i[1], i[2], None, hint) + if hint: + self.hints.append(hint) + ##print self.hints + + + def computeHints_mod(self): + board = "" + # + b = "" + for s in self.game.s.foundations: + if s.cards: + b = b + " " + self.str2(s.cards[-1]) + if b: + board = board + "Founds:" + b + "\n" + # + b = "" + for s in self.game.s.reserves: + if s.cards: + b = b + " " + self.str1(s.cards[-1]) + else: + b = b + " -" + if b: + board = board + "FC:" + b + "\n" + # + for s in self.game.s.rows: + b = "" + for c in s.cards: + b = b + self.str1(c) + " " + board = board + b.strip() + "\n" + # + ##print board + # solver = apply(FreecellSolver.FCSolver, self.fcs_args) + solver = FreecellSolver.alloc() + solver.config(["-l", "good-intentions"]); + solver.config(["--decks-num", self.fcs_args[0], + "--stacks-num", self.fcs_args[1], + "--freecells-num", self.fcs_args[2], + ]) + + game_type = self.fcs_args[3] + game_type_defaults = {'sbb' : 'alternate_color', 'sm' : 'limited', 'esf': 'all'} + for k,v in game_type_defaults.items(): + if (not game_type.has_key(k)): + game_type[k] = v + + solver.config(["--sequences-are-built-by", game_type['sbb'], + "--sequence-move", game_type['sm'], + "--empty-stacks-filled-by", game_type['esf'], + ]) + + solver.limit_iterations(200000) + + h = solver.solve(board) + if (h == "solved"): + move = solver.get_next_move() + hint = None + founds_map = [2,0,3,1,6,4,7,5] + my_hints = [] + while (move): + print move + + if (move['type'] == "s2s"): + ncards = move['num_cards'] + else: + ncards = 1 + + src_idx = move['src'] + if move['type'] in ("s2s", "s2found", "s2f"): + src = self.game.s.rows[src_idx] + else: + src = self.game.s.reserves[src_idx] + + d_idx = move['dest'] + if move['type'] in ("s2s", "f2s"): + dest = self.game.s.rows[d_idx] + elif move['type'] in ("s2f", "f2f"): + dest = self.game.s.reserves[d_idx] + else: + dest = self.game.s.foundations[founds_map[d_idx]] + + # hint = (999999, 0, ncards, src, dest, None, 0) + my_hints.append([ncards, src, dest]) + # self.hints.append(hint) + move = solver.get_next_move(); + hint = None + my_hints.reverse() + for i in my_hints: + hint = (999999, 0, i[0], i[1], i[2], None, hint) + self.hints.append(hint) + #i = len(h) + #assert i % 3 == 0 + #hint = None + #map = self.game.s.foundations + self.game.s.rows + self.game.s.reserves + #while i > 0: + # i = i - 3 + # v = struct.unpack(" +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import os, types + +# PySol imports +from version import VERSION, VERSION_TUPLE +from mfxutil import Pickler, Unpickler, UnpicklingError +from mfxutil import Struct, EnvError + +# Toolkit imports +from pysoltk import tkversion, loadImage, copyImage, createImage + +try: + import Image +except ImportError: + Image = None + +# /*********************************************************************** +# // Images +# ************************************************************************/ + + +class ImagesCardback: + def __init__(self, index, name, image, menu_image=None): + if menu_image is None: menu_image = image + self.index = index + self.name = name + self.image = image + self.menu_image = menu_image + + +class Images: + def __init__(self, dataloader, cs, r=1): + self.d = dataloader + self.cs = cs + self.reduced = r + if cs is None: + return + # copy from cardset + self.CARDW, self.CARDH, self.CARDD = cs.CARDW/r, cs.CARDH/r, cs.CARDD/r + self.CARD_XOFFSET = cs.CARD_XOFFSET + self.CARD_YOFFSET = cs.CARD_YOFFSET + if r > 1: + self.CARD_XOFFSET = max(10, cs.CARD_XOFFSET)/r + self.CARD_YOFFSET = max(10, cs.CARD_YOFFSET)/r + self.SHADOW_XOFFSET, self.SHADOW_YOFFSET = cs.SHADOW_XOFFSET/r, cs.SHADOW_YOFFSET/r + self.CARD_DX, self.CARD_DY = cs.CARD_DX/r, cs.CARD_DY/r + # other + self._shade_index = 0 + self._card = [] + self._back = [] + self._bottom = [] + self._bottom_negative = [] + self._bottom_positive = [] + self._letter = [] + self._letter_negative = [] + self._letter_positive = [] + self._shadow = [] + self._shade = [] + + def destruct(self): + pass + + def __loadCard(self, filename, check_w=1, check_h=1): + ##print '__loadCard:', filename + f = os.path.join(self.cs.dir, filename) + img = loadImage(file=f) + w, h = img.width(), img.height() + if self.CARDW < 0: + self.CARDW, self.CARDH = w, h + else: + if ((check_w and w != self.CARDW) or + (check_h and h != self.CARDH)): + raise Exception, "Invalid size %dx%d of image %s" % (w, h, f) + return img + + def __addBack(self, im1, name): + r = max(self.CARDW / 40.0, self.CARDH / 60.0) + r = max(2, int(round(r))) + im2 = im1.subsample(r) + self._back.append(ImagesCardback(len(self._back), name, im1, im2)) + + def _createMissingImages(self): + # back + if not self._back: + im = createImage(self.CARDW, self.CARDH, fill="#a0a0a0", outline="#000000") + name = "" + self.__addBack(im, name) + self.cs.backnames = tuple(self.cs.backnames) + (name,) + # bottoms / letters + bottom = None + while len(self._bottom_positive) < 7: + if bottom is None: + bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#000000") + self._bottom_positive.append(bottom) + while len(self._bottom_negative) < 7: + if bottom is None: + bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#ffffff") + self._bottom_negative.append(bottom) + while len(self._letter_positive) < 4: + if bottom is None: + bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#000000") + self._letter_positive.append(bottom) + while len(self._letter_negative) < 4: + if bottom is None: + bottom = createImage(self.CARDW, self.CARDH, fill=None, outline="#ffffff") + self._letter_negative.append(bottom) + + def load(self, app, progress=None, fast=0): + ##fast = 1 + ##fast = 2 + if fast > 1: + # only for testing + self.cs.backnames = () + self.cs.nbottoms = 0 + self.cs.nletters = 0 + ext = self.cs.ext[1:] + pstep = 0 + if progress: + pstep = self.cs.ncards + len(self.cs.backnames) + self.cs.nbottoms + self.cs.nletters + if not fast: + pstep = pstep + self.cs.nshadows + 1 # shadows & shade + pstep = max(0, (80.0 - progress.percent) / pstep) + # load face cards + for n in self.cs.getFaceCardNames(): + self._card.append(self.__loadCard(n + self.cs.ext)) + self._card[-1].filename = n + if progress: progress.update(step=pstep) + assert len(self._card) == self.cs.ncards + # load backgrounds + for name in self.cs.backnames: + if name: + try: + im = self.__loadCard(name) + self.__addBack(im, name) + except: + pass + if progress: progress.update(step=1) + # load bottoms + for i in range(self.cs.nbottoms): + try: + name = "bottom%02d.%s" % (i + 1, ext) + self._bottom_positive.append(self.__loadCard(name)) + except: + pass + if progress: progress.update(step=pstep) + # load negative bottoms + try: + name = "bottom%02d-n.%s" % (i + 1, ext) + self._bottom_negative.append(self.__loadCard(name)) + except: + pass + if progress: progress.update(step=pstep) + # load letters + for rank in range(self.cs.nletters): + try: + name = "l%02d.%s" % (rank + 1, ext) + self._letter_positive.append(self.__loadCard(name)) + except: + pass + if progress: progress.update(step=pstep) + # load negative letters + try: + name = "l%02d-n.%s" % (rank + 1, ext) + self._letter_negative.append(self.__loadCard(name)) + except: + pass + if progress: progress.update(step=pstep) + # shadow + for i in range(self.cs.nshadows): + if fast: + self._shadow.append(None) + else: + name = "shadow%02d.%s" % (i, ext) + try: + im = self.__loadCard(name, check_w=0, check_h=0) + except: + im = None + self._shadow.append(im) + if progress: progress.update(step=pstep) + # shade + if fast: + self._shade.append(None) + else: + self._shade.append(self.__loadCard("shade." + ext)) + if progress: progress.update(step=pstep) + # create missing + self._createMissingImages() + # + self._bottom = self._bottom_positive + self._letter = self._letter_positive + return 1 + + def getFace(self, deck, suit, rank): + index = suit * len(self.cs.ranks) + rank + ##print "getFace:", suit, rank, index + return self._card[index % self.cs.ncards] + + def getBack(self, deck, suit, rank): + index = self.cs.backindex % len(self._back) + return self._back[index].image + + def getTalonBottom(self): + return self._bottom[0] + + def getReserveBottom(self): + return self._bottom[0] + + def getSuitBottom(self, suit=-1): + assert type(suit) is types.IntType + if suit == -1: return self._bottom[1] # any suit + i = 3 + suit + if i >= len(self._bottom): + # Trump (for Tarock type games) + return self._bottom[1] + return self._bottom[i] + + def getBraidBottom(self): + return self._bottom[2] + + def getLetter(self, rank): + assert 0 <= rank <= 3 + if rank >= len(self._letter): + return self._bottom[0] + return self._letter[rank] + + def getShadow(self, ncards): + assert ncards >= 0 + if ncards >= len(self._shadow): + ##ncards = len(self._shadow) - 1 + return None + return self._shadow[ncards] + + def getShade(self): + return self._shade[self._shade_index] + + def getCardbacks(self): + return self._back + + def setNegative(self, flag=0): + if flag: + self._bottom = self._bottom_negative + self._letter = self._letter_negative + else: + self._bottom = self._bottom_positive + self._letter = self._letter_positive + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class SubsampledImages(Images): + def __init__(self, images, r=2): + Images.__init__(self, None, images.cs, r=r) + self._card = self._subsample(images._card, r) + self._bottom_positive = self._subsample(images._bottom_positive, r) + self._letter_positive = self._subsample(images._letter_positive, r) + self._bottom_negative = self._subsample(images._bottom_negative, r) + self._letter_negative = self._subsample(images._letter_negative, r) + self._bottom = self._bottom_positive + self._letter = self._letter_positive + # + for _back in images._back: + if _back is None: + self._back.append(None) + else: + im = _back.image.subsample(r) + self._back.append(ImagesCardback(len(self._back), _back.name, im, im)) + # + CW, CH = self.CARDW, self.CARDH + for im in images._shade: + if im is None or tkversion < (8, 3, 0, 0): + self._shade.append(None) + else: + self._shade.append(copyImage(im, 0, 0, CW, CH)) + + def getShadow(self, ncards): + return None + + def _subsample(self, l, r): + s = [] + for im in l: + if im is None or r == 1: + s.append(im) + else: + s.append(im.subsample(r)) + return s + diff --git a/pysollib/layout.py b/pysollib/layout.py new file mode 100644 index 0000000000..85009086c4 --- /dev/null +++ b/pysollib/layout.py @@ -0,0 +1,913 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys + +# PySol imports +from mfxutil import destruct, Struct, SubclassResponsibility +from pysoltk import MfxCanvasText + + +# /*********************************************************************** +# // a helper class to create common layouts +# ************************************************************************/ + +# a layout stack +class _LayoutStack: + def __init__(self, x, y, suit=None): + self.x = int(round(x)) + self.y = int(round(y)) + self.suit = suit + self.text_args = {} + self.text_format = "%d" + + def setText(self, x, y, anchor="center", format=None, **kw): + self.text_args["x"] = x + self.text_args["y"] = y + self.text_args["anchor"] = anchor + self.text_args.update(kw) + if format is not None: + self.text_format = format + + +class Layout: + def __init__(self, game, XM=10, YM=10, **kw): + self.game = game + self.canvas = self.game.canvas + self.size = None + self.s = Struct( + talon = None, + waste = None, + foundations = [], + rows = [], + reserves = [], + ) + self.stackmap = {} + self.regions = [] + # set visual constants + images = self.game.app.images + self.CW = images.CARDW + self.CH = images.CARDH + self.XM = XM # XMARGIN + self.YM = YM # YMARGIN + self.XS = self.CW + XM # XSPACE + self.YS = self.CH + YM # YSPACE + self.XOFFSET = images.CARD_XOFFSET + self.YOFFSET = images.CARD_YOFFSET + self.__dict__.update(kw) +## if self.game.preview > 1: +## if kw.has_key("XOFFSET"): +## self.XOFFSET = self.XOFFSET / self.game.preview +## if kw.has_key("YOFFSET"): +## self.YOFFSET = self.YOFFSET / self.game.preview + + def __createStack(self, x, y, suit=None): + stack = _LayoutStack(x, y, suit) + mapkey = (stack.x, stack.y) + #from pprint import pprint + #print mapkey + #pprint(self.stackmap) + assert not self.stackmap.has_key(mapkey) + self.stackmap[mapkey] = stack + return stack + + + # + # public util for use by class Game + # + + def getTextAttr(self, stack, anchor): + x, y = 0, 0 + if stack is not None: + x, y = stack.x, stack.y + if anchor == "n": + return (x + self.CW / 2, y - self.YM, "center", "%d") + if anchor == "nn": + return (x + self.CW / 2, y - self.YM, "s", "%d") + if anchor == "s": + return (x + self.CW / 2, y + self.YS, "center", "%d") + if anchor == "ss": + return (x + self.CW / 2, y + self.YS, "n", "%d") + if anchor == "nw": + return (x - self.XM, y, "ne", "%d") + if anchor == "sw": + return (x - self.XM, y + self.CH, "se", "%d") + f = "%2d" + if self.game.gameinfo.decks > 1: + f = "%3d" + if anchor == "ne": + return (x + self.XS, y, "nw", f) + if anchor == "se": + return (x + self.XS, y + self.CH, "sw", f) + if anchor == "e": + return (x + self.XS, y + self.CH / 2, "w", f) + raise Exception, anchor + + def createText(self, stack, anchor, dx=0, dy=0, text_format=""): + if self.canvas.preview > 1: + return + assert stack.texts.ncards is None + tx, ty, ta, tf = self.getTextAttr(stack, anchor) + stack.texts.ncards = MfxCanvasText(self.canvas, tx+dx, ty+dy, + anchor=ta, + font=self.game.app.getFont("canvas_default")) + stack.texts.ncards.text_format = text_format or tf + + def setRegion(self, stacks, rects): + self.regions.append((stacks, rects)) + + + # + # util for use by a Game + # + + def defaultAll(self): + game = self.game + # create texts + if game.s.talon: + game.s.talon.texts.ncards = self.defaultText(self.s.talon) + if game.s.waste: + game.s.waste.texts.ncards = self.defaultText(self.s.waste) + # define stack-groups + self.defaultStackGroups() + # set regions + self.defaultRegions() + + def defaultText(self, layout_stack): + if self.canvas.preview > 1: + return None + ##print layout_stack, layout_stack.text_args + if layout_stack is None or not layout_stack.text_args: + return None + layout_stack.text_args["font"] = self.game.app.getFont("canvas_default") + t = apply(MfxCanvasText, (self.game.canvas,), layout_stack.text_args) + t.text_format = layout_stack.text_format + return t + + # define stack-groups + def defaultStackGroups(self): + game = self.game + waste = [] + if game.s.waste is not None: waste = [game.s.waste] + game.sg.talonstacks = [game.s.talon] + waste + game.sg.dropstacks = game.s.rows + game.s.reserves + waste + game.sg.openstacks = game.s.foundations + game.s.rows + game.s.reserves + game.sg.reservestacks = game.s.reserves + + def defaultRegions(self): + for region in self.regions: + # convert layout-stacks to corresponding game-stacks + stacks = [] + for s in region[0]: + mapkey = (s.x, s.y) + id = self.game.stackmap[mapkey] + stacks.append(self.game.allstacks[id]) + ##print stacks, region[1] + self.game.setRegion(stacks, region[1]) + + + # + # Baker's Dozen layout + # - left: 2 rows + # - right: foundations, talon + # + + def bakersDozenLayout(self, rows, texts=0, playcards=9): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + halfrows = (rows + 1) / 2 + + # set size so that at least 9 cards are fully playable + h = YS + min(2*YS, (playcards-1)*self.YOFFSET) + h = max(h, 5*YS/2, 3*YS/2+CH) + h = min(h, 3*YS) + + # create rows + x, y = XM, YM + for i in range(halfrows): + self.s.rows.append(S(x+i*XS, y)) + for i in range(rows-halfrows): + self.s.rows.append(S(x+i*XS, y+h)) + + # create foundations + x, y = XM + halfrows * XS, YM + self.setRegion(self.s.rows, (-999, -999, x - CW / 2, 999999)) + for suit in range(suits): + for i in range(decks): + self.s.foundations.append(S(x+i*XS, y, suit=suit)) + y = y + YS + + # create talon + h = YM + 2*h + self.s.talon = s = S(x, h - YS) + if texts: + assert 0 + + # set window + self.size = (XM + (halfrows+decks)*XS, h) + + + # + # FreeCell layout + # - top: free cells, foundations + # - below: rows + # - left bottom: talon + # + + def freeCellLayout(self, rows, reserves, texts=0, playcards=18): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + toprows = reserves + 1 + suits*decks + maxrows = max(rows, toprows) + + w = XM + maxrows*XS + + # set size so that at least 2/3 of a card is visible with 18 cards + h = CH*2/3 + (playcards-1)*self.YOFFSET + h = YM + YS + max(h, 3*YS) + + # create reserves & foundations + x, y = (w - (toprows*XS - XM))/2, YM + for i in range(reserves): + self.s.reserves.append(S(x, y)) + x = x + XS + for suit in range(suits): + for i in range(decks): + x = x + XS + self.s.foundations.append(S(x, y, suit=suit)) + + # create rows + x, y = (w - (rows*XS - XM))/2, YM + YS + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + self.setRegion(self.s.rows, (-999, y - YM / 2, 999999, 999999)) + + # create talon + x, y = XM, h - YS + self.s.talon = s = S(x, y) + if texts: + # place text right of stack + s.setText(x + XS, y + CH, anchor="sw", format="%3d") + + # set window + self.size = (w, h) + + + # + # Gypsy layout + # - left: rows + # - right: foundations, talon + # + + def gypsyLayout(self, rows, waste=0, texts=1, playcards=25): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + + # set size so that at least 2/3 of a card is visible with 25 cards + h = CH*2/3 + (playcards-1)*self.YOFFSET + h = YM + max(h, (suits+1)*YS) + + # create rows + x, y = XM, YM + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + self.setRegion(self.s.rows, (-999, -999, x - CW / 2, 999999)) + + # create foundations + for suit in range(suits): + for i in range(decks): + self.s.foundations.append(S(x+i*XS, y, suit=suit)) + y = y + YS + + # create talon and waste + x, y = x + (decks-1)*XS, h - YS + if texts: + x = x - XS/2 + self.s.talon = s = S(x, y) + if texts: + # place text right of stack + s.setText(x + XS, y + CH, anchor="sw", format="%3d") + if waste: + x = x - XS + self.s.waste = s = S(x, y) + if texts: + # place text left of stack + s.setText(x - XM, y + CH, anchor="se", format="%3d") + + # set window + self.size = (XM + (rows+decks)*XS, h) + + + # + # Harp layout + # - top: rows + # - bottom: foundations, waste, talon + # + + def harpLayout(self, rows, waste, texts=1, playcards=19): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + + w = max(rows*XS, (suits*decks+waste+1)*XS, (suits*decks+1)*XS+2*XM) + w = XM + w + + # set size so that at least 19 cards are fully playable + h = YS + (playcards-1)*self.YOFFSET + h = max(h, 3*YS) + + # top + x, y = (w - (rows*XS - XM))/2, YM + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + + # bottom + x, y = XM, YM + h + self.setRegion(self.s.rows, (-999, -999, 999999, y - YS / 2)) + for suit in range(suits): + for i in range(decks): + self.s.foundations.append(S(x, y, suit=suit)) + x = x + XS + if waste: + x = w - 2*XS + self.s.waste = s = S(x, y) + if texts: + # place text above stack + s.setText(x + CW / 2, y - YM, anchor="s") + x = w - XS + self.s.talon = s = S(x, y) + if texts: + # place text above stack + s.setText(x + CW / 2, y - YM, anchor="s") + + # set window + self.size = (w, YM + h + YS) + + + # + # Klondike layout + # - top: talon, waste, foundations + # - bottom: rows + # + + def klondikeLayout(self, rows, waste, texts=1, playcards=16, center=1): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + foundrows = 1 + (suits > 5) + frows = decks * suits / foundrows + toprows = 1 + waste + frows + maxrows = max(rows, toprows) + + # set size so that at least 2/3 of a card is visible with 16 cards + h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h = max(h, 2 * YS) + + # top + x, y = XM, YM + self.s.talon = s = S(x, y) + if texts: + if waste or not center or maxrows - frows <= 1: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="n") + else: + # place text right of stack + s.setText(x + XS, y, anchor="nw", format="%3d") + if waste: + x = x + XS + self.s.waste = s = S(x, y) + if texts: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="n") + + for row in range(foundrows): + x = XM + (maxrows - frows) * XS + if center and frows + 2 * (1 + waste + 1) <= maxrows: + # center the foundations + x = XM + (maxrows - frows) * XS / 2 + for suit in range(suits / foundrows): + for i in range(decks): + self.s.foundations.append(S(x, y, suit=suit + (row * (suits / 2)))) + x = x + XS + y = y + YS + + # bottom + x = XM + if rows < maxrows: x += (maxrows-rows) * XS/2 + y += YM * (3 - foundrows) + self.setRegion(self.s.rows, (-999, y - YM / 2, 999999, 999999)) + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + + # set window + self.size = (XM + maxrows * XS, h + YM + YS * foundrows) + + + # + # Yukon layout + # - left: rows + # - right: foundations + # - left bottom: talon + # + + def yukonLayout(self, rows, texts=0, playcards=20): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + + # set size so that at least 2/3 of a card is visible with 20 cards + h = CH*2/3 + (playcards-1)*self.YOFFSET + h = YM + max(h, suits*YS) + + # create rows + x, y = XM, YM + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + self.setRegion(self.s.rows, (-999, -999, x - CW / 2, 999999)) + + # create foundations + for suit in range(suits): + for i in range(decks): + self.s.foundations.append(S(x+i*XS, y, suit=suit)) + y = y + YS + + # create talon + x, y = XM, h - YS + self.s.talon = s = S(x, y) + if texts: + # place text right of stack + s.setText(x + XS, y + CH, anchor="sw", format="%3d") + + # set window + self.size = (XM + (rows+decks)*XS, h) + + + # + # Easy layout + # - top: talon, waste, foundations + # - bottom: rows + # + + def easyLayout(self, rows, waste, texts=1, playcards=10, center=1): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + ranks = len(self.game.gameinfo.ranks) + frows = 4 * decks / (1 + (decks >= 3)) + toprows = 1 + waste + frows + maxrows = max(rows, toprows) + yextra = 0 + + # set size so that at least 2/3 of a card is visible with 10 cards + h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h = max(h, 2 * YS) + + # top + x, y = XM, YM + self.s.talon = s = S(x, y) + if texts: + if waste or not center or maxrows - frows <= 1: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="n") + yextra = 20 + else: + # place text right of stack + s.setText(x + XS, y, anchor="nw", format="%3d") + if waste: + x = x + XS + self.s.waste = s = S(x, y) + if texts: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="n") + x = XM + (maxrows - frows) * XS + if center and frows + 2 * (1 + waste + 1) <= maxrows: + # center the foundations + x = XM + (maxrows - frows) * XS / 2 + + x0, y0 = x, y + for i in range(decks): + for rank in range(ranks): + self.s.foundations.append(S(x0, y0, suit=rank)) + x0 = x0 + XS + if i == 1 and decks > 2: + x0, y0 = x, y + YS + y = y0 + + # bottom + x, y = XM, y + YS + yextra * (decks <= 2) + self.setRegion(self.s.rows, (-999, y - YM / 2, 999999, 999999)) + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + + # set window + self.size = (XM + maxrows * XS, YM + YS + yextra + h) + + + # + # Samuri layout + # - top center: rows + # - left & right: foundations + # - bottom center: talon + # + + def samuriLayout(self, rows, waste, texts=1, playcards=20, center=1): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + toprows = 2 * decks + rows + yextra = 0 + + # set size so that at least 2/3 of a card is visible with 20 cards + h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h = max(h, 2 * YS) + + # bottom center + x = (XM + (toprows * XS) / 2) - XS + y = h + self.s.talon = s = S(x, y) + if texts: + if waste or not center or toprows - rows <= 1: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="n") + yextra = 20 + else: + # place text right of stack + s.setText(x + XS, y, anchor="nw", format="%3d") + if waste: + x = x + XS + self.s.waste = s = S(x, y) + if texts: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="n") + + # left & right + x, y = XM, YM + d, x0, y0 = 0, x, y + for suit in range(12): + for i in range(decks): + x0, y0 = x + XS * i, y + YS * d + self.s.foundations.append(S(x0, y0, suit=suit)) + if i == decks - 1 and suit == 5: + x0, y0 = x + XS * (toprows - decks), YM + d, x, y = -1, x0, y0 + d = d + 1 + + # top center + x, y = XM + XS * decks, YM + self.setRegion(self.s.rows, (x - XM / 2, 0, x + XS * rows, 999999)) + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + + # set window + self.size = (XM + toprows * XS, YM + YS + yextra + h) + + + # + # Sumo layout + # - top center: rows + # - left & right: foundations + # - bottom center: talon + # + + def sumoLayout(self, rows, reserves, texts=0, playcards=12, center=1): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + assert reserves % 2 == 0 + toprows = 12 + maxrows = max(rows, toprows) + w = XM + maxrows * XS + + # set size so that at least 2/3 of a card is visible with 12 cards + h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h = max(h, 2 * YS) + + # create foundations + x, y = XM, YM + for i in range(decks): + for suit in range(12): + self.s.foundations.append(S(x, y, suit=suit)) + x = x + XS + x, y = XM, y + YS + + # create rows + x, y = XM + XS * ((toprows - rows) / 2), YM + YS * decks + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + self.setRegion(self.s.rows, (XS + XM / 2, YS * decks + YM / 2, XS * 11 - XM / 2, 999999)) + + # create reserves + x, y = XM, YM + YS * decks + for i in range(reserves / 2): + self.s.reserves.append(S(x, y)) + y = y + YS + x, y = w - XS, YM + YS * decks + for i in range(reserves / 2): + self.s.reserves.append(S(x, y)) + y = y + YS + + # create talon + x, y = XM, h + YM + self.s.talon = s = S(x, y) + if texts: + # place text right of stack + s.setText(x + XS, y + CH, anchor="sw", format="%3d") + + # set window + self.size = (XM + toprows * XS, YM + YS + h) + + + # + # Fun layout + # - top: rows + # - right: foundations + # - bottom right: reserves + # + + def funLayout(self, rows, reserves, texts=0, playcards=12, center=1): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + ranks = len(self.game.gameinfo.ranks) + assert rows % 2 == 0 + assert reserves % decks == 0 + toprows = decks + rows / 2 + w = XM * 2 + toprows * XS + + # set size so that at least 2/3 of a card is visible with 12 cards + h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h = max(h, 2 * YS) + + # create foundations + x, y = w - XS * decks, YM + for i in range(decks): + for rank in range(ranks): + self.s.foundations.append(S(x, y, suit=rank)) + y = y + YS + x, y = x + XS, YM + + # create rows + x, y = XM, YM + for i in range(rows / 2): + self.s.rows.append(S(x, y)) + x = x + XS + x, y = XM, (YS + h) / 2 + for i in range(rows / 2): + self.s.rows.append(S(x, y)) + x = x + XS + self.setRegion(self.s.rows, (0, 0, XS * rows / 2 + XM / 2, 999999)) + + # create reserves + x, y = w - XS * decks, YM + YS * 4 + for i in range(decks): + for i in range(reserves / decks): + self.s.reserves.append(S(x, y)) + y = y + YS + x, y = x + XS, YM + YS * 4 + + # create talon + x, y = XM, h + self.s.talon = s = S(x, y) + if texts: + # place text right of stack + s.setText(x + XS, y + CH, anchor="sw", format="%3d") + + # set window + self.size = (w, YM + YS + h) + + + # + # Oonsoo layout + # - top: talon & rows + # - left: reserves + # - center right: rows + # + + def oonsooLayout(self, rows, reserves, texts=0, playcards=12, center=1): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + assert rows % 2 == 0 + toprows = decks + rows / 2 + w = XM * 2 + toprows * (XS + XM) + + # set size so that at least 2/3 of a card is visible with 12 cards + h = CH * 2 / 3 + (playcards - 1) * self.YOFFSET + h = max(h, 2 * YS) + + # create talon + x, y = XM, YM + self.s.talon = s = S(x, y) + if texts: + # place text below stack + s.setText(x + CW / 2, y + YS, anchor="center", format="%d") + + # create rows + x, y = XS + XM * 3, YM + for i in range(rows / 2): + self.s.rows.append(S(x, y)) + x = x + XS + XM + x, y = XS + XM * 3, (YS + h) / 2 + for i in range(rows / 2): + self.s.rows.append(S(x, y)) + x = x + XS + XM + self.setRegion(self.s.rows, (XS + XM, -999, 999999, 999999)) + + # create reserves + x, y = XM, YM * 3 + YS + for i in range(decks): + for i in range(reserves / decks): + self.s.reserves.append(S(x, y)) + y = y + YS + x, y = x + XS, YM + YS * 4 + + # set window + self.size = (w, YM + YS + h) + + + # + # Ghulam layout + # - left & right: foundations & reserves + # - center: two groups of rows + # - lower right: talon + # + + def ghulamLayout(self, rows, reserves=0, texts=0): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + assert rows % 2 == 0 + assert reserves % 2 == 0 + + # set size + w, h = XM * 3 + XS * ((rows / 2) + 2), YM + YS * ((suits / 2) + 2) + + # create foundations + x, y = XM, YM + for i in range(suits): + self.s.foundations.append(S(x, y, suit=i)) + y = y + YS + if i == suits / 2 - 1: + x, y = w - XS, YM + + # create rows + x = XM * 2 + XS + for i in range(rows / 2): + self.s.rows.append(S(x + i * XS, YM)) + for i in range(rows / 2): + self.s.rows.append(S(x + i * XS, h / 2)) + self.setRegion(self.s.rows, (XM + XS, -999, w - XM - XS, 999999)) + + # create reserves + for i in range(reserves / 2): + self.s.reserves.append(S(XM, h - YS * (i + 1))) + for i in range(reserves / 2): + self.s.reserves.append(S(w - XS, h - YS * (i + 1))) + + # create talon + self.s.talon = s = S(w - XS * 2, h - YS) + if texts: + assert 0 + + # set window + self.size = (w, h) + + + # + # Generiklon layout + # - top: talon & foundations + # - bottom: rows + # + + def generiklonLayout(self, rows, waste = 1, height = 6): + S = self.__createStack + CW, CH = self.CW, self.CH + XM, YM = self.XM, self.YM + XS, YS = self.XS, self.YS + + decks = self.game.gameinfo.decks + suits = len(self.game.gameinfo.suits) + bool(self.game.gameinfo.trumps) + frows = suits * decks / 2 + fspace = XS * (rows - 1) / 2 + + # Set window size + w, h = XM + XS * rows, YM * 2 + YS * height + self.size = (w, h) + + # Talon + x, y = XM, YM + self.s.talon = s = S(x, y) + s.setText(x + XS, y + CH, anchor = "sw", format = "%3d") + self.s.waste = s = S(x, y + YS) + s.setText(x + XS, y + YS + CH, anchor = "sw", format = "%3d") + + # Create foundations + x = w - fspace - XS * frows / 2 + for suit in range(suits / 2): + for i in range(decks): + self.s.foundations.append(S(x, y, suit = suit)) + x = x + XS + x = w - fspace - XS * frows / 2 + y = y + YS + for suit in range(suits / 2): + for i in range(decks): + self.s.foundations.append(S(x, y, suit = suit + suits / 2)) + x = x + XS + + # bottom + x, y = XM, YM * 2 + YS * 2 + for i in range(rows): + self.s.rows.append(S(x, y)) + x = x + XS + self.setRegion(self.s.rows, (-999, y - YM, 999999, 999999)) + diff --git a/pysollib/main.py b/pysollib/main.py new file mode 100644 index 0000000000..dbf733e3de --- /dev/null +++ b/pysollib/main.py @@ -0,0 +1,536 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, os, re, string, time, types +import traceback +import getopt +import gettext + +# PySol imports +from mfxutil import destruct, EnvError +from util import CARDSET, DataLoader +from version import VERSION +from settings import PACKAGE +from resource import Tile +from gamedb import GI +from app import Application +from pysolaudio import thread, pysolsoundserver +from pysolaudio import AbstractAudioClient, PysolSoundServerModuleClient, Win32AudioClient + +# Toolkit imports +from pysoltk import tkname, tkversion, wm_withdraw, wm_set_icon, loadImage +from pysoltk import MfxDialog, MfxExceptionDialog +from pysoltk import TclError, MfxRoot +from pysoltk import PysolProgressBar + +from tkFont import Font + +# /*********************************************************************** +# // +# ************************************************************************/ + +def fatal_no_cardsets(app): + app.wm_withdraw() + d = MfxDialog(app.top, title=PACKAGE + _(" installation error"), + text=_('''No %ss were found !!! + +Main data directory is: +%s + +Please check your %s installation. +''') % (CARDSET, app.dataloader.dir, PACKAGE), + bitmap="error", strings=(_("Quit"),)) + ##raise Exception, "no "+CARDSET+"s found !" + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def parse_option(argv): + prog_name = argv[0] + try: + optlist, args = getopt.getopt(argv[1:], "h", + ["fg=", "foreground=", + "bg=", "background=", + "fn=", "font=", + "noplugins", + "nosound", + "help"]) + except getopt.GetoptError, err: + print _("%s: %s\ntry %s --help for more information") \ + % (prog_name, err, prog_name) + return None + opts = {"help": 0, + "fg": None, + "bg": None, + "fn": None, + "noplugins": 0, + "nosound": 0, + } + for i in optlist: + if i[0] in ("-h", "--help"): + opts["help"] = 1 + elif i[0] in ("--fg", "--foreground"): + opts["fg"] = i[1] + elif i[0] in ("--bg", "--background"): + opts["bg"] = i[1] + elif i[0] in ("--fn", "--font"): + opts["fn"] = i[1] + elif i[0] == "--noplugins": + opts["noplugins"] = 1 + elif i[0] == "--nosound": + opts["nosound"] = 1 + + if opts["help"]: + print _("""Usage: %s [OPTIONS] [FILE] + --fg --foreground=COLOR foreground color + --bg --background=COLOR background color + --fn --font=FONT default font + --nosound disable sound support + --noplugins disable load plugins + -h --help display this help and exit + + FILE - file name of a saved game +""") % prog_name + return None + + if len(args) > 1: + print _("%s: too many files\ntry %s --help for more information") % (prog_name, prog_name) + return None + filename = args and args[0] or None + if filename and not os.path.isfile(filename): + print _("%s: invalide file name\ntry %s --help for more information") % (prog_name, prog_name) + return None + return opts, filename + +# /*********************************************************************** +# // +# ************************************************************************/ + +def pysol_init(app, args): + # try to create the config directory + for d in ( + app.dn.config, + app.dn.savegames, + os.path.join(app.dn.config, "music"), + ##os.path.join(app.dn.config, "screenshots"), + os.path.join(app.dn.config, "tiles"), + os.path.join(app.dn.config, "tiles", "stretch"), + os.path.join(app.dn.config, "cardsets"), + os.path.join(app.dn.config, "plugins"), + ): + if not os.path.exists(d): + try: os.mkdir(d) + except: pass + + # init commandline options (undocumented) + opts = parse_option(args) + if not opts: + return 1 + sys.exit(1) + opts, filename = opts + wm_command = "" + prog = sys.executable + if prog and os.path.isfile(prog): + argv0 = os.path.normpath(args[0]) + prog = os.path.abspath(prog) + if os.path.isfile(argv0): + wm_command = prog + " " + os.path.abspath(argv0) + if filename: + app.commandline.loadgame = filename + + # init games database + import games + import games.contrib + import games.special + import games.ultra + import games.mahjongg + + # init DataLoader + f = os.path.join("html", "license.html") + app.dataloader = DataLoader(args[0], f) + + # try to load plugins + if not opts["noplugins"]: + for dir in (os.path.join(app.dataloader.dir, "games"), + os.path.join(app.dataloader.dir, "plugins"), + app.dn.plugins): + try: + app.loadPlugins(dir) + except: + pass + + # init toolkit 1) + top = MfxRoot(className=PACKAGE) + app.top = top + app.top_bg = top.cget("bg") + app.top_palette = [None, None] # [fg, bg] + app.top_cursor = top.cget("cursor") + + # print some debug info + + # load options + app.loadOptions() + + # init audio 1) + warn_thread = 0 + warn_pysolsoundserver = 0 + app.audio = None + if not opts["nosound"]: + if os.name == "nt" and app.opt.sound_mode == 0: + app.audio = Win32AudioClient() + elif pysolsoundserver: + app.audio = PysolSoundServerModuleClient() + elif os.name == "nt": + app.audio = Win32AudioClient() + if app.audio: + app.audio.startServer() + if app.audio.server is None: + if os.name == "nt" and not isinstance(app.audio, Win32AudioClient): + app.audio.destroy() + app.audio = Win32AudioClient() + app.audio.startServer() + else: + app.audio = AbstractAudioClient() + # update sound_mode + if isinstance(app.audio, PysolSoundServerModuleClient): + app.opt.sound_mode = 1 + else: + app.opt.sound_mode = 0 + + # init toolkit 2) + sw, sh, sd = top.winfo_screenwidth(), top.winfo_screenheight(), top.winfo_screendepth() + top.wm_group(top) + top.wm_title(PACKAGE + " " + VERSION) + top.wm_iconname(PACKAGE + " " + VERSION) + if sw < 640 or sh < 480: + top.wm_minsize(400, 300) + else: + top.wm_minsize(520, 360) + ##self.top.wm_maxsize(9999, 9999) # unlimited + top.wm_protocol("WM_DELETE_WINDOW", top.wmDeleteWindow) + if wm_command: + top.wm_command(wm_command) + if 1: + # set expected window size to assist the layout of the window manager + top.config(width=min(800,sw-64), height=min(600,sh-64)) + try: + wm_set_icon(top, app.dataloader.findIcon()) + except: pass + + # set global color scheme + if not opts["fg"] and not opts["bg"]: + if os.name == "posix": # Unix/X11 + top.option_add('*selectBackground', '#00008b', 50) + top.option_add('*selectForeground', 'white', 50) + top.option_add('*Entry.background', 'white', 50) + top.option_add('*Listbox.background', 'white', 50) + if os.name == "mac": + color, priority = "#d9d9d9", "60" + classes = ( + "Button", "Canvas", "Checkbutton", "Entry", + "Frame", "Label", "Listbox", "Menubutton", ### "Menu", + "Message", "Radiobutton", "Scale", "Scrollbar", "Text", + ) + for c in classes: + top.option_add("*" + c + "*background", color, priority) + top.option_add("*" + c + "*activeBackground", color, priority) + else: + bg, fg = opts["bg"], opts["fg"] + if bg: + top.tk_setPalette(bg) + app.top_palette[1] = bg + app.top_bg = bg + if fg: + top.option_add("*foreground", fg) + app.top_palette[0] = fg + + # font + if opts["fn"]: + font = opts["fn"] + top.option_add("*font", font) + elif os.name == 'posix': + top.option_add("*font", "Helvetica 12", 50) + font = top.option_get('font', '') + else: + font = None + try: + f = Font(top, font) + except: + print >> sys.stderr, "invalide font name:", font + pass + else: + if font: + fa = f.actual() + app.opt.fonts["default"] = (fa["family"], + fa["size"], + fa["slant"], + fa["weight"]) + else: + app.opt.fonts["default"] = None + + # check games + if len(app.gdb.getGamesIdSortedByName()) == 0: + app.wm_withdraw() + d = MfxDialog(top, title=PACKAGE + _(" installation error"), + text=_(''' +No games were found !!! + +Main data directory is: +%s + +Please check your %s installation. +''') % (app.dataloader.dir, PACKAGE), + bitmap="error", strings=(_("Quit"),)) + return 1 + + # init cardsets + app.initCardsets() + cardset = None + c = app.opt.cardset.get(0) + if c: + cardset = app.cardset_manager.getByName(c[0]) + if cardset and c[1]: + cardset.updateCardback(backname=c[1]) + if not cardset: + cardset = app.cardset_manager.get(0) + if app.cardset_manager.len() == 0 or not cardset: + fatal_no_cardsets(app) + return 3 + + # init tiles + manager = app.tabletile_manager + tile = Tile() + tile.color = app.opt.table_color + tile.name = "None" + tile.filename = None + manager.register(tile) + app.initTiles() + if app.opt.tabletile_name: ### and top.winfo_screendepth() > 8: + for tile in manager.getAll(): + if app.opt.tabletile_name == tile.basename: + app.tabletile_index = tile.index + break + + # init samples and music resources + app.initSamples() + app.initMusic() + + # init audio 2) + app.audio.connectServer(app) + if app.audio.audiodev is None: + app.opt.sound = 0 + if not opts["nosound"] and pysolsoundserver and not app.audio.connected: + print PACKAGE + ": could not connect to pysolsoundserver, sound disabled." + warn_pysolsoundserver = 1 + app.audio.updateSettings() + # start up the background music + if app.audio.audiodev: + music = app.music_manager.getAll() + if music: + app.music_playlist = list(music)[:] + app.miscrandom.shuffle(app.music_playlist) + if 1: ## and not app.debug: + for m in app.music_playlist: + if m.name.lower() == "bye_for_now": + app.music_playlist.remove(m) + app.music_playlist.insert(0, m) + break + app.audio.playContinuousMusic(app.music_playlist) + + # prepare the progress bar + app.loadImages1() + if not app.progress_images: + app.progress_images = (loadImage(app.gimages.logos[0]), + loadImage(app.gimages.logos[1])) + app.wm_withdraw() + + # warn about audio problems + if not opts["nosound"] and os.name == "posix" and pysolsoundserver is None: + if 1 and app.opt.sound and re.search(r"linux", sys.platform, re.I): + warn_pysolsoundserver = 1 + if thread is None: + warn_thread = 1 + if thread is None: + print PACKAGE + ": Python thread module not found, sound disabled." + else: + print PACKAGE + ": pysolsoundserver module not found, sound disabled." + sys.stdout.flush() + if not opts["nosound"]: + if warn_thread: + top.update() + d = MfxDialog(top, title=PACKAGE + _(" installation problem"), + text=_("Your Python installation is compiled without thread support.\n\nSounds and background music will be disabled."), + bitmap="warning", strings=(_("OK"),)) + elif warn_pysolsoundserver: + top.update() + d = MfxDialog(top, title=PACKAGE + _(" installation problem"), + text=_("The pysolsoundserver module was not found.\n\nSounds and background music will be disabled."), + bitmap="warning", strings=(_("OK"),)) + + # create the progress bar + title = _("Welcome to ") + PACKAGE + color = app.opt.table_color + if app.tabletile_index > 0: + color = "#008200" + app.intro.progress = PysolProgressBar(app, top, title=title, color=color, + images=app.progress_images) + + # prepare other images + app.loadImages2() + app.loadImages3() + app.loadImages4() + + # load cardset + progress = app.intro.progress + if not app.loadCardset(cardset, progress=progress, update=1): + for cardset in app.cardset_manager.getAll(): + progress.reset() + if app.loadCardset(cardset, progress=progress, update=1): + break + else: + fatal_no_cardsets(app) + return 3 + + # ok + return 0 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def pysol_exit(app): + # clean up + if app.audio is not None: + app.audio.destroy() # shut down audio + destruct(app.audio) + app.wm_withdraw() + if app.canvas is not None: + app.canvas.destroy() + destruct(app.canvas) + if app.toolbar is not None: + app.toolbar.destroy() + destruct(app.toolbar) + if app.menubar is not None: + destruct(app.menubar) + top = app.top + destruct(app) + app = None + if top is not None: + try: + top.destroy() + except: + pass + destruct(top) + + +# /*********************************************************************** +# // PySol main entry +# ************************************************************************/ + +def pysol_main(args): + # create the application + app = Application() + r = pysol_init(app, args) + if r != 0: + return r + # let's go - enter the mainloop + app.mainloop() +## try: +## r = pysol_init(app, args) +## if r != 0: +## return r +## # let's go - enter the mainloop +## app.mainloop() +## except KeyboardInterrupt, ex: +## print "Exiting on SIGINT." +## pass +## except StandardError, ex: +## if not app.top: +## raise +## t = str(ex.__class__) +## if str(ex): t = t + ":\n" + str(ex) +## d = MfxDialog(app.top, title=PACKAGE + " internal error", +## text="Internal errror. Please report this bug:\n\n"+t, +## strings=("Quit",), bitmap="error") + try: + pysol_exit(app) + except: + pass + return 0 + + +# /*********************************************************************** +# // main +# ************************************************************************/ + +def main(args=None): + + # setup (mainly for JPython) + if not hasattr(sys, "platform"): + sys.platform = "unknown" + if not hasattr(sys, "executable"): + sys.executable = None + if not hasattr(os, "defpath"): + os.defpath = "" + + # check versions + if sys.platform[:4] != "java": + if sys.version[:5] < "1.5.2": + print "%s needs Python 1.5.2 or better (you have %s)" % (PACKAGE, sys.version) + return 1 + assert len(tkversion) == 4 + if tkname == "tk": + import Tkinter + if tkversion < (8, 0, 0, 0): + print "%s needs Tcl/Tk 8.0 or better (you have %s)" % (PACKAGE, str(tkversion)) + return 1 + # check that Tkinter bindings are also at version 1.5.2 + if not hasattr(Tkinter.Wm, "wm_aspect") or not hasattr(Tkinter.Canvas, "tag_lower"): + print "%s: please update the Python-Tk bindings (aka Tkinter) to version 1.5.2 or better" % (PACKAGE,) + return 1 + # check Python + if -1 % 13 != 12: + raise Exception, "-1 % 13 != 12" + + # run it + r = pysol_main(args) + ##print "FINAL\n"; dumpmem() + return r + diff --git a/pysollib/mfxutil.py b/pysollib/mfxutil.py new file mode 100644 index 0000000000..412cbca3c9 --- /dev/null +++ b/pysollib/mfxutil.py @@ -0,0 +1,437 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, os, time, types +#import traceback + +try: + from cPickle import Pickler, Unpickler, UnpicklingError +except ImportError: + from pickle import Pickler, Unpickler, UnpicklingError + +try: + import thread +except: + thread = None + +if os.name == "mac": + # macfs module is deprecated, consider using Carbon.File or Carbon.Folder + import macfs, MACFS + +win32api = shell = shellcon = None +if sys.platform.startswith('win'): + try: + import win32api + #from win32com.shell import shell, shellcon + except ImportError: + pass + +# /*********************************************************************** +# // exceptions +# ************************************************************************/ + +# work around a Mac problem +##EnvError = EnvironmentError +EnvError = (IOError, OSError, os.error,) + + +class SubclassResponsibility(Exception): + pass + + +# /*********************************************************************** +# // misc. util +# ************************************************************************/ + +## def static(f, *args, **kw): +## if args: +## a = tuple([f.im_class()] + list(args)) +## else: +## a = (f.im_class(),) +## return apply(f, a, kw) + + +## def ifelse(expr, val1, val2): +## if expr: +## return val1 +## return val2 + + +## def merge_dict(dict1, dict2, merge_none=1): +## for k, v in dict2.items(): +## if dict1.has_key(k): +## if type(dict1[k]) is type(v): +## dict1[k] = v +## elif dict2[k] is None and merge_none: +## dict1[k] = v + + +# this is a quick hack - we definitely need Unicode support... +def latin1_to_ascii(n): + #return n + n = n.encode('iso8859-1', 'replace') + ## FIXME: rewrite this for better speed + n = (n.replace("\xc4", "Ae") + .replace("\xd6", "Oe") + .replace("\xdc", "Ue") + .replace("\xe4", "ae") + .replace("\xf6", "oe") + .replace("\xfc", "ue")) + return n + +## import htmlentitydefs +## htmlentitydefs_i = {} +## def latin1_to_html(n): +## global htmlentitydefs_i +## if not htmlentitydefs_i: +## for k, v in htmlentitydefs.entitydefs.items(): +## htmlentitydefs_i[v] = "&" + k + ";" +## s, g = "", htmlentitydefs_i.get +## for c in n: +## s = s + g(c, c) +## return s + + +## def hexify(s): +## return "%02x"*len(s) % tuple(map(ord, s)) + + +def format_time(t): + ##print 'format_time:', t + if t <= 0: return "0:00" + if t < 3600: return "%d:%02d" % (t / 60, t % 60) + return "%d:%02d:%02d" % (t / 3600, (t % 3600) / 60, t % 60) + + +# /*********************************************************************** +# // misc. portab stuff +# ************************************************************************/ + +def getusername(): + if os.name == "nt": + return win32_getusername() + user = os.environ.get("USER","").strip() + if not user: + user = os.environ.get("LOGNAME","").strip() + return user + + +def gethomedir(): + if os.name == "nt": + return win32_gethomedir() + home = os.environ.get("HOME", "").strip() + if not home or not os.path.isdir(home): + home = os.curdir + return os.path.abspath(home) + + +def getprefdir(package, home=None): + if os.name == "nt": + return win32_getprefdir(package) + if os.name == "mac": + vrefnum, dirid = macfs.FindFolder(MACFS.kOnSystemDisk, MACFS.kPreferencesFolderType, 0) + fss = macfs.FSSpec((vrefnum, dirid, ":" + "PySolFC")) + return fss.as_pathname() + if home is None: + home = gethomedir() + return os.path.join(home, ".PySolFC") + + +# high resolution clock() and sleep() +uclock = time.clock +usleep = time.sleep +if os.name == "posix": + uclock = time.time + +# /*********************************************************************** +# // MSWin util +# ************************************************************************/ + +def win32_getusername(): + user = os.environ.get('USERNAME','').strip() + try: + user = win32api.GetUserName().strip() + except AttributeError: + pass + return user + +def win32_getprefdir(package): + hd = win32_gethomedir() + return os.path.join(hd, 'PySolFC') +## dname = os.path.expanduser("~\\.pysol") +## if not os.path.isdir(dname): +## try: +## dname = os.path.join( +## shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0), +## package, package) +## except AttributeError: +## pass +## return os.path.abspath(dname) + +def win32_gethomedir(): + # %USERPROFILE%, %APPDATA% + hd = os.path.expanduser('~') + if hd == '~': # win9x + return os.path.abspath('/') + return hd + +# /*********************************************************************** +# // memory util +# ************************************************************************/ + +def destruct(obj): + # assist in breaking circular references + if obj is not None: + assert type(obj) is types.InstanceType + for k in obj.__dict__.keys(): + obj.__dict__[k] = None + ##del obj.__dict__[k] + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Struct: + def __init__(self, **kw): + self.__dict__.update(kw) + + def __str__(self): + return str(self.__dict__) + + def __setattr__(self, key, value): + if not self.__dict__.has_key(key): + raise AttributeError, key + self.__dict__[key] = value + + def addattr(self, **kw): + for key in kw.keys(): + if hasattr(self, key): + raise AttributeError, key + self.__dict__.update(kw) + + def update(self, dict): + for key in dict.keys(): + if not self.__dict__.has_key(key): + raise AttributeError, key + self.__dict__.update(dict) + + def clear(self): + for key in self.__dict__.keys(): + t = type(key) + if t is types.ListType: + self.__dict__[key] = [] + elif t is types.TupleType: + self.__dict__[key] = () + elif t is types.DictType: + self.__dict__[key] = {} + else: + self.__dict__[key] = None + + def copy(self): + c = Struct() + c.__class__ = self.__class__ + c.__dict__.update(self.__dict__) + return c + + +# /*********************************************************************** +# // keyword argument util +# ************************************************************************/ + +# update keyword arguments with default arguments +def kwdefault(kw, **defaults): + for k, v in defaults.items(): + if not kw.has_key(k): + kw[k] = v + + +class KwStruct: + def __init__(self, kw={}, **defaults): + if isinstance(kw, KwStruct): + kw = kw.__dict__ + if isinstance(defaults, KwStruct): + defaults = defaults.__dict__ + if defaults: + kw = kw.copy() + for k, v in defaults.items(): + if not kw.has_key(k): + kw[k] = v + self.__dict__.update(kw) + + def __setattr__(self, key, value): + if not self.__dict__.has_key(key): + raise AttributeError, key + self.__dict__[key] = value + + def __getitem__(self, key): + return getattr(self, key) + + def get(self, key, default=None): + return self.__dict__.get(key, default) + + def getKw(self): + return self.__dict__ + + +# /*********************************************************************** +# // pickling support +# ************************************************************************/ + +def pickle(obj, filename, binmode=0): + f = None + try: + f = open(filename, "wb") + p = Pickler(f, binmode) + p.dump(obj) + f.close(); f = None + ##print "Pickled", filename + finally: + if f: f.close() + + +def unpickle(filename): + f, obj = None, None + try: + f = open(filename, "rb") + p = Unpickler(f) + x = p.load() + f.close(); f = None + obj = x + ##print "Unpickled", filename + finally: + if f: f.close() + return obj + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def spawnv(file, args=()): + if not args: + args = () + args = (file,) + tuple(args) + # + if not os.path.isfile(file): + raise os.error, str(file) + mode = os.stat(file)[0] + if not (mode & 0100): + return 0 + # + if os.name == "posix": + pid = os.fork() + if pid == -1: + raise os.error, "fork failed" + if pid != 0: + # parent + try: + os.waitpid(pid, 0) + except: + pass + return 1 + # child + # 1) close all files + for fd in range(255, -1, -1): + try: + os.close(fd) + except: + pass + # 2) open stdin, stdout and stderr to /dev/null + try: + fd = os.open("/dev/null", os.O_RDWR) + os.dup(fd) + os.dup(fd) + except: + pass + # 3) fork again and exec program + try: + if os.fork() == 0: + try: + os.setpgrp() + except: + pass + os.execv(file, args) + except: + pass + # 4) exit + while 1: + os._exit(0) + return 0 + + +def spawnvp(file, args=()): + if file and os.path.isabs(file): + try: + if spawnv(file, args): + return file + except: + ##if traceback: traceback.print_exc() + pass + return None + # + path = os.environ.get("PATH", "") + path = path.split(os.pathsep) + for dir in path: + try: + if dir and os.path.isdir(dir): + f = os.path.join(dir, file) + try: + if spawnv(f, args): + return f + except: + ##if traceback: traceback.print_exc() + pass + except: + ##if traceback: traceback.print_exc() + pass + return None + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def openURL(url): + try: + import webbrowser + webbrowser.open(url) + return 1 + except: + return 0 + + diff --git a/pysollib/move.py b/pysollib/move.py new file mode 100644 index 0000000000..87afb8dbd0 --- /dev/null +++ b/pysollib/move.py @@ -0,0 +1,436 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys + + +# /*********************************************************************** +# // moves (undo / redo) +# ************************************************************************/ + +## Currently we have the following atomic moves: +## - move the top cards from one stack on the top of another +## - flip the top card of a stack +## - turn a whole stack onto another stack +## - update the model or complete view a stack +## - increase the round (the number of redeals) +## - save the seed of game.random +## - shuffle a stack + +class AtomicMove: + + def do(self, game): + self.redo(game) + + def __repr__(self): + return str(self.__dict__) + def __str__(self): + return str(self.__dict__) + + # Custom comparision for detecting redo moves. See Game.finishMove(). + def cmpForRedo(self, other): + return -1 + + +# /*********************************************************************** +# // Move the top N cards from a stack to another stack. +# ************************************************************************/ + +class AMoveMove(AtomicMove): + def __init__(self, ncards, from_stack, to_stack, frames, shadow=-1): + assert from_stack is not to_stack + self.ncards = ncards + self.from_stack_id = from_stack.id + self.to_stack_id = to_stack.id + self.frames = frames + self.shadow = shadow + + # do the actual move + def __doMove(self, game, ncards, from_stack, to_stack): + if game.moves.state == game.S_PLAY: + assert to_stack.acceptsCards(from_stack, from_stack.cards[-ncards:]) + cards = [] + for i in range(ncards): + card = from_stack.removeCard() + cards.append(card) + cards.reverse() + if self.frames != 0: + x, y = to_stack.getPositionFor(cards[0]) + game.animatedMoveTo(from_stack, to_stack, cards, x, y, frames=self.frames, shadow=self.shadow) + for c in cards: + to_stack.addCard(c) + + def redo(self, game): + self.__doMove(game, self.ncards, game.allstacks[self.from_stack_id], + game.allstacks[self.to_stack_id]) + + def undo(self, game): + self.__doMove(game, self.ncards, game.allstacks[self.to_stack_id], + game.allstacks[self.from_stack_id]) + + def cmpForRedo(self, other): + return (cmp(self.ncards, other.ncards) or + cmp(self.from_stack_id, other.from_stack_id) or + cmp(self.to_stack_id, other.to_stack_id)) + + +# /*********************************************************************** +# // Flip the top card of a stack. +# ************************************************************************/ + +class AFlipMove(AtomicMove): + def __init__(self, stack): + self.stack_id = stack.id + + # do the actual move + def __doMove(self, game, stack): + card = stack.cards[-1] + if card.face_up: + card.showBack() + else: + card.showFace() + + def redo(self, game): + self.__doMove(game, game.allstacks[self.stack_id]) + + def undo(self, game): + self.__doMove(game, game.allstacks[self.stack_id]) + + def cmpForRedo(self, other): + return cmp(self.stack_id, other.stack_id) + + +# /*********************************************************************** +# // Flip all cards +# ************************************************************************/ + +class AFlipAllMove(AtomicMove): + def __init__(self, stack): + self.stack_id = stack.id + + # do the actual move + def __doMove(self, game, stack): + #card = stack.cards[-1] + for card in stack.cards: + if card.face_up: + card.showBack() + else: + card.showFace() + + def redo(self, game): + self.__doMove(game, game.allstacks[self.stack_id]) + + def undo(self, game): + self.__doMove(game, game.allstacks[self.stack_id]) + + def cmpForRedo(self, other): + return cmp(self.stack_id, other.stack_id) + + +# /*********************************************************************** +# // Turn the Waste stack onto the empty Talon. +# ************************************************************************/ + +class ATurnStackMove(AtomicMove): + def __init__(self, from_stack, to_stack, update_flags=1): + assert from_stack is not to_stack + self.from_stack_id = from_stack.id + self.to_stack_id = to_stack.id + self.update_flags = update_flags + + def redo(self, game): + from_stack = game.allstacks[self.from_stack_id] + to_stack = game.allstacks[self.to_stack_id] + assert len(from_stack.cards) > 0 + assert len(to_stack.cards) == 0 + l = len(from_stack.cards) + for i in range(l): + ##unhide = (i >= l - 2) + unhide = 1 + ##print 1, unhide, from_stack.getCard().__dict__ + card = from_stack.removeCard(unhide=unhide, update=0) + ##print 2, unhide, card.__dict__ + assert card.face_up + to_stack.addCard(card, unhide=unhide, update=0) + card.showBack(unhide=unhide) + ##print 3, unhide, to_stack.getCard().__dict__ + if self.update_flags & 2: + ### not used yet + assert 0 + from_stack.round = from_stack.round + 1 + if self.update_flags & 1: + assert to_stack is game.s.talon + assert to_stack.round < to_stack.max_rounds or to_stack.max_rounds < 0 + to_stack.round = to_stack.round + 1 + from_stack.updateText() + to_stack.updateText() + + def undo(self, game): + from_stack = game.allstacks[self.to_stack_id] + to_stack = game.allstacks[self.from_stack_id] + assert len(from_stack.cards) > 0 + assert len(to_stack.cards) == 0 + l = len(from_stack.cards) + for i in range(l): + ##unhide = (i >= l - 2) + unhide = 1 + card = from_stack.removeCard(unhide=unhide, update=0) + assert not card.face_up + card.showFace(unhide=unhide) + to_stack.addCard(card, unhide=unhide, update=0) + if self.update_flags & 2: + ### not used yet + assert 0 + assert to_stack.round > 1 + to_stack.round = to_stack.round - 1 + if self.update_flags & 1: + assert from_stack is game.s.talon + assert from_stack.round > 1 + from_stack.round = from_stack.round - 1 + from_stack.updateText() + to_stack.updateText() + + def cmpForRedo(self, other): + return (cmp(self.from_stack_id, other.from_stack_id) or + cmp(self.to_stack_id, other.to_stack_id) or + cmp(self.update_flags, other.update_flags)) + + +# /*********************************************************************** +# // ATurnStackMove is somewhat optimized to avoid unnecessary +# // unhide and hide operations. +# // FIXME: doesn't work yet +# ************************************************************************/ + +class NEW_ATurnStackMove(AtomicMove): + def __init__(self, from_stack, to_stack, update_flags=1): + assert from_stack is not to_stack + self.from_stack_id = from_stack.id + self.to_stack_id = to_stack.id + self.update_flags = update_flags + + # do the actual turning move + def __doMove(self, from_stack, to_stack, show_face): + assert len(from_stack.cards) > 0 + assert len(to_stack.cards) == 0 + for card in from_stack.cards: + card.item.dtag(from_stack.group) + card.item.addtag(to_stack.group) + if show_face: + assert not card.face_up + card.showFace(unhide=0) + else: + assert card.face_up + card.showBack(unhide=0) + to_stack.cards = from_stack.cards + from_stack.cards = [] + from_stack.refreshView() + from_stack.updateText() + to_stack.refreshView() + to_stack.updateText() + + def redo(self, game): + from_stack = game.allstacks[self.from_stack_id] + to_stack = game.allstacks[self.to_stack_id] + if self.update_flags & 1: + assert to_stack is game.s.talon + assert to_stack.round < to_stack.max_rounds or to_stack.max_rounds < 0 + to_stack.round = to_stack.round + 1 + self.__doMove(from_stack, to_stack, 0) + + def undo(self, game): + from_stack = game.allstacks[self.from_stack_id] + to_stack = game.allstacks[self.to_stack_id] + if self.update_flags & 1: + assert to_stack is game.s.talon + assert to_stack.round > 1 + to_stack.round = to_stack.round - 1 + self.__doMove(to_stack, from_stack, 1) + + def cmpForRedo(self, other): + return (cmp(self.from_stack_id, other.from_stack_id) or + cmp(self.to_stack_id, other.to_stack_id) or + cmp(self.update_flags, other.update_flags)) + + +# /*********************************************************************** +# // Update the view or model of a stack. Only needed for complex +# // games in combination with undo. +# ************************************************************************/ + +class AUpdateStackMove(AtomicMove): + def __init__(self, stack, flags): + self.stack_id = stack.id + self.flags = flags + + # do the actual move + def __doMove(self, game, stack, undo): + if self.flags & 64: + # model + stack.updateModel(undo, self.flags) + else: + # view + if self.flags & 16: + stack.updateText() + if self.flags & 32: + stack.refreshView() + + def redo(self, game): + if (self.flags & 3) in (1, 3): + self.__doMove(game, game.allstacks[self.stack_id], 0) + + def undo(self, game): + if (self.flags & 3) in (2, 3): + self.__doMove(game, game.allstacks[self.stack_id], 1) + + def cmpForRedo(self, other): + return cmp(self.stack_id, other.stack_id) or cmp(self.flags, other.flags) + + +AUpdateStackModelMove = AUpdateStackMove +AUpdateStackViewMove = AUpdateStackMove + + +# /*********************************************************************** +# // Increase the `round' member variable of a Talon stack. +# ************************************************************************/ + +class ANextRoundMove(AtomicMove): + def __init__(self, stack): + self.stack_id = stack.id + + def redo(self, game): + stack = game.allstacks[self.stack_id] + assert stack is game.s.talon + assert stack.round < stack.max_rounds or stack.max_rounds < 0 + stack.round = stack.round + 1 + stack.updateText() + + def undo(self, game): + stack = game.allstacks[self.stack_id] + assert stack is game.s.talon + assert stack.round > 1 + stack.round = stack.round - 1 + stack.updateText() + + def cmpForRedo(self, other): + return cmp(self.stack_id, other.stack_id) + + +# /*********************************************************************** +# // Save the current state (needed for undo in some games). +# ************************************************************************/ + +class ASaveSeedMove(AtomicMove): + def __init__(self, game): + self.state = game.random.getstate() + + def redo(self, game): + game.random.setstate(self.state) + + def undo(self, game): + game.random.setstate(self.state) + + def cmpForRedo(self, other): + return cmp(self.state, other.state) + + +# /*********************************************************************** +# // Save game variables +# ************************************************************************/ + +class ASaveStateMove(AtomicMove): + def __init__(self, game, flags): + self.state = game.getState() + self.flags = flags + + def redo(self, game): + if (self.flags & 3) in (1, 3): + game.setState(self.state) + + def undo(self, game): + if (self.flags & 3) in (2, 3): + game.setState(self.state) + + def cmpForRedo(self, other): + return cmp(self.state, other.state) + + +# /*********************************************************************** +# // Shuffle all cards of a stack. Saves the seed. Does not flip any cards. +# ************************************************************************/ + +class AShuffleStackMove(AtomicMove): + def __init__(self, stack, game): + self.stack_id = stack.id + # save cards and state + self.card_ids = tuple(map(lambda c: c.id, stack.cards)) + self.state = game.random.getstate() + + def redo(self, game): + stack = game.allstacks[self.stack_id] + # paranoia + assert stack is game.s.talon + assert self.card_ids == tuple(map(lambda c: c.id, stack.cards)) + # shuffle (see random) + game.random.setstate(self.state) + seq = stack.cards + n = len(seq) - 1 + while n > 0: + j = game.random.randint(0, n) + seq[n], seq[j] = seq[j], seq[n] + n = n - 1 + stack.refreshView() + + def undo(self, game): + stack = game.allstacks[self.stack_id] + # restore cards + cards = [] + for id in self.card_ids: + c = game.cards[id] + assert c.id == id + cards.append(c) + stack.cards = cards + # restore the state + game.random.setstate(self.state) + stack.refreshView() + + def cmpForRedo(self, other): + return (cmp(self.stack_id, other.stack_id) or + cmp(self.card_ids, other.card_ids) or + cmp(self.state, other.state)) + diff --git a/pysollib/pysolaudio.py b/pysollib/pysolaudio.py new file mode 100644 index 0000000000..acab3e9598 --- /dev/null +++ b/pysollib/pysolaudio.py @@ -0,0 +1,331 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import os, re, string, sys, time, types +import traceback + +import thread +try: + import pysolsoundserver +except ImportError: + pysolsoundserver = None + + +# /*********************************************************************** +# // basic audio client +# ************************************************************************/ + +class AbstractAudioClient: + def __init__(self): + self.server = None + self.audiodev = None + self.connected = 0 + self.app = None + self.file_cache = {} + self.sample_priority = -1 + self.sample_loop = 0 + self.music_priority = -1 + self.music_loop = 0 + + def __del__(self): + self.destroy() + + # start server - set self.server on success (may also set self.audiodev) + def startServer(self): + pass + + # connect to server - set self.audiodev on success + def connectServer(self, app): + assert app + self.app = app + if self.server is not None: + try: + if self._connectServer(): + self.connected = 1 + except: + if traceback: traceback.print_exc() + self.destroy() + + # disconnect and stop server + def destroy(self): + if self.audiodev is not None: + try: + self._destroy() + except: + pass + self.server = None + self.audiodev = None + self.connected = 0 + self.app = None + + # + # high-level interface + # + + def stopAll(self): + self.stopSamples() + self.stopMusic() + + def playSample(self, name, priority=0, loop=0, volume=-1): + if self.audiodev is None or not self.app or not self.app.opt.sound: + return 0 + if priority <= self.sample_priority and self.sample_loop: + return 0 + obj = self.app.sample_manager.getByName(name) + if not obj or not obj.absname: + return 0 + try: + if self._playSample(obj.absname, priority, loop, volume): + self.sample_priority = priority + self.sample_loop = loop + return 1 + except: + if traceback: traceback.print_exc() + return 0 + + def stopSamples(self): + if self.audiodev is None: + return + try: + self._stopSamples() + except: + if traceback: traceback.print_exc() + self.sample_priority = -1 + self.sample_loop = 0 + + def stopSamplesLoop(self): + if self.audiodev is None: + return + try: + self._stopSamplesLoop() + except: + if traceback: traceback.print_exc() + self.sample_priority = -1 + self.sample_loop = 0 + + def playMusic(self, basename, priority=0, loop=0, volume=-1): + if self.audiodev is None or not self.app or not self.app.opt.sound: + return 0 + if priority <= self.music_priority and self.music_loop: + return 0 + obj = self.app.music_manager.getByBasename(basename) + if not obj or not obj.absname: + return 0 + try: + if self._playMusic(obj.absname, priority, loop, volume): + self.music_priority = priority + self.music_loop = loop + return 1 + except: + if traceback: traceback.print_exc() + return 0 + + def stopMusic(self): + if self.audiodev is None: + return + try: + self._stopMusic() + except: + if traceback: traceback.print_exc() + self.music_priority = -1 + self.music_loop = 0 + + # + # subclass - core implementation + # + + def _connectServer(self): + return 0 + + def _destroy(self): + pass + + def _playSample(self, filename, priority, loop, volume): + return 0 + + def _stopSamples(self): + pass + + def _stopSamplesLoop(self): + self._stopSamples() + + def _playMusic(self, name, priority, loop, volume): + return 0 + + def _stopMusic(self): + pass + + # + # subclass - extensions + # + + def getMusicInfo(self): + return -1 + + def playContinuousMusic(self, music_list): + pass + + def playNextMusic(self): + pass + + def updateSettings(self): + pass + + +# /*********************************************************************** +# // pysolsoundserver module +# ************************************************************************/ + +class PysolSoundServerModuleClient(AbstractAudioClient): + def startServer(self): + # use the module + try: + self.audiodev = pysolsoundserver + self.audiodev.init() + self.server = 1 # success - see also tk/menubar.py + except: + if traceback: traceback.print_exc() + self.server = None + self.audiodev = None + + def cmd(self, cmd): + return self.audiodev.cmd(cmd) + + # connect to server + def _connectServer(self): + r = self.cmd("protocol 6") + if r != 0: + return 0 + if 0 and self.app.debug: + self.cmd("debug 1") + return 1 + + # disconnect and stop server + def _destroy(self): + self.audiodev.exit() + + # + # + # + + def _playSample(self, filename, priority, loop, volume): + self.cmd("playwav '%s' %d %d %d %d" % (filename, -1, priority, loop, volume)) + return 1 + + def _stopSamples(self): + self.cmd("stopwav") + + def _stopSamplesLoop(self): + self.cmd("stopwavloop") + + def _playMusic(self, filename, priority, loop, volume): + self.cmd("playmus '%s' %d %d %d %d" % (filename, -1, priority, loop, volume)) + return 1 + + def _stopMusic(self): + self.cmd("stopmus") + + def getMusicInfo(self): + if self.audiodev: + return self.audiodev.getMusicInfo() + return -1 + + def playContinuousMusic(self, music_list): + if self.audiodev is None or not self.app: + return + try: + loop = 999999 + for music in music_list: + if music.absname: + self.cmd("queuemus '%s' %d %d %d %d" % (music.absname, music.index, 0, loop, music.volume)) + self.cmd("startqueue") + except: + if traceback: traceback.print_exc() + + def playNextMusic(self): + self.cmd("nextmus") + + def updateSettings(self): + if self.audiodev is None or not self.app: + return + s, m = 0, 0 + if self.app.opt.sound: + s = self.app.opt.sound_sample_volume + m = self.app.opt.sound_music_volume + try: + self.cmd("setwavvol %d" % s) + self.cmd("setmusvol %d" % m) + except: + if traceback: traceback.print_exc() + + +# /*********************************************************************** +# // Win32 winsound audio +# ************************************************************************/ + +class Win32AudioClient(AbstractAudioClient): + def startServer(self): + # use the built-in winsound module + try: + import winsound #keep# + self.audiodev = winsound + del winsound + self.server = 0 # success - see also tk/menubar.py + except: + self.server = None + self.audiodev = None + + def _playSample(self, filename, priority, loop, volume): + a = self.audiodev + flags = a.SND_FILENAME | a.SND_NODEFAULT | a.SND_NOWAIT | a.SND_ASYNC + if loop: + flags = flags | a.SND_LOOP + if priority <= self.sample_priority: + flags = flags | a.SND_NOSTOP + ###print filename, flags, priority + try: + a.PlaySound(filename, flags) + return 1 + except: pass + return 0 + + def _stopSamples(self): + a = self.audiodev + flags = a.SND_NODEFAULT | a.SND_PURGE + a.PlaySound(None, flags) + + diff --git a/pysollib/pysolrandom.py b/pysollib/pysolrandom.py new file mode 100644 index 0000000000..ff2c793aa8 --- /dev/null +++ b/pysollib/pysolrandom.py @@ -0,0 +1,233 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, os, re, time, types +import random + +##---------------------------------------------------------------------- + +class PysolRandom(random.Random): + #MAX_SEED = 0L + #MAX_SEED = 0xffffffffffffffffL # 64 bits + MAX_SEED = 100000000000000000000L # 20 digits + + ORIGIN_UNKNOWN = 0 + ORIGIN_RANDOM = 1 + ORIGIN_PREVIEW = 2 # random from preview + ORIGIN_SELECTED = 3 # manually entered + ORIGIN_NEXT_GAME = 4 # "Next game number" + + def __init__(self, seed=None): + if seed is None: + seed = self._getRandomSeed() + random.Random.__init__(self, seed) + self.initial_seed = seed + self.initial_state = self.getstate() + self.origin = self.ORIGIN_UNKNOWN + + def __str__(self): + return self.str(self.initial_seed) + + def str(self, seed): + return '%020d' % seed + + def reset(self): + self.setstate(self.initial_state) + + def copy(self): + random = PysolRandom(0L) + random.__class__ = self.__class__ + random.__dict__.update(self.__dict__) + return random + + def increaseSeed(self, seed): + if seed < self.MAX_SEED: + return seed + 1L + return 0L + + def _getRandomSeed(self): + t = long(time.time() * 256.0) + t = (t ^ (t >> 24)) % (self.MAX_SEED + 1L) + return t + + +# /*********************************************************************** +# // Abstract PySol Random number generator. +# // +# // We use a seed of type long in the range [0, MAX_SEED]. +# ************************************************************************/ + +class PysolRandomMFX: + MAX_SEED = 0L + + ORIGIN_UNKNOWN = 0 + ORIGIN_RANDOM = 1 + ORIGIN_PREVIEW = 2 # random from preview + ORIGIN_SELECTED = 3 # manually entered + ORIGIN_NEXT_GAME = 4 # "Next game number" + + def __init__(self, seed=None): + if seed is None: + seed = self._getRandomSeed() + self.initial_seed = self.setSeed(seed) + self.origin = self.ORIGIN_UNKNOWN + + def __str__(self): + return self.str(self.initial_seed) + + def reset(self): + self.seed = self.initial_seed + + def getSeed(self): + return self.seed + + def setSeed(self, seed): + seed = self._convertSeed(seed) + if type(seed) is not types.LongType: + raise TypeError, "seeds must be longs" + if not (0L <= seed <= self.MAX_SEED): + raise ValueError, "seed out of range" + self.seed = seed + return seed + + def getstate(self): + return self.seed + + def setstate(self, state): + self.seed = state + + def copy(self): + random = PysolRandom(0L) + random.__class__ = self.__class__ + random.__dict__.update(self.__dict__) + return random + + # + # implementation + # + + def choice(self, seq): + return seq[int(self.random() * len(seq))] + + # Get a random integer in the range [a, b] including both end points. + def randint(self, a, b): + return a + int(self.random() * (b+1-a)) + + def randrange(self, a, b): + return self.randint(a, b-1) + + # + # subclass responsibility + # + + # Get the next random number in the range [0.0, 1.0). + def random(self): + raise SubclassResponsibility + + # + # subclass overrideable + # + + def _convertSeed(self, seed): + return long(seed) + + def increaseSeed(self, seed): + if seed < self.MAX_SEED: + return seed + 1L + return 0L + + def _getRandomSeed(self): + t = long(time.time() * 256.0) + t = (t ^ (t >> 24)) % (self.MAX_SEED + 1L) + return t + + # + # shuffle + # see: Knuth, Vol. 2, Chapter 3.4.2, Algorithm P + # see: FAQ of sci.crypt: "How do I shuffle cards ?" + # + + def shuffle(self, seq): + n = len(seq) - 1 + while n > 0: + j = self.randint(0, n) + seq[n], seq[j] = seq[j], seq[n] + n = n - 1 + +# /*********************************************************************** +# // Linear Congruential random generator +# // In PySol this is only used for 0 <= seed <= 32000. +# ************************************************************************/ + +class LCRandom31(PysolRandomMFX): + MAX_SEED = 0x7fffffffL # 31 bits + + def str(self, seed): + return "%05d" % int(seed) + + def random(self): + self.seed = (self.seed*214013L + 2531011L) & self.MAX_SEED + return (self.seed >> 16) / 32768.0 + + def randint(self, a, b): + self.seed = (self.seed*214013L + 2531011L) & self.MAX_SEED + return a + (int(self.seed >> 16) % (b+1-a)) + + +# /*********************************************************************** +# // PySol support code +# ************************************************************************/ + +# construct Random from seed string +def constructRandom(s): + s = re.sub(r"L$", "", str(s)) # cut off "L" from possible conversion to long + s = re.sub(r"[\s\#\-\_\.\,]", "", s.lower()) + if not s: + return None + seed = long(s) + if 0 <= seed <= 32000: + return LCRandom31(seed) + return PysolRandom(seed) + +# test +if __name__ == '__main__': + _, r = constructRandom('12345') + print r.randint(0, 100) + print r.random() + print type(r) + + diff --git a/pysollib/pysoltk.py b/pysollib/pysoltk.py new file mode 100644 index 0000000000..64c4f7b8af --- /dev/null +++ b/pysollib/pysoltk.py @@ -0,0 +1,43 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +from tk.tkconst import * +from tk.tkutil import * +from tk.tkcanvas import * +from tk.tkwrap import * +from tk.tkwidget import * +from tk.tkhtml import * +from tk.edittextdialog import * +from tk.tkstats import * +from tk.playeroptionsdialog import * +from tk.soundoptionsdialog import * +from tk.demooptionsdialog import * +from tk.timeoutsdialog import * +from tk.colorsdialog import * +from tk.fontsdialog import * +from tk.gameinfodialog import * +from tk.toolbar import * +from tk.statusbar import * +from tk.progressbar import * +from tk.menubar import * +from tk.card import * +from tk.selectcardset import * +from tk.selecttree import * diff --git a/pysollib/resource.py b/pysollib/resource.py new file mode 100644 index 0000000000..527e48ffeb --- /dev/null +++ b/pysollib/resource.py @@ -0,0 +1,589 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import sys, os, glob, operator, types +#import traceback + +# PySol imports +from mfxutil import win32api +from mfxutil import Struct, KwStruct, EnvError, latin1_to_ascii +from version import VERSION +from settings import PACKAGE + + +# /*********************************************************************** +# // Abstract +# ************************************************************************/ + +class Resource(Struct): + def __init__(self, **kw): + kw = KwStruct(kw, + name = "", + filename = "", + basename = "", # basename of filename + absname = "", # absolute filename + # implicit + index = -1, + error = 0, # error while loading this resource + ) + apply(Struct.__init__, (self,), kw.getKw()) + + def getSortKey(self): + return latin1_to_ascii(self.name).lower() + + +class ResourceManager: + def __init__(self): + self._selected_key = -1 + self._objects = [] + self._objects_by_name = None + self._objects_cache_name = {} + self._objects_cache_filename = {} + self._objects_cache_basename = {} + self._objects_cache_absname = {} + + def getSelected(self): + return self._selected_key + + def setSelected(self, index): + assert -1 <= index < len(self._objects) + self._selected_key = index + + def len(self): + return len(self._objects) + + def register(self, obj): + assert obj.index == -1 + assert obj.name and not self._objects_cache_name.has_key(obj.name) + self._objects_cache_name[obj.name] = obj + if obj.filename: + obj.absname = os.path.abspath(obj.filename) + obj.basename = os.path.basename(obj.filename) + self._objects_cache_filename[obj.filename] = obj + self._objects_cache_basename[obj.basename] = obj + self._objects_cache_absname[obj.absname] = obj + obj.index = len(self._objects) + self._objects.append(obj) + self._objects_by_name = None # invalidate + + def get(self, index): + if 0 <= index < len(self._objects): + return self._objects[index] + return None + + def getByName(self, key): + return self._objects_cache_name.get(key) + + def getByBasename(self, key): + return self._objects_cache_basename.get(key) + + def getAll(self): + return tuple(self._objects) + + def getAllSortedByName(self): + if self._objects_by_name is None: + l = map(lambda obj: (obj.getSortKey(), obj), self._objects) + l.sort() + self._objects_by_name = tuple(map(lambda item: item[1], l)) + return self._objects_by_name + + # + # static methods + # + + def _addDir(self, result, dir): + try: + if dir: + dir = os.path.normpath(dir) + if dir and os.path.isdir(dir) and not dir in result: + result.append(dir) + except EnvError, ex: + pass + + def _addRegistryKey(self, result, hkey, subkey): + k = None + try: + k = win32api.RegOpenKeyEx(hkey, subkey, 0, KEY_READ) + nsubkeys, nvalues, t = win32api.RegQueryInfoKey(k) + for i in range(nvalues): + try: + key, value, vtype = win32api.RegEnumValue(k, i) + except: + break + if not key or not value: + continue + if vtype == 1 and type(value) is types.StringType: + for d in value.split(os.pathsep): + self._addDir(result, d.strip()) + finally: + if k is not None: + try: + win32api.RegCloseKey(k) + except: + pass + + def getSearchDirs(self, app, search, env=None): + if type(search) is types.StringType: + search = (search,) + result = [] + if env: + for d in os.environ.get(env, "").split(os.pathsep): + self._addDir(result, d.strip()) + for dir in (app.dataloader.dir, app.dn.maint, app.dn.config): + if not dir: + continue + dir = os.path.normpath(dir) + if not dir or not os.path.isdir(dir): + continue + for s in search: + try: + if s[-2:] == "-*": + d = os.path.normpath(os.path.join(dir, s[:-2])) + self._addDir(result, d) + globdirs = glob.glob(d + "-*") + globdirs.sort() + for d in globdirs: + self._addDir(result, d) + else: + self._addDir(result, os.path.join(dir, s)) + except EnvError, ex: + pass + if app.debug >= 2: + print "getSearchDirs", env, search, "->", result + return result + + def getRegistryDirs(self, app, categories): + if not win32api: + return [] + # + vendors = ("Markus Oberhumer", "",) + versions = (VERSION, "",) + if type(categories) is types.StringType: + categories = (categories,) + # + result = [] + for version in versions: + for vendor in vendors: + for category in categories: + t = ("Software", vendor, PACKAGE, version, category) + t = filter(None, t) + subkey = '\\'.join(t) + ##print "getRegistryDirs subkey", subkey + for hkey in (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE): + try: + self._addRegistryKey(result, hkey, subkey) + except: + pass + # + if app.debug >= 2: + print "getRegistryDirs", category, "->", result + return result + + +# /*********************************************************************** +# // Cardset +# ************************************************************************/ + +# CardsetInfo constants +class CSI: + # cardset size + SIZE_TINY = 1 + SIZE_SMALL = 2 + SIZE_MEDIUM = 3 + SIZE_LARGE = 4 + SIZE_XLARGE = 5 + + # cardset types + TYPE_FRENCH = 1 + TYPE_HANAFUDA = 2 + TYPE_TAROCK = 3 + TYPE_MAHJONGG = 4 + TYPE_HEXADECK = 5 + TYPE_MUGHAL_GANJIFA = 6 + TYPE_NAVAGRAHA_GANJIFA = 7 + TYPE_DASHAVATARA_GANJIFA = 8 + TYPE_TRUMP_ONLY = 9 + + TYPE = { + 1: "French type (52 cards)", + 2: "Hanafuda type (48 cards)", + 3: "Tarock type (78 cards)", + 4: "Mahjongg type (42 tiles)", + 5: "Hex A Deck type (68 cards)", + 6: "Mughal Ganjifa type (96 cards)", + 7: "Navagraha Ganjifa type (108 cards)", + 8: "Dashavatara Ganjifa type (120 cards)", + 9: "Trumps only type (variable cards)", + } + + # cardset styles + STYLE = { + 1: "Adult", # + 2: "Animals", # + 3: "Anime", # + 4: "Art", # + 5: "Cartoons", # + 6: "Children", # + 7: "Classic look", # + 8: "Collectors", # scanned collectors cardsets + 9: "Computers", # + 10: "Engines", # + 11: "Fantasy", # + 30: "Ganjifa", # + 12: "Hanafuda", # + 29: "Hex A Deck", # + 13: "Holiday", # + 28: "Mahjongg", # + 14: "Movies", # + 31: "Matrix", # + 15: "Music", # + 16: "Nature", # + 17: "Operating Systems", # e.g. cards with Linux logos + 19: "People", # famous people + 20: "Places", # + 21: "Plain", # + 22: "Products", # + 18: "Round cardsets", # + 23: "Science Fiction", # + 24: "Sports", # + 27: "Tarock", # + 25: "Vehicels", # + 26: "Video Games", # + } + + # cardset nationality (suit and rank symbols) + NATIONALITY = { + 1021: "Australia", # + 1001: "Austria", # + 1019: "Belgium", # + 1010: "Canada", # + 1011: "China", # + 1012: "Czech Republic", # + 1013: "Denmark", # + 1003: "England", # + 1004: "France", # + 1006: "Germany", # + 1014: "Great Britain", # + 1015: "Hungary", # + 1020: "India", # + 1005: "Italy", # + 1016: "Japan", # + 1002: "Netherlands", # + 1007: "Russia", # + 1008: "Spain", # + 1017: "Sweden", # + 1009: "Switzerland", # + 1018: "USA", # + } + + # cardset creation date + DATE = { + 10: "1000 - 1099", + 11: "1100 - 1199", + 12: "1200 - 1299", + 13: "1300 - 1399", + 14: "1400 - 1499", + 15: "1500 - 1599", + 16: "1600 - 1699", + 17: "1700 - 1799", + 18: "1800 - 1899", + 19: "1900 - 1999", + 20: "2000 - 2099", + 21: "2100 - 2199", + 22: "2200 - 2299", + } + + # + TYPE_NAME = {} + +def create_csi_type_name(): + for id, type in CSI.TYPE.items(): + i = type.find('type') + if i > 0: + CSI.TYPE_NAME[id] = type[:i-1] + else: + CSI.TYPE_NAME[id] = type + +if not CSI.TYPE_NAME: + create_csi_type_name() + + +class CardsetConfig(Struct): + # see config.txt and _readCardsetConfig() + def __init__(self): + Struct.__init__(self, + # line[0] + version = 1, + ext = ".gif", + type = CSI.TYPE_FRENCH, + ncards = -1, + styles = [], + year = 0, + # line[1] + ident = "", + name = "", + # line[2] + CARDW = 0, + CARDH = 0, + CARDD = 0, + # line[3] + CARD_XOFFSET = 0, + CARD_YOFFSET = 0, + SHADOW_XOFFSET = 0, + SHADOW_YOFFSET = 0, + # line[4] + backindex = 0, + # line[5] + backnames = (), + # other + CARD_DX = 0, # relative pos of real card image within Card + CARD_DY = 0, + ) + + +class Cardset(Resource): + def __init__(self, **kw): + # start with all fields from CardsetConfig + config = CardsetConfig() + kw = apply(KwStruct, (config.__dict__,), kw) + # si is the SelectionInfo struct that will be queried by + # the "select cardset" dialogs. It can be freely modified. + si = Struct(type=0, size=0, styles=[], nationalities=[], dates=[]) + kw = KwStruct(kw, + # essentials + ranks = (), + suits = (), + trumps = (), + nbottoms = 7, + nletters = 4, + nshadows = 1 + 13, + # selection criterias + si = si, + # implicit + backname = None, + dir = "", + ) + apply(Resource.__init__, (self,), kw.getKw()) + + def getFaceCardNames(self): + names = [] + for suit in self.suits: + for rank in self.ranks: + names.append("%02d%s" % (rank + 1, suit)) + for trump in self.trumps: + names.append("%02d%s" % (trump + 1, "z")) + assert len(names) == self.ncards + return names + + def getPreviewCardNames(self): + names = self.getFaceCardNames() + pnames = [] + ranks, suits = self.ranks, self.suits + lr, ls = len(ranks), len(suits) + if lr == 0 or ls == 0: # TYPE_TRUMP_ONLY + return names[:16], 4 + if lr >= 4: + ls = min(ls, 4) + low_ranks, high_ranks = 1, 3 + ###if self.type == 3: high_ranks = 4 + for rank in range(0, low_ranks) + range(lr-high_ranks, lr): + for suit in range(ls): + index = suit * len(self.ranks) + rank + pnames.append(names[index % len(names)]) + return pnames, ls + + def updateCardback(self, backname=None, backindex=None): + # update default back + if type(backname) is types.StringType: + if backname in self.backnames: + backindex = self.backnames.index(backname) + if type(backindex) is types.IntType: + self.backindex = backindex % len(self.backnames) + self.backname = self.backnames[self.backindex] + + +class CardsetManager(ResourceManager): + def __init__(self): + ResourceManager.__init__(self) + self.registered_types = {} + self.registered_sizes = {} + self.registered_styles = {} + self.registered_nationalities = {} + self.registered_dates = {} + + def _check(self, cs): + s = cs.type + if not CSI.TYPE.has_key(s): + return 0 + cs.si.type = s + if s == CSI.TYPE_FRENCH: + cs.ranks = range(13) + cs.suits = "cshd" + elif s == CSI.TYPE_HANAFUDA: + cs.ranks = range(12) + cs.suits = "cshd" + elif s == CSI.TYPE_TAROCK: + cs.nbottoms = 8 + cs.ranks = range(14) + cs.suits = "cshd" + cs.trumps = range(22) + elif s == CSI.TYPE_MAHJONGG: + cs.ranks = range(10) + cs.suits = "abc" + cs.trumps = range(12) + # + cs.nbottoms = 0 + cs.nletters = 0 + cs.nshadows = 0 + elif s == CSI.TYPE_HEXADECK: + cs.nbottoms = 8 + cs.ranks = range(16) + cs.suits = "cshd" + cs.trumps = range(4) + elif s == CSI.TYPE_MUGHAL_GANJIFA: + cs.nbottoms = 11 + cs.ranks = range(12) + cs.suits = "abcdefgh" + elif s == CSI.TYPE_NAVAGRAHA_GANJIFA: + #???return 0 ## FIXME + cs.nbottoms = 12 + cs.ranks = range(12) + cs.suits = "abcdefghi" + elif s == CSI.TYPE_DASHAVATARA_GANJIFA: + cs.nbottoms = 13 + cs.ranks = range(12) + cs.suits = "abcdefghij" + elif s == CSI.TYPE_TRUMP_ONLY: + #???return 0 ## FIXME + #cs.nbottoms = 7 + #cs.ranks = () + #cs.suits = "" + #cs.trumps = range(cs.ncards) + cs.nbottoms = 1 + cs.nletters = 0 + cs.nshadows = 0 + cs.ranks = () + cs.suits = "" + cs.trumps = range(cs.ncards) + + else: + return 0 + return 1 + + def register(self, cs): + if not self._check(cs): + return + cs.ncards = len(cs.ranks) * len(cs.suits) + len(cs.trumps) + cs.name = cs.name[:25] + if not (1 <= cs.si.size <= 5): + CW, CH = cs.CARDW, cs.CARDH + if CW <= 55 and CH <= 72: + cs.si.size = CSI.SIZE_TINY + elif CW <= 60 and CH <= 85: + cs.si.size = CSI.SIZE_SMALL + elif CW <= 75 and CH <= 105: + cs.si.size = CSI.SIZE_MEDIUM + elif CW <= 90 and CH <= 125: + cs.si.size = CSI.SIZE_LARGE + else: + cs.si.size = CSI.SIZE_XLARGE + # + keys = cs.styles[:] + cs.si.styles = tuple(filter(lambda s: CSI.STYLE.has_key(s), keys)) + for s in cs.si.styles: + self.registered_styles[s] = self.registered_styles.get(s, 0) + 1 + cs.si.nationalities = tuple(filter(lambda s: CSI.NATIONALITY.has_key(s), keys)) + for s in cs.si.nationalities: + self.registered_nationalities[s] = self.registered_nationalities.get(s, 0) + 1 + keys = (cs.year / 100,) + cs.si.dates = tuple(filter(lambda s: CSI.DATE.has_key(s), keys)) + for s in cs.si.dates: + self.registered_dates[s] = self.registered_dates.get(s, 0) + 1 + # + s = cs.si.type + self.registered_types[s] = self.registered_types.get(s, 0) + 1 + s = cs.si.size + self.registered_sizes[s] = self.registered_sizes.get(s, 0) + 1 + cs.updateCardback() + ResourceManager.register(self, cs) + + +# /*********************************************************************** +# // Tile +# ************************************************************************/ + +class Tile(Resource): + def __init__(self, **kw): + kw = KwStruct(kw, + color = None, + text_color = "#000000", + stretch = 0, + ) + apply(Resource.__init__, (self,), kw.getKw()) + + +class TileManager(ResourceManager): + pass + + +# /*********************************************************************** +# // Sample +# ************************************************************************/ + +class Sample(Resource): + def __init__(self, **kw): + kw = KwStruct(kw, + volume = -1, + ) + apply(Resource.__init__, (self,), kw.getKw()) + + +class SampleManager(ResourceManager): + pass + + +# /*********************************************************************** +# // Music +# ************************************************************************/ + +class Music(Sample): + pass + + +class MusicManager(SampleManager): + pass + diff --git a/pysollib/settings.py b/pysollib/settings.py new file mode 100644 index 0000000000..65cb17f858 --- /dev/null +++ b/pysollib/settings.py @@ -0,0 +1,60 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +import sys, os + +n_ = lambda x: x + +# +#PACKAGE = "PySolFC" +PACKAGE = "PySol" +PACKAGE_URL = "http://sourceforge.net/projects/pysolfc/" + +# data dirs +DATA_DIRS = [] +# you can add your extra directories here +if os.name == "posix": + DATA_DIRS = [ + '/usr/share/PySolFC', + '/usr/local/share/PySolFC', + '/usr/games/PySolFC', + '/usr/local/games/PySolFC', + ] +if os.name == "nt": + pass +if os.name == "mac": + pass + +# sound mixers +MIXERS = () +if os.name == "nt": + MIXERS = (("sndvol32.exe", None),) +elif os.name == "posix": + MIXERS = ( + #("alsamixer", None), + ("kmix", None), + ("gmix", None), + ) + +TOP_SIZE = 10 +TOP_TITLE = n_("Top 10") + + diff --git a/pysollib/stack.py b/pysollib/stack.py new file mode 100644 index 0000000000..2168e16b2b --- /dev/null +++ b/pysollib/stack.py @@ -0,0 +1,2101 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['cardsFaceUp', + 'cardsFaceDown', + 'isRankSequence', + 'isAlternateColorSequence', + 'isSameColorSequence', + 'isSameSuitSequence', + 'isAnySuitButOwnSequence', + 'getNumberOfFreeStacks', + 'getPileFromStacks', + 'Stack', + 'DealRow_StackMethods', + 'DealBaseCard_StackMethods', + 'TalonStack', + 'DealRowTalonStack', + 'InitialDealTalonStack', + 'OpenStack', + 'AbstractFoundationStack', + 'SS_FoundationStack', + 'RK_FoundationStack', + 'AC_FoundationStack', + 'SequenceStack_StackMethods', + 'BasicRowStack', + 'SequenceRowStack', + 'AC_RowStack', + 'SC_RowStack', + 'SS_RowStack', + 'RK_RowStack', + 'UD_AC_RowStack', + 'UD_SC_RowStack', + 'UD_SS_RowStack', + 'UD_RK_RowStack', + 'FreeCell_AC_RowStack', + 'FreeCell_SS_RowStack', + 'Spider_AC_RowStack', + 'Spider_SS_RowStack', + 'Yukon_AC_RowStack', + 'Yukon_SS_RowStack', + 'KingAC_RowStack', + 'KingSS_RowStack', + 'KingRK_RowStack', + 'WasteStack', + 'WasteTalonStack', + 'FaceUpWasteTalonStack', + 'OpenTalonStack', + 'ReserveStack', + 'InvisibleStack', + 'StackWrapper', + 'WeakStackWrapper', + 'FullStackWrapper', + ] + +# imports +import time, types + +# PySol imports +from mfxutil import Struct, kwdefault, SubclassResponsibility +from util import Timer +from util import ACE, KING, SUITS +from util import ANY_SUIT, ANY_COLOR, ANY_RANK, NO_RANK +from util import NO_REDEAL, UNLIMITED_REDEALS, VARIABLE_REDEALS +from pysoltk import tkname, EVENT_HANDLED, EVENT_PROPAGATE +from pysoltk import CURSOR_DRAG, ANCHOR_NW, ANCHOR_SE +from pysoltk import bind, unbind_destroy +from pysoltk import after, after_idle, after_cancel +from pysoltk import MfxCanvasGroup, MfxCanvasImage, MfxCanvasRectangle, MfxCanvasText +from pysoltk import Card + +from tkFont import Font + +# /*********************************************************************** +# // Let's start with some test methods for cards. +# // Empty card-lists return false. +# ************************************************************************/ + +# check that all cards are face-up +def cardsFaceUp(cards): + if not cards: return 0 + for c in cards: + if not c.face_up: + return 0 + return 1 + +# check that all cards are face-down +def cardsFaceDown(cards): + if not cards: return 0 + for c in cards: + if c.face_up: + return 0 + return 1 + +# check that cards are face-up and build down by rank +def isRankSequence(cards, mod=8192, dir=-1): + if not cardsFaceUp(cards): + return 0 + c1 = cards[0] + for c2 in cards[1:]: + if (c1.rank + dir) % mod != c2.rank: + return 0 + c1 = c2 + return 1 + +# check that cards are face-up and build down by alternate color +def isAlternateColorSequence(cards, mod=8192, dir=-1): + if not cardsFaceUp(cards): + return 0 + c1 = cards[0] + for c2 in cards[1:]: + if (c1.rank + dir) % mod != c2.rank or c1.color == c2.color: + return 0 + c1 = c2 + return 1 + +# check that cards are face-up and build down by same color +def isSameColorSequence(cards, mod=8192, dir=-1): + if not cardsFaceUp(cards): + return 0 + c1 = cards[0] + for c2 in cards[1:]: + if (c1.rank + dir) % mod != c2.rank or c1.color != c2.color: + return 0 + c1 = c2 + return 1 + +# check that cards are face-up and build down by same suit +def isSameSuitSequence(cards, mod=8192, dir=-1): + if not cardsFaceUp(cards): + return 0 + c1 = cards[0] + for c2 in cards[1:]: + if (c1.rank + dir) % mod != c2.rank or c1.suit != c2.suit: + return 0 + c1 = c2 + return 1 + +# check that cards are face-up and build down by any suit but own +def isAnySuitButOwnSequence(cards, mod=8192, dir=-1): + if not cardsFaceUp(cards): + return 0 + c1 = cards[0] + for c2 in cards[1:]: + if (c1.rank + dir) % mod != c2.rank or c1.suit == c2.suit: + return 0 + c1 = c2 + return 1 + +def getNumberOfFreeStacks(stacks): + return len(filter(lambda s: not s.cards, stacks)) + +# collect the top cards of several stacks into a pile +def getPileFromStacks(stacks, reverse=0): + cards = [] + for s in stacks: + if not s.cards or not s.cards[-1].face_up: + return None + cards.append(s.cards[-1]) + if reverse: + cards.reverse() + return cards + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Stack: + # A generic stack of cards. + # + # This is used as a base class for all other stacks (e.g. the talon, + # the foundations and the row stacks). + # + # The default event handlers turn the top card of the stack with + # its face up on a (single or double) click, and also support + # moving a subpile around. + + def __init__(self, x, y, game, cap={}): + # Arguments are the stack's nominal x and y position (the top + # left corner of the first card placed in the stack), and the + # game object (which is used to get the canvas; subclasses use + # the game object to find other stacks). + + # + # link back to game + # + id = len(game.allstacks) + game.allstacks.append(self) + x = int(round(x)) + y = int(round(y)) + mapkey = (x, y) + ###assert not game.stackmap.has_key(mapkey) ## can happen in PyJonngg + game.stackmap[mapkey] = id + + # + # setup our pseudo MVC scheme + # + model, view, controller = self, self, self + + # + # model + # + model.id = id + model.game = game + model.cards = [] + + # capabilites - the game logic + model.cap = Struct( + suit = -1, # required suit for this stack (-1 is ANY_SUIT) + color = -1, # required color for this stack (-1 is ANY_COLOR) + rank = -1, # required rank for this stack (-1 is ANY_RANK) + base_suit = -1, # base suit for this stack (-1 is ANY_SUIT) + base_color = -1, # base color for this stack (-1 is ANY_COLOR) + base_rank = -1, # base rank for this stack (-1 is ANY_RANK) + dir = 0, # direction - stack builds up/down + mod = 8192, # modulo for wrap around (typically 13 or 8192) + max_move = 0, # can move at most # cards at a time + max_accept = 0, # can accept at most # cards at a time + max_cards = 999999, # total number of cards may not exceed this + # not commonly used: + min_move = 1, # must move at least # cards at a time + min_accept = 1, # must accept at least # cards at a time + min_cards = 0, # total number of cards this stack at least requires + ) + model.cap.update(cap) + assert type(model.cap.suit) is types.IntType + assert type(model.cap.color) is types.IntType + assert type(model.cap.rank) is types.IntType + assert type(model.cap.base_suit) is types.IntType + assert type(model.cap.base_color) is types.IntType + assert type(model.cap.base_rank) is types.IntType + # + # view + # + view.x = x + view.y = y + view.canvas = game.canvas + view.CARD_XOFFSET = 0 + view.CARD_YOFFSET = 0 + view.group = MfxCanvasGroup(view.canvas) + ##view.group.move(view.x, view.y) + # image items + view.images = Struct( + bottom = None, # canvas item + redeal = None, # canvas item + redeal_img = None, # the corresponding PhotoImage + ) + # other canvas items + view.items = Struct( + bottom = None, # dummy canvas item + ) + # text items + view.texts = Struct( + ncards = None, # canvas item + # by default only used by Talon: + rounds = None, # canvas item + redeal = None, # canvas item + redeal_str = None, # the corresponding string + # for use by derived stacks: + misc = None, # canvas item + ) + view.top_bottom = None # the highest of all bottom items + view.is_visible = view.x >= -100 and view.y >= -100 + view.is_open = -1 + view.can_hide_cards = -1 + view.max_shadow_cards = -1 + + def destruct(self): + # help breaking circular references + unbind_destroy(self.group) + + def prepareStack(self): + self.prepareView() + if self.is_visible: + self.initBindings() + + + # bindings {view widgets bind to controller} + def initBindings(self): + group = self.group + bind(group, "<1>", self.__clickEventHandler) + ##bind(group, "", self.__motionEventHandler) + bind(group, "", self.__motionEventHandler) + bind(group, "", self.__releaseEventHandler) + bind(group, "", self.__controlclickEventHandler) + bind(group, "", self.__shiftclickEventHandler) + bind(group, "", self.__doubleclickEventHandler) + bind(group, "<3>", self.__rightclickEventHandler) + bind(group, "<2>", self.__middleclickEventHandler) + bind(group, "", self.__middleclickEventHandler) + bind(self.group, "", self.__shiftrightclickEventHandler) + ##bind(self.group, "", "") + bind(group, "", self.__enterEventHandler) + bind(group, "", self.__leaveEventHandler) + + def prepareView(self): + ##assertView(self) + # prepare some variables + ox, oy = self.CARD_XOFFSET, self.CARD_YOFFSET + if type(ox) is types.IntType: + self.CARD_XOFFSET = (ox,) + else: + self.CARD_XOFFSET = tuple(map(int, map(round, ox))) + if type(oy) is types.IntType: + self.CARD_YOFFSET = (oy,) + else: + self.CARD_YOFFSET = tuple(map(int, map(round, oy))) + if self.can_hide_cards < 0: + self.can_hide_cards = self.is_visible + if self.cap.max_cards < 3: + self.can_hide_cards = 0 + elif filter(None, self.CARD_XOFFSET): + self.can_hide_cards = 0 + elif filter(None, self.CARD_YOFFSET): + self.can_hide_cards = 0 + elif self.canvas.preview: + self.can_hide_cards = 0 + if self.can_hide_cards: + # compute hide-off direction (see class Card) + CW, CH = self.game.app.images.CARDW, self.game.app.images.CARDH + cx = self.x + CW / 2 + cy = self.y + CH / 2 + if cy < 3 * CH / 2: + self.hide_x, self.hide_y = 0, -10000 # hide at top + elif cx < 3 * CW / 2: + self.hide_x, self.hide_y = -10000, 0 # hide at left + elif cy > self.game.height - 3 * CH / 2: + self.hide_x, self.hide_y = 0, 10000 # hide at bottom + else: + self.hide_x, self.hide_y = 10000, 0 # hide at right + if self.is_open < 0: + self.is_open = (self.is_visible and + (abs(self.CARD_XOFFSET[0]) >= 5 or + abs(self.CARD_YOFFSET[0]) >= 5)) + if self.max_shadow_cards < 0: + self.max_shadow_cards = 999999 + if abs(self.CARD_YOFFSET[0]) != self.game.app.images.CARD_YOFFSET: + # don't display a shadow if the YOFFSET of the stack + # and the images don't match + self.max_shadow_cards = 1 + # bottom image + if self.is_visible: + self.prepareBottom() + + # stack bottom image + def prepareBottom(self): + assert self.is_visible and self.images.bottom is None + img = self.getBottomImage() + if img is not None: + self.images.bottom = MfxCanvasImage(self.canvas, self.x, self.y, + image=img, anchor=ANCHOR_NW) + self.images.bottom.addtag(self.group) + self.top_bottom = self.images.bottom + + # invisible stack bottom + # We need this if we want to get any events for an empty stack (which + # is needed by the quickPlayHandler in some games like Montana) + def prepareInvisibleBottom(self): + assert self.is_visible and self.items.bottom is None + images = self.game.app.images + self.items.bottom = MfxCanvasRectangle(self.canvas, self.x, self.y, + self.x + images.CARDW, + self.y + images.CARDH, + fill="", outline="", width=0) + self.items.bottom.addtag(self.group) + self.top_bottom = self.items.bottom + + # sanity checks + def assertStack(self): + assert self.cap.min_move > 0 + assert self.cap.min_accept > 0 + assert not hasattr(self, "suit") + + + # + # Core access methods {model -> view} + # + + # Add a card add the top of a stack. Also update display. {model -> view} + def addCard(self, card, unhide=1, update=1): + model, view = self, self + model.cards.append(card) + card.tkraise(unhide=unhide) + if view.can_hide_cards and len(model.cards) >= 3: + # we only need to display the 2 top cards + model.cards[-3].hide(self) + card.item.addtag(view.group) + view._position(card) + if update: + view.updateText() + return card + + # Remove a card from the stack. Also update display. {model -> view} + def removeCard(self, card=None, unhide=1, update=1): + model, view = self, self + assert len(model.cards) > 0 + if card is None: + card = model.cards[-1] + # optimized a little bit (compare with the else below) + card.item.dtag(view.group) + if unhide and self.can_hide_cards: + card.unhide() + if len(self.cards) >= 3: + model.cards[-3].unhide() + del model.cards[-1] + else: + card.item.dtag(view.group) + if unhide and view.can_hide_cards: + # Note: the 2 top cards ([-1] and [-2]) are already unhidden. + card.unhide() + if len(model.cards) >= 3: + if card is model.cards[-1] or model is self.cards[-2]: + # Make sure that 2 top cards will be un-hidden. + model.cards[-3].unhide() + model.cards.remove(card) + if update: + view.updateText() + return card + + # Get the top card {model} + def getCard(self): + if self.cards: + return self.cards[-1] + return None + + # get the largest moveable pile {model} - uses canMoveCards() + def getPile(self): + if self.cap.max_move > 0: + cards = self.cards[-self.cap.max_move:] + while len(cards) >= self.cap.min_move: + if self.canMoveCards(cards): + return cards + del cards[0] + return None + + # Position the card on the canvas {view} + def _position(self, card): + x, y = self.getPositionFor(card) + card.moveTo(x, y) + + # find card + def _findCard(self, event): + model, view = self, self + if event is not None and model.cards: + # ask the canvas + return view.canvas.findCard(self, event) + return -1 + + # find card + def _findCardXY(self, x, y, cards=None): + model, view = self, self + if cards is None: cards = model.cards + images = self.game.app.images + index = -1 + for i in range(len(cards)): + c = cards[i] + r = (c.x, c.y, c.x + images.CARDW, c.y + images.CARDH) + if x >= r[0] and x < r[2] and y >= r[1] and y < r[3]: + index = i + return index + + # generic model update (can be used for undo/redo - see move.py) + def updateModel(self, undo, flags): + pass + + # copy model data - see Hint.AClonedStack + def copyModel(self, clone): + clone.id = self.id + clone.game = self.game + clone.cap = self.cap + + def getRankDir(self, cards=None): + if cards is None: + cards = self.cards[-2:] + if len(cards) < 2: + return 0 + dir = (cards[-1].rank - cards[-2].rank) % self.cap.mod + if dir > self.cap.mod / 2: + return dir - self.cap.mod + return dir + + + # + # Basic capabilities {model} + # Used by various subclasses. + # + + def basicIsBlocked(self): + # Check if the stack is blocked (e.g. Pyramid or Mahjongg) + return 0 + + def basicAcceptsCards(self, from_stack, cards): + # Check that the limits are ok and that the cards are face up + if from_stack is self or self.basicIsBlocked(): + return 0 + cap = self.cap + l = len(cards) + if l < cap.min_accept or l > cap.max_accept: + return 0 + l = l + len(self.cards) + if l > cap.max_cards: # note: we don't check cap.min_cards here + return 0 + for c in cards: + if not c.face_up: + return 0 + if cap.suit >= 0 and c.suit != cap.suit: + return 0 + if cap.color >= 0 and c.color != cap.color: + return 0 + if cap.rank >= 0 and c.rank != cap.rank: + return 0 + if self.cards: + # top card of our stack must be face up + return self.cards[-1].face_up + else: + # check required base + c = cards[0] + if cap.base_suit >= 0 and c.suit != cap.base_suit: + return 0 + if cap.base_color >= 0 and c.color != cap.base_color: + return 0 + if cap.base_rank >= 0 and c.rank != cap.base_rank: + return 0 + return 1 + + def basicCanMoveCards(self, cards): + # Check that the limits are ok and the cards are face up + if self.basicIsBlocked(): + return 0 + cap = self.cap + l = len(cards) + if l < cap.min_move or l > cap.max_move: + return 0 + l = len(self.cards) - l + if l < cap.min_cards: # note: we don't check cap.max_cards here + return 0 + return cardsFaceUp(cards) + + + # + # Capabilities - important for game logic {model} + # + + def acceptsCards(self, from_stack, cards): + # Do we accept receiving `cards' from `from_stack' ? + return 0 + + def canMoveCards(self, cards): + # Can we move these cards when assuming they are our top-cards ? + return 0 + + def canFlipCard(self): + # Can we flip our top card ? + return 0 + + def canDropCards(self, stacks): + # Can we drop the top cards onto one of the foundation stacks ? + return (None, 0) # return the stack and the number of cards + + + # + # State {model} + # + + def resetGame(self): + # Called when starting a new game. + pass + + def __repr__(self): + # Return a string for debug print statements. + return "%s(%d)" % (self.__class__.__name__, self.id) + + + # + # Atomic move actions {model -> view} + # + + def flipMove(self): + # Flip the top card. + self.game.flipMove(self) + + def moveMove(self, ncards, to_stack, frames=-1, shadow=-1): + # Move the top n cards. + self.game.moveMove(ncards, self, to_stack, frames=frames, shadow=shadow) + self.fillStack() + + def fillStack(self): + self.game.fillStack(self) + + + # + # Playing move actions. Better not override. + # + + def playFlipMove(self, sound=1): + if sound: + self.game.playSample("flip", 5) + self.flipMove() + if not self.game.checkForWin(): + self.game.autoPlay() + self.game.finishMove() + + def playMoveMove(self, ncards, to_stack, frames=-1, shadow=-1, sound=1): + if sound: + if to_stack in self.game.s.foundations: + self.game.playSample("drop", priority=30) + else: + self.game.playSample("move", priority=10) + self.moveMove(ncards, to_stack, frames=frames, shadow=shadow) + if not self.game.checkForWin(): + # let the player put cards back from the foundations + if not self in self.game.s.foundations: + self.game.autoPlay() + self.game.finishMove() + + + # + # Appearance {view} + # + + def getBottomImage(self): + return None + + def getPositionFor(self, card): + model, view = self, self + if view.can_hide_cards: + return view.x, view.y + x, y = view.x, view.y + ix, iy, lx, ly = 0, 0, len(view.CARD_XOFFSET), len(view.CARD_YOFFSET) + for c in model.cards: + if c is card: + break + x = x + view.CARD_XOFFSET[ix] + y = y + view.CARD_YOFFSET[iy] + ix = (ix + 1) % lx + iy = (iy + 1) % ly + return (x, y) + + # Fully update the view of a stack - updates + # hiding, card positions and stacking order. + # Avoid calling this as it is rather slow. + def refreshView(self): + model, view = self, self + cards = model.cards + if not view.is_visible or len(cards) < 2: + return + if view.can_hide_cards: + # hide all lower cards + for c in cards[:-2]: + ##print "refresh hide", c, c.hide_stack + c.hide(self) + # unhide the 2 top cards + for c in cards[-2:]: + ##print "refresh unhide 1", c, c.hide_stack + c.unhide() + ##print "refresh unhide 1", c, c.hide_stack, c.hide_x, c.hide_y + # update the card postions and stacking order + item = cards[0].item + x, y = view.x, view.y + ix, iy, lx, ly = 0, 0, len(view.CARD_XOFFSET), len(view.CARD_YOFFSET) + for c in cards[1:]: + c.item.tkraise(item) + item = c.item + if not view.can_hide_cards: + x = x + view.CARD_XOFFSET[ix] + y = y + view.CARD_YOFFSET[iy] + ix = (ix + 1) % lx + iy = (iy + 1) % ly + c.moveTo(x, y) + + def updateText(self): + if self.game.preview > 1 or self.texts.ncards is None: + return + t = "" + format = "%d" + if self.texts.ncards.text_format is not None: + format = self.texts.ncards.text_format + if format == "%D": + format = "" + if self.cards: + format = "%d" + if format: + t = format % len(self.cards) + if 0 and self.game.app.debug: + visible = 0 + for c in self.cards: + if c.isHidden(): + assert c.hide_stack is not None + assert c.hide_x != 0 or c.hide_y != 0 + else: + visible = visible + 1 + assert c.hide_stack is None + assert c.hide_x == 0 and c.hide_y == 0 + t = t + " %2d" % visible + self.texts.ncards.config(text=t) + + def basicShallHighlightSameRank(self, card): + # by default all open stacks are available for highlighting + assert card in self.cards + if not self.is_visible or not card.face_up: + return 0 + if card is self.cards[-1]: + return 1 + return self.is_open + + def basicShallHighlightMatch(self, card): + # by default all open stacks are available for highlighting + return self.basicShallHighlightSameRank(card) + + def highlightSameRank(self, event): + i = self._findCard(event) + if i < 0: + return 0 + card = self.cards[i] + if not self.basicShallHighlightSameRank(card): + return 0 + col = self.game.app.opt.highlight_samerank_colors + info = [ (self, card, card, col[1]) ] + for s in self.game.allstacks: + for c in s.cards: + if c is card: continue + # check the rank + if c.rank != card.rank: continue + # ask the target stack + if s.basicShallHighlightSameRank(c): + info.append((s, c, c, col[3])) + self.game.stats.highlight_samerank = self.game.stats.highlight_samerank + 1 + return self.game._highlightCards(info, self.game.app.opt.highlight_samerank_sleep) + + def highlightMatchingCards(self, event): + i = self._findCard(event) + if i < 0: + return 0 + card = self.cards[i] + if not self.basicShallHighlightMatch(card): + return 0 + col = self.game.app.opt.highlight_cards_colors + c1 = c2 = card + info = [] + found = 0 + for s in self.game.allstacks: + # continue if both stacks are foundations + if self in self.game.s.foundations and s in self.game.s.foundations: + continue + # for all cards + for c in s.cards: + if c is card: continue + # ask the target stack + if not s.basicShallHighlightMatch(c): + continue + # ask the game + if self.game.shallHighlightMatch(self, card, s, c): + found = 1 + if s is self: + # enlarge rectangle for neighbours + j = self.cards.index(c) + if i - 1 == j: c1 = c; continue + if i + 1 == j: c2 = c; continue + info.append((s, c, c, col[3])) + if found: + if info: + self.game.stats.highlight_cards = self.game.stats.highlight_cards + 1 + info.append((self, c1, c2, col[1])) + return self.game._highlightCards(info, self.game.app.opt.highlight_cards_sleep) + if not self.basicIsBlocked(): + self.game.highlightNotMatching() + return 0 + + + # + # Subclass overridable handlers {contoller -> model -> view} + # + + def clickHandler(self, event): + return 0 + + def middleclickHandler(self, event): + # default action: show the card if it is overlapped by other cards + if not self.is_open: + return 0 + i = self._findCard(event) + positions = len(self.cards) - i - 1 + if i < 0 or positions <= 0 or not self.cards[i].face_up: + return 0 + ##print self.cards[i] + self.cards[i].item.tkraise() + self.game.canvas.update_idletasks() + self.game.sleep(self.game.app.opt.raise_card_sleep) + self.cards[i].item.lower(self.cards[i+1].item) + self.game.canvas.update_idletasks() + return 1 + + def rightclickHandler(self, event): + return 0 + + def doubleclickHandler(self, event): + return self.clickHandler(event) + + def controlclickHandler(self, event): + return 0 + + def shiftclickHandler(self, event): + # default action: highlight all cards of the same rank + if self.game.app.opt.highlight_samerank: + return self.highlightSameRank(event) + return 0 + + def shiftrightclickHandler(self, event): + return 0 + + def releaseHandler(self, event, drag, sound=1): + # default action: move cards back to their origin position + if drag.cards: + if sound: + self.game.playSample("nomove") + self.moveCardsBackHandler(event, drag) + + def moveCardsBackHandler(self, event, drag): + for card in drag.cards: + self._position(card) + + + # + # Event handlers {controller} + # + + def __defaultClickEventHandler(self, event, handler, start_drag=0, cancel_drag=1): + if self.game.demo: + self.game.stopDemo(event) + self.game.interruptSleep() + if self.game.busy: return EVENT_HANDLED + if self.game.drag.stack and cancel_drag: + self.game.drag.stack.cancelDrag(event) # in case we lost an event + if start_drag: + # this handler may start a drag operation + r = handler(event) + if r <= 0: + sound = r == 0 + self.startDrag(event, sound=sound) + else: + handler(event) + return EVENT_HANDLED + + def __clickEventHandler(self, event): + if self.game.app.opt.sticky_mouse: + cancel_drag = 0 + start_drag = not self.game.drag.stack + if start_drag: + handler = self.clickHandler + else: + handler = self.finishDrag + else: + cancel_drag = 1 + start_drag = 1 + handler = self.clickHandler + return self.__defaultClickEventHandler(event, handler, start_drag, cancel_drag) + + def __doubleclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.doubleclickHandler) + + def __middleclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.middleclickHandler) + + def __rightclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.rightclickHandler) + + def __controlclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.controlclickHandler) + + def __shiftclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.shiftclickHandler) + + def __shiftrightclickEventHandler(self, event): + return self.__defaultClickEventHandler(event, self.shiftrightclickHandler) + + def __motionEventHandler(self, event): + if not self.game.drag.stack or not self is self.game.drag.stack: + return EVENT_PROPAGATE + if self.game.demo: + self.game.stopDemo(event) + if self.game.busy: return EVENT_HANDLED + if not self.game.app.opt.sticky_mouse: # 1: + # use a timer to update the drag + # this allows us to skip redraws on slow machines + drag = self.game.drag + if drag.timer is None: + drag.timer = after_idle(self.canvas, self.keepDragTimer) + drag.event = event + else: + # update now + self.keepDrag(event) + return EVENT_HANDLED + + def __releaseEventHandler(self, event): + if self.game.demo: + self.game.stopDemo(event) + self.game.interruptSleep() + if self.game.busy: return EVENT_HANDLED + if not self.game.app.opt.sticky_mouse: + self.keepDrag(event) + self.finishDrag(event) + return EVENT_HANDLED + + def __enterEventHandler(self, event): + if not self.game.drag.stack: + after_idle(self.canvas, self.game.showHelp, + 'help', self.getHelp(), ##+' '+self.getBaseCard(), + 'info', self.getNumCards()) + return EVENT_HANDLED + + def __leaveEventHandler(self, event): + if not self.game.drag.stack: + after_idle(self.canvas, self.game.showHelp) + if not self.game.app.opt.sticky_mouse: + return EVENT_HANDLED + drag_stack = self.game.drag.stack + if self is drag_stack: + x, y = event.x, event.y + w, h = self.game.canvas.winfo_width(), self.game.canvas.winfo_height() + if x < 0 or y < 0 or x >= w or y >= h: + # cancel drag if mouse leave canvas + drag_stack.cancelDrag(event) + after_idle(self.canvas, self.game.showHelp) + return EVENT_HANDLED + else: + # continue drag + return self.__motionEventHandler(event) + else: + return EVENT_PROPAGATE + + + # + # Drag internals {controller -> model -> view} + # + + # begin a drag operation + def startDrag(self, event, sound=1): + #print event.x, event.y + assert self.game.drag.stack is None + i = self._findCard(event) + if i < 0 or not self.canMoveCards(self.cards[i:]): + return + x_offset, y_offset = self.cards[i].x, self.cards[i].y + if sound: + self.game.playSample("startdrag") + self.lastx = event.x + self.lasty = event.y + game = self.game + drag = game.drag + drag.start_x = event.x + drag.start_y = event.y + drag.stack = self + drag.noshade_stacks = [ self ] + drag.cards = self.cards[i:] + images = game.app.images + drag.shadows = self.createShadows(drag.cards) + ##sx, sy = 0, 0 + sx, sy = -images.SHADOW_XOFFSET, -images.SHADOW_YOFFSET + dx, dy = 0, 0 + if self.game.app.opt.sticky_mouse: + # return cards under mouse + dx = event.x - (x_offset+images.CARDW+sx) + dy = event.y - (y_offset+images.CARDH+sy) + if dx < 0: dx = 0 + if dy < 0: dy = 0 + for s in drag.shadows: + if dx > 0 or dy > 0: + s.move(dx, dy) + s.tkraise() + for card in drag.cards: + card.tkraise() + card.moveBy(sx+dx, sy+dy) + if self.game.app.opt.dragcursor: + self.game.canvas.config(cursor=CURSOR_DRAG) + + # continue a drag operation + def keepDrag(self, event): + drag = self.game.drag + if not drag.cards: + return + assert self is drag.stack + dx = event.x - self.lastx + dy = event.y - self.lasty + if dx or dy: + self.lastx = event.x + self.lasty = event.y + if self.game.app.opt.shade: + self._updateShade() + for s in drag.shadows: + s.move(dx, dy) + for card in drag.cards: + card.moveBy(dx, dy) + drag.event = None + + def keepDragTimer(self): + drag = self.game.drag + after_cancel(drag.timer) + drag.timer = None + if drag.event: + self.keepDrag(drag.event) + self.canvas.update_idletasks() + + # create shadows, return a tuple of MfxCanvasImages + def createShadows(self, cards, dx=0, dy=0): + if not self.game.app.opt.shadow or self.canvas.preview > 1: + return () + l = len(cards) + if l == 0 or l > self.max_shadow_cards: + return () + images = self.game.app.images + cx, cy = cards[0].x, cards[0].y + for c in cards[1:]: + if c.x != cx or abs(c.y - cy) != images.CARD_YOFFSET: + return () + cy = c.y + img0, img1 = images.getShadow(0), images.getShadow(l) + if 0: + # Dynamically compute the shadow. Doesn't work because + # PhotoImage.copy() doesn't preserve transparency. + img1 = images.getShadow(13) + if img1: + h = images.CARDH - img0.height() + h = h + (l - 1) * self.CARD_YOFFSET[0] + if h < img1.height(): + import Tkinter + dest = Tkinter.PhotoImage(width=img1.width(), height=h) + dest.blank() + img1.tk.call(dest, "copy", img1.name, "-from", 0, 0, img1.width(), h) + assert dest.height() == h and dest.width() == img1.width() + #print h, img1.height(), dest.height() + img1 = dest + self._foo = img1 # keep a reference + elif h > img1.height(): + img1 = None + if img0 and img1: + c = cards[-1] + if self.CARD_YOFFSET[0] < 0: c = cards[0] + cx, cy = c.x + images.CARDW + dx, c.y + images.CARDH + dy + s1 = MfxCanvasImage(self.game.canvas, cx, cy - img0.height(), + image=img1, anchor=ANCHOR_SE) + s2 = MfxCanvasImage(self.game.canvas, cx, cy, + image=img0, anchor=ANCHOR_SE) + s1.lower(c.item) + s2.lower(c.item) + return (s1, s2) + return () + + # handle shade within a drag operation + def _deleteShade(self): + if self.game.drag.shade_img: + self.game.drag.shade_img.delete() + self.game.drag.shade_img = None + self.game.drag.shade_stack = None + + def _updateShade(self): + # optimized for speed - we use lots of local variables + game = self.game + images = game.app.images + img = images.getShade() + if img is None: + return + CW, CH = images.CARDW, images.CARDH + drag = game.drag + ##stacks = game.allstacks + c = drag.cards[0] + stacks = ( game.getClosestStack(c, drag.stack), ) + r1_0, r1_1, r1_2, r1_3 = c.x, c.y, c.x + CW, c.y + CH + sstack, sdiff, sx, sy = None, 999999999, 0, 0 + for s in stacks: + if s is None or s in drag.noshade_stacks: + continue + if s.cards: + c = s.cards[-1] + r2 = (c.x, c.y, c.x + CW, c.y + CH) + else: + r2 = (s.x, s.y, s.x + CW, s.y + CH) + if r1_2 <= r2[0] or r1_3 <= r2[1] or r2[2] <= r1_0 or r2[3] <= r1_1: + # rectangles do not intersect + continue + if s in drag.canshade_stacks: + pass + elif s.acceptsCards(drag.stack, drag.cards): + drag.canshade_stacks.append(s) + else: + drag.noshade_stacks.append(s) + continue + diff = (r1_0 - r2[0])**2 + (r1_1 - r2[1])**2 + if diff < sdiff: + sstack, sdiff, sx, sy = s, diff, r2[0], r2[1] + if sstack is drag.shade_stack: + return + if sstack is None: + self._deleteShade() + return + # move or create the shade image + drag.shade_stack = sstack + if drag.shade_img: + drag.shade_img.moveTo(sx, sy) + else: + img = MfxCanvasImage(game.canvas, sx, sy, image=img, anchor=ANCHOR_NW) + drag.shade_img = img + # raise/lower the shade image to the correct stacking order + if drag.shadows: + img.lower(drag.shadows[0]) + else: + img.lower(drag.cards[0].item) + + def _stopDrag(self): + drag = self.game.drag + after_cancel(drag.timer) + drag.timer = None + self._deleteShade() + drag.canshade_stacks = [] + drag.noshade_stacks = [] + for s in drag.shadows: + s.delete() + drag.shadows = [] + drag.stack = None + drag.cards = [] + + # finish a drag operation + def finishDrag(self, event=None): + if self.game.app.opt.dragcursor: + self.game.canvas.config(cursor=self.game.app.top_cursor) + drag = self.game.drag.copy() + self._stopDrag() + if drag.cards: + assert drag.stack is self + self.releaseHandler(event, drag) + + # cancel a drag operation + def cancelDrag(self, event=None): + if self.game.app.opt.dragcursor: + self.game.canvas.config(cursor=self.game.app.top_cursor) + drag = self.game.drag.copy() + self._stopDrag() + if drag.cards: + assert drag.stack is self + self.moveCardsBackHandler(event, drag) + + def getHelp(self): + # devel + return str(self) + + def getBaseCard(self): + return '' + + def _getBaseCard(self): + if self.cap.max_accept == 0: + return '' + br = self.cap.base_rank + s = _('Base card - %s.') + if br == NO_RANK: s = _('Empty row cannot be filled.') + elif br == -1: s = s % _('any card') + elif br == 10: s = s % _('Jack') + elif br == 11: s = s % _('Queen') + elif br == 12: s = s % _('King') + elif br == 0 : s = s % _('Ace') + else : s = s % str(br) + return s + + def getNumCards(self): + n = len(self.cards) + if n == 0 : return _('No cards') + elif n == 1 : return _('1 card') + else : return str(n)+_(' cards') + + +# /*********************************************************************** +# // Abstract interface that supports a concept of dealing. +# ************************************************************************/ + +class DealRow_StackMethods: + # Deal a card to each of the RowStacks. Return number of cards dealt. + def dealRow(self, rows=None, flip=1, reverse=0, frames=-1, sound=0): + if rows is None: rows = self.game.s.rows + if sound and frames and self.game.app.opt.animations: + self.game.startDealSample() + n = self.dealToStacks(rows, flip, reverse, frames) + if sound: + self.game.stopSamples() + return n + + # Same, but no error if not enough cards are available. + def dealRowAvail(self, rows=None, flip=1, reverse=0, frames=-1, sound=0): + if rows is None: rows = self.game.s.rows + if sound and frames and self.game.app.opt.animations: + self.game.startDealSample() + if len(self.cards) < len(rows): + rows = rows[:len(self.cards)] + n = self.dealToStacks(rows, flip, reverse, frames) + if sound: + self.game.stopSamples() + return n + + def dealToStacks(self, stacks, flip=1, reverse=0, frames=-1): + if not self.cards or not stacks: + return 0 + assert len(self.cards) >= len(stacks) + old_state = self.game.enterState(self.game.S_DEAL) + if reverse: + stacks = list(stacks) + stacks.reverse() + for r in stacks: + assert not self.getCard().face_up + assert r is not self + if frames == 0 and self.game.moves.state == self.game.S_INIT: + # optimized a little bit for initial dealing + c = self.removeCard(update=0) + r.addCard(c, update=0) + # doing the flip after the move seems to be a little faster + if flip: + c.showFace() + else: + if flip: + self.game.flipMove(self) + self.game.moveMove(1, self, r, frames=frames) + self.game.leaveState(old_state) + return len(stacks) + + # all Aces go to the Foundations + def dealToStacksOrFoundations(self, stacks, flip=1, reverse=0, frames=-1, rank=-1): + if rank < 0: + rank = self.game.s.foundations[0].cap.base_rank + if not self.cards or not stacks: + return 0 + old_state = self.game.enterState(self.game.S_DEAL) + if reverse: + stacks = list(stacks) + stacks.reverse() + n = 0 + for r in stacks: + assert r is not self + while self.cards: + n = n + 1 + if flip: + self.game.flipMove(self) + if flip and self.cards[-1].rank == rank: + for s in self.game.s.foundations: + assert s is not self + if s.acceptsCards(self, self.cards[-1:]): + self.game.moveMove(1, self, s, frames=frames) + break + else: + self.game.moveMove(1, self, r, frames=frames) + break + self.game.leaveState(old_state) + return n + + +class DealBaseCard_StackMethods: + def dealSingleBaseCard(self, frames=-1, update_saveinfo=1): + c = self.cards[-1] + self.dealBaseCards(ncards=1, frames=frames, update_saveinfo=0) + for s in self.game.s.foundations: + s.cap.base_rank = c.rank + if update_saveinfo: + cap = Struct(base_rank=c.rank) + self.game.saveinfo.stack_caps.append((s.id, cap)) + return c + + def dealBaseCards(self, ncards=1, frames=-1, update_saveinfo=1): + assert self.game.moves.state == self.game.S_INIT + assert not self.base_cards + while ncards > 0: + assert self.cards + c = self.cards[-1] + for s in self.game.s.foundations: + if not s.cards and (s.cap.base_suit < 0 or s.cap.base_suit == c.suit): + break + else: + assert 0 + s = None + s.cap.base_rank = c.rank + if update_saveinfo: + cap = Struct(base_rank=c.rank) + self.game.saveinfo.stack_caps.append((s.id, cap)) + if not c.face_up: + self.game.flipMove(self) + self.game.moveMove(1, self, s, frames=frames) + ncards = ncards - 1 + + +# /*********************************************************************** +# // The Talon is a stack with support for dealing. +# ************************************************************************/ + +class TalonStack(Stack, DealRow_StackMethods, DealBaseCard_StackMethods): + def __init__(self, x, y, game, max_rounds=1, num_deal=1, **cap): + Stack.__init__(self, x, y, game, cap=cap) + self.max_rounds = max_rounds + self.num_deal = num_deal + self.resetGame() + + def resetGame(self): + self.round = 1 + self.base_cards = [] # for DealBaseCard_StackMethods + + def assertStack(self): + Stack.assertStack(self) + n = self.game.gameinfo.redeals + if n < 0: assert self.max_rounds == n + else: assert self.max_rounds == n + 1 + + # Control of dealing is transferred to the game which usually + # transfers it back to the Talon - see dealCards() below. + def clickHandler(self, event): + return self.game.dealCards(sound=1) + + def rightclickHandler(self, event): + return self.clickHandler(event) + + # Usually called by Game.canDealCards() + def canDealCards(self): + return len(self.cards) > 0 + + # Actual dealing, usually called by Game.dealCards(). + # Either deal all cards in Game.startGame(), or subclass responsibility. + def dealCards(self, sound=0): + pass + + # remove all cards from all stacks + def removeAllCards(self): + for stack in self.game.allstacks: + while stack.cards: + ##stack.removeCard(update=0) + stack.removeCard(unhide=0, update=0) + for stack in self.game.allstacks: + stack.updateText() + + def updateText(self, update_rounds=1, update_redeal=1): + ##assertView(self) + Stack.updateText(self) + if update_rounds and self.game.preview <= 1: + if self.texts.rounds is not None: + t = _("Round %d") % self.round + self.texts.rounds.config(text=t) + if update_redeal: + deal = self.canDealCards() != 0 + if self.images.redeal is not None: + img = (self.getRedealImages())[deal] + if img is not None and img is not self.images.redeal_img: + self.images.redeal.config(image=img) + self.images.redeal_img = img + t = ("", _("Redeal"))[deal] + else: + t = (_("Stop"), _("Redeal"))[deal] + if self.texts.redeal is not None and self.game.preview <= 1: + if t != self.texts.redeal_str: + self.texts.redeal.config(text=t) + self.texts.redeal_str = t + + def prepareView(self): + Stack.prepareView(self) + if not self.is_visible or self.images.bottom is None: + return + if self.images.redeal is not None or self.texts.redeal is not None: + return + if self.game.preview > 1: + return + images = self.game.app.images + cx, cy, ca = self.x + images.CARDW/2, self.y + images.CARDH/2, "center" + if images.CARDW >= 54 and images.CARDH >= 54: + # add a redeal image above the bottom image + img = (self.getRedealImages())[self.max_rounds != 1] + if img is not None: + self.images.redeal = MfxCanvasImage(self.game.canvas, cx, cy, + image=img, anchor="center") + self.images.redeal_img = img + self.images.redeal.tkraise(self.top_bottom) + self.images.redeal.addtag(self.group) + self.top_bottom = self.images.redeal + if images.CARDH >= 90: + cy, ca = self.y + images.CARDH - 4, "s" + else: + ca = None + font = self.game.app.getFont("canvas_default") + text_width = Font(self.game.canvas, font).measure(_('Redeal')) + if images.CARDW >= text_width+4 and ca: + # add a redeal text above the bottom image + if self.max_rounds != 1: + images = self.game.app.images + self.texts.redeal = MfxCanvasText(self.game.canvas, cx, cy, + anchor=ca, font=font) + self.texts.redeal_str = "" + self.texts.redeal.tkraise(self.top_bottom) + self.texts.redeal.addtag(self.group) + self.top_bottom = self.texts.redeal + + def getBottomImage(self): + return self.game.app.images.getTalonBottom() + + def getRedealImages(self): + # returns a tuple of two PhotoImages + return self.game.app.gimages.redeal + + def getHelp(self): + if self.max_rounds == -2: nredeals = _('Variable redeals.') + elif self.max_rounds == -1: nredeals = _('Unlimited redeals.') + elif self.max_rounds == 1: nredeals = _('No redeals.') + elif self.max_rounds == 2: nredeals = _('One redeal.') + else: nredeals = str(self.max_rounds-1)+_(' redeals.') + ##round = _('Round #%d.') % self.round + return _('Talon.')+' '+nredeals ##+' '+round + + def getBaseCard(self): + return self._getBaseCard() + + +# A single click deals one card to each of the RowStacks. +class DealRowTalonStack(TalonStack): + def dealCards(self, sound=0): + return self.dealRowAvail(sound=sound) + + +# For games where the Talon is only used for the initial dealing. +class InitialDealTalonStack(TalonStack): + # no bindings + def initBindings(self): + pass + # no bottom + def getBottomImage(self): + return None + + +# /*********************************************************************** +# // An OpenStack is a stack where cards can be placed and dragged +# // (i.e. FoundationStack, RowStack, ReserveStack, ...) +# // +# // Note that it defaults to max_move=1 and max_accept=0. +# ************************************************************************/ + +class OpenStack(Stack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=0, max_cards=999999) + Stack.__init__(self, x, y, game, cap=cap) + + + # + # Capabilities {model} + # + + def acceptsCards(self, from_stack, cards): + # default for OpenStack: we cannot accept cards (max_accept defaults to 0) + return self.basicAcceptsCards(from_stack, cards) + + def canMoveCards(self, cards): + # default for OpenStack: we can move the top card (max_move defaults to 1) + return self.basicCanMoveCards(cards) + + def canFlipCard(self): + # default for OpenStack: we can flip the top card + if self.basicIsBlocked() or not self.cards: + return 0 + return not self.cards[-1].face_up + + def canDropCards(self, stacks): + if self.basicIsBlocked() or not self.cards: + return (None, 0) + cards = self.cards[-1:] + if self.canMoveCards(cards): + for s in stacks: + if s is not self and s.acceptsCards(self, cards): + return (s, 1) + return (None, 0) + + + # + # Mouse handlers {controller} + # + + def clickHandler(self, event): + flipstacks, dropstacks, quickstacks = self.game.getAutoStacks(event) + if self in flipstacks and self.canFlipCard(): + self.playFlipMove() + return -1 # continue this event (start a drag) + return 0 + + def rightclickHandler(self, event): + if self.doubleclickHandler(event): + return 1 + if self.game.app.opt.quickplay: + flipstacks, dropstacks, quickstacks = self.game.getAutoStacks(event) + if self in quickstacks: + n = self.quickPlayHandler(event) + self.game.stats.quickplay_moves += n + return n + return 0 + + def doubleclickHandler(self, event): + # flip or drop a card + flipstacks, dropstacks, quickstacks = self.game.getAutoStacks(event) + if self in flipstacks and self.canFlipCard(): + self.playFlipMove() + return -1 # continue this event (start a drag) + if self in dropstacks: + to_stack, ncards = self.canDropCards(self.game.s.foundations) + if to_stack: + self.game.playSample("autodrop", priority=30) + self.playMoveMove(ncards, to_stack, sound=0) + return 1 + return 0 + + def controlclickHandler(self, event): + # highlight matching cards + if self.game.app.opt.highlight_cards: + return self.highlightMatchingCards(event) + return 0 + + def releaseHandler(self, event, drag, sound=1): + cards = drag.cards + # check if we moved the card by at least 10 pixels + if event is not None: + dx, dy = event.x - drag.start_x, event.y - drag.start_y + if abs(dx) < 10 and abs(dy) < 10: + # move cards back to their origin stack + Stack.releaseHandler(self, event, drag, sound=sound) + return + ##print dx, dy + # get destination stack + stack = self.game.getClosestStack(cards[0], self) + # move cards + if not stack or stack is self or not stack.acceptsCards(self, cards): + # move cards back to their origin stack + Stack.releaseHandler(self, event, drag, sound=sound) + else: + # this code actually moves the cards to the new stack + self.playMoveMove(len(cards), stack, frames=0, sound=sound) + + def quickPlayHandler(self, event, from_stacks=None, to_stacks=None): + ##print 'quickPlayHandler', from_stacks, to_stacks + # from_stacks and to_stacks are meant for possible + # use in a subclasses + if from_stacks is None: + from_stacks = self.game.sg.dropstacks + if to_stacks is None: + ##to_stacks = self.game.s.rows + self.game.s.reserves + ##to_stacks = self.game.sg.dropstacks + to_stacks = self.game.s.foundations + self.game.sg.dropstacks + ##from pprint import pprint; pprint(to_stacks) + moves = [] + # + if not self.cards: + for s in from_stacks: + if s is not self and s.cards: + pile = s.getPile() + if pile and self.acceptsCards(s, pile): + score = self.game.getQuickPlayScore(len(pile), s, self) + moves.append((score, -len(moves), len(pile), s, self)) + else: + pile1, pile2 = None, self.getPile() + if pile2: + i = self._findCard(event) + if i >= 0: + pile = self.cards[i:] + if len(pile) != len(pile2) and self.canMoveCards(pile): + pile1 = pile + for pile in (pile1, pile2): + if not pile: + continue + for s in to_stacks: + if s is not self and s.acceptsCards(self, pile): + score = self.game.getQuickPlayScore(len(pile), self, s) + moves.append((score, -len(moves), len(pile), self, s)) + # + if moves: + moves.sort() + moves.reverse() + ##from pprint import pprint + ##pprint(moves) + if moves[0][0] >= 0: + ##self.game.playSample("startdrag") + moves[0][3].playMoveMove(moves[0][2], moves[0][4]) + return 1 + return 0 + + def getHelp(self): + if self.cap.max_accept == 0: + return _('Reserve. No building.') + return '' + + +# /*********************************************************************** +# // Foundations stacks +# ************************************************************************/ + +class AbstractFoundationStack(OpenStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, suit=suit, base_suit=suit, base_rank=ACE, + dir=1, max_accept=1, max_cards=13) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def canDropCards(self, stacks): + return (None, 0) + + def clickHandler(self, event): + return 0 + + def rightclickHandler(self, event): + return 0 + + def quickPlayHandler(self, event, from_stacks=None, to_stacks=None): + return 0 + + def getBottomImage(self): + return self.game.app.images.getSuitBottom(self.cap.base_suit) + + def getBaseCard(self): + return self._getBaseCard() + + +# A SameSuit_FoundationStack is the typical Foundation stack. +# It builds up in rank and suit. +class SS_FoundationStack(AbstractFoundationStack): + def acceptsCards(self, from_stack, cards): + if not AbstractFoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if self.cards: + # check the rank + if (self.cards[-1].rank + self.cap.dir) % self.cap.mod != cards[0].rank: + return 0 + return 1 + + def getHelp(self): + if self.cap.dir > 0: return _('Foundation. Build up by suit.') + elif self.cap.dir < 0: return _('Foundation. Build down by suit.') + else: return _('Foundation. Build by same rank.') + + +# A Rank_FoundationStack builds up in rank and ignores color and suit. +class RK_FoundationStack(SS_FoundationStack): + def __init__(self, x, y, game, suit=ANY_SUIT, **cap): + apply(SS_FoundationStack.__init__, (self, x, y, game, suit), cap) + + def assertStack(self): + SS_FoundationStack.assertStack(self) + assert self.cap.suit == ANY_SUIT + assert self.cap.color == ANY_COLOR + + def getHelp(self): + if self.cap.dir > 0: return _('Foundation. Build up regardless of suit.') + elif self.cap.dir < 0: return _('Foundation. Build down regardless of suit.') + else: return _('Foundation. Build by same rank.') + + +# A AlternateColor_FoundationStack builds up in rank and alternate color. +# It is used in only a few games. +class AC_FoundationStack(SS_FoundationStack): + def __init__(self, x, y, game, suit, **cap): + kwdefault(cap, base_suit=suit) + apply(SS_FoundationStack.__init__, (self, x, y, game, ANY_SUIT), cap) + + def acceptsCards(self, from_stack, cards): + if not SS_FoundationStack.acceptsCards(self, from_stack, cards): + return 0 + if self.cards: + # check the color + if cards[0].color == self.cards[-1].color: + return 0 + return 1 + + def getHelp(self): + if self.cap.dir > 0: return _('Foundation. Build up by alternate color.') + elif self.cap.dir < 0: return _('Foundation. Build down by alternate color.') + else: return _('Foundation. Build by same rank.') + + +# /*********************************************************************** +# // Abstract classes for row stacks. +# ************************************************************************/ + +# Abstract class. +class SequenceStack_StackMethods: + def _isSequence(self, cards): + # Are the cards in a basic sequence for our stack ? + raise SubclassResponsibility + + def _isAcceptableSequence(self, cards): + return self._isSequence(cards) + + def _isMoveableSequence(self, cards): + return self._isSequence(cards) + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + # cards must be an acceptable sequence + if not self._isAcceptableSequence(cards): + return 0 + # [topcard + cards] must be an acceptable sequence + if self.cards and not self._isAcceptableSequence([self.cards[-1]] + cards): + return 0 + return 1 + + def canMoveCards(self, cards): + return self.basicCanMoveCards(cards) and self._isMoveableSequence(cards) + + +# Abstract class. +class BasicRowStack(OpenStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, dir=-1, base_rank=ANY_RANK) + apply(OpenStack.__init__, (self, x, y, game), cap) + self.CARD_YOFFSET = game.app.images.CARD_YOFFSET + + def getHelp(self): + if self.cap.max_accept == 0: + return _('Row. No building.') + return '' + + #def getBaseCard(self): + # return self._getBaseCard() + + +# Abstract class. +class SequenceRowStack(SequenceStack_StackMethods, BasicRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=999999, max_accept=999999) + apply(BasicRowStack.__init__, (self, x, y, game), cap) + def getBaseCard(self): + return self._getBaseCard() + + +# /*********************************************************************** +# // Row stacks (the main playing stacks on the Tableau). +# ************************************************************************/ + +# +# Implementation of common row stacks follows here. +# + +# An AlternateColor_RowStack builds down by rank and alternate color. +# e.g. Klondike +class AC_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up by alternate color.') + elif self.cap.dir < 0: return _('Row. Build down by alternate color.') + else: return _('Row. Build by same rank.') + +# A SameColor_RowStack builds down by rank and same color. +# e.g. Klondike +class SC_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isSameColorSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up by color.') + elif self.cap.dir < 0: return _('Row. Build down by color.') + else: return _('Row. Build by same rank.') + +# A SameSuit_RowStack builds down by rank and suit. +class SS_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isSameSuitSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up by suit.') + elif self.cap.dir < 0: return _('Row. Build down by suit.') + else: return _('Row. Build by same rank.') + +# A Rank_RowStack builds down by rank ignoring suit. +class RK_RowStack(SequenceRowStack): + def _isSequence(self, cards): + return isRankSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up regardless of suit.') + elif self.cap.dir < 0: return _('Row. Build down regardless of suit.') + else: return _('Row. Build by same rank.') + +# A Freecell_AlternateColor_RowStack +class FreeCell_AC_RowStack(AC_RowStack): + def canMoveCards(self, cards): + max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1 + return len(cards) <= max_move and AC_RowStack.canMoveCards(self, cards) + +# A Freecell_SameSuit_RowStack (i.e. Baker's Game) +class FreeCell_SS_RowStack(SS_RowStack): + def canMoveCards(self, cards): + max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1 + return len(cards) <= max_move and SS_RowStack.canMoveCards(self, cards) + +# A Spider_AlternateColor_RowStack builds down by rank and alternate color, +# but accepts sequences that match by rank only. +class Spider_AC_RowStack(AC_RowStack): + def _isAcceptableSequence(self, cards): + return isRankSequence(cards, self.cap.mod, self.cap.dir) + +# A Spider_SameSuit_RowStack builds down by rank and suit, +# but accepts sequences that match by rank only. +class Spider_SS_RowStack(SS_RowStack): + def _isAcceptableSequence(self, cards): + return isRankSequence(cards, self.cap.mod, self.cap.dir) + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up regardless of suit.') + elif self.cap.dir < 0: return _('Row. Build down regardless of suit.') + else: return _('Row. Build by same rank.') + +# A Yukon_AlternateColor_RowStack builds down by rank and alternate color, +# but can move any face-up cards regardless of sequence. +class Yukon_AC_RowStack(BasicRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=999999, max_accept=999999) + apply(BasicRowStack.__init__, (self, x, y, game), cap) + + def _isSequence(self, c1, c2): + return (c1.rank + self.cap.dir) % self.cap.mod == c2.rank and c1.color != c2.color + + def acceptsCards(self, from_stack, cards): + if not self.basicAcceptsCards(from_stack, cards): + return 0 + # [topcard + card[0]] must be acceptable + if self.cards and not self._isSequence(self.cards[-1], cards[0]): + return 0 + return 1 + + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up by alternate color, can move any face-up cards regardless of sequence.') + elif self.cap.dir < 0: return _('Row. Build down by alternate color, can move any face-up cards regardless of sequence.') + else: return _('Row. Build by same rank, can move any face-up cards regardless of sequence.') + + +# A Yukon_SameSuit_RowStack builds down by rank and suit, +# but can move any face-up cards regardless of sequence. +class Yukon_SS_RowStack(Yukon_AC_RowStack): + def _isSequence(self, c1, c2): + return (c1.rank + self.cap.dir) % self.cap.mod == c2.rank and c1.suit == c2.suit + def getHelp(self): + if self.cap.dir > 0: return _('Row. Build up by suit, can move any face-up cards regardless of sequence.') + elif self.cap.dir < 0: return _('Row. Build down by suit, can move any face-up cards regardless of sequence.') + else: return _('Row. Build by same rank, can move any face-up cards regardless of sequence.') + +# +# King-versions of some of the above stacks: they accepts only Kings or +# sequences starting with a King as base_rank cards (i.e. when empty). +# + +class KingAC_RowStack(AC_RowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, base_rank=KING) + apply(AC_RowStack.__init__, (self, x, y, game), cap) + +class KingSS_RowStack(SS_RowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, base_rank=KING) + apply(SS_RowStack.__init__, (self, x, y, game), cap) + +class KingRK_RowStack(RK_RowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, base_rank=KING) + apply(RK_RowStack.__init__, (self, x, y, game), cap) + + +# up or down by color +class UD_SC_RowStack(SequenceRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1) + apply(SequenceRowStack.__init__, (self, x, y, game), cap) + def _isSequence(self, cards): + return (isSameColorSequence(cards, self.cap.mod, 1) or + isSameColorSequence(cards, self.cap.mod, -1)) + def getHelp(self): + return _('Row. Build up or down by color.') + +# up or down by alternate color +class UD_AC_RowStack(SequenceRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1) + apply(SequenceRowStack.__init__, (self, x, y, game), cap) + def _isSequence(self, cards): + return (isAlternateColorSequence(cards, self.cap.mod, 1) or + isAlternateColorSequence(cards, self.cap.mod, -1)) + def getHelp(self): + return _('Row. Build up or down by alternate color.') + +# up or down by suit +class UD_SS_RowStack(SequenceRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1) + apply(SequenceRowStack.__init__, (self, x, y, game), cap) + def _isSequence(self, cards): + return (isSameSuitSequence(cards, self.cap.mod, 1) or + isSameSuitSequence(cards, self.cap.mod, -1)) + def getHelp(self): + return _('Row. Build up or down by suit.') + +# up or down by rank ignoring suit +class UD_RK_RowStack(SequenceRowStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1, max_accept=1) + apply(SequenceRowStack.__init__, (self, x, y, game), cap) + def _isSequence(self, cards): + return (isRankSequence(cards, self.cap.mod, 1) or + isRankSequence(cards, self.cap.mod, -1)) + def getHelp(self): + return _('Row. Build up or down regardless of suit.') + + + + +# /*********************************************************************** +# // WasteStack (a helper stack for the Talon, e.g. in Klondike) +# ************************************************************************/ + +class WasteStack(OpenStack): + def getHelp(self): + return _('Waste.') + + +class WasteTalonStack(TalonStack): + # A single click moves the top cards to the game's waste and + # moves it face up; if we're out of cards, it moves the waste + # back to the talon and increases the number of rounds (redeals). + def __init__(self, x, y, game, max_rounds, num_deal=1, waste=None, **cap): + apply(TalonStack.__init__, (self, x, y, game, max_rounds, num_deal), cap) + self.waste = waste + + def prepareStack(self): + TalonStack.prepareStack(self) + if self.waste is None: + self.waste = self.game.s.waste + + def canDealCards(self): + waste = self.waste + if self.cards: + num_cards = min(len(self.cards), self.num_deal) + return len(waste.cards) + num_cards <= waste.cap.max_cards + elif waste.cards and self.round != self.max_rounds: + return 1 + return 0 + + def dealCards(self, sound=0): + old_state = self.game.enterState(self.game.S_DEAL) + num_cards = 0 + waste = self.waste + if self.cards: + if sound and not self.game.demo: + self.game.playSample("dealwaste") + num_cards = min(len(self.cards), self.num_deal) + assert len(waste.cards) + num_cards <= waste.cap.max_cards + for i in range(num_cards): + if not self.cards[-1].face_up: + self.game.flipMove(self) + self.game.moveMove(1, self, waste, frames=4, shadow=0) + self.fillStack() + elif waste.cards and self.round != self.max_rounds: + if sound: + self.game.playSample("turnwaste", priority=20) + num_cards = len(waste.cards) + self.game.turnStackMove(waste, self, update_flags=1) + self.game.leaveState(old_state) + return num_cards + + +class FaceUpWasteTalonStack(WasteTalonStack): + def canFlipCard(self): + return len(self.cards) > 0 and not self.cards[-1].face_up + + def fillStack(self): + if self.canFlipCard(): + self.game.flipMove(self) + + +class OpenTalonStack(TalonStack, OpenStack): + canMoveCards = OpenStack.canMoveCards + canDropCards = OpenStack.canDropCards + releaseHandler = OpenStack.releaseHandler + + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_move=1) + apply(TalonStack.__init__, (self, x, y, game), cap) + + def canDealCards(self): + return 0 + + def canFlipCard(self): + return len(self.cards) > 0 and not self.cards[-1].face_up + + def fillStack(self): + if self.canFlipCard(): + self.game.flipMove(self) + + def clickHandler(self, event): + if self.canDealCards(): + return TalonStack.clickHandler(self, event) + else: + return OpenStack.clickHandler(self, event) + + +# /*********************************************************************** +# // ReserveStack (free cell) +# ************************************************************************/ + +class ReserveStack(OpenStack): + def __init__(self, x, y, game, **cap): + kwdefault(cap, max_accept=1, max_cards=1) + apply(OpenStack.__init__, (self, x, y, game), cap) + + def getBottomImage(self): + return self.game.app.images.getReserveBottom() + + def getHelp(self): + if self.cap.max_accept == 0: + return _('Reserve. No building.') + return _('Free cell.') + + +# /*********************************************************************** +# // InvisibleStack (an internal off-screen stack to hold cards) +# ************************************************************************/ + +class InvisibleStack(Stack): + def __init__(self, game, **cap): + x, y = -500, -500 - len(game.allstacks) + kwdefault(cap, max_move=0, max_accept=0) + Stack.__init__(self, x, y, game, cap=cap) + + def assertStack(self): + Stack.assertStack(self) + assert not self.is_visible + + # no bindings + def initBindings(self): + pass + + # no bottom + def getBottomImage(self): + return None + + +# /*********************************************************************** +# // A StackWrapper is a functor (function object) that creates a +# // new stack when called, i.e. it wraps the constructor. +# // +# // "cap" are the capabilites, see class Stack above. +# ************************************************************************/ + +# self.cap override any call-time cap +class StackWrapper: + def __init__(self, stack_class, **cap): + assert type(stack_class) is types.ClassType + assert issubclass(stack_class, Stack) + self.stack_class = stack_class + self.cap = cap + + # return a new stack (an instance of the stack class) + def __call__(self, x, y, game, **cap): + # must preserve self.cap, so create a shallow copy + c = self.cap.copy() + apply(kwdefault, (c,), cap) + return apply(self.stack_class, (x, y, game), c) + + +# call-time cap override self.cap +class WeakStackWrapper(StackWrapper): + def __call__(self, x, y, game, **cap): + apply(kwdefault, (cap,), self.cap) + return apply(self.stack_class, (x, y, game), cap) + + +# self.cap only, call-time cap is completely ignored +class FullStackWrapper(StackWrapper): + def __call__(self, x, y, game, **cap): + return apply(self.stack_class, (x, y, game), self.cap) + + +# /*********************************************************************** +# // +# ************************************************************************/ + diff --git a/pysollib/stats.py b/pysollib/stats.py new file mode 100644 index 0000000000..d3039bfa75 --- /dev/null +++ b/pysollib/stats.py @@ -0,0 +1,213 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import os, sys, time, types + +# PySol imports +from mfxutil import SubclassResponsibility, Struct, destruct +from mfxutil import format_time +from util import PACKAGE, VERSION +from gamedb import GI + +# Toolkit imports +from pysoltk import MfxDialog + + +# // FIXME - this a quick hack and needs a rewrite + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class PysolStatsFormatter: + def __init__(self, app): + self.app = app + + # + # + # + + class StringWriter: + def __init__(self): + self.text = "" + + def p(self, s): + self.text = self.text + s + + def nl(self, count=1): + self.p("\n" * count) + + def pheader(self, s): + self.p(s) + + def pstats(self, *args, **kwargs): + s = "%-30s %7s %7s %7s %7s %7s %7s\n" % args + self.p(s) + + def plog(self, gamename, gamenumber, date, status, gameid=-1, won=-1): + self.p("%-25s %-20s %17s %s\n" % (gamename, gamenumber, date, status)) + + + class FileWriter(StringWriter): + def __init__(self, file): + self.file = file + + def p(self, s): + self.file.write(s.encode('utf-8')) + + # + # + # + + def writeHeader(self, writer, header, pagewidth=72): + date = time.ctime(time.time()) + date = time.strftime("%Y-%m-%d %H:%M", time.localtime(time.time())) + blanks = max(pagewidth - len(header) - len(date), 1) + writer.pheader(header + " "*blanks + date + "\n") + writer.pheader("-" * pagewidth + "\n") + writer.pheader("\n") + + def writeStats(self, writer, player, header, sort_by='name'): + app = self.app + # + sort_functions = { + 'name': app.getGamesIdSortedByName, + 'played': app.getGamesIdSortedByPlayed, + 'won': app.getGamesIdSortedByWon, + 'lost': app.getGamesIdSortedByLost, + 'time': app.getGamesIdSortedByPlayingTime, + 'moves': app.getGamesIdSortedByMoves, + 'percent': app.getGamesIdSortedByPercent, + } + sort_func = sort_functions[sort_by] + + self.writeHeader(writer, header, 62) + writer.pstats(player or _("Demo games"), + _("Played"), + _("Won"), + _("Lost"), + _('Playing time'), + _('Moves'), + _("% won")) + writer.nl() + twon, tlost, tgames, ttime, tmoves = 0, 0, 0, 0, 0 + g = sort_func() + for id in g: + name = app.getGameMenuitemName(id) + #won, lost = app.stats.getStats(player, id) + won, lost, time, moves = app.stats.getFullStats(player, id) + twon, tlost = twon + won, tlost + lost + ttime, tmoves = ttime+time, tmoves+moves + if won + lost > 0: perc = "%.1f" % (100.0 * won / (won + lost)) + else: perc = "0.0" + if won > 0 or lost > 0 or id == app.game.id: + #writer.pstats(name, won+lost, won, lost, perc, gameid=id) + t = format_time(time) + m = str(round(moves, 1)) + writer.pstats(name, won+lost, won, lost, t, m, perc, gameid=id) + tgames = tgames + 1 + writer.nl() + won, lost = twon, tlost + if won + lost > 0: + if won > 0: + time = format_time(ttime/won) + moves = round(tmoves/won, 1) + else: + time = format_time(0) + moves = 0 + perc = "%.1f" % (100.0*won/(won+lost)) + else: perc = "0.0" + writer.pstats(_("Total (%d out of %d games)") % (tgames, len(g)), + won+lost, won, lost, time, moves, perc) + writer.nl(2) + return tgames + + def _writeLog(self, writer, player, header, prev_games): + if not player or not prev_games: + return 0 + self.writeHeader(writer, header, 71) + writer.plog(_("Game"), _("Game number"), _("Started at "), _("Status")) + writer.nl() + twon, tlost = 0, 0 + for pg in prev_games: + if type(pg) is not types.TupleType: + continue + if len(pg) == 5: + pg = pg + ("", None, None, 1) + elif len(pg) == 7: + pg = pg + (None, 1) + elif len(pg) == 8: + pg = pg + (1,) + if len(pg) < 8: + continue + gameid = pg[0] + if type(gameid) is not types.IntType: + continue + gi = self.app.getGameInfo(gameid) + if not gi: + gi = self.app.getGameInfo(GI.PROTECTED_GAMES.get(gameid)) + if gi: + name = gi.short_name + else: + name = _("** UNKNOWN %d **") % gameid + f = pg[1] + if len(f) == 16: + ##gamenumber = "%s-%s-%s-%s" % (f[0:4], f[4:8], f[8:12], f[12:16]) + gamenumber = "%s-%s-%s" % (f[4:8], f[8:12], f[12:16]) + elif len(f) <= 20: + gamenumber = f + else: + gamenumber = _("** ERROR **") + date = time.strftime("%Y-%m-%d %H:%M", time.localtime(pg[3])) + if pg[2] >= 0: + won = pg[2] > 0 + twon, tlost = twon + won, tlost + (1 - won) + status = "*error*" + if -2 <= pg[2] <= 2: + status = (_("Loaded"), _("Not won"), _("Lost"), _("Won"), _("Perfect")) [pg[2]+2] + writer.plog(name, gamenumber, date, status, gameid=gameid, won=pg[2]) + writer.nl(2) + return 1 + + def writeFullLog(self, writer, player, header): + prev_games = self.app.stats.prev_games.get(player) + return self._writeLog(writer, player, header, prev_games) + + def writeSessionLog(self, writer, player, header): + prev_games = self.app.stats.session_games.get(player) + return self._writeLog(writer, player, header, prev_games) diff --git a/pysollib/tk/__init__.py b/pysollib/tk/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pysollib/tk/card.py b/pysollib/tk/card.py new file mode 100644 index 0000000000..3daa733969 --- /dev/null +++ b/pysollib/tk/card.py @@ -0,0 +1,289 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['Card'] + +# imports + +# PySol imports +from pysollib.acard import AbstractCard + +# Toolkit imports +from tkconst import tkversion, TK_DASH_PATCH +from tkcanvas import MfxCanvasGroup, MfxCanvasImage + + +# /*********************************************************************** +# // +# ************************************************************************/ + +# any Tk version +class _HideableCard_1(AbstractCard): + def hide(self, stack): + if stack is self.hide_stack: + return + if self.hide_stack: + hx, hy = stack.hide_x - self.hide_x, stack.hide_y - self.hide_y + else: + hx, hy = stack.hide_x, stack.hide_y + ####self.item.move(hx, hy) + item = self.item + item.canvas.tk.call(item.canvas._w, "move", item.id, hx, hy) + self.hide_x, self.hide_y = stack.hide_x, stack.hide_y + self.hide_stack = stack + ##print "hide:", self.id, hx, hy, self.item.coords() + + def unhide(self): + if self.hide_stack is None: + return 0 + ####self.item.move(-self.hide_x, -self.hide_y) + item = self.item + item.canvas.tk.call(item.canvas._w, "move", item.id, -self.hide_x, -self.hide_y) + ##print "unhide:", self.id, -self.hide_x, -self.hide_y, self.item.coords() + self.hide_x, self.hide_y = 0, 0 + self.hide_stack = None + return 1 + + +# needs Tk 8.3.0 or better +class _HideableCard_2(AbstractCard): + def hide(self, stack): + if stack is self.hide_stack: + return + self.item.config(state="hidden") + self.hide_stack = stack + ##print "hide:", self.id, self.item.coords() + + def unhide(self): + if self.hide_stack is None: + return 0 + ##print "unhide:", self.id, self.item.coords() + self.item.config(state="normal") + self.hide_stack = None + return 1 + + +_HideableCard =_HideableCard_1 +if 1 and tkversion >= (8, 3, 0, 0): + _HideableCard =_HideableCard_2 + + +# /*********************************************************************** +# // New implemetation since 2.10 +# // +# // We use a single CanvasImage and call CanvasImage.config() to +# // turn the card. +# // This makes turning cards a little bit slower, but dragging cards +# // around is noticeable faster as the total number of images is +# // reduced by half. +# ************************************************************************/ + +class _OneImageCard(_HideableCard): + def __init__(self, id, deck, suit, rank, game, x=0, y=0): + _HideableCard.__init__(self, id, deck, suit, rank, game, x=x, y=y) + self._face_image = game.getCardFaceImage(deck, suit, rank) + self._back_image = game.getCardBackImage(deck, suit, rank) + self._active_image = self._back_image + self.item = MfxCanvasImage(game.canvas, self.x, self.y, image=self._active_image, anchor="nw") + ##self._setImage = self.item.config + + def _setImage(self, image): + if image is not self._active_image: + self.item.config(image=image) + self._active_image = image + + def showFace(self, unhide=1): + if not self.face_up: + self._setImage(image=self._face_image) + self.tkraise(unhide) + self.face_up = 1 + + def showBack(self, unhide=1): + if self.face_up: + self._setImage(image=self._back_image) + self.tkraise(unhide) + self.face_up = 0 + + def updateCardBackground(self, image): + self._back_image = image + if not self.face_up: + self._setImage(image=image) + + # + # optimized by inlining + # + + def moveBy(self, dx, dy): + dx, dy = int(dx), int(dy) + self.x = self.x + dx + self.y = self.y + dy + item = self.item + item.canvas.tk.call(item.canvas._w, "move", item.id, dx, dy) + + +# /*********************************************************************** +# // New idea since 3.00 +# // +# // Hide a card by configuring the canvas image to None. +# ************************************************************************/ + +class _OneImageCardWithHideByConfig(_OneImageCard): + def hide(self, stack): + if stack is self.hide_stack: + return + self._setImage(image=None) + self.hide_stack = stack + + def unhide(self): + if self.hide_stack is None: + return 0 + if self.face_up: + self._setImage(image=self._face_image) + else: + self._setImage(image=self._back_image) + self.hide_stack = None + return 1 + + # + # much like in _OneImageCard + # + + def showFace(self, unhide=1): + if not self.face_up: + if unhide: + self._setImage(image=self._face_image) + self.item.tkraise() + self.face_up = 1 + + def showBack(self, unhide=1): + if self.face_up: + if unhide: + self._setImage(image=self._back_image) + self.item.tkraise() + self.face_up = 0 + + def updateCardBackground(self, image): + self._back_image = image + if not self.face_up and not self.hide_stack: + self._setImage(image=image) + + +# /*********************************************************************** +# // Old implemetation prior to 2.10 +# // +# // The card consists of two CanvasImages. To show the card face up, +# // the face item is placed in front of the back. To show it face +# // down, this is reversed. +# ************************************************************************/ + +class _TwoImageCard(_HideableCard): + # Private instance variables: + # __face, __back -- the canvas items making up the card + def __init__(self, id, deck, suit, rank, game, x=0, y=0): + _HideableCard.__init__(self, id, deck, suit, rank, game, x=x, y=y) + self.item = MfxCanvasGroup(game.canvas) + self.__face = MfxCanvasImage(game.canvas, self.x, self.y, image=game.getCardFaceImage(deck, suit, rank), anchor="nw") + self.__back = MfxCanvasImage(game.canvas, self.x, self.y, image=game.getCardBackImage(deck, suit, rank), anchor="nw") + self.__face.addtag(self.item) + self.__back.addtag(self.item) + + def showFace(self, unhide=1): + if not self.face_up: + if TK_DASH_PATCH: + self.__back.config(state="hidden") + self.__face.config(state="normal") + self.__face.tkraise() + self.tkraise(unhide) + self.face_up = 1 + + def showBack(self, unhide=1): + if self.face_up: + if TK_DASH_PATCH: + self.__face.config(state="hidden") + self.__back.config(state="normal") + self.__back.tkraise() + self.tkraise(unhide) + self.face_up = 0 + + def updateCardBackground(self, image): + self.__back.config(image=image) + + +# /*********************************************************************** +# // New idea since 2.90 +# // +# // The card consists of two CanvasImages. Instead of raising +# // one image above the other we move the inactive image out +# // of the visible canvas. +# ************************************************************************/ + +class _TwoImageCardWithHideItem(_HideableCard): + # Private instance variables: + # __face, __back -- the canvas items making up the card + def __init__(self, id, deck, suit, rank, game, x=0, y=0): + _HideableCard.__init__(self, id, deck, suit, rank, game, x=x, y=y) + self.item = MfxCanvasGroup(game.canvas) + self.__face = MfxCanvasImage(game.canvas, self.x, self.y + 11000, image=game.getCardFaceImage(deck, suit, rank), anchor="nw") + self.__back = MfxCanvasImage(game.canvas, self.x, self.y, image=game.getCardBackImage(deck, suit, rank), anchor="nw") + self.__face.addtag(self.item) + self.__back.addtag(self.item) + + def showFace(self, unhide=1): + if not self.face_up: + self.__back.move(0, 10000) + ##self.__face.tkraise() + self.__face.move(0, -11000) + self.tkraise(unhide) + self.face_up = 1 + + def showBack(self, unhide=1): + if self.face_up: + self.__face.move(0, 11000) + ##self.__back.tkraise() + self.__back.move(0, -10000) + self.tkraise(unhide) + self.face_up = 0 + + def updateCardBackground(self, image): + self.__back.config(image=image) + + + +# choose the implementation +Card = _TwoImageCardWithHideItem +Card = _TwoImageCard +Card = _OneImageCardWithHideByConfig +Card = _OneImageCard + diff --git a/pysollib/tk/colorsdialog.py b/pysollib/tk/colorsdialog.py new file mode 100644 index 0000000000..f0d050622c --- /dev/null +++ b/pysollib/tk/colorsdialog.py @@ -0,0 +1,138 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = ['ColorsDialog'] + +# imports +import os, sys +from Tkinter import * +from tkColorChooser import askcolor + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog + +# /*********************************************************************** +# // +# ************************************************************************/ + +class ColorsDialog(MfxDialog): + def __init__(self, parent, title, app, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + + frame = Frame(top_frame) + frame.pack(expand=YES, fill=BOTH, padx=5, pady=10) + frame.columnconfigure(0, weight=1) + + self.table_text_color_var = BooleanVar() + self.table_text_color_var.set(app.opt.table_text_color) + self.table_text_color_value_var = StringVar() + self.table_text_color_value_var.set(app.opt.table_text_color_value) + ##self.table_color_var = StringVar() + ##self.table_color_var.set(app.opt.table_color) + self.highlight_piles_colors_var = StringVar() + self.highlight_piles_colors_var.set(app.opt.highlight_piles_colors[1]) + self.highlight_cards_colors_1_var = StringVar() + self.highlight_cards_colors_1_var.set(app.opt.highlight_cards_colors[1]) + self.highlight_cards_colors_2_var = StringVar() + self.highlight_cards_colors_2_var.set(app.opt.highlight_cards_colors[3]) + self.highlight_samerank_colors_1_var = StringVar() + self.highlight_samerank_colors_1_var.set(app.opt.highlight_samerank_colors[1]) + self.highlight_samerank_colors_2_var = StringVar() + self.highlight_samerank_colors_2_var.set(app.opt.highlight_samerank_colors[3]) + self.hintarrow_color_var = StringVar() + self.hintarrow_color_var.set(app.opt.hintarrow_color) + self.highlight_not_matching_color_var = StringVar() + self.highlight_not_matching_color_var.set(app.opt.highlight_not_matching_color) + # + c = Checkbutton(frame, variable=self.table_text_color_var, + text=_("Text foreground:"), anchor=W) + c.grid(row=0, column=0, sticky=W+E) + l = Label(frame, width=10, height=2, + bg=self.table_text_color_value_var.get(), + textvariable=self.table_text_color_value_var) + l.grid(row=0, column=1, padx=5) + b = Button(frame, text=_('Change...'), width=10, + command=lambda l=l: self.selectColor(l)) + b.grid(row=0, column=2) + row = 1 + for title, var in ( + ##('Table:', self.table_color_var), + (_('Highlight piles:'), self.highlight_piles_colors_var), + (_('Highlight cards 1:'), self.highlight_cards_colors_1_var), + (_('Highlight cards 2:'), self.highlight_cards_colors_2_var), + (_('Highlight same rank 1:'), self.highlight_samerank_colors_1_var), + (_('Highlight same rank 2:'), self.highlight_samerank_colors_2_var), + (_('Hint arrow:'), self.hintarrow_color_var), + (_('Highlight not matching:'), self.highlight_not_matching_color_var), + ): + Label(frame, text=title, anchor=W).grid(row=row, column=0, sticky=W+E) + l = Label(frame, width=10, height=2, + bg=var.get(), textvariable=var) + l.grid(row=row, column=1, padx=5) + b = Button(frame, text=_('Change...'), width=10, + command=lambda l=l: self.selectColor(l)) + b.grid(row=row, column=2) + row += 1 + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + # + self.table_text_color = self.table_text_color_var.get() + self.table_text_color_value = self.table_text_color_value_var.get() + ##self.table_color = self.table_color_var.get() + self.highlight_piles_colors = (None, + self.highlight_piles_colors_var.get()) + self.highlight_cards_colors = (None, + self.highlight_cards_colors_1_var.get(), + None, + self.highlight_cards_colors_2_var.get()) + self.highlight_samerank_colors = (None, + self.highlight_samerank_colors_1_var.get(), + None, + self.highlight_samerank_colors_2_var.get()) + self.hintarrow_color = self.hintarrow_color_var.get() + self.highlight_not_matching_color = self.highlight_not_matching_color_var.get() + + def selectColor(self, label): + c = askcolor(master=self.top, initialcolor=label.cget('bg'), + title=_("Select color")) + if c and c[1]: + label.configure(bg=c[1]) + #label.configure(text=c[1]) # don't work + label.setvar(label.cget('textvariable'), c[1]) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth = 0, + ) + return MfxDialog.initKw(self, kw) + + + + diff --git a/pysollib/tk/demooptionsdialog.py b/pysollib/tk/demooptionsdialog.py new file mode 100644 index 0000000000..ba62f499a3 --- /dev/null +++ b/pysollib/tk/demooptionsdialog.py @@ -0,0 +1,112 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['DemoOptionsDialog'] + +# imports +import os, sys, Tkinter + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog + +# /*********************************************************************** +# // +# ************************************************************************/ + +class DemoOptionsDialog(MfxDialog): + def __init__(self, parent, title, app, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.demo_logo_var = Tkinter.BooleanVar() + self.demo_logo_var.set(app.opt.demo_logo != 0) + self.demo_score_var = Tkinter.BooleanVar() + self.demo_score_var.set(app.opt.demo_score != 0) + self.demo_sleep_var = Tkinter.DoubleVar() + self.demo_sleep_var.set(app.opt.demo_sleep) + widget = Tkinter.Checkbutton(top_frame, variable=self.demo_logo_var, + text=_("Display floating Demo logo")) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + widget = Tkinter.Checkbutton(top_frame, variable=self.demo_score_var, + text=_("Show score in statusbar")) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + widget = Tkinter.Scale(top_frame, from_=0.2, to=9.9, + resolution=0.1, orient=Tkinter.HORIZONTAL, + length="3i", label=_("Set demo delay in seconds"), + variable=self.demo_sleep_var, takefocus=0) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + # + self.demo_logo = self.demo_logo_var.get() + self.demo_score = self.demo_score_var.get() + self.demo_sleep = self.demo_sleep_var.get() + + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth = 0, + ) + return MfxDialog.initKw(self, kw) + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +def demooptionsdialog_main(args): + from tkutil import wm_withdraw + opt = Struct(demo_logo=1, demo_sleep=1.5) + app = Struct(opt=opt) + tk = Tkinter.Tk() + wm_withdraw(tk) + tk.update() + d = DemoOptionsDialog(tk, "Demo options", app) + print d.status, d.button, ":", d.demo_logo, d.demo_sleep + return 0 + +if __name__ == "__main__": + import sys + sys.exit(demooptionsdialog_main(sys.argv)) + diff --git a/pysollib/tk/edittextdialog.py b/pysollib/tk/edittextdialog.py new file mode 100644 index 0000000000..47705c5b72 --- /dev/null +++ b/pysollib/tk/edittextdialog.py @@ -0,0 +1,129 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['DisplayTextDialog', 'EditTextDialog'] + +# imports +import os, sys, Tkinter + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog +from tkhtml import MfxScrolledText, MfxReadonlyScrolledText + +# /*********************************************************************** +# // +# ************************************************************************/ + +class DisplayTextDialog(MfxDialog): + Text_Class = MfxReadonlyScrolledText + + def __init__(self, parent, title, text, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + bg = top_frame["bg"] + self.text_w = self.Text_Class(top_frame, bd=1, relief="sunken", + wrap="word", width=64, height=16, + bg=bg) + self.text = "" + if text: + self.text = text + old_state = self.text_w["state"] + self.text_w.config(state="normal") + self.text_w.insert("insert", self.text) + self.text_w.config(state=old_state) + self.text_w.pack(side="top", fill="both", expand=1) + ###self.text_w.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + # + focus = self.createButtons(bottom_frame, kw) + #focus = self.text_w + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"),), default=0, + resizable = 1, + separatorwidth = 0, + ) + return MfxDialog.initKw(self, kw) + + +class EditTextDialog(DisplayTextDialog): + Text_Class = MfxScrolledText + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=-1, + resizable = 1, + separatorwidth = 0, + ) + return MfxDialog.initKw(self, kw) + + def destroy(self): + self.text = self.text_w.get("1.0", "end") + DisplayTextDialog.destroy(self) + + def wmDeleteWindow(self, *event): # ignore + pass + + def mCancel(self, *event): # ignore + pass + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +def edittextdialog_main(args): + from tkutil import wm_withdraw + tk = Tkinter.Tk() + wm_withdraw(tk) + tk.update() + d = DisplayTextDialog(tk, "Comment for game #12345", text="Test") + d = EditTextDialog(tk, "Comment for game #12345", text="Test") + print d.text + return 0 + +if __name__ == "__main__": + import sys + sys.exit(edittextdialog_main(sys.argv)) + diff --git a/pysollib/tk/fontsdialog.py b/pysollib/tk/fontsdialog.py new file mode 100644 index 0000000000..a5a10203e7 --- /dev/null +++ b/pysollib/tk/fontsdialog.py @@ -0,0 +1,211 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = ['FontsDialog'] + +# imports +import os, sys +import types +from Tkinter import * +import tkFont + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog +from tkutil import bind + +# /*********************************************************************** +# // +# ************************************************************************/ + +class FontChooserDialog(MfxDialog): + def __init__(self, parent, title, init_font, **kw): + ##print init_font + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + + self.font_family = 'Helvetica' + self.font_size = 12 + self.font_weight = 'normal' + self.font_slant = 'roman' + + if not init_font is None: + assert 2 <= len(init_font) <= 4 + assert type(init_font[1]) is types.IntType + self.font_family, self.font_size = init_font[:2] + if len(init_font) > 2: + if init_font[2] in ['bold', 'normal']: + self.font_weight = init_font[2] + elif init_font[2] in ['italic', 'roman']: + self.font_slant = init_font[2] + else: + raise TypeError, 'invalid font style: '+ init_font[2] + if len(init_font) > 3: + if init_font[3] in ['bold', 'normal']: + self.font_weight = init_font[3] + elif init_font[2] in ['italic', 'roman']: + self.font_slant = init_font[3] + else: + raise TypeError, 'invalid font style: '+ init_font[3] + + #self.family_var = StringVar() + self.weight_var = BooleanVar() + self.slant_var = BooleanVar() + self.size_var = IntVar() + + frame = Frame(top_frame) + frame.pack(expand=YES, fill=BOTH, padx=5, pady=10) + frame.columnconfigure(0, weight=1) + #frame.rowconfigure(1, weight=1) + self.entry = Entry(frame, bg='white') + self.entry.grid(row=0, column=0, columnspan=2, sticky=W+E+N+S) + self.entry.insert(END, _('abcdefghABCDEFGH')) + self.list_box = Listbox(frame, width=36) + sb = Scrollbar(frame) + self.list_box.configure(yscrollcommand=sb.set) + sb.configure(command=self.list_box.yview) + self.list_box.grid(row=1, column=0, sticky=W+E+N+S) # rowspan=4 + sb.grid(row=1, column=1, sticky=N+S) + bind(self.list_box, '<>', self.fontupdate) + ##self.list_box.focus() + cb1 = Checkbutton(frame, anchor=W, text=_('Bold'), + command=self.fontupdate, variable=self.weight_var) + cb1.grid(row=2, column=0, columnspan=2, sticky=W+E) + cb2 = Checkbutton(frame, anchor=W, text=_('Italic'), + command=self.fontupdate, variable=self.slant_var) + cb2.grid(row=3, column=0, columnspan=2, sticky=W+E) + + sc = Scale(frame, from_=6, to=40, resolution=1, + #label='Size', + orient=HORIZONTAL, + command=self.fontupdate, variable=self.size_var) + sc.grid(row=4, column=0, columnspan=2, sticky=W+E+N+S) + # + self.size_var.set(self.font_size) + self.weight_var.set(self.font_weight == 'bold') + self.slant_var.set(self.font_slant == 'italic') + font_families = list(tkFont.families()) + font_families.sort() + selected = -1 + n = 0 + for font in font_families: + self.list_box.insert(END, font) + if font.lower() == self.font_family.lower(): + selected = n + n += 1 + if selected >= 0: + self.list_box.select_set(selected) + self.list_box.see(selected) + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + self.font = (self.font_family, self.font_size, + self.font_slant, self.font_weight) + + def fontupdate(self, *args): + if self.list_box.curselection(): + self.font_family = self.list_box.get(self.list_box.curselection()) + self.font_weight = self.weight_var.get() and 'bold' or 'normal' + self.font_slant = self.slant_var.get() and 'italic' or 'roman' + self.font_size = self.size_var.get() + self.entry.configure(font=(self.font_family, self.font_size, + self.font_slant, self.font_weight)) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth=0, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + +# /*********************************************************************** +# // +# ************************************************************************/ + +class FontsDialog(MfxDialog): + def __init__(self, parent, title, app, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + + frame = Frame(top_frame) + frame.pack(expand=YES, fill=BOTH, padx=5, pady=10) + frame.columnconfigure(0, weight=1) + + self.fonts = {} + row = 0 + for fn in (#"default", + "sans", + "small", + "fixed", + "canvas_default", + #"canvas_card", + "canvas_fixed", + "canvas_large", + "canvas_small", + #"tree_small", + ): + font = app.opt.fonts[fn] + self.fonts[fn] = font + title = fn.replace("_", " ").capitalize()+": " + Label(frame, text=title, anchor=W).grid(row=row, column=0, sticky=W+E) + if font: + title = " ".join([str(i) for i in font if not i in ('roman', 'normal')]) + elif font is None: + title = 'Default' + l = Label(frame, font=font, text=title) + l.grid(row=row, column=1) + b = Button(frame, text=_('Change...'), width=10, + command=lambda l=l, fn=fn: self.selectFont(l, fn)) + b.grid(row=row, column=2) + row += 1 + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + + def selectFont(self, label, fn): + d = FontChooserDialog(self.top, _("Select font"), self.fonts[fn]) + if d.status == 0 and d.button == 0: + self.fonts[fn] = d.font + title = " ".join([str(i) for i in d.font if not i in ('roman', 'normal')]) + label.configure(font=d.font, text=title) + + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth = 0, + ) + return MfxDialog.initKw(self, kw) + + + + diff --git a/pysollib/tk/gameinfodialog.py b/pysollib/tk/gameinfodialog.py new file mode 100644 index 0000000000..46c5071d21 --- /dev/null +++ b/pysollib/tk/gameinfodialog.py @@ -0,0 +1,136 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + + +__all__ = ['GameInfoDialog'] + +# imports +import os, sys +from Tkinter import * + +# PySol imports +from pysollib.mfxutil import KwStruct +from pysollib.gamedb import GI + +# Toolkit imports +from tkwidget import _ToplevelDialog, MfxDialog + +# /*********************************************************************** +# // +# ************************************************************************/ + +class GameInfoDialog(MfxDialog): + def __init__(self, parent, title, app, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + + frame = Frame(top_frame) + frame.pack(expand=YES, fill=BOTH, padx=5, pady=10) + frame.columnconfigure(0, weight=1) + + game = app.game + gi = game.gameinfo + # + if gi.redeals == -2: redeals = 'VARIABLE' + elif gi.redeals == -1: redeals = 'UNLIMITED' + else: redeals = str(gi.redeals) + cat = '' + type = '' + flags = [] + for attr in dir(GI): + if attr.startswith('GC_'): + c = getattr(GI, attr) + if gi.category == c: + cat = attr + elif attr.startswith('GT_'): + t = getattr(GI, attr) + if t < (1<<12)-1: + if gi.si.game_type == t: + type = attr + else: + if gi.si.game_flags & t: + flags.append(attr) + # + row = 0 + for n, t in (('Name:', gi.name), + ('Short name:', gi.short_name), + ('ID:', gi.id), + ('Alt names:', '\n'.join(gi.altnames)), + ('Decks:', gi.decks), + ('Cards:', gi.ncards), + ('Redeals:', redeals), + ('Category:', cat), + ('Type:', type), + ('Flags:', '\n'.join(flags)), + ('Rules filename:', gi.rules_filename), + ('Module:', game.__module__), + ('Class:', game.__class__.__name__), + ('Hint:', game.Hint_Class.__name__), + ): + if t: + Label(frame, text=n, anchor=W).grid(row=row, column=0, sticky=N+W) + Label(frame, text=t, anchor=W, justify=LEFT).grid(row=row, column=1, sticky=N+W) + row += 1 + + if game.s.talon: + self.showStacks(frame, row, 'Talon:', game.s.talon) + row += 1 + if game.s.waste: + self.showStacks(frame, row, 'Waste:', game.s.waste) + row += 1 + for t, s in ( + ('Foundations:', game.s.foundations,), + ('Rows:', game.s.rows,), + ('Reserves:', game.s.reserves,), + ): + if s: + self.showStacks(frame, row, t, s) + row += 1 + + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + + def showStacks(self, frame, row, title, stacks): + Label(frame, text=title, anchor=W).grid(row=row, column=0, sticky=N+W) + if isinstance(stacks, (list, tuple)): + fs = {} + for f in stacks: + cn = f.__class__.__name__ + if fs.has_key(cn): + fs[cn] += 1 + else: + fs[cn] = 1 + t = '\n'.join(['%s (%d)' % (i[0], i[1]) for i in fs.items()]) + else: + t = stacks.__class__.__name__ + Label(frame, text=t, anchor=W, justify=LEFT).grid(row=row, column=1, sticky=N+W) + + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"),), default=0, + separatorwidth = 2, + ) + return MfxDialog.initKw(self, kw) diff --git a/pysollib/tk/menubar.py b/pysollib/tk/menubar.py new file mode 100644 index 0000000000..c360daf1b5 --- /dev/null +++ b/pysollib/tk/menubar.py @@ -0,0 +1,1052 @@ +# -*- coding: koi8-r -*- +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['PysolMenubar'] + +# imports +import math, os, re, sys, types +import Tkinter, tkColorChooser, tkFileDialog + +# PySol imports +from pysollib.mfxutil import destruct, Struct, kwdefault +from pysollib.util import CARDSET +from pysollib.version import VERSION +from pysollib.settings import PACKAGE +from pysollib.settings import TOP_TITLE +from pysollib.gamedb import GI +from pysollib.actions import PysolMenubarActions +from pysollib.pysolaudio import pysolsoundserver + +# toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE, CURSOR_WATCH, COMPOUNDS +from tkutil import bind, after_idle +from selectgame import SelectGameDialog, SelectGameDialogWithPreview +from selectcardset import SelectCardsetDialogWithPreview +from selectcardset import SelectCardsetByTypeDialogWithPreview +from selecttile import SelectTileDialogWithPreview + +#from toolbar import TOOLBAR_BUTTONS +from tkconst import TOOLBAR_BUTTONS + +gettext = _ +n_ = lambda x: x + + +# /*********************************************************************** +# // +# ************************************************************************/ + +def createToolbarMenu(menubar, menu): + data_dir = os.path.join(menubar.app.dataloader.dir, 'images', 'toolbar') + tearoff = menu.cget('tearoff') + submenu = MfxMenu(menu, label=n_('Style'), tearoff=tearoff) + for f in os.listdir(data_dir): + d = os.path.join(data_dir, f) + if os.path.isdir(d): + name = f.replace('_', ' ').capitalize() + submenu.add_radiobutton(label=name, + variable=menubar.tkopt.toolbar_style, + value=f, command=menubar.mOptToolbarStyle) + + submenu = MfxMenu(menu, label=n_('Relief'), tearoff=tearoff) + submenu.add_radiobutton(label=n_('Flat'), + variable=menubar.tkopt.toolbar_relief, + value=Tkinter.FLAT, + command=menubar.mOptToolbarRelief) + submenu.add_radiobutton(label=n_('Raised'), + variable=menubar.tkopt.toolbar_relief, + value=Tkinter.RAISED, + command=menubar.mOptToolbarRelief) + + submenu = MfxMenu(menu, label=n_('Compound'), tearoff=tearoff) + for comp, label in COMPOUNDS: + submenu.add_radiobutton(label=label, + variable=menubar.tkopt.toolbar_compound, + value=comp, command=menubar.mOptToolbarCompound) + menu.add_separator() + menu.add_radiobutton(label=n_("Hide"), + variable=menubar.tkopt.toolbar, value=0, + command=menubar.mOptToolbar) + menu.add_radiobutton(label=n_("Top"), + variable=menubar.tkopt.toolbar, value=1, + command=menubar.mOptToolbar) + menu.add_radiobutton(label=n_("Bottom"), + variable=menubar.tkopt.toolbar, value=2, + command=menubar.mOptToolbar) + menu.add_radiobutton(label=n_("Left"), + variable=menubar.tkopt.toolbar, value=3, + command=menubar.mOptToolbar) + menu.add_radiobutton(label=n_("Right"), + variable=menubar.tkopt.toolbar, value=4, + command=menubar.mOptToolbar) + menu.add_separator() + menu.add_radiobutton(label=n_("Small icons"), + variable=menubar.tkopt.toolbar_size, value=0, + command=menubar.mOptToolbarSize) + menu.add_radiobutton(label=n_("Large icons"), + variable=menubar.tkopt.toolbar_size, value=1, + command=menubar.mOptToolbarSize) + # + #return + menu.add_separator() + submenu = MfxMenu(menu, label=n_('Customize toolbar'), tearoff=tearoff) + for w in TOOLBAR_BUTTONS: + submenu.add_checkbutton(label=gettext(w.capitalize()), + variable=menubar.tkopt.toolbar_vars[w], + command=lambda m=menubar, w=w: m.mOptToolbarConfig(w)) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxMenubar(Tkinter.Menu): + addPath = None + + def __init__(self, master, **kw): + self.name = kw["name"] + tearoff = 0 + self.n = kw["tearoff"] = int(kw.get("tearoff", tearoff)) + apply(Tkinter.Menu.__init__, (self, master, ), kw) + + def labeltoname(self, label): + #print label, type(label) + name = re.sub(r"[^0-9a-zA-Z]", "", label).lower() + label = gettext(label) + underline = -1 + m = re.search(r"^(.*)\&([^\&].*)$", label) + if m: + l1, l2 = m.group(1), m.group(2) + l1 = re.sub(r"\&\&", "&", l1) + l2 = re.sub(r"\&\&", "&", l2) + label = l1 + l2 + underline = len(l1) + return name, label, underline + + def add(self, itemType, cnf={}): + label = cnf.get("label") + if label: + name = cnf.get('name') + try: + del cnf['name'] # TclError: unknown option "-name" + except KeyError: + pass + if not name: + name, label, underline = self.labeltoname(label) + cnf["underline"] = cnf.get("underline", underline) + cnf["label"] = label + if name and self.addPath: + path = str(self._w) + "." + name + self.addPath(path, self, self.n, cnf.get("menu")) + Tkinter.Menu.add(self, itemType, cnf) + self.n = self.n + 1 + + +class MfxMenu(MfxMenubar): + def __init__(self, master, label, underline=None, **kw): + if kw.has_key('name'): + name, label_underline = kw['name'], -1 + else: + name, label, label_underline = self.labeltoname(label) + kwdefault(kw, name=name) + apply(MfxMenubar.__init__, (self, master,), kw) + if underline is None: + underline = label_underline + if master: + master.add_cascade(menu=self, name=name, label=label, underline=underline) + + +# /*********************************************************************** +# // - create menubar +# // - update menubar +# // - menu actions +# ************************************************************************/ + +class PysolMenubar(PysolMenubarActions): + def __init__(self, app, top): + PysolMenubarActions.__init__(self, app, top) + # init columnbreak + self.__cb_max = int(self.top.winfo_screenheight()/22) +## sh = self.top.winfo_screenheight() +## self.__cb_max = 22 +## if sh >= 600: self.__cb_max = 27 +## if sh >= 768: self.__cb_max = 32 +## if sh >= 1024: self.__cb_max = 40 + # create menus + self.__menubar = None + self.__menupath = {} + self.__keybindings = {} + self._createMenubar() + # set the menubar + self.updateBackgroundImagesMenu() + self.top.config(menu=self.__menubar) + + # create a GTK-like path + def _addPath(self, path, menu, index, submenu): + if not self.__menupath.has_key(path): + ##print path, menu, index, submenu + self.__menupath[path] = (menu, index, submenu) + + def _getEnabledState(self, enabled): + if enabled: + return "normal" + return "disabled" + + + # + # create the menubar + # + + def _createMenubar(self): + MfxMenubar.addPath = self._addPath + kw = { "name": "menubar" } + if 1 and os.name == "posix": + pass + kw["relief"] = "groove" + kw["activeborderwidth"] = 1 + self.__menubar = apply(MfxMenubar, (self.top,), kw) + + # init keybindings + bind(self.top, "", self._keyPressHandler) + + m = "Ctrl-" + if os.name == "mac": m = "Cmd-" + + menu = MfxMenu(self.__menubar, n_("&File")) + menu.add_command(label=n_("&New game"), command=self.mNewGame, accelerator="N") + submenu = MfxMenu(menu, label=n_("R&ecent games")) + ##menu.add_command(label=n_("Select &random game"), command=self.mSelectRandomGame, accelerator=m+"R") + submenu = MfxMenu(menu, label=n_("Select &random game")) + submenu.add_command(label=n_("&All games"), command=lambda self=self: self.mSelectRandomGame('all'), accelerator=m+"R") + submenu.add_command(label=n_("Games played and &won"), command=lambda self=self: self.mSelectRandomGame('won')) + submenu.add_command(label=n_("Games played and ¬ won"), command=lambda self=self: self.mSelectRandomGame('not won')) + submenu.add_command(label=n_("Games not &played"), command=lambda self=self: self.mSelectRandomGame('not played')) + menu.add_command(label=n_("Select game by nu&mber..."), command=self.mSelectGameById, accelerator=m+"M") + menu.add_separator() + submenu = MfxMenu(menu, label=n_("Fa&vorite games")) + menu.add_command(label=n_("A&dd to favorites"), command=self.mAddFavor) + menu.add_command(label=n_("R&emove from favorites"), command=self.mDelFavor) + menu.add_separator() + menu.add_command(label=n_("&Open..."), command=self.mOpen, accelerator=m+"O") + menu.add_command(label=n_("&Save"), command=self.mSave, accelerator=m+"S") + menu.add_command(label=n_("Save &as..."), command=self.mSaveAs) + menu.add_separator() + menu.add_command(label=n_("&Hold and quit"), command=self.mHoldAndQuit) + menu.add_command(label=n_("&Quit"), command=self.mQuit, accelerator=m+"Q") + + menu = MfxMenu(self.__menubar, label=n_("&Select")) + self._addSelectGameMenu(menu) + + menu = MfxMenu(self.__menubar, label=n_("&Edit")) + menu.add_command(label=n_("&Undo"), command=self.mUndo, accelerator="Z") + menu.add_command(label=n_("&Redo"), command=self.mRedo, accelerator="R") + menu.add_command(label=n_("Redo &all"), command=self.mRedoAll) + + menu.add_separator() + submenu = MfxMenu(menu, label=n_("&Set bookmark")) + for i in range(9): + label = _("Bookmark %d") % (i + 1) + submenu.add_command(label=label, command=lambda self=self, i=i: self.mSetBookmark(i)) + submenu = MfxMenu(menu, label=n_("Go&to bookmark")) + for i in range(9): + label = _("Bookmark %d") % (i + 1) + acc = m + "%d" % (i + 1) + submenu.add_command(label=label, command=lambda self=self, i=i: self.mGotoBookmark(i), accelerator=acc) + menu.add_command(label=n_("&Clear bookmarks"), command=self.mClearBookmarks) + menu.add_separator() + + menu.add_command(label=n_("Restart &game"), command=self.mRestart, accelerator=m+"G") + + menu = MfxMenu(self.__menubar, label=n_("&Game")) + menu.add_command(label=n_("&Deal cards"), command=self.mDeal, accelerator="D") + menu.add_command(label=n_("&Auto drop"), command=self.mDrop, accelerator="A") + menu.add_checkbutton(label=n_("&Pause"), variable=self.tkopt.pause, command=self.mPause, accelerator="P") + #menu.add_command(label=n_("&Pause"), command=self.mPause, accelerator="P") + menu.add_separator() + menu.add_command(label=n_("S&tatus..."), command=self.mStatus, accelerator="T") + menu.add_checkbutton(label=n_("&Comments..."), variable=self.tkopt.comment, command=self.mEditGameComment) + menu.add_separator() + submenu = MfxMenu(menu, label=n_("&Statistics")) + submenu.add_command(label=n_("Current game..."), command=lambda self=self: self.mPlayerStats(mode=101)) + submenu.add_command(label=n_("All games..."), command=lambda self=self: self.mPlayerStats(mode=102)) + submenu.add_separator() + submenu.add_command(label=n_("Session log..."), command=lambda self=self: self.mPlayerStats(mode=104)) + submenu.add_command(label=n_("Full log..."), command=lambda self=self: self.mPlayerStats(mode=103)) + submenu.add_separator() + submenu.add_command(label=TOP_TITLE+"...", command=self.mTop10, accelerator=m+"T") + submenu = MfxMenu(menu, label=n_("D&emo statistics")) + submenu.add_command(label=n_("Current game..."), command=lambda self=self: self.mPlayerStats(mode=1101)) + submenu.add_command(label=n_("All games..."), command=lambda self=self: self.mPlayerStats(mode=1102)) + + menu = MfxMenu(self.__menubar, label=n_("&Assist")) + menu.add_command(label=n_("&Hint"), command=self.mHint, accelerator="H") + menu.add_command(label=n_("Highlight p&iles"), command=self.mHighlightPiles, accelerator="I") + menu.add_separator() + menu.add_command(label=n_("&Demo"), command=self.mDemo, accelerator=m+"D") + menu.add_command(label=n_("Demo (&all games)"), command=self.mMixedDemo) + menu = MfxMenu(self.__menubar, label=n_("&Options")) + menu.add_command(label=n_("&Player options..."), command=self.mOptPlayerOptions) + submenu = MfxMenu(menu, label=n_("&Automatic play")) + submenu.add_checkbutton(label=n_("Auto &face up"), variable=self.tkopt.autofaceup, command=self.mOptAutoFaceUp) + submenu.add_checkbutton(label=n_("&Auto drop"), variable=self.tkopt.autodrop, command=self.mOptAutoDrop) + submenu.add_checkbutton(label=n_("Auto &deal"), variable=self.tkopt.autodeal, command=self.mOptAutoDeal) + submenu.add_separator() + submenu.add_checkbutton(label=n_("&Quick play"), variable=self.tkopt.quickplay, command=self.mOptQuickPlay) + submenu = MfxMenu(menu, label=n_("Assist &level")) + submenu.add_checkbutton(label=n_("Enable &undo"), variable=self.tkopt.undo, command=self.mOptEnableUndo) + submenu.add_checkbutton(label=n_("Enable &bookmarks"), variable=self.tkopt.bookmarks, command=self.mOptEnableBookmarks) + submenu.add_checkbutton(label=n_("Enable &hint"), variable=self.tkopt.hint, command=self.mOptEnableHint) + submenu.add_checkbutton(label=n_("Enable highlight p&iles"), variable=self.tkopt.highlight_piles, command=self.mOptEnableHighlightPiles) + submenu.add_checkbutton(label=n_("Enable highlight &cards"), variable=self.tkopt.highlight_cards, command=self.mOptEnableHighlightCards) + submenu.add_checkbutton(label=n_("Enable highlight same &rank"), variable=self.tkopt.highlight_samerank, command=self.mOptEnableHighlightSameRank) + submenu.add_checkbutton(label=n_("Highlight &no matching"), variable=self.tkopt.highlight_not_matching, command=self.mOptEnableHighlightNotMatching) + submenu.add_separator() + submenu.add_checkbutton(label=n_("Show removed tiles (in Mahjongg games)"), variable=self.tkopt.mahjongg_show_removed, command=self.mOptMahjonggShowRemoved) + submenu.add_checkbutton(label=n_("Show hint arrow (in Shisen-Sho games)"), variable=self.tkopt.shisen_show_hint, command=self.mOptShisenShowHint) + menu.add_separator() + label = n_("&Sound") + if self.app.audio.audiodev is None: + menu.add_checkbutton(label=label, variable=self.tkopt.sound, command=self.mOptSound, state=Tkinter.DISABLED) + elif pysolsoundserver: + menu.add_checkbutton(label=label, variable=self.tkopt.sound, command=self.mOptSoundDialog) + else: + menu.add_checkbutton(label=label, variable=self.tkopt.sound, command=self.mOptSound) + # cardsets + #manager = self.app.cardset_manager + #n = manager.len() + menu.add_command(label=n_("Cards&et..."), command=self.mSelectCardsetDialog, accelerator=m+"E") + menu.add_command(label=n_("Table t&ile..."), command=self.mSelectTileDialog) + # this submenu will get set by updateBackgroundImagesMenu() + submenu = MfxMenu(menu, label=n_("Card &background")) + submenu = MfxMenu(menu, label=n_("Card &view")) + submenu.add_checkbutton(label=n_("Card shado&w"), variable=self.tkopt.shadow, command=self.mOptShadow) + submenu.add_checkbutton(label=n_("Shade &legal moves"), variable=self.tkopt.shade, command=self.mOptShade) + submenu.add_checkbutton(label=n_("&Negative card bottom"), variable=self.tkopt.negative_bottom, command=self.mOptNegativeBottom) + submenu = MfxMenu(menu, label=n_("A&nimations")) + submenu.add_radiobutton(label=n_("&None"), variable=self.tkopt.animations, value=0, command=self.mOptAnimations) + submenu.add_radiobutton(label=n_("&Timer based"), variable=self.tkopt.animations, value=2, command=self.mOptAnimations) + submenu.add_radiobutton(label=n_("&Fast"), variable=self.tkopt.animations, value=1, command=self.mOptAnimations) + submenu.add_radiobutton(label=n_("&Slow"), variable=self.tkopt.animations, value=3, command=self.mOptAnimations) + submenu.add_radiobutton(label=n_("&Very slow"), variable=self.tkopt.animations, value=4, command=self.mOptAnimations) + menu.add_checkbutton(label=n_("Stick&y mouse"), variable=self.tkopt.sticky_mouse, command=self.mOptStickyMouse) + menu.add_separator() + #menu.add_command(label="&Hint options...", command=self.mOptHintOptions) + #menu.add_command(label="&Demo options...", command=self.mOptDemoOptions) + menu.add_command(label=n_("&Fonts..."), command=self.mOptFontsOptions) + menu.add_command(label=n_("&Colors..."), command=self.mOptColorsOptions) + menu.add_command(label=n_("Time&outs..."), command=self.mOptTimeoutsOptions) + menu.add_separator() + submenu = MfxMenu(menu, label=n_("&Toolbar")) + createToolbarMenu(self, submenu) + submenu = MfxMenu(menu, label=n_("Stat&usbar")) + submenu.add_checkbutton(label=n_("Show &statusbar"), variable=self.tkopt.statusbar, command=self.mOptStatusbar) + submenu.add_checkbutton(label=n_("Show &number of cards"), variable=self.tkopt.num_cards, command=self.mOptNumCards) + submenu.add_checkbutton(label=n_("Show &help bar"), variable=self.tkopt.helpbar, command=self.mOptHelpbar) + menu.add_checkbutton(label=n_("&Demo logo"), variable=self.tkopt.demo_logo, command=self.mOptDemoLogo) + menu.add_checkbutton(label=n_("Startup splash sc&reen"), variable=self.tkopt.splashscreen, command=self.mOptSplashscreen) +### menu.add_separator() +### menu.add_command(label="Save options", command=self.mOptSave) + + menu = MfxMenu(self.__menubar, label=n_("&Help")) + menu.add_command(label=n_("&Contents"), command=self.mHelp, accelerator=m+"F1") + menu.add_command(label=n_("&How to play"), command=self.mHelpHowToPlay) + menu.add_command(label=n_("&Rules for this game"), command=self.mHelpRules, accelerator="F1") + menu.add_command(label=n_("&License terms"), command=self.mHelpLicense) + ##menu.add_command(label=n_("What's &new ?"), command=self.mHelpNews) + menu.add_separator() + menu.add_command(label=n_("&About ")+PACKAGE+"...", command=self.mHelpAbout) + + MfxMenubar.addPath = None + + ### FIXME: all key bindings should be *added* to keyPressHandler + ctrl = "Control-" + self._bindKey("", "n", self.mNewGame) + self._bindKey("", "g", self.mSelectGameDialog) + self._bindKey("", "v", self.mSelectGameDialogWithPreview) + self._bindKey(ctrl, "r", lambda e, self=self: self.mSelectRandomGame()) + self._bindKey(ctrl, "m", self.mSelectGameById) + self._bindKey(ctrl, "n", self.mNewGameWithNextId) + self._bindKey(ctrl, "o", self.mOpen) + ##self._bindKey("", "F3", self.mOpen) # undocumented + self._bindKey(ctrl, "s", self.mSave) + ##self._bindKey("", "F2", self.mSaveAs) # undocumented + self._bindKey(ctrl, "q", self.mQuit) + self._bindKey("", "z", self.mUndo) + self._bindKey("", "BackSpace", self.mUndo) # undocumented + self._bindKey("", "r", self.mRedo) + self._bindKey(ctrl, "g", self.mRestart) + self._bindKey("", "space", self.mDeal) # undocumented + self._bindKey("", "t", self.mStatus) + self._bindKey(ctrl, "t", self.mTop10) + self._bindKey("", "h", self.mHint) + self._bindKey(ctrl, "h", self.mHint1) # undocumented + ##self._bindKey("", "Shift_L", self.mHighlightPiles) + ##self._bindKey("", "Shift_R", self.mHighlightPiles) + self._bindKey("", "i", self.mHighlightPiles) + self._bindKey(ctrl, "d", self.mDemo) + self._bindKey(ctrl, "e", self.mSelectCardsetDialog) + self._bindKey(ctrl, "b", self.mOptChangeCardback) # undocumented + self._bindKey(ctrl, "i", self.mOptChangeTableTile) # undocumented + self._bindKey(ctrl, "p", self.mOptPlayerOptions) # undocumented + self._bindKey(ctrl, "F1", self.mHelp) + self._bindKey("", "F1", self.mHelpRules) + self._bindKey("", "Print", self.mScreenshot) + self._bindKey(ctrl, "u", self.mPlayNextMusic) # undocumented + self._bindKey("", "p", self.mPause) + self._bindKey("", "Pause", self.mPause) + self._bindKey("", "Escape", self.mIconify) + # ASD and LKJ + self._bindKey("", "a", self.mDrop) + self._bindKey(ctrl, "a", self.mDrop1) + self._bindKey("", "s", self.mUndo) + self._bindKey("", "d", self.mDeal) + self._bindKey("", "l", self.mDrop) + self._bindKey(ctrl, "l", self.mDrop1) + self._bindKey("", "k", self.mUndo) + self._bindKey("", "j", self.mDeal) + # + self._bindKey("", "slash", self.mGameInfo) # undocumented, devel + + for i in range(9): + self._bindKey(ctrl, str(i+1), lambda event, self=self, i=i: self.mGotoBookmark(i, confirm=0)) + + # undocumented, devel + self._bindKey(ctrl, "End", self.mPlayNextMusic) + self._bindKey(ctrl, "Prior", self.mSelectPrevGameByName) + self._bindKey(ctrl, "Next", self.mSelectNextGameByName) + self._bindKey(ctrl, "Up", self.mSelectPrevGameById) + self._bindKey(ctrl, "Down", self.mSelectNextGameById) + + + # + # key binding utility + # + + def _bindKey(self, modifier, key, func): + if 0 and not modifier and len(key) == 1: + self.__keybindings[key.lower()] = func + self.__keybindings[key.upper()] = func + return + sequence = "<" + modifier + "KeyPress-" + key + ">" + try: + bind(self.top, sequence, func) + if len(key) == 1 and key != key.upper(): + key = key.upper() + sequence = "<" + modifier + "KeyPress-" + key + ">" + bind(self.top, sequence, func) + except: + raise + + + def _keyPressHandler(self, event): + r = EVENT_PROPAGATE + if event and self.game: + ##print event.__dict__ + if self.game.demo: + # stop the demo by setting self.game.demo.keypress + if event.char: # ignore Ctrl/Shift/etc. + self.game.demo.keypress = event.char + r = EVENT_HANDLED + func = self.__keybindings.get(event.char) + if func and (event.state & ~2) == 0: + func(event) + r = EVENT_HANDLED + return r + + # + # Select Game menu creation + # + + def _addSelectGameMenu(self, menu): + #games = map(self.app.gdb.get, self.app.gdb.getGamesIdSortedByShortName()) + games = map(self.app.gdb.get, self.app.gdb.getGamesIdSortedByName()) + ##games = tuple(games) + ###menu = MfxMenu(menu, label="Select &game") + menu.add_command(label=n_("All &games..."), command=self.mSelectGameDialog, accelerator="G") + menu.add_command(label=n_("Playable pre&view..."), command=self.mSelectGameDialogWithPreview, accelerator="V") + menu.add_separator() + data = (n_("&Popular games"), lambda gi: gi.si.game_flags & GI.GT_POPULAR) + self._addSelectGameSubMenu(menu, games, (data, ), + self.mSelectGamePopular, self.tkopt.gameid_popular) + submenu = MfxMenu(menu, label=n_("&French games")) + self._addSelectGameSubMenu(submenu, games, GI.SELECT_GAME_BY_TYPE, + self.mSelectGame, self.tkopt.gameid) + submenu = MfxMenu(menu, label=n_("&Mahjongg games")) + self._addSelectMahjonggGameSubMenu(submenu, + self.mSelectGame, self.tkopt.gameid) + submenu = MfxMenu(menu, label=n_("&Oriental games")) + self._addSelectGameSubMenu(submenu, games, + GI.SELECT_ORIENTAL_GAME_BY_TYPE, + self.mSelectGame, self.tkopt.gameid) + submenu = MfxMenu(menu, label=n_("&Special games")) + self._addSelectGameSubMenu(submenu, games, GI.SELECT_SPECIAL_GAME_BY_TYPE, + self.mSelectGame, self.tkopt.gameid) + menu.add_separator() + submenu = MfxMenu(menu, label=n_("All games by name")) + self._addSelectAllGameSubMenu(submenu, games, + self.mSelectGame, self.tkopt.gameid) + + + def _addSelectGameSubMenu(self, menu, games, select_data, command, variable): + ##print select_data + need_sep = 0 + for label, select_func in select_data: + if label is None: + need_sep = 1 + continue + g = filter(select_func, games) + if not g: + continue + if need_sep: + menu.add_separator() + need_sep = 0 + submenu = MfxMenu(menu, label=label) + self._addSelectGameSubSubMenu(submenu, g, command, variable) + + def _addSelectMahjonggGameSubMenu(self, menu, command, variable): +## games = [] +## g = [] +## c0 = c1 = None +## for i in self.app.gdb.getGamesIdSortedByShortName(): +## gi = self.app.gdb.get(i) +## if gi.si.game_type == GI.GT_MAHJONGG: +## c = gettext(gi.short_name).strip()[0] +## if c0 is None: +## c0 = c +## elif c != c0: +## if g: +## games.append((c0, g)) +## g = [] +## c0 = c +## #else: +## #if g: +## g.append(gi) +## if g: +## games.append((c0, g)) +## n = 0 +## gg = [] +## c0 = c1 = None +## for c, g in games: +## if c0 is None: +## c0 = c +## if len(gg) + len(g) > self.__cb_max: +## if gg: +## if c0 != c1: +## label = c0+' - '+c1 +## else: +## label = c1 +## c0 = c +## submenu = MfxMenu(menu, label=label, name=None) +## self._addSelectGameSubSubMenu(submenu, gg, command, +## variable, short_name=True) +## gg = [] +## c1 = c +## gg += g +## if gg: +## label = c0+' - '+c +## submenu = MfxMenu(menu, label=label, name=None) +## self._addSelectGameSubSubMenu(submenu, gg, command, +## variable, short_name=True) +## return + + + g = [] + c0 = c1 = None + for i in self.app.gdb.getGamesIdSortedByShortName(): + gi = self.app.gdb.get(i) + if gi.si.game_type == GI.GT_MAHJONGG: + c = gettext(gi.short_name).strip()[0] + if c0 is None: + c0 = c + if len(g) >= self.__cb_max and c != c1: + label = c0 + ' - ' + c1 + if c0 == c1: + label = c0 + submenu = MfxMenu(menu, label=label, name=None) + self._addSelectGameSubSubMenu(submenu, g, command, + variable, short_name=True) + g = [gi] + c0 = c + else: + g.append(gi) + c1 = c + if g: + label = c0 + ' - ' + c1 + submenu = MfxMenu(menu, label=label, name=None) + self._addSelectGameSubSubMenu(submenu, g, command, + variable, short_name=True) + + def _addSelectAllGameSubMenu(self, menu, g, command, variable): + n, d = 0, self.__cb_max + i = 0 + while True: + columnbreak = i > 0 and (i % d) == 0 + i += 1 + if not g[n:n+d]: + break + m = min(n+d-1, len(g)-1) + label = gettext(g[n].name)[:3]+' - '+gettext(g[m].name)[:3] + submenu = MfxMenu(menu, label=label, name=None) + self._addSelectGameSubSubMenu(submenu, g[n:n+d], command, variable) + n += d + if columnbreak: + menu.entryconfigure(i, columnbreak=columnbreak) + + def _addSelectGameSubSubMenu(self, menu, g, command, variable, short_name=False): + ##cb = (25, self.__cb_max) [ len(g) > 4 * 25 ] + ##cb = min(cb, self.__cb_max) + cb = self.__cb_max + for i in range(len(g)): + gi = g[i] + columnbreak = i > 0 and (i % cb) == 0 + if short_name: + label = gi.short_name + else: + label = gi.name + menu.add_radiobutton(command=command, variable=variable, + columnbreak=columnbreak, + value=gi.id, label=label, name=None) + + + # + # Select Game menu actions + # + + def _mSelectGameDialog(self, d): + if d.status == 0 and d.button == 0 and d.gameid != self.game.id: + self.tkopt.gameid.set(d.gameid) + self.tkopt.gameid_popular.set(d.gameid) + if 0: + self._mSelectGame(d.gameid, random=d.random) + else: + # don't ask areYouSure() + self._cancelDrag() + self.game.endGame() + self.game.quitGame(d.gameid, random=d.random) + return EVENT_HANDLED + + def __restoreCursor(self, *event): + self.game.setCursor(cursor=self.app.top_cursor) + + def mSelectGameDialog(self, *event): + if self._cancelDrag(break_pause=False): return + self.game.setCursor(cursor=CURSOR_WATCH) + after_idle(self.top, self.__restoreCursor) + d = SelectGameDialog(self.top, title=_("Select game"), + app=self.app, gameid=self.game.id) + return self._mSelectGameDialog(d) + + def mSelectGameDialogWithPreview(self, *event): + if self._cancelDrag(break_pause=False): return + self.game.setCursor(cursor=CURSOR_WATCH) + bookmark = None + if 0: + # use a bookmark for our preview game + if self.game.setBookmark(-2, confirm=0): + bookmark = self.game.gsaveinfo.bookmarks[-2][0] + del self.game.gsaveinfo.bookmarks[-2] + after_idle(self.top, self.__restoreCursor) + d = SelectGameDialogWithPreview(self.top, title=_("Select game"), + app=self.app, gameid=self.game.id, + bookmark=bookmark) + return self._mSelectGameDialog(d) + + + # + # menubar overrides + # + + def updateFavoriteGamesMenu(self): + gameids = self.app.opt.favorite_gameid + # delete all entries + submenu = self.__menupath[".menubar.file.favoritegames"][2] + submenu.delete(0, "last") + # insert games + for id in gameids: + gi = self.app.getGameInfo(id) + if not gi: + continue + submenu.add_radiobutton(command=self.mSelectGame, + variable=self.tkopt.gameid, + value=gi.id, label=gi.name) + + state = self._getEnabledState + in_favor = self.app.game.id in gameids + menu, index, submenu = self.__menupath[".menubar.file.addtofavorites"] + menu.entryconfig(index, state=state(not in_favor)) + menu, index, submenu = self.__menupath[".menubar.file.removefromfavorites"] + menu.entryconfig(index, state=state(in_favor)) + + + def updateRecentGamesMenu(self, gameids): + # delete all entries + submenu = self.__menupath[".menubar.file.recentgames"][2] + submenu.delete(0, "last") + # insert games + cb = (25, self.__cb_max) [ len(gameids) > 4 * 25 ] + i = 0 + for id in gameids: + gi = self.app.getGameInfo(id) + if not gi: + continue + columnbreak = i > 0 and (i % cb) == 0 + submenu.add_radiobutton(command=self.mSelectGame, + variable=self.tkopt.gameid, + columnbreak=columnbreak, + value=gi.id, label=gi.name) + i = i + 1 + + def updateBookmarkMenuState(self): + state = self._getEnabledState + mp1 = self.__menupath.get(".menubar.edit.setbookmark") + mp2 = self.__menupath.get(".menubar.edit.gotobookmark") + mp3 = self.__menupath.get(".menubar.edit.clearbookmarks") + if mp1 is None or mp2 is None or mp3 is None: + return + x = self.app.opt.bookmarks and self.game.canSetBookmark() + # + menu, index, submenu = mp1 + for i in range(9): + submenu.entryconfig(i, state=state(x)) + menu.entryconfig(index, state=state(x)) + # + menu, index, submenu = mp2 + ms = 0 + for i in range(9): + s = self.game.gsaveinfo.bookmarks.get(i) is not None + submenu.entryconfig(i, state=state(s and x)) + ms = ms or s + menu.entryconfig(index, state=state(ms and x)) + # + menu, index, submenu = mp3 + menu.entryconfig(index, state=state(ms and x)) + + def updateBackgroundImagesMenu(self): + mp = self.__menupath.get(".menubar.options.cardbackground") + # delete all entries + submenu = mp[2] + submenu.delete(0, "last") + # insert new cardbacks + mbacks = self.app.images.getCardbacks() + cb = int(math.ceil(math.sqrt(len(mbacks)))) + for i in range(len(mbacks)): + columnbreak = i > 0 and (i % cb) == 0 + submenu.add_radiobutton(label=mbacks[i].name, image=mbacks[i].menu_image, variable=self.tkopt.cardback, value=i, + command=self.mOptCardback, columnbreak=columnbreak, indicatoron=0, hidemargin=0) + + + # + # menu updates + # + + def setMenuState(self, state, path): + #print state, path + path = ".menubar." + path + mp = self.__menupath.get(path) + menu, index, submenu = mp + s = self._getEnabledState(state) + menu.entryconfig(index, state=s) + + def setToolbarState(self, state, path): + #print state, path + s = self._getEnabledState(state) + w = getattr(self.app.toolbar, path + "_button") + w["state"] = s + + + # + # menu actions + # + + DEFAULTEXTENSION = ".pso" + FILETYPES = ((PACKAGE+" files", "*"+DEFAULTEXTENSION), ("All files", "*")) + + def mAddFavor(self, *event): + gameid = self.app.game.id + if gameid not in self.app.opt.favorite_gameid: + self.app.opt.favorite_gameid.append(gameid) + self.updateFavoriteGamesMenu() + + def mDelFavor(self, *event): + gameid = self.app.game.id + if gameid in self.app.opt.favorite_gameid: + self.app.opt.favorite_gameid.remove(gameid) + self.updateFavoriteGamesMenu() + + def mOpen(self, *event): + if self._cancelDrag(break_pause=False): return + filename = self.game.filename + if filename: + idir, ifile = os.path.split(os.path.normpath(filename)) + else: + idir, ifile = "", "" + if not idir: + idir = self.app.dn.savegames + d = tkFileDialog.Open() + filename = d.show(filetypes=self.FILETYPES, defaultextension=self.DEFAULTEXTENSION, + initialdir=idir, initialfile=ifile) + if filename: + filename = os.path.normpath(filename) + ##filename = os.path.normcase(filename) + if os.path.isfile(filename): + self.game.loadGame(filename) + + def mSaveAs(self, *event): + if self._cancelDrag(break_pause=False): return + if not self.menustate.save_as: + return + filename = self.game.filename + if not filename: + filename = self.app.getGameSaveName(self.game.id) + if os.name == "posix": + filename = filename + "-" + self.game.getGameNumber(format=0) + else: + filename = filename + "-01" + filename = filename + self.DEFAULTEXTENSION + idir, ifile = os.path.split(os.path.normpath(filename)) + if not idir: + idir = self.app.dn.savegames + ##print self.game.filename, ifile + d = tkFileDialog.SaveAs() + filename = d.show(filetypes=self.FILETYPES, defaultextension=self.DEFAULTEXTENSION, + initialdir=idir, initialfile=ifile) + if filename: + filename = os.path.normpath(filename) + ##filename = os.path.normcase(filename) + self.game.saveGame(filename) + self.updateMenus() + + def mSelectCardsetDialog(self, *event): + if self._cancelDrag(break_pause=False): return + ##strings, default = ("OK", "Load", "Cancel"), 0 + strings, default = (None, _("Load"), _("Cancel"),), 1 + ##if os.name == "posix": + strings, default = (None, _("Load"), _("Cancel"), _("Info..."),), 1 + t = CARDSET + key = self.app.nextgame.cardset.index + d = SelectCardsetDialogWithPreview(self.top, title=_("Select ")+t, + app=self.app, manager=self.app.cardset_manager, key=key, + strings=strings, default=default) + cs = self.app.cardset_manager.get(d.key) + if cs is None or d.key == self.app.cardset.index: + return + if d.status == 0 and d.button in (0, 1) and d.key >= 0: + self.app.nextgame.cardset = cs + if d.button == 1: + self._cancelDrag() + self.game.endGame(bookmark=1) + self.game.quitGame(bookmark=1) + + def _mOptCardback(self, index): + if self._cancelDrag(break_pause=False): return + cs = self.app.cardset + old_index = cs.backindex + cs.updateCardback(backindex=index) + if cs.backindex == old_index: + return + self.app.updateCardset(self.game.id) + for card in self.game.cards: + image = self.app.images.getBack(card.deck, card.suit, card.rank) + card.updateCardBackground(image=image) + self.app.canvas.update_idletasks() + self.tkopt.cardback.set(cs.backindex) + + def mOptCardback(self, *event): + self._mOptCardback(self.tkopt.cardback.get()) + + def mOptChangeCardback(self, *event): + self._mOptCardback(self.app.cardset.backindex + 1) + + def _mOptTableTile(self, i): + if self.app.setTile(i): + self.tkopt.tabletile.set(i) + + def _mOptTableColor(self, color): + tile = self.app.tabletile_manager.get(0) + tile.color = color + if self.app.setTile(0): + self.tkopt.tabletile.set(0) + + def mOptTableTile(self, *event): + if self._cancelDrag(break_pause=False): return + self._mOptTableTile(self.tkopt.tabletile.get()) + + def mOptChangeTableTile(self, *event): + if self._cancelDrag(break_pause=False): return + n = self.app.tabletile_manager.len() + if n >= 2: + i = (self.tkopt.tabletile.get() + 1) % n + self._mOptTableTile(i) + + def mSelectTileDialog(self, *event): + if self._cancelDrag(break_pause=False): return + key = self.app.tabletile_index + if key <= 0: + key = self.app.opt.table_color.lower() + d = SelectTileDialogWithPreview(self.top, app=self.app, + title=_("Select table background"), + manager=self.app.tabletile_manager, + key=key) + if d.status == 0 and d.button in (0, 1): + if type(d.key) is types.StringType: + self._mOptTableColor(d.key) + elif d.key > 0 and d.key != self.app.tabletile_index: + self._mOptTableTile(d.key) + + def mOptTableColor(self, *event): + if self._cancelDrag(break_pause=False): return + c = tkColorChooser.askcolor(initialcolor=self.app.opt.table_color, + title=_("Select table color")) + if c and c[1]: + self._mOptTableColor(c[1]) + + def mOptToolbar(self, *event): + ##if self._cancelDrag(break_pause=False): return + self.setToolbarSide(self.tkopt.toolbar.get()) + + def mOptToolbarStyle(self, *event): + ##if self._cancelDrag(break_pause=False): return + self.setToolbarStyle(self.tkopt.toolbar_style.get()) + + def mOptToolbarCompound(self, *event): + ##if self._cancelDrag(break_pause=False): return + self.setToolbarCompound(self.tkopt.toolbar_compound.get()) + + def mOptToolbarSize(self, *event): + ##if self._cancelDrag(break_pause=False): return + self.setToolbarSize(self.tkopt.toolbar_size.get()) + + def mOptToolbarRelief(self, *event): + ##if self._cancelDrag(break_pause=False): return + self.setToolbarRelief(self.tkopt.toolbar_relief.get()) + + def mOptToolbarConfig(self, w): + self.toolbarConfig(w, self.tkopt.toolbar_vars[w].get()) + + def mOptStatusbar(self, *event): + if self._cancelDrag(break_pause=False): return + if not self.app.statusbar: return + side = self.tkopt.statusbar.get() + self.app.opt.statusbar = side + if self.app.statusbar.show(side): + self.top.update_idletasks() + + def mOptNumCards(self, *event): + if self._cancelDrag(break_pause=False): return + self.app.opt.num_cards = self.tkopt.num_cards.get() + + def mOptHelpbar(self, *event): + if self._cancelDrag(break_pause=False): return + if not self.app.helpbar: return + show = self.tkopt.helpbar.get() + self.app.opt.helpbar = show + if self.app.helpbar.show(show): + self.top.update_idletasks() + + def mOptDemoLogo(self, *event): + if self._cancelDrag(break_pause=False): return + self.app.opt.demo_logo = self.tkopt.demo_logo.get() + + def mOptSplashscreen(self, *event): + if self._cancelDrag(break_pause=False): return + self.app.opt.splashscreen = self.tkopt.splashscreen.get() + + def mOptStickyMouse(self, *event): + if self._cancelDrag(break_pause=False): return + self.app.opt.sticky_mouse = self.tkopt.sticky_mouse.get() + + def mOptNegativeBottom(self, *event): + if self._cancelDrag(): return + n = self.tkopt.negative_bottom.get() + self.app.opt.negative_bottom = n + self.app.updateCardset() + self.game.endGame(bookmark=1) + self.game.quitGame(bookmark=1) + + + # + # toolbar support + # + + def setToolbarSide(self, side): + if self._cancelDrag(break_pause=False): return + self.app.opt.toolbar = side + self.tkopt.toolbar.set(side) # update radiobutton + if self.app.toolbar.show(side): + self.top.update_idletasks() + + def setToolbarSize(self, size): + if self._cancelDrag(break_pause=False): return + self.app.opt.toolbar_size = size + self.tkopt.toolbar_size.set(size) # update radiobutton + dir = self.app.getToolbarImagesDir() + if self.app.toolbar.updateImages(dir, size): + self.game.updateStatus(player=self.app.opt.player) + self.top.update_idletasks() + + def setToolbarStyle(self, style): + if self._cancelDrag(break_pause=False): return + self.app.opt.toolbar_style = style + self.tkopt.toolbar_style.set(style) # update radiobutton + dir = self.app.getToolbarImagesDir() + size = self.app.opt.toolbar_size + if self.app.toolbar.updateImages(dir, size): + ##self.game.updateStatus(player=self.app.opt.player) + self.top.update_idletasks() + + def setToolbarCompound(self, compound): + if self._cancelDrag(break_pause=False): return + self.app.opt.toolbar_compound = compound + self.tkopt.toolbar_compound.set(compound) # update radiobutton + if self.app.toolbar.setCompound(compound): + self.game.updateStatus(player=self.app.opt.player) + self.top.update_idletasks() + + def setToolbarRelief(self, relief): + if self._cancelDrag(break_pause=False): return + self.app.opt.toolbar_relief = relief + self.tkopt.toolbar_relief.set(relief) # update radiobutton + self.app.toolbar.setRelief(relief) + self.top.update_idletasks() + + def toolbarConfig(self, w, v): + if self._cancelDrag(break_pause=False): return + self.app.opt.toolbar_vars[w] = v + self.app.toolbar.config(w, v) + self.top.update_idletasks() + + + diff --git a/pysollib/tk/playeroptionsdialog.py b/pysollib/tk/playeroptionsdialog.py new file mode 100644 index 0000000000..3cd0892e1a --- /dev/null +++ b/pysollib/tk/playeroptionsdialog.py @@ -0,0 +1,188 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['PlayerOptionsDialog'] + +# imports +import os, sys, Tkinter + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog +from tkutil import bind + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class SelectUserNameDialog(MfxDialog): + def __init__(self, parent, title, usernames=[], **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, + kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + listbox = Tkinter.Listbox(top_frame) + listbox.pack(side='left', fill='both', expand=1) + scrollbar = Tkinter.Scrollbar(top_frame) + scrollbar.pack(side='right', fill='y') + listbox.configure(yscrollcommand=scrollbar.set) + scrollbar.configure(command=listbox.yview) + + self.username = None + self.listbox = listbox + bind(listbox, '<>', self.updateUserName) + # + for un in usernames: + listbox.insert('end', un) + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + #if listbox.curselection(): + # self.username = listbox.get(listbox.curselection()) + + def updateUserName(self, *args): + self.username = self.listbox.get(self.listbox.curselection()) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth=0, + resizable=0, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + + + +class PlayerOptionsDialog(MfxDialog): + def __init__(self, parent, title, app, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + self.app = app + # + self.update_stats_var = Tkinter.BooleanVar() + self.update_stats_var.set(app.opt.update_player_stats != 0) + self.confirm_var = Tkinter.BooleanVar() + self.confirm_var.set(app.opt.confirm != 0) + self.win_animation_var = Tkinter.BooleanVar() + self.win_animation_var.set(app.opt.win_animation != 0) + # + frame = Tkinter.Frame(top_frame) + frame.pack(expand=1, fill='both', padx=5, pady=10) + widget = Tkinter.Label(frame, text=_("\nPlease enter your name"), + #justify='left', anchor='w', + takefocus=0) + widget.grid(row=0, column=0, columnspan=2, sticky='ew', padx=0, pady=5) + w = kw.get("e_width", 30) # width in characters + self.player_var = Tkinter.Entry(frame, exportselection=1, width=w) + self.player_var.insert(0, app.opt.player) + self.player_var.grid(row=1, column=0, sticky='ew', padx=0, pady=5) + widget = Tkinter.Button(frame, text=_('Select...'), + command=self.selectUserName) + widget.grid(row=1, column=1, padx=5, pady=5) + widget = Tkinter.Checkbutton(frame, variable=self.confirm_var, + anchor='w', text=_("Confirm quit")) + widget.grid(row=2, column=0, columnspan=2, sticky='ew', padx=0, pady=5) + widget = Tkinter.Checkbutton(frame, variable=self.update_stats_var, + anchor='w', + text=_("Update statistics and logs")) + widget.grid(row=3, column=0, columnspan=2, sticky='ew', padx=0, pady=5) +### widget = Tkinter.Checkbutton(frame, variable=self.win_animation_var, +### text="Win animation") +### widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + frame.columnconfigure(0, weight=1) + # + self.player = self.player_var.get() + self.confirm = self.confirm_var.get() + self.update_stats = self.update_stats_var.get() + self.win_animation = self.win_animation_var.get() + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + def selectUserName(self, *args): + names = self.app.getAllUserNames() + d = SelectUserNameDialog(self.top, _("Select name"), names) + if d.status == 0 and d.button == 0 and d.username: + self.player_var.delete(0, 'end') + self.player_var.insert(0, d.username) + + def mDone(self, button): + self.button = button + self.player = self.player_var.get() + self.confirm = self.confirm_var.get() + self.update_stats = self.update_stats_var.get() + self.win_animation = self.win_animation_var.get() + raise SystemExit + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth=2, + resizable=0, + padx=10, pady=10, + ) + return MfxDialog.initKw(self, kw) + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +def playeroptionsdialog_main(args): + from tkutil import wm_withdraw + opt = Struct(player="Test", update_player_stats=1) + app = Struct(opt=opt) + tk = Tkinter.Tk() + wm_withdraw(tk) + tk.update() + d = PlayerOptionsDialog(tk, "Player options", app) + print d.status, d.button, ":", d.player, d.update_stats + return 0 + +if __name__ == "__main__": + import sys + sys.exit(playeroptionsdialog_main(sys.argv)) + diff --git a/pysollib/tk/progressbar.py b/pysollib/tk/progressbar.py new file mode 100644 index 0000000000..fc8df50625 --- /dev/null +++ b/pysollib/tk/progressbar.py @@ -0,0 +1,157 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['PysolProgressBar'] + +# imports +import os, sys, Tkinter + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkutil import makeToplevel, setTransient, wm_set_icon + + +# /*********************************************************************** +# // a simple progress bar +# ************************************************************************/ + +class PysolProgressBar: + def __init__(self, app, parent, title=None, images=None, + color="blue", width=300, height=25, show_text=1): + self.parent = parent + self.percent = 0 + self.top = makeToplevel(parent, title=title) + self.top.wm_protocol("WM_DELETE_WINDOW", self.wmDeleteWindow) + self.top.wm_group(parent) + self.top.wm_resizable(0, 0) + self.frame = Tkinter.Frame(self.top, relief=Tkinter.FLAT, bd=0, + takefocus=0) + self.cframe = Tkinter.Frame(self.frame, relief=Tkinter.SUNKEN, bd=1, + takefocus=0) + self.canvas = Tkinter.Canvas(self.cframe, width=width, height=height, + takefocus=0, bd=0, highlightthickness=0) + self.scale = self.canvas.create_rectangle(-10, -10, 0, height, + outline=color, fill=color) + self.text = -1 + if show_text: + self.text = self.canvas.create_text(0, 0, anchor=Tkinter.CENTER) + self.cframe.grid_configure(column=0, row=0, sticky="ew") + if images: + self.f1 = Tkinter.Label(self.frame, image=images[0]) + self.f1.grid_configure(column=0, row=0, sticky="ew", ipadx=8, ipady=4) + self.cframe.grid_configure(column=1, row=0, sticky="ew", padx=8) + self.f2 = Tkinter.Label(self.frame, image=images[1]) + self.f2.grid_configure(column=2, row=0, sticky="ew", ipadx=8, ipady=4) + self.top.config(cursor="watch") + if app: + try: + wm_set_icon(self.top, app.dataloader.findIcon()) + except: pass + self.pack() + if 1: + setTransient(self.top, None, relx=0.5, rely=0.5) + else: + self.update(percent=0) + + def wmDeleteWindow(self): + return EVENT_HANDLED + + def destroy(self): + if self.top is None: # already destroyed + return + self.top.wm_withdraw() + self.top.quit() + self.top.destroy() + self.top = None + + def pack(self, **kw): + self.canvas.pack(fill=Tkinter.X, expand=0) + apply(self.frame.pack, (), kw) + + def reset(self, percent=0): + self.percent = percent + + def update(self, percent=None, step=1): + if self.top is None: # already destroyed + return + if percent is None: + self.percent = self.percent + step + elif percent > self.percent: + self.percent = percent + else: + return + self.percent = min(100, max(0, self.percent)) + c = self.canvas + width, height = c.winfo_reqwidth(), c.winfo_reqheight() + c.coords(self.scale, -10, -10, + (self.percent * width ) / 100.0, height) + if self.text >= 0: + c.coords(self.text, width/2, height/2) + c.itemconfig(self.text, text="%d %%" % int(round(self.percent))) + c.update() + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +class TestProgressBar: + def __init__(self, parent): + self.parent = parent + self.progress = PysolProgressBar(None, parent, title="Progress", color="#008200") + self.progress.pack(ipadx=10, ipady=10) + self.progress.frame.after(1000, self.update) + + def update(self, event=None): + if self.progress.percent >= 100: + self.parent.after_idle(self.progress.destroy) + return + self.progress.update(step=1) + self.progress.frame.after(30, self.update) + +def progressbar_main(args): + from tkutil import wm_withdraw + tk = Tkinter.Tk() + wm_withdraw(tk) + pb = TestProgressBar(tk) + tk.mainloop() + return 0 + +if __name__ == "__main__": + import sys + sys.exit(progressbar_main(sys.argv)) + + diff --git a/pysollib/tk/selectcardset.py b/pysollib/tk/selectcardset.py new file mode 100644 index 0000000000..7c9b4d0a80 --- /dev/null +++ b/pysollib/tk/selectcardset.py @@ -0,0 +1,404 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['SelectCardsetByTypeDialogWithPreview'] + +# imports +import os, re, sys, types, Tkinter + +# PySol imports +from pysollib.mfxutil import destruct, Struct, KwStruct +from pysollib.util import CARDSET +from pysollib.resource import CSI + +# Toolkit imports +from tkutil import loadImage +from tkwidget import _ToplevelDialog, MfxDialog, MfxScrolledCanvas +from tkcanvas import MfxCanvasImage +from selecttree import SelectDialogTreeLeaf, SelectDialogTreeNode +from selecttree import SelectDialogTreeData, SelectDialogTreeCanvas + + +# /*********************************************************************** +# // Nodes +# ************************************************************************/ + +class SelectCardsetLeaf(SelectDialogTreeLeaf): + pass + + +class SelectCardsetNode(SelectDialogTreeNode): + def _getContents(self): + contents = [] + for obj in self.tree.data.all_objects: + if self.select_func(obj): + node = SelectCardsetLeaf(self.tree, self, text=obj.name, key=obj.index) + contents.append(node) + return contents or self.tree.data.no_contents + + +# /*********************************************************************** +# // Tree database +# ************************************************************************/ + +class SelectCardsetData(SelectDialogTreeData): + def __init__(self, manager, key): + SelectDialogTreeData.__init__(self) + self.all_objects = manager.getAllSortedByName() + self.all_objects = filter(lambda obj: not obj.error, self.all_objects) + self.no_contents = [ SelectCardsetLeaf(None, None, _("(no cardsets)"), key=None), ] + # + select_by_type = None + items = CSI.TYPE.items() + items.sort(lambda a, b: cmp(a[1], b[1])) + nodes = [] + for key, name in items: + if manager.registered_types.get(key): + nodes.append(SelectCardsetNode(None, name, lambda cs, key=key: key == cs.si.type)) + if nodes: + select_by_type = SelectCardsetNode(None, _("by Type"), tuple(nodes), expanded=1) + # + select_by_style = None + items = CSI.STYLE.items() + items.sort(lambda a, b: cmp(a[1], b[1])) + nodes = [] + for key, name in items: + if manager.registered_styles.get(key): + nodes.append(SelectCardsetNode(None, name, lambda cs, key=key: key in cs.si.styles)) + if nodes: + nodes.append(SelectCardsetNode(None, _("Uncategorized"), lambda cs: not cs.si.styles)) + select_by_style = SelectCardsetNode(None, _("by Style"), tuple(nodes)) + # + select_by_nationality = None + items = CSI.NATIONALITY.items() + items.sort(lambda a, b: cmp(a[1], b[1])) + nodes = [] + for key, name in items: + if manager.registered_nationalities.get(key): + nodes.append(SelectCardsetNode(None, name, lambda cs, key=key: key in cs.si.nationalities)) + if nodes: + nodes.append(SelectCardsetNode(None, _("Uncategorized"), lambda cs: not cs.si.nationalities)) + select_by_nationality = SelectCardsetNode(None, _("by Nationality"), tuple(nodes)) + # + select_by_date = None + items = CSI.DATE.items() + items.sort(lambda a, b: cmp(a[1], b[1])) + nodes = [] + for key, name in items: + if manager.registered_dates.get(key): + nodes.append(SelectCardsetNode(None, name, lambda cs, key=key: key in cs.si.dates)) + if nodes: + nodes.append(SelectCardsetNode(None, _("Uncategorized"), lambda cs: not cs.si.dates)) + select_by_date = SelectCardsetNode(None, _("by Date"), tuple(nodes)) + # + self.rootnodes = filter(None, ( + SelectCardsetNode(None, _("All Cardsets"), lambda cs: 1, expanded=len(self.all_objects)<=12), + SelectCardsetNode(None, _("by Size"), ( + SelectCardsetNode(None, _("Tiny cardsets"), lambda cs: cs.si.size == CSI.SIZE_TINY), + SelectCardsetNode(None, _("Small cardsets"), lambda cs: cs.si.size == CSI.SIZE_SMALL), + SelectCardsetNode(None, _("Medium cardsets"), lambda cs: cs.si.size == CSI.SIZE_MEDIUM), + SelectCardsetNode(None, _("Large cardsets"), lambda cs: cs.si.size == CSI.SIZE_LARGE), + SelectCardsetNode(None, _("XLarge cardsets"), lambda cs: cs.si.size == CSI.SIZE_XLARGE), + ), expanded=1), + select_by_type, + select_by_style, + select_by_date, + select_by_nationality, + )) + + +class SelectCardsetByTypeData(SelectDialogTreeData): + def __init__(self, manager, key): + SelectDialogTreeData.__init__(self) + self.all_objects = manager.getAllSortedByName() + self.no_contents = [ SelectCardsetLeaf(None, None, _("(no cardsets)"), key=None), ] + # + items = CSI.TYPE.items() + items.sort(lambda a, b: cmp(a[1], b[1])) + nodes = [] + for key, name in items: + if manager.registered_types.get(key): + nodes.append(SelectCardsetNode(None, name, lambda cs, key=key: key == cs.si.type)) + select_by_type = SelectCardsetNode(None, _("by Type"), tuple(nodes), expanded=1) + # + self.rootnodes = filter(None, ( + select_by_type, + )) + + +# /*********************************************************************** +# // Canvas that shows the tree +# ************************************************************************/ + +class SelectCardsetTree(SelectDialogTreeCanvas): + data = None + + +class SelectCardsetByTypeTree(SelectDialogTreeCanvas): + data = None + + +# /*********************************************************************** +# // Dialog +# ************************************************************************/ + +class SelectCardsetDialogWithPreview(MfxDialog): + Tree_Class = SelectCardsetTree + TreeDataHolder_Class = SelectCardsetTree + TreeData_Class = SelectCardsetData + + def __init__(self, parent, title, app, manager, key=None, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + if key is None: + key = manager.getSelected() + self.manager = manager + self.key = key + #padx, pady = kw.padx, kw.pady + padx, pady = 5, 5 + if self.TreeDataHolder_Class.data is None: + self.TreeDataHolder_Class.data = self.TreeData_Class(manager, key) + # + self.top.wm_minsize(400, 200) + if self.top.winfo_screenwidth() >= 800: + w1, w2 = 216, 400 + else: + w1, w2 = 200, 300 + if Tkinter.TkVersion >= 8.4: + paned_window = Tkinter.PanedWindow(top_frame) + paned_window.pack(expand=1, fill='both') + left_frame = Tkinter.Frame(paned_window) + right_frame = Tkinter.Frame(paned_window) + paned_window.add(left_frame) + paned_window.add(right_frame) + else: + left_frame = Tkinter.Frame(top_frame) + right_frame = Tkinter.Frame(top_frame) + left_frame.pack(side='left', expand=0, fill='both') + right_frame.pack(side='right', expand=1, fill='both') + font = app.getFont("default") + self.tree = self.Tree_Class(self, left_frame, key=key, + default=kw.default, + font=font, width=w1) + self.tree.frame.pack(fill='both', expand=1, padx=padx, pady=pady) + self.preview = MfxScrolledCanvas(right_frame, width=w2) + self.preview.setTile(app, app.tabletile_index, force=True) + self.preview.pack(fill='both', expand=1, padx=padx, pady=pady) + # create a preview of the current state + self.preview_key = -1 + self.preview_images = [] + self.updatePreview(key) + # + focus = self.createButtons(bottom_frame, kw) + focus = self.tree.frame + self.mainloop(focus, kw.timeout) + + def destroy(self): + self.tree.updateNodesWithTree(self.tree.rootnodes, None) + self.tree.destroy() + self.preview.unbind_all() + self.preview_images = [] + MfxDialog.destroy(self) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Load"), _("Cancel"),), default=0, + separatorwidth=2, resizable=1, + font=None, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + + def mDone(self, button): + if button in (0, 1): # Ok/Load + self.key = self.tree.selection_key + self.tree.n_expansions = 1 # save xyview in any case + if button in (3, 4): + cs = self.manager.get(self.tree.selection_key) + if not cs: + return + ##title = CARDSET+" "+cs.name + title = CARDSET.capitalize()+" "+cs.name + CardsetInfoDialog(self.top, title=title, cardset=cs, images=self.preview_images) + return + MfxDialog.mDone(self, button) + + def updatePreview(self, key): + if key == self.preview_key: + return + canvas = self.preview.canvas + canvas.deleteAllItems() + self.preview_images = [] + cs = self.manager.get(key) + if not cs: + self.preview_key = -1 + return + names, columns = cs.getPreviewCardNames() + try: + #???names, columns = cs.getPreviewCardNames() + for n in names: + f = os.path.join(cs.dir, n + cs.ext) + self.preview_images.append(loadImage(file=f)) + except: + self.preview_key = -1 + self.preview_images = [] + return + i, x, y, sx, sy, dx, dy = 0, 10, 10, 0, 0, cs.CARDW + 10, cs.CARDH + 10 + for image in self.preview_images: + MfxCanvasImage(canvas, x, y, anchor="nw", image=image) + sx, sy = max(x, sx), max(y, sy) + i = i + 1 + if i % columns == 0: + x, y = 10, y + dy + else: + x = x + dx + canvas.config(scrollregion=(0, 0, sx+dx, sy+dy)) + canvas.config(width=sx+dx, height=sy+dy) + #canvas.config(xscrollincrement=dx, yscrollincrement=dy) +## self.preview.showHbar() +## self.preview.showVbar() + self.preview_key = key + + +class SelectCardsetByTypeDialogWithPreview(SelectCardsetDialogWithPreview): + Tree_Class = SelectCardsetByTypeTree + TreeDataHolder_Class = SelectCardsetByTypeTree + TreeData_Class = SelectCardsetByTypeData + +# /*********************************************************************** +# // Cardset Info +# ************************************************************************/ + +class CardsetInfoDialog(MfxDialog): + def __init__(self, parent, title, cardset, images, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + frame = Tkinter.Frame(top_frame) + frame.pack(fill="both", expand=True, padx=5, pady=10) + # + # + if Tkinter.TkVersion >= 8.4: + info_frame = Tkinter.LabelFrame(frame, text=_('About cardset')) + else: + info_frame = Tkinter.Frame(frame) + info_frame.grid(row=0, column=0, columnspan=2, sticky='ew', + padx=0, pady=5, ipadx=5, ipady=5) + styles = nationalities = year = None + if cardset.si.styles: + styles = '\n'.join([CSI.STYLE[i] for i in cardset.si.styles]) + if cardset.si.nationalities: + nationalities = '\n'.join([CSI.NATIONALITY[i] + for i in cardset.si.nationalities]) + if cardset.year: + year = str(cardset.year) + row = 0 + for n, t in ( + ##('Version:', str(cardset.version)), + (_('Type:'), CSI.TYPE[cardset.type]), + (_('Styles:'), styles), + (_('Nationality:'), nationalities), + (_('Year:'), year), + ##(_('Number of cards:'), str(cardset.ncards)), + (_('Size:'), '%d x %d' % (cardset.CARDW, cardset.CARDH)), + ): + if not t is None: + l = Tkinter.Label(info_frame, text=n, + anchor='w', justify='left') + l.grid(row=row, column=0, sticky='nw') + l = Tkinter.Label(info_frame, text=t, + anchor='w', justify='left') + l.grid(row=row, column=1, sticky='nw') + row += 1 + if images: + try: + from random import choice + im = choice(images) + f = os.path.join(cardset.dir, cardset.backname) + self.back_image = loadImage(file=f) + canvas = Tkinter.Canvas(info_frame, + width=2*im.width()+30, + height=im.height()+2) + canvas.create_image(10, 1, image=im, anchor='nw') + canvas.create_image(im.width()+20, 1, + image=self.back_image, anchor='nw') + canvas.grid(row=0, column=2, rowspan=row+1, sticky='ne') + info_frame.columnconfigure(2, weight=1) + info_frame.rowconfigure(row, weight=1) + except: + pass + ##bg = top_frame["bg"] + bg = 'white' + text_w = Tkinter.Text(frame, bd=1, relief="sunken", wrap="word", + padx=4, width=64, height=16, bg=bg) + text_w.grid(row=1, column=0, sticky='nsew') + sb = Tkinter.Scrollbar(frame) + sb.grid(row=1, column=1, sticky='ns') + text_w.configure(yscrollcommand=sb.set) + sb.configure(command=text_w.yview) + frame.columnconfigure(0, weight=1) + frame.rowconfigure(1, weight=1) + # + text = '' + f = os.path.join(cardset.dir, "COPYRIGHT") + try: + text = open(f).read() + except: + pass + if text: + text_w.config(state="normal") + text_w.insert("insert", text) + text_w.config(state="disabled") + # + focus = self.createButtons(bottom_frame, kw) + #focus = text_w + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"),), default=0, + resizable = 1, + separatorwidth = 2, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + + diff --git a/pysollib/tk/selectgame.py b/pysollib/tk/selectgame.py new file mode 100644 index 0000000000..f254e2b6d3 --- /dev/null +++ b/pysollib/tk/selectgame.py @@ -0,0 +1,571 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import os, re, sys, types, Tkinter +from UserList import UserList + +# PySol imports +from pysollib.mfxutil import destruct, Struct, KwStruct +from pysollib.mfxutil import format_time +from pysollib.gamedb import GI +from pysollib.help import helpHTML +from pysollib.resource import CSI + +# Toolkit imports +from tkutil import unbind_destroy +from tkwidget import _ToplevelDialog, MfxDialog, MfxScrolledCanvas +from tkcanvas import MfxCanvasText +from selecttree import SelectDialogTreeLeaf, SelectDialogTreeNode +from selecttree import SelectDialogTreeData, SelectDialogTreeCanvas + +gettext = _ + +# /*********************************************************************** +# // Nodes +# ************************************************************************/ + +class SelectGameLeaf(SelectDialogTreeLeaf): + pass + + +class SelectGameNode(SelectDialogTreeNode): + def _getContents(self): + contents = [] + if isinstance(self.select_func, UserList): + # key/value pairs + for id, name in self.select_func: + if id and name: + name = gettext(name) # name of game + node = SelectGameLeaf(self.tree, self, name, key=id) + contents.append(node) + else: + for gi in self.tree.data.all_games_gi: + if gi and self.select_func is None: + # All games + ##name = '%s (%s)' % (gi.name, CSI.TYPE_NAME[gi.category]) + name = gi.name + name = gettext(name) # name of game + node = SelectGameLeaf(self.tree, self, name, key=gi.id) + contents.append(node) + elif gi and self.select_func(gi): + name = gi.name + name = gettext(name) # name of game + node = SelectGameLeaf(self.tree, self, name, key=gi.id) + contents.append(node) + return contents or self.tree.data.no_games + + +# /*********************************************************************** +# // Tree database +# ************************************************************************/ + +class SelectGameData(SelectDialogTreeData): + def __init__(self, app): + SelectDialogTreeData.__init__(self) + self.all_games_gi = map(app.gdb.get, app.gdb.getGamesIdSortedByName()) + self.no_games = [ SelectGameLeaf(None, None, _("(no games)"), None), ] + # + s_by_type = s_oriental = s_special = s_original = s_contrib = None + g = [] + for data in (GI.SELECT_GAME_BY_TYPE, + GI.SELECT_ORIENTAL_GAME_BY_TYPE, + GI.SELECT_SPECIAL_GAME_BY_TYPE, + GI.SELECT_ORIGINAL_GAME_BY_TYPE, + GI.SELECT_CONTRIB_GAME_BY_TYPE): + gg = [] + for name, select_func in data: + if name is None or not filter(select_func, self.all_games_gi): + continue + name = gettext(name) + name = name.replace("&", "") + gg.append(SelectGameNode(None, name, select_func)) + g.append(gg) + if 1 and g[0]: + s_by_type = SelectGameNode(None, _("French games"), tuple(g[0]), expanded=1) + pass + if 1 and g[1]: + s_oriental = SelectGameNode(None, _("Oriental Games"), tuple(g[1])) + pass + if 1 and g[2]: + s_special = SelectGameNode(None, _("Special Games"), tuple(g[2])) + pass + if 1 and g[3]: + s_original = SelectGameNode(None, _("Original Games"), tuple(g[3])) + pass + if 1 and g[4]: + ##s_contrib = SelectGameNode(None, "Contributed Games", tuple(g[4])) + pass + # + s_by_compatibility, gg = None, [] + for name, games in GI.GAMES_BY_COMPATIBILITY: + select_func = lambda gi, games=games: gi.id in games + if name is None or not filter(select_func, self.all_games_gi): + continue + name = gettext(name) + gg.append(SelectGameNode(None, name, select_func)) + if 1 and gg: + s_by_compatibility = SelectGameNode(None, _("by Compatibility"), tuple(gg)) + pass +## # +## s_mahjongg, gg = None, [] +## for name, games in GI.GAMES_BY_COMPATIBILITY: +## select_func = lambda gi, games=games: gi.id in games +## if name is None or not filter(select_func, self.all_games_gi): +## continue +## gg.append(SelectGameNode(None, name, select_func)) +## if 1 and gg: +## s_by_compatibility = SelectGameNode(None, "by Compatibility", tuple(gg)) +## pass + # + s_by_pysol_version, gg = None, [] + for name, games in GI.GAMES_BY_PYSOL_VERSION: + select_func = lambda gi, games=games: gi.id in games + if name is None or not filter(select_func, self.all_games_gi): + continue + name = _("New games in v") + name + gg.append(SelectGameNode(None, name, select_func)) + if 1 and gg: + s_by_pysol_version = SelectGameNode(None, _("by PySol version"), tuple(gg)) + pass + # + ul_alternate_names = UserList(list(app.gdb.getGamesTuplesSortedByAlternateName())) + # + self.rootnodes = filter(None, ( + #SelectGameNode(None, "All Games", lambda gi: 1, expanded=0), + SelectGameNode(None, _("All Games"), None, expanded=0), + SelectGameNode(None, _("Alternate Names"), ul_alternate_names), + SelectGameNode(None, _("Popular Games"), lambda gi: gi.si.game_flags & GI.GT_POPULAR, expanded=0), + SelectGameNode(None, _("Mahjongg Games"), + lambda gi: gi.si.game_type == GI.GT_MAHJONGG, + expanded=0), + s_oriental, + s_special, + s_by_type, + SelectGameNode(None, _("by Game Feature"), ( + SelectGameNode(None, _("by Number of Cards"), ( + SelectGameNode(None, _("32 cards"), lambda gi: gi.si.ncards == 32), + SelectGameNode(None, _("48 cards"), lambda gi: gi.si.ncards == 48), + SelectGameNode(None, _("52 cards"), lambda gi: gi.si.ncards == 52), + SelectGameNode(None, _("64 cards"), lambda gi: gi.si.ncards == 64), + SelectGameNode(None, _("78 cards"), lambda gi: gi.si.ncards == 78), + SelectGameNode(None, _("104 cards"), lambda gi: gi.si.ncards == 104), + SelectGameNode(None, _("144 cards"), lambda gi: gi.si.ncards == 144), + SelectGameNode(None, _("Other number"), lambda gi: gi.si.ncards not in (32, 48, 52, 64, 78, 104, 144)), + )), + SelectGameNode(None, _("by Number of Decks"), ( + SelectGameNode(None, _("1 deck games"), lambda gi: gi.si.decks == 1), + SelectGameNode(None, _("2 deck games"), lambda gi: gi.si.decks == 2), + SelectGameNode(None, _("3 deck games"), lambda gi: gi.si.decks == 3), + SelectGameNode(None, _("4 deck games"), lambda gi: gi.si.decks == 4), + )), + SelectGameNode(None, _("by Number of Redeals"), ( + SelectGameNode(None, _("No redeal"), lambda gi: gi.si.redeals == 0), + SelectGameNode(None, _("1 redeal"), lambda gi: gi.si.redeals == 1), + SelectGameNode(None, _("2 redeals"), lambda gi: gi.si.redeals == 2), + SelectGameNode(None, _("3 redeals"), lambda gi: gi.si.redeals == 3), + SelectGameNode(None, _("Unlimited redeals"), lambda gi: gi.si.redeals == -1), +## SelectGameNode(None, "Variable redeals", lambda gi: gi.si.redeals == -2), + SelectGameNode(None, _("Other number of redeals"), lambda gi: gi.si.redeals not in (-1, 0, 1, 2, 3)), + )), + s_by_compatibility, + )), + s_by_pysol_version, + SelectGameNode(None, _("Other Categories"), ( + SelectGameNode(None, _("Games for Children (very easy)"), lambda gi: gi.si.game_flags & GI.GT_CHILDREN), + SelectGameNode(None, _("Games with Scoring"), lambda gi: gi.si.game_flags & GI.GT_SCORE), + SelectGameNode(None, _("Games with Separate Decks"), lambda gi: gi.si.game_flags & GI.GT_SEPARATE_DECKS), + SelectGameNode(None, _("Open Games (all cards visible)"), lambda gi: gi.si.game_flags & GI.GT_OPEN), + SelectGameNode(None, _("Relaxed Variants"), lambda gi: gi.si.game_flags & GI.GT_RELAXED), + )), + s_original, + s_contrib, + )) + + +# /*********************************************************************** +# // Canvas that shows the tree +# ************************************************************************/ + +class SelectGameTreeWithPreview(SelectDialogTreeCanvas): + data = None + html_viewer = None + + +class SelectGameTree(SelectGameTreeWithPreview): + def singleClick(self, event=None): + self.doubleClick(event) + + +# /*********************************************************************** +# // Dialog +# ************************************************************************/ + +class SelectGameDialog(MfxDialog): + Tree_Class = SelectGameTree + TreeDataHolder_Class = SelectGameTreeWithPreview + TreeData_Class = SelectGameData + + def __init__(self, parent, title, app, gameid, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.app = app + self.gameid = gameid + self.random = None + if self.TreeDataHolder_Class.data is None: + self.TreeDataHolder_Class.data = self.TreeData_Class(app) + # + self.top.wm_minsize(200, 200) + font = app.getFont("default") + self.tree = self.Tree_Class(self, top_frame, key=gameid, + font=font, + default=kw.default) + self.tree.frame.pack(fill=Tkinter.BOTH, expand=1, + padx=kw.padx, pady=kw.pady) + # + focus = self.createButtons(bottom_frame, kw) + focus = self.tree.frame + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(None, None, _("Cancel"),), default=0, + separatorwidth=2, resizable=1, + font=None, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + + def destroy(self): + self.app = None + self.tree.updateNodesWithTree(self.tree.rootnodes, None) + self.tree.destroy() + MfxDialog.destroy(self) + + def mDone(self, button): + if button == 0: # Ok or double click + self.gameid = self.tree.selection_key + self.tree.n_expansions = 1 # save xyview in any case + if button == 1: # Rules + doc = self.app.getGameRulesFilename(self.tree.selection_key) + if not doc: + return + dir = os.path.join("html", "rules") + helpHTML(self.app, doc, dir, self.top) + return + MfxDialog.mDone(self, button) + + +# /*********************************************************************** +# // Dialog +# ************************************************************************/ + +class SelectGameDialogWithPreview(SelectGameDialog): + Tree_Class = SelectGameTreeWithPreview + + def __init__(self, parent, title, app, gameid, bookmark=None, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.app = app + self.gameid = gameid + self.bookmark = bookmark + self.random = None + if self.TreeDataHolder_Class.data is None: + self.TreeDataHolder_Class.data = self.TreeData_Class(app) + # + self.top.wm_minsize(400, 200) + sw = self.top.winfo_screenwidth() + if sw >= 1100: + w1, w2 = 250, 600 + elif sw >= 900: + w1, w2 = 250, 500 + elif sw >= 800: + w1, w2 = 220, 480 + else: + w1, w2 = 200, 300 + ##print sw, w1, w2 + w2 = max(200, min(w2, 10 + 12*(app.subsampled_images.CARDW+10))) + ##print sw, w1, w2 + ##padx, pady = kw.padx, kw.pady + padx, pady = kw.padx/2, kw.pady/2 + # PanedWindow + if Tkinter.TkVersion >= 8.4: + paned_window = Tkinter.PanedWindow(top_frame) + paned_window.pack(expand=1, fill='both') + left_frame = Tkinter.Frame(paned_window) + right_frame = Tkinter.Frame(paned_window) + paned_window.add(left_frame) + paned_window.add(right_frame) + else: + left_frame = Tkinter.Frame(top_frame) + right_frame = Tkinter.Frame(top_frame) + left_frame.pack(side='left', expand=1, fill='both') + right_frame.pack(side='right', expand=1, fill='both') + # Tree + font = app.getFont("default") + self.tree = self.Tree_Class(self, left_frame, key=gameid, + default=kw.default, font=font, width=w1) + self.tree.frame.pack(padx=padx, pady=pady, expand=1, fill='both') + # LabelFrame + if Tkinter.TkVersion >= 8.4: + info_frame = Tkinter.LabelFrame(right_frame, text=_('About game')) + stats_frame = Tkinter.LabelFrame(right_frame, text=_('Statistics')) + else: + info_frame = Tkinter.Frame(right_frame) + stats_frame = Tkinter.Frame(right_frame) + info_frame.grid(row=0, column=0, padx=padx, pady=pady, + ipadx=padx, ipady=pady, sticky='nws') + stats_frame.grid(row=0, column=1, padx=padx, pady=pady, + ipadx=padx, ipady=pady, sticky='nws') + # Info + self.info_labels = {} + i = 0 + for n, t, f, row in ( + ('name', _('Name:'), info_frame, 0), + ('altnames', _('Alternate names:'), info_frame, 1), + ('category', _('Category:'), info_frame, 2), + ('type', _('Type:'), info_frame, 3), + ('decks', _('Decks:'), info_frame, 4), + ('redeals', _('Redeals:'), info_frame, 5), + # + ('played', _('Played:'), stats_frame, 0), + ('won', _('Won:'), stats_frame, 1), + ('lost', _('Lost:'), stats_frame, 2), + ('time', _('Playing time:'), stats_frame, 3), + ('moves', _('Moves:'), stats_frame, 4), + ('percent', _('% won:'), stats_frame, 5), + ): + title_label = Tkinter.Label(f, text=t, justify='left', anchor='w') + title_label.grid(row=row, column=0, sticky='nw') + text_label = Tkinter.Label(f, justify='left', anchor='w') + text_label.grid(row=row, column=1, sticky='nw') + self.info_labels[n] = (title_label, text_label) + ##info_frame.columnconfigure(1, weight=1) + info_frame.rowconfigure(6, weight=1) + stats_frame.rowconfigure(6, weight=1) + # Canvas + self.preview = MfxScrolledCanvas(right_frame, width=w2) + self.preview.setTile(app, app.tabletile_index, force=True) + self.preview.grid(row=1, column=0, columnspan=3, + padx=padx, pady=pady, sticky='nsew') + right_frame.columnconfigure(1, weight=1) + right_frame.rowconfigure(1, weight=1) + + # set the scale factor + self.preview.canvas.preview = 2 + # create a preview of the current game + self.preview_key = -1 + self.preview_game = None + self.preview_app = None + self.updatePreview(gameid, animations=0) + # + focus = self.createButtons(bottom_frame, kw) + ##focus = self.tree.frame + SelectGameTreeWithPreview.html_viewer = None + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("Select"), _("Rules"), _("Cancel"),), + default=0, + ) + return SelectGameDialog.initKw(self, kw) + + def destroy(self): + self.deletePreview(destroy=1) + self.preview.unbind_all() + SelectGameDialog.destroy(self) + + def deletePreview(self, destroy=0): + self.preview_key = -1 + # clean up the canvas + if self.preview: + unbind_destroy(self.preview.canvas) + self.preview.canvas.deleteAllItems() + if destroy: + self.preview.canvas.delete("all") + # + #for l in self.info_labels.values(): + # l.config(text='') + # destruct the game + if self.preview_game: + self.preview_game.endGame() + self.preview_game.destruct() + destruct(self.preview_game) + self.preview_game = None + # destruct the app + if destroy: + if self.preview_app: + destruct(self.preview_app) + self.preview_app = None + + def updatePreview(self, gameid, animations=5): + if gameid == self.preview_key: + return + self.deletePreview() + canvas = self.preview.canvas + # + gi = self.app.gdb.get(gameid) + if not gi: + self.preview_key = -1 + return + # + if self.preview_app is None: + self.preview_app = Struct( + # variables + audio = self.app.audio, + canvas = canvas, + cardset = self.app.cardset.copy(), + comments = self.app.comments.new(), + debug = 0, + gamerandom = self.app.gamerandom, + gdb = self.app.gdb, + gimages = self.app.gimages, + images = self.app.subsampled_images, + menubar = None, + miscrandom = self.app.miscrandom, + opt = self.app.opt.copy(), + startup_opt = self.app.startup_opt, + stats = self.app.stats.new(), + top = None, + top_cursor = self.app.top_cursor, + toolbar = None, + # methods + constructGame = self.app.constructGame, + getFont = self.app.getFont, + ) + self.preview_app.opt.shadow = 0 + self.preview_app.opt.shade = 0 + # + self.preview_app.audio = None # turn off audio for intial dealing + if animations >= 0: + self.preview_app.opt.animations = animations + # + if self.preview_game: + self.preview_game.endGame() + self.preview_game.destruct() + ##self.top.wm_title("Select Game - " + self.app.getGameTitleName(gameid)) + #title = gettext(unicode(self.app.getGameTitleName(gameid), 'utf-8')) + title = gettext(self.app.getGameTitleName(gameid)) + self.top.wm_title(_("Playable Preview - ") + title) +## if False: +## cw, ch = canvas.winfo_width(), canvas.winfo_height() +## if cw >= 100 and ch >= 100: +## MfxCanvasText(canvas, cw / 2, ch - 4, +## preview=0, anchor="s", text=_("Playable Area"), +## font=self.app.getFont("canvas_large")) +## if self.app.opt.table_text_color: +## canvas.setTextColor(self.app.opt.table_text_color_value) + # + self.preview_game = gi.gameclass(gi) + self.preview_game.createPreview(self.preview_app) + tx, ty = 0, 0 + gw, gh = self.preview_game.width, self.preview_game.height + canvas.config(scrollregion=(-tx, -ty, -tx, -ty)) + canvas.xview_moveto(0) + canvas.yview_moveto(0) + # + random = None + if gameid == self.gameid: + random = self.app.game.random.copy() + if gameid == self.gameid and self.bookmark: + self.preview_game.restoreGameFromBookmark(self.bookmark) + else: + self.preview_game.newGame(random=random, autoplay=1) + canvas.config(scrollregion=(-tx, -ty, gw, gh)) + # + self.preview_app.audio = self.app.audio + if self.app.opt.animations: + self.preview_app.opt.animations = 5 + else: + self.preview_app.opt.animations = 0 + # save seed + self.random = self.preview_game.random.copy() + self.random.origin = self.random.ORIGIN_PREVIEW + self.preview_key = gameid + # + self.updateInfo(gameid) + + def updateInfo(self, gameid): + gi = self.app.gdb.get(gameid) + # info + name = gettext(gi.name) + altnames = '\n'.join([gettext(n) for n in gi.altnames]) + category = gettext(CSI.TYPE[gi.category]) + type = '' + if GI.TYPE_NAMES.has_key(gi.si.game_type): + type = gettext(GI.TYPE_NAMES[gi.si.game_type]) + if gi.redeals == -2: redeals = _('variable') + elif gi.redeals == -1: redeals = _('unlimited') + else: redeals = str(gi.redeals) + # stats + won, lost, time, moves = self.app.stats.getFullStats(self.app.opt.player, gameid) + if won+lost > 0: percent = "%.1f" % (100.0*won/(won+lost)) + else: percent = "0.0" + time = format_time(time) + moves = str(round(moves, 1)) + for n, t in ( + ('name', name), + ('altnames', altnames), + ('category', category), + ('type', type), + ('decks', gi.decks), + ('redeals', redeals), + ('played', won+lost), + ('won', won), + ('lost', lost), + ('time', time), + ('moves', moves), + ('percent', percent), + ): + title_label, text_label = self.info_labels[n] + if t == '': + title_label.grid_remove() + text_label.grid_remove() + else: + title_label.grid() + text_label.grid() + text_label.config(text=t) + #self.info_labels[n].config(text=t) + + diff --git a/pysollib/tk/selecttile.py b/pysollib/tk/selecttile.py new file mode 100644 index 0000000000..d50c38964d --- /dev/null +++ b/pysollib/tk/selecttile.py @@ -0,0 +1,205 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + + +# imports +import os, string, sys, types +import Tkinter, tkColorChooser + +# PySol imports +from pysollib.mfxutil import destruct, Struct, KwStruct +from pysollib.resource import CSI + +# Toolkit imports +from tkutil import loadImage +from tkwidget import _ToplevelDialog, MfxDialog, MfxScrolledCanvas +from selecttree import SelectDialogTreeLeaf, SelectDialogTreeNode +from selecttree import SelectDialogTreeData, SelectDialogTreeCanvas + + +# /*********************************************************************** +# // Nodes +# ************************************************************************/ + +class SelectTileLeaf(SelectDialogTreeLeaf): + pass + + +class SelectTileNode(SelectDialogTreeNode): + def _getContents(self): + contents = [] + for obj in self.tree.data.all_objects: + if self.select_func(obj): + node = SelectTileLeaf(self.tree, self, text=obj.name, key=obj.index) + contents.append(node) + return contents or self.tree.data.no_contents + + +# /*********************************************************************** +# // Tree database +# ************************************************************************/ + +class SelectTileData(SelectDialogTreeData): + def __init__(self, manager, key): + SelectDialogTreeData.__init__(self) + self.all_objects = manager.getAllSortedByName() + self.all_objects = filter(lambda obj: not obj.error, self.all_objects) + self.all_objects = filter(lambda tile: tile.index > 0 and tile.filename, self.all_objects) + self.no_contents = [ SelectTileLeaf(None, None, _("(no tiles)"), key=None), ] + e1 = type(key) is types.StringType or len(self.all_objects) <=17 + e2 = 1 + self.rootnodes = ( + SelectTileNode(None, _("Solid Colors"), ( + SelectTileLeaf(None, None, _("Blue"), key="#0082df"), + SelectTileLeaf(None, None, _("Green"), key="#008200"), + SelectTileLeaf(None, None, _("Navy"), key="#000086"), + SelectTileLeaf(None, None, _("Olive"), key="#868200"), + SelectTileLeaf(None, None, _("Orange"), key="#f79600"), + SelectTileLeaf(None, None, _("Teal"), key="#008286"), + ), expanded=e1), + SelectTileNode(None, _("All Backgrounds"), lambda tile: 1, expanded=e2), + ) + + +# /*********************************************************************** +# // Canvas that shows the tree +# ************************************************************************/ + +class SelectTileTree(SelectDialogTreeCanvas): + data = None + + +# /*********************************************************************** +# // Dialog +# ************************************************************************/ + +class SelectTileDialogWithPreview(MfxDialog): + Tree_Class = SelectTileTree + TreeDataHolder_Class = SelectTileTree + TreeData_Class = SelectTileData + + def __init__(self, parent, title, app, manager, key=None, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + if key is None: + key = manager.getSelected() + self.app = app + self.manager = manager + self.key = key + self.table_color = app.opt.table_color + if self.TreeDataHolder_Class.data is None: + self.TreeDataHolder_Class.data = self.TreeData_Class(manager, key) + # + self.top.wm_minsize(400, 200) + if self.top.winfo_screenwidth() >= 800: + w1, w2 = 200, 400 + else: + w1, w2 = 200, 300 + font = app.getFont("default") + self.tree = self.Tree_Class(self, top_frame, key=key, + default=kw.default, + font=font, + width=w1) + self.tree.frame.pack(side="left", fill=Tkinter.BOTH, expand=0, padx=kw.padx, pady=kw.pady) + self.preview = MfxScrolledCanvas(top_frame, width=w2, hbar=0, vbar=0) + self.preview.pack(side="right", fill=Tkinter.BOTH, expand=1, + padx=kw.padx, pady=kw.pady) + # create a preview of the current state + self.preview_key = -1 + self.updatePreview(key) + # + focus = self.createButtons(bottom_frame, kw) + focus = self.tree.frame + self.mainloop(focus, kw.timeout) + + def destroy(self): + self.tree.updateNodesWithTree(self.tree.rootnodes, None) + self.tree.destroy() + self.preview.unbind_all() + MfxDialog.destroy(self) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Solid color..."), _("Cancel"),), default=0, + separatorwidth=2, resizable=1, + font=None, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + + def mDone(self, button): + if button == 0: # "OK" or double click + if type(self.tree.selection_key) in types.StringTypes: + self.key = str(self.tree.selection_key) + else: + self.key = self.tree.selection_key + self.tree.n_expansions = 1 # save xyview in any case + if button == 1: # "Solid color..." + c = tkColorChooser.askcolor(master=self.top, + initialcolor=self.table_color, + title=_("Select table color")) + if c and c[1]: + color = str(c[1]) + self.key = color.lower() + self.table_color = self.key + self.tree.updateSelection(self.key) + self.updatePreview(self.key) + return + MfxDialog.mDone(self, button) + + def updatePreview(self, key): + ##print key + if key == self.preview_key: + return + canvas = self.preview.canvas + canvas.deleteAllItems() + if type(key) in types.StringTypes: + # solid color + canvas.config(bg=key) + canvas.setTile(None) + canvas.setTextColor(None) + self.preview_key = key + self.table_color = key + return + tile = self.manager.get(key) + if tile: + if self.preview.setTile(self.app, key): + return + self.preview_key = -1 + diff --git a/pysollib/tk/selecttree.py b/pysollib/tk/selecttree.py new file mode 100644 index 0000000000..1eddef43b3 --- /dev/null +++ b/pysollib/tk/selecttree.py @@ -0,0 +1,190 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['SelectDialogTreeData'] + +# imports +import os, re, sys, types +import Tkinter, tkFont + +# PySol imports +from pysollib.mfxutil import destruct, Struct, KwStruct, kwdefault + +# Toolkit imports +from tkutil import makeImage +from tkwidget import _ToplevelDialog, MfxDialog, MfxScrolledCanvas +from tkcanvas import MfxCanvas +from tktree import MfxTreeLeaf, MfxTreeNode, MfxTreeInCanvas + + +# /*********************************************************************** +# // Nodes +# ************************************************************************/ + +class SelectDialogTreeLeaf(MfxTreeLeaf): + def drawSymbol(self, x, y, **kw): + if self.tree.nodes.get(self.symbol_id) is not self: + self.symbol_id = self.tree.canvas.create_image(x, y, + image=self.tree.data.img[2+(self.key is None)], anchor="nw") + self.tree.nodes[self.symbol_id] = self + + +class SelectDialogTreeNode(MfxTreeNode): + def __init__(self, tree, text, select_func, expanded=0, parent_node=None): + MfxTreeNode.__init__(self, tree, parent_node, text, key=None, expanded=expanded) + # callable or a tuple/list of MfxTreeNodes + self.select_func = select_func + + def drawSymbol(self, x, y, **kw): + if self.tree.nodes.get(self.symbol_id) is not self: + self.symbol_id = self.tree.canvas.create_image(x, y, + image=self.tree.data.img[self.expanded], anchor="nw") + self.tree.nodes[self.symbol_id] = self + + def getContents(self): + # cached values + if self.subnodes is not None: + return self.subnodes + ##print self.whoami() + if type(self.select_func) in (types.TupleType, types.ListType): + return self.select_func + return self._getContents() + + def _getContents(self): + # subclass + return [] + + +# /*********************************************************************** +# // Tree database +# ************************************************************************/ + +class SelectDialogTreeData: + img = None + def __init__(self): + self.tree_xview = (0.0, 1.0) + self.tree_yview = (0.0, 1.0) + + +# /*********************************************************************** +# // Canvas that shows the tree (left side) +# ************************************************************************/ + +class SelectDialogTreeCanvas(MfxTreeInCanvas): + def __init__(self, dialog, parent, key, default, + font=None, width=-1, height=-1, hbar=2, vbar=3): + self.dialog = dialog + self.default = default + self.n_selections = 0 + self.n_expansions = 0 + # + disty = 16 + if width < 0: + width = 400 + if height < 0: + height = 20 * disty + if parent and parent.winfo_screenheight() >= 600: + height = 25 * disty + if parent and parent.winfo_screenheight() >= 800: + height = 30 * disty + self.lines = height / disty + MfxTreeInCanvas.__init__(self, parent, self.data.rootnodes, + width=width, height=height, + hbar=hbar, vbar=vbar) + self.style.distx = 20 + self.style.disty = disty + self.style.width = 16 # width of symbol + self.style.height = 14 # height of symbol + if font: + self.style.font = font + f = tkFont.Font(parent, font) + h = f.metrics()["linespace"] + self.style.disty = max(self.style.width, h) + + self.draw() + self.updateSelection(key) + if self.hbar: + ##print self.data.tree_yview + ##print self.canvas.xview() + self.canvas.xview_moveto(self.data.tree_xview[0]) + if self.vbar: + ##print self.data.tree_yview + ##print self.canvas.yview() + self.canvas.yview_moveto(self.data.tree_yview[0]) + + def destroy(self): + if self.n_expansions > 0: # must save updated xyview + self.data.tree_xview = self.canvas.xview() + self.data.tree_yview = self.canvas.yview() + MfxTreeInCanvas.destroy(self) + + def getContents(self, node): + return node.getContents() + + def singleClick(self, event=None): + node = self.findNode() + if isinstance(node, MfxTreeLeaf): + if not node.selected and node.key is not None: + oldcur = self.canvas["cursor"] + self.canvas["cursor"] = "watch" + self.canvas.update_idletasks() + self.n_selections = self.n_selections + 1 + self.updateSelection(node.key) + self.dialog.updatePreview(self.selection_key) + self.canvas["cursor"] = oldcur + elif isinstance(node, MfxTreeNode): + self.n_expansions = self.n_expansions + 1 + node.expanded = not node.expanded + self.redraw() + return "break" + + def doubleClick(self, event=None): + node = self.findNode() + if isinstance(node, MfxTreeLeaf): + if node.key is not None: + self.n_selections = self.n_selections + 1 + self.updateSelection(node.key) + self.dialog.mDone(self.default) + elif isinstance(node, MfxTreeNode): + self.n_expansions = self.n_expansions + 1 + node.expanded = not node.expanded + self.redraw() + return "break" + +# /*********************************************************************** +# // Canvas for a preview (right side) +# ************************************************************************/ + +##SelectDialogPreviewCanvas = MfxScrolledCanvas diff --git a/pysollib/tk/soundoptionsdialog.py b/pysollib/tk/soundoptionsdialog.py new file mode 100644 index 0000000000..bb0c70b3a0 --- /dev/null +++ b/pysollib/tk/soundoptionsdialog.py @@ -0,0 +1,180 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['SoundOptionsDialog'] + +# imports +import os, sys, string, Tkinter +import traceback + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct, spawnvp +from pysollib.settings import PACKAGE +from pysollib.pysolaudio import pysolsoundserver +from pysollib.settings import MIXERS + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog + +# /*********************************************************************** +# // +# ************************************************************************/ + +class SoundOptionsDialog(MfxDialog): + MIXER = () + + def __init__(self, parent, title, app, **kw): + self.app = app + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.saved_opt = app.opt.copy() + self.sound = Tkinter.BooleanVar() + self.sound.set(app.opt.sound != 0) + self.sound_mode = Tkinter.BooleanVar() + self.sound_mode.set(app.opt.sound_mode != 0) + self.sample_volume = Tkinter.IntVar() + self.sample_volume.set(app.opt.sound_sample_volume) + self.music_volume = Tkinter.IntVar() + self.music_volume.set(app.opt.sound_music_volume) + widget = Tkinter.Checkbutton(top_frame, variable=self.sound, + text=_("Sound enabled"), + anchor=Tkinter.W) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady, + expand=Tkinter.YES, fill=Tkinter.BOTH) + if os.name == "nt" and pysolsoundserver: + widget = Tkinter.Checkbutton(top_frame, variable=self.sound_mode, + text=_("Use DirectX for sound playing"), + command=self.mOptSoundDirectX) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + if pysolsoundserver and app.startup_opt.sound_mode > 0: + widget = Tkinter.Scale(top_frame, from_=0, to=128, + resolution=1, orient=Tkinter.HORIZONTAL, + length="3i", label=_("Sample volume"), + variable=self.sample_volume, takefocus=0) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady, + expand=Tkinter.YES, fill=Tkinter.BOTH) + widget = Tkinter.Scale(top_frame, from_=0, to=128, + resolution=1, orient=Tkinter.HORIZONTAL, + length="3i", label=_("Music volume"), + variable=self.music_volume, takefocus=0) + widget.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady, + expand=Tkinter.YES, fill=Tkinter.BOTH) + else: + # remove "Apply" button + kw.strings[1] = None + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + strings=[_("OK"), _("Apply"), _("Mixer..."), _("Cancel"),] + if self.MIXER is None: + strings[2] = (_("Mixer..."), -1) +## if os.name != "nt" and not self.app.debug: +## strings[2] = None + kw = KwStruct(kw, + strings=strings, default=0, + separatorwidth=2, resizable=1, + font=None, + padx=10, pady=10, + buttonpadx=10, buttonpady=5, + ) + return MfxDialog.initKw(self, kw) + + def mDone(self, button): + if button == 0 or button == 1: + self.app.opt.sound = self.sound.get() + self.app.opt.sound_mode = self.sound_mode.get() + self.app.opt.sound_sample_volume = self.sample_volume.get() + self.app.opt.sound_music_volume = self.music_volume.get() + elif button == 2: + for name, args in MIXERS: + try: + f = spawnvp(name, args) + if f: + self.MIXER = (f, args) + return + except: + if traceback: traceback.print_exc() + pass + self.MIXER = None + elif button == 3: + self.app.opt = self.saved_opt + if self.app.audio: + self.app.audio.updateSettings() + if button == 1: + self.app.audio.playSample("drop", priority=1000) + if button == 1: + return EVENT_HANDLED + return MfxDialog.mDone(self, button) + + def mCancel(self, *event): + return self.mDone(2) + + def wmDeleteWindow(self, *event): + return self.mDone(0) + + def mOptSoundDirectX(self, *event): + ##print self.sound_mode.get() + d = MfxDialog(self.top, title=_("Sound preferences info"), + text=_("Changing DirectX settings will take effect\nthe next time you restart ")+PACKAGE, + bitmap="warning", + default=0, strings=(_("OK"),)) + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +def soundoptionsdialog_main(args): + from tkutil import wm_withdraw + opt = Struct(sound=1, sound_mode=1, sound_sample_volume=128, sound_music_volume=96) + app = Struct(opt=opt, audio=None, debug=0) + tk = Tkinter.Tk() + wm_withdraw(tk) + tk.update() + d = SoundOptionsDialog(tk, "Sound settings", app) + print d.status, d.button + return 0 + +if __name__ == "__main__": + import sys + sys.exit(soundoptionsdialog_main(sys.argv)) + diff --git a/pysollib/tk/statusbar.py b/pysollib/tk/statusbar.py new file mode 100644 index 0000000000..2ca8645f99 --- /dev/null +++ b/pysollib/tk/statusbar.py @@ -0,0 +1,182 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['PysolStatusbar', + 'HelpStatusbar'] + +# imports +import os, sys, Tkinter + +# PySol imports +from pysollib.mfxutil import destruct + +# Toolkit imports +from tkwidget import MfxTooltip + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxStatusbar: + def __init__(self, top, row, column, columnspan): + self.top = top + self._show = True + self._widgets = [] + self._tooltips = [] + # + self._row = row + self._column = column + self._columnspan = columnspan + self.padx = 0 + self.pady = 0 + # + self.frame = Tkinter.Frame(self.top, bd=1, relief='raised') + self.frame.grid(row=self._row, column=self._column, + columnspan=self._columnspan, sticky='ew') + + # util + def _createLabel(self, name, + text="", relief='sunken', + side='left', fill='none', + padx=-1, expand=0, width=0, + tooltip=None): + if padx < 0: padx = self.padx + label = Tkinter.Label(self.frame, text=text, + width=width, relief=relief) + label.pack(side=side, fill=fill, padx=padx, expand=expand) + setattr(self, name + "_label", label) + self._widgets.append(label) + if tooltip: + b = MfxTooltip(label) + self._tooltips.append(b) + b.setText(tooltip) + return label + + + # + # public methods + # + + def updateText(self, **kw): + for k, v in kw.items(): + label = getattr(self, k + "_label") + #label["text"] = str(v) + label["text"] = unicode(v) + + def configLabel(self, name, **kw): + label = getattr(self, name + "_label") + apply(label.config, (), kw) + + def show(self, show=True, resize=False): + if self._show == show: + return False + if resize: + self.top.wm_geometry("") # cancel user-specified geometry + if not show: + # hide + self.frame.grid_forget() + else: + # show + self.frame.grid(row=self._row, column=self._column, + columnspan=self._columnspan, sticky='ew') + self._show = show + return True + + def hide(self, resize=0): + self.show(None, resize) + + def destroy(self): + for w in self._tooltips: + if w: w.destroy() + self._tooltips = [] + for w in self._widgets: + if w: w.destroy() + self._widgets = [] + + +class PysolStatusbar(MfxStatusbar): + def __init__(self, top): + MfxStatusbar.__init__(self, top, row=3, column=0, columnspan=3) + # + for n, t, w in ( + ("time", _("Playing time"), 10), + ("moves", _('Moves/Total moves'), 10), + ("gamenumber", _("Game number"), 26), + ("stats", _("Games played: won/lost"), 12), + ): + self._createLabel(n, tooltip=t, width=w) + # + l = self._createLabel("info", fill='both', expand=1) + ##l.config(text="", justify="left", anchor='w') + l.config(padx=8) + + +class HelpStatusbar(MfxStatusbar): + def __init__(self, top): + MfxStatusbar.__init__(self, top, row=4, column=0, columnspan=3) + l = self._createLabel("info", fill='both', expand=1) + l.config(text="", justify="left", anchor='w', padx=8) + + +class HtmlStatusbar(MfxStatusbar): + def __init__(self, top, row, column, columnspan): + MfxStatusbar.__init__(self, top, + row=row, column=column, columnspan=columnspan) + l = self._createLabel("url", fill='both', expand=1) + l.config(text="", justify="left", anchor='w', padx=8) + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +class TestStatusbar(PysolStatusbar): + def __init__(self, top, args): + PysolStatusbar.__init__(self, top) + # test some settings + self.updateText(moves=999, gamenumber="#0123456789ABCDEF0123") + self.updateText(info="Some info text.") + +def statusbar_main(args): + tk = Tkinter.Tk() + statusbar = TestStatusbar(tk, args) + tk.mainloop() + return 0 + +if __name__ == "__main__": + sys.exit(statusbar_main(sys.argv)) + + diff --git a/pysollib/tk/timeoutsdialog.py b/pysollib/tk/timeoutsdialog.py new file mode 100644 index 0000000000..caff51542d --- /dev/null +++ b/pysollib/tk/timeoutsdialog.py @@ -0,0 +1,99 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +__all__ = ['TimeoutsDialog'] + +# imports +import os, sys +from Tkinter import * + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct, Struct + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import _ToplevelDialog, MfxDialog + +# /*********************************************************************** +# // +# ************************************************************************/ + +class TimeoutsDialog(MfxDialog): + def __init__(self, parent, title, app, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + #self.createBitmaps(top_frame, kw) + + frame = Frame(top_frame) + frame.pack(expand=YES, fill=BOTH, padx=5, pady=10) + frame.columnconfigure(0, weight=1) + + self.demo_sleep_var = DoubleVar() + self.demo_sleep_var.set(app.opt.demo_sleep) + self.hint_sleep_var = DoubleVar() + self.hint_sleep_var.set(app.opt.hint_sleep) + self.raise_card_sleep_var = DoubleVar() + self.raise_card_sleep_var.set(app.opt.raise_card_sleep) + self.highlight_piles_sleep_var = DoubleVar() + self.highlight_piles_sleep_var.set(app.opt.highlight_piles_sleep) + self.highlight_cards_sleep_var = DoubleVar() + self.highlight_cards_sleep_var.set(app.opt.highlight_cards_sleep) + self.highlight_samerank_sleep_var = DoubleVar() + self.highlight_samerank_sleep_var.set(app.opt.highlight_samerank_sleep) + # + #Label(frame, text='Set delays in seconds').grid(row=0, column=0, columnspan=2) + row = 0 + for title, var in ((_('Demo:'), self.demo_sleep_var), + (_('Hint:'), self.hint_sleep_var), + (_('Raise card:'), self.raise_card_sleep_var), + (_('Highlight piles:'), self.highlight_piles_sleep_var), + (_('Highlight cards:'), self.highlight_cards_sleep_var), + (_('Highlight same rank:'), self.highlight_samerank_sleep_var), + ): + Label(frame, text=title, anchor=W).grid(row=row, column=0, sticky=W+E) + widget = Scale(frame, from_=0.2, to=9.9, + resolution=0.1, orient=HORIZONTAL, + length="3i", + variable=var, takefocus=0) + widget.grid(row=row, column=1) + row += 1 + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + # + self.demo_sleep = self.demo_sleep_var.get() + self.hint_sleep = self.hint_sleep_var.get() + self.raise_card_sleep = self.raise_card_sleep_var.get() + self.highlight_piles_sleep = self.highlight_piles_sleep_var.get() + self.highlight_cards_sleep = self.highlight_cards_sleep_var.get() + self.highlight_samerank_sleep = self.highlight_samerank_sleep_var.get() + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth=0, + ) + return MfxDialog.initKw(self, kw) + + + + diff --git a/pysollib/tk/tkcanvas.py b/pysollib/tk/tkcanvas.py new file mode 100644 index 0000000000..334a431972 --- /dev/null +++ b/pysollib/tk/tkcanvas.py @@ -0,0 +1,343 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['MfxCanvasGroup', + 'MfxCanvasImage', + 'MfxCanvasText', + 'MfxCanvasLine', + 'MfxCanvasRectangle', + 'MfxCanvas'] + +# imports +import os, sys, types +import Tkinter, Canvas +try: + # PIL (Python Image Library) + import Image +except ImportError: + Image = None +else: + import ImageTk + # for py2exe + import GifImagePlugin, PngImagePlugin, JpegImagePlugin, BmpImagePlugin, PpmImagePlugin + Image._initialized=2 + +# Toolkit imports +from tkutil import bind, unbind_destroy, loadImage + + +# /*********************************************************************** +# // canvas items +# ************************************************************************/ + +class MfxCanvasGroup(Canvas.Group): + def __init__(self, canvas, tag=None): + Canvas.Group.__init__(self, canvas=canvas, tag=tag) + # register ourself so that we can unbind from the canvas + assert not self.canvas.items.has_key(self.id) + self.canvas.items[self.id] = self + def addtag(self, tag, option="withtag"): + self.canvas.addtag(tag, option, self.id) + def delete(self): + del self.canvas.items[self.id] + Canvas.Group.delete(self) + def gettags(self): + return self.canvas.tk.splitlist(self._do("gettags")) + +class MfxCanvasImage(Canvas.ImageItem): + def moveTo(self, x, y): + c = self.coords() + self.move(x - int(c[0]), y - int(c[1])) + +MfxCanvasLine = Canvas.Line + +MfxCanvasRectangle = Canvas.Rectangle + +class MfxCanvasText(Canvas.CanvasText): + def __init__(self, canvas, x, y, preview=-1, **kw): + if preview < 0: + preview = canvas.preview + if preview > 1: + return + if not kw.has_key("fill"): + kw["fill"] = canvas._text_color + apply(Canvas.CanvasText.__init__, (self, canvas, x, y), kw) + self.text_format = None + canvas._text_items.append(self) + + +# /*********************************************************************** +# // canvas +# ************************************************************************/ + +class MfxCanvas(Tkinter.Canvas): + def __init__(self, *args, **kw): + apply(Tkinter.Canvas.__init__, (self,) + args, kw) + self.preview = 0 + # this is also used by lib-tk/Canvas.py + self.items = {} + # private + self.__tileimage = None + self.__tiles = [] # id of canvas items + self.__topimage = None + self.__tops = [] # id of canvas items + # friend MfxCanvasText + self._text_color = "#000000" + self._stretch_bg_image = 0 + self._text_items = [] + # resize bg image + self.bind('', lambda e: self.set_bg_image()) + + def set_bg_image(self): + ##print 'set_bg_image', self._bg_img + if not hasattr(self, '_bg_img'): + return + if not self._bg_img: # solid color + return + stretch = self._stretch_bg_image + if Image: + if stretch: + w = max(self.winfo_width(), int(self.cget('width'))) + h = max(self.winfo_height(), int(self.cget('height'))) + im = self._bg_img.resize((w, h)) + image = ImageTk.PhotoImage(im) + else: + image = ImageTk.PhotoImage(self._bg_img) + else: # not Image + stretch = 0 + image = self._bg_img + for id in self.__tiles: + self.delete(id) + self.__tiles = [] + # must keep a reference to the image, otherwise Python will + # garbage collect it... + self.__tileimage = image + if stretch: + # + id = self._x_create("image", 0, 0, image=image, anchor="nw") + self.tag_lower(id) # also see tag_lower above + self.__tiles.append(id) + else: + iw, ih = image.width(), image.height() + #sw = max(self.winfo_screenwidth(), 1024) + #sh = max(self.winfo_screenheight(), 768) + sw = max(self.winfo_width(), int(self.cget('width'))) + sh = max(self.winfo_height(), int(self.cget('height'))) + for x in range(0, sw - 1, iw): + for y in range(0, sh - 1, ih): + id = self._x_create("image", x, y, image=image, anchor="nw") + self.tag_lower(id) # also see tag_lower above + self.__tiles.append(id) + return 1 + + + # + # top-image support + # + + def _x_create(self, itemType, *args, **kw): + return Tkinter.Canvas._create(self, itemType, args, kw) + + def _create(self, itemType, args, kw): + ##print "_create:", itemType, args, kw + id = Tkinter.Canvas._create(self, itemType, args, kw) + if self.__tops: + self.tk.call(self._w, "lower", id, self.__tops[0]) + return id + + def tag_raise(self, id, aboveThis=None): + ##print "tag_raise:", id, aboveThis + if aboveThis is None and self.__tops: + self.tk.call(self._w, "lower", id, self.__tops[0]) + else: + self.tk.call(self._w, "raise", id, aboveThis) + + def tag_lower(self, id, belowThis=None): + ##print "tag_lower:", id, belowThis + if belowThis is None and self.__tiles: + self.tk.call(self._w, "raise", id, self.__tiles[-1]) + else: + self.tk.call(self._w, "lower", id, belowThis) + + + # + # + # + + def setInitialSize(self, width, height): + ##print 'setInitialSize:', width, height + self.config(width=width, height=height) + self.config(scrollregion=(0, 0, width, height)) + + + # + # + # + + # delete all CanvasItems, but keep the background and top tiles + def deleteAllItems(self): + self._text_items = [] + for id in self.items.keys(): + assert not id in self.__tiles # because the tile is created by id + unbind_destroy(self.items[id]) + self.items[id].delete() + assert self.items == {} + + def findCard(self, stack, event): + if isinstance(stack.cards[0].item, Canvas.Group): + current = self.gettags("current") # get tags + for i in range(len(stack.cards)): + if stack.cards[i].item.tag in current: + return i + else: + current = self.find("withtag", "current") # get item ids + for i in range(len(stack.cards)): + if stack.cards[i].item.id in current: + return i + return -1 + + def setTextColor(self, color): + if color is None: + c = self.cget("bg") + if type(c) is not types.StringType or c[0] != "#" or len(c) != 7: + return + v = [] + for i in (1, 3, 5): + v.append(int(c[i:i+2], 16)) + luminance = (0.212671 * v[0] + 0.715160 * v[1] + 0.072169 * v[2]) / 255 + ##print c, ":", v, "luminance", luminance + color = ("#000000", "#ffffff") [luminance < 0.3] + if self._text_color != color: + self._text_color = color + for item in self._text_items: + item.config(fill=self._text_color) + + def setTile(self, image, stretch=0): + ##print 'setTile:', image, stretch + if image: + if Image: + try: + self._bg_img = Image.open(image) + except: + return 0 + else: + try: + self._bg_img = loadImage(file=image, dither=1) + except: + return 0 + self._stretch_bg_image = stretch + self.set_bg_image() + else: + for id in self.__tiles: + self.delete(id) + self.__tiles = [] + self._bg_img = None + return 1 + + def setTopImage(self, image, cw=0, ch=0): + try: + if image and type(image) is types.StringType: + image = loadImage(file=image) + except Tkinter.TclError: + return 0 + if len(self.__tops) == 1 and image is self.__tops[0]: + return 1 + for id in self.__tops: + self.delete(id) + self.__tops = [] + # must keep a reference to the image, otherwise Python will + # garbage collect it... + self.__topimage = image + if image is None: + return 1 + iw, ih = image.width(), image.height() + if cw <= 0: + ##cw = max(int(self.cget("width")), self.winfo_width()) + cw = self.winfo_width() + if ch <= 0: + ##ch = max(int(self.cget("height")), self.winfo_height()) + ch = self.winfo_height() + ###print iw, ih, cw, ch + x = (cw - iw) / 2 + y = (ch - ih) / 2 + id = self._x_create("image", x, y, image=image, anchor="nw") + self.tk.call(self._w, "raise", id) + self.__tops.append(id) + return 1 + + # + # Pause support + # + + def hideAllItems(self): + for item in self.items.values(): + item.config(state='hidden') + + def showAllItems(self): + for item in self.items.values(): + item.config(state='normal') + + + # + # restricted but fast _bind and _substitute + # + + def _bind(self, what, sequence, func, add, needcleanup=1): + funcid = self._register(func, self._substitute, needcleanup) + cmd = ('%sif {"[%s %s]" == "break"} break\n' % + (add and '+' or '', funcid, "%x %y")) + self.tk.call(what + (sequence, cmd)) + return funcid + + def _substitute(self, *args): + e = Tkinter.Event() + e.x = int(args[0]) + e.y = int(args[1]) + return (e,) + + + # + # debug + # + + def update(self): + ##import mfxutil; print mfxutil.callername() + # ?????? + Tkinter.Canvas.update(self) + + def update_idletasks(self): + ##import mfxutil; print mfxutil.callername() + Tkinter.Canvas.update_idletasks(self) + diff --git a/pysollib/tk/tkconst.py b/pysollib/tk/tkconst.py new file mode 100644 index 0000000000..94c52dbcf7 --- /dev/null +++ b/pysollib/tk/tkconst.py @@ -0,0 +1,124 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['tkname', + 'tkversion', + 'TK_DASH_PATCH', + 'EVENT_HANDLED', + 'EVENT_PROPAGATE', + 'CURSOR_DRAG', + 'CURSOR_WATCH', + 'ANCHOR_CENTER', + 'ANCHOR_N', + 'ANCHOR_NW', + 'ANCHOR_NE', + 'ANCHOR_S', + 'ANCHOR_SW', + 'ANCHOR_SE', + 'ANCHOR_W', + 'ANCHOR_E', + 'TOOLBAR_BUTTONS', + ] + +# imports +import sys, os +import Tkinter + +# Toolkit imports + +n_ = lambda x: x + +# /*********************************************************************** +# // constants +# ************************************************************************/ + +tkname = "tk" + +# (major version, minor version, micro version, patchlevel) +tkversion = (8, 0, 0, 0) +try: + m = str(Tkinter._tkinter.TK_VERSION).split(".") + if m: + m = map(int, m) + 4*[0] + tkversion = tuple(m[:4]) + del m +except: + pass + +# experimental +TK_DASH_PATCH = 0 + + +EVENT_HANDLED = "break" +EVENT_PROPAGATE = None + +CURSOR_DRAG = "hand1" +CURSOR_WATCH = "watch" + +ANCHOR_CENTER = Tkinter.CENTER +ANCHOR_N = Tkinter.N +ANCHOR_NW = Tkinter.NW +ANCHOR_NE = Tkinter.NE +ANCHOR_S = Tkinter.S +ANCHOR_SW = Tkinter.SW +ANCHOR_SE = Tkinter.SE +ANCHOR_W = Tkinter.W +ANCHOR_E = Tkinter.E + +COMPOUNDS = ( + ##(Tkinter.BOTTOM, 'bottom'), + ##(Tkinter.CENTER, 'center'), + ##(Tkinter.RIGHT, 'right'), + (Tkinter.NONE, n_('Icons only')), + (Tkinter.TOP, n_('Text below icons')), + (Tkinter.LEFT, n_('Text beside icons')), + ('text', n_('Text only')), + ) + +TOOLBAR_BUTTONS = ( + "new", + "restart", + "open", + "save", + "undo", + "redo", + "autodrop", + "pause", + "statistics", + "rules", + "quit", + "player", + ) + diff --git a/pysollib/tk/tkhtml.py b/pysollib/tk/tkhtml.py new file mode 100644 index 0000000000..afe0f2ce34 --- /dev/null +++ b/pysollib/tk/tkhtml.py @@ -0,0 +1,549 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['tkHTMLViewer'] + +# imports +import os, sys, re, string, types +import htmllib, formatter +import Tkinter + +# PySol imports +from pysollib.mfxutil import Struct, openURL +from pysollib.settings import PACKAGE + +# Toolkit imports +from tkutil import bind, unbind_destroy, loadImage +from tkwidget import MfxDialog +from statusbar import HtmlStatusbar + + +REMOTE_PROTOCOLS = ("ftp:", "gopher:", "http:", "mailto:", "news:", "telnet:") + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxScrolledText(Tkinter.Text): + def __init__(self, parent=None, **cnf): + fcnf = {} + for k in cnf.keys(): + if type(k) is types.ClassType or k == "name": + fcnf[k] = cnf[k] + del cnf[k] + if cnf.has_key("bg"): + fcnf["bg"] = cnf["bg"] + self.frame = apply(Tkinter.Frame, (parent,), fcnf) + self.vbar = Tkinter.Scrollbar(self.frame, name="vbar") + self.vbar.pack(side=Tkinter.RIGHT, fill=Tkinter.Y) + cnf["name"] = "text" + apply(Tkinter.Text.__init__, (self, self.frame), cnf) + self.pack(side=Tkinter.LEFT, fill=Tkinter.BOTH, expand=1) + self["yscrollcommand"] = self.vbar.set + self.vbar["command"] = self.yview + + # FIXME: copy Pack methods of self.frame -- this is a hack! + for m in Tkinter.Pack.__dict__.keys(): + if m[0] != "_" and m != "config" and m != "configure": + ##print m, getattr(self.frame, m) + setattr(self, m, getattr(self.frame, m)) + + self.frame["highlightthickness"] = 0 + self.vbar["highlightthickness"] = 0 + ##print self.__dict__ + + # XXX these are missing in Tkinter.py + def xview_moveto(self, fraction): + return self.tk.call(self._w, "xview", "moveto", fraction) + def xview_scroll(self, number, what): + return self.tk.call(self._w, "xview", "scroll", number, what) + def yview_moveto(self, fraction): + return self.tk.call(self._w, "yview", "moveto", fraction) + def yview_scroll(self, number, what): + return self.tk.call(self._w, "yview", "scroll", number, what) + + +class MfxReadonlyScrolledText(MfxScrolledText): + def __init__(self, parent=None, **cnf): + apply(MfxScrolledText.__init__, (self, parent), cnf) + self.config(state="disabled", insertofftime=0) + self.frame.config(takefocus=0) + self.config(takefocus=0) + self.vbar.config(takefocus=0) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class tkHTMLWriter(formatter.DumbWriter): + def __init__(self, text, viewer, app): + formatter.DumbWriter.__init__(self, self, maxcol=9999) + + self.text = text + self.viewer = viewer + + ## + font = app.getFont("sans") + size = font[1] + fixed = app.getFont("fixed") + sign = 1 + if size < 0: sign = -1 + self.fontmap = { + "h1" : (font[0], size + 12*sign, "bold"), + "h2" : (font[0], size + 8*sign, "bold"), + "h3" : (font[0], size + 6*sign, "bold"), + "h4" : (font[0], size + 4*sign, "bold"), + "h5" : (font[0], size + 2*sign, "bold"), + "h6" : (font[0], size + 1*sign, "bold"), + "bold" : (font[0], size, "bold"), + "italic" : (font[0], size, "italic"), + "pre" : fixed, + } + + self.text.config(cursor=self.viewer.defcursor, font=font) + for f in self.fontmap.keys(): + self.text.tag_config(f, font=self.fontmap[f]) + + self.anchor = None + self.anchor_mark = None + self.font = None + self.font_mark = None + self.indent = "" + + def createCallback(self, href): + class Functor: + def __init__(self, viewer, arg): + self.viewer = viewer + self.arg = arg + def __call__(self, *args): + self.viewer.updateHistoryXYView() + return self.viewer.display(self.arg) + return Functor(self.viewer, href) + + def write(self, data): + ## FIXME + ##if self.col == 0 and self.atbreak == 0: + ## self.text.insert("insert", self.indent) + self.text.insert("insert", data) + + def __write(self, data): + self.text.insert("insert", data) + + def anchor_bgn(self, href, name, type): + if href: + ##self.text.update_idletasks() # update display during parsing + self.anchor = (href, name, type) + self.anchor_mark = self.text.index("insert") + + def anchor_end(self): + if self.anchor: + url = self.anchor[0] + tag = "href_" + url + self.text.tag_add(tag, self.anchor_mark, "insert") + self.text.tag_bind(tag, "<1>", self.createCallback(url)) + self.text.tag_bind(tag, "", lambda e: self.anchor_enter(url)) + self.text.tag_bind(tag, "", self.anchor_leave) + self.text.tag_config(tag, foreground="blue", underline=1) + self.anchor = None + + def anchor_enter(self, url): + for p in REMOTE_PROTOCOLS: + if url.startswith(p): + break + else: + url = 'file://'+self.viewer.basejoin(url) + self.viewer.statusbar.updateText(url=url) + self.text.config(cursor=self.viewer.handcursor) + + def anchor_leave(self, *args): + self.viewer.statusbar.updateText(url='') + self.text.config(cursor=self.viewer.defcursor) + + def new_font(self, font): + # end the current font + if self.font: + ##print "end_font(%s)" % `self.font` + self.text.tag_add(self.font, self.font_mark, "insert") + self.font = None + # start the new font + if font: + ##print "start_font(%s)" % `font` + self.font_mark = self.text.index("insert") + if self.fontmap.has_key(font[0]): + self.font = font[0] + elif font[3]: + self.font = "pre" + elif font[2]: + self.font = "bold" + elif font[1]: + self.font = "italic" + else: + self.font = None + + def new_margin(self, margin, level): + self.indent = " " * level + + def send_label_data(self, data): + self.__write(self.indent + data + " ") + + def send_paragraph(self, blankline): + if self.col > 0: + self.__write("\n") + if blankline > 0: + self.__write("\n" * blankline) + self.col = 0 + self.atbreak = 0 + + def send_hor_rule(self, *args): + width = int(int(self.text["width"]) * 0.9) + self.__write("_" * width) + self.__write("\n") + self.col = 0 + self.atbreak = 0 + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class tkHTMLParser(htmllib.HTMLParser): + def anchor_bgn(self, href, name, type): + htmllib.HTMLParser.anchor_bgn(self, href, name, type) + self.formatter.writer.anchor_bgn(href, name, type) + + def anchor_end(self): + if self.anchor: + self.anchor = None + self.formatter.writer.anchor_end() + + def do_dt(self, attrs): + self.formatter.end_paragraph(1) + self.ddpop() + + def handle_image(self, src, alt, ismap, align, width, height): + self.formatter.writer.viewer.showImage(src, alt, ismap, align, width, height) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class tkHTMLViewer: + def __init__(self, parent): + self.parent = parent + self.home = None + self.url = None + self.history = Struct( + list = [], + index = 0, + ) + self.images = {} # need to keep a reference because of garbage collection + self.defcursor = parent["cursor"] + self.handcursor = "hand2" + + # create buttons + button_width = 8 + self.homeButton = Tkinter.Button(parent, text=_("Index"), + width=button_width, + command=self.goHome) + self.homeButton.grid(row=0, column=0, sticky='w') + self.backButton = Tkinter.Button(parent, text=_("Back"), + width=button_width, + command=self.goBack) + self.backButton.grid(row=0, column=1, sticky='w') + self.forwardButton = Tkinter.Button(parent, text=_("Forward"), + width=button_width, + command=self.goForward) + self.forwardButton.grid(row=0, column=2, sticky='w') + self.closeButton = Tkinter.Button(parent, text=_("Close"), + width=button_width, + command=self.destroy) + self.closeButton.grid(row=0, column=3, sticky='e') + + # create text widget + text_frame = Tkinter.Frame(parent) + text_frame.grid(row=1, column=0, columnspan=4, sticky='nsew') + self.text = MfxReadonlyScrolledText(text_frame, + fg="#000000", bg="#f7f3ff", + cursor=self.defcursor, + wrap="word", padx=20, pady=20) + self.text.pack(side="top", fill="both", expand=1) + + # statusbar + self.statusbar = HtmlStatusbar(parent, row=2, column=0, columnspan=4) + + parent.columnconfigure(2, weight=1) + parent.rowconfigure(1, weight=1) + + self.initBindings() + + def initBindings(self): + w = self.parent + bind(w, "WM_DELETE_WINDOW", self.destroy) + bind(w, "", self.destroy) + bind(w, "", self.page_up) + bind(w, "", self.page_down) + bind(w, "", self.unit_up) + bind(w, "", self.unit_down) + bind(w, "", self.scroll_top) + bind(w, "", self.scroll_top) + bind(w, "", self.scroll_bottom) + bind(w, "", self.goBack) + + def destroy(self, *event): + unbind_destroy(self.parent) + try: + self.parent.wm_withdraw() + except: pass + try: + self.parent.destroy() + except: pass + self.parent = None + + def page_up(self, *event): + self.text.yview_scroll(-1, "page") + return "break" + def page_down(self, *event): + self.text.yview_scroll(1, "page") + return "break" + def unit_up(self, *event): + self.text.yview_scroll(-1, "unit") + return "break" + def unit_down(self, *event): + self.text.yview_scroll(1, "unit") + return "break" + def scroll_top(self, *event): + self.text.yview_moveto(0) + return "break" + def scroll_bottom(self, *event): + self.text.yview_moveto(1) + return "break" + + # locate a file relative to the current self.url + def basejoin(self, url, baseurl=None, relpath=1): + if baseurl is None: + baseurl = self.url + if 0: + import urllib + url = urllib.pathname2url(url) + if relpath and self.url: + url = urllib.basejoin(baseurl, url) + else: + url = os.path.normpath(url) + if relpath and baseurl and not os.path.isabs(url): + h1, t1 = os.path.split(url) + h2, t2 = os.path.split(baseurl) + if cmp(h1, h2) != 0: + url = os.path.join(h2, h1, t1) + url = os.path.normpath(url) + return url + + def openfile(self, url): + if url[-1:] == "/" or os.path.isdir(url): + url = os.path.join(url, "index.html") + url = os.path.normpath(url) + return open(url, "rb"), url + + def display(self, url, add=1, relpath=1, xview=0, yview=0): + # for some reason we have to stop the PySol demo + # (is this a multithread problem with Tkinter ?) + if self.__dict__.get("app"): + if self.app and self.app.game: + self.app.game.stopDemo() + ##self.app.game._cancelDrag() + + # ftp: and http: would work if we use urllib, but this widget is + # far too limited to display anything but our documentation... + for p in REMOTE_PROTOCOLS: + if url.startswith(p): + if not openURL(url): + self.errorDialog(PACKAGE + _(''' HTML limitation: +The %s protocol is not supported yet. + +Please use your standard web browser +to open the following URL: +%s +''') % (p, url)) + return + + # locate the file relative to the current url + url = self.basejoin(url, relpath=relpath) + + # read the file + try: + file = None + if 0: + import urllib + file = urllib.urlopen(url) + else: + file, url = self.openfile(url) + data = file.read() + file.close() + file = None + except Exception, ex: + if file: file.close() + self.errorDialog(_("Unable to service request:\n") + url + "\n\n" + str(ex)) + return + except: + if file: file.close() + self.errorDialog(_("Unable to service request:\n") + url) + return + + self.url = url + if self.home is None: + self.home = self.url + if add: + self.addHistory(self.url, xview=xview, yview=yview) + + ##print self.history.index, self.history.list + if self.history.index > 1: + self.backButton.config(state="normal") + else: + self.backButton.config(state="disabled") + if self.history.index < len(self.history.list): + self.forwardButton.config(state="normal") + else: + self.forwardButton.config(state="disabled") + + old_c1, old_c2 = self.defcursor, self.handcursor + self.defcursor = self.handcursor = "watch" + self.text.config(cursor=self.defcursor) + self.text.update_idletasks() + ##self.frame.config(cursor=self.defcursor) + ##self.frame.update_idletasks() + self.text.config(state="normal") + self.text.delete("1.0", "end") + ##self.images = {} + writer = tkHTMLWriter(self.text, self, self.app) + fmt = formatter.AbstractFormatter(writer) + parser = tkHTMLParser(fmt) + parser.feed(data) + parser.close() + self.text.config(state="disabled") + if 0.0 <= xview <= 1.0: + self.text.xview_moveto(xview) + if 0.0 <= yview <= 1.0: + self.text.yview_moveto(yview) + self.parent.wm_title(parser.title) + self.parent.wm_iconname(parser.title) + self.defcursor, self.handcursor = old_c1, old_c2 + self.text.config(cursor=self.defcursor) + ##self.frame.config(cursor=self.defcursor) + + def addHistory(self, url, xview=0, yview=0): + if self.history.index > 0: + u, xv, yv = self.history.list[self.history.index-1] + if cmp(u, url) == 0: + self.updateHistoryXYView() + return + del self.history.list[self.history.index : ] + self.history.list.append((url, xview, yview)) + self.history.index = self.history.index + 1 + + def updateHistoryXYView(self): + if self.history.index > 0: + url, xview, yview = self.history.list[self.history.index-1] + xview = self.text.xview()[0] + yview = self.text.yview()[0] + self.history.list[self.history.index-1] = (url, xview, yview) + + def goBack(self, *event): + if self.history.index > 1: + self.updateHistoryXYView() + self.history.index = self.history.index - 1 + url, xview, yview = self.history.list[self.history.index-1] + self.display(url, add=0, relpath=0, xview=xview, yview=yview) + + def goForward(self, *event): + if self.history.index < len(self.history.list): + self.updateHistoryXYView() + url, xview, yview = self.history.list[self.history.index] + self.history.index = self.history.index + 1 + self.display(url, add=0, relpath=0, xview=xview, yview=yview) + + def goHome(self, *event): + if self.home and cmp(self.home, self.url) != 0: + self.updateHistoryXYView() + self.display(self.home, relpath=0) + + def errorDialog(self, msg): + d = MfxDialog(self.parent, title=PACKAGE+" HTML Problem", + text=msg, bitmap="warning", + strings=(_("OK"),), default=0) + + def showImage(self, src, alt, ismap, align, width, height): + url = self.basejoin(src) + ##print url, ":", src, alt, ismap, align, width, height + if self.images.has_key(url): + img = self.images[url] + else: + try: + img = Tkinter.PhotoImage(master=self.parent, file=url) + except: + img = None + self.images[url] = img + ##print url, img + if img: + padx, pady = 10, 10 + padx, pady = 0, 20 + if align.lower() == "left": + padx = 0 + self.text.image_create(index="insert", image=img, padx=padx, pady=pady) + + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +def tkhtml_main(args): + try: + url = args[1] + except: + url = os.path.join(os.pardir, os.pardir, "data", "html", "index.html") + top = Tkinter.Tk() + top.wm_minsize(400, 200) + viewer = tkHTMLViewer(top) + viewer.display(url) + top.mainloop() + return 0 + +if __name__ == "__main__": + sys.exit(tkhtml_main(sys.argv)) + + diff --git a/pysollib/tk/tkstats.py b/pysollib/tk/tkstats.py new file mode 100644 index 0000000000..279a68af0a --- /dev/null +++ b/pysollib/tk/tkstats.py @@ -0,0 +1,864 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['SingleGame_StatsDialog', + 'AllGames_StatsDialog', + 'FullLog_StatsDialog', + 'SessionLog_StatsDialog', + 'Status_StatsDialog', + 'Top_StatsDialog'] + +# imports +import os, string, sys, types +import time +import Tkinter, tkFont + +# PySol imports +from pysollib.mfxutil import destruct, Struct, kwdefault, KwStruct +from pysollib.mfxutil import format_time +##from pysollib.util import * +from pysollib.stats import PysolStatsFormatter +from pysollib.settings import TOP_TITLE + +# Toolkit imports +from tkutil import bind, unbind_destroy, loadImage +from tkwidget import _ToplevelDialog, MfxDialog +from tkwidget import MfxScrolledCanvas + +gettext = _ + + +# FIXME - this file a quick hack and needs a rewrite + +# /*********************************************************************** +# // +# ************************************************************************/ + +class SingleGame_StatsDialog(MfxDialog): + def __init__(self, parent, title, app, player, gameid, **kw): + self.app = app + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.top_frame = top_frame + self.createBitmaps(top_frame, kw) + # + self.player = player or _("Demo games") + self.top.wm_minsize(200, 200) + self.button = kw.default + # + ##createChart = self.create3DBarChart + createChart = self.createPieChart + ##createChart = self.createSimpleChart +## if parent.winfo_screenwidth() < 800 or parent.winfo_screenheight() < 600: +## createChart = self.createPieChart +## createChart = self.createSimpleChart + # + self.font = self.app.getFont("default") + self.tk_font = tkFont.Font(self.top, self.font) + self.font_metrics = self.tk_font.metrics() + self._calc_tabs() + # + won, lost = app.stats.getStats(player, gameid) + createChart(app, won, lost, _("Total")) + won, lost = app.stats.getSessionStats(player, gameid) + createChart(app, won, lost, _("Current session")) + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + # + # helpers + # + + def _calc_tabs(self): + # + font = self.tk_font + t0 = 160 + t = '' + for i in (_("Won:"), + _("Lost:"), + _("Total:")): + if len(i) > len(t): + t = i + t1 = font.measure(t) +## t1 = max(font.measure(_("Won:")), +## font.measure(_("Lost:")), +## font.measure(_("Total:"))) + t1 += 10 + ##t2 = font.measure('99999')+10 + t2 = 45 + ##t3 = font.measure('100%')+10 + t3 = 45 + tx = (t0, t0+t1+t2, t0+t1+t2+t3) + # + ls = self.font_metrics['linespace'] + ls += 5 + ls = max(ls, 20) + ty = (ls, 2*ls, 3*ls+15, 3*ls+25) + # + self.tab_x, self.tab_y = tx, ty + + def _getPwon(self, won, lost): + pwon, plost = 0.0, 0.0 + if won + lost > 0: + pwon = float(won) / (won + lost) + pwon = min(max(pwon, 0.00001), 0.99999) + plost = 1.0 - pwon + return pwon, plost + + def _createChartInit(self, text): + w, h = self.tab_x[-1]+20, self.tab_y[-1]+20 + c = Tkinter.Canvas(self.top_frame, width=w, height=h) + c.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=0, padx=20, pady=10) + self.canvas = c + ##self.fg = c.cget("insertbackground") + self.fg = c.option_get('foreground', '') or c.cget("insertbackground") + # + c.create_rectangle(2, 7, w, h, fill="", outline="#7f7f7f") + l = Tkinter.Label(c, text=text, font=self.font, bd=0, padx=3, pady=1) + dy = int(self.font_metrics['ascent']) - 10 + dy = dy/2 + c.create_window(20, -dy, window=l, anchor="nw") + + def _createChartTexts(self, tx, ty, won, lost): + c, tfont, fg = self.canvas, self.font, self.fg + pwon, plost = self._getPwon(won, lost) + # + x = tx[0] + dy = int(self.font_metrics['ascent']) - 10 + dy = dy/2 + c.create_text(x, ty[0]-dy, text=_("Won:"), anchor="nw", font=tfont, fill=fg) + c.create_text(x, ty[1]-dy, text=_("Lost:"), anchor="nw", font=tfont, fill=fg) + c.create_text(x, ty[2]-dy, text=_("Total:"), anchor="nw", font=tfont, fill=fg) + x = tx[1] - 16 + c.create_text(x, ty[0]-dy, text="%d" % won, anchor="ne", font=tfont, fill=fg) + c.create_text(x, ty[1]-dy, text="%d" % lost, anchor="ne", font=tfont, fill=fg) + c.create_text(x, ty[2]-dy, text="%d" % (won + lost), anchor="ne", font=tfont, fill=fg) + y = ty[2] - 11 + c.create_line(tx[0], y, x, y, fill=fg) + if won + lost > 0: + x = tx[2] + pw = int(round(100.0 * pwon)) + c.create_text(x, ty[0]-dy, text="%d%%" % pw, anchor="ne", font=tfont, fill=fg) + c.create_text(x, ty[1]-dy, text="%d%%" % (100-pw), anchor="ne", font=tfont, fill=fg) + + +## def _createChart3DBar(self, canvas, perc, x, y, p, col): +## if perc < 0.005: +## return +## # translate and scale +## p = list(p[:]) +## for i in (0, 1, 2, 3): +## p[i] = (x + p[i][0], y + p[i][1]) +## j = i + 4 +## dx = int(round(p[j][0] * perc)) +## dy = int(round(p[j][1] * perc)) +## p[j] = (p[i][0] + dx, p[i][1] + dy) +## # draw rects +## def draw_rect(a, b, c, d, col, canvas=canvas, p=p): +## points = (p[a][0], p[a][1], p[b][0], p[b][1], +## p[c][0], p[c][1], p[d][0], p[d][1]) +## canvas.create_polygon(points, fill=col) +## draw_rect(0, 1, 5, 4, col[0]) +## draw_rect(1, 2, 6, 5, col[1]) +## draw_rect(4, 5, 6, 7, col[2]) +## # draw lines +## def draw_line(a, b, canvas=canvas, p=p): +## ##print a, b, p[a], p[b] +## canvas.create_line(p[a][0], p[a][1], p[b][0], p[b][1]) +## draw_line(0, 1) +## draw_line(1, 2) +## draw_line(0, 4) +## draw_line(1, 5) +## draw_line(2, 6) +## ###draw_line(3, 7) ## test +## draw_line(4, 5) +## draw_line(5, 6) +## draw_line(6, 7) +## draw_line(7, 4) + + + # + # charts + # + +## def createSimpleChart(self, app, won, lost, text): +## #c, tfont, fg = self._createChartInit(frame, 300, 100, text) +## self._createChartInit(300, 100, text) +## c, tfont, fg = self.canvas, self.font, self.fg +## # +## tx = (90, 180, 210) +## ty = (21, 41, 75) +## self._createChartTexts(tx, ty, won, lost) + +## def create3DBarChart(self, app, won, lost, text): +## image = app.gimages.stats[0] +## iw, ih = image.width(), image.height() +## #c, tfont, fg = self._createChartInit(frame, iw+160, ih, text) +## self._createChartInit(iw+160, ih, text) +## c, tfont, fg = self.canvas, self.font, self.fg +## pwon, plost = self._getPwon(won, lost) +## # +## tx = (iw+20, iw+110, iw+140) +## yy = ih/2 ## + 7 +## ty = (yy+21-46, yy+41-46, yy+75-46) +## # +## c.create_image(0, 7, image=image, anchor="nw") +## # +## p = ((0, 0), (44, 6), (62, -9), (20, -14), +## (-3, -118), (-1, -120), (-1, -114), (-4, -112)) +## col = ("#00ff00", "#008200", "#00c300") +## self._createChart3DBar(c, pwon, 102, 145+7, p, col) +## p = ((0, 0), (49, 6), (61, -10), (15, -15), +## (1, -123), (3, -126), (4, -120), (1, -118)) +## col = ("#ff0000", "#860400", "#c70400") +## self._createChart3DBar(c, plost, 216, 159+7, p, col) +## # +## self._createChartTexts(tx, ty, won, lost) +## c.create_text(tx[0], ty[0]-48, text=self.player, anchor="nw", font=tfont, fill=fg) + + def createPieChart(self, app, won, lost, text): + #c, tfont, fg = self._createChartInit(frame, 300, 100, text) + # + self._createChartInit(text) + c, tfont, fg = self.canvas, self.font, self.fg + pwon, plost = self._getPwon(won, lost) + # + #tx = (160, 250, 280) + #ty = (21, 41, 75) + # + tx, ty = self.tab_x, self.tab_y + if won + lost > 0: + ##s, ewon, elost = 90.0, -360.0 * pwon, -360.0 * plost + s, ewon, elost = 0.0, 360.0 * pwon, 360.0 * plost + c.create_arc(20, 25+9, 110, 75+9, fill="#007f00", start=s, extent=ewon) + c.create_arc(20, 25+9, 110, 75+9, fill="#7f0000", start=s+ewon, extent=elost) + c.create_arc(20, 25, 110, 75, fill="#00ff00", start=s, extent=ewon) + c.create_arc(20, 25, 110, 75, fill="#ff0000", start=s+ewon, extent=elost) + x, y = tx[0] - 25, ty[0] + c.create_rectangle(x, y, x+10, y+10, fill="#00ff00") + y = ty[1] + c.create_rectangle(x, y, x+10, y+10, fill="#ff0000") + else: + c.create_oval(20, 25+10, 110, 75+10, fill="#7f7f7f") + c.create_oval(20, 25, 110, 75, fill="#f0f0f0") + c.create_text(65, 50, text=_("No games"), anchor="center", font=tfont, fill="#bfbfbf") + # + self._createChartTexts(tx, ty, won, lost) + + # + # + # + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), + (_("All games..."), 102), + (TOP_TITLE+"...", 105), + (_("Reset..."), 302)), default=0, + image=self.app.gimages.logos[5], + separatorwidth=2, + resizable=0, + padx=10, pady=10, + ) + return MfxDialog.initKw(self, kw) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class CanvasWriter(PysolStatsFormatter.StringWriter): + def __init__(self, canvas, parent_window, font, w, h): + self.canvas = canvas + self.parent_window = parent_window + ##self.fg = canvas.cget("insertbackground") + self.fg = canvas.option_get('foreground', '') or canvas.cget("insertbackground") + self.font = font + self.w = w + self.h = h + self.x = self.y = 0 + self.gameid = None + self.gamenumber = None + self.canvas.config(yscrollincrement=h) + self._tabs = None + + def _addItem(self, id): + self.canvas.dialog.nodes[id] = (self.gameid, self.gamenumber) + + def p(self, s): + if self.y > 16000: + return + h1, h2 = 0, 0 + while s and s[0] == "\n": + s = s[1:] + h1 = h1 + self.h + while s and s[-1] == "\n": + s = s[:-1] + h2 = h2 + self.h + self.y = self.y + h1 + if s: + id = self.canvas.create_text(self.x, self.y, text=s, anchor="nw", + font=self.font, fill=self.fg) + self._addItem(id) + self.y = self.y + h2 + + def pheader(self, s): + pass + + def _calc_tabs(self, arg): + tw = 15*self.w + ##tw = 160 + self._tabs = [tw] + font = tkFont.Font(self.canvas, self.font) + for t in arg[1:]: + tw = font.measure(t)+20 + self._tabs.append(tw) + self._tabs.append(10) + + def pstats(self, *args, **kwargs): + gameid=kwargs.get('gameid', None) + header = False + if self._tabs is None: + # header + header = True + self._calc_tabs(args) + self.gameid = 'header' + self.gamenumber = None +## if False: +## sort_by = ( 'name', 'played', 'won', 'lost', +## 'time', 'moves', 'percent', ) +## frame = Tkinter.Frame(self.canvas) +## i = 0 +## for t in args: +## w = self._tabs[i] +## if i == 0: +## w += 10 +## b = Tkinter.Button(frame, text=t) +## b.grid(row=0, column=i, sticky='ew') +## b.bind('<1>', lambda e, f=self.parent_window.rearrange, s=sort_by[i]: f(s)) +## frame.columnconfigure(i, minsize=w) +## i += 1 +## self.canvas.create_window(0, 0, window=frame, anchor='nw') +## self.y += 20 +## return +## if False: +## i = 0 +## x = 0 +## for t in args: +## w = self._tabs[i] +## h = 18 +## anchor = 'ne' +## y = 0 +## self.canvas.create_rectangle(x+2, y, x+w, y+h, width=1, +## fill="#00ff00", outline="#000000") +## x += w +## self.canvas.create_text(x-3, y+3, text=t, anchor=anchor) +## i += 1 +## self.y += 20 +## return + + else: + self.gameid = gameid + self.gamenumber = None + if self.y > 16000: + return + x, y = 1, self.y + p = self._pstats_text + t1, t2, t3, t4, t5, t6, t7 = args + h = 0 + if not header: t1=gettext(t1) # game name + + for var, text, anchor, tab in ( + ('name', t1, 'nw', self._tabs[0]+self._tabs[1]), + ('played', t2, 'ne', self._tabs[2]), + ('won', t3, 'ne', self._tabs[3]), + ('lost', t4, 'ne', self._tabs[4]), + ('time', t5, 'ne', self._tabs[5]), + ('moves', t6, 'ne', self._tabs[6]), + ('percent', t7, 'ne', self._tabs[7]), ): + if header: self.gamenumber=var + h = max(h, p(x, y, anchor=anchor, text=text)) + x += tab + + self.pstats_perc(x, y, t7) + self.y += h + self.gameid = None + return + +## h = max(h, p(x, y, anchor="nw", text=t1)) +## if header: self.gamenumber='played' +## x += self._tabs[0]+self._tabs[1] +## h = max(h, p(x, y, anchor="ne", text=t2)) +## if header: self.gamenumber='won' +## x += self._tabs[2] +## h = max(h, p(x, y, anchor="ne", text=t3)) +## if header: self.gamenumber='lost' +## x += self._tabs[3] +## h = max(h, p(x, y, anchor="ne", text=t4)) +## if header: self.gamenumber='time' +## x += self._tabs[4] +## h = max(h, p(x, y, anchor="ne", text=t5)) +## if header: self.gamenumber='moves' +## x += self._tabs[5] +## h = max(h, p(x, y, anchor="ne", text=t6)) +## if header: self.gamenumber='percent' +## x += self._tabs[6] +## h = max(h, p(x, y, anchor="ne", text=t7)) +## x += self._tabs[7] +## self.pstats_perc(x, y, t7) +## self.y += h +## self.gameid = None + + def _pstats_text(self, x, y, **kw): + kwdefault(kw, font=self.font, fill=self.fg) + id = apply(self.canvas.create_text, (x, y), kw) + self._addItem(id) + return self.h + ##bbox = self.canvas.bbox(id) + ##return bbox[3] - bbox[1] + + def pstats_perc(self, x, y, t): + if not (t and "0" <= t[0] <= "9"): + return + perc = int(round(float(str(t)))) + if perc < 1: + return + rx, ry, rw, rh = x, y+1, 2 + 8*10, self.h-5 + if 1: + w = int(round(rw*perc/100.0)) + if 1 and w < 1: + return + if w > 0: + w = max(3, w) + w = min(rw - 2, w) + id = self.canvas.create_rectangle(rx, ry, rx+w, ry+rh, width=1, + fill="#00ff00", outline="#000000") + if w < rw: + id = self.canvas.create_rectangle(rx+w, ry, rx+rw, ry+rh, width=1, + fill="#ff0000", outline="#000000") + return + ##fill = "#ffffff" + ##fill = self.canvas["bg"] + fill = None + id = self.canvas.create_rectangle(rx, ry, rx+rw, ry+rh, width=1, + fill=fill, outline="#808080") + if 1: + rx, rw = rx + 1, rw - 1 + ry, rh = ry + 1, rh - 1 + w = int(round(rw*perc/100.0)) + if w > 0: + id = self.canvas.create_rectangle(rx, ry, rx+w, ry+rh, width=0, + fill="#00ff00", outline="") + if w < rw: + id = self.canvas.create_rectangle(rx+w, ry, rx+rw, ry+rh, width=0, + fill="#ff0000", outline="") + return + p = 1.0 + ix = rx + 2 + for i in (1, 11, 21, 31, 41, 51, 61, 71, 81, 91): + if perc < i: + break + ##c = "#ff8040" + r, g, b = 255, 128*p, 64*p + c = "#%02x%02x%02x" % (int(r), int(g), int(b)) + id = self.canvas.create_rectangle(ix, ry+2, ix+6, ry+rh-2, width=0, + fill=c, outline=c) + ix = ix + 8 + p = max(0.0, p - 0.1) + + def plog(self, gamename, gamenumber, date, status, gameid=-1, won=-1): + if gameid > 0 and "0" <= gamenumber[0:1] <= "9": + self.gameid = gameid + self.gamenumber = gamenumber + self.p("%-25s %-20s %17s %s\n" % (gamename, gamenumber, date, status)) + self.gameid = None + self.gamenumber = None + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class AllGames_StatsDialogScrolledCanvas(MfxScrolledCanvas): + pass + + +class AllGames_StatsDialog(MfxDialog): + # for font "canvas_fixed" + #CHAR_W, CHAR_H = 7, 16 + #if os.name == "mac": CHAR_W = 6 + # + YVIEW = 0 + FONT_TYPE = "default" + + def __init__(self, parent, title, app, player, **kw): + lines = 25 + #if parent and parent.winfo_screenheight() < 600: + # lines = 20 + # + self.font = app.getFont(self.FONT_TYPE) + font = tkFont.Font(parent, self.font) + self.font_metrics = font.metrics() + self.CHAR_H = self.font_metrics['linespace'] + self.CHAR_W = font.measure('M') + self.app = app + # + self.player = player + self.title = title + self.sort_by = 'name' + # + kwdefault(kw, width=self.CHAR_W*64, height=lines*self.CHAR_H) + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.top.wm_minsize(200, 200) + self.button = kw.default + # + self.sc = AllGames_StatsDialogScrolledCanvas(top_frame, + width=kw.width, height=kw.height) + self.sc.pack(fill=Tkinter.BOTH, expand=1, padx=kw.padx, pady=kw.pady) + # + self.nodes = {} + self.canvas = self.sc.canvas + self.canvas.dialog = self + bind(self.canvas, "<1>", self.singleClick) + self.fillCanvas(player, title) + bbox = self.canvas.bbox("all") + ##print bbox + ##self.canvas.config(scrollregion=bbox) + self.canvas.config(scrollregion=(0,0,bbox[2],bbox[3])) + self.canvas.yview_moveto(self.YVIEW) + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), + (_("Save to file"), 202), + (_("Reset all..."), 301),), + default=0, + separatorwidth=2, resizable=1, + padx=10, pady=10, + #width=900, + ) + return MfxDialog.initKw(self, kw) + + def destroy(self): + self.app = None + self.canvas.dialog = None + self.nodes = {} + self.sc.destroy() + MfxDialog.destroy(self) + + def rearrange(self, sort_by): + if self.sort_by == sort_by: return + self.sort_by = sort_by + self.fillCanvas(self.player, self.title) + + def singleClick(self, event=None): + id = self.canvas.find_withtag(Tkinter.CURRENT) + if not id: + return + ##print id, self.nodes.get(id[0]) + gameid, gamenumber = self.nodes.get(id[0], (None, None)) + if gameid == 'header': + if self.sort_by == gamenumber: return + self.sort_by = gamenumber + self.fillCanvas(self.player, self.title) + return + ## FIXME / TODO + return + if gameid and gamenumber: + print gameid, gamenumber + elif gameid: + print gameid + + # + # + # + + def fillCanvas(self, player, header): + self.canvas.delete('all') + self.nodes = {} + a = PysolStatsFormatter(self.app) + #print 'CHAR_W:', self.CHAR_W + writer = CanvasWriter(self.canvas, self, + self.font, self.CHAR_W, self.CHAR_H) + if not a.writeStats(writer, player, header, sort_by=self.sort_by): + writer.p(_("No entries for player ") + player + "\n") + destruct(writer) + destruct(a) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class FullLog_StatsDialog(AllGames_StatsDialog): + YVIEW = 1 + FONT_TYPE = "fixed" + + def fillCanvas(self, player, header): + a = PysolStatsFormatter(self.app) + writer = CanvasWriter(self.canvas, self, self.font, self.CHAR_W, self.CHAR_H) + if not a.writeFullLog(writer, player, header): + writer.p(_("No log entries for %s\n") % player) + destruct(a) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), (_("Session log..."), 104), (_("Save to file"), 203)), default=0, + width=76*self.CHAR_W, + ) + return AllGames_StatsDialog.initKw(self, kw) + + +class SessionLog_StatsDialog(FullLog_StatsDialog): + def fillCanvas(self, player, header): + a = PysolStatsFormatter(self.app) + writer = CanvasWriter(self.canvas, self, self.font, self.CHAR_W, self.CHAR_H) + if not a.writeSessionLog(writer, player, header): + writer.p(_("No current session log entries for %s\n") % player) + destruct(a) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), (_("Full log..."), 103), (_("Save to file"), 204)), default=0, + ) + return FullLog_StatsDialog.initKw(self, kw) + +# /*********************************************************************** +# // +# ************************************************************************/ + +class Status_StatsDialog(MfxDialog): + def __init__(self, parent, game): + stats, gstats = game.stats, game.gstats + w1 = w2 = "" + n = 0 + for s in game.s.foundations: + n = n + len(s.cards) + w1 = (_("Highlight piles: ") + str(stats.highlight_piles) + "\n" + + _("Highlight cards: ") + str(stats.highlight_cards) + "\n" + + _("Highlight same rank: ") + str(stats.highlight_samerank) + "\n") + if game.s.talon: + if game.gameinfo.redeals != 0: + w2 = w2 + _("\nRedeals: ") + str(game.s.talon.round - 1) + w2 = w2 + _("\nCards in Talon: ") + str(len(game.s.talon.cards)) + if game.s.waste and game.s.waste not in game.s.foundations: + w2 = w2 + _("\nCards in Waste: ") + str(len(game.s.waste.cards)) + if game.s.foundations: + w2 = w2 + _("\nCards in Foundations: ") + str(n) + # + date = time.strftime("%Y-%m-%d %H:%M", time.localtime(game.gstats.start_time)) + MfxDialog.__init__(self, parent, title=_("Game status"), + text=game.getTitleName() + "\n" + + game.getGameNumber(format=1) + "\n" + + _("Playing time: ") + game.getTime() + "\n" + + _("Started at: ") + date + "\n\n"+ + _("Moves: ") + str(game.moves.index) + "\n" + + _("Undo moves: ") + str(stats.undo_moves) + "\n" + + _("Bookmark moves: ") + str(gstats.goto_bookmark_moves) + "\n" + + _("Demo moves: ") + str(stats.demo_moves) + "\n" + + _("Total player moves: ") + str(stats.player_moves) + "\n" + + _("Total moves in this game: ") + str(stats.total_moves) + "\n" + + _("Hints: ") + str(stats.hints) + "\n" + + "\n" + + w1 + w2, + strings=(_("OK"), + (_("Statistics..."), 101), + (TOP_TITLE+"...", 105), ), + image=game.app.gimages.logos[3], + image_side="left", image_padx=20, + padx=20, separatorwidth=2) + +# /*********************************************************************** +# // +# ************************************************************************/ + +class _TopDialog(MfxDialog): + def __init__(self, parent, title, top, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + + cnf = {'master': top_frame, + 'highlightthickness': 1, + 'highlightbackground': 'black', + } + frame = apply(Tkinter.Frame, (), cnf) + frame.pack(expand=Tkinter.YES, fill=Tkinter.BOTH, padx=10, pady=10) + frame.columnconfigure(0, weight=1) + cnf['master'] = frame + cnf['text'] = _('N') + l = apply(Tkinter.Label, (), cnf) + l.grid(row=0, column=0, sticky='ew') + cnf['text'] = _('Game number') + l = apply(Tkinter.Label, (), cnf) + l.grid(row=0, column=1, sticky='ew') + cnf['text'] = _('Started at') + l = apply(Tkinter.Label, (), cnf) + l.grid(row=0, column=2, sticky='ew') + cnf['text'] = _('Result') + l = apply(Tkinter.Label, (), cnf) + l.grid(row=0, column=3, sticky='ew') + + row = 1 + for i in top: + # N + cnf['text'] = str(row) + l = apply(Tkinter.Label, (), cnf) + l.grid(row=row, column=0, sticky='ew') + # Game number + cnf['text'] = '#'+str(i.game_number) + l = apply(Tkinter.Label, (), cnf) + l.grid(row=row, column=1, sticky='ew') + # Start time + t = time.strftime('%Y-%m-%d %H:%M', time.localtime(i.game_start_time)) + cnf['text'] = t + l = apply(Tkinter.Label, (), cnf) + l.grid(row=row, column=2, sticky='ew') + # Result + if isinstance(i.value, float): + # time + s = format_time(i.value) + else: + # moves + s = str(i.value) + cnf['text'] = s + l = apply(Tkinter.Label, (), cnf) + l.grid(row=row, column=3, sticky='ew') + row += 1 + + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + + def initKw(self, kw): + kw = KwStruct(kw, strings=(_('OK'),), default=0, separatorwidth = 2) + return MfxDialog.initKw(self, kw) + + +class Top_StatsDialog(MfxDialog): + def __init__(self, parent, title, app, player, gameid, **kw): + self.app = app + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + + frame = Tkinter.Frame(top_frame) + frame.pack(expand=Tkinter.YES, fill=Tkinter.BOTH, padx=5, pady=10) + frame.columnconfigure(0, weight=1) + + if app.stats.games_stats.has_key(player) \ + and app.stats.games_stats[player].has_key(gameid) \ + and app.stats.games_stats[player][gameid].time_result.top: + + Tkinter.Label(frame, text=_('Minimum')).grid(row=0, column=1) + Tkinter.Label(frame, text=_('Maximum')).grid(row=0, column=2) + Tkinter.Label(frame, text=_('Average')).grid(row=0, column=3) + ##Tkinter.Label(frame, text=_('Total')).grid(row=0, column=4) + + s = app.stats.games_stats[player][gameid] + row = 1 + ll = [ + (_('Playing time:'), + format_time(s.time_result.min), + format_time(s.time_result.max), + format_time(s.time_result.average), + format_time(s.time_result.total), + s.time_result.top, + ), + (_('Moves:'), + s.moves_result.min, + s.moves_result.max, + round(s.moves_result.average, 2), + s.moves_result.total, + s.moves_result.top, + ), + (_('Total moves:'), + s.total_moves_result.min, + s.total_moves_result.max, + round(s.total_moves_result.average, 2), + s.total_moves_result.total, + s.total_moves_result.top, + ), + ] +## if s.score_result.min: +## ll.append(('Score:', +## s.score_result.min, +## s.score_result.max, +## round(s.score_result.average, 2), +## s.score_result.top, +## )) +## if s.score_casino_result.min: +## ll.append(('Casino Score:', +## s.score_casino_result.min, +## s.score_casino_result.max, +## round(s.score_casino_result.average, 2), )) + for l, min, max, avr, tot, top in ll: + Tkinter.Label(frame, text=l).grid(row=row, column=0) + Tkinter.Label(frame, text=str(min)).grid(row=row, column=1) + Tkinter.Label(frame, text=str(max)).grid(row=row, column=2) + Tkinter.Label(frame, text=str(avr)).grid(row=row, column=3) + ##Tkinter.Label(frame, text=str(tot)).grid(row=row, column=4) + b = Tkinter.Button(frame, text=TOP_TITLE+' ...', width=10, + command=lambda top=top: self.showTop(top)) + b.grid(row=row, column=5) + row += 1 + else: + Tkinter.Label(frame, text=_('No TOP for this game')).pack() + + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + def showTop(self, top): + #print top + d = _TopDialog(self.top, TOP_TITLE, top) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_('OK'),), + default=0, + image=self.app.gimages.logos[4], + separatorwidth = 2) + return MfxDialog.initKw(self, kw) diff --git a/pysollib/tk/tktree.py b/pysollib/tk/tktree.py new file mode 100644 index 0000000000..dd047d302f --- /dev/null +++ b/pysollib/tk/tktree.py @@ -0,0 +1,422 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +# imports +import os, string, types +import Tkinter + +# Toolkit imports +from tkutil import bind, unbind_destroy +from tkwidget import MfxScrolledCanvas + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxTreeBaseNode: + def __init__(self, tree, parent_node, text, key): + self.tree = tree + self.parent_node = parent_node + self.text = text + self.key = key + # state + self.selected = 0 + self.subnodes = None + # canvas item ids + self.symbol_id = None + self.text_id = None + self.textrect_id = None + + def registerKey(self): + if self.key is not None: + l = self.tree.keys.get(self.key, []) + l.append(self) + self.tree.keys[self.key] = l + + def whoami(self): + if self.parent_node is None: + return (self.text, ) + else: + return self.parent_node.whoami() + (self.text, ) + + def draw(self, x, y, lastx=None, lasty=None): + canvas, style = self.tree.canvas, self.tree.style + topleftx = x + style.distx + toplefty = y - style.height / 2 #+++ + # draw the horizontal line + if lastx is not None: + canvas.create_line(x, y, topleftx, y, stipple=style.linestyle, fill=style.linecolor) + # draw myself - ugly, ugly... + self.selected = 0 + self.symbol_id = -1 + self.drawSymbol(topleftx, toplefty) + linestart = style.distx + style.width + 5 + self.text_id = -1 + self.drawText(x + linestart, y) + return x, y, x, y + style.disty + + # + # + # + + def drawText(self, x, y): + canvas, style = self.tree.canvas, self.tree.style + if self.selected: + fg, bg = style.text_selected_fg, style.text_selected_bg + else: + fg, bg = style.text_normal_fg, style.text_normal_bg + # + if self.tree.nodes.get(self.text_id) is self: + canvas.itemconfig(self.text_id, fill=fg) + else: + # note: I don't use Label + canvas.create_window here + # because it doesn't propagate events to the canvas + # and has some other re-display annoyances + ##print 'style.font:', style.font + self.text_id = canvas.create_text(x+1, y, text=self.text, + anchor="w", justify="left", + font=style.font, + fill=fg) + self.tree.nodes[self.text_id] = self + # + if self.tree.nodes.get(self.textrect_id) is self: + try: + # _tkinter.TclError: unknown option "-fill" ??? + canvas.itemconfig(self.textrect_id, fill=bg) + except Tkinter.TclError: + pass + elif self.selected: + b = canvas.bbox(self.text_id) + self.textrect_id = canvas.create_rectangle(b[0]-1, b[1]-1, b[2]+1, b[3]+1, + fill=bg, outline="") + canvas.tag_lower(self.textrect_id, self.text_id) + self.tree.nodes[self.textrect_id] = self + + def updateText(self): + if self.tree.nodes.get(self.text_id) is self: + self.drawText(-1, -1) + + # + # + # + + def drawSymbol(self, x, y, **kw): + canvas, style = self.tree.canvas, self.tree.style + color = kw.get("color") + if color is None: + if self.selected: + color = "darkgreen" + else: + color = "green" + # note: rectangle outline is one pixel + if self.tree.nodes.get(self.symbol_id) is self: + canvas.itemconfig(self.symbol_id, fill=color) + else: + self.symbol_id = canvas.create_rectangle( + x+1, y+1, x + style.width, y + style.height, fill=color) + self.tree.nodes[self.symbol_id] = self + + def updateSymbol(self): + if self.tree.nodes.get(self.symbol_id) is self: + self.drawSymbol(-1, -1) + + +# /*********************************************************************** +# // Terminal and non-terminal nodes +# ************************************************************************/ + +class MfxTreeLeaf(MfxTreeBaseNode): + def drawText(self, x, y): + if self.text_id < 0: + self.registerKey() + MfxTreeBaseNode.drawText(self, x, y) + + +class MfxTreeNode(MfxTreeBaseNode): + def __init__(self, tree, parent_node, text, key, expanded=0): + MfxTreeBaseNode.__init__(self, tree, parent_node, text, key) + self.expanded = expanded + + def drawChildren(self, x, y, lastx, lasty): + # get subnodes + self.subnodes = self.tree.getContents(self) + # draw subnodes + lx, ly = lastx, lasty + nx, ny = x, y + for node in self.subnodes: + # update tree + node.tree = self.tree + # draw node + lx, ly, nx, ny = node.draw(nx, ny, lx, ly) + # draw the vertical line + if self.subnodes: + style = self.tree.style + dy = (style.disty-style.width)/2 + y = y-style.disty/2-dy + self.tree.canvas.create_line(x, y, nx, ly, + stipple=style.linestyle, + fill=style.linecolor) + return ny + + + def draw(self, x, y, ilastx=None, ilasty=None): + # draw myself + lx, ly, nx, ny = MfxTreeBaseNode.draw(self, x, y, ilastx, ilasty) + if self.expanded: + style = self.tree.style + childx = nx + style.distx + style.width / 2 + childy = ny + clastx = nx + style.distx + style.width / 2 + clasty = ly + style.height /2 + ny = self.drawChildren(childx, childy, clastx, clasty) + return lx, ly, x, ny + + # + # + # + + def drawSymbol(self, x, y, **kw): + color = kw.get("color") + if color is None: + if self.expanded: + color = "red" + else: + color = "pink" + MfxTreeBaseNode.drawSymbol(self, x, y, color=color) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxTreeInCanvas(MfxScrolledCanvas): + class Style: + def __init__(self): + self.distx = 16 + self.disty = 18 + self.width = 16 # width of symbol + self.height = 16 # height of symbol + self.originx = 0 + self.originy = 0 + self.text_normal_fg = "black" + self.text_normal_bg = "white" + self.text_selected_fg = "white" + self.text_selected_bg = "#00008b" # "darkblue" + self.font = None + self.linestyle = "gray50" + self.linecolor = "black" + if os.name == "nt": + self.linestyle = "" # Tk bug ? + self.linecolor = "gray50" + + def __init__(self, parent, rootnodes, **kw): + bg = kw["bg"] = kw.get("bg") or parent.cget("bg") + apply(MfxScrolledCanvas.__init__, (self, parent,), kw) + # + self.rootnodes = rootnodes + self.updateNodesWithTree(self.rootnodes, self) + self.selection_key = None + self.nodes = {} + self.keys = {} + # + self.style = self.Style() + ##self.style.text_normal_fg = self.canvas.cget("insertbackground") + self.style.text_normal_fg = self.canvas.option_get('foreground', '') or self.canvas.cget("insertbackground") + self.style.text_normal_bg = bg + # + bind(self.canvas, "", self.singleClick) + bind(self.canvas, "", self.doubleClick) + ##bind(self.canvas, "", xxx) + self.pack(fill=Tkinter.BOTH, expand=1) + + def destroy(self): + for node in self.keys.get(self.selection_key, []): + node.selected = 0 + MfxScrolledCanvas.destroy(self) + + def findNode(self, event=None): + id = self.canvas.find_withtag(Tkinter.CURRENT) + if id: + return self.nodes.get(id[0]) + return None + + # + # draw nodes + # + + def draw(self): + nx, ny = self.style.originx, self.style.originy + # Account for initial offsets, see topleft[xy] in BaseNode.draw(). + # We do this so that our bounding box always starts at (0,0) + # and the yscrollincrement works nicely. + nx = nx - self.style.distx + ny = ny + self.style.height / 2 + for node in self.rootnodes: + # update tree + node.tree = self + # draw + try: + lx, ly, nx, ny = node.draw(nx, ny, None, None) + except Tkinter.TclError: + # FIXME: Tk bug ??? + raise + # set scroll region + bbox = self.canvas.bbox("all") + ##self.canvas.config(scrollregion=bbox) + self.canvas.config(scrollregion=(0,0,bbox[2],bbox[3])) + self.canvas.config(yscrollincrement=self.style.disty) + + def clear(self): + self.nodes = {} + self.keys = {} + self.canvas.delete("all") + + def redraw(self): + oldcur = self.canvas["cursor"] + self.canvas["cursor"] = "watch" + self.canvas.update_idletasks() + self.clear() + self.draw() + self.updateSelection(self.selection_key) + self.canvas["cursor"] = oldcur + + # + # + # + + def getContents(self, node): + # Overload this, supposed to return a list of subnodes of node. + pass + + def singleClick(self, event=None): + # Overload this if you want to know when a node is clicked on. + pass + + def doubleClick(self, event=None): + # Overload this if you want to know when a node is d-clicked on. + self.singleClick(event) + + # + # + # + + def updateSelection(self, key): + l1 = self.keys.get(self.selection_key, []) + l2 = self.keys.get(key, []) + for node in l1: + if node.selected and not node in l2: + node.selected = 0 + node.updateSymbol() + node.updateText() + for node in l2: + if not node.selected: + node.selected = 1 + node.updateSymbol() + node.updateText() + self.selection_key = key + + def updateNodesWithTree(self, nodes, tree): + for node in nodes: + node.tree = tree + if node.subnodes: + self.updateNodesWithTree(node.subnodes, tree) + + +# /*********************************************************************** +# // +# ************************************************************************/ + + +class DirectoryBrowser(MfxTreeInCanvas): + def __init__(self, parent, dirs): + nodes = [] + if type(dirs) is types.StringType: + dirs = (dirs,) + for dir in dirs: + self.addNode(nodes, None, dir, dir) + # note: best results if height is a multiple of style.disty + MfxTreeInCanvas.__init__(self, parent, nodes, height=25*18) + self.draw() + + def addNode(self, list, node, filename, text): + try: + if os.path.isdir(filename): + list.append(MfxTreeNode(self, node, text, key=filename)) + else: + list.append(MfxTreeLeaf(self, node, text, key=filename)) + except EnvironmentError: + pass + + def getContents(self, node): + # use cached values + if node.subnodes is not None: + return node.subnodes + # + dir = node.key + print "Getting %s" % dir + try: + filenames = os.listdir(dir) + filenames.sort() + except EnvironmentError: + return () + contents = [] + for filename in filenames: + self.addNode(contents, node, os.path.join(dir, filename), filename) + ##print "gotten" + return contents + + def singleClick(self, event=None): + node = self.findNode(event) + if not node: + return + print "Clicked node %s %s" % (node.text, node.key) + if isinstance(node, MfxTreeLeaf): + self.updateSelection(key=node.key) + elif isinstance(node, MfxTreeNode): + node.expanded = not node.expanded + self.redraw() + return "break" + + +if __name__ == "__main__": + tk = Tkinter.Tk() + if os.name == "nt": + app = DirectoryBrowser(tk, ("c:\\", "c:\\windows")) + else: + app = DirectoryBrowser(tk, ("/", "/home")) + tk.mainloop() + + diff --git a/pysollib/tk/tkutil.py b/pysollib/tk/tkutil.py new file mode 100644 index 0000000000..d728d33b17 --- /dev/null +++ b/pysollib/tk/tkutil.py @@ -0,0 +1,396 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['wm_withdraw', + 'wm_deiconify', + 'wm_map', + 'wm_set_icon', + 'wm_get_geometry', + #'setTransient', + #'makeToplevel', + 'makeHelpToplevel', + 'bind', + 'unbind_destroy', + 'after', + 'after_idle', + 'after_cancel', + #'makeImage', + 'copyImage', + 'loadImage', + #'fillImage', + 'createImage', + ] + +# imports +import sys, os, re +import Tkinter +try: + # PIL + import Image + import ImageTk +except ImportError: + Image = None + +# Toolkit imports +from tkconst import tkversion +from pysollib.settings import PACKAGE + + +# /*********************************************************************** +# // window manager util +# ************************************************************************/ + +def wm_withdraw(window): + window.wm_withdraw() + +def wm_deiconify(window): + need_fix = os.name == "nt" and tkversion < (8, 3, 0, 0) + if need_fix: + # FIXME: This is needed so the window pops up on top on Windows. + try: + window.wm_iconify() + window.update_idletasks() + except Tkinter.TclError: + # wm_iconify() may fail if the window is transient + pass + window.wm_deiconify() + +def wm_map(window, maximized=0): + if window.wm_state() != "iconic": + if maximized and os.name == "nt": + window.wm_state("zoomed") + else: + wm_deiconify(window) + +def wm_set_icon(window, filename): + if not filename: + return + if os.name == "posix": + window.wm_iconbitmap("@" + filename) + window.wm_iconmask("@" + filename) + +__wm_get_geometry_re = re.compile(r"^(\d+)x(\d+)\+([\-]?\d+)\+([\-]?\d+)$") + +def wm_get_geometry(window): + g = window.wm_geometry() + m = __wm_get_geometry_re.search(g) + if not m: + raise Tkinter.TclError, "invalid geometry " + str(g) + l = map(int, m.groups()) + if window.wm_state() == "zoomed": + # workaround as Tk returns the "unzoomed" origin + l[2] = l[3] = 0 + return l + + +# /*********************************************************************** +# // window util +# ************************************************************************/ + +def setTransient(window, parent, relx=None, rely=None, expose=1): + # Make an existing toplevel window transient for a parent. + # + # The window must exist but should not yet have been placed; in + # other words, this should be called after creating all the + # subwidget but before letting the user interact. + + # remain invisible while we figure out the geometry + window.wm_withdraw() + window.wm_group(parent) + need_fix = os.name == "nt" and tkversion < (8, 3, 0, 0) + if need_fix: + # FIXME: This is needed to avoid ugly frames on Windows. + window.wm_geometry("+%d+%d" % (-10000, -10000)) + if expose and parent is not None: + # FIXME: This is needed so the window pops up on top on Windows. + window.wm_iconify() + if parent and parent.wm_state() != "withdrawn": + window.wm_transient(parent) + # actualize geometry information + window.update_idletasks() + # show + x, y = __getWidgetXY(window, parent, relx=relx, rely=rely) + if need_fix: + if expose: + wm_deiconify(window) + window.wm_geometry("+%d+%d" % (x, y)) + else: + window.wm_geometry("+%d+%d" % (x, y)) + if expose: + window.wm_deiconify() + +def makeToplevel(parent, title=None): + # Create a Toplevel window. + # + # This is a shortcut for a Toplevel() instantiation plus calls to + # set the title and icon name of the window. + window = Tkinter.Toplevel(parent) #, class_=PACKAGE) + ##window.wm_group(parent) + ##window.wm_command("") + if os.name == "posix": + window.wm_command("/bin/true") + ##window.wm_protocol("WM_SAVE_YOURSELF", None) + if title: + window.wm_title(title) + window.wm_iconname(title) + return window + +def makeHelpToplevel(app, title=None): + # Create an independent Toplevel window. + parent = app.top + window = Tkinter.Tk(className=PACKAGE) + font = parent.option_get('font', '') + if font: + window.option_add('*font', font) + fg, bg = app.top_palette + if bg: + window.tk_setPalette(bg) + if fg: + window.option_add('*foreground', fg) + window.option_add('*selectBackground', '#00008b', 50) + window.option_add('*selectForeground', 'white', 50) + if title: + window.wm_title(title) + window.wm_iconname(title) + return window + +#makeHelpToplevel = makeToplevel + + +def __getWidgetXY(widget, parent, relx=None, rely=None, + w_width=None, w_height=None): + if w_width is None: + w_width = widget.winfo_reqwidth() + if w_height is None: + w_height = widget.winfo_reqheight() + s_width = widget.winfo_screenwidth() + s_height = widget.winfo_screenheight() + m_x = m_y = 0 + m_width, m_height = s_width, s_height + if parent and parent.winfo_ismapped(): + ##print parent.wm_geometry() + ##print parent.winfo_geometry(), parent.winfo_x(), parent.winfo_y(), parent.winfo_rootx(), parent.winfo_rooty(), parent.winfo_vrootx(), parent.winfo_vrooty() + m_x = m_y = None + if os.name == "nt": + try: + m_width, m_height, m_x, m_y = wm_get_geometry(parent) + except: + pass + if m_x is None: + m_x = parent.winfo_x() + m_y = parent.winfo_y() + m_width = parent.winfo_width() + m_height = parent.winfo_height() + if relx is None: relx = 0.5 + if rely is None: rely = 0.3 + else: + if relx is None: relx = 0.5 + if rely is None: rely = 0.5 + m_x = max(m_x, 0) + m_y = max(m_y, 0) + else: + if relx is None: relx = 0.5 + if rely is None: rely = 0.3 + x = m_x + int((m_width - w_width) * relx) + y = m_y + int((m_height - w_height) * rely) + ##print x, y, w_width, w_height, m_x, m_y, m_width, m_height + # make sure the widget is fully on screen + if x < 0: x = 0 + elif x + w_width + 32 > s_width: x = max(0, (s_width - w_width) / 2) + if y < 0: y = 0 + elif y + w_height + 32 > s_height: y = max(0, (s_height - w_height) / 2) + return x, y + + +# /*********************************************************************** +# // bind wrapper - Tkinter doesn't properly delete all bindings +# ************************************************************************/ + +__mfx_bindings = {} +__mfx_wm_protocols = ("WM_DELETE_WINDOW", "WM_TAKE_FOCUS", "WM_SAVE_YOURSELF") + +def bind(widget, sequence, func, add=None): + assert callable(func) + if sequence in __mfx_wm_protocols: + funcid = widget._register(func) + widget.tk.call("wm", "protocol", widget._w, sequence, funcid) + elif add is None: + funcid = widget.bind(sequence, func) + else: + ##add = add and "+" or "" + funcid = widget.bind(sequence, func, add) + k = id(widget) + if __mfx_bindings.has_key(k): + __mfx_bindings[k].append((sequence, funcid)) + else: + __mfx_bindings[k] = [(sequence, funcid)] + +def unbind_destroy(widget): + if widget is None: + return + k = id(widget) + if __mfx_bindings.has_key(k): + for sequence, funcid in __mfx_bindings[k]: + ##print widget, sequence, funcid + try: + if sequence in __mfx_wm_protocols: + widget.tk.call("wm", "protocol", widget._w, sequence, "") + ##widget.deletecommand(funcid) + else: + widget.unbind(sequence, funcid) + except Tkinter.TclError: + pass + del __mfx_bindings[k] + ##for k in __mfx_bindings.keys(): print __mfx_bindings[k] + ##print len(__mfx_bindings.keys()) + + +# /*********************************************************************** +# // timer wrapper - Tkinter doesn't properly delete all commands +# ************************************************************************/ + +def after(widget, ms, func, *args): + timer = apply(widget.after, (ms, func) + args) + command = widget._tclCommands[-1] + return (timer, command, widget) + +def after_idle(widget, func, *args): + return apply(after, (widget, "idle", func) + args) + +def after_cancel(t): + if t is not None: + t[2].after_cancel(t[0]) + try: + t[2].deletecommand(t[1]) + except Tkinter.TclError: + pass + + +# /*********************************************************************** +# // image handling +# ************************************************************************/ + +if Image: + class PIL_Image(ImageTk.PhotoImage): + def __init__(self, file): + im = Image.open(file).convert('RGBA') + ImageTk.PhotoImage.__init__(self, im) + self._pil_image = im + def subsample(self, r): + im = self._pil_image + w, h = im.size + w, h = int(float(w)/r), int(float(h)/r) + im = ImageTk.PhotoImage(im.resize((w, h))) + return im + + +def makeImage(file=None, data=None, dither=None, alpha=None): + kw = {} + if data is None: + assert file is not None + kw["file"] = file + else: + #assert data is not None + kw["data"] = data + if Image: + # use PIL + if file: + im = PIL_Image(file) + return im + # fromstring(mode, size, data, decoder_name='raw', *args) + else: + return Tkinter.PhotoImage(data=data) + if os.name == "nt": + # not available in Tk after about 8.0 + #if dither is not None: + # kw["dither"] = dither + if alpha is not None: + kw["alpha"] = alpha + return apply(Tkinter.PhotoImage, (), kw) + +loadImage = makeImage + +def copyImage(image, x, y, width, height): + if Image: + if isinstance(image, PIL_Image): + return ImageTk.PhotoImage(image._pil_image.crop((x, y, x+width, y+height))) + dest = Tkinter.PhotoImage(width=width, height=height) + assert dest.width() == width + assert dest.height() == height + dest.blank() + image.tk.call(dest, "copy", image.name, "-from", x, y, x+width, y+height) + assert dest.width() == width + assert dest.height() == height + return dest + +def fillImage(image, fill, outline=None): + if not fill and not outline: + return + width = image.width() + height = image.height() + ow = 1 # outline width + if width <= 2*ow or height <= 2*ow: + fill = fill or outline + outline = None + if not outline: + f = (fill,) * width + f = (f,) * height + assert len(f) == height + image.put(f) + elif not fill: + l = ((outline,) * width,) + for y in range(0, ow): + image.put(l, (0, y)) + for y in range(height-ow, height): + image.put(l, (0, y)) + p = ((outline,) * ow,) + for y in range(ow, height-ow): + image.put(p, (0, y)) + image.put(p, (width-ow, y)) + else: + l1 = (outline,) * width + l2 = (outline,) * ow + (fill,) * (width-2*ow) + (outline,) * ow + f = (l1,) * ow + (l2,) * (height-2*ow) + (l1,) * ow + assert len(f) == height + image.put(f) + +def createImage(width, height, fill, outline=None): + image = Tkinter.PhotoImage(width=width, height=height) + assert image.width() == width + assert image.height() == height + image.blank() + fillImage(image, fill, outline) + return image + diff --git a/pysollib/tk/tkwidget.py b/pysollib/tk/tkwidget.py new file mode 100644 index 0000000000..ae5030ffe9 --- /dev/null +++ b/pysollib/tk/tkwidget.py @@ -0,0 +1,646 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['MfxDialog', + 'MfxExceptionDialog', + 'MfxSimpleEntry', + 'MfxTooltip', + 'MfxScrolledCanvas', + ] + +# imports +import os, sys, types, Tkinter +import traceback + +# PySol imports +from pysollib.mfxutil import destruct, kwdefault, KwStruct +from pysollib.mfxutil import win32api + +# Toolkit imports +from tkconst import tkversion +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkutil import after, after_idle, after_cancel +from tkutil import bind, unbind_destroy, makeImage +from tkutil import makeToplevel, setTransient +from tkcanvas import MfxCanvas + +# /*********************************************************************** +# // abstract base class for the dialogs in this module +# ************************************************************************/ + +class _ToplevelDialog: + img = None + def __init__(self, parent, title="", resizable=0, default=-1): + self.parent = parent + self.status = 0 + self.button = default + self.timer = None + self.top = makeToplevel(parent, title=title) + self.top.wm_resizable(resizable, resizable) + ##w, h = self.top.winfo_screenwidth(), self.top.winfo_screenheight() + ##self.top.wm_maxsize(w-4, h-32) + bind(self.top, "WM_DELETE_WINDOW", self.wmDeleteWindow) + + def mainloop(self, focus=None, timeout=0): + bind(self.top, "", self.mCancel) + if focus is not None: + focus.focus() + setTransient(self.top, self.parent) + try: + self.top.grab_set() + except Tkinter.TclError: + if traceback: traceback.print_exc() + pass + if timeout > 0: + self.timer = after(self.top, timeout, self.mTimeout) + try: self.top.mainloop() + except SystemExit: + pass + self.destroy() + + def destroy(self): + after_cancel(self.timer) + unbind_destroy(self.top) + try: + self.top.wm_withdraw() + except: + if traceback: traceback.print_exc() + pass + try: + self.top.destroy() + except: + if traceback: traceback.print_exc() + pass + #destruct(self.top) + if 1 and self.parent: # ??? + try: + ##self.parent.update_idletasks() + # FIXME: why do we need this under Windows ? + if hasattr(self.parent, "busyUpdate"): + self.parent.busyUpdate() + else: + self.parent.update() + except: + if traceback: traceback.print_exc() + pass + self.top = None + self.parent = None + + def wmDeleteWindow(self, *event): + self.status = 1 + raise SystemExit + ##return EVENT_HANDLED + + def mCancel(self, *event): + self.status = 1 + raise SystemExit + + def mTimeout(self, *event): + self.status = 2 + raise SystemExit + + +# /*********************************************************************** +# // replacement for the tk_dialog script +# ************************************************************************/ + +class MfxDialog(_ToplevelDialog): + def __init__(self, parent, title, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.button = kw.default + msg = Tkinter.Label(top_frame, text=kw.text, justify=kw.justify, + width=kw.width) + msg.pack(fill=Tkinter.BOTH, expand=1, padx=kw.padx, pady=kw.pady) + # + focus = self.createButtons(bottom_frame, kw) + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + timeout=0, resizable=0, + text="", justify="center", + strings=(_("OK"),), default=0, + width=0, + padx=20, pady=20, + bitmap=None, bitmap_side="left", + bitmap_padx=10, bitmap_pady=20, + image=None, image_side="left", + image_padx=10, image_pady=20, + ) + # default to separator if more than one button + sw = 2 * (len(kw.strings) > 1) + kwdefault(kw.__dict__, separatorwidth=sw) + return kw + + def createFrames(self, kw): + bottom_frame = Tkinter.Frame(self.top) + bottom_frame.pack(side=Tkinter.BOTTOM, fill=Tkinter.BOTH, ipady=3) + if kw.separatorwidth > 0: + separator = Tkinter.Frame(self.top, relief="sunken", + height=kw.separatorwidth, width=kw.separatorwidth, + borderwidth=kw.separatorwidth / 2) + separator.pack(side=Tkinter.BOTTOM, fill=Tkinter.X) + top_frame = Tkinter.Frame(self.top) + top_frame.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1) + return top_frame, bottom_frame + + def createBitmaps(self, frame, kw): + bm = ["error", "info", "question", "warning"] + if kw.bitmap in bm: + img = None + if self.img: + img = self.img[bm.index(kw.bitmap)] + b = Tkinter.Label(frame, image=img) + b.pack(side=kw.bitmap_side, padx=kw.bitmap_padx, pady=kw.bitmap_pady) + elif kw.bitmap: + b = Tkinter.Label(frame, bitmap=kw.bitmap) + b.pack(side=kw.bitmap_side, padx=kw.bitmap_padx, pady=kw.bitmap_pady) + elif kw.image: + b = Tkinter.Label(frame, image=kw.image) + b.pack(side=kw.image_side, padx=kw.image_padx, pady=kw.image_pady) + + def createButtons(self, frame, kw): + button = column = -1 + padx, pady = kw.get("buttonpadx", 10), kw.get("buttonpady", 10) + focus = None + max_len = 0 + for s in kw.strings: + if type(s) is types.TupleType: + s = s[0] + if s: + ##s = re.sub(r"[\s\.\,]", "", s) + s = s.replace('...', '.') + max_len = max(max_len, len(s)) + ##print s, len(s) + if max_len > 12 and os.name == 'posix': button_width = max_len + elif max_len > 9 : button_width = max_len+1 + elif max_len > 6 : button_width = max_len+2 + else : button_width = 8 + #print 'button_width =', button_width + for s in kw.strings: + xbutton = button = button + 1 + if type(s) is types.TupleType: + assert len(s) == 2 + button = int(s[1]) + s = s[0] + if s is None: + continue + if button < 0: + b = Tkinter.Button(frame, text=s, state="disabled") + button = xbutton + else: + b = Tkinter.Button(frame, text=s, default="normal", + command=(lambda self=self, button=button: self.mDone(button))) + if button == kw.default: + focus = b + focus.config(default="active") + l = len(s) +## if 1 and l < max_len: +## l = l + (max_len - l) / 2 +## b.config(width=l) + b.config(width=button_width) + column = column + 1 + b.grid_configure(column=column, row=0, sticky="ew", padx=padx, pady=pady) + b.grid_columnconfigure(column) + if focus is not None: + l = (lambda event=None, self=self, button=kw.default: self.mDone(button)) + bind(self.top, "", l) + bind(self.top, "", l) + return focus + + def mDone(self, button): + self.button = button + raise SystemExit + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxExceptionDialog(MfxDialog): + def __init__(self, parent, ex, title="Error", **kw): + kw = KwStruct(kw, bitmap="error") + text = kw.get("text", "") + if not text.endswith("\n"): + text = text + "\n" + text = text + "\n" + if isinstance(ex, EnvironmentError) and ex.filename is not None: + t = "[Errno %s] %s:\n%s" % (ex.errno, ex.strerror, repr(ex.filename)) + else: + t = str(ex) + kw.text = text + unicode(t, errors='replace') + apply(MfxDialog.__init__, (self, parent, title), kw.getKw()) + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class MfxSimpleEntry(MfxDialog): + def __init__(self, parent, title, label, value, **kw): + kw = self.initKw(kw) + _ToplevelDialog.__init__(self, parent, title, kw.resizable, kw.default) + top_frame, bottom_frame = self.createFrames(kw) + self.createBitmaps(top_frame, kw) + # + self.value = value + if label: + label = Tkinter.Label(top_frame, text=label, takefocus=0) + label.pack(pady=5) + w = kw.get("e_width", 0) # width in characters + self.var = Tkinter.Entry(top_frame, exportselection=1, width=w) + self.var.insert(0, value) + self.var.pack(side=Tkinter.TOP, padx=kw.padx, pady=kw.pady) + # + focus = self.createButtons(bottom_frame, kw) + focus = self.var + self.mainloop(focus, kw.timeout) + + def initKw(self, kw): + kw = KwStruct(kw, + strings=(_("OK"), _("Cancel")), default=0, + separatorwidth = 0, + ) + return MfxDialog.initKw(self, kw) + + def mDone(self, button): + self.button = button + self.value = self.var.get() + raise SystemExit + + +# /*********************************************************************** +# // a simple tooltip +# ************************************************************************/ + +class MfxTooltip: + def __init__(self, widget): + # private vars + self.widget = widget + self.text = None + self.timer = None + self.cancel_timer = None + self.tooltip = None + self.label = None + self.bindings = [] + self.bindings.append(self.widget.bind("", self._enter)) + self.bindings.append(self.widget.bind("", self._leave)) + self.bindings.append(self.widget.bind("", self._leave)) + # user overrideable settings + self.time = 600 # milliseconds + self.cancel_time = 5000 + self.relief = Tkinter.SOLID + self.justify = Tkinter.LEFT + self.fg = "#000000" + self.bg = "#ffffe0" + self.xoffset = 0 + self.yoffset = 4 + + def setText(self, text): + self.text = text + + def _unbind(self): + if self.bindings and self.widget: + self.widget.unbind("", self.bindings[0]) + self.widget.unbind("", self.bindings[1]) + self.widget.unbind("", self.bindings[2]) + self.bindings = [] + + def destroy(self): + self._unbind() + self._leave() + + def _enter(self, *event): + after_cancel(self.timer) + after_cancel(self.cancel_timer) + self.cancel_timer = None + self.timer = after(self.widget, self.time, self._showTip) + + def _leave(self, *event): + after_cancel(self.timer) + after_cancel(self.cancel_timer) + self.timer = self.cancel_timer = None + if self.tooltip: + self.label.destroy() + destruct(self.label) + self.label = None + self.tooltip.destroy() + destruct(self.tooltip) + self.tooltip = None + + def _showTip(self): + self.timer = None + if self.tooltip or not self.text: + return + if isinstance(self.widget, Tkinter.Button): + if self.widget["state"] == Tkinter.DISABLED: + return + ##x = self.widget.winfo_rootx() + x = self.widget.winfo_pointerx() + y = self.widget.winfo_rooty() + self.widget.winfo_height() + x += self.xoffset + y += self.yoffset + self.tooltip = Tkinter.Toplevel() + self.tooltip.wm_iconify() + self.tooltip.wm_overrideredirect(1) + self.tooltip.wm_protocol("WM_DELETE_WINDOW", self.destroy) + self.label = Tkinter.Label(self.tooltip, text=self.text, + relief=self.relief, justify=self.justify, + fg=self.fg, bg=self.bg, bd=1, takefocus=0) + self.label.pack(ipadx=1, ipady=1) + self.tooltip.wm_geometry("%+d%+d" % (x, y)) + self.tooltip.wm_deiconify() + self.cancel_timer = after(self.widget, self.cancel_time, self._leave) + ##self.tooltip.tkraise() + + +# /*********************************************************************** +# // A canvas widget with scrollbars and some useful bindings. +# ************************************************************************/ + +class MfxScrolledCanvas: + def __init__(self, parent, hbar=2, vbar=2, **kw): + bg = kw.get("bg", parent.cget("bg")) + kwdefault(kw, bg=bg, highlightthickness=0) + self.parent = parent + self.createFrame(kw) + self.canvas = None + self.hbar = None + self.hbar_mode = hbar + self.vbar = None + self.vbar_mode = vbar + self.hbar_show = 0 + self.vbar_show = 0 + self.resize_pending = 0 + self.timer = None + self.createCanvas(kw) + self.frame.grid_rowconfigure(0, weight=1) + self.frame.grid_columnconfigure(0, weight=1) + if hbar: + if hbar == 3: + w = 21 + if win32api: + w = win32api.GetSystemMetrics(3) # SM_CYHSCROLL + self.frame.grid_rowconfigure(1, minsize=w) + self.createHbar(bg) + if not vbar: + bind(self.hbar, "", self._mapBar) + self.bindHbar() + if vbar: + if vbar == 3: + w = 21 + if win32api: + w = win32api.GetSystemMetrics(2) # SM_CXVSCROLL + self.frame.grid_columnconfigure(1, minsize=w) + self.createVbar(bg) + bind(self.vbar, "", self._mapBar) + self.bindVbar() + ###self.canvas.focus_set() + + # + # + # + + def destroy(self): + after_cancel(self.timer) + self.timer = None + self.unbind_all() + self.canvas.destroy() + self.frame.destroy() + + def pack(self, **kw): + apply(self.frame.pack, (), kw) + + def grid(self, **kw): + apply(self.frame.grid, (), kw) + + # + # + # + + def setTile(self, app, i, force=False): + tile = app.tabletile_manager.get(i) + if tile is None or tile.error: + return False + ##print i, tile + if i == 0: + assert tile.color + assert tile.filename is None + else: + assert tile.color is None + assert tile.filename + assert tile.basename + if not force: + if i == app.tabletile_index and tile.color == app.opt.table_color: + return False + # + if not self.canvas.setTile(tile.filename, tile.stretch): + tile.error = True + return False + + if i == 0: + self.canvas.config(bg=tile.color) + ##app.top.config(bg=tile.color) + color = None + else: + self.canvas.config(bg=app.top_bg) + ##app.top.config(bg=app.top_bg) + color = tile.text_color + + if app.opt.table_text_color: + self.canvas.setTextColor(app.opt.table_text_color_value) + else: + self.canvas.setTextColor(color) + + return True + + # + # + # + + def unbind_all(self): + unbind_destroy(self.hbar) + unbind_destroy(self.vbar) + unbind_destroy(self.canvas) + unbind_destroy(self.frame) + + def createFrame(self, kw): + width = kw.get("width") + height = kw.get("height") + self.frame = Tkinter.Frame(self.parent, width=width, height=height, bg=None) + + def createCanvas(self, kw): + #self.canvas = apply(Tkinter.Canvas, (self.frame,), kw) + self.canvas = apply(MfxCanvas, (self.frame,), kw) + self.canvas.grid(row=0, column=0, sticky="news") + def createHbar(self, bg): + self.hbar = Tkinter.Scrollbar(self.frame, name="hbar", bg=bg, takefocus=0, orient="horizontal") + self.canvas["xscrollcommand"] = self._setHbar + self.hbar["command"] = self.canvas.xview + def createVbar(self, bg): + self.vbar = Tkinter.Scrollbar(self.frame, name="vbar", bg=bg, takefocus=0) + self.canvas["yscrollcommand"] = self._setVbar + self.vbar["command"] = self.canvas.yview + def bindHbar(self, w=None): + if w is None: + w = self.canvas + bind(w, "", self.unit_left) + bind(w, "", self.unit_right) + def bindVbar(self, w=None): + if w is None: + w = self.canvas + bind(w, "", self.page_up) + bind(w, "", self.page_down) + bind(w, "", self.unit_up) + bind(w, "", self.unit_down) + bind(w, "", self.scroll_top) + bind(w, "", self.scroll_top) + bind(w, "", self.scroll_bottom) + # mousewheel support + if os.name == 'posix': + bind(w, '<4>', self.mouse_wheel_up) + bind(w, '<5>', self.mouse_wheel_down) + # don't work on Linux + #bind(w, '', self.mouse_wheel) + + + def mouse_wheel(self, *args): + print 'MfxScrolledCanvas.mouse_wheel', args + + def _mapBar(self, event): + # see: autoscroll.tcl, http://mini.net/cgi-bin/wikit/950.html + top = event.widget.winfo_toplevel() + g = top.wm_geometry() + if self.resize_pending: + self.resize_pending = 0 + self.canvas.update() + self.canvas.update_idletasks() + top.wm_geometry(g) + + def _setHbar(self, *args): + ##apply(self.hbar.set, args) + self.canvas.update() + apply(self.hbar.set, self.canvas.xview()) + self.showHbar() + ##self.hbar.update_idletasks() + def _setVbar(self, *args): + ##apply(self.vbar.set, args) + self.canvas.update() + apply(self.vbar.set, self.canvas.yview()) + self.showVbar() + ##self.vbar.update_idletasks() + + def showHbar(self, show=-1): + if not self.hbar: + return 0 + if show < 0: + show = self.hbar_mode + if show > 1: + if not self.canvas.winfo_ismapped(): + return 0 + ##self.canvas.update() + view = self.canvas.xview() + show = abs(view[0]) > 0.0001 or abs(view[1] - 1.0) > 0.0001 + if show == self.hbar_show: + return 0 + if show: + self.hbar.grid(row=1, column=0, sticky="we") + else: + self.hbar.grid_forget() + self.hbar_show = show + return 1 + + def showVbar(self, show=-1): + if not self.vbar: + return 0 + if show < 0: + show = self.vbar_mode + if show > 1: + if not self.canvas.winfo_ismapped(): + return 0 + ##self.canvas.update() + view = self.canvas.yview() + show = abs(view[0]) > 0.0001 or abs(view[1] - 1.0) > 0.0001 + if show == self.vbar_show: + return 0 + if show: + self.vbar.grid(row=0, column=1, sticky="ns") + else: + self.vbar.grid_forget() + self.vbar_show = show + return 1 + + def page_up(self, *event): + self.canvas.yview_scroll(-1, "page") + return "break" + def page_down(self, *event): + self.canvas.yview_scroll(1, "page") + return "break" + def unit_up(self, *event): + self.canvas.yview_scroll(-1, "unit") + return "break" + def unit_down(self, *event): + self.canvas.yview_scroll(1, "unit") + return "break" + def mouse_wheel_up(self, *event): + self.canvas.yview_scroll(-5, "unit") + return "break" + def mouse_wheel_down(self, *event): + self.canvas.yview_scroll(5, "unit") + return "break" + def page_left(self, *event): + self.canvas.xview_scroll(-1, "page") + return "break" + def page_right(self, *event): + self.canvas.xview_scroll(1, "page") + return "break" + def unit_left(self, *event): + self.canvas.xview_scroll(-1, "unit") + return "break" + def unit_right(self, *event): + self.canvas.xview_scroll(1, "unit") + return "break" + def scroll_top(self, *event): + self.canvas.yview_moveto(0) + return "break" + def scroll_bottom(self, *event): + self.canvas.yview_moveto(1) + return "break" + + diff --git a/pysollib/tk/tkwrap.py b/pysollib/tk/tkwrap.py new file mode 100644 index 0000000000..dfb21889d0 --- /dev/null +++ b/pysollib/tk/tkwrap.py @@ -0,0 +1,172 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['TclError', + 'BooleanVar', + 'IntVar', + 'StringVar', + 'MfxRoot'] + +# imports +import os, sys, time, types +import Tkinter +from Tkinter import TclError + +# PySol imports +from pysollib.mfxutil import destruct, Struct +from tkutil import after_idle +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE + +# /*********************************************************************** +# // menubar +# ************************************************************************/ + +BooleanVar = Tkinter.BooleanVar +IntVar = Tkinter.IntVar +StringVar = Tkinter.StringVar + + +# /*********************************************************************** +# // Wrapper class for Tk. +# // Required so that a Game will get properly destroyed. +# ************************************************************************/ + +class MfxRoot(Tkinter.Tk): + def __init__(self, **kw): + apply(Tkinter.Tk.__init__, (self,), kw) + self.app = None + # for interruptible sleep + #self.sleep_var = Tkinter.IntVar(self) + #self.sleep_var.set(0) + self.sleep_var = 0 + self.after_id = None + ##self.bind('', self._sleepEvent, add=True) + + def connectApp(self, app): + self.app = app + + # sometimes an update() is needed under Windows, whereas + # under Unix an update_idletasks() would be enough... + def busyUpdate(self): + game = None + if self.app: game = self.app.game + if not game: + self.update() + else: + old_busy = game.busy + game.busy = 1 + if game.canvas: + game.canvas.update() + self.update() + game.busy = old_busy + + def mainquit(self): + self.after_idle(self.quit) + + def screenshot(self, filename): + ##print 'MfxRoot.screenshot not yet implemented' + pass + + def setCursor(self, cursor): + if 0: + ## FIXME: this causes ugly resizes ! + Tkinter.Tk.config(self, cursor=cursor) + elif 0: + ## and this is even worse + ##print self.children + for v in self.children.values(): + v.config(cursor=cursor) + else: + pass + + # + # sleep + # + + def sleep(self, seconds): + #time.sleep(seconds) + self.after(int(seconds*1000)) + return + print 'sleep', seconds + timeout = int(seconds*1000) + self.sleep_var = 0 + while timeout > 0: + self.update() + self.update_idletasks() + if self.sleep_var: + break + self.after(100) + timeout -= 100 + print 'finish sleep' + return + if self.after_id: + self.after_cancel(self.after_id) + self.after_id = self.after(int(seconds*1000), self._sleepEvent) + self.sleep_var.set(1) + self.update() + self.wait_variable(self.sleep_var) + if self.after_id: + self.after_cancel(self.after_id) + self.after_id = None + print 'finish sleep' + + def _sleepEvent(self, *args): + return + print '_sleepEvent', args + self.interruptSleep() + return EVENT_PROPAGATE + + def interruptSleep(self): + return + print 'interruptSleep' + self.update() + self.update_idletasks() + self.sleep_var = 1 + #self.sleep_var.set(0) + #self.after_idle(self.sleep_var.set, 0) + + # + # + # + + def update(self): + Tkinter.Tk.update(self) + + def wmDeleteWindow(self): + if self.app and self.app.menubar: + self.app.menubar.mQuit() + else: + ##self.after_idle(self.quit) + pass diff --git a/pysollib/tk/toolbar.py b/pysollib/tk/toolbar.py new file mode 100644 index 0000000000..01d99f3fee --- /dev/null +++ b/pysollib/tk/toolbar.py @@ -0,0 +1,526 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['PysolToolbar'] #, 'TOOLBAR_BUTTONS'] + +# imports +import os, sys, types, Tkinter +import traceback +try: + # PIL + import Image, ImageTk +except ImportError: + Image = None + +# PySol imports +from pysollib.mfxutil import destruct +from pysollib.util import IMAGE_EXTENSIONS +from pysollib.settings import PACKAGE +from pysollib.actions import PysolToolbarActions + +# Toolkit imports +from tkconst import EVENT_HANDLED, EVENT_PROPAGATE +from tkwidget import MfxTooltip +from menubar import createToolbarMenu, MfxMenu + +gettext = _ +n_ = lambda x: x + + +# /*********************************************************************** +# // +# ************************************************************************/ + +class ToolbarButton(Tkinter.Button): + def __init__(self, parent, toolbar, toolbar_name, position, **kwargs): + Tkinter.Button.__init__(self, parent, kwargs) + self.toolbar = toolbar + self.toolbar_name = toolbar_name + self.position = position + self.visible = False + def show(self, orient, force=False): + if self.visible and not force: + return + self.visible = True + padx, pady= 2, 2 + if orient == Tkinter.HORIZONTAL: + self.grid(row=0, + column=self.position, + ipadx=padx, ipady=pady, + sticky='nsew') + else: + self.grid(row=self.position, + column=0, + ipadx=padx, ipady=pady, + sticky='nsew') + def hide(self): + if not self.visible: return + self.visible = False + self.grid_forget() + +class ToolbarSeparator(Tkinter.Frame): + def __init__(self, parent, toolbar, position, **kwargs): + Tkinter.Frame.__init__(self, parent, kwargs) + self.toolbar = toolbar + self.position = position + self.visible = False + def show(self, orient, force=False): + if self.visible and not force: + return + self.visible = True + width = 4 + height = 4 + padx = 6 + pady = 4 + if orient == Tkinter.HORIZONTAL: + self.config(width=width, height=height) + self.grid(row=0, + column=self.position, + padx=padx, pady=pady, + sticky='ns') + else: + self.config(width=height, height=width) + self.grid(row=self.position, + column=0, + padx=pady, pady=padx, + sticky='ew') + def hide(self): + if not self.visible: return + self.visible = False + self.grid_forget() + +class ToolbarFlatSeparator(ToolbarSeparator): + pass + +class ToolbarLabel(Tkinter.Message): + def __init__(self, parent, toolbar, toolbar_name, position, **kwargs): + Tkinter.Message.__init__(self, parent, kwargs) + self.toolbar = toolbar + self.toolbar_name = toolbar_name + self.position = position + self.visible = False + def show(self, orient, force=False): + if self.visible and not force: + return + self.visible = True + padx = 4 + pady = 4 + if orient == Tkinter.HORIZONTAL: + self.grid(row=0, + column=self.position, + padx=padx, pady=pady, + sticky='nsew') + else: + self.grid(row=self.position, + column=0, + padx=padx, pady=pady, + sticky='nsew') + def hide(self): + if not self.visible: return + self.visible = False + self.grid_forget() + + +# /*********************************************************************** +# // Note: Applications should call show/hide after constructor. +# ************************************************************************/ + +class PysolToolbar(PysolToolbarActions): + + def __init__(self, top, dir, size=0, relief=Tkinter.FLAT, + compound=Tkinter.NONE): + + PysolToolbarActions.__init__(self) + + self.top = top + self._setRelief(relief) + self.side = -1 + self._tooltips = [] + self._widgets = [] + self.dir = dir + self.size = size + self.compound = compound + self.orient=Tkinter.HORIZONTAL + self.label_padx = 4 + self.label_pady = 4 + self.button_pad = 2 + # + self.frame = Tkinter.Frame(top) + # + for l, f, t in ( + (n_("New"), self.mNewGame, _("New game")), + (n_("Restart"), self.mRestart, _("Restart the\ncurrent game")), + (None, None, None), + (n_("Open"), self.mOpen, _("Open a\nsaved game")), + (n_("Save"), self.mSave, _("Save game")), + (None, None, None), + (n_("Undo"), self.mUndo, _("Undo last move")), + (n_("Redo"), self.mRedo, _("Redo last move")), + (n_("Autodrop"), self.mDrop, _("Auto drop cards")), + (n_("Pause"), self.mPause, _("Pause game")), + (None, None, None), + (n_("Statistics"), self.mPlayerStats, _("View statistics")), + (n_("Rules"), self.mHelpRules, _("Rules for this game")), + (None, None, None), + (n_("Quit"), self.mQuit, _("Quit ")+PACKAGE), + ): + if l is None: + sep = self._createSeparator() + sep.bind("<1>", self.clickHandler) + sep.bind("<3>", self.rightclickHandler) + else: + self._createButton(l, f, tooltip=t) + + sep = self._createFlatSeparator() + sep.bind("<1>", self.clickHandler) + sep.bind("<3>", self.rightclickHandler) + self._createLabel("player", label=n_('Player'), + tooltip=_("Player options")) + # + self.player_label.bind("<1>",self.mOptPlayerOptions) + ##self.player_label.bind("<3>",self.mOptPlayerOptions) + self.popup = None + self.frame.bind("<1>", self.clickHandler) + self.frame.bind("<3>", self.rightclickHandler) + # + self.setCompound(compound, force=True) + # Change the look of the frame to match the platform look + # (see also setRelief) + if os.name == 'posix': + #self.frame.config(bd=0, highlightthickness=1) + self.frame.config(bd=1, relief='raised', highlightthickness=0) + elif os.name == "nt": + self.frame.config(bd=2, relief="groove", padx=2, pady=2) + #self._createSeparator(width=4, side=Tkinter.LEFT, relief=Tkinter.FLAT) + #self._createSeparator(width=4, side=Tkinter.RIGHT, relief=Tkinter.FLAT) + else: + self.frame.config(bd=0, highlightthickness=1) + + def config(self, w, v): + if w == 'player': + # label + if v: + self.player_label.show(orient=self.orient) + else: + self.player_label.hide() + else: + # button + widget = getattr(self, w+'_button') + position = widget.position + if v: + widget.show(orient=self.orient) + else: + widget.hide() + # + prev_visible = None + for w in self._widgets: + if w.__class__ is ToolbarSeparator: + if prev_visible is None or prev_visible.__class__ is ToolbarSeparator: + w.hide() + else: + w.show(orient=self.orient) + elif w.__class__ is ToolbarFlatSeparator: + if prev_visible.__class__ is ToolbarSeparator: + prev_visible.hide() + if w.visible: + prev_visible = w + + def _setRelief(self, relief): + if type(relief) is types.IntType: + relief = (Tkinter.RAISED, Tkinter.FLAT)[relief] + elif relief in (Tkinter.RAISED, Tkinter.FLAT): + pass + else: + relief = Tkinter.FLAT + self.button_relief = relief + if relief == Tkinter.RAISED: + self.separator_relief = Tkinter.FLAT + else: + self.separator_relief = Tkinter.RAISED + return relief + + # util + def _loadImage(self, name): + file = os.path.join(self.dir, name) + image = None + for ext in IMAGE_EXTENSIONS: + file = os.path.join(self.dir, name+ext) + if os.path.isfile(file): + if Image: + image = ImageTk.PhotoImage(Image.open(file)) + else: + image = Tkinter.PhotoImage(file=file) + break + return image + + def _createSeparator(self): + position=len(self._widgets) + sep = ToolbarSeparator(self.frame, + position=position, + toolbar=self, + bd=1, + highlightthickness=1, + width=4, + takefocus=0, + relief=self.separator_relief) + sep.show(orient=self.orient) + self._widgets.append(sep) + return sep + + def _createFlatSeparator(self): + position=len(self._widgets) + sep = ToolbarFlatSeparator(self.frame, + position=position, + toolbar=self, + bd=1, + highlightthickness=1, + width=5, + takefocus=0, + relief='flat') + sep.show(orient=self.orient) + self.frame.rowconfigure(position, weight=1) + self.frame.columnconfigure(position, weight=1) + self._widgets.append(sep) + return sep + + def _createButton(self, label, command, tooltip=None): + name = label.lower() + image = self._loadImage(name) + position = len(self._widgets) + button = ToolbarButton(self.frame, + position=position, + toolbar=self, + toolbar_name=name, + command=command, takefocus=0, + text=gettext(label), + relief=self.button_relief, + padx=self.button_pad, + pady=self.button_pad) + if image: + button.config(image=image) + button.show(orient=self.orient) + setattr(self, name + "_image", image) + setattr(self, name + "_button", button) + self._widgets.append(button) + if tooltip: + b = MfxTooltip(button) + self._tooltips.append(b) + b.setText(tooltip) + return button + + def _createLabel(self, name, label=None, tooltip=None): + aspect = (400, 300) [self.getSize() != 0] + position = len(self._widgets) + label = ToolbarLabel(self.frame, + position=position, + toolbar=self, + toolbar_name=name, + relief="ridge", + justify="center", + aspect=aspect) + label.show(orient=self.orient) + setattr(self, name + "_label", label) + self._widgets.append(label) + if tooltip: + b = MfxTooltip(label) + self._tooltips.append(b) + b.setText(tooltip) + return label + + def _busy(self): + if not self.side or not self.game or not self.menubar: + return 1 + self.game.stopDemo() + self.game.interruptSleep() + return self.game.busy + + + # + # public methods + # + + def show(self, side=1, resize=1): + if self.side == side: + return 0 + if resize: + self.top.wm_geometry("") # cancel user-specified geometry + if not side: + # hide + self.frame.grid_forget() + else: + # show + pack_func = self.frame.grid_configure + + if side == 1: + # top + pack_func(row=0, column=1, sticky='ew') + elif side == 2: + # bottom + pack_func(row=2, column=1, sticky='ew') + elif side == 3: + # left + pack_func(row=1, column=0, sticky='ns') + else: + # right + pack_func(row=1, column=2, sticky='ns') + # set orient + orient = side in (1, 2) and Tkinter.HORIZONTAL or Tkinter.VERTICAL + self._setOrient(orient) + self.side = side + return 1 + + def hide(self, resize=1): + self.show(0, resize) + + def destroy(self): + for w in self._tooltips: + if w: w.destroy() + self._tooltips = [] + for w in self._widgets: + if w: w.destroy() + self._widgets = [] + + def setCursor(self, cursor): + if self.side: + self.frame.config(cursor=cursor) + self.frame.update_idletasks() + + def connectGame(self, game, menubar): + PysolToolbarActions.connectGame(self, game, menubar) + if self.popup: + self.popup.destroy() + destruct(self.popup) + self.popup = None + if menubar: + tkopt = menubar.tkopt + self.popup = MfxMenu(master=None, label=n_('Toolbar'), tearoff=0) + createToolbarMenu(menubar, self.popup) + + def updateText(self, **kw): + for name in kw.keys(): + label = getattr(self, name + "_label") + label["text"] = kw[name] + + def updateImages(self, dir, size): + if dir == self.dir and size == self.size: + return 0 + if not os.path.isdir(dir): + return 0 + old_dir, old_size = self.dir, self.size + self.dir, self.size = dir, size + data = [] + try: + for w in self._widgets: + if not isinstance(w, ToolbarButton): + continue + name = w.toolbar_name + image = self._loadImage(name) + data.append((name, w, image)) + except: + self.dir, self.size = old_dir, old_size + return 0 + l = self.player_label + aspect = (400, 300) [size != 0] + l.config(aspect=aspect) + for name, w, image in data: + w.config(image=image) + setattr(self, name + "_image", image) + self.setCompound(self.compound, force=True) + return 1 + + def setRelief(self, relief): + if self.button_relief == relief: + return False + self._setRelief(relief) + if os.name == 'posix': + self.frame.config(relief=self.separator_relief) + for w in self._widgets: + if isinstance(w, ToolbarButton): + w.config(relief=self.button_relief) + elif w.__class__ is ToolbarSeparator: # not ToolbarFlatSeparator + w.config(relief=self.separator_relief) + return True + + def setCompound(self, compound, force=False): + if not force and self.compound == compound: + return False + for w in self._widgets: + if not isinstance(w, ToolbarButton): + continue + if compound == 'text': + w.config(compound=Tkinter.NONE, image='') + else: + image = getattr(self, w.toolbar_name+'_image') + w.config(compound=compound, image=image) + self.compound = compound + return True + + def _setOrient(self, orient=Tkinter.HORIZONTAL, force=False): + if not force and self.orient == orient: + return False + for w in self._widgets: + if w.visible: + w.show(orient=orient, force=True) + self.orient = orient + return True + + # + # Mouse event handlers + # + + def clickHandler(self, event): + if self._busy(): return EVENT_HANDLED + return EVENT_HANDLED + + def rightclickHandler(self, event): + if self._busy(): return EVENT_HANDLED + if self.popup: + ##print event.x, event.y, event.x_root, event.y_root, event.__dict__ + self.popup.tk_popup(event.x_root, event.y_root) + return EVENT_HANDLED + + def middleclickHandler(self, event): + if self._busy(): return EVENT_HANDLED + if 1 <= self.side <= 2: + self.menubar.setToolbarSide(3 - self.side) + return EVENT_HANDLED + + def getSize(self): + if self.compound == 'text': + return 0 + size = self.size + comp = int(self.compound in (Tkinter.TOP, Tkinter.BOTTOM)) + return int((size+comp) != 0) + diff --git a/pysollib/util.py b/pysollib/util.py new file mode 100644 index 0000000000..3ae41ec941 --- /dev/null +++ b/pysollib/util.py @@ -0,0 +1,228 @@ +## vim:ts=4:et:nowrap +## +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer +## Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer +## All Rights Reserved. +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +## Markus F.X.J. Oberhumer +## +## http://www.oberhumer.com/pysol +## +##---------------------------------------------------------------------------## + +__all__ = ['SUITS', + 'COLORS', + 'RANKS', + 'ACE', + 'JACK', + 'QUEEN', + 'KING', + 'ANY_SUIT', + 'ANY_COLOR', + 'ANY_RANK', + 'NO_SUIT', + 'NO_COLOR', + 'NO_RANK', + 'UNLIMITED_MOVES', + 'UNLIMITED_ACCEPTS', + 'UNLIMITED_CARDS', + 'NO_REDEAL', + 'UNLIMITED_REDEALS', + 'VARIABLE_REDEALS', + 'CARDSET', + 'IMAGE_EXTENSIONS', + 'get_version_tuple', + 'Timer', + 'DataLoader', + ] + +# imports +import sys, os, re, time, types + +# PySol imports +from version import VERSION, VERSION_TUPLE +from mfxutil import Pickler, Unpickler, UnpicklingError +from mfxutil import Struct, EnvError +from settings import DATA_DIRS, PACKAGE + +# /*********************************************************************** +# // constants +# ************************************************************************/ + +# Suits values are 0-3. This maps to colors 0-1. +SUITS = (_("Club"), _("Spade"), _("Heart"), _("Diamond")) +COLORS = (_("black"), _("red")) + +# Card ranks are 0-12. We also define symbolic names for the picture cards. +RANKS = (_("Ace"), "2", "3", "4", "5", "6", "7", "8", "9", "10", + _("Jack"), _("Queen"), _("King")) +ACE = 0 +JACK = 10 +QUEEN = 11 +KING = 12 + +# Special values for Stack.cap: +ANY_SUIT = -1 +ANY_COLOR = -1 +ANY_RANK = -1 +NO_SUIT = 999999 # no card can ever match this suit +NO_COLOR = 999999 # no card can ever match this color +NO_RANK = 999999 # no card can ever match this rank +UNLIMITED_MOVES = 999999 # for max_move +UNLIMITED_ACCEPTS = 999999 # for max_accept +UNLIMITED_CARDS = 999999 # for max_cards +# +NO_REDEAL = 0 +UNLIMITED_REDEALS = -1 +VARIABLE_REDEALS = -2 + +CARDSET = _("cardset") + +IMAGE_EXTENSIONS = (".gif", ".ppm",) +if 1 and os.name == "nt": + IMAGE_EXTENSIONS = (".png", ".gif", ".ppm", ".jpg",) + pass +try: + import Image +except ImportError: + pass +else: + IMAGE_EXTENSIONS = (".png", ".gif", ".jpg", ".ppm", ".bmp") + + +def get_version_tuple(version_string): + v = re.split(r"[^\d\.]", version_string) + if not v or not v[0]: + return (0,) + v = v[0].split(".") + v = filter(lambda x: x != "", v) + if not v or not v[0]: + return (0,) + return tuple(map(int, v)) + + +# /*********************************************************************** +# // simple benchmarking +# ************************************************************************/ + +class Timer: + def __init__(self, msg = ""): + self.msg = msg + self.clock = time.time + if os.name == "nt": + self.clock = time.clock + self.start = self.clock() + def reset(self): + self.start = self.clock() + def get(self): + return self.clock() - self.start + def __repr__(self): + return "%-20s %6.3f seconds" % (self.msg, self.clock() - self.start) + + +# /*********************************************************************** +# // DataLoader +# ************************************************************************/ + +class DataLoader: + def __init__(self, argv0, filenames, path=[]): + self.dir = None + if type(filenames) is types.StringType: + filenames = (filenames,) + assert type(filenames) in (types.TupleType, types.ListType) + #$ init path + path = path[:] + head, tail = os.path.split(argv0) + if not head: + head = os.curdir + # dir where placed startup script + path.append(head) + path.append(os.path.join(head, "data")) + path.append(os.path.join(head, os.pardir, "data")) + # dir where placed pysol package + path.append(os.path.join(sys.path[0], "data")) + path.append(os.path.join(sys.path[0], "pysollib", "data")) + # from settings.py + path.extend(DATA_DIRS) + # check path for valid directories + self.path = [] + for p in path: + if not p: continue + np = os.path.abspath(p) + if np and (not np in self.path) and os.path.isdir(np): + self.path.append(np) + # now try to find all filenames along path + for p in self.path: + n = 0 + for filename in filenames: + f = os.path.join(p, filename) + if os.path.isfile(f): + n = n + 1 + else: + break + if n == len(filenames): + self.dir = p + break + else: + raise os.error, str(argv0) + ": DataLoader could not find " + str(filenames) + ##print path, self.path, self.dir + + + def __findFile(self, func, filename, subdirs=None, do_raise=1): + if subdirs is None: + subdirs = ("",) + elif type(subdirs) is types.StringType: + subdirs = (subdirs,) + for dir in subdirs: + f = os.path.join(self.dir, dir, filename) + f = os.path.normpath(f) + if func(f): + return f + if do_raise: + raise os.error, "DataLoader could not find " + filename + " in " + self.dir + " " + str(subdirs) + return None + + def findFile(self, filename, subdirs=None): + return self.__findFile(os.path.isfile, filename, subdirs) + + def findImage(self, filename, subdirs=None): + for ext in IMAGE_EXTENSIONS: + f = self.__findFile(os.path.isfile, filename+ext, subdirs, 0) + if f: + return f + raise os.error, "DataLoader could not find image " + filename + " in " + self.dir + " " + str(subdirs) + + def findIcon(self, filename=None, subdirs=None): + if not filename: + filename = PACKAGE.lower() + root, ext = os.path.splitext(filename) + if not ext: + filename = filename + ".xbm" + return self.findFile(filename, subdirs) + + def findDir(self, filename, subdirs=None): + return self.__findFile(os.path.isdir, filename, subdirs) + diff --git a/pysollib/version.py b/pysollib/version.py new file mode 100644 index 0000000000..1ed2586b9e --- /dev/null +++ b/pysollib/version.py @@ -0,0 +1,30 @@ +##---------------------------------------------------------------------------## +## +## PySol -- a Python Solitaire game +## +## 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; see the file COPYING. +## If not, write to the Free Software Foundation, Inc., +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +## +##---------------------------------------------------------------------------## + +VERSION = "4.82" +#VERSION_DATE = "20 Aug 2003" +VERSION_MAJOR = 4 +VERSION_MINOR = 82 +VERSION_TUPLE = (4, 82) + +FC_VERSION = "0.9.2" +#FC_VERSION_TUPLE = (0, 4, 0) + diff --git a/scripts/all_games.py b/scripts/all_games.py new file mode 100755 index 0000000000..2084532bd5 --- /dev/null +++ b/scripts/all_games.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python +# -*- mode: python; coding: koi8-r; -*- +# + +import sys, os, re, time +from pprint import pprint + +import gettext +gettext.install('pysol', 'locale', unicode=True) + +pysollib_path = os.path.join(sys.path[0], '..') +sys.path[0] = os.path.normpath(pysollib_path) +rules_dir = os.path.normpath(os.path.join(pysollib_path, 'data/html/rules')) +#pprint(sys.path) +#print rules_dir + +import pysollib.games +import pysollib.games.contrib +import pysollib.games.special +import pysollib.games.ultra +import pysollib.games.mahjongg + +from pysollib.gamedb import GAME_DB +from pysollib.gamedb import GI +from pysollib.mfxutil import latin1_to_ascii +from pysollib.resource import CSI + +def getGameRulesFilename(n): + if n.startswith('Mahjongg'): return 'mahjongg.html' + n = re.sub(r"[\[\(].*$", "", n) + n = latin1_to_ascii(n) + n = re.sub(r"[^\w]", "", n) + n = n.lower() + ".html" + return n + +GAME_BY_TYPE = { + GI.GT_BAKERS_DOZEN: "Baker's Dozen", + GI.GT_BELEAGUERED_CASTLE: "Beleaguered Castle", + GI.GT_CANFIELD: "Canfield", + GI.GT_FAN_TYPE: "Fan", + GI.GT_FORTY_THIEVES: "Forty Thieves", + GI.GT_FREECELL: "FreeCell", + GI.GT_GOLF: "Golf", + GI.GT_GYPSY: "Gypsy", + GI.GT_KLONDIKE: "Klondike", + GI.GT_MONTANA: "Montana", + GI.GT_NAPOLEON: "Napoleon", + GI.GT_NUMERICA: "Numerica", + GI.GT_PAIRING_TYPE: "Pairing", + GI.GT_RAGLAN: "Raglan", + GI.GT_SIMPLE_TYPE: "Simple game", + GI.GT_SPIDER: "Spider", + GI.GT_TERRACE: "Terrace", + GI.GT_YUKON: "Yukon", + GI.GT_1DECK_TYPE: "One-Deck game", + GI.GT_2DECK_TYPE: "Two-Deck game", + GI.GT_3DECK_TYPE: "Three-Deck game", + GI.GT_4DECK_TYPE: "Four-Deck game", + + GI.GT_MATRIX: "Matrix", + GI.GT_MEMORY: "Memory", + GI.GT_POKER_TYPE: "Poker", + GI.GT_PUZZLE_TYPE: "Puzzle", + GI.GT_TAROCK: "Tarock", + GI.GT_HEXADECK: "Hex A Deck", + GI.GT_HANAFUDA: "Hanafuda", + GI.GT_DASHAVATARA_GANJIFA: "Dashavatara Ganjifa", + GI.GT_MAHJONGG: "Mahjongg", + GI.GT_MUGHAL_GANJIFA:"Mughal Ganjifa", + GI.GT_SHISEN_SHO:"Shisen-Sho", + +} + +def by_category(): + games = GAME_DB.getGamesIdSortedById() + games_by_cat = {} + for id in games: + gi = GAME_DB.get(id) + gt = CSI.TYPE_NAME[gi.category] + if games_by_cat.has_key(gt): + games_by_cat[gt] += 1 + else: + games_by_cat[gt] = 1 + games_by_cat_list = [(i, j) for i, j in games_by_cat.items()] + games_by_cat_list.sort(lambda i, j: cmp(j[1], i[1])) +## print '' +## for i in games_by_cat_list: +## print '' % i +## print '
NameNumber
%s%s
' + print '
    ' + for i in games_by_cat_list: + print '
  • %s (%s games)
  • ' % i + print '
' + return + +def by_type(): + games = GAME_DB.getGamesIdSortedById() + games_by_type = {} + for id in games: + gi = GAME_DB.get(id) + if not GAME_BY_TYPE.has_key(gi.si.game_type): + print gi.si.game_type + continue + gt = GAME_BY_TYPE[gi.si.game_type] + if games_by_type.has_key(gt): + games_by_type[gt] += 1 + else: + games_by_type[gt] = 1 + games_by_type_list = games_by_type.items() + games_by_type_list.sort(lambda i, j: cmp(i[0], j[0])) +## print '' +## for i in games_by_type_list: +## print '' % i +## print '
NameNumber
%s%s
' + print '
    ' + for i in games_by_type_list: + print '
  • %s (%s games)
  • ' % i + print '
' + return + +def all_games(sort_by='id'): + #rules_dir = 'rules' + print ''' + +''' + + if sort_by == 'id': + get_games_func = GAME_DB.getGamesIdSortedById + else: + get_games_func = GAME_DB.getGamesIdSortedByName + + for id in get_games_func(): + gi = GAME_DB.get(id) + if not gi.rules_filename: + rules_fn = getGameRulesFilename(gi.name) + else: + rules_fn = gi.rules_filename + gt = CSI.TYPE_NAME[gi.category] + if gt == 'French': + gt = 'French (%s)' % GAME_BY_TYPE[gi.si.game_type] + if 1 and os.path.exists(os.path.join(rules_dir, rules_fn)): + fn = '../data/html/rules/'+rules_fn + print ''' +''' % (id, fn, + gi.name.encode('utf-8'), '
'.join(gi.altnames).encode('utf-8'), gt) + else: + print ''' +''' % (id, gi.name.encode('utf-8'), + '
'.join(gi.altnames).encode('utf-8'), gt) + print '
IDNameAlternate namesType
%s +%s +%s%s
%s%s%s%s
' + +def create_html(sort_by): + print '' + print 'Total games: %d' % len(GAME_DB.getGamesIdSortedById()) + print '

Categories

' + by_category() + print '

Types

' + by_type() + print '

All games

' + all_games(sort_by) + print '' + + +def get_text(): + #get_games_func = GAME_DB.getGamesIdSortedById + get_games_func = GAME_DB.getGamesIdSortedByName + + games_list = {} # for unique + for id in get_games_func(): + gi = GAME_DB.get(id) + games_list[gi.name] = '' + if gi.name != gi.short_name: + games_list[gi.short_name] = '' + for n in gi.altnames: + games_list[n] = '' + games_list = games_list.keys() + games_list.sort() + print '''\ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PySol 0.0.1\\n" +"POT-Creation-Date: %s\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME \\n" +"Language-Team: LANGUAGE \\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=CHARSET\\n" +"Content-Transfer-Encoding: ENCODING\\n" +"Generated-By: %s 0.1\\n" + +''' % (time.asctime(), sys.argv[0]) + for g in games_list: + print 'msgid "%s"\nmsgstr ""\n' % g.encode('utf-8') + +def plain_text(): + #get_games_func = GAME_DB.getGamesIdSortedById + get_games_func = GAME_DB.getGamesIdSortedByName + games_list = {} # for unique + for id in get_games_func(): + gi = GAME_DB.get(id) + games_list[gi.name] = '' + #if gi.name != gi.short_name: + # games_list[gi.short_name] = '' + for n in gi.altnames: + games_list[n] = '' + games_list = games_list.keys() + games_list.sort() + for g in games_list: + print g.encode('utf-8') + + + +## +if len(sys.argv) < 2 or sys.argv[1] == 'html': + sort_by = 'id' + if len(sys.argv) > 2: + sort_by = sys.argv[2] + create_html(sort_by) +elif sys.argv[1] == 'gettext': + get_text() +elif sys.argv[1] == 'text': + plain_text() + + + diff --git a/scripts/build.bat b/scripts/build.bat new file mode 100755 index 0000000000..b5c9c0de0a --- /dev/null +++ b/scripts/build.bat @@ -0,0 +1,11 @@ +rem simple script for building windows package + +cd .. +rm -rf dist +mkdir dist +cp -r locale dist +cp freecell-solver\freecell-solver-2.8.6-bin\fc-solve.exe dist +python setup.py py2exe +python scripts\create_iss.py +"d:\Program Files\Inno Setup 5\ISCC.exe" setup.iss +pause diff --git a/scripts/cardset_viewer.py b/scripts/cardset_viewer.py new file mode 100755 index 0000000000..d9491d9d67 --- /dev/null +++ b/scripts/cardset_viewer.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python +# -*- mode: python; coding: koi8-r; -*- +# + +import sys, os +from glob import glob +from math import sqrt, sin, cos, pi +from Tkinter import * +try: + import Image, ImageTk +except ImportError: + Image = None + +cardset_type = { + '1': 'French', + '2': 'Hanafuda', + '3': 'Tarock', + '4': 'Mahjongg', + '5': 'Hexadeck', + '6': 'Mughal Ganjifa', + '7': 'Navagraha Ganjifa', + '8': 'Dashavatara Ganjifa', + '9': 'Trump only', + } + +class Cardset: + def __init__(self, dir, name, type, ext, x, y): + self.dir, self.name, self.type, self.ext, self.x, self.y = dir, name, type, ext, x, y + +def create_cs_list(ls): + cardsets_list = {} + for f in ls: + dir = os.path.split(f)[0] + lines = open(f).readlines() + l0 = lines[0].split(';') + try: + ext = l0[2] + except IndexError: + ##print f + ext = '.gif' + if len(l0) > 3: + type = cardset_type[l0[3]] + else: + #type = 'Unknown' + type = 'French' + l1 = lines[1].split(';') + name = l1[1].strip() + l2 = lines[2].split() + x, y = int(l2[0]), int(l2[1]) + cs = Cardset(dir, name, type, ext, x, y) + cardsets_list[name] = cs + return cardsets_list + +tk_images = [] +zoom = 0 +def show_cardset(*args): + global tk_images + tk_images = [] + if list_box.curselection(): + cs_name = list_box.get(list_box.curselection()) + cs = cardsets_dict[cs_name] + ls = glob(os.path.join(cs.dir, '[0-9][0-9][a-z]'+cs.ext)) + ls += glob(os.path.join(cs.dir, 'back*'+cs.ext)) + #ls += glob(os.path.join(cs.dir, 'bottom*.gif')) + #ls += glob(os.path.join(cs.dir, 'l*.gif')) + #ls = glob(os.path.join(cs.dir, '*.gif')) + ##if not ls: return + ls.sort() + n = 0 + pf = None + x, y = 10, 10 + width, height = 0, 0 + canvas.delete('all') + for f in ls: + if Image: + filter = { + 'NEAREST' : Image.NEAREST, + 'BILINEAR' : Image.BILINEAR, + 'BICUBIC' : Image.BICUBIC, + 'ANTIALIAS': Image.ANTIALIAS, + } [filter_var.get()] + ##filter = Image.BILINEAR + ##filter = Image.BICUBIC + ##filter = Image.ANTIALIAS + ##print f + im = Image.open(f) + if zoom != 0: + w, h = im.size + im = im.convert('RGBA') # for save transparency + if rotate_var.get(): + # rotate + #if filter == Image.ANTIALIAS: + # filter = Image.BICUBIC + z = zoom*5 + a = abs(pi/2/90*z) + neww = int(w*cos(a)+h*sin(a)) + newh = int(h*cos(a)+w*sin(a)) + ##print w, h, neww, newh + d = int(sqrt(w*w+h*h)) + dx, dy = (d-w)/2, (d-h)/2 + newim = Image.new('RGBA', (d, d)) + newim.paste(im, (dx, dy)) + im = newim + im = im.rotate(z, resample=filter) + x0, y0 = (d-neww)/2, (d-newh)/2 + x1, y1 = d-x0, d-y0 + im = im.crop((x0, y0, x1, y1)) + t = str(z) + else: + # zoom + z = 1.0 + zoom/10.0 + z = max(0.2, z) + im = im.resize((int(w*z), int(h*z)), resample=filter) + t = '%d %%' % int(z*100) + + zoom_label.config(text=t) + + else: + zoom_label.config(text='') + image = ImageTk.PhotoImage(im) + else: + image = PhotoImage(file=f) + tk_images.append(image) + ff = os.path.split(f)[1] + if pf is None: + pf = ff[:2] + x, y = 10, 10 + elif ff[:2] != pf: + pf = ff[:2] + x = 10 + y += image.height()+10 + else: + x += image.width()+10 + canvas.create_image(x, y, image=image, anchor=NW) + ##canvas.create_rectangle(x, y, x+image.width(), y+image.height()) + width = max(width, x) + height = max(height, y) + width, height = width+image.width()+10, height+image.height()+10 + canvas.config(scrollregion=(0, 0, width, height)) + ##print image.width(), image.height() + label.config(text='''\ +Name: %s +Type: %s +Directory: %s''' % (cs.name, cs.type, cs.dir)) + +def zoom_in(*args): + global zoom + zoom += 1 + show_cardset() + +def zoom_out(*args): + global zoom + zoom -= 1 + show_cardset() + +def zoom_cancel(*args): + global zoom + zoom = 0 + show_cardset() + +def show_info(*args): + if list_box.curselection(): + cs_name = list_box.get(list_box.curselection()) + cs = cardsets_dict[cs_name] + fn = os.path.join(cs.dir, 'COPYRIGHT') + top = Toplevel() + text = Text(top) + text.insert('insert', open(fn).read()) + text.pack(expand=YES, fill=BOTH) + b_frame = Frame(top) + b_frame.pack(fill=X) + button = Button(b_frame, text='Close', command=top.destroy) + button.pack(side=RIGHT) + +def create_widgets(): + global list_box, canvas, label, zoom_label + # + root = Tk() + # + list_box = Listbox(root) + list_box.grid(row=0, column=0, rowspan=2, sticky=NS) + cardsets_list = list(cardsets_dict) + cardsets_list.sort() + for cs in cardsets_list: + list_box.insert(END, cs) + list_box.bind('<>', show_cardset) + # + sb = Scrollbar(root) + sb.grid(row=0, column=1, rowspan=2, sticky=NS) + list_box.config(yscrollcommand=sb.set) + sb.config(command=list_box.yview) + # + canvas = Canvas(root, bg='#5eab6b') + canvas.grid(row=0, column=2, sticky=NSEW) + canvas.bind('<4>', lambda e: canvas.yview_scroll(-5, 'unit')) + canvas.bind('<5>', lambda e: canvas.yview_scroll(5, 'unit')) + # + sb = Scrollbar(root) + sb.grid(row=0, column=3, sticky=NS) + canvas.config(yscrollcommand=sb.set) + sb.config(command=canvas.yview) + # + if True: + sb = Scrollbar(root, orient=HORIZONTAL) + sb.grid(row=1, column=2, sticky=EW) + canvas.config(xscrollcommand=sb.set) + sb.config(command=canvas.xview) + # + label = Label(root) + label.grid(row=2, column=0, columnspan=4) + # + b_frame = Frame(root) + b_frame.grid(row=3, column=0, columnspan=4, sticky=EW) + button = Button(b_frame, text='Quit', command=root.quit, width=8) + button.pack(side=RIGHT) + button = Button(b_frame, text='Info', command=show_info, width=8) + button.pack(side=RIGHT) + if Image: + global rotate_var, filter_var + rotate_var = IntVar(root) + filter_var = StringVar(root) + button = Button(b_frame, text=' + ', command=zoom_in) + button.pack(side=LEFT) + button = Button(b_frame, text=' - ', command=zoom_out) + button.pack(side=LEFT) + button = Button(b_frame, text=' = ', command=zoom_cancel) + button.pack(side=LEFT) + button = Checkbutton(b_frame, text='Rotate', indicatoron=0, + selectcolor=b_frame['bg'], width=8, + variable=rotate_var, command=show_cardset) + button.pack(side=LEFT, fill='y') + om = OptionMenu(b_frame, filter_var, + 'NEAREST', 'BILINEAR', 'BICUBIC', 'ANTIALIAS', + command=show_cardset) + filter_var.set('NEAREST') + om.pack(side=LEFT, fill='y') + + zoom_label = Label(b_frame) + zoom_label.pack(side=LEFT) + # + root.columnconfigure(2, weight=1) + root.rowconfigure(0, weight=1) + + root.title('Show Cardsets') + + return root + +if __name__ == '__main__': + if len(sys.argv) > 1: + data_dir = sys.argv[1] + else: + data_dir = os.path.normpath(os.path.join(sys.path[0], os.pardir, 'data')) + ls = glob(os.path.join(data_dir, '*', 'config.txt')) + cardsets_dict = create_cs_list(ls) + root = create_widgets() + root.mainloop() diff --git a/scripts/create_iss.py b/scripts/create_iss.py new file mode 100755 index 0000000000..80fbcbf081 --- /dev/null +++ b/scripts/create_iss.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +prog_name = 'PySol Fan Club edition' +prog_version = '0.9.0' + +import os + +dirs_list = [] +files_list = [] +for root, dirs, files in os.walk('dist'): + if files: + files_list.append(root) + dirs_list.append(root) + +out = open('setup.iss', 'w') + +print >> out, ''' +[Setup] +AppName=%(prog_name)s +AppVerName=%(prog_name)s v.%(prog_version)s +DefaultDirName={pf}\\%(prog_name)s +DefaultGroupName=%(prog_name)s +UninstallDisplayIcon={app}\\pysol.exe +Compression=lzma +SolidCompression=yes +SourceDir=dist +OutputDir=. +OutputBaseFilename=PySolFC_%(prog_version)s_setup + +[Icons] +Name: "{group}\\%(prog_name)s"; Filename: "{app}\\pysol.exe" +Name: "{group}\\Uninstall %(prog_name)s"; Filename: "{uninstallexe}" +Name: "{userdesktop}\\%(prog_name)s"; Filename: "{app}\\pysol.exe" +''' % vars() + +print >> out, '[Dirs]' +for d in dirs_list[1:]: + print >> out, 'Name: "{app}%s"' % d.replace('dist', '') + +print >> out +print >> out, '[Files]' +print >> out, 'Source: "*"; DestDir: "{app}"' +for d in files_list[1:]: + d = d.replace('dist\\', '') + print >> out, 'Source: "%s\\*"; DestDir: "{app}\\%s"' % (d, d) + + diff --git a/scripts/mahjongg_utils.py b/scripts/mahjongg_utils.py new file mode 100755 index 0000000000..fce6a1a654 --- /dev/null +++ b/scripts/mahjongg_utils.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +# -*- mode: python; coding: utf-8; -*- + +import sys, os, re + +alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + +def decode_layout(layout): + # decode tile positions + assert layout[0] == "0" + assert (len(layout) - 1) % 3 == 0 + tiles = [] + for i in range(1, len(layout), 3): + n = alpha.find(layout[i]) + level, height = n / 7, n % 7 + 1 + tx = alpha.find(layout[i+1]) + ty = alpha.find(layout[i+2]) + assert n >= 0 and tx >= 0 and ty >= 0 + for tl in range(level, level + height): + tiles.append((tl, tx, ty)) + tiles.sort() + return tiles + +def encode_layout(layout): + # encode positions + s = '0' + ##layout.sort() + x_max = max([t[1] for t in layout]) + y_max = max([t[2] for t in layout]) + for x in range(x_max+1): + for y in range(y_max+1): + l = [t[0] for t in layout if t[1] == x and t[2] == y] + if not l: + continue + i_0 = i_n = l[0] + for i in l[1:]: + if i == i_n+1: + i_n = i + continue + s += alpha[i_0*7+(i_n-i_0)] + alpha[x] + alpha[y] + i_0 = i_n = i + s += alpha[i_0*7+(i_n-i_0)] + alpha[x] + alpha[y] + +## for tl, tx, ty in layout: +## s += alpha[tl*7]+alpha[tx]+alpha[ty] + return s + + + +def parse_kyodai(filename): + # Kyodai (http://www.kyodai.com/) + + fd = open(filename) + fd.readline() + fd.readline() + + s = fd.readline() + i = 0 + y = 0 + z = 0 + layout = [] + while True: + ss = s[i:i+34] + if not ss: + break + x = 0 + for c in ss: + if c == '1': + layout.append((z, x, y)) + x += 1 + y += 1 + if y == 20: + y = 0 + z += 1 + i += 34 + layout.sort() + return normalize(layout) + + +def parse_ace(filename): + # Ace of Penguins (http://www.delorie.com/store/ace/) + l = open(filename).read().replace('\n', '').split(',') + l.reverse() + layout = [] + layer = 0 + while True: + x = int(l.pop()) + if x == 127: + break + if x <= 0: + x = -x + y, z = int(l.pop()), int(l.pop()) + if layer < z: + layer = z + layout.append((z, x, y)) + layout.sort() + return normalize(layout) + + +def parse_kmahjongg(filename): + # KMahjongg + fd = open(filename) + fd.readline() + lines = fd.readlines() + level = 0 + n = 0 + layout = [] + for s in lines: + i = 0 + while True: + i = s.find('1', i) + if i >= 0: + layout.append((level, i, n)) + i += 1 + else: + break + n += 1 + if n == 16: + n = 0 + level += 1 + layout.sort() + return normalize(layout) + + +def parse_xmahjongg(filename): + if open(filename).readline().startswith('Kyodai'): + return parse_kyodai(filename) + fd = open(filename) + layout = [] + for s in fd: + s = s.strip() + if not s: + continue + if s.startswith('#'): + continue + row, col, lev = s.split() + layout.append((int(lev), int(col), int(row))) + layout.sort() + return normalize(layout) + + +def normalize(l): + minx = min([i[1] for i in l]) + if minx: + l = [(i[0], i[1]-minx, i[2]) for i in l] + miny = min([i[2] for i in l]) + if miny: + l = [(i[0], i[1], i[2]-miny) for i in l] + return l + + +if __name__ == '__main__': + gameid = 5200 + + usage = '''usage: +%s TYPE FILE ... + where TYPE are: + k | kyodai - parse kyodai file + x | xmahjongg - parse xmahjongg file + m | kmahjongg - parse kmahjongg file + a | ace - parse ace of penguins file +''' % sys.argv[0] + + if len(sys.argv) < 3: + sys.exit(usage) + if sys.argv[1] in ['k', 'kyodai']: + parse_func = parse_kyodai + elif sys.argv[1] in ['x', 'xmahjongg']: + parse_func = parse_xmahjongg + elif sys.argv[1] in ['m', 'kmahjongg']: + parse_func = parse_kmahjongg + elif sys.argv[1] in ['a', 'ace']: + parse_func = parse_ace + else: + sys.exit(usage) + + for filename in sys.argv[2:]: + + layout = parse_func(filename) + layout = normalize(layout) + + #print filename, len(layout) + + s = encode_layout(layout) + + # check + lt = decode_layout(s) + if lt != layout: + print '*** ERROR ***' + else: + ##print s + + gamename = os.path.split(filename)[1].split('.')[0] + #classname = gamename.replace(' ', '_') + #classname = 'Mahjongg_' + re.sub('\W', '', classname) + + ncards = len(layout) + + if ncards != 144: + print '''r(%d, "%s", ncards=%d, layout="%s") +''' % (gameid, gamename, ncards, s) + + else: + print '''r(%d, "%s", layout="%s") +''' % (gameid, gamename, s) + + gameid += 1 + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000000..ea699b17b2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,8 @@ +[sdist] +formats = bztar +force_manifest = 1 + +[bdist_rpm] +release = 1 +doc_files = COPYING README +use_bzip2 = 1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..2da5fad8e6 --- /dev/null +++ b/setup.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +# -*- mode: python; -*- + +import os +from distutils.core import setup +from pysollib.version import FC_VERSION as VERSION +if os.name == 'nt': + import py2exe + +if os.name == 'posix': + data_dir = 'share/PySolFC' +elif os.name == 'nt': + data_dir = 'data' +else: + data_dir = 'data' + +datas = [ + 'html', + 'images', + 'sound', + 'tiles', + 'toolbar', + ] +for s in open('MANIFEST.in'): + if s.startswith('graft data/cardset-'): + datas.append(s[11:].strip()) +data_files = [] +for d in datas: + for root, dirs, files in os.walk(os.path.join('data', d)): + if files: + #files = map(lambda f: os.path.join(root, f), files) + files = [os.path.join(root, f) for f in files] + data_files.append((os.path.join(data_dir, root[5:]), files)) +if os.name == 'posix': + data_files.append(('share/pixmaps', ['data/pysol.xbm', 'data/pysol.xpm'])) + for l in ('ru', 'ru_RU'): + data_files.append(('share/locale/%s/LC_MESSAGES' % l, + ['locale/%s/LC_MESSAGES/pysol.mo' % l])) + +long_description = """\ +PySol is a solitaire card game. Its features include support for many +different games, very nice look and feel, multiple cardsets and +backgrounds, unlimited undo & redo, load & save games, player +statistics, hint system, demo games, support for user written plug-ins, +integrated HTML help browser, and it's free Open Source software. +""" +kw = { + 'name' : 'PySolFC', + 'version' : VERSION, + 'url' : 'http://sourceforge.net/projects/pysolfc/', + 'author' : 'Skomoroh', + 'author_email' : 'skomoroh@gmail.com', + 'description' : 'PySol - a solitaire game collection', + 'long_description' : long_description, + 'license' : 'GPL', + 'scripts' : ['pysol'], + 'packages' : ['pysollib', + 'pysollib.tk', + 'pysollib.games', + 'pysollib.games.contrib', + 'pysollib.games.special', + 'pysollib.games.ultra', + 'pysollib.games.mahjongg'], + 'data_files' : data_files, + } + +if os.name == 'nt': + kw['windows'] = [{'script': 'pysol', + 'icon_resources': [(1, "data/pysol.ico")], }] + +setup(**kw)