From 3c6df76c7a1c534f376e94184f3a8ea507bd3ebf Mon Sep 17 00:00:00 2001
From: skomoroh <skomoroh@39dd0a4e-7c14-0410-91b3-c4f2d318f732>
Date: Thu, 27 Jul 2006 21:25:30 +0000
Subject: [PATCH] + 6 new games + new action: `show descript. of piles'; new
 class: StackDesc * Stack.getHelp: rename `Row' -> `Tableau' * misc.
 improvements

git-svn-id: https://pysolfc.svn.sourceforge.net/svnroot/pysolfc/PySolFC/trunk@29 39dd0a4e-7c14-0410-91b3-c4f2d318f732
---
 pysollib/app.py                  |  2 +-
 pysollib/game.py                 | 29 +++++++++-
 pysollib/games/auldlangsyne.py   |  2 +-
 pysollib/games/calculation.py    |  2 +-
 pysollib/games/curdsandwhey.py   |  2 +-
 pysollib/games/diplomat.py       | 30 ++++++++++-
 pysollib/games/fortythieves.py   | 93 ++++++++++++++++++++++++++++++--
 pysollib/games/golf.py           |  4 +-
 pysollib/games/gypsy.py          | 64 +++++++++++++++++++++-
 pysollib/games/harp.py           | 35 ++++++++++--
 pysollib/games/klondike.py       |  2 +-
 pysollib/games/numerica.py       | 10 ++--
 pysollib/games/pileon.py         | 12 ++---
 pysollib/games/royalcotillion.py | 15 ------
 pysollib/games/sultan.py         | 86 ++++++++++++++++++++++++++---
 pysollib/games/windmill.py       | 41 ++++++++++----
 pysollib/games/yukon.py          |  6 +--
 pysollib/layout.py               |  2 +-
 pysollib/stack.py                | 61 +++++++++++----------
 pysollib/tk/menubar.py           | 19 +++++--
 pysollib/tk/tkwidget.py          | 41 ++++++++++++++
 21 files changed, 460 insertions(+), 98 deletions(-)

diff --git a/pysollib/app.py b/pysollib/app.py
index e1e49439..639dec14 100644
--- a/pysollib/app.py
+++ b/pysollib/app.py
@@ -501,7 +501,6 @@ class Application:
         self.toolbar = None
         self.canvas = None
         self.statusbar = None
-        self.cardsets_cache = {}
         #
         self.game = None
         self.dataloader = None
@@ -521,6 +520,7 @@ class Application:
         self.progress_images = []
         self.cardset_manager = CardsetManager()
         self.cardset = None             # current cardset
+        self.cardsets_cache = {}
         self.tabletile_manager = TileManager()
         self.tabletile_index = 0        # current table tile
         self.sample_manager = SampleManager()
diff --git a/pysollib/game.py b/pysollib/game.py
index 8f8928b0..a36c093f 100644
--- a/pysollib/game.py
+++ b/pysollib/game.py
@@ -116,6 +116,7 @@ class Game:
         self.cards = []
         self.stackmap = {}              # dict with (x,y) tuples as key
         self.allstacks = []
+        self.stackdesc_list = []
         self.demo_logo = None
         self.pause_logo = None
         self.s = Struct(                # stacks
@@ -701,6 +702,7 @@ class Game:
         if break_pause and self.pause:
             self.doPause()
         self.interruptSleep()
+        self.deleteStackDesc()
         if self.busy: return 1
         if self.drag.stack:
             self.drag.stack.cancelDrag()
@@ -715,12 +717,14 @@ class Game:
             self.app.menubar.disableMenus()
 
 
+
     #
     # UI & graphics support
     #
 
     def clickHandler(self, *args):
         self.interruptSleep()
+        self.deleteStackDesc()
         if self.demo:
             self.stopDemo()
         return EVENT_PROPAGATE
@@ -793,7 +797,7 @@ class Game:
     def _unmapHandler(self, event):
         # pause game if root window has been iconified
         if event.widget is self.top and not self.pause:
-            self.doPause()
+            self.app.menubar.mPause()
 
 
     #
@@ -2461,6 +2465,29 @@ in the current implementation.''' % version
         if kw.has_key('help') and self.app.opt.helpbar:
             self.app.helpbar.updateText(info=kw['help'])
 
+    #
+    # Piles descriptions
+    #
+
+    def showStackDesc(self):
+        from pysoltk import StackDesc
+        from stack import InitialDealTalonStack
+        sd_list = []
+        for s in self.allstacks:
+            sd = (s.__class__.__name__, s.cap.base_rank, s.cap.dir)
+            if sd in sd_list:
+                # one of each uniq pile
+                continue
+            if isinstance(s, InitialDealTalonStack):
+                continue
+            self.stackdesc_list.append(StackDesc(self, s))
+            sd_list.append(sd)
+
+    def deleteStackDesc(self):
+        if self.stackdesc_list:
+            for sd in self.stackdesc_list:
+                sd.delete()
+            self.stackdesc_list = []
 
     #
     # subclass hooks
diff --git a/pysollib/games/auldlangsyne.py b/pysollib/games/auldlangsyne.py
index 06e905ca..6e9a7164 100644
--- a/pysollib/games/auldlangsyne.py
+++ b/pysollib/games/auldlangsyne.py
@@ -154,7 +154,7 @@ class Strategy_RowStack(BasicRowStack):
         return self.game.app.images.getReserveBottom()
 
     def getHelp(self):
-        return _('Row. Build regardless of rank and suit.')
+        return _('Tableau. Build regardless of rank and suit.')
 
 
 class Strategy(Game):
diff --git a/pysollib/games/calculation.py b/pysollib/games/calculation.py
index 0ad14be1..c1cbdaae 100644
--- a/pysollib/games/calculation.py
+++ b/pysollib/games/calculation.py
@@ -101,7 +101,7 @@ class Calculation_RowStack(BasicRowStack):
         return self.game.app.images.getReserveBottom()
 
     def getHelp(self):
-        return _('Row. Build regardless of rank and suit.')
+        return _('Tableau. Build regardless of rank and suit.')
 
 
 # /***********************************************************************
diff --git a/pysollib/games/curdsandwhey.py b/pysollib/games/curdsandwhey.py
index dd26195b..6537ceb9 100644
--- a/pysollib/games/curdsandwhey.py
+++ b/pysollib/games/curdsandwhey.py
@@ -57,7 +57,7 @@ class CurdsAndWhey_RowStack(BasicRowStack):
         return isSameSuitSequence(cards) or isRankSequence(cards, dir=0)
 
     def getHelp(self):
-        return _('Row. Build down by suit or of the same rank.')
+        return _('Tableau. Build down by suit or of the same rank.')
 
 
 class CurdsAndWhey(Game):
diff --git a/pysollib/games/diplomat.py b/pysollib/games/diplomat.py
index a5606425..99469452 100644
--- a/pysollib/games/diplomat.py
+++ b/pysollib/games/diplomat.py
@@ -41,6 +41,7 @@ from pysollib.stack import *
 from pysollib.game import Game
 from pysollib.layout import Layout
 from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint
+from pysollib.pysoltk import MfxCanvasText
 
 from fortythieves import FortyThieves_Hint
 from spider import Spider_Hint
@@ -138,7 +139,7 @@ class Congress(Diplomat):
     # game layout (just rearrange the stacks a little bit)
     #
 
-    def createGame(self):
+    def createGame(self, max_rounds=1):
         # create layout
         l, s = Layout(self), self.s
 
@@ -160,11 +161,18 @@ class Congress(Diplomat):
                 stack.CARD_YOFFSET = 0
                 s.rows.append(stack)
         x, y, = l.XM, l.YM
-        s.talon = WasteTalonStack(x, y, self, max_rounds=1)
+        s.talon = WasteTalonStack(x, y, self, max_rounds=max_rounds)
         l.createText(s.talon, "ss")
         x = x + l.XS
         s.waste = WasteStack(x, y, self)
         l.createText(s.waste, "ss")
+        if max_rounds > 1:
+            tx, ty, ta, tf = l.getTextAttr(s.waste, "ne")
+            font = self.app.getFont("canvas_default")
+            s.talon.texts.rounds = MfxCanvasText(self.canvas,
+                                                 tx, ty,
+                                                 anchor=ta,
+                                                 font=font)
 
         # define stack-groups
         l.defaultStackGroups()
@@ -251,6 +259,22 @@ class LittleNapoleon(Diplomat):
         return 0
 
 
+# /***********************************************************************
+# // Twin Queens
+# ************************************************************************/
+
+class TwinQueens(Congress):
+    Foundation_Classes = [
+        StackWrapper(SS_FoundationStack, base_rank=KING, mod=13),
+        StackWrapper(SS_FoundationStack, base_rank=KING, mod=13),
+        ]
+    RowStack_Class = StackWrapper(SS_RowStack, max_move=1)
+
+    def createGame(self):
+        Congress.createGame(self, max_rounds=2)
+
+
+
 # register the game
 registerGame(GameInfo(149, Diplomat, "Diplomat",
                       GI.GT_FORTY_THIEVES, 2, 0, GI.SL_BALANCED))
@@ -268,4 +292,6 @@ registerGame(GameInfo(548, Parliament, "Parliament",
                       GI.GT_FORTY_THIEVES, 2, 0, GI.SL_BALANCED))
 registerGame(GameInfo(549, Wheatsheaf, "Wheatsheaf",
                       GI.GT_FORTY_THIEVES, 2, 0, GI.SL_BALANCED))
+registerGame(GameInfo(563, TwinQueens, "Twin Queens",
+                      GI.GT_FORTY_THIEVES, 2, 1, GI.SL_MOSTLY_SKILL))
 
diff --git a/pysollib/games/fortythieves.py b/pysollib/games/fortythieves.py
index e764b6fc..44e5ba4c 100644
--- a/pysollib/games/fortythieves.py
+++ b/pysollib/games/fortythieves.py
@@ -32,7 +32,6 @@
 __all__ = []
 
 # imports
-import sys
 
 # PySol imports
 from pysollib.gamedb import registerGame, GameInfo, GI
@@ -43,6 +42,9 @@ from pysollib.layout import Layout
 from pysollib.hint import AbstractHint, DefaultHint, CautiousDefaultHint
 from pysollib.pysoltk import MfxCanvasText
 
+from gypsy import DieRussische_Foundation
+
+
 # /***********************************************************************
 # //
 # ************************************************************************/
@@ -440,7 +442,7 @@ class Indian_RowStack(SequenceRowStack):
     def _isSequence(self, cards):
         return isAnySuitButOwnSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        return _('Row. Build down in any suit but the same.')
+        return _('Tableau. Build down in any suit but the same.')
 
 
 class Indian(FortyThieves):
@@ -805,8 +807,6 @@ class Waterloo(FortyThieves):
 # // Junction
 # ************************************************************************/
 
-from gypsy import DieRussische_Foundation
-
 class Junction(Game):
 
     def createGame(self, rows=7):
@@ -849,6 +849,89 @@ class Junction(Game):
         return card1.color != card2.color and abs(card1.rank-card2.rank) == 1
 
 
+# /***********************************************************************
+# // The Spark
+# ************************************************************************/
+
+class TheSpark_Talon(TalonStack):
+
+    def canDealCards(self):
+        return len(self.cards) > 0
+
+    def dealCards(self, sound=0):
+        old_state = self.game.enterState(self.game.S_DEAL)
+        num_cards = 0
+        if self.cards:
+            if sound and not self.game.demo:
+                self.game.playSample("dealwaste")
+            for i in range(self.num_deal):
+                for r in self.game.s.reserves:
+                    if not self.cards:
+                        break
+                    self.game.flipMove(self)
+                    self.game.moveMove(1, self, r, frames=4, shadow=0)
+                    num_cards += 1
+        self.game.leaveState(old_state)
+        return num_cards
+
+
+class TheSpark(Game):
+    Hint_Class = CautiousDefaultHint
+
+    def createGame(self):
+
+        l, s = Layout(self), self.s
+
+        w, h = l.XM+8*l.XS, l.YM+4*l.YS
+        self.setSize(w, h)
+
+        x, y = l.XM, l.YM
+        for i in range(8):
+            s.foundations.append(SS_FoundationStack(x, y, self,
+                                 suit=i/2, base_rank=KING, mod=13))
+            x += l.XS
+        x, y = l.XM, l.YM+l.YS
+        s.talon = TheSpark_Talon(x, y, self, max_rounds=1, num_deal=3)
+        l.createText(s.talon, 'se')
+        y += l.YS
+        for i in (0,1):
+            stack = WasteStack(x, y, self)
+            s.reserves.append(stack)
+            l.createText(stack, 'se')
+            y += l.YS
+        y = l.YM+l.YS*3/2
+        for i in range(2):
+            x = l.XM+2*l.XS
+            for j in range(6):
+                stack = SS_RowStack(x, y, self, max_move=1)
+                stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
+                s.rows.append(stack)
+                x += l.XS
+            y += l.YS
+
+        l.defaultStackGroups()
+
+
+    def _shuffleHook(self, cards):
+        # move Aces to top of the Talon (i.e. first cards to be dealt)
+        return self._shuffleHookMoveToTop(cards,
+                                          lambda c: (c.rank == KING, c.suit))
+
+
+    def startGame(self):
+        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
+        self.startDealSample()
+        self.s.talon.dealRow()
+        self.s.talon.dealCards()          # deal first card to WasteStack
+
+
+    def shallHighlightMatch(self, stack1, card1, stack2, card2):
+        return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1
+
+
+
+
+
 
 # register the game
 registerGame(GameInfo(13, FortyThieves, "Forty Thieves",
@@ -941,5 +1024,7 @@ registerGame(GameInfo(540, Waterloo, "Waterloo",
 registerGame(GameInfo(556, Junction, "Junction",
                       GI.GT_FORTY_THIEVES, 4, 0, GI.SL_MOSTLY_SKILL,
                       ranks=(0, 6, 7, 8, 9, 10, 11, 12) ))
+registerGame(GameInfo(564, TheSpark, "The Spark",
+                      GI.GT_FORTY_THIEVES, 2, 0, GI.SL_MOSTLY_LUCK))
 
 
diff --git a/pysollib/games/golf.py b/pysollib/games/golf.py
index b946c836..fecb49fc 100644
--- a/pysollib/games/golf.py
+++ b/pysollib/games/golf.py
@@ -111,7 +111,7 @@ class Golf_RowStack(BasicRowStack):
     def clickHandler(self, event):
         return self.doubleclickHandler(event)
     def getHelp(self):
-        return _('Row. No building.')
+        return _('Tableau. No building.')
 
 
 # /***********************************************************************
@@ -411,7 +411,7 @@ class BlackHole_RowStack(ReserveStack):
     def clickHandler(self, event):
         return self.doubleclickHandler(event)
     def getHelp(self):
-        return _('Row. No building.')
+        return _('Tableau. No building.')
 
 
 class BlackHole(Game):
diff --git a/pysollib/games/gypsy.py b/pysollib/games/gypsy.py
index f27f5485..58cc2ccc 100644
--- a/pysollib/games/gypsy.py
+++ b/pysollib/games/gypsy.py
@@ -539,13 +539,68 @@ class Elba(Gypsy):
 
 class Millie(Gypsy):
     Layout_Method = Layout.klondikeLayout
-    RowStack_Class = AC_RowStack
 
     def startGame(self):
         self.startDealSample()
         self.s.talon.dealRow()
 
 
+# /***********************************************************************
+# // Hypotenuse
+# // Eternal Triangle
+# // Right Triangle
+# ************************************************************************/
+
+class Hypotenuse(Gypsy):
+    Layout_Method = Layout.klondikeLayout
+    RowStack_Class = KingAC_RowStack
+
+    def createGame(self):
+        Gypsy.createGame(self, rows=10, playcards=24)
+
+    def startGame(self, flip=0, reverse=1):
+        for i in range(1, 10):
+            self.s.talon.dealRow(rows=self.s.rows[:i], flip=0, frames=0)
+        self.startDealSample()
+        self.s.talon.dealRow()
+
+
+class EternalTriangle(Hypotenuse):
+
+    def startGame(self, flip=0, reverse=1):
+        for i in range(1, 10):
+            self.s.talon.dealRow(rows=self.s.rows[i:], frames=0)
+        self.startDealSample()
+        self.s.talon.dealRow()
+
+
+class RightTriangle_Talon(OpenStack, DealRowTalonStack):
+    def __init__(self, x, y, game, max_rounds=1, num_deal=1, **cap):
+        Stack.__init__(self, x, y, game, cap=cap)
+        self.max_rounds = max_rounds
+        self.num_deal = num_deal
+        self.round = 1
+        self.base_cards = []        # for DealBaseCard_StackMethods
+
+    def canFlipCard(self):
+        return False
+
+    def getBottomImage(self):
+        return self.game.app.images.getReserveBottom()
+
+    def getHelp(self):
+        return ''
+
+class RightTriangle(Hypotenuse):
+    Talon_Class = StackWrapper(RightTriangle_Talon, max_accept=1, max_move=1)
+
+    def createGame(self):
+        Gypsy.createGame(self, rows=10, playcards=24)
+        self.sg.dropstacks.append(self.s.talon)
+        self.sg.openstacks.append(self.s.talon)
+        self.sg.reservestacks.append(self.s.talon)
+
+
 # register the game
 registerGame(GameInfo(1, Gypsy, "Gypsy",
                       GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
@@ -593,4 +648,11 @@ registerGame(GameInfo(487, Millie, "Millie",
                       GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
 registerGame(GameInfo(498, Steve, "Steve",
                       GI.GT_GYPSY, 2, 0, GI.SL_BALANCED))
+registerGame(GameInfo(566, Hypotenuse, "Hypotenuse",
+                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
+registerGame(GameInfo(567, EternalTriangle, "Eternal Triangle",
+                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL,
+                      altnames=('Lobachevsky',) ))
+registerGame(GameInfo(568, RightTriangle, "Right Triangle",
+                      GI.GT_GYPSY, 2, 0, GI.SL_MOSTLY_SKILL))
 
diff --git a/pysollib/games/harp.py b/pysollib/games/harp.py
index 7562ba56..de6bf679 100644
--- a/pysollib/games/harp.py
+++ b/pysollib/games/harp.py
@@ -224,12 +224,14 @@ class Arabella(DoubleKlondike):
 # ************************************************************************/
 
 class BigDeal(DoubleKlondike):
-    def createGame(self, rows=12, max_rounds=2):
+    RowStack_Class = KingAC_RowStack
+
+    def createGame(self, rows=12, max_rounds=2, XOFFSET=0):
         l, s = Layout(self), self.s
         self.setSize(l.XM+(rows+2)*l.XS, l.YM+8*l.YS)
         x, y = l.XM, l.YM
         for i in range(rows):
-            s.rows.append(AC_RowStack(x, y, self, base_rank=KING))
+            s.rows.append(self.RowStack_Class(x, y, self))
             x += l.XS
         for i in range(2):
             y = l.YM
@@ -242,16 +244,39 @@ class BigDeal(DoubleKlondike):
         l.createText(s.talon, 'n')
         x += l.XS
         s.waste = WasteStack(x, y, self)
+        s.waste.CARD_XOFFSET = XOFFSET
         l.createText(s.waste, 'n')
         if max_rounds > 1:
             tx, ty, ta, tf = l.getTextAttr(s.talon, 'nn')
-            ty -= 2*l.TEXT_MARGIN
+            ty -= l.TEXT_MARGIN
             font = self.app.getFont('canvas_default')
-            s.talon.texts.rounds = MfxCanvasText(self.canvas, tx, ty, anchor=ta, font=font)
+            s.talon.texts.rounds = MfxCanvasText(self.canvas, tx, ty,
+                                                 anchor=ta, font=font)
         self.setRegion(s.rows, (-999, -999, l.XM+rows*l.XS-l.CW/2, 999999), priority=1)
         l.defaultStackGroups()
 
 
+# /***********************************************************************
+# // Delivery
+# ************************************************************************/
+
+class Delivery(BigDeal):
+    RowStack_Class = StackWrapper(SS_RowStack, max_move=1)
+
+    def createGame(self):
+        dx = self.app.images.CARDW/10
+        BigDeal.createGame(self, rows=12, max_rounds=1, XOFFSET=dx)
+
+    def shallHighlightMatch(self, stack1, card1, stack2, card2):
+        return card1.suit == card2.suit and abs(card1.rank-card2.rank) == 1
+
+    def startGame(self):
+        for i in range(2):
+            self.s.talon.dealRow(frames=0)
+        self.startDealSample()
+        self.s.talon.dealRow()
+        self.s.talon.dealCards()          # deal first card to WasteStack
+
 
 # register the game
 registerGame(GameInfo(21, DoubleKlondike, "Double Klondike",
@@ -277,4 +302,6 @@ registerGame(GameInfo(497, Arabella, "Arabella",
                       GI.GT_KLONDIKE, 3, 0, GI.SL_BALANCED))
 registerGame(GameInfo(545, BigDeal, "Big Deal",
                       GI.GT_KLONDIKE | GI.GT_ORIGINAL, 4, 1, GI.SL_BALANCED))
+registerGame(GameInfo(562, Delivery, "Delivery",
+                      GI.GT_FORTY_THIEVES | GI.GT_ORIGINAL, 4, 0, GI.SL_BALANCED))
 
diff --git a/pysollib/games/klondike.py b/pysollib/games/klondike.py
index 22985c82..518a9e8e 100644
--- a/pysollib/games/klondike.py
+++ b/pysollib/games/klondike.py
@@ -145,7 +145,7 @@ class ThumbAndPouch_RowStack(SequenceRowStack):
     def _isSequence(self, cards):
         return isAnySuitButOwnSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        return _('Row. Build down in any suit but the same.')
+        return _('Tableau. Build down in any suit but the same.')
 
 
 class ThumbAndPouch(Klondike):
diff --git a/pysollib/games/numerica.py b/pysollib/games/numerica.py
index bc2f0072..e7ddfad6 100644
--- a/pysollib/games/numerica.py
+++ b/pysollib/games/numerica.py
@@ -86,8 +86,8 @@ class Numerica_RowStack(BasicRowStack):
         return self.game.app.images.getReserveBottom()
 
     def getHelp(self):
-        ##return _('Row. Accepts any one card from the Waste.')
-        return _('Row. Build regardless of rank and suit.')
+        ##return _('Tableau. Accepts any one card from the Waste.')
+        return _('Tableau. Build regardless of rank and suit.')
 
 
 # /***********************************************************************
@@ -233,8 +233,8 @@ class PussInTheCorner_RowStack(BasicRowStack):
     def getBottomImage(self):
         return self.game.app.images.getReserveBottom()
     def getHelp(self):
-        ##return _('Row. Accepts any one card from the Waste.')
-        return _('Row. Build regardless of rank and suit.')
+        ##return _('Tableau. Accepts any one card from the Waste.')
+        return _('Tableau. Build regardless of rank and suit.')
 
 
 class PussInTheCorner(Numerica):
@@ -587,7 +587,7 @@ class Strategerie_RowStack(BasicRowStack):
         return self.game.app.images.getReserveBottom()
 
     def getHelp(self):
-        return _('Row. Build regardless of rank and suit.')
+        return _('Tableau. Build regardless of rank and suit.')
 
 
 class Strategerie_ReserveStack(ReserveStack):
diff --git a/pysollib/games/pileon.py b/pysollib/games/pileon.py
index 875019eb..4a079ead 100644
--- a/pysollib/games/pileon.py
+++ b/pysollib/games/pileon.py
@@ -146,22 +146,22 @@ class Foursome(Game):
 
     def createGame(self, rows=6, texts=True):
         l, s = Layout(self), self.s
-        max_rows = max(6, rows)
-        self.setSize(l.XM+max_rows*l.XS, l.YM+3*l.YS+13*l.YOFFSET)
-        x, y = l.XM+(max_rows-6)*l.XS/2, l.YM
+        max_rows = max(8, rows)
+        self.setSize(l.XM+max_rows*l.XS, l.YM+2*l.YS+13*l.YOFFSET)
+        x, y = l.XM+l.XS*(max_rows-4)/2, l.YM
         for i in range(4):
             s.reserves.append(ReserveStack(x, y, self))
             x += l.XS
         x = l.XM+(max_rows-1)*l.XS
         s.foundations.append(AbstractFoundationStack(x, y, self,
                              suit=ANY_SUIT, max_cards=52, max_accept=0))
-        x, y = l.XM, l.YM+l.YS
+        x, y = l.XM+l.XS*(max_rows-rows)/2, l.YM+l.YS
         for i in range(rows):
             s.rows.append(UD_AC_RowStack(x, y, self, mod=13))
             x += l.XS
-        s.talon = self.Talon_Class(self.width-l.XS, self.height-l.YS, self)
+        s.talon = self.Talon_Class(l.XM, l.YM, self)
         if texts:
-            l.createText(s.talon, 'n')
+            l.createText(s.talon, 'ne')
         l.defaultStackGroups()
 
     def startGame(self):
diff --git a/pysollib/games/royalcotillion.py b/pysollib/games/royalcotillion.py
index b68a7c4d..8261a448 100644
--- a/pysollib/games/royalcotillion.py
+++ b/pysollib/games/royalcotillion.py
@@ -233,21 +233,6 @@ class Alhambra_RowStack(UD_SS_RowStack):
         return self.game.app.images.getReserveBottom()
 
 
-class Alhambra_Talon__(RedealTalonStack):
-
-    def canDealCards(self):
-        if self.round == self.max_rounds:
-            return len(self.cards) != 0
-        return not self.game.isGameWon()
-
-    def dealCards(self, sound=0):
-        if self.cards:
-            return self.dealRowAvail(sound=sound)
-        RedealTalonStack.redealCards(self, frames=0,
-                                     shuffle=False, sound=sound)
-        return self.dealRowAvail(sound=sound)
-
-
 class Alhambra_Talon(DealRowTalonStack):
     def canDealCards(self):
         r_cards = sum([len(r.cards) for r in self.game.s.rows])
diff --git a/pysollib/games/sultan.py b/pysollib/games/sultan.py
index 0b38af90..d3027ed6 100644
--- a/pysollib/games/sultan.py
+++ b/pysollib/games/sultan.py
@@ -746,22 +746,23 @@ class Marshal(Game):
     def createGame(self):
 
         l, s = Layout(self), self.s
-        self.setSize(l.XM+8*l.XS, l.YM+5*l.YS)
+        self.setSize(l.XM+9*l.XS, l.YM+5*l.YS)
 
         x, y = l.XM, l.YM
         for i in range(4):
             s.foundations.append(SS_FoundationStack(x, y, self, suit=i))
-            x += l.XS
+            y += l.YS
+        x, y = self.width-l.XS, l.YM
         for i in range(4):
             s.foundations.append(SS_FoundationStack(x, y, self,
                                  suit=i, base_rank=KING, dir=-1))
-            x += l.XS
-        x, y = l.XM, l.YM+l.YS
-        s.talon = TalonStack(x, y, self)
+            y += l.YS
+        x, y = (self.width-l.XS)/2, self.height-l.YS
+        s.talon = DealRowTalonStack(x, y, self)
         l.createText(s.talon, 'se')
-        y = l.YM+l.YS
+        y = l.YM
         for i in range(4):
-            x = l.XM+2*l.XS
+            x = l.XM+l.XS*3/2
             for j in range(6):
                 stack = UD_SS_RowStack(x, y, self, base_rank=NO_RANK)
                 s.rows.append(stack)
@@ -788,6 +789,75 @@ class Marshal(Game):
                 (abs(card1.rank-card2.rank) == 1))
 
 
+# /***********************************************************************
+# // Royal Aids
+# ************************************************************************/
+
+class RoyalAids_RowStack(KingAC_RowStack):
+    def getBottomImage(self):
+        return self.game.app.images.getReserveBottom()
+
+
+class RoyalAids(Game):
+
+    Hint_Class = CautiousDefaultHint
+
+    def createGame(self):
+
+        l, s = Layout(self), self.s
+        self.setSize(l.XM+8*l.XS, l.YM+4*l.YS)
+
+        x0 = l.XM+1.5*l.XS
+        for k in (0,1):
+            suit = 0
+            for i, j in ((1,0), (0,0.5), (2,0.5), (1,1)):
+                x, y = x0+i*l.XS, l.YM+j*l.YS
+                s.foundations.append(AC_FoundationStack(x, y, self, suit=suit))
+                suit += 1
+            x0 += 3.5*l.XS
+
+        x, y = l.XM, l.YM+l.YS
+        s.talon = WasteTalonStack(x, y, self, max_rounds=UNLIMITED_REDEALS)
+        l.createText(s.talon, 'se')
+        y += l.YS
+        s.waste = WasteStack(x, y, self)
+        l.createText(s.waste, 'se')
+
+        x, y = l.XM+4*l.XS, l.YM+2*l.YS
+        for i in (0,1):
+            stack = RoyalAids_RowStack(x, y, self, max_move=1)
+            s.rows.append(stack)
+            stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
+            x += l.XS
+        x, y = l.XM+3*l.XS, l.YM+3*l.YS
+        for i in range(4):
+            stack = BasicRowStack(x, y, self)
+            s.reserves.append(stack)
+            stack.CARD_XOFFSET, stack.CARD_YOFFSET = 0, 0
+            x += l.XS
+
+        l.defaultStackGroups()
+
+
+    def _shuffleHook(self, cards):
+        return self._shuffleHookMoveToTop(cards,
+                   lambda c: (c.rank == ACE, (c.deck, c.suit)))
+
+
+    def startGame(self):
+        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
+        for i in range(6):
+            self.s.talon.dealRow(rows=self.s.reserves, frames=0)
+        self.startDealSample()
+        for i in range(4):
+            self.s.talon.dealRow(rows=self.s.reserves)
+        self.s.talon.dealCards()
+
+
+    def shallHighlightMatch(self, stack1, card1, stack2, card2):
+        return (card1.color != card2.color and
+                (abs(card1.rank-card2.rank) == 1))
+
 
 # register the game
 registerGame(GameInfo(330, Sultan, "Sultan",
@@ -819,3 +889,5 @@ registerGame(GameInfo(477, CornerSuite, "Corner Suite",
                       GI.GT_2DECK_TYPE, 1, 0, GI.SL_MOSTLY_LUCK))
 registerGame(GameInfo(559, Marshal, "Marshal",
                       GI.GT_2DECK_TYPE, 2, 0, GI.SL_BALANCED))
+registerGame(GameInfo(565, RoyalAids, "Royal Aids",
+                      GI.GT_2DECK_TYPE, 2, UNLIMITED_REDEALS, GI.SL_BALANCED))
diff --git a/pysollib/games/windmill.py b/pysollib/games/windmill.py
index bc267c86..1a49963a 100644
--- a/pysollib/games/windmill.py
+++ b/pysollib/games/windmill.py
@@ -77,6 +77,7 @@ class Windmill(Game):
         ]
     RowStack_Class = Windmill_RowStack
 
+    FOUNDATIONS_LAYOUT = ((1,0.6), (3,0.6), (1,3.4), (3,3.4))
     ROWS_LAYOUT = ((2,0), (2,1), (0,2), (1,2), (3,2), (4,2), (2,3), (2,4))
     FILL_STACK = True
 
@@ -84,12 +85,14 @@ class Windmill(Game):
     # game layout
     #
 
-    def createGame(self):
+    def createGame(self, card_x_space=20):
         # create layout
-        l, s = Layout(self, card_x_space=20), self.s
+        l, s = Layout(self, card_x_space=card_x_space), self.s
 
         # set window
-        self.setSize(7*l.XS+l.XM, 5*l.YS+l.YM+l.YM)
+        max_x = max([i[0] for i in self.FOUNDATIONS_LAYOUT+self.ROWS_LAYOUT])
+        max_y = max([i[1] for i in self.FOUNDATIONS_LAYOUT+self.ROWS_LAYOUT])
+        self.setSize((3+max_x)*l.XS+l.XM, (1+max_y)*l.YS+l.YM+l.YM)
 
         # create stacks
         x = l.XM
@@ -109,7 +112,7 @@ class Windmill(Game):
         fnd_cls = self.Foundation_Classes[0]
         s.foundations.append(fnd_cls(x, y, self))
         fnd_cls = self.Foundation_Classes[1]
-        for d in ((1,0.6), (3,0.6), (1,3.4), (3,3.4)):
+        for d in self.FOUNDATIONS_LAYOUT:
             x, y = x0 + d[0] * l.XS, y0 + d[1] * l.YS
             s.foundations.append(fnd_cls(x, y, self))
 
@@ -158,22 +161,38 @@ class DutchSolitaire_RowStack(UD_RK_RowStack):
 
 class DutchSolitaire(Windmill):
     Foundation_Classes = [
-        StackWrapper(BlackHole_Foundation, suit=ANY_SUIT, mod=13, max_cards=UNLIMITED_CARDS),
-        StackWrapper(BlackHole_Foundation, suit=ANY_SUIT, mod=13, max_cards=UNLIMITED_CARDS),
+        StackWrapper(BlackHole_Foundation, suit=ANY_SUIT, mod=13,
+                     max_cards=UNLIMITED_CARDS, min_cards=1),
+        StackWrapper(BlackHole_Foundation, suit=ANY_SUIT, mod=13,
+                     max_cards=UNLIMITED_CARDS, min_cards=1),
         ]
     RowStack_Class = DutchSolitaire_RowStack
 
-    ##ROWS_LAYOUT = ((2,0), (2,1), (0,2), (1,2), (3,2), (4,2), (2,3), (2,4))
-    ROWS_LAYOUT = ((2,0), (2,1), (1,2), (3,2), (2,3), (2,4))
+    FOUNDATIONS_LAYOUT = ((1,1), (3,1), (1,3), (3,3))
+    ROWS_LAYOUT = ((2,0.5), (-0.5,2), (0.5,2), (3.5,2), (4.5,2), (2,3.5))
     FILL_STACK = False
 
+    def createGame(self):
+        Windmill.createGame(self, card_x_space=10)
+
     def _shuffleHook(self, cards):
-        return cards
+        # move 5 Aces to top of the Talon (i.e. first cards to be dealt)
+        def select_cards(c):
+            if c.rank == ACE:
+                if c.suit in (0, 1):
+                    return True, c.suit
+                if c.suit == 3 and c.deck == 0:
+                    return True, c.suit
+            return False, None
+        return self._shuffleHookMoveToTop(cards, select_cards)
 
     def startGame(self):
+        self.s.talon.dealRow(rows=self.s.foundations, frames=0)
+        for i in range(8):
+            self.s.talon.dealRow(frames=0)
         self.startDealSample()
-        #self.s.talon.dealRow(rows=(self.s.foundations[0],))
-        #self.s.talon.dealRow()
+        self.s.talon.dealRow()
+        self.s.talon.dealRow()
         self.s.talon.dealCards()          # deal first card to WasteStack
 
     def getAutoStacks(self, event=None):
diff --git a/pysollib/games/yukon.py b/pysollib/games/yukon.py
index 6ccb67fe..a5ed6f67 100644
--- a/pysollib/games/yukon.py
+++ b/pysollib/games/yukon.py
@@ -140,7 +140,7 @@ class Moosehide_RowStack(Yukon_AC_RowStack):
     def _isSequence(self, c1, c2):
         return (c1.suit != c2.suit and c1.rank == c2.rank+1)
     def getHelp(self):
-        return _('Row. Build down in any suit but the same, can move any face-up cards regardless of sequence.')
+        return _('Tableau. Build down in any suit but the same, can move any face-up cards regardless of sequence.')
 
 class Moosehide(Yukon):
     RowStack_Class = StackWrapper(Moosehide_RowStack, base_rank=KING)
@@ -199,7 +199,7 @@ class Alaska_RowStack(Yukon_SS_RowStack):
                 ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank or
                  (c2.rank + self.cap.dir) % self.cap.mod == c1.rank))
     def getHelp(self):
-        return _('Row. Build up or down by suit, can move any face-up cards regardless of sequence.')
+        return _('Tableau. Build up or down by suit, can move any face-up cards regardless of sequence.')
 
 
 class Alaska(RussianSolitaire):
@@ -216,7 +216,7 @@ class Roslin_RowStack(Yukon_AC_RowStack):
                 ((c1.rank + self.cap.dir) % self.cap.mod == c2.rank or
                  (c2.rank + self.cap.dir) % self.cap.mod == c1.rank))
     def getHelp(self):
-        return _('Row. Build up or down by alternate color, can move any face-up cards regardless of sequence.')
+        return _('Tableau. Build up or down by alternate color, can move any face-up cards regardless of sequence.')
 
 
 class Roslin(Yukon):
diff --git a/pysollib/layout.py b/pysollib/layout.py
index aa3e02ce..119989d4 100644
--- a/pysollib/layout.py
+++ b/pysollib/layout.py
@@ -500,7 +500,7 @@ class Layout:
         if rows < maxrows: x += (maxrows-rows) * XS/2
         ##y += YM * (3 - foundrows)
         y += text_height
-        self.setRegion(self.s.rows, (-999, y - YM / 2, 999999, 999999))
+        self.setRegion(self.s.rows, (-999, y-CH/2, 999999, 999999))
         for i in range(rows):
             self.s.rows.append(S(x, y))
             x = x + XS
diff --git a/pysollib/stack.py b/pysollib/stack.py
index 3da4d72a..6ac0ae51 100644
--- a/pysollib/stack.py
+++ b/pysollib/stack.py
@@ -1556,8 +1556,8 @@ class TalonStack(Stack,
         ##round = _('Round #%d.') % self.round
         return _('Talon.')+' '+nredeals ##+' '+round
 
-    def getBaseCard(self):
-        return self._getBaseCard()
+    #def getBaseCard(self):
+    #    return self._getBaseCard()
 
 
 # A single click deals one card to each of the RowStacks.
@@ -1777,6 +1777,9 @@ class AbstractFoundationStack(OpenStack):
         if len(self.cards) == self.cap.max_cards:
             self.game.closeStackMove(self)
 
+    def getHelp(self):
+        return _('Foundation.')
+
 
 # A SameSuit_FoundationStack is the typical Foundation stack.
 # It builds up in rank and suit.
@@ -1874,7 +1877,7 @@ class BasicRowStack(OpenStack):
 
     def getHelp(self):
         if self.cap.max_accept == 0:
-            return _('Row. No building.')
+            return _('Tableau. No building.')
         return ''
 
     #def getBaseCard(self):
@@ -1904,9 +1907,9 @@ class AC_RowStack(SequenceRowStack):
     def _isSequence(self, cards):
         return isAlternateColorSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up by alternate color.')
-        elif self.cap.dir < 0: return _('Row. Build down by alternate color.')
-        else:                  return _('Row. Build by same rank.')
+        if self.cap.dir > 0:   return _('Tableau. Build up by alternate color.')
+        elif self.cap.dir < 0: return _('Tableau. Build down by alternate color.')
+        else:                  return _('Tableau. Build by same rank.')
 
 # A SameColor_RowStack builds down by rank and same color.
 # e.g. Klondike
@@ -1914,27 +1917,27 @@ class SC_RowStack(SequenceRowStack):
     def _isSequence(self, cards):
         return isSameColorSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up by color.')
-        elif self.cap.dir < 0: return _('Row. Build down by color.')
-        else:                  return _('Row. Build by same rank.')
+        if self.cap.dir > 0:   return _('Tableau. Build up by color.')
+        elif self.cap.dir < 0: return _('Tableau. Build down by color.')
+        else:                  return _('Tableau. Build by same rank.')
 
 # A SameSuit_RowStack builds down by rank and suit.
 class SS_RowStack(SequenceRowStack):
     def _isSequence(self, cards):
         return isSameSuitSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up by suit.')
-        elif self.cap.dir < 0: return _('Row. Build down by suit.')
-        else:                  return _('Row. Build by same rank.')
+        if self.cap.dir > 0:   return _('Tableau. Build up by suit.')
+        elif self.cap.dir < 0: return _('Tableau. Build down by suit.')
+        else:                  return _('Tableau. Build by same rank.')
 
 # A Rank_RowStack builds down by rank ignoring suit.
 class RK_RowStack(SequenceRowStack):
     def _isSequence(self, cards):
         return isRankSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up regardless of suit.')
-        elif self.cap.dir < 0: return _('Row. Build down regardless of suit.')
-        else:                  return _('Row. Build by same rank.')
+        if self.cap.dir > 0:   return _('Tableau. Build up regardless of suit.')
+        elif self.cap.dir < 0: return _('Tableau. Build down regardless of suit.')
+        else:                  return _('Tableau. Build by same rank.')
 
 # A Freecell_AlternateColor_RowStack
 class FreeCell_AC_RowStack(AC_RowStack):
@@ -1960,9 +1963,9 @@ class Spider_SS_RowStack(SS_RowStack):
     def _isAcceptableSequence(self, cards):
         return isRankSequence(cards, self.cap.mod, self.cap.dir)
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up regardless of suit.')
-        elif self.cap.dir < 0: return _('Row. Build down regardless of suit.')
-        else:                  return _('Row. Build by same rank.')
+        if self.cap.dir > 0:   return _('Tableau. Build up regardless of suit.')
+        elif self.cap.dir < 0: return _('Tableau. Build down regardless of suit.')
+        else:                  return _('Tableau. Build by same rank.')
 
 # A Yukon_AlternateColor_RowStack builds down by rank and alternate color,
 # but can move any face-up cards regardless of sequence.
@@ -1983,10 +1986,12 @@ class Yukon_AC_RowStack(BasicRowStack):
         return 1
 
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up by alternate color, can move any face-up cards regardless of sequence.')
-        elif self.cap.dir < 0: return _('Row. Build down by alternate color, can move any face-up cards regardless of sequence.')
-        else:                  return _('Row. Build by same rank, can move any face-up cards regardless of sequence.')
+        if self.cap.dir > 0:   return _('Tableau. Build up by alternate color, can move any face-up cards regardless of sequence.')
+        elif self.cap.dir < 0: return _('Tableau. Build down by alternate color, can move any face-up cards regardless of sequence.')
+        else:                  return _('Tableau. Build by same rank, can move any face-up cards regardless of sequence.')
 
+    def getBaseCard(self):
+        return self._getBaseCard()
 
 # A Yukon_SameSuit_RowStack builds down by rank and suit,
 # but can move any face-up cards regardless of sequence.
@@ -1994,9 +1999,9 @@ class Yukon_SS_RowStack(Yukon_AC_RowStack):
     def _isSequence(self, c1, c2):
         return (c1.rank + self.cap.dir) % self.cap.mod == c2.rank and c1.suit == c2.suit
     def getHelp(self):
-        if self.cap.dir > 0:   return _('Row. Build up by suit, can move any face-up cards regardless of sequence.')
-        elif self.cap.dir < 0: return _('Row. Build down by suit, can move any face-up cards regardless of sequence.')
-        else:                  return _('Row. Build by same rank, can move any face-up cards regardless of sequence.')
+        if self.cap.dir > 0:   return _('Tableau. Build up by suit, can move any face-up cards regardless of sequence.')
+        elif self.cap.dir < 0: return _('Tableau. Build down by suit, can move any face-up cards regardless of sequence.')
+        else:                  return _('Tableau. Build by same rank, can move any face-up cards regardless of sequence.')
 
 #
 # King-versions of some of the above stacks: they accepts only Kings or
@@ -2028,7 +2033,7 @@ class UD_SC_RowStack(SequenceRowStack):
         return (isSameColorSequence(cards, self.cap.mod, 1) or
                 isSameColorSequence(cards, self.cap.mod, -1))
     def getHelp(self):
-        return _('Row. Build up or down by color.')
+        return _('Tableau. Build up or down by color.')
 
 # up or down by alternate color
 class UD_AC_RowStack(SequenceRowStack):
@@ -2039,7 +2044,7 @@ class UD_AC_RowStack(SequenceRowStack):
         return (isAlternateColorSequence(cards, self.cap.mod, 1) or
                 isAlternateColorSequence(cards, self.cap.mod, -1))
     def getHelp(self):
-        return _('Row. Build up or down by alternate color.')
+        return _('Tableau. Build up or down by alternate color.')
 
 # up or down by suit
 class UD_SS_RowStack(SequenceRowStack):
@@ -2050,7 +2055,7 @@ class UD_SS_RowStack(SequenceRowStack):
         return (isSameSuitSequence(cards, self.cap.mod, 1) or
                 isSameSuitSequence(cards, self.cap.mod, -1))
     def getHelp(self):
-        return _('Row. Build up or down by suit.')
+        return _('Tableau. Build up or down by suit.')
 
 # up or down by rank ignoring suit
 class UD_RK_RowStack(SequenceRowStack):
@@ -2061,7 +2066,7 @@ class UD_RK_RowStack(SequenceRowStack):
         return (isRankSequence(cards, self.cap.mod, 1) or
                 isRankSequence(cards, self.cap.mod, -1))
     def getHelp(self):
-        return _('Row. Build up or down regardless of suit.')
+        return _('Tableau. Build up or down regardless of suit.')
 
 
 
diff --git a/pysollib/tk/menubar.py b/pysollib/tk/menubar.py
index 74c1a2b5..5e96ebca 100644
--- a/pysollib/tk/menubar.py
+++ b/pysollib/tk/menubar.py
@@ -37,7 +37,7 @@
 __all__ = ['PysolMenubar']
 
 # imports
-import math, os, re, sys, types
+import math, os, re, types
 import Tkinter, tkColorChooser, tkFileDialog
 
 # PySol imports
@@ -318,6 +318,8 @@ class PysolMenubar(PysolMenubarActions):
         menu.add_separator()
         menu.add_command(label=n_("&Demo"), command=self.mDemo, accelerator=m+"D")
         menu.add_command(label=n_("Demo (&all games)"), command=self.mMixedDemo)
+        menu.add_separator()
+        menu.add_command(label=n_("Show descriptions od piles"), command=self.mStackDesk, accelerator="F2")
         menu = MfxMenu(self.__menubar, label=n_("&Options"))
         menu.add_command(label=n_("&Player options..."), command=self.mOptPlayerOptions)
         submenu = MfxMenu(menu, label=n_("&Automatic play"))
@@ -425,8 +427,8 @@ class PysolMenubar(PysolMenubarActions):
         self._bindKey("",   "Print", self.mScreenshot)
         self._bindKey(ctrl, "u", self.mPlayNextMusic)   # undocumented
         self._bindKey("",   "p", self.mPause)
-        self._bindKey("",   "Pause", self.mPause)
-        self._bindKey("",   "Escape", self.mIconify)
+        self._bindKey("",   "Pause", self.mPause)       # undocumented
+        self._bindKey("",   "Escape", self.mIconify)    # undocumented
         # ASD and LKJ
         self._bindKey("",   "a", self.mDrop)
         self._bindKey(ctrl, "a", self.mDrop1)
@@ -436,6 +438,8 @@ class PysolMenubar(PysolMenubarActions):
         self._bindKey(ctrl, "l", self.mDrop1)
         self._bindKey("",   "k", self.mUndo)
         self._bindKey("",   "j", self.mDeal)
+
+        self._bindKey("",   "F2", self.mStackDesk)
         #
         self._bindKey("",   "slash", self.mGameInfo) # undocumented, devel
 
@@ -1053,5 +1057,14 @@ class PysolMenubar(PysolMenubarActions):
         self.app.toolbar.config(w, v)
         self.top.update_idletasks()
 
+    #
+    # stacks descriptions
+    #
 
+    def mStackDesk(self, *event):
+        if self.game.stackdesc_list:
+            self.game.deleteStackDesc()
+        else:
+            if self._cancelDrag(break_pause=True): return
+            self.game.showStackDesc()
 
diff --git a/pysollib/tk/tkwidget.py b/pysollib/tk/tkwidget.py
index b6fd0086..5c8503e3 100644
--- a/pysollib/tk/tkwidget.py
+++ b/pysollib/tk/tkwidget.py
@@ -38,6 +38,7 @@ __all__ = ['MfxMessageDialog',
            'MfxSimpleEntry',
            'MfxTooltip',
            'MfxScrolledCanvas',
+           'StackDesc',
            ]
 
 # imports
@@ -669,3 +670,43 @@ class MfxScrolledCanvas:
         return self._yview('moveto', 1)
 
 
+# /***********************************************************************
+# //
+# ************************************************************************/
+
+class StackDesc:
+
+    def __init__(self, game, stack):
+        self.game = game
+        self.stack = stack
+        self.canvas = game.canvas
+
+        font = game.app.getFont('canvas_small')
+        ##print self.app.cardset.CARDW, self.app.images.CARDW
+        cardw = game.app.images.CARDW
+        x, y = stack.x+cardw/2, stack.y
+        text = stack.getHelp()+'\n'+stack.getBaseCard()
+        text = text.strip()
+        if text:
+            frame = Tkinter.Frame(self.canvas, highlightthickness=1,
+                                  highlightbackground='black')
+            label = Tkinter.Message(frame, font=font, text=text, width=cardw-8,
+                                    fg='#000000', bg='#ffffe0')
+            label.pack()
+            self.label = label
+            self.id = self.canvas.create_window(x, y, window=frame, anchor='n')
+            self.binding = label.bind('<ButtonPress>', self.buttonPressEvent)
+        else:
+            self.id = None
+
+    def buttonPressEvent(self, *event):
+        self.game.deleteStackDesc()
+
+    def delete(self):
+        if self.id:
+            self.canvas.delete(self.id)
+            self.label.unbind('<ButtonPress>', self.binding)
+
+
+
+