1
0
Fork 0
mirror of https://github.com/shlomif/PySolFC.git synced 2025-03-12 04:07:01 -04:00

Compare commits

...

5 commits

Author SHA1 Message Date
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
18 changed files with 262 additions and 27 deletions

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

@ -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

@ -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.

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
@ -594,7 +594,7 @@ 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)) + (526,)),
('dev', tuple(range(971, 975)) + tuple(range(18005, 18007)) + (526,)),
)
# deprecated - the correct way is to or a GI.GT_XXX flag

View file

@ -1791,6 +1791,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 +1810,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 +1818,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 +1849,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))
@ -1927,3 +1961,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

@ -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

@ -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

@ -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,12 +614,13 @@ class PysolMenubarTkCommon:
menu.add_command(
label=n_("&Statistics..."),
command=self.mPlayerStats, accelerator=m+"T")
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_separator()
menu.add_command(
label=n_("D&emo statistics..."),
command=lambda: self.mPlayerStats(mode=1101))
menu.add_command(
label=n_("Demo log..."),
command=lambda: self.mPlayerStats(mode=1103))