1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-03-12 04:07:01 -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 = [
('credits.html', 'PySol Credits'),
('credits_old.html', 'PySol Credits'),
('faq.html', 'PySol - FAQ'),
('ganjifa.html', 'PySol - General Ganjifa Card Rules'),
('general_rules.html', 'PySol - General Rules'),
('glossary.html', 'PySol - Glossary'),

View file

@ -323,6 +323,15 @@ the deck.</p>
Hearts, and Diamonds.</p>
</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>
<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).
</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>
<p>
<ul type="disc">

View file

@ -19,9 +19,10 @@
<li> <a href="cardset_customization.html">Cardset Customization</a>
<li> <a href="plugins.html">Plugins</a>
</ul>
<h2>Misc</h2>
<h2>About</h2>
<ul>
<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="license.html">PySol license terms</a>
<li> <a href="credits.html">PySol credits</a>

View file

@ -1,10 +1,9 @@
<h1>Report a Bug</h1>
<p>
Before you report a bug, please verify that you are running
the latest version of PySolFC, and also check the PySolFC site's
<a href="https://pysolfc.sourceforge.io/faq.html">FAQ page</a>,
and issues previously reported on GitHub, to ensure that the bug
was not previously reported.
the latest version of PySolFC. Also, check the
<a href="faq.html">FAQ</a>, and issues previously reported
on GitHub, to ensure that the bug was not previously reported.
<p>
If you found a bug in PySolFC, the best place to report it
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>
<p>
Accordion's Revenge is unwinnable if the first or second card is
declared. As such, PySol will reselect the declared card if it
is chosen.
declared. As such, PySol will never select either of those cards as
the target.
<p>
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>
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>
<p>
Pairing game type. 1 deck. No redeal.
Pairing type. 1 deck. No redeal.
<h3>Object</h3>
<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"
#: pysollib/kivy/menubar.py:513
msgid "Auto face up"
msgid "Auto face-up"
msgstr "Automatisch aufdecken"
#: pysollib/kivy/menubar.py:523
@ -5174,7 +5174,7 @@ msgstr "Kommentare..."
msgid "Log..."
msgstr ""
msgid "Demo Log..."
msgid "Demo log..."
msgstr ""
#: pysollib/ui/tktile/menubar.py:427

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -112,7 +112,7 @@ class GI:
# extra flags
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_HIDDEN = 1 << 15 # not visible in menus, but games can be loaded
GT_OPEN = 1 << 16
@ -320,7 +320,6 @@ class GI:
904: 68, # Lexington Harp
237: 22231, # Three Peaks
297: 631, # Alternation/Alternations
526: 447, # Australian/Outback Patience
640: 566, # Hypotenuse/Brazilian Patience
# Lost Mahjongg Layouts
@ -368,12 +367,12 @@ class GI:
# Hamilton, Labyrinth, Treize, Wall
("Gnome AisleRiot", (
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,
95, 96, 97, 100, 104, 105, 111, 112, 113, 130, 135, 139, 144,
146, 147, 148, 200, 201, 206, 224, 225, 229, 230, 233, 257,
258, 277, 280, 281, 282, 283, 284, 334, 384, 479, 495, 551,
552, 553, 572, 593, 674, 700, 715, 716, 737, 772, 810, 819,
824, 829, 859, 874, 906, 934, 22231,
38, 40, 41, 42, 43, 44, 45, 48, 58, 65, 67, 89, 91, 92, 93,
94, 95, 96, 97, 100, 104, 105, 111, 112, 113, 130, 135, 139,
144, 146, 147, 148, 200, 201, 206, 224, 225, 229, 230, 233,
257, 258, 277, 280, 281, 282, 283, 284, 334, 384, 479, 495,
551, 552, 553, 572, 593, 674, 700, 715, 716, 737, 772, 810,
819, 824, 829, 859, 874, 906, 934, 22231,
)),
# Hoyle Card Games
@ -575,7 +574,7 @@ class GI:
('fc-0.9.0', tuple(range(323, 421))),
('fc-0.9.1', tuple(range(421, 441))),
('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-1.0', tuple(range(671, 711))),
('fc-1.1', tuple(range(711, 759))),
@ -595,7 +594,8 @@ class GI:
tuple(range(19000, 19012)) + tuple(range(22303, 22311)) +
tuple(range(22353, 22361))),
('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

View file

@ -225,6 +225,7 @@ class Vineyard(CastlesInSpain):
# ************************************************************************
# * Cruel
# * Relaxed Cruel
# * Unusual
# ************************************************************************
@ -310,6 +311,10 @@ class Cruel(CastlesInSpain):
shallHighlightMatch = Game._shallHighlightMatch_SS
class RelaxedCruel(Cruel):
RowStack_Class = StackWrapper(SS_RowStack, base_rank=NO_RANK)
class Unusual(Cruel):
def createGame(self):
@ -453,3 +458,6 @@ registerGame(GameInfo(876, Vineyard, "Vineyard",
GI.SL_MOSTLY_SKILL))
registerGame(GameInfo(907, Martha, "Stewart",
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 +",
GI.GT_NAPOLEON, 2, 0, GI.SL_BALANCED))
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",
GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
registerGame(GameInfo(717, Well, "Well",

View file

@ -383,51 +383,18 @@ class PyramidDozen(Giza):
# ************************************************************************
# * Thirteen
# * FIXME: UNFINISHED
# * (this doesn't work yet as 2 cards of the Waste should be playable)
# * Pyramid Thirteen
# ************************************************************************
# 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):
#
# 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
#
Talon_Class = StackWrapper(Pyramid_Talon, max_rounds=1, max_accept=1)
def startGame(self):
self.startDealSample()
@ -1791,6 +1758,8 @@ class Hurricane_Reserve(Hurricane_StackMethods, OpenStack):
class Hurricane(Pyramid):
Hint_Class = Hurricane_Hint
RowStack_Class = Hurricane_RowStack
Reserve_Class = Hurricane_Reserve
def createGame(self):
# create layout
@ -1808,7 +1777,7 @@ class Hurricane(Pyramid):
(0, 2), (1, 2), (2, 2), (3, 2),
):
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
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
y = layout.YM+layout.YS
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)
x += layout.XS
@ -1847,6 +1816,38 @@ class Hurricane(Pyramid):
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
registerGame(GameInfo(38, Pyramid, "Pyramid",
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.SL_MOSTLY_LUCK,
altnames=("Pyramid's Stones", "Pyramid Clear")))
# registerGame(GameInfo(44, Thirteen, "Thirteen",
# GI.GT_PAIRING_TYPE, 1, 0))
registerGame(GameInfo(44, Thirteen, "Pyramid Thirteen",
GI.GT_PAIRING_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
registerGame(GameInfo(592, Giza, "Giza",
GI.GT_PAIRING_TYPE | GI.GT_OPEN, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(593, Thirteens, "Thirteens",
@ -1927,3 +1928,6 @@ registerGame(GameInfo(961, Nines, "Nines",
GI.GT_PAIRING_TYPE, 1, 0, GI.SL_LUCK))
registerGame(GameInfo(969, ElevenTriangle, "Eleven Triangle",
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
registerGame(GameInfo(118, SiebenBisAs, "Sieben bis As",
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",
GI.GT_MONTANA | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL,
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
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)
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
gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
suits=list(range(4)), ranks=list(range(16)),
trumps=list(range(4)))
trumps=list(range(4)), altnames=altnames)
registerGame(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,
GI.SL_BALANCED)
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,
GI.SL_BALANCED)
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):
game = self.game
game.playSample("droppair", priority=200)
game.closed_cards = game.closed_cards - 2
game.closed_cards -= 2
game.score = game.score + 5
rightclickHandler = clickHandler
@ -108,6 +108,7 @@ class Memory24(Game):
# game extras
self.other_stack = None
self.other_stack2 = None
self.closed_cards = -1
self.score = 0
@ -255,7 +256,7 @@ 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.closed_cards -= 2
game.score = game.score + 5
#
old_state = game.enterState(game.S_FILL)
@ -271,6 +272,8 @@ class Concentration(Memory24):
WIN_SCORE = 50
PERFECT_SCORE = 130 # 5 * (13*4)/2
RowStack_Class = Concentration_RowStack
#
# game layout
#
@ -281,6 +284,7 @@ class Concentration(Memory24):
# game extras
self.other_stack = None
self.other_stack2 = None
self.closed_cards = -1
self.score = 0
@ -291,11 +295,12 @@ class Concentration(Memory24):
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,
s.rows.append(self.RowStack_Class(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")
s.internals.append(InvisibleStack(self))
# create text
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
# ************************************************************************
# * 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
registerGame(GameInfo(886, Memory16, "Memory 16",
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",
GI.GT_MEMORY | GI.GT_SCORE, 1, 0, GI.SL_SKILL,
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
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)
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(16003, Makara, 'Makara', GI.GT_MUGHAL_GANJIFA, 1, 0, GI.SL_MOSTLY_SKILL)
r(16004, AshtaDikapala, 'Ashta Dikapala', GI.GT_MUGHAL_GANJIFA, 1, 0,
GI.SL_BALANCED)
GI.SL_BALANCED, altnames=('Eight Guardians'))
del r

View file

@ -501,11 +501,11 @@ class TrumpsRow(Montana):
# ************************************************************************
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
gi = GameInfo(id, gameclass, name, game_type, decks, redeals, skill_level,
ranks=list(range(14)), trumps=list(range(22)),
si={"ncards": numcards})
altnames=altnames, si={"ncards": numcards})
registerGame(gi)
return gi
@ -519,7 +519,8 @@ r(13166, Serpent, 'Serpent', GI.GT_TAROCK | GI.GT_OPEN, 2, 0,
GI.SL_MOSTLY_SKILL)
r(13167, Rambling, 'Rambling', GI.GT_TAROCK | GI.GT_OPEN, 2, 0,
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,
numcards=73)
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):
Hint_Class = YukonType_Hint
RowStack_Class = StackWrapper(Scorpion_RowStack, base_rank=KING)
FACEDOWNS = (4, 4, 4, 0, 0, 0)
def createGame(self):
RelaxedSpider.createGame(self, rows=7, playcards=20)
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=1, frames=0)
self._startAndDealRow()
@ -379,46 +380,46 @@ class ScorpionTail(Scorpion):
shallHighlightMatch = Game._shallHighlightMatch_AC
# ************************************************************************
# * Double Scorpion
# * Triple Scorpion
# ************************************************************************
class DoubleScorpion(Scorpion):
Talon_Class = InitialDealTalonStack
FACEDOWNS = (5, 5, 5, 5, 0, 0, 0, 0, 0)
def createGame(self):
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):
Talon_Class = InitialDealTalonStack
FACEDOWNS = (5, 5, 5, 5, 5, 0, 0, 0, 0, 0, 0)
def createGame(self):
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)
self.s.talon.dealRow(rows=self.s.rows[i:], flip=1, frames=0)
self._startAndDealRow()
# ************************************************************************
# * Scorpion II
# ************************************************************************
class ScorpionII(Scorpion):
FACEDOWNS = (3, 3, 3, 0, 0, 0)
# ************************************************************************
# * Wasp
# * Wasp II
# ************************************************************************
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._startAndDealRow()
class WaspII(ScorpionII):
RowStack_Class = Scorpion_RowStack
# ************************************************************************
@ -1124,19 +1125,6 @@ class Incompatibility(Spidike):
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
# ************************************************************************
@ -1671,3 +1659,5 @@ registerGame(GameInfo(917, Astrocyte, "Astrocyte",
registerGame(GameInfo(971, Microbe, "Microbe",
GI.GT_SPIDER | GI.GT_SEPARATE_DECKS, 2, 0,
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
lr = len(self.game.s.rows)
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 i in range(len(r.cards)):
num_cards = num_cards + 1
num_cards += 1
self.game.moveMove(1, r, self, frames=0)
assert len(self.cards) == num_cards
if num_cards == 0: # game already finished
@ -169,6 +177,32 @@ class BoxKite(StHelena):
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
@ -452,3 +486,5 @@ registerGame(GameInfo(621, RegalFamily, "Regal Family",
registerGame(GameInfo(859, KingsAudience, "King's Audience",
GI.GT_1DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK,
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
# * Outback Patience
# * Tasmanian Patience
# * Canberra
# * Raw Prawn
@ -482,9 +483,9 @@ class AustralianPatience(RussianSolitaire):
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
Layout.klondikeLayout(l, rows=rows, waste=1)
Layout.klondikeLayout(l, rows=rows, waste=1, playcards=playcards)
self.setSize(l.size[0], l.size[1])
s.talon = WasteTalonStack(l.s.talon.x, l.s.talon.y, self,
max_rounds=max_rounds, num_deal=num_deal)
@ -500,6 +501,14 @@ class AustralianPatience(RussianSolitaire):
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):
def createGame(self):
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,
altnames=("Roslyn",)))
registerGame(GameInfo(447, AustralianPatience, "Australian Patience",
GI.GT_YUKON, 1, 0, GI.SL_BALANCED,
altnames=('Outback Patience',)))
GI.GT_YUKON, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(450, RawPrawn, "Raw Prawn",
GI.GT_YUKON, 1, 0, GI.SL_BALANCED))
registerGame(GameInfo(456, BimBom, "Bim Bom",
@ -909,6 +917,8 @@ registerGame(GameInfo(492, Geoffrey, "Geoffrey",
GI.GT_YUKON, 1, 0, GI.SL_MOSTLY_SKILL))
registerGame(GameInfo(525, Queensland, "Queensland",
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",
GI.GT_SPIDER, 1, 0, GI.SL_BALANCED,
altnames=('Ukrainian Solitaire',)))

View file

@ -26,6 +26,8 @@ import os
import re
import subprocess
import time
from collections import OrderedDict
from io import BytesIO
from pysollib.mfxutil import destruct
@ -996,6 +998,72 @@ class FreeCellSolver_Hint(Base_Solver_Hint):
-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):
game = self.game
self.board = ''

View file

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

View file

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

View file

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

View file

@ -353,7 +353,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
check = ttk.Checkbutton(
size_frame, text=_('Auto scaling'),
variable=self.auto_scale,
takefocus=False,
command=self._updateAutoScale
)
check.grid(row=5, column=0, columnspan=2, sticky='ew',
@ -364,7 +363,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
self.aspect_check = ttk.Checkbutton(
size_frame, text=_('Preserve aspect ratio'),
variable=self.preserve_aspect,
takefocus=False,
# command=self._updateScale
)
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'
).grid(row=row, column=0, sticky='ew')
w = PysolScale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0,
orient='horizontal',
length="3i", # label=_('Sample volume'),
variable=self.sample_volume)
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'
).grid(row=row, column=0, sticky='ew')
w = PysolScale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0,
orient='horizontal',
length="3i", # label=_('Music volume'),
variable=self.music_volume)
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')
widget = PysolScale(lframe, from_=0.2, to=9.9, value=var.get(),
resolution=0.1, orient='horizontal',
length="3i", variable=var, takefocus=0)
length="3i", variable=var)
widget.grid(row=row, column=1)
row += 1
#

View file

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

View file

@ -271,7 +271,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
check = tkinter.Checkbutton(
left_frame, text=_('Auto scaling'),
variable=self.auto_scale,
takefocus=False,
command=self._updateAutoScale
)
check.grid(row=3, column=0, columnspan=2, sticky='w',
@ -282,7 +281,6 @@ class SelectCardsetDialogWithPreview(MfxDialog):
self.aspect_check = tkinter.Checkbutton(
left_frame, text=_('Preserve aspect ratio'),
variable=self.preserve_aspect,
takefocus=False,
# command=self._updateScale
)
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.grid(row=row, column=0, sticky='w', padx=5)
w = tkinter.Scale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0,
orient='horizontal',
length="3i", # label=_('Sample volume'),
variable=self.sample_volume)
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.grid(row=row, column=0, sticky='w', padx=5)
w = tkinter.Scale(frame, from_=0, to=128, resolution=1,
orient='horizontal', takefocus=0,
orient='horizontal',
length="3i", # label=_('Music volume'),
variable=self.music_volume)
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')
widget = tkinter.Scale(frame, from_=0.2, to=9.9,
resolution=0.1, orient='horizontal',
length="3i", variable=var, takefocus=0)
length="3i", variable=var)
widget.grid(row=row, column=1)
row += 1
#

View file

@ -94,7 +94,7 @@ class WizardDialog(MfxDialog):
if w.variable is None:
w.variable = tkinter.BooleanVar()
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)
if w.current_value is None:

View file

@ -614,14 +614,15 @@ class PysolMenubarTkCommon:
menu.add_command(
label=n_("&Statistics..."),
command=self.mPlayerStats, accelerator=m+"T")
menu.add_command(
label=n_("Log..."),
command=lambda: self.mPlayerStats(mode=103))
menu.add_separator()
menu.add_command(
label=n_("D&emo statistics..."),
command=lambda: self.mPlayerStats(mode=1101))
menu.add_command(
label=n_("Log..."),
command=lambda: self.mPlayerStats(mode=103))
menu.add_command(
label=n_("Demo Log..."),
label=n_("Demo log..."),
command=lambda: self.mPlayerStats(mode=1103))
menu.add_separator()
menu.add_command(
@ -666,7 +667,7 @@ class PysolMenubarTkCommon:
command=self.mOptPlayerOptions, accelerator=m+'P')
submenu = MfxMenu(menu, label=n_("&Automatic play"))
submenu.add_checkbutton(
label=n_("Auto &face up"), variable=self.tkopt.autofaceup,
label=n_("Auto &face-up"), variable=self.tkopt.autofaceup,
command=self.mOptAutoFaceUp)
submenu.add_checkbutton(
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):
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):
return self._successful_import('tests/unit/data/with-10-for-rank.txt',
'''FC: - - - -