1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-04-05 00:02:29 -04:00

Compare commits

...

20 commits

Author SHA1 Message Date
Shlomi Fish
c8b6ce0fcd add more regex code to match 2025-02-28 14:10:00 +02:00
Shlomi Fish
97322f81f4 test presence of a <move/> element
add a testcase for the XML output
2025-02-28 13:56:16 +02:00
Shlomi Fish
e05f72af6c test presence of a <move/> element
add a testcase for the XML output
2025-02-28 13:56:16 +02:00
Shlomi Fish
986de91999 add a testcase for the XML output 2025-02-28 13:56:16 +02:00
Shlomi Fish
cc1f4b0bca [Wip] kpatience xml exporting (Jan 2025 try) 2025-02-28 13:56:16 +02:00
Joe R
81150b4681 Add Relaxed Cruel game 2025-02-26 19:55:01 -05:00
Joe R
f799093e35 Restore unfinished Thirteen game as Pyramid Thirteen, using correct AisleRiot rules 2025-02-23 22:32:22 -05:00
Joe R
12118d12ed Clean up Scorpion type games 2025-02-23 14:18:08 -05:00
Joe R
fd4a4e1378 Fill in missing translated names 2025-02-20 18:59:01 -05:00
Joe R
63a63fdfd3 Add Louis game 2025-02-20 18:47:21 -05:00
Joe R
ff9cc5e98c Add FAQ to the in-app documentation 2025-02-20 17:26:10 -05:00
Joe R
cadf8b2084 Fix some controls being unselectable by tab 2025-02-18 19:16:04 -05:00
Joe R
f4dec3ed16 Add Ides of March game 2025-02-17 21:24:26 -05:00
Joe R
5c8d5c26b4 Cleanup 2025-02-17 20:49:25 -05:00
Joe R
cb0dd1ec2f Add definition of super move to the glossary 2025-02-13 21:09:17 -05:00
Joe R
dc5ab96c80 Add Families game 2025-02-13 20:55:16 -05:00
Joe R
54a978b4e2 Fix flake8 2025-02-11 21:56:10 -05:00
Joe R
5b9f64a7eb Support alternate names for alternate deck games 2025-02-11 21:39:43 -05:00
Joe R
21c2780e8e Tweak menu text for consistency 2025-02-06 21:56:32 -05:00
Joe R
ed2da8cd46 Restore lost Outback Patience game, with the correct rules 2025-02-06 19:59:16 -05:00
50 changed files with 647 additions and 177 deletions

64
html-src/faq.html Normal file
View file

@ -0,0 +1,64 @@
<h1>FAQ</h1>
<h2>The animation is too slow...</h2>
<p>
Unfortunately the Tcl/Tk toolkit lacks a sprite concept, so
there is a lot of (invisible double-buffered) redraw going on
when dragging cards around.
<p>
Disabling <i>Card shadow</i>, <i>Shade legal moves</i>,
background table tiles and sound will somewhat improve the display speed.
<h2>The table tiles look strange</h2>
<p>
Background table tiles should only be enabled when using
a true-color video mode - otherwise they may look bad
because of dithering.
<p>
BTW, you can add your own background tiles by copying the images
to the main <i>data/tiles</i> or your home <i>~/.PySolFC/tiles</i> directory.
<!-- They must be in GIF or PPM format. -->
<h2>My antivirus app says the Windows installer contains a virus.</h2>
<p>
We have been asked about the fact that some anti viruses, including those
on <a href="https://www.virustotal.com/">VirusTotal</a>, have identified the Microsoft
Windows downloads as containing malware. What we know is that they are generated from
the <a href="https://en.wikipedia.org/wiki/Free_and_open-source_software">open source</a>
source code by <a href="https://www.appveyor.com/">AppVeyor</a>, are checked using
VirusTotal before they are uploaded, and at that point were considered to be malware clean.
<p>
However, as time passes, they seem to accumulate classifyings as containing malware
in what appears to be <a href="https://en.wikipedia.org/wiki/False_positives_and_false_negatives">false positives</a>. So we believe the downloads are nonetheless safe and free of
malware.
<p>
Furthermore, note that we believe that many anti-malware applications are harmful
by themselves, and that they are all an incomplete (non-)solution to an
issue that does not exist in <a href="https://en.wikipedia.org/wiki/Linux#Desktop">Linux</a> and the <a href="https://en.wikipedia.org/wiki/List_of_BSD_operating_systems">BSDs operating systems</a>.
<p>
For more information, see:
<ol>
<li><a href="https://sourceforge.net/p/pysolfc/discussion/503709/thread/d841b6a1/">Forum discussion</a></li>
<li><a href="http://linuxmafia.com/~rick/faq/">Viruses on Linux</a></li>
<li><a href="https://www.mail-archive.com/wikimedia-l@lists.wikimedia.org/msg30001.html">Discussion about anti-viruses.</a></li>
</ol>
<h2>I received an error that there is no module named "formatter".</h2>
<p>
This error occurs if you're trying to run an older version of PySolFC
with Python 3.10. If you are using Python 3.10 or later, please upgrade your
version of PySolFC to 2.14.0 or later - older versions are not compatible with
Python 3.10.
<p>
If you are getting your copy from your Linux distribution's package manager,
please contact the people who maintain the packages for your distribution.
You can always get the latest version from the PySolFC website or from
Flathub.
<h2>It won't let me deal more cards in Spider.</h2>
<p>
When playing Spider, you are not allowed to deal more cards if there are any
empty piles. If there aren't enough cards left to put one in each pile, the
game is lost. Part of the game is planning ahead when to deal more cards to
avoid this situation.
<p>
If you want a variation without this rule, you can play Relaxed Spider instead.

View file

@ -46,6 +46,7 @@ fix_gettext()
files = [ files = [
('credits.html', 'PySol Credits'), ('credits.html', 'PySol Credits'),
('credits_old.html', 'PySol Credits'), ('credits_old.html', 'PySol Credits'),
('faq.html', 'PySol - FAQ'),
('ganjifa.html', 'PySol - General Ganjifa Card Rules'), ('ganjifa.html', 'PySol - General Ganjifa Card Rules'),
('general_rules.html', 'PySol - General Rules'), ('general_rules.html', 'PySol - General Rules'),
('glossary.html', 'PySol - Glossary'), ('glossary.html', 'PySol - Glossary'),

View file

@ -323,6 +323,15 @@ the deck.</p>
Hearts, and Diamonds.</p> Hearts, and Diamonds.</p>
</dd> </dd>
<dt><b>SUPER MOVE</b></dt>
<dd>
<p>A special move where you move a sequence of cards at once, in a game
that does not normally allow it. However, if there are enough open
reserves and empty stacks to perform the move by moving cards between
them one at a time, you can make it as a single move as a shortcut.</p>
</dd>
<dt><b>TABLEAU</b></dt> <dt><b>TABLEAU</b></dt>
<dd> <dd>

View file

@ -55,27 +55,6 @@ disable certain features as they would be trivial otherwise.
The logic involved is not too clever on purpose (i.e. it does not consult the hint system). The logic involved is not too clever on purpose (i.e. it does not consult the hint system).
</ul> </ul>
<h2>The animation is too slow...</h2>
<p>
Unfortunately the Tcl/Tk toolkit lacks a sprite concept, so
there is a lot of (invisible double-buffered) redraw going on
when dragging cards around.
<p>
Disabling <i>Card shadow</i>, <i>Shade legal moves</i>,
background table tiles and sound will somewhat improve the display speed.
<h2>The table tiles look strange</h2>
<p>
Background table tiles should only be enabled when using
a true-color video mode - otherwise they may look bad
because of dithering.
<p>
BTW, you can add your own background tiles by copying the images
to the main <i>data/tiles</i> or your home <i>~/.PySolFC/tiles</i> directory.
<!-- They must be in GIF or PPM format. -->
<h2>Some notes about scoring</h2> <h2>Some notes about scoring</h2>
<p> <p>
<ul type="disc"> <ul type="disc">

View file

@ -19,9 +19,10 @@
<li> <a href="cardset_customization.html">Cardset Customization</a> <li> <a href="cardset_customization.html">Cardset Customization</a>
<li> <a href="plugins.html">Plugins</a> <li> <a href="plugins.html">Plugins</a>
</ul> </ul>
<h2>Misc</h2> <h2>About</h2>
<ul> <ul>
<li> <a href="news.html">What's new?</a> <li> <a href="news.html">What's new?</a>
<li> <a href="faq.html">FAQ</a>
<li> <a href="report_bug.html">Report a Bug</a> <li> <a href="report_bug.html">Report a Bug</a>
<li> <a href="license.html">PySol license terms</a> <li> <a href="license.html">PySol license terms</a>
<li> <a href="credits.html">PySol credits</a> <li> <a href="credits.html">PySol credits</a>

View file

@ -1,10 +1,9 @@
<h1>Report a Bug</h1> <h1>Report a Bug</h1>
<p> <p>
Before you report a bug, please verify that you are running Before you report a bug, please verify that you are running
the latest version of PySolFC, and also check the PySolFC site's the latest version of PySolFC. Also, check the
<a href="https://pysolfc.sourceforge.io/faq.html">FAQ page</a>, <a href="faq.html">FAQ</a>, and issues previously reported
and issues previously reported on GitHub, to ensure that the bug on GitHub, to ensure that the bug was not previously reported.
was not previously reported.
<p> <p>
If you found a bug in PySolFC, the best place to report it If you found a bug in PySolFC, the best place to report it
is on GitHub. To do so, create an issue at is on GitHub. To do so, create an issue at

View file

@ -15,7 +15,7 @@ This card must be the last remaining card in order to win the game.
<h3>Notes</h3> <h3>Notes</h3>
<p> <p>
Accordion's Revenge is unwinnable if the first or second card is Accordion's Revenge is unwinnable if the first or second card is
declared. As such, PySol will reselect the declared card if it declared. As such, PySol will never select either of those cards as
is chosen. the target.
<p> <p>
The concept of Accordion's Revenge was invented by Mark Masten. The concept of Accordion's Revenge was invented by Mark Masten.

View file

@ -1,4 +1,4 @@
<h1>Big Braid</h1> <h1>Big Braid (Der gro&szlig;e Zopf)</h1>
<p> <p>
Napoleon type. 3 decks. 2 redeals. Napoleon type. 3 decks. 2 redeals.

View file

@ -0,0 +1,27 @@
<h1>Families</h1>
<p>
Memory game type. 32 cards. No redeal.
<h3>Object</h3>
<p>
Remove all groups of jack-queen-king of the same suit.
<h3>Rules</h3>
<p>
Families is played with eight sets of Jack, Queen, and King - two of
each suit, along with six black jokers and two red jokers.
<p>
Flip three cards. If you flip a sequence of Jack, Queen, and King of
the same suit, the cards are removed. If not, they are flipped face-down.
<p>
If a red joker is flipped, all of the remaining unmatched cards that were
not flipped are reshuffled and redealt (after you've flipped all three
cards).
<p>
If a pair of black jokers is flipped, the game is lost. If all eight
sequences are removed before this happens, the game is won.
<h3>Notes</h3>
<p>
<i>Undo</i>, <i>Bookmarks</i>, <i>Autodrop</i> and <i>Quickplay</i>
are disabled for this game.

View file

@ -1,6 +1,6 @@
<h1>Hurricane</h1> <h1>Hurricane</h1>
<p> <p>
Pairing game type. 1 deck. No redeal. Pairing type. 1 deck. No redeal.
<h3>Object</h3> <h3>Object</h3>
<p> <p>

View file

@ -0,0 +1,12 @@
<h1>Ides of March</h1>
<p>
Pairing type. 1 deck. No redeal.
<h3>Object</h3>
<p>
Move all cards to the single foundation.
<h3>Quick Description</h3>
<p>
Like <a href="hurricane.html">Hurricane</a>,
but remove pairs whose ranks total 15, or pairs of aces.

19
html-src/rules/louis.html Normal file
View file

@ -0,0 +1,19 @@
<h1>Louis</h1>
<p>
Two-Deck game type. 2 decks. 2 redeals.
<h3>Object</h3>
<p>
Move all the cards to the foundations.
<h3>Quick Description</h3>
<p>
Like <a href="sthelena.html">St. Helena</a>,
but at the start of the game, a card is dealt to each of the
twelve tableau piles. During this round, an empty tableau pile
will be immediately filled from the talon. When no moves are left,
the rest of the deck can be dealt.
<p>
Also, there are no restrictions as to which tableau piles
cards can be moved to foundations from, and tableau piles are
built up or down by same suit.

View file

@ -0,0 +1,12 @@
<h1>Outback Patience</h1>
<p>
Yukon type. 2 decks. No redeal.
<h3>Object</h3>
<p>
Move all cards to the foundations.
<h3>Quick Description</h3>
<p>
Like <a href="austalianpatience.html">Australian Patience</a>,
but with two decks, and eight piles of seven cards each.

View file

@ -0,0 +1,22 @@
<h1>Pyramid Thirteen</h1>
<p>
Pairing game type. 1 deck. No redeal.
<h3>Object</h3>
<p>
Move all cards to the single foundation.
<h3>Quick Description</h3>
<p>
Like <a href="pyramid.html">Pyramid</a>, but all but the
front row of the pyramid are dealt face-down, and no redeals
are allowed.
<h3>Notes</h3>
<p>
This difficult variant of Pyramid is based on the Gnome AisleRiot
rules. It is called "Thirteen" there, but renamed Pyramid Thirteen
here to avoid confusion with the game
<a href="thirteens.html">Thirteens</a>.
<p>
<i>Quickplay</i> is disabled for this game.

View file

@ -0,0 +1,12 @@
<h1>Relaxed Cruel</h1>
<p>
Baker's Dozen type. 1 deck. Unlimited redeals.
<h3>Object</h3>
<p>
Move all cards to the foundations.
<h3>Quick Description</h3>
<p>
Just like <a href="cruel.html">Cruel</a>,
but the number of cards you can move as a sequence is not restricted.

View file

@ -0,0 +1,13 @@
<h1>Wasp II</h1>
<p>
Spider type. 1 deck. No redeal.
<h3>Object</h3>
<p>
Move all cards to the foundations.
<h3>Quick Description</h3>
<p>
Like <a href="wasp.html">Wasp</a>,
but only three of the tableau piles contain
face-down cards (similar to <a href="scorpionii.html">Scorpion II</a>).

View file

@ -2653,7 +2653,7 @@ msgid "Automatic play"
msgstr "Automatisierung" msgstr "Automatisierung"
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
msgid "Auto face up" msgid "Auto face-up"
msgstr "Automatisch aufdecken" msgstr "Automatisch aufdecken"
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -5174,7 +5174,7 @@ msgstr "Kommentare..."
msgid "Log..." msgid "Log..."
msgstr "" msgstr ""
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -2694,7 +2694,7 @@ msgid "Automatic play"
msgstr "Jouer auto" msgstr "Jouer auto"
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
msgid "Auto face up" msgid "Auto face-up"
msgstr "Retourner auto" msgstr "Retourner auto"
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -5225,7 +5225,7 @@ msgstr "&Commentaires..."
msgid "Log..." msgid "Log..."
msgstr "Journal..." msgstr "Journal..."
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -2707,7 +2707,7 @@ msgstr "Gioco automatico"
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
#, fuzzy #, fuzzy
msgid "Auto face up" msgid "Auto face-up"
msgstr "Auto &scopri" msgstr "Auto &scopri"
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -5291,7 +5291,7 @@ msgstr "&Commenti..."
msgid "Log..." msgid "Log..."
msgstr "Log..." msgstr "Log..."
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -2707,7 +2707,7 @@ msgid "Automatic play"
msgstr "Gra &automatyczna" msgstr "Gra &automatyczna"
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
msgid "Auto face up" msgid "Auto face-up"
msgstr "Odkrywaj automatycznie" msgstr "Odkrywaj automatycznie"
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -5245,7 +5245,7 @@ msgstr "Komentarze..."
msgid "Log..." msgid "Log..."
msgstr "Log..." msgstr "Log..."
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -2715,7 +2715,7 @@ msgid "Automatic play"
msgstr "Jogar automaticamente" msgstr "Jogar automaticamente"
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
msgid "Auto face up" msgid "Auto face-up"
msgstr "Virar pra cima automaticamente" msgstr "Virar pra cima automaticamente"
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -5247,7 +5247,7 @@ msgstr "&Comentários..."
msgid "Log..." msgid "Log..."
msgstr "Registro" msgstr "Registro"
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -2524,7 +2524,7 @@ msgid "Automatic play"
msgstr "" msgstr ""
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
msgid "Auto face up" msgid "Auto face-up"
msgstr "" msgstr ""
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -4977,7 +4977,7 @@ msgstr ""
msgid "Log..." msgid "Log..."
msgstr "" msgstr ""
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -2713,7 +2713,7 @@ msgstr "Настройки &автоматической игры"
#: pysollib/kivy/menubar.py:513 #: pysollib/kivy/menubar.py:513
#, fuzzy #, fuzzy
msgid "Auto face up" msgid "Auto face-up"
msgstr "Автоматически &переворачивать" msgstr "Автоматически &переворачивать"
#: pysollib/kivy/menubar.py:523 #: pysollib/kivy/menubar.py:523
@ -5314,7 +5314,7 @@ msgstr "&Комментарии..."
msgid "Log..." msgid "Log..."
msgstr "Лог..." msgstr "Лог..."
msgid "Demo Log..." msgid "Demo log..."
msgstr "" msgstr ""
#: pysollib/ui/tktile/menubar.py:427 #: pysollib/ui/tktile/menubar.py:427

View file

@ -112,7 +112,7 @@ class GI:
# extra flags # extra flags
GT_BETA = 1 << 12 # beta version of game driver GT_BETA = 1 << 12 # beta version of game driver
GT_CHILDREN = 1 << 13 # *not used* GT_CHILDREN = 1 << 13
GT_CONTRIB = 1 << 14 # contributed games under the GNU GPL GT_CONTRIB = 1 << 14 # contributed games under the GNU GPL
GT_HIDDEN = 1 << 15 # not visible in menus, but games can be loaded GT_HIDDEN = 1 << 15 # not visible in menus, but games can be loaded
GT_OPEN = 1 << 16 GT_OPEN = 1 << 16
@ -320,7 +320,6 @@ class GI:
904: 68, # Lexington Harp 904: 68, # Lexington Harp
237: 22231, # Three Peaks 237: 22231, # Three Peaks
297: 631, # Alternation/Alternations 297: 631, # Alternation/Alternations
526: 447, # Australian/Outback Patience
640: 566, # Hypotenuse/Brazilian Patience 640: 566, # Hypotenuse/Brazilian Patience
# Lost Mahjongg Layouts # Lost Mahjongg Layouts
@ -368,12 +367,12 @@ class GI:
# Hamilton, Labyrinth, Treize, Wall # Hamilton, Labyrinth, Treize, Wall
("Gnome AisleRiot", ( ("Gnome AisleRiot", (
1, 2, 8, 9, 11, 12, 13, 19, 24, 27, 29, 31, 33, 34, 35, 36, 1, 2, 8, 9, 11, 12, 13, 19, 24, 27, 29, 31, 33, 34, 35, 36,
38, 40, 41, 42, 43, 45, 48, 58, 65, 67, 89, 91, 92, 93, 94, 38, 40, 41, 42, 43, 44, 45, 48, 58, 65, 67, 89, 91, 92, 93,
95, 96, 97, 100, 104, 105, 111, 112, 113, 130, 135, 139, 144, 94, 95, 96, 97, 100, 104, 105, 111, 112, 113, 130, 135, 139,
146, 147, 148, 200, 201, 206, 224, 225, 229, 230, 233, 257, 144, 146, 147, 148, 200, 201, 206, 224, 225, 229, 230, 233,
258, 277, 280, 281, 282, 283, 284, 334, 384, 479, 495, 551, 257, 258, 277, 280, 281, 282, 283, 284, 334, 384, 479, 495,
552, 553, 572, 593, 674, 700, 715, 716, 737, 772, 810, 819, 551, 552, 553, 572, 593, 674, 700, 715, 716, 737, 772, 810,
824, 829, 859, 874, 906, 934, 22231, 819, 824, 829, 859, 874, 906, 934, 22231,
)), )),
# Hoyle Card Games # Hoyle Card Games
@ -575,7 +574,7 @@ class GI:
('fc-0.9.0', tuple(range(323, 421))), ('fc-0.9.0', tuple(range(323, 421))),
('fc-0.9.1', tuple(range(421, 441))), ('fc-0.9.1', tuple(range(421, 441))),
('fc-0.9.2', tuple(range(441, 466))), ('fc-0.9.2', tuple(range(441, 466))),
('fc-0.9.3', tuple(range(466, 661))), ('fc-0.9.3', tuple(range(466, 526)) + tuple(range(527, 661))),
('fc-0.9.4', tuple(range(661, 671))), ('fc-0.9.4', tuple(range(661, 671))),
('fc-1.0', tuple(range(671, 711))), ('fc-1.0', tuple(range(671, 711))),
('fc-1.1', tuple(range(711, 759))), ('fc-1.1', tuple(range(711, 759))),
@ -595,7 +594,8 @@ class GI:
tuple(range(19000, 19012)) + tuple(range(22303, 22311)) + tuple(range(19000, 19012)) + tuple(range(22303, 22311)) +
tuple(range(22353, 22361))), tuple(range(22353, 22361))),
('fc-3.1', tuple(range(961, 971))), ('fc-3.1', tuple(range(961, 971))),
('dev', tuple(range(971, 973)) + tuple(range(18005, 18007))), ('dev', tuple(range(971, 978)) + tuple(range(18005, 18007)) +
(44, 526,)),
) )
# deprecated - the correct way is to or a GI.GT_XXX flag # deprecated - the correct way is to or a GI.GT_XXX flag

View file

@ -225,6 +225,7 @@ class Vineyard(CastlesInSpain):
# ************************************************************************ # ************************************************************************
# * Cruel # * Cruel
# * Relaxed Cruel
# * Unusual # * Unusual
# ************************************************************************ # ************************************************************************
@ -310,6 +311,10 @@ class Cruel(CastlesInSpain):
shallHighlightMatch = Game._shallHighlightMatch_SS shallHighlightMatch = Game._shallHighlightMatch_SS
class RelaxedCruel(Cruel):
RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK)
class Unusual(Cruel): class Unusual(Cruel):
def createGame(self): def createGame(self):
@ -453,3 +458,6 @@ registerGame(GameInfo(876, Vineyard, "Vineyard",
GI.SL_MOSTLY_SKILL)) GI.SL_MOSTLY_SKILL))
registerGame(GameInfo(907, Martha, "Stewart", registerGame(GameInfo(907, Martha, "Stewart",
GI.GT_BAKERS_DOZEN, 1, 0, GI.SL_BALANCED)) GI.GT_BAKERS_DOZEN, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(977, RelaxedCruel, "Relaxed Cruel",
GI.GT_BAKERS_DOZEN | GI.GT_OPEN | GI.GT_RELAXED, 1, -1,
GI.SL_BALANCED))

View file

@ -646,7 +646,8 @@ registerGame(GameInfo(376, Backbone, "Backbone",
registerGame(GameInfo(377, BackbonePlus, "Backbone +", registerGame(GameInfo(377, BackbonePlus, "Backbone +",
GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED)) GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(510, BigBraid, "Big Braid", registerGame(GameInfo(510, BigBraid, "Big Braid",
GI.GT_NAPOLEON | GI.GT_ORIGINAL, 3, 2, GI.SL_BALANCED)) GI.GT_NAPOLEON | GI.GT_ORIGINAL, 3, 2, GI.SL_BALANCED,
altnames=("Der grose Zopf",)))
registerGame(GameInfo(694, Casket, "Casket", registerGame(GameInfo(694, Casket, "Casket",
GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED)) GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(717, Well, "Well", registerGame(GameInfo(717, Well, "Well",

View file

@ -383,51 +383,18 @@ class PyramidDozen(Giza):
# ************************************************************************ # ************************************************************************
# * Thirteen # * Pyramid Thirteen
# * FIXME: UNFINISHED
# * (this doesn't work yet as 2 cards of the Waste should be playable)
# ************************************************************************ # ************************************************************************
# Previous comments suggest there would need to be two waste piles. This
# is not true. Based on AisleRiot's rules, the two waste cards can only
# be played with each other, which is the same as how PySol's traditional
# version works. So the remaining AisleRiot differences are captured
# in "Pyramid Thirteen", renamed from "Thirteen" to avoid confusion with
# "Thirteens"
class Thirteen(Pyramid): class Thirteen(Pyramid):
Talon_Class = StackWrapper(Pyramid_Talon, max_rounds=1, max_accept=1)
#
# game layout
#
def createGame(self):
# create layout
layout, s = Layout(self), self.s
# set window
self.setSize(7*layout.XS+layout.XM, 5*layout.YS+layout.YM)
# create stacks
for i in range(7):
x = layout.XM + (6-i) * layout.XS // 2
y = layout.YM + layout.YS + i * layout.YS // 2
for j in range(i+1):
s.rows.append(Pyramid_RowStack(x, y, self))
x = x + layout.XS
x, y = layout.XM, layout.YM
s.talon = WasteTalonStack(x, y, self, max_rounds=1)
layout.createText(s.talon, "s")
x = x + layout.XS
s.waste = Pyramid_Waste(x, y, self, max_accept=1)
layout.createText(s.waste, "s")
s.waste.CARD_XOFFSET = 14
x, y = self.width - layout.XS, layout.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): def startGame(self):
self.startDealSample() self.startDealSample()
@ -1791,6 +1758,8 @@ class Hurricane_Reserve(Hurricane_StackMethods, OpenStack):
class Hurricane(Pyramid): class Hurricane(Pyramid):
Hint_Class = Hurricane_Hint Hint_Class = Hurricane_Hint
RowStack_Class = Hurricane_RowStack
Reserve_Class = Hurricane_Reserve
def createGame(self): def createGame(self):
# create layout # create layout
@ -1808,7 +1777,7 @@ class Hurricane(Pyramid):
(0, 2), (1, 2), (2, 2), (3, 2), (0, 2), (1, 2), (2, 2), (3, 2),
): ):
x, y = layout.XM + 1.5*layout.XS + ww*xx, layout.YM + layout.YS*yy x, y = layout.XM + 1.5*layout.XS + ww*xx, layout.YM + layout.YS*yy
stack = Hurricane_Reserve(x, y, self, max_accept=1) stack = self.Reserve_Class(x, y, self, max_accept=1)
stack.CARD_XOFFSET, stack.CARD_YOFFSET = layout.XOFFSET, 0 stack.CARD_XOFFSET, stack.CARD_YOFFSET = layout.XOFFSET, 0
s.reserves.append(stack) s.reserves.append(stack)
@ -1816,7 +1785,7 @@ class Hurricane(Pyramid):
x = layout.XM + 1.5*layout.XS + layout.XS+2*layout.XOFFSET + d//2 x = layout.XM + 1.5*layout.XS + layout.XS+2*layout.XOFFSET + d//2
y = layout.YM+layout.YS y = layout.YM+layout.YS
for i in range(3): for i in range(3):
stack = Hurricane_RowStack(x, y, self, max_accept=1) stack = self.RowStack_Class(x, y, self, max_accept=1)
s.rows.append(stack) s.rows.append(stack)
x += layout.XS x += layout.XS
@ -1847,6 +1816,38 @@ class Hurricane(Pyramid):
self.leaveState(old_state) self.leaveState(old_state)
# ************************************************************************
# * Ides of March
# ************************************************************************
class IdesOfMarch_StackMethods(Pyramid_StackMethods):
def acceptsCards(self, from_stack, cards):
if from_stack is self:
return False
if len(cards) != 1:
return False
if not self.cards:
return False
c1 = self.cards[-1]
c2 = cards[0]
return (c1.face_up and c2.face_up and
(c1.rank + c2.rank == 13 or c1.rank + c2.rank == 0))
class IdesOfMarch_RowStack(IdesOfMarch_StackMethods, BasicRowStack):
pass
class IdesOfMarch_Reserve(IdesOfMarch_StackMethods, OpenStack):
pass
class IdesOfMarch(Hurricane):
RowStack_Class = IdesOfMarch_RowStack
Reserve_Class = IdesOfMarch_Reserve
# register the game # register the game
registerGame(GameInfo(38, Pyramid, "Pyramid", registerGame(GameInfo(38, Pyramid, "Pyramid",
GI.GT_PAIRING_TYPE, 1, 2, GI.SL_MOSTLY_LUCK)) GI.GT_PAIRING_TYPE, 1, 2, GI.SL_MOSTLY_LUCK))
@ -1854,8 +1855,8 @@ registerGame(GameInfo(193, RelaxedPyramid, "Relaxed Pyramid",
GI.GT_PAIRING_TYPE | GI.GT_RELAXED, 1, 2, GI.GT_PAIRING_TYPE | GI.GT_RELAXED, 1, 2,
GI.SL_MOSTLY_LUCK, GI.SL_MOSTLY_LUCK,
altnames=("Pyramid's Stones", "Pyramid Clear"))) altnames=("Pyramid's Stones", "Pyramid Clear")))
# registerGame(GameInfo(44, Thirteen, "Thirteen", registerGame(GameInfo(44, Thirteen, "Pyramid Thirteen",
# GI.GT_PAIRING_TYPE, 1, 0)) GI.GT_PAIRING_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(592, Giza, "Giza", registerGame(GameInfo(592, Giza, "Giza",
GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0, GI.SL_BALANCED)) GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(593, Thirteens, "Thirteens", registerGame(GameInfo(593, Thirteens, "Thirteens",
@ -1927,3 +1928,6 @@ registerGame(GameInfo(961, Nines, "Nines",
GI.GT_PAIRING_TYPE, 1, 0, GI.SL_LUCK)) GI.GT_PAIRING_TYPE, 1, 0, GI.SL_LUCK))
registerGame(GameInfo(969, ElevenTriangle, "Eleven Triangle", registerGame(GameInfo(969, ElevenTriangle, "Eleven Triangle",
GI.GT_PAIRING_TYPE, 1, 0, GI.SL_MOSTLY_LUCK)) GI.GT_PAIRING_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(974, IdesOfMarch, "Ides of March",
GI.GT_PAIRING_TYPE, 1, 0, GI.SL_MOSTLY_LUCK,
altnames=("XV",)))

View file

@ -291,7 +291,8 @@ class Maze(Game):
# register the game # register the game
registerGame(GameInfo(118, SiebenBisAs, "Sieben bis As", registerGame(GameInfo(118, SiebenBisAs, "Sieben bis As",
GI.GT_MONTANA | GI.GT_OPEN | GI.GT_STRIPPED, 1, 0, GI.GT_MONTANA | GI.GT_OPEN | GI.GT_STRIPPED, 1, 0,
GI.SL_MOSTLY_SKILL, ranks=(0, 6, 7, 8, 9, 10, 11, 12))) GI.SL_MOSTLY_SKILL, ranks=(0, 6, 7, 8, 9, 10, 11, 12),
altnames=("Seven to Ace",)))
registerGame(GameInfo(144, Maze, "Maze", registerGame(GameInfo(144, Maze, "Maze",
GI.GT_MONTANA | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL, GI.GT_MONTANA | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL,
si={"ncards": 48})) si={"ncards": 48}))

View file

@ -1266,10 +1266,12 @@ class Dashavatara(Game):
# * # *
# ***********************************************************************/ # ***********************************************************************/
def r(id, gameclass, name, game_type, decks, redeals, skill_level): def r(id, gameclass, name, game_type, decks, redeals, skill_level,
altnames=()):
game_type = game_type | GI.GT_DASHAVATARA_GANJIFA game_type = game_type | GI.GT_DASHAVATARA_GANJIFA
gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level, gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
suits=list(range(10)), ranks=list(range(12))) suits=list(range(10)), ranks=list(range(12)),
altnames=altnames)
registerGame(gi) registerGame(gi)
return gi return gi

View file

@ -1605,11 +1605,12 @@ class MagicMontana(Montana):
# ************************************************************************ # ************************************************************************
def r(id, gameclass, name, game_type, decks, redeals, skill_level): def r(id, gameclass, name, game_type, decks, redeals, skill_level,
altnames=()):
game_type = game_type | GI.GT_HEXADECK game_type = game_type | GI.GT_HEXADECK
gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level, gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
suits=list(range(4)), ranks=list(range(16)), suits=list(range(4)), ranks=list(range(16)),
trumps=list(range(4))) trumps=list(range(4)), altnames=altnames)
registerGame(gi) registerGame(gi)
return gi return gi
@ -1633,7 +1634,7 @@ r(16674, HiddenPassages, 'Hidden Passages', GI.GT_HEXADECK, 1, 1,
r(16675, CluitjarsLair, 'Cluitjar\'s Lair', GI.GT_HEXADECK, 1, 0, r(16675, CluitjarsLair, 'Cluitjar\'s Lair', GI.GT_HEXADECK, 1, 0,
GI.SL_BALANCED) GI.SL_BALANCED)
r(16676, MerlinsMeander, 'Merlin\'s Meander', GI.GT_HEXADECK, 2, 2, r(16676, MerlinsMeander, 'Merlin\'s Meander', GI.GT_HEXADECK, 2, 2,
GI.SL_BALANCED) GI.SL_BALANCED, altnames=('Merlin\'s Coil'))
r(16677, MagesGame, 'Mage\'s Game', GI.GT_HEXADECK | GI.GT_OPEN, 1, 0, r(16677, MagesGame, 'Mage\'s Game', GI.GT_HEXADECK | GI.GT_OPEN, 1, 0,
GI.SL_BALANCED) GI.SL_BALANCED)
r(16678, Convolution, 'Convolution', GI.GT_HEXADECK | GI.GT_OPEN, 2, 0, r(16678, Convolution, 'Convolution', GI.GT_HEXADECK | GI.GT_OPEN, 2, 0,

View file

@ -71,7 +71,7 @@ class Memory_RowStack(OpenStack):
def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1):
game = self.game game = self.game
game.playSample("droppair", priority=200) game.playSample("droppair", priority=200)
game.closed_cards = game.closed_cards - 2 game.closed_cards -= 2
game.score = game.score + 5 game.score = game.score + 5
rightclickHandler = clickHandler rightclickHandler = clickHandler
@ -108,6 +108,7 @@ class Memory24(Game):
# game extras # game extras
self.other_stack = None self.other_stack = None
self.other_stack2 = None
self.closed_cards = -1 self.closed_cards = -1
self.score = 0 self.score = 0
@ -255,7 +256,7 @@ class Concentration_RowStack(Memory_RowStack):
def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1): def _dropPairMove(self, n, other_stack, frames=-1, shadow=-1):
game = self.game game = self.game
game.playSample("droppair", priority=200) game.playSample("droppair", priority=200)
game.closed_cards = game.closed_cards - 2 game.closed_cards -= 2
game.score = game.score + 5 game.score = game.score + 5
# #
old_state = game.enterState(game.S_FILL) old_state = game.enterState(game.S_FILL)
@ -271,6 +272,8 @@ class Concentration(Memory24):
WIN_SCORE = 50 WIN_SCORE = 50
PERFECT_SCORE = 130 # 5 * (13*4)/2 PERFECT_SCORE = 130 # 5 * (13*4)/2
RowStack_Class = Concentration_RowStack
# #
# game layout # game layout
# #
@ -281,6 +284,7 @@ class Concentration(Memory24):
# game extras # game extras
self.other_stack = None self.other_stack = None
self.other_stack2 = None
self.closed_cards = -1 self.closed_cards = -1
self.score = 0 self.score = 0
@ -291,11 +295,12 @@ class Concentration(Memory24):
for i in range(self.ROWS): for i in range(self.ROWS):
for j in range(self.COLUMNS): for j in range(self.COLUMNS):
x, y = l.XM + j*l.XS, l.YM + i*l.YS x, y = l.XM + j*l.XS, l.YM + i*l.YS
s.rows.append(Concentration_RowStack(x, y, self, s.rows.append(self.RowStack_Class(x, y, self,
max_move=0, max_accept=0, max_cards=1)) max_move=0, max_accept=0, max_cards=1))
x, y = l.XM + self.COLUMNS*l.XS//2, self.height - l.YS x, y = l.XM + self.COLUMNS*l.XS//2, self.height - l.YS
s.talon = InitialDealTalonStack(x, y, self) s.talon = InitialDealTalonStack(x, y, self)
l.createText(s.talon, dx=-10, anchor="sw", text_format="%D") l.createText(s.talon, dx=-10, anchor="sw", text_format="%D")
s.internals.append(InvisibleStack(self))
# create text # create text
x, y = l.XM, self.height - l.YM x, y = l.XM, self.height - l.YM
@ -390,6 +395,151 @@ class MemorySequence(Memory24):
return card1.suit == card2.suit and card1.rank == card2.rank + 1 return card1.suit == card2.suit and card1.rank == card2.rank + 1
# ************************************************************************
# * Families
# ************************************************************************
class Families_RowStack(Memory_RowStack):
def clickHandler(self, event):
game = self.game
if (game.score == -1):
return 1
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
elif game.other_stack2 is None:
game.playSample("flip", priority=5)
self.flipMove()
game.other_stack2 = self
else:
assert len(game.other_stack.cards) == 1 and \
game.other_stack.cards[-1].face_up
c1, c2, c3 = self, game.other_stack, game.other_stack2
self.flipMove()
if not self.game.handleMatch(c1, c2, c3):
game.playSample("flip", priority=5)
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)
game.other_stack2.flipMove()
game.canvas.update_idletasks()
game.sleep(0.2)
self.flipMove()
game.other_stack = None
game.other_stack2 = None
self.game.finishMove()
self.game.checkForWin()
return 1
class Families(Concentration):
Hint_Class = None
COLUMNS = 8
ROWS = 4
RowStack_Class = Families_RowStack
def updateText(self):
pass
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
if game.loadinfo.other_stack2_id >= 0:
self.other_stack2 = self.allstacks[game.loadinfo.other_stack2_id]
else:
self.other_stack2 = None
self.closed_cards = game.loadinfo.closed_cards
self.score = game.loadinfo.score
def handleMatch(self, stack1, stack2, stack3):
card1 = stack1.cards[-1]
card2 = stack2.cards[-1]
card3 = stack3.cards[-1]
if (card1.suit == card2.suit and card2.suit == card3.suit and
card1.rank != card2.rank and card2.rank != card3.rank and
card1.rank != card3.rank):
self.playSample("droppair", priority=200)
self.closed_cards -= 3
#
old_state = self.enterState(self.S_FILL)
f = self.s.talon
self.moveMove(1, stack1, f)
self.moveMove(1, stack2, f)
self.moveMove(1, stack3, f)
self.leaveState(old_state)
return True
else:
redjokers = 0
blackjokers = 0
if card1.suit == 4 and card1.rank == 0:
blackjokers += 1
if card2.suit == 4 and card2.rank == 0:
blackjokers += 1
if card3.suit == 4 and card3.rank == 0:
blackjokers += 1
if card1.suit == 4 and card1.rank == 1:
redjokers += 1
if card2.suit == 4 and card2.rank == 1:
redjokers += 1
if card3.suit == 4 and card3.rank == 1:
redjokers += 1
if blackjokers > 1:
self.score = -1
return True
if redjokers > 0:
self.reshuffle()
def reshuffle(self):
old_state = self.enterState(self.S_FILL)
stacks = ()
for r in self.s.rows:
if r.cards and not r.cards[-1].face_up:
stacks += (r,)
self.moveMove(len(r.cards), r, self.s.internals[0],
frames=0)
self.shuffleStackMove(self.s.internals[0])
self.startDealSample()
for r in stacks:
self.moveMove(1, self.s.internals[0], r)
self.stopSamples()
self.leaveState(old_state)
def isGameWon(self):
return self.closed_cards == 8 and self.score > -1
def getStuck(self):
return self.score == -1
def _loadGameHook(self, p):
self.loadinfo.addattr(other_stack_id=p.load())
self.loadinfo.addattr(other_stack2_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)
if self.other_stack2:
p.dump(self.other_stack2.id)
else:
p.dump(-1)
p.dump(self.closed_cards)
p.dump(self.score)
# register the game # register the game
registerGame(GameInfo(886, Memory16, "Memory 16", registerGame(GameInfo(886, Memory16, "Memory 16",
GI.GT_MEMORY | GI.GT_SCORE | GI.GT_CHILDREN, 2, 0, GI.GT_MEMORY | GI.GT_SCORE | GI.GT_CHILDREN, 2, 0,
@ -417,3 +567,7 @@ registerGame(GameInfo(178, Concentration, "Concentration",
registerGame(GameInfo(843, MemorySequence, "Memory Sequence", registerGame(GameInfo(843, MemorySequence, "Memory Sequence",
GI.GT_MEMORY | GI.GT_SCORE, 1, 0, GI.SL_SKILL, GI.GT_MEMORY | GI.GT_SCORE, 1, 0, GI.SL_SKILL,
suits=(1,), altnames=('Ace Through King',))) suits=(1,), altnames=('Ace Through King',)))
registerGame(GameInfo(973, Families, "Families",
GI.GT_MEMORY, 2, 0, GI.SL_MOSTLY_SKILL,
ranks=(10, 11, 12), subcategory=GI.GS_JOKER_DECK,
trumps=(0, 0, 0, 1)))

View file

@ -1157,10 +1157,12 @@ class AshtaDikapala(Game):
# * # *
# ************************************************************************ # ************************************************************************
def r(id, gameclass, name, game_type, decks, redeals, skill_level): def r(id, gameclass, name, game_type, decks, redeals, skill_level,
altnames=()):
game_type = game_type | GI.GT_MUGHAL_GANJIFA game_type = game_type | GI.GT_MUGHAL_GANJIFA
gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level, gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
suits=list(range(8)), ranks=list(range(12))) suits=list(range(8)), ranks=list(range(12)),
altnames=altnames)
registerGame(gi) registerGame(gi)
return gi return gi
@ -1187,6 +1189,6 @@ r(16001, Danda, 'Danda', GI.GT_MUGHAL_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL)
r(16002, Khadga, 'Khadga', GI.GT_MUGHAL_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL) r(16002, Khadga, 'Khadga', GI.GT_MUGHAL_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL)
r(16003, Makara, 'Makara', GI.GT_MUGHAL_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL) r(16003, Makara, 'Makara', GI.GT_MUGHAL_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL)
r(16004, AshtaDikapala, 'Ashta Dikapala', GI.GT_MUGHAL_GANJIFA, 1, 0, r(16004, AshtaDikapala, 'Ashta Dikapala', GI.GT_MUGHAL_GANJIFA, 1, 0,
GI.SL_BALANCED) GI.SL_BALANCED, altnames=('Eight Guardians'))
del r del r

View file

@ -501,11 +501,11 @@ class TrumpsRow(Montana):
# ************************************************************************ # ************************************************************************
def r(id, gameclass, name, game_type, decks, redeals, skill_level, def r(id, gameclass, name, game_type, decks, redeals, skill_level,
numcards=78): numcards=78, altnames=()):
game_type = game_type | GI.GT_TAROCK | GI.GT_CONTRIB | GI.GT_ORIGINAL game_type = game_type | GI.GT_TAROCK | GI.GT_CONTRIB | GI.GT_ORIGINAL
gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level, gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
ranks=list(range(14)), trumps=list(range(22)), ranks=list(range(14)), trumps=list(range(22)),
si={"ncards": numcards}) altnames=altnames, si={"ncards": numcards})
registerGame(gi) registerGame(gi)
return gi return gi
@ -519,7 +519,8 @@ r(13166, Serpent, 'Serpent', GI.GT_TAROCK | GI.GT_OPEN, 2, 0,
GI.SL_MOSTLY_SKILL) GI.SL_MOSTLY_SKILL)
r(13167, Rambling, 'Rambling', GI.GT_TAROCK | GI.GT_OPEN, 2, 0, r(13167, Rambling, 'Rambling', GI.GT_TAROCK | GI.GT_OPEN, 2, 0,
GI.SL_MOSTLY_SKILL) GI.SL_MOSTLY_SKILL)
r(13168, FoolsUp, "Fool's Up", GI.GT_TAROCK, 1, 0, GI.SL_LUCK) r(13168, FoolsUp, "Fool's Up", GI.GT_TAROCK, 1, 0, GI.SL_LUCK,
altnames=('Solitairot'))
r(13169, TrumpsRow, "Trumps Row", GI.GT_TAROCK, 1, 4, GI.SL_MOSTLY_SKILL, r(13169, TrumpsRow, "Trumps Row", GI.GT_TAROCK, 1, 4, GI.SL_MOSTLY_SKILL,
numcards=73) numcards=73)
r(22232, LeGrandeTeton, 'Le Grande Teton', GI.GT_TAROCK, 1, 0, GI.SL_BALANCED) r(22232, LeGrandeTeton, 'Le Grande Teton', GI.GT_TAROCK, 1, 0, GI.SL_BALANCED)

View file

@ -348,15 +348,16 @@ class Scorpion_RowStack(Yukon_SS_RowStack, Spider_RowStack):
class Scorpion(RelaxedSpider): class Scorpion(RelaxedSpider):
Hint_Class = YukonType_Hint Hint_Class = YukonType_Hint
RowStack_Class = StackWrapper(Scorpion_RowStack, base_rank=KING) RowStack_Class = StackWrapper(Scorpion_RowStack, base_rank=KING)
FACEDOWNS = (4, 4, 4, 0, 0, 0)
def createGame(self): def createGame(self):
RelaxedSpider.createGame(self, rows=7, playcards=20) RelaxedSpider.createGame(self, rows=7, playcards=20)
def startGame(self): def startGame(self):
for i in (4, 4, 4, 0, 0, 0): for i in self.FACEDOWNS:
self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=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.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0)
self._startAndDealRow() self._startAndDealRow()
@ -379,46 +380,46 @@ class ScorpionTail(Scorpion):
shallHighlightMatch = Game._shallHighlightMatch_AC shallHighlightMatch = Game._shallHighlightMatch_AC
# ************************************************************************
# * Double Scorpion
# * Triple Scorpion
# ************************************************************************
class DoubleScorpion(Scorpion): class DoubleScorpion(Scorpion):
Talon_Class = InitialDealTalonStack FACEDOWNS = (5, 5, 5, 5, 0, 0, 0, 0, 0)
def createGame(self): def createGame(self):
RelaxedSpider.createGame(self, rows=10, playcards=26, texts=0) RelaxedSpider.createGame(self, rows=10, playcards=26, texts=0)
def startGame(self):
for i in (5, 5, 5, 5, 0, 0, 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()
self.s.talon.dealRowAvail()
class TripleScorpion(Scorpion): class TripleScorpion(Scorpion):
Talon_Class = InitialDealTalonStack Talon_Class = InitialDealTalonStack
FACEDOWNS = (5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0)
def createGame(self): def createGame(self):
RelaxedSpider.createGame(self, rows=13, playcards=30, texts=0) RelaxedSpider.createGame(self, rows=13, playcards=30, texts=0)
def startGame(self):
for i in (5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0): # ************************************************************************
self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0) # * Scorpion II
self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0) # ************************************************************************
self._startAndDealRow()
class ScorpionII(Scorpion):
FACEDOWNS = (3, 3, 3, 0, 0, 0)
# ************************************************************************ # ************************************************************************
# * Wasp # * Wasp
# * Wasp II
# ************************************************************************ # ************************************************************************
class Wasp(Scorpion): class Wasp(Scorpion):
RowStack_Class = Scorpion_RowStack # anything on an empty space RowStack_Class = Scorpion_RowStack # anything on an empty space
def startGame(self):
for i in (3, 3, 3, 0, 0, 0): class WaspII(ScorpionII):
self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0) RowStack_Class = Scorpion_RowStack
self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0)
self._startAndDealRow()
# ************************************************************************ # ************************************************************************
@ -1124,19 +1125,6 @@ class Incompatibility(Spidike):
self._startDealNumRowsAndDealSingleRow(4) self._startDealNumRowsAndDealSingleRow(4)
# ************************************************************************
# * Scorpion II
# ************************************************************************
class ScorpionII(Scorpion):
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._startAndDealRow()
# ************************************************************************ # ************************************************************************
# * Tarantula # * Tarantula
# ************************************************************************ # ************************************************************************
@ -1671,3 +1659,5 @@ registerGame(GameInfo(917, Astrocyte, "Astrocyte",
registerGame(GameInfo(971, Microbe, "Microbe", registerGame(GameInfo(971, Microbe, "Microbe",
GI.GT_SPIDER | GI.GT_SEPARATE_DECKS, 2, 0, GI.GT_SPIDER | GI.GT_SEPARATE_DECKS, 2, 0,
GI.SL_MOSTLY_SKILL)) GI.SL_MOSTLY_SKILL))
registerGame(GameInfo(976, WaspII, "Wasp II",
GI.GT_SPIDER, 1, 0, GI.SL_MOSTLY_SKILL))

View file

@ -51,10 +51,18 @@ class StHelena_Talon(TalonStack):
# move all cards to the Talon and redeal # move all cards to the Talon and redeal
lr = len(self.game.s.rows) lr = len(self.game.s.rows)
num_cards = 0 num_cards = 0
assert len(self.cards) == 0 if len(self.cards) > 0:
num_cards = len(self.cards)
self.game.startDealSample()
for i in range(lr):
k = min(lr, len(self.cards))
for j in range(k):
self.game.flipAndMoveMove(self, self.game.s.rows[j], 4)
self.game.stopSamples()
return num_cards
for r in self.game.s.rows[::-1]: for r in self.game.s.rows[::-1]:
for i in range(len(r.cards)): for i in range(len(r.cards)):
num_cards = num_cards + 1 num_cards += 1
self.game.moveMove(1, r, self, frames=0) self.game.moveMove(1, r, self, frames=0)
assert len(self.cards) == num_cards assert len(self.cards) == num_cards
if num_cards == 0: # game already finished if num_cards == 0: # game already finished
@ -169,6 +177,32 @@ class BoxKite(StHelena):
shallHighlightMatch = Game._shallHighlightMatch_RKW shallHighlightMatch = Game._shallHighlightMatch_RKW
# ************************************************************************
# * Louis
# ************************************************************************
class Louis(StHelena):
Foundation_Class = SS_FoundationStack
RowStack_Class = StackWrapper(UD_SS_RowStack, base_rank=NO_RANK, mod=13)
shallHighlightMatch = Game._shallHighlightMatch_RKW
def startGame(self):
self.startDealSample()
self.s.talon.dealRow(self.s.foundations)
self.s.talon.dealRow()
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 (self.s.talon.cards and stack in self.s.rows
and len(stack.cards) == 0):
self.s.talon.dealRow(rows=[stack])
# ************************************************************************ # ************************************************************************
# * Les Quatre Coins # * Les Quatre Coins
@ -452,3 +486,5 @@ registerGame(GameInfo(621, RegalFamily, "Regal Family",
registerGame(GameInfo(859, KingsAudience, "King's Audience", registerGame(GameInfo(859, KingsAudience, "King's Audience",
GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK, GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK,
altnames=("Queen's Audience"))) altnames=("Queen's Audience")))
registerGame(GameInfo(975, Louis, "Louis",
GI.GT_2DECK_TYPE, 2, 2, GI.SL_BALANCED))

View file

@ -472,6 +472,7 @@ class Panopticon(TenAcross):
# ************************************************************************ # ************************************************************************
# * Australian Patience # * Australian Patience
# * Outback Patience
# * Tasmanian Patience # * Tasmanian Patience
# * Canberra # * Canberra
# * Raw Prawn # * Raw Prawn
@ -482,9 +483,9 @@ class AustralianPatience(RussianSolitaire):
RowStack_Class = StackWrapper(Yukon_SS_RowStack, base_rank=KING) RowStack_Class = StackWrapper(Yukon_SS_RowStack, base_rank=KING)
def createGame(self, rows=7, max_rounds=1, num_deal=1): def createGame(self, rows=7, max_rounds=1, num_deal=1, playcards=16):
l, s = Layout(self), self.s l, s = Layout(self), self.s
Layout.klondikeLayout(l, rows=rows, waste=1) Layout.klondikeLayout(l, rows=rows, waste=1, playcards=playcards)
self.setSize(l.size[0], l.size[1]) self.setSize(l.size[0], l.size[1])
s.talon = WasteTalonStack(l.s.talon.x, l.s.talon.y, self, s.talon = WasteTalonStack(l.s.talon.x, l.s.talon.y, self,
max_rounds=max_rounds, num_deal=num_deal) max_rounds=max_rounds, num_deal=num_deal)
@ -500,6 +501,14 @@ class AustralianPatience(RussianSolitaire):
self._startDealNumRowsAndDealRowAndCards(3) self._startDealNumRowsAndDealRowAndCards(3)
class OutbackPatience(AustralianPatience):
def createGame(self):
AustralianPatience.createGame(self, rows=8, playcards=25)
def startGame(self):
self._startDealNumRowsAndDealRowAndCards(6)
class TasmanianPatience(AustralianPatience): class TasmanianPatience(AustralianPatience):
def createGame(self): def createGame(self):
AustralianPatience.createGame(self, max_rounds=-1, num_deal=3) AustralianPatience.createGame(self, max_rounds=-1, num_deal=3)
@ -895,8 +904,7 @@ registerGame(GameInfo(387, Roslin, "Roslin",
GI.GT_YUKON, 1, 0, GI.SL_MOSTLY_SKILL, GI.GT_YUKON, 1, 0, GI.SL_MOSTLY_SKILL,
altnames=("Roslyn",))) altnames=("Roslyn",)))
registerGame(GameInfo(447, AustralianPatience, "Australian Patience", registerGame(GameInfo(447, AustralianPatience, "Australian Patience",
GI.GT_YUKON, 1, 0, GI.SL_BALANCED, GI.GT_YUKON, 1, 0, GI.SL_BALANCED))
altnames=('Outback Patience',)))
registerGame(GameInfo(450, RawPrawn, "Raw Prawn", registerGame(GameInfo(450, RawPrawn, "Raw Prawn",
GI.GT_YUKON, 1, 0, GI.SL_BALANCED)) GI.GT_YUKON, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(456, BimBom, "Bim Bom", registerGame(GameInfo(456, BimBom, "Bim Bom",
@ -909,6 +917,8 @@ registerGame(GameInfo(492, Geoffrey, "Geoffrey",
GI.GT_YUKON, 1, 0, GI.SL_MOSTLY_SKILL)) GI.GT_YUKON, 1, 0, GI.SL_MOSTLY_SKILL))
registerGame(GameInfo(525, Queensland, "Queensland", registerGame(GameInfo(525, Queensland, "Queensland",
GI.GT_YUKON, 1, 0, GI.SL_BALANCED)) GI.GT_YUKON, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(526, OutbackPatience, "Outback Patience",
GI.GT_YUKON, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(530, RussianSpider, "Russian Spider", registerGame(GameInfo(530, RussianSpider, "Russian Spider",
GI.GT_SPIDER, 1, 0, GI.SL_BALANCED, GI.GT_SPIDER, 1, 0, GI.SL_BALANCED,
altnames=('Ukrainian Solitaire',))) altnames=('Ukrainian Solitaire',)))

View file

@ -26,6 +26,8 @@ import os
import re import re
import subprocess import subprocess
import time import time
from collections import OrderedDict
from io import BytesIO from io import BytesIO
from pysollib.mfxutil import destruct from pysollib.mfxutil import destruct
@ -996,6 +998,72 @@ class FreeCellSolver_Hint(Base_Solver_Hint):
-1 -1
) )
def calcBoardXML(self):
from io import StringIO
from xml.sax.saxutils import XMLGenerator
game = self.game
self.board = ''
is_simple_simon = self._isSimpleSimon()
b = []
for s in game.s.foundations:
if s.cards:
b.append(s.cards[0 if is_simple_simon else -1])
assert len(b) == 0
b = []
for s in game.s.reserves:
b.append((s.cards[-1]) if s.cards else None)
assert all(x is None for x in b)
nextid = 1
ids = {}
out = StringIO("")
xmler = XMLGenerator(out=out, encoding='UTF-8')
xmler.startDocument()
xmler.startElement(name='state', attrs={})
for row_idx, s in enumerate(game.s.rows):
moveattrs = []
moveattrs.append(('pile', 'store{}'.format(row_idx)))
moveattrs.append(('position', '{}'.format(0)))
moveattrs.sort()
dmoveattrs = OrderedDict(moveattrs)
xmler.startElement(name='move', attrs=dmoveattrs)
b = []
for c in s.cards:
cardattrs = []
cs = self.card2str1(c)
if cs in ids:
this_id = ids[cs]
else:
this_id = nextid
nextid += 1
ids[cs] = this_id
cardattrs.append(('id', '{}'.format(this_id)))
this_rank = [
"ace", "two", "three", "four", "five", "six", "seven",
"eight", "nine", "ten", "jack", "queen", "king"
][c.rank]
cardattrs.append(('rank', '{}'.format(this_rank)))
this_suit = [
"clubs", "spades", "hearts", "diamonds",
][c.suit]
cardattrs.append(('suit', '{}'.format(this_suit)))
cardattrs.append(('turn', '{}'.format("face-up")))
cardattrs.sort()
dcardattrs = OrderedDict(cardattrs)
xmler.startElement(name='card', attrs=dcardattrs)
if not c.face_up:
cs = '<%s>' % cs
xmler.endElement(name='card')
xmler.endElement(name='move')
xmler.endElement(name='state')
xmler.endDocument()
ret = out.getvalue()
return ret
def calcBoardString(self): def calcBoardString(self):
game = self.game game = self.game
self.board = '' self.board = ''

View file

@ -571,7 +571,7 @@ class LOptionsMenuGenerator(LTreeGenerator):
LTreeNode(text=_('Automatic play'))) LTreeNode(text=_('Automatic play')))
if rg: if rg:
self.addCheckNode(tv, rg, self.addCheckNode(tv, rg,
_('Auto face up'), _('Auto face-up'),
self.menubar.tkopt.autofaceup, self.menubar.tkopt.autofaceup,
self.menubar.mOptAutoFaceUp) self.menubar.mOptAutoFaceUp)

View file

@ -456,7 +456,7 @@ class AShuffleStackMove(AtomicMove):
def redo(self, game): def redo(self, game):
stack = game.allstacks[self.stack_id] stack = game.allstacks[self.stack_id]
# paranoia # paranoia
assert stack is game.s.talon assert stack is game.s.talon or stack in game.s.internals
# shuffle (see random) # shuffle (see random)
game.random.setstate(self.state) game.random.setstate(self.state)
seq = stack.cards seq = stack.cards

View file

@ -259,7 +259,7 @@ class PysolMenubarTk:
] ]
for label, action, opt_name, update_game in ( for label, action, opt_name, update_game in (
('A&uto drop', 'optautodrop', 'autodrop', False), ('A&uto drop', 'optautodrop', 'autodrop', False),
('Auto &face up', '', 'autofaceup', False), ('Auto &face-up', '', 'autofaceup', False),
('Auto &deal', '', 'autodeal', False), ('Auto &deal', '', 'autodeal', False),
('&Quick play', '', 'quickplay', False), ('&Quick play', '', 'quickplay', False),
('Enable &undo', '', 'undo', False), ('Enable &undo', '', 'undo', False),

View file

@ -353,7 +353,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
check = ttk.Checkbutton( check = ttk.Checkbutton(
size_frame, text=_('Auto scaling'), size_frame, text=_('Auto scaling'),
variable=self.auto_scale, variable=self.auto_scale,
takefocus=False,
command=self._updateAutoScale command=self._updateAutoScale
) )
check.grid(row=5, column=0, columnspan=2, sticky='ew', check.grid(row=5, column=0, columnspan=2, sticky='ew',
@ -364,7 +363,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
self.aspect_check = ttk.Checkbutton( self.aspect_check = ttk.Checkbutton(
size_frame, text=_('Preserve aspect ratio'), size_frame, text=_('Preserve aspect ratio'),
variable=self.preserve_aspect, variable=self.preserve_aspect,
takefocus=False,
# command=self._updateScale # command=self._updateScale
) )
self.aspect_check.grid(row=6, column=0, sticky='ew', self.aspect_check.grid(row=6, column=0, sticky='ew',

View file

@ -109,7 +109,7 @@ class SoundOptionsDialog(MfxDialog):
ttk.Label(frame, text=_('Sample volume:'), anchor='w' ttk.Label(frame, text=_('Sample volume:'), anchor='w'
).grid(row=row, column=0, sticky='ew') ).grid(row=row, column=0, sticky='ew')
w = PysolScale(frame, from_=0, to=128, resolution=1, w = PysolScale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0, orient='horizontal',
length="3i", # label=_('Sample volume'), length="3i", # label=_('Sample volume'),
variable=self.sample_volume) variable=self.sample_volume)
w.grid(row=row, column=1, sticky='w', padx=5) w.grid(row=row, column=1, sticky='w', padx=5)
@ -122,7 +122,7 @@ class SoundOptionsDialog(MfxDialog):
ttk.Label(frame, text=_('Music volume:'), anchor='w' ttk.Label(frame, text=_('Music volume:'), anchor='w'
).grid(row=row, column=0, sticky='ew') ).grid(row=row, column=0, sticky='ew')
w = PysolScale(frame, from_=0, to=128, resolution=1, w = PysolScale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0, orient='horizontal',
length="3i", # label=_('Music volume'), length="3i", # label=_('Music volume'),
variable=self.music_volume) variable=self.music_volume)
w.grid(row=row, column=1, sticky='w', padx=5) w.grid(row=row, column=1, sticky='w', padx=5)

View file

@ -72,7 +72,7 @@ class TimeoutsDialog(MfxDialog):
row=row, column=0, sticky='we') row=row, column=0, sticky='we')
widget = PysolScale(lframe, from_=0.2, to=9.9, value=var.get(), widget = PysolScale(lframe, from_=0.2, to=9.9, value=var.get(),
resolution=0.1, orient='horizontal', resolution=0.1, orient='horizontal',
length="3i", variable=var, takefocus=0) length="3i", variable=var)
widget.grid(row=row, column=1) widget.grid(row=row, column=1)
row += 1 row += 1
# #

View file

@ -105,8 +105,7 @@ class WizardDialog(MfxDialog):
elif w.widget == 'check': elif w.widget == 'check':
if w.variable is None: if w.variable is None:
w.variable = tkinter.BooleanVar() w.variable = tkinter.BooleanVar()
ch = ttk.Checkbutton(frame, variable=w.variable, ch = ttk.Checkbutton(frame, variable=w.variable)
takefocus=False)
ch.grid(row=row, column=1, sticky='ew', padx=2, pady=2) ch.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
if w.current_value is None: if w.current_value is None:

View file

@ -271,7 +271,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
check = tkinter.Checkbutton( check = tkinter.Checkbutton(
left_frame, text=_('Auto scaling'), left_frame, text=_('Auto scaling'),
variable=self.auto_scale, variable=self.auto_scale,
takefocus=False,
command=self._updateAutoScale command=self._updateAutoScale
) )
check.grid(row=3, column=0, columnspan=2, sticky='w', check.grid(row=3, column=0, columnspan=2, sticky='w',
@ -282,7 +281,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
self.aspect_check = tkinter.Checkbutton( self.aspect_check = tkinter.Checkbutton(
left_frame, text=_('Preserve aspect ratio'), left_frame, text=_('Preserve aspect ratio'),
variable=self.preserve_aspect, variable=self.preserve_aspect,
takefocus=False,
# command=self._updateScale # command=self._updateScale
) )
self.aspect_check.grid(row=4, column=0, sticky='w', self.aspect_check.grid(row=4, column=0, sticky='w',

View file

@ -109,7 +109,7 @@ class SoundOptionsDialog(MfxDialog):
w = tkinter.Label(frame, text=_('Sample volume:')) w = tkinter.Label(frame, text=_('Sample volume:'))
w.grid(row=row, column=0, sticky='w', padx=5) w.grid(row=row, column=0, sticky='w', padx=5)
w = tkinter.Scale(frame, from_=0, to=128, resolution=1, w = tkinter.Scale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0, orient='horizontal',
length="3i", # label=_('Sample volume'), length="3i", # label=_('Sample volume'),
variable=self.sample_volume) variable=self.sample_volume)
w.grid(row=row, column=1, sticky='ew', padx=5) w.grid(row=row, column=1, sticky='ew', padx=5)
@ -117,7 +117,7 @@ class SoundOptionsDialog(MfxDialog):
w = tkinter.Label(frame, text=_('Music volume:')) w = tkinter.Label(frame, text=_('Music volume:'))
w.grid(row=row, column=0, sticky='w', padx=5) w.grid(row=row, column=0, sticky='w', padx=5)
w = tkinter.Scale(frame, from_=0, to=128, resolution=1, w = tkinter.Scale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0, orient='horizontal',
length="3i", # label=_('Music volume'), length="3i", # label=_('Music volume'),
variable=self.music_volume) variable=self.music_volume)
w.grid(row=row, column=1, sticky='ew', padx=5) w.grid(row=row, column=1, sticky='ew', padx=5)

View file

@ -71,7 +71,7 @@ class TimeoutsDialog(MfxDialog):
).grid(row=row, column=0, sticky='we') ).grid(row=row, column=0, sticky='we')
widget = tkinter.Scale(frame, from_=0.2, to=9.9, widget = tkinter.Scale(frame, from_=0.2, to=9.9,
resolution=0.1, orient='horizontal', resolution=0.1, orient='horizontal',
length="3i", variable=var, takefocus=0) length="3i", variable=var)
widget.grid(row=row, column=1) widget.grid(row=row, column=1)
row += 1 row += 1
# #

View file

@ -94,7 +94,7 @@ class WizardDialog(MfxDialog):
if w.variable is None: if w.variable is None:
w.variable = tkinter.BooleanVar() w.variable = tkinter.BooleanVar()
ch = tkinter.Checkbutton(frame, variable=w.variable, ch = tkinter.Checkbutton(frame, variable=w.variable,
takefocus=False, anchor='w') anchor='w')
ch.grid(row=row, column=1, sticky='ew', padx=2, pady=2) ch.grid(row=row, column=1, sticky='ew', padx=2, pady=2)
if w.current_value is None: if w.current_value is None:

View file

@ -614,14 +614,15 @@ class PysolMenubarTkCommon:
menu.add_command( menu.add_command(
label=n_("&Statistics..."), label=n_("&Statistics..."),
command=self.mPlayerStats, accelerator=m+"T") command=self.mPlayerStats, accelerator=m+"T")
menu.add_command(
label=n_("Log..."),
command=lambda: self.mPlayerStats(mode=103))
menu.add_separator()
menu.add_command( menu.add_command(
label=n_("D&emo statistics..."), label=n_("D&emo statistics..."),
command=lambda: self.mPlayerStats(mode=1101)) command=lambda: self.mPlayerStats(mode=1101))
menu.add_command( menu.add_command(
label=n_("Log..."), label=n_("Demo log..."),
command=lambda: self.mPlayerStats(mode=103))
menu.add_command(
label=n_("Demo Log..."),
command=lambda: self.mPlayerStats(mode=1103)) command=lambda: self.mPlayerStats(mode=1103))
menu.add_separator() menu.add_separator()
menu.add_command( menu.add_command(
@ -666,7 +667,7 @@ class PysolMenubarTkCommon:
command=self.mOptPlayerOptions, accelerator=m+'P') command=self.mOptPlayerOptions, accelerator=m+'P')
submenu = MfxMenu(menu, label=n_("&Automatic play")) submenu = MfxMenu(menu, label=n_("&Automatic play"))
submenu.add_checkbutton( submenu.add_checkbutton(
label=n_("Auto &face up"), variable=self.tkopt.autofaceup, label=n_("Auto &face-up"), variable=self.tkopt.autofaceup,
command=self.mOptAutoFaceUp) command=self.mOptAutoFaceUp)
submenu.add_checkbutton( submenu.add_checkbutton(
label=n_("A&uto drop"), variable=self.tkopt.autodrop, label=n_("A&uto drop"), variable=self.tkopt.autodrop,

View file

@ -50,6 +50,32 @@ class ImportFileTests(unittest.TestCase):
def _successful_import(self, fn, want_s, blurb): def _successful_import(self, fn, want_s, blurb):
self.assertEqual(self._calc_hint(fn).calcBoardString(), want_s, blurb) self.assertEqual(self._calc_hint(fn).calcBoardString(), want_s, blurb)
def _successful_import__XML_test(self, fn, expected_regex, blurb):
hint = self._calc_hint(fn)
xml_output = hint.calcBoardXML()
# import sys
# print(xml_output, file=sys.stderr)
self.assertRegex(
text=xml_output, expected_regex=expected_regex, msg=blurb)
def test_import_XML(self):
return self._successful_import__XML_test(
fn='tests/unit/data/with-10-for-rank.txt',
expected_regex=(
'''<state><move pile="store0" position="0">'''
'''<card id="[0-9]+" rank="four"'''
''' suit="clubs" turn="face-up"></card>'''
'''<card id="[0-9]+" rank="two"'''
''' suit="clubs" turn="face-up"></card>'''
'''<card id="[0-9]+" rank="nine"'''
''' suit="clubs" turn="face-up"></card>'''
'''<card id="[0-9]+" rank="eight"'''
''' suit="clubs" turn="face-up"></card>'''
'''<card id="[0-9]+" rank="queen"'''
''' suit="spades" turn="face-up"></card>'''
),
blurb='xml import worked')
def test_import(self): def test_import(self):
return self._successful_import('tests/unit/data/with-10-for-rank.txt', return self._successful_import('tests/unit/data/with-10-for-rank.txt',
'''FC: - - - - '''FC: - - - -