From 52ef64302b6ac7dc80a9b0dfde12b8fa5cddbdb9 Mon Sep 17 00:00:00 2001 From: skomoroh Date: Wed, 19 Jul 2006 21:17:01 +0000 Subject: [PATCH] + 4 new games + added `close stack' (Game.closeStackMove, Stack.closeStackMove, ACloseStackMove) + option `shade_filled_stacks' + new stack RedealTalonStack and new Game method redealCards * added closeStackMove to PileOn and PictureGallery (used flipAllMove) git-svn-id: file:///home/shlomif/Backup/svn-dumps/PySolFC/svnsync-repos/pysolfc/PySolFC/trunk@21 efabe8c0-fbe8-4139-b769-b5e6d273206e --- pysollib/acard.py | 7 ++ pysollib/actions.py | 8 ++ pysollib/app.py | 1 + pysollib/game.py | 27 +++++- pysollib/games/auldlangsyne.py | 2 +- pysollib/games/braid.py | 2 +- pysollib/games/curdsandwhey.py | 2 +- pysollib/games/dieboesesieben.py | 2 +- pysollib/games/fortythieves.py | 32 +++++++ pysollib/games/freecell.py | 15 ++++ pysollib/games/gypsy.py | 2 +- pysollib/games/klondike.py | 28 +++++- pysollib/games/montecarlo.py | 4 +- pysollib/games/picturegallery.py | 41 +++++++-- pysollib/games/pileon.py | 16 +++- pysollib/games/royalcotillion.py | 2 +- pysollib/games/siebenbisas.py | 2 +- pysollib/games/special/pegged.py | 2 +- pysollib/games/sultan.py | 29 +++--- pysollib/games/takeaway.py | 4 + pysollib/games/yukon.py | 23 +++-- pysollib/move.py | 26 +++++- pysollib/stack.py | 146 ++++++++++++++++++++++++++----- pysollib/tk/card.py | 3 + pysollib/tk/menubar.py | 5 +- pysollib/tk/tkcanvas.py | 15 +++- 26 files changed, 366 insertions(+), 80 deletions(-) diff --git a/pysollib/acard.py b/pysollib/acard.py index e23f09c7..a1569350 100644 --- a/pysollib/acard.py +++ b/pysollib/acard.py @@ -146,3 +146,10 @@ class AbstractCard: def updateCardBackground(self, image): raise SubclassResponsibility + + def close(self): + pass + + def unclose(self): + pass + diff --git a/pysollib/actions.py b/pysollib/actions.py index 59ba57d1..65280458 100644 --- a/pysollib/actions.py +++ b/pysollib/actions.py @@ -122,6 +122,7 @@ class PysolMenubarActions: animations = IntVar(), shadow = BooleanVar(), shade = BooleanVar(), + shade_filled_stacks = BooleanVar(), toolbar = IntVar(), toolbar_style = StringVar(), toolbar_relief = StringVar(), @@ -164,6 +165,7 @@ class PysolMenubarActions: tkopt.highlight_cards.set(opt.highlight_cards) tkopt.highlight_samerank.set(opt.highlight_samerank) tkopt.highlight_not_matching.set(opt.highlight_not_matching) + tkopt.shade_filled_stacks.set(opt.shade_filled_stacks) tkopt.mahjongg_show_removed.set(opt.mahjongg_show_removed) tkopt.shisen_show_hint.set(opt.shisen_show_hint) tkopt.sound.set(opt.sound) @@ -853,6 +855,12 @@ class PysolMenubarActions: self.app.opt.highlight_not_matching = self.tkopt.highlight_not_matching.get() ##self.game.updateMenus() + def mOptShadeFilledStacks(self, *args): + if self._cancelDrag(break_pause=False): return + self.app.opt.shade_filled_stacks = self.tkopt.shade_filled_stacks.get() + self.game.endGame(bookmark=1) + self.game.quitGame(bookmark=1) + def mOptMahjonggShowRemoved(self, *args): if self._cancelDrag(): return self.app.opt.mahjongg_show_removed = self.tkopt.mahjongg_show_removed.get() diff --git a/pysollib/app.py b/pysollib/app.py index e5d375da..3ca6b47f 100644 --- a/pysollib/app.py +++ b/pysollib/app.py @@ -102,6 +102,7 @@ class Options: self.animations = 2 # default to Timer based self.shadow = 1 self.shade = 1 + self.shade_filled_stacks = True self.demo_logo = 1 self.demo_score = 0 self.toolbar = 1 diff --git a/pysollib/game.py b/pysollib/game.py index c33f3812..0781aea5 100644 --- a/pysollib/game.py +++ b/pysollib/game.py @@ -62,6 +62,7 @@ from pysoltk import Card from move import AMoveMove, AFlipMove, ATurnStackMove from move import ANextRoundMove, ASaveSeedMove, AShuffleStackMove from move import AUpdateStackMove, AFlipAllMove, ASaveStateMove +from move import ACloseStackMove from hint import DefaultHint from help import helpAbout @@ -177,6 +178,12 @@ class Game: if self.s.talon: assert hasattr(self.s.talon, "round") assert hasattr(self.s.talon, "max_rounds") + if self.app.debug and self.s.foundations: + ncards = 0 + for stack in self.s.foundations: + ncards += stack.cap.max_cards + if ncards != self.gameinfo.ncards: + print 'WARNING: invalid sum of foundations.max_cards:', self.__class__.__name__, ncards, self.gameinfo.ncards # optimize regions self.optimizeRegions() # create cards @@ -987,6 +994,8 @@ class Game: def getCardBackImage(self, deck, suit, rank): return self.app.images.getBack(deck, suit, rank) + def getCardShadeImage(self): + return self.app.images.getShade() # # layout support @@ -1085,6 +1094,10 @@ class Game: def fillStack(self, stack): pass + # redeal cards (used in RedealTalonStack; all cards already in talon) + def redealCards(self): + pass + # the actual hint class (or None) Hint_Class = DefaultHint @@ -1931,7 +1944,7 @@ for %d moves. # move type 8 def flipAllMove(self, stack): assert stack - am = AFlipAllMove(self, stack) + am = AFlipAllMove(stack) self.__storeMove(am) am.do(self) self.hints.list = None @@ -1943,6 +1956,13 @@ for %d moves. am.do(self) ##self.hints.list = None + # move type 10 + def closeStackMove(self, stack): + assert stack + am = ACloseStackMove(stack) + self.__storeMove(am) + am.do(self) + # Finish the current move. def finishMove(self): @@ -2244,7 +2264,10 @@ Please report this bug.""")) if not game.canLoadGame(version_tuple, game_version): destruct(game) game = None - assert game is not None, "Cannot load this game from version " + version + "\nas the game rules have changed\nin the current implementation." + assert game is not None, '''\ +Cannot load this game from version %s +as the game rules have changed +in the current implementation.''' % version game.version = version game.version_tuple = version_tuple # diff --git a/pysollib/games/auldlangsyne.py b/pysollib/games/auldlangsyne.py index b1fa8bf3..fb3a92b2 100644 --- a/pysollib/games/auldlangsyne.py +++ b/pysollib/games/auldlangsyne.py @@ -422,7 +422,7 @@ class Amazons(Game): l.createText(s.talon, "ss") x, y = l.XM+2*l.XS, l.YM for i in range(4): - s.foundations.append(Amazons_Foundation(x, y, self, suit=i)) + s.foundations.append(Amazons_Foundation(x, y, self, suit=i, max_cards=7)) x += l.XS x, y = l.XM+2*l.XS, l.YM+l.YS for i in range(4): diff --git a/pysollib/games/braid.py b/pysollib/games/braid.py index d095f06b..98e0b19b 100644 --- a/pysollib/games/braid.py +++ b/pysollib/games/braid.py @@ -174,7 +174,7 @@ class Braid(Game): s.foundations.append(cl(x, y, self, suit=i)) x += l.XS y = y + l.YS - x = 8*l.XS+decks*l.XS/2 + x = 8*l.XS+decks*l.XS/2+l.XM/2 self.texts.info = MfxCanvasText(self.canvas, x, y, anchor="n", font=self.app.getFont("canvas_default")) diff --git a/pysollib/games/curdsandwhey.py b/pysollib/games/curdsandwhey.py index 7c3e09f1..2ddef268 100644 --- a/pysollib/games/curdsandwhey.py +++ b/pysollib/games/curdsandwhey.py @@ -241,7 +241,7 @@ class Arachnida(CurdsAndWhey): s.rows.append(stack) x += l.XS s.foundations.append(AbstractFoundationStack(x, y, self, suit=ANY_SUIT, - max_accept=0)) + max_accept=0, max_cards=104)) l.createText(s.foundations[0], "ss") # define stack-groups diff --git a/pysollib/games/dieboesesieben.py b/pysollib/games/dieboesesieben.py index 828c5ecd..1531fd22 100644 --- a/pysollib/games/dieboesesieben.py +++ b/pysollib/games/dieboesesieben.py @@ -98,7 +98,7 @@ class DieBoeseSieben(Game): # create stacks for i in range(8): x, y, = l.XM + i*l.XS, l.YM - s.foundations.append(DieRussische_Foundation(x, y, self, i/2, max_move=0)) + s.foundations.append(DieRussische_Foundation(x, y, self, i/2, max_move=0, max_cards=8)) for i in range(rows): x, y, = l.XM + (2*i+8-rows)*l.XS/2, l.YM + l.YS s.rows.append(AC_RowStack(x, y, self)) diff --git a/pysollib/games/fortythieves.py b/pysollib/games/fortythieves.py index 3e400d13..3b0c1dd4 100644 --- a/pysollib/games/fortythieves.py +++ b/pysollib/games/fortythieves.py @@ -768,6 +768,36 @@ class Squadron(FortyThieves): self.s.talon.dealCards() # deal first card to WasteStack +# /*********************************************************************** +# // Waterloo +# ************************************************************************/ + +class Waterloo(FortyThieves): + + RowStack_Class = Spider_SS_RowStack + + ROW_MAX_MOVE = UNLIMITED_MOVES + DEAL = (0, 1) + + def createGame(self): + FortyThieves.createGame(self, rows=6) + + def _shuffleHook(self, cards): + # move Aces to top of the Talon (i.e. first cards to be dealt) + return self._shuffleHookMoveToTop(cards, + lambda c: (c.rank == ACE, (c.deck, c.suit))) + + def startGame(self): + self.startDealSample() + self.s.talon.dealRow(rows=self.s.foundations) + self.s.talon.dealRow() + self.s.talon.dealCards() # deal first card to WasteStack + + def getQuickPlayScore(self, ncards, from_stack, to_stack): + if to_stack.cards: + return int(from_stack.cards[-1].suit == to_stack.cards[-1].suit)+1 + return 0 + # register the game registerGame(GameInfo(13, FortyThieves, "Forty Thieves", @@ -855,5 +885,7 @@ registerGame(GameInfo(528, FinalBattle, "Final Battle", GI.GT_FORTY_THIEVES, 2, 0, GI.SL_BALANCED)) registerGame(GameInfo(529, SanJuanHill, "San Juan Hill", GI.GT_FORTY_THIEVES, 2, 0, GI.SL_BALANCED)) +registerGame(GameInfo(540, Waterloo, "Waterloo", + GI.GT_FORTY_THIEVES, 2, 0, GI.SL_BALANCED)) diff --git a/pysollib/games/freecell.py b/pysollib/games/freecell.py index 56b1398a..ff0c81dd 100644 --- a/pysollib/games/freecell.py +++ b/pysollib/games/freecell.py @@ -543,6 +543,19 @@ class OceanTowers(TripleFreecell): return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1 +# /*********************************************************************** +# // KingCell +# ************************************************************************/ + +class KingCell_RowStack(RK_RowStack): + def canMoveCards(self, cards): + max_move = getNumberOfFreeStacks(self.game.s.reserves) + 1 + return len(cards) <= max_move and RK_RowStack.canMoveCards(self, cards) + +class KingCell(FreeCell): + Hint_Class = FreeCellType_Hint + RowStack_Class = StackWrapper(KingCell_RowStack, base_rank=KING) + # register the game registerGame(GameInfo(5, RelaxedFreeCell, "Relaxed FreeCell", @@ -585,4 +598,6 @@ registerGame(GameInfo(513, OceanTowers, "Ocean Towers", GI.GT_FREECELL | GI.GT_OPEN | GI.GT_ORIGINAL, 2, 0, GI.SL_MOSTLY_SKILL)) registerGame(GameInfo(520, GermanFreeCell, "German FreeCell", GI.GT_FREECELL | GI.GT_OPEN, 1, 0, GI.SL_SKILL)) +registerGame(GameInfo(542, KingCell, "KingCell", + GI.GT_FREECELL | GI.GT_OPEN, 1, 0, GI.SL_MOSTLY_SKILL)) diff --git a/pysollib/games/gypsy.py b/pysollib/games/gypsy.py index f2a3c5f0..f27f5485 100644 --- a/pysollib/games/gypsy.py +++ b/pysollib/games/gypsy.py @@ -188,7 +188,7 @@ class DieRussische_RowStack(AC_RowStack): class DieRussische(Gypsy): Talon_Class = InitialDealTalonStack - Foundation_Class = StackWrapper(DieRussische_Foundation, min_cards=1) + Foundation_Class = StackWrapper(DieRussische_Foundation, min_cards=1, max_cards=8) RowStack_Class = DieRussische_RowStack def createGame(self): diff --git a/pysollib/games/klondike.py b/pysollib/games/klondike.py index 874e1ebf..42496a89 100644 --- a/pysollib/games/klondike.py +++ b/pysollib/games/klondike.py @@ -323,6 +323,21 @@ class Canister(Klondike): self.s.talon.dealRow(rows=self.s.rows[2:6]) +class Usk(Somerset): + + Talon_Class = RedealTalonStack + RowStack_Class = StackWrapper(AC_RowStack, base_rank=KING) + + def createGame(self): + Klondike.createGame(self, max_rounds=2, rows=10, waste=0, texts=0) + + def redealCards(self): + n = 0 + while self.s.talon.cards: + self.s.talon.dealRowAvail(rows=self.s.rows[n:], frames=4) + n += 1 + + # /*********************************************************************** # // Agnes Sorel # ************************************************************************/ @@ -376,6 +391,7 @@ class AchtmalAcht(EightTimesEight): # /*********************************************************************** # // Batsford +# // Batsford Again # ************************************************************************/ class Batsford_ReserveStack(ReserveStack): @@ -389,7 +405,8 @@ class Batsford_ReserveStack(ReserveStack): class Batsford(Klondike): def createGame(self, **layout): - l = Klondike.createGame(self, rows=10, max_rounds=1, playcards=22) + kwdefault(layout, rows=10, max_rounds=1, playcards=22) + l = apply(Klondike.createGame, (self,), layout) s = self.s x, y = l.XM, self.height - l.YS s.reserves.append(Batsford_ReserveStack(x, y, self, max_cards=3)) @@ -398,6 +415,11 @@ class Batsford(Klondike): l.defaultStackGroups() +class BatsfordAgain(Batsford): + def createGame(self): + Batsford.createGame(self, max_rounds=2) + + # /*********************************************************************** # // Jumbo # ************************************************************************/ @@ -1199,4 +1221,8 @@ registerGame(GameInfo(522, ArticGarden, "Artic Garden", GI.GT_RAGLAN, 1, 0, GI.SL_MOSTLY_SKILL)) registerGame(GameInfo(532, GoldRush, "Gold Rush", GI.GT_KLONDIKE, 1, 2, GI.SL_BALANCED)) +registerGame(GameInfo(539, Usk, "Usk", + GI.GT_KLONDIKE, 1, 1, GI.SL_BALANCED)) +registerGame(GameInfo(541, BatsfordAgain, "Batsford Again", + GI.GT_KLONDIKE, 2, 1, GI.SL_BALANCED)) diff --git a/pysollib/games/montecarlo.py b/pysollib/games/montecarlo.py index f296c137..76588027 100644 --- a/pysollib/games/montecarlo.py +++ b/pysollib/games/montecarlo.py @@ -135,7 +135,7 @@ class MonteCarlo(Game): dir=0, base_rank=NO_RANK)) x, y = l.XM + 11*l.XS/2, l.YM s.foundations.append(self.Foundation_Class(x, y, self, suit=ANY_SUIT, - max_move=0, max_cards=52, base_rank=ANY_RANK)) + max_move=0, max_cards=self.gameinfo.ncards, base_rank=ANY_RANK)) l.createText(s.foundations[0], "ss") y = y + 2*l.YS s.talon = self.Talon_Class(x, y, self, max_rounds=1) @@ -598,7 +598,7 @@ class TheWish(Game): x, y = self.width - l.XS, self.height - l.YS s.foundations.append(AbstractFoundationStack(x, y, self, suit=ANY_SUIT, - max_move=0, max_cards=52, max_accept=0, base_rank=ANY_RANK)) + max_move=0, max_cards=32, max_accept=0, base_rank=ANY_RANK)) l.createText(s.foundations[0], "nn") # define stack-groups diff --git a/pysollib/games/picturegallery.py b/pysollib/games/picturegallery.py index d329ee9f..5766ea6d 100644 --- a/pysollib/games/picturegallery.py +++ b/pysollib/games/picturegallery.py @@ -137,10 +137,18 @@ class PictureGallery_Foundation(RK_FoundationStack): def getBottomImage(self): return self.game.app.images.getLetter(ACE) + def closeStackMove(self): + if len(self.cards) == 8: + self.game.flipAllMove(self) + + def canFlipCard(self): + return False + class PictureGallery_TableauStack(SS_RowStack): - def __init__(self, x, y, game, base_rank, yoffset, dir=3): - SS_RowStack.__init__(self, x, y, game, base_rank=base_rank, dir=dir, max_accept=1) + def __init__(self, x, y, game, base_rank, yoffset, dir=3, max_cards=4): + SS_RowStack.__init__(self, x, y, game, + base_rank=base_rank, dir=dir, max_cards=max_cards, max_accept=1) self.CARD_YOFFSET = yoffset def acceptsCards(self, from_stack, cards): @@ -154,6 +162,13 @@ class PictureGallery_TableauStack(SS_RowStack): def getBottomImage(self): return self.game.app.images.getLetter(self.cap.base_rank) + def closeStackMove(self): + if len(self.cards) == self.cap.max_cards: + self.game.flipAllMove(self) + + def canFlipCard(self): + return False + class PictureGallery_RowStack(BasicRowStack): def acceptsCards(self, from_stack, cards): @@ -176,7 +191,11 @@ class PictureGallery(Game): Hint_Class = PictureGallery_Hint Foundation_Class = PictureGallery_Foundation - TableauStack_Class = PictureGallery_TableauStack + TableauStack_Classes = [ + StackWrapper(PictureGallery_TableauStack, base_rank=3, max_cards=4, dir=3), + StackWrapper(PictureGallery_TableauStack, base_rank=2, max_cards=4, dir=3), + StackWrapper(PictureGallery_TableauStack, base_rank=1, max_cards=4, dir=3), + ] RowStack_Class = StackWrapper(PictureGallery_RowStack, max_accept=1) Talon_Class = DealRowTalonStack @@ -184,7 +203,8 @@ class PictureGallery(Game): # game layout # - def createGame(self, rows=3, waste=False, dir=3): + def createGame(self, waste=False): + rows = len(self.TableauStack_Classes) # create layout l, s = Layout(self), self.s TABLEAU_YOFFSET = min(9, max(3, l.YOFFSET / 3)) @@ -201,10 +221,10 @@ class PictureGallery(Game): y = l.YM + l.CH / 2 s.foundations.append(self.Foundation_Class(x, y, self)) y = l.YM - for i in range(rows,0,-1): #(3, 2, 1): + for cl in self.TableauStack_Classes: x = l.XM for j in range(8): - s.tableaux.append(self.TableauStack_Class(x, y, self, i, yoffset=TABLEAU_YOFFSET, dir=dir)) + s.tableaux.append(cl(x, y, self, yoffset=TABLEAU_YOFFSET)) x = x + l.XS y = y + th x, y = l.XM, y + l.YM @@ -297,7 +317,10 @@ class GreatWheel_RowStack(BasicRowStack): class GreatWheel(PictureGallery): Foundation_Class = GreatWheel_Foundation - TableauStack_Class = PictureGallery_TableauStack + TableauStack_Classes = [ + StackWrapper(PictureGallery_TableauStack, base_rank=2, max_cards=5, dir=2), + StackWrapper(PictureGallery_TableauStack, base_rank=1, max_cards=6, dir=2), + ] RowStack_Class = StackWrapper(GreatWheel_RowStack, max_accept=1) Talon_Class = StackWrapper(WasteTalonStack, max_rounds=1) @@ -373,12 +396,12 @@ class MountOlympus(Game): x, y = l.XM+l.XS, l.YM for i in range(8): s.foundations.append(MountOlympus_Foundation(x, y, self, - suit=i/2, base_rank=ACE, dir=2, max_move=0)) + suit=i/2, base_rank=ACE, dir=2, max_move=0, max_cards=7)) x += l.XS x, y = l.XM+l.XS, l.YM+l.YS for i in range(8): s.foundations.append(MountOlympus_Foundation(x, y, self, - suit=i/2, base_rank=1, dir=2, max_move=0)) + suit=i/2, base_rank=1, dir=2, max_move=0, max_cards=6)) x += l.XS x, y = l.XM, l.YM+2*l.YS for i in range(9): diff --git a/pysollib/games/pileon.py b/pysollib/games/pileon.py index e89202bc..4727a5ae 100644 --- a/pysollib/games/pileon.py +++ b/pysollib/games/pileon.py @@ -50,6 +50,13 @@ class PileOn_RowStack(RK_RowStack): def getBottomImage(self): return self.game.app.images.getReserveBottom() + def closeStackMove(self): + if len(self.cards) == 4 and isRankSequence(self.cards, dir=0): + self.game.flipAllMove(self) + + def canFlipCard(self): + return False + class PileOn(Game): Hint_Class = DefaultHint @@ -106,19 +113,20 @@ class PileOn(Game): def isGameWon(self): for r in self.s.rows: - if r.cards: - if len(r.cards) != 4 or not r._isSequence(r.cards): - return 0 - return 1 + if r.cards and not cardsFaceDown(r.cards): + return False + return True def shallHighlightMatch(self, stack1, card1, stack2, card2): return card1.rank == card2.rank + class SmallPileOn(PileOn): TWIDTH = 3 NSTACKS = 11 PLAYCARDS = 4 + class PileOn2Decks(PileOn): TWIDTH = 4 NSTACKS = 15 diff --git a/pysollib/games/royalcotillion.py b/pysollib/games/royalcotillion.py index a375db37..0f405275 100644 --- a/pysollib/games/royalcotillion.py +++ b/pysollib/games/royalcotillion.py @@ -413,7 +413,7 @@ class BritishConstitution(Game): # create stacks x, y = l.XM+l.XS, l.YM for i in range(8): - s.foundations.append(BritishConstitution_Foundation(x, y, self, suit=int(i/2))) + s.foundations.append(BritishConstitution_Foundation(x, y, self, suit=int(i/2), max_cards=11)) x += l.XS y = l.YM+l.YS diff --git a/pysollib/games/siebenbisas.py b/pysollib/games/siebenbisas.py index 088996f1..e2a7b6bb 100644 --- a/pysollib/games/siebenbisas.py +++ b/pysollib/games/siebenbisas.py @@ -136,7 +136,7 @@ class SiebenBisAs(Game): s.reserves.append(ReserveStack(x, y, self, max_accept=0)) for i in range(4): x, y, = l.XM + (i+3)*l.XS, l.YM + 4*l.YS - s.foundations.append(SiebenBisAs_Foundation(x, y, self, i, base_rank=6, mod=13, max_move=0)) + s.foundations.append(SiebenBisAs_Foundation(x, y, self, i, base_rank=6, mod=13, max_move=0, max_cards=8)) s.talon = InitialDealTalonStack(l.XM, self.height-l.YS, self) # define stack-groups diff --git a/pysollib/games/special/pegged.py b/pysollib/games/special/pegged.py index 376818e4..ecc75d91 100644 --- a/pysollib/games/special/pegged.py +++ b/pysollib/games/special/pegged.py @@ -141,7 +141,7 @@ class Pegged(Game): s.rows.append(stack) self.map[stack.pos] = stack x, y = self.width - l.XS, l.YM - s.foundations.append(AbstractFoundationStack(x, y, self, ANY_SUIT, max_move=0, max_accept=0)) + s.foundations.append(AbstractFoundationStack(x, y, self, ANY_SUIT, max_move=0, max_accept=0, max_cards=self.gameinfo.ncards)) l.createText(s.foundations[0], "ss") y = self.height - l.YS s.talon = InitialDealTalonStack(x, y, self) diff --git a/pysollib/games/sultan.py b/pysollib/games/sultan.py index 8fc8ca50..72b99c59 100644 --- a/pysollib/games/sultan.py +++ b/pysollib/games/sultan.py @@ -53,20 +53,20 @@ class Sultan(Game): self.setSize(w, h) # create stacks - lay = ((0,0,0,1), - (2,0,0,1), - (0,1,1,1), - (2,1,1,1), - (1,1,2,0), - (1,2,2,1), - (0,2,3,1), - (2,2,3,1), - (1,0,2,1), + lay = ((0,0,0,1,13), + (2,0,0,1,13), + (0,1,1,1,13), + (2,1,1,1,13), + (1,1,2,0,1), + (1,2,2,1,13), + (0,2,3,1,13), + (2,2,3,1,13), + (1,0,2,1,12), ) - for i, j, suit, max_accept in lay: + for i, j, suit, max_accept, max_cards in lay: x, y = 2*l.XM+l.XS+i*l.XS, l.YM+j*l.YS stack = SS_FoundationStack(x, y, self, suit=suit, - max_move=0, max_accept=max_accept, mod=13) + max_move=0, max_accept=max_accept, max_cards=max_cards, mod=13) s.foundations.append(stack) x, y = l.XM, l.YM @@ -309,7 +309,7 @@ class IdleAces(Game): x, y = x0+i*l.XS, y0+j*l.YS s.foundations.append(RK_FoundationStack(x, y, self, ##suit=ANY_SUIT, - base_rank=1, max_move=0)) + base_rank=1, max_move=0, max_cards=12)) k += 1 k = 0 for i, j in((1, 0.2), (3, 0.2), (1, 2.8), (3, 2.8)): @@ -635,14 +635,15 @@ class SixesAndSevens(Game): for i in range(2): x = l.XM for j in range(4): - s.foundations.append(SS_FoundationStack(x, y, self, suit=j, base_rank=6)) + s.foundations.append(SS_FoundationStack(x, y, self, + suit=j, base_rank=6, max_cards=7)) x += l.XS y += l.YS for i in range(2): x = l.XM for j in range(4): s.foundations.append(SS_FoundationStack(x, y, self, suit=j, - base_rank=5, dir=-1)) + base_rank=5, dir=-1, max_cards=6)) x += l.XS y += l.YS y = l.YM diff --git a/pysollib/games/takeaway.py b/pysollib/games/takeaway.py index 4e65dd14..a4633c1d 100644 --- a/pysollib/games/takeaway.py +++ b/pysollib/games/takeaway.py @@ -46,6 +46,10 @@ class TakeAway_Foundation(AbstractFoundationStack): return (c1.rank == (c2.rank + 1) % mod or c2.rank == (c1.rank + 1) % mod) + def closeStackMove(self): + pass + + class TakeAway(Game): RowStack_Class = BasicRowStack diff --git a/pysollib/games/yukon.py b/pysollib/games/yukon.py index ddbf544d..6ccb67fe 100644 --- a/pysollib/games/yukon.py +++ b/pysollib/games/yukon.py @@ -171,17 +171,22 @@ class Odessa(RussianSolitaire): # // Grandfather # ************************************************************************/ +class Grandfather_Talon(RedealTalonStack): + def redealCards(self, sound=0): + RedealTalonStack.redealCards(self, sound=sound, shuffle=True) + class Grandfather(RussianSolitaire): + Talon_Class = StackWrapper(Grandfather_Talon, max_rounds=3) + def startGame(self): - n = 1 - for i in (2,4,6,5,3,1): - self.s.talon.dealRow(rows=[self.s.rows[n]]*i, flip=0, frames=0) - n += 1 - n = 0 + for i, j in ((1,7),(1,6),(2,6),(2,5),(3,5),(3,4)): + self.s.talon.dealRowAvail(rows=self.s.rows[i:j], flip=0, frames=0) self.startDealSample() - for i in (1,5,5,5,5,5,5): - self.s.talon.dealRow(rows=[self.s.rows[n]]*i) - n += 1 + self.s.talon.dealRowAvail() + for i in range(4): + self.s.talon.dealRowAvail(rows=self.s.rows[1:]) + + redealCards = startGame # /*********************************************************************** @@ -644,7 +649,7 @@ registerGame(GameInfo(20, RussianSolitaire, "Russian Solitaire", registerGame(GameInfo(27, Odessa, "Odessa", GI.GT_YUKON, 1, 0, GI.SL_BALANCED)) registerGame(GameInfo(278, Grandfather, "Grandfather", - GI.GT_YUKON, 1, 0, GI.SL_MOSTLY_LUCK)) + GI.GT_YUKON, 1, 2, GI.SL_BALANCED)) registerGame(GameInfo(186, Alaska, "Alaska", GI.GT_YUKON, 1, 0, GI.SL_BALANCED)) registerGame(GameInfo(187, ChineseDiscipline, "Chinese Discipline", diff --git a/pysollib/move.py b/pysollib/move.py index 87afb8db..6144f7f7 100644 --- a/pysollib/move.py +++ b/pysollib/move.py @@ -144,7 +144,6 @@ class AFlipAllMove(AtomicMove): # do the actual move def __doMove(self, game, stack): - #card = stack.cards[-1] for card in stack.cards: if card.face_up: card.showBack() @@ -434,3 +433,28 @@ class AShuffleStackMove(AtomicMove): cmp(self.card_ids, other.card_ids) or cmp(self.state, other.state)) + +# /*********************************************************************** +# // ACloseStackMove +# ************************************************************************/ + +class ACloseStackMove(AtomicMove): + + def __init__(self, stack): + self.stack_id = stack.id + + def redo(self, game): + stack = game.allstacks[self.stack_id] + assert stack.cards + stack.is_closed = True + stack._shadeStack() + + def undo(self, game): + stack = game.allstacks[self.stack_id] + assert stack.cards + stack.is_closed = False + stack._unshadeStack() + + def cmpForRedo(self, other): + return cmp(self.stack_id, other.stack_id) + diff --git a/pysollib/stack.py b/pysollib/stack.py index 6e6b0047..00c90b04 100644 --- a/pysollib/stack.py +++ b/pysollib/stack.py @@ -45,9 +45,11 @@ __all__ = ['cardsFaceUp', 'Stack', 'DealRow_StackMethods', 'DealBaseCard_StackMethods', + 'RedealCards_StackMethods', 'TalonStack', 'DealRowTalonStack', 'InitialDealTalonStack', + 'RedealTalonStack', 'OpenStack', 'AbstractFoundationStack', 'SS_FoundationStack', @@ -276,10 +278,12 @@ class Stack: bottom = None, # canvas item redeal = None, # canvas item redeal_img = None, # the corresponding PhotoImage + shade_img = None, ) # other canvas items view.items = Struct( bottom = None, # dummy canvas item + shade_item = None, ) # text items view.texts = Struct( @@ -296,6 +300,8 @@ class Stack: view.is_open = -1 view.can_hide_cards = -1 view.max_shadow_cards = -1 + # + view.is_closed = False def destruct(self): # help breaking circular references @@ -422,6 +428,7 @@ class Stack: view._position(card) if update: view.updateText() + self.closeStackMove() return card # Remove a card from the stack. Also update display. {model -> view} @@ -449,6 +456,9 @@ class Stack: model.cards.remove(card) if update: view.updateText() + if self.is_closed: + self._unshadeStack() + self.is_closed = False return card # Get the top card {model} @@ -621,6 +631,8 @@ class Stack: def fillStack(self): self.game.fillStack(self) + def closeStackMove(self): + pass # # Playing move actions. Better not override. @@ -974,6 +986,8 @@ class Stack: i = self._findCard(event) if i < 0 or not self.canMoveCards(self.cards[i:]): return + if self.is_closed: + self.items.shade_item.config(state='hidden') x_offset, y_offset = self.cards[i].x, self.cards[i].y if sound: self.game.playSample("startdrag") @@ -1048,24 +1062,29 @@ class Stack: return () cy = c.y img0, img1 = images.getShadow(0), images.getShadow(l) - if 0: - # Dynamically compute the shadow. Doesn't work because - # PhotoImage.copy() doesn't preserve transparency. - img1 = images.getShadow(13) - if img1: - h = images.CARDH - img0.height() - h = h + (l - 1) * self.CARD_YOFFSET[0] - if h < img1.height(): - import Tkinter - dest = Tkinter.PhotoImage(width=img1.width(), height=h) - dest.blank() - img1.tk.call(dest, "copy", img1.name, "-from", 0, 0, img1.width(), h) - assert dest.height() == h and dest.width() == img1.width() - #print h, img1.height(), dest.height() - img1 = dest - self._foo = img1 # keep a reference - elif h > img1.height(): - img1 = None +## if 0: +## # Dynamically compute the shadow. Doesn't work because +## # PhotoImage.copy() doesn't preserve transparency. +## img1 = images.getShadow(13) +## if img1: +## h = images.CARDH - img0.height() +## h = h + (l - 1) * self.CARD_YOFFSET[0] +## if h < img1.height(): +## if hasattr(img1, '_pil_image'): # use PIL +## import ImageTk +## im = img1._pil_image.crop((0,0,img1.width(),h)) +## img1 = ImageTk.PhotoImage(im) +## else: +## import Tkinter +## dest = Tkinter.PhotoImage(width=img1.width(), height=h) +## dest.blank() +## img1.tk.call(dest, "copy", img1.name, "-from", 0, 0, img1.width(), h) +## assert dest.height() == h and dest.width() == img1.width() +## #print h, img1.height(), dest.height() +## img1 = dest +## self._foo = img1 # keep a reference +## elif h > img1.height(): +## img1 = None if img0 and img1: c = cards[-1] if self.CARD_YOFFSET[0] < 0: c = cards[0] @@ -1090,7 +1109,11 @@ class Stack: # optimized for speed - we use lots of local variables game = self.game images = game.app.images - img = images.getShade() + if not self.images.shade_img: + img = images.getShade() + self.images.shade_img = img + else: + img = self.images.shade_img if img is None: return CW, CH = images.CARDW, images.CARDH @@ -1139,6 +1162,31 @@ class Stack: else: img.lower(drag.cards[0].item) + # for closeStackMove + def _shadeStack(self): + if not self.game.app.opt.shade_filled_stacks: + return + if not self.images.shade_img: + img = self.game.app.images.getShade() + self.images.shade_img = img + else: + img = self.images.shade_img + if img is None: + return + if not self.items.shade_item: + self.game.canvas.update_idletasks() + card = self.cards[-1] + item = MfxCanvasImage(self.game.canvas, card.x, card.y, + image=img, anchor=ANCHOR_NW) + ##item.tkraise() + item.addtag(self.group) + self.items.shade_item = item + + def _unshadeStack(self): + if self.items.shade_item: + self.items.shade_item.delete() + self.items.shade_item = None + def _stopDrag(self): drag = self.game.drag after_cancel(drag.timer) @@ -1151,6 +1199,9 @@ class Stack: drag.shadows = [] drag.stack = None drag.cards = [] + if self.is_closed: + self.items.shade_item.config(state='normal') + self.items.shade_item.tkraise() # finish a drag operation def finishDrag(self, event=None): @@ -1173,8 +1224,7 @@ class Stack: self.moveCardsBackHandler(event, drag) def getHelp(self): - # devel - return str(self) + return str(self) # debug def getBaseCard(self): return '' @@ -1319,11 +1369,46 @@ class DealBaseCard_StackMethods: ncards = ncards - 1 +class RedealCards_StackMethods: + + def redealCards(self, sound=0, shuffle=False, reverse=False, frames=4): + if sound and self.game.app.opt.animations: + self.game.startDealSample() + lr = len(self.game.s.rows) + # move all cards to the Talon + num_cards = 0 + assert len(self.cards) == 0 + rows = list(self.game.s.rows)[:] + if reverse: + rows.reverse() + for r in rows: + for i in range(len(r.cards)): + num_cards += 1 + self.game.moveMove(1, r, self, frames=0) + if self.cards[-1].face_up: + self.game.flipMove(self) + assert len(self.cards) == num_cards + if num_cards == 0: # game already finished + return 0 + if shuffle: + # shuffle + self.game.shuffleStackMove(self) + # redeal + self.game.nextRoundMove(self) + self.game.redealCards() + if sound: + self.game.stopSamples() + return num_cards + + # /*********************************************************************** # // The Talon is a stack with support for dealing. # ************************************************************************/ -class TalonStack(Stack, DealRow_StackMethods, DealBaseCard_StackMethods): +class TalonStack(Stack, + DealRow_StackMethods, + DealBaseCard_StackMethods, + ): def __init__(self, x, y, game, max_rounds=1, num_deal=1, **cap): Stack.__init__(self, x, y, game, cap=cap) self.max_rounds = max_rounds @@ -1361,8 +1446,8 @@ class TalonStack(Stack, DealRow_StackMethods, DealBaseCard_StackMethods): def removeAllCards(self): for stack in self.game.allstacks: while stack.cards: - ##stack.removeCard(update=0) - stack.removeCard(unhide=0, update=0) + stack.removeCard(update=0) + ##stack.removeCard(unhide=0, update=0) for stack in self.game.allstacks: stack.updateText() @@ -1461,6 +1546,15 @@ class InitialDealTalonStack(TalonStack): return None +class RedealTalonStack(TalonStack, RedealCards_StackMethods): + def canDealCards(self): + if self.round == self.max_rounds: + return False + return not self.game.isGameWon() + def dealCards(self, sound=0): + RedealCards_StackMethods.redealCards(self, sound=sound) + + # /*********************************************************************** # // An OpenStack is a stack where cards can be placed and dragged # // (i.e. FoundationStack, RowStack, ReserveStack, ...) @@ -1646,6 +1740,10 @@ class AbstractFoundationStack(OpenStack): def getBaseCard(self): return self._getBaseCard() + def closeStackMove(self): + if len(self.cards) == self.cap.max_cards: + self.game.closeStackMove(self) + # A SameSuit_FoundationStack is the typical Foundation stack. # It builds up in rank and suit. diff --git a/pysollib/tk/card.py b/pysollib/tk/card.py index 3daa7339..c63a586c 100644 --- a/pysollib/tk/card.py +++ b/pysollib/tk/card.py @@ -115,8 +115,10 @@ class _OneImageCard(_HideableCard): _HideableCard.__init__(self, id, deck, suit, rank, game, x=x, y=y) self._face_image = game.getCardFaceImage(deck, suit, rank) self._back_image = game.getCardBackImage(deck, suit, rank) + self._shade_image = game.getCardShadeImage() self._active_image = self._back_image self.item = MfxCanvasImage(game.canvas, self.x, self.y, image=self._active_image, anchor="nw") + self.shade_item = None ##self._setImage = self.item.config def _setImage(self, image): @@ -153,6 +155,7 @@ class _OneImageCard(_HideableCard): item.canvas.tk.call(item.canvas._w, "move", item.id, dx, dy) + # /*********************************************************************** # // New idea since 3.00 # // diff --git a/pysollib/tk/menubar.py b/pysollib/tk/menubar.py index 9f90173b..f2701299 100644 --- a/pysollib/tk/menubar.py +++ b/pysollib/tk/menubar.py @@ -335,8 +335,8 @@ class PysolMenubar(PysolMenubarActions): submenu.add_checkbutton(label=n_("Enable highlight same &rank"), variable=self.tkopt.highlight_samerank, command=self.mOptEnableHighlightSameRank) submenu.add_checkbutton(label=n_("Highlight &no matching"), variable=self.tkopt.highlight_not_matching, command=self.mOptEnableHighlightNotMatching) submenu.add_separator() - submenu.add_checkbutton(label=n_("Show removed tiles (in Mahjongg games)"), variable=self.tkopt.mahjongg_show_removed, command=self.mOptMahjonggShowRemoved) - submenu.add_checkbutton(label=n_("Show hint arrow (in Shisen-Sho games)"), variable=self.tkopt.shisen_show_hint, command=self.mOptShisenShowHint) + submenu.add_checkbutton(label=n_("&Show removed tiles (in Mahjongg games)"), variable=self.tkopt.mahjongg_show_removed, command=self.mOptMahjonggShowRemoved) + submenu.add_checkbutton(label=n_("Show hint &arrow (in Shisen-Sho games)"), variable=self.tkopt.shisen_show_hint, command=self.mOptShisenShowHint) menu.add_separator() label = n_("&Sound...") if self.app.audio.audiodev is None: @@ -354,6 +354,7 @@ class PysolMenubar(PysolMenubarActions): submenu.add_checkbutton(label=n_("Card shado&w"), variable=self.tkopt.shadow, command=self.mOptShadow) submenu.add_checkbutton(label=n_("Shade &legal moves"), variable=self.tkopt.shade, command=self.mOptShade) submenu.add_checkbutton(label=n_("&Negative cards bottom"), variable=self.tkopt.negative_bottom, command=self.mOptNegativeBottom) + submenu.add_checkbutton(label=n_("Shade &filled stacks"), variable=self.tkopt.shade_filled_stacks, command=self.mOptShadeFilledStacks) submenu = MfxMenu(menu, label=n_("A&nimations")) submenu.add_radiobutton(label=n_("&None"), variable=self.tkopt.animations, value=0, command=self.mOptAnimations) submenu.add_radiobutton(label=n_("&Timer based"), variable=self.tkopt.animations, value=2, command=self.mOptAnimations) diff --git a/pysollib/tk/tkcanvas.py b/pysollib/tk/tkcanvas.py index 334a4319..9a78d0e1 100644 --- a/pysollib/tk/tkcanvas.py +++ b/pysollib/tk/tkcanvas.py @@ -222,10 +222,17 @@ class MfxCanvas(Tkinter.Canvas): if stack.cards[i].item.tag in current: return i else: - current = self.find("withtag", "current") # get item ids - for i in range(len(stack.cards)): - if stack.cards[i].item.id in current: - return i +## current = self.find("withtag", "current") # get item ids +## for i in range(len(stack.cards)): +## if stack.cards[i].item.id in current: +## return i + x, y = event.x, event.y + items = list(self.find_overlapping(x,y,x,y)) + items.reverse() + for item in items: + for i in range(len(stack.cards)): + if stack.cards[i].item.id == item: + return i return -1 def setTextColor(self, color):